add support for Oracle SQL Dialect
Bonus points go to Lars Klonowski for sharing his experience with Oracle
migration on repo-discuss ML:
https://groups.google.com/forum/#!topic/repo-discuss/7LcT1CwEroo
Change-Id: I029ee6a76fcf3eae3d6b126a0ab4fd6c0e69601f
diff --git a/README_ORACLE b/README_ORACLE
new file mode 100644
index 0000000..45f66d1
--- /dev/null
+++ b/README_ORACLE
@@ -0,0 +1,13 @@
+Oracle has an issue with availability of jdbc driver in public maven repository.
+
+To test DialectOracle ojdbc6.jar is needed.
+
+1. Obtain the driver either from your local installation or download it from Oracle technet site
+2. Install the driver:
+
+mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc6 \
+ -Dversion=11.2.0.3 -Dpackaging=jar -Dfile=<path to ojdbc6.jar> -DgeneratePom=true
+
+3. Use specific maven profile to run Oracle related unit tests:
+
+mvn package -P oracle
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 74e6fea..78321b8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -252,6 +252,26 @@
</license>
</licenses>
+ <profiles>
+ <profile>
+ <id>oracle</id>
+ <dependencies>
+ <dependency>
+ <groupId>com.oracle</groupId>
+ <artifactId>ojdbc6</artifactId>
+ <version>11.2.0.3</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>skip-proprietary-databases</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ </profile>
+ </profiles>
+
<build>
<plugins>
<plugin>
@@ -320,7 +340,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.0</version>
+ <version>4.11</version>
<scope>test</scope>
</dependency>
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectOracle.java b/src/main/java/com/google/gwtorm/schema/sql/DialectOracle.java
new file mode 100644
index 0000000..7c7e340
--- /dev/null
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectOracle.java
@@ -0,0 +1,123 @@
+// Copyright 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import com.google.gwtorm.schema.ColumnModel;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.StatementExecutor;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.HashSet;
+import java.util.Set;
+
+public class DialectOracle extends SqlDialect {
+
+ public DialectOracle() {
+ typeNames.put(Types.BIGINT, "NUMBER(19,0)");
+ typeNames.put(Types.LONGVARCHAR, "CLOB");
+ }
+
+ @Override
+ public boolean handles(String url, Connection c) throws SQLException {
+ return url.startsWith("jdbc:oracle:");
+ }
+
+ @Override
+ public boolean canDetermineIndividualBatchUpdateCounts() {
+ return false;
+ }
+ @Override
+ public boolean canDetermineTotalBatchUpdateCount() {
+ return false;
+ }
+
+ @Override
+ public Set<String> listTables(final Connection db) throws SQLException {
+ Statement s = db.createStatement();
+ try {
+ ResultSet rs = s.executeQuery("SELECT table_name FROM user_tables");
+ try {
+ HashSet<String> tables = new HashSet<String>();
+ while (rs.next()) {
+ tables.add(rs.getString(1).toLowerCase());
+ }
+ return tables;
+ } finally {
+ rs.close();
+ }
+ } finally {
+ s.close();
+ }
+ }
+
+ @Override
+ public Set<String> listSequences(Connection db) throws SQLException {
+ Statement s = db.createStatement();
+ try {
+ ResultSet rs = s.executeQuery("SELECT sequence_name FROM user_sequences");
+ try {
+ HashSet<String> sequences = new HashSet<String>();
+ while (rs.next()) {
+ sequences.add(rs.getString(1).toLowerCase());
+ }
+ return sequences;
+ } finally {
+ rs.close();
+ }
+ } finally {
+ s.close();
+ }
+ }
+
+ @Override
+ public void addColumn(StatementExecutor stmt, String tableName,
+ ColumnModel col) throws OrmException {
+ final StringBuilder r = new StringBuilder();
+ r.append("ALTER TABLE ");
+ r.append(tableName);
+ r.append(" ADD ");
+ r.append(col.getColumnName());
+ r.append(" ");
+ r.append(getSqlTypeInfo(col).getSqlType(col, this));
+ stmt.execute(r.toString());
+ }
+
+ @Override
+ public void renameColumn(StatementExecutor e, String tableName,
+ String fromColumn, ColumnModel col) throws OrmException {
+ StringBuffer sb = new StringBuffer();
+ sb.append("ALTER TABLE ");
+ sb.append(tableName);
+ sb.append(" RENAME COLUMN ");
+ sb.append(fromColumn);
+ sb.append(" TO ");
+ sb.append(col.getColumnName());
+ e.execute(sb.toString());
+ }
+
+ @Override
+ public String getNextSequenceValueSql(String seqname) {
+ return "SELECT " + seqname + ".nextval FROM dual";
+ }
+
+ @Override
+ public boolean selectHasLimit() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java b/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
index ced8ad1..f839384 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
@@ -43,6 +43,7 @@
DIALECTS.add(new DialectH2());
DIALECTS.add(new DialectPostgreSQL());
DIALECTS.add(new DialectMySQL());
+ DIALECTS.add(new DialectOracle());
}
public static void register(SqlDialect dialect) {
diff --git a/src/test/java/com/google/gwtorm/schema/sql/DialectOracleSQLTest.java b/src/test/java/com/google/gwtorm/schema/sql/DialectOracleSQLTest.java
new file mode 100644
index 0000000..21a0ac6
--- /dev/null
+++ b/src/test/java/com/google/gwtorm/schema/sql/DialectOracleSQLTest.java
@@ -0,0 +1,210 @@
+// Copyright 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+
+import com.google.gwtorm.data.Address;
+import com.google.gwtorm.data.Person;
+import com.google.gwtorm.data.PhoneBookDb;
+import com.google.gwtorm.data.PhoneBookDb2;
+import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.jdbc.JdbcExecutor;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.jdbc.SimpleDataSource;
+import com.google.gwtorm.server.OrmException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.Set;
+
+public class DialectOracleSQLTest {
+ private final static String ORACLE_DRIVER = "oracle.jdbc.driver.OracleDriver";
+ private Connection db;
+ private JdbcExecutor executor;
+ private SqlDialect dialect;
+ private Database<PhoneBookDb> phoneBook;
+ private Database<PhoneBookDb2> phoneBook2;
+
+ @Before
+ public void setUp() throws Exception {
+ try {
+ Class.forName(ORACLE_DRIVER);
+ } catch (Exception e) {
+ assumeNoException(e);
+ }
+
+ final String sid = "xe"; // Oracle instance name
+ final String user = "gwtorm"; // Oracle schema=user name=database
+ final String pass = "gwtorm";
+
+ db = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:"
+ + sid, user, pass);
+ executor = new JdbcExecutor(db);
+ dialect = new DialectOracle().refine(db);
+
+ final Properties p = new Properties();
+ p.setProperty("driver", ORACLE_DRIVER);
+ p.setProperty("url", db.getMetaData().getURL());
+ p.setProperty("user", user);
+ p.setProperty("password", pass);
+ phoneBook =
+ new Database<PhoneBookDb>(new SimpleDataSource(p), PhoneBookDb.class);
+ phoneBook2 =
+ new Database<PhoneBookDb2>(new SimpleDataSource(p), PhoneBookDb2.class);
+
+ drop("SEQUENCE address_id");
+ drop("SEQUENCE cnt");
+
+ drop("TABLE addresses");
+ drop("TABLE foo");
+ drop("TABLE bar");
+ drop("TABLE people");
+ }
+
+ private void drop(String drop) {
+ try {
+ execute("DROP " + drop);
+ } catch (OrmException e) {
+ }
+ }
+
+ @After
+ public void tearDown() {
+ if (executor != null) {
+ executor.close();
+ }
+ executor = null;
+
+ if (db != null) {
+ try {
+ db.close();
+ } catch (SQLException e) {
+ throw new RuntimeException("Cannot close database", e);
+ }
+ }
+ db = null;
+ }
+
+ private void execute(final String sql) throws OrmException {
+ executor.execute(sql);
+ }
+
+ @Test
+ public void testListSequences() throws OrmException, SQLException {
+ assertTrue(dialect.listSequences(db).isEmpty());
+
+ execute("CREATE SEQUENCE cnt");
+ execute("CREATE TABLE foo (cnt INT)");
+
+ Set<String> s = dialect.listSequences(db);
+ assertEquals(1, s.size());
+ assertTrue(s.contains("cnt"));
+ assertFalse(s.contains("foo"));
+ }
+
+ @Test
+ public void testListTables() throws OrmException, SQLException {
+ assertTrue(dialect.listTables(db).isEmpty());
+
+ execute("CREATE SEQUENCE cnt");
+ execute("CREATE TABLE foo (cnt INT)");
+
+ Set<String> s = dialect.listTables(db);
+ assertEquals(1, s.size());
+ assertFalse(s.contains("cnt"));
+ assertTrue(s.contains("foo"));
+ }
+
+ @Test
+ public void testUpgradeSchema() throws SQLException, OrmException {
+ final PhoneBookDb p = phoneBook.open();
+ try {
+ p.updateSchema(executor);
+
+ execute("CREATE SEQUENCE cnt");
+ execute("CREATE TABLE foo (cnt INT)");
+
+ execute("ALTER TABLE people ADD fake_name VARCHAR(20)");
+ execute("ALTER TABLE people DROP COLUMN registered");
+ execute("DROP TABLE addresses");
+ execute("DROP SEQUENCE address_id");
+
+ Set<String> sequences, tables;
+
+ p.updateSchema(executor);
+ sequences = dialect.listSequences(db);
+ tables = dialect.listTables(db);
+ assertTrue(sequences.contains("cnt"));
+ assertTrue(tables.contains("foo"));
+
+ assertTrue(sequences.contains("address_id"));
+ assertTrue(tables.contains("addresses"));
+
+ p.pruneSchema(executor);
+ sequences = dialect.listSequences(db);
+ tables = dialect.listTables(db);
+ assertFalse(sequences.contains("cnt"));
+ assertFalse(tables.contains("foo"));
+
+ final Person.Key pk = new Person.Key("Bob");
+ final Person bob = new Person(pk, p.nextAddressId());
+ p.people().insert(Collections.singleton(bob));
+
+ final Address addr =
+ new Address(new Address.Key(pk, "home"), "some place");
+ p.addresses().insert(Collections.singleton(addr));
+ } finally {
+ p.close();
+ }
+
+ final PhoneBookDb2 p2 = phoneBook2.open();
+ try {
+ ((JdbcSchema) p2).renameField(executor, "people", "registered",
+ "isRegistered");
+ } finally {
+ p2.close();
+ }
+ }
+
+ @Test
+ public void testRenameTable() throws SQLException, OrmException {
+ assertTrue(dialect.listTables(db).isEmpty());
+ execute("CREATE TABLE foo (cnt INT)");
+ Set<String> s = dialect.listTables(db);
+ assertEquals(1, s.size());
+ assertTrue(s.contains("foo"));
+ final PhoneBookDb p = phoneBook.open();
+ try {
+ ((JdbcSchema) p).renameTable(executor, "foo", "bar");
+ } finally {
+ p.close();
+ }
+ s = dialect.listTables(db);
+ assertTrue(s.contains("bar"));
+ assertFalse(s.contains("for"));
+
+ }
+}