]> source.dussan.org Git - iciql.git/commitdiff
Added support for HSQL database. Revised dialects some more.
authorJames Moger <james.moger@gmail.com>
Thu, 11 Aug 2011 18:04:01 +0000 (14:04 -0400)
committerJames Moger <james.moger@gmail.com>
Thu, 11 Aug 2011 18:04:01 +0000 (14:04 -0400)
Moved CREATE TABLE and CREATE INDEX statement generation to the dialect.
Added DECIMAL(length, scale) support.
Improved automatic dialect detection.
Unspecified length string is now CLOB instead of TEXT.
Boolean now maps to BOOLEAN instead of BIT.
Expressions on unmapped fields will throw an IciqlException.
Improved exception reporting.

26 files changed:
.classpath
README.markdown
docs/00_index.mkd
docs/01_model_classes.mkd
docs/02_table_versioning.mkd
docs/05_releases.mkd
src/com/iciql/Db.java
src/com/iciql/DbVersion.java
src/com/iciql/Define.java
src/com/iciql/Iciql.java
src/com/iciql/IciqlException.java
src/com/iciql/ModelUtils.java
src/com/iciql/Query.java
src/com/iciql/QueryBetween.java
src/com/iciql/SQLDialect.java
src/com/iciql/SQLDialectDefault.java
src/com/iciql/SQLDialectH2.java
src/com/iciql/SQLDialectHSQL.java [new file with mode: 0644]
src/com/iciql/SQLDialectMySQL.java
src/com/iciql/SQLStatement.java
src/com/iciql/TableDefinition.java
src/com/iciql/TableInspector.java
src/com/iciql/ValidationRemark.java
src/com/iciql/build/Build.java
src/com/iciql/util/Utils.java
tests/com/iciql/test/StatementLoggerTest.java [deleted file]

index 7e657623fd0964bf56bf248b5041f48e234f782a..c52fc50efde38bba14c6972a4534850810f82147 100644 (file)
@@ -29,5 +29,6 @@
                        <attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/slf4j-api-1.6.1-javadoc.jar!/"/>\r
                </attributes>\r
        </classpathentry>\r
+       <classpathentry kind="lib" path="ext/hsqldb-2.2.4.jar"/>\r
        <classpathentry kind="output" path="bin"/>\r
 </classpath>\r
index 416bba2f4a5241ff10ae66b239fa477919557fd0..dc8b761dbed13291edec723a83f1971421d2a604 100644 (file)
@@ -17,8 +17,8 @@ iciql **is not**...
 \r
 Supported Databases\r
 -------\r
-- [H2](http://h2database.com)\r
-- [MySQL](http://mysql.com)\r
+- [H2 1.3+](http://h2database.com)\r
+- [HSQLDB 2.2+](http://hsqldb.org)\r
 - Support for others is planned and should only require creating a simple "dialect" class.\r
 \r
 License\r
index 70048e7cab400d6c57ddc117770af867165174b5..1377db1c528773a2e5856f4957e75db897352e18 100644 (file)
@@ -36,7 +36,7 @@ select * from products
 </table>\r
 \r
 ### Supported Databases\r
-[H2](http://h2database.com), [MySQL](http://mysql.com)\r
+[H2 1.3+](http://h2database.com), [HSQLDB 2.2+](http://hsqldb.org)\r
 \r
 Support for others is planned and should only require creating a simple "dialect" class.\r
 \r
index bdddd80b943537eb75b10c8f00ed39858ba113ac..658ff2b87d645f8abbef0ebb4814410743e33aab 100644 (file)
@@ -17,7 +17,7 @@ The following data types can be used for all iciql expressions.
 <table>\r
 <tr><th colspan="2">All Databases</th></tr>\r
 <tr><td>java.lang.String</td>\r
-<td>VARCHAR *(length > 0)* or TEXT *(length == 0)*</td></tr>\r
+<td>VARCHAR *(length > 0)* or CLOB *(length == 0)*</td></tr>\r
        \r
 <tr><td>java.lang.Boolean</td>\r
 <td>BIT</td></tr>\r
@@ -41,7 +41,7 @@ The following data types can be used for all iciql expressions.
 <td>DOUBLE</td></tr>\r
        \r
 <tr><td>java.math.BigDecimal</td>\r
-<td>DECIMAL</td></tr>\r
+<td>DECIMAL *(length == 0)* or DECIMAL(length,scale) *(length > 0)*</td></tr>\r
        \r
 <tr><td>java.sql.Date</td>\r
 <td>DATE</td></tr>\r
@@ -56,7 +56,7 @@ The following data types can be used for all iciql expressions.
 <td>TIMESTAMP</td></tr>\r
 \r
 <tr><td>java.lang.Enum.name()<br/>*default type*</td>\r
-<td>VARCHAR *(length > 0)* or TEXT *(length == 0)*<br/>*EnumType.NAME*</td></tr>\r
+<td>VARCHAR *(length > 0)* or CLOB *(length == 0)*<br/>*EnumType.NAME*</td></tr>\r
 \r
 <tr><td>java.lang.Enum.ordinal()</td>\r
 <td>INT<br/>*EnumType.ORDINAL*</td></tr>\r
index 12cdf6cf6e3f41b1d9c1fb16e8e5ee6aba501c25..29942fed12299603fadd496cd8906df53d464201 100644 (file)
@@ -8,22 +8,22 @@ AND/OR<br/>
 Your `com.iciql.DbUpgrader` implementation must specify the `IQVersion(version)` annotation\r
 \r
 ### How does it work?\r
-If you choose to use versioning, iciql will maintain a table within your database named *_iq_versions* which is defined as:\r
+If you choose to use versioning, iciql will maintain a table within your database named *iq_versions* which is defined as:\r
 \r
-    CREATE TABLE _IQ_VERSIONS(SCHEMANAME VARCHAR(255) NOT NULL, TABLENAME VARCHAR(255) NOT NULL, VERSION INT NOT NULL)\r
+    CREATE TABLE IQ_VERSIONS(SCHEMANAME VARCHAR(255) NOT NULL, TABLENAME VARCHAR(255) NOT NULL, VERSION INT NOT NULL)\r
     \r
 This database table is automatically created if and only if at least one of your model classes specifies a *version* > 0.\r
 \r
-When you generate a statement, iciql will compare the annotated version field of your model class to its last known value in the *_iq_versions* table.  If *_iq_versions* lags behind the model annotation, iciql will immediately call the registered `com.iciql.DbUpgrader` implementation before generating and executing the current statement.\r
+When you generate a statement, iciql will compare the annotated version field of your model class to its last known value in the *iq_versions* table.  If *iq_versions* lags behind the model annotation, iciql will immediately call the registered `com.iciql.DbUpgrader` implementation before generating and executing the current statement.\r
 \r
 When an upgrade scenario is identified, the current version and the annotated version information is passed to either:\r
 \r
 - `DbUpgrader.upgradeDatabase(db, fromVersion, toVersion)`\r
 - `DbUpgrader.upgradeTable(db, schema, table, fromVersion, toVersion)`\r
 \r
-both of which allow for non-linear upgrades.  If the upgrade method call is successful and returns *true*, iciql will update the *_iq_versions* table with the annotated version number.\r
+both of which allow for non-linear upgrades.  If the upgrade method call is successful and returns *true*, iciql will update the *iq_versions* table with the annotated version number.\r
 \r
 The actual upgrade procedure is beyond the scope of iciql and is your responsibility to implement.  This is simply a mechanism to automatically identify when an upgrade is necessary.\r
 \r
 **NOTE:**<br/>\r
-The database entry of the *_iq_versions* table is specified as SCHEMANAME='' and TABLENAME=''.
\ No newline at end of file
+The database entry of the *iq_versions* table is specified as SCHEMANAME='' and TABLENAME=''.
\ No newline at end of file
index 7fb0113797ecbf62698e64e5f6b78402b76cfb55..c9990625067773b0fd61777f477fffc98c36c95e 100644 (file)
@@ -7,9 +7,17 @@
 **%VERSION%** ([zip](http://code.google.com/p/iciql/downloads/detail?name=%ZIP%)|[jar](http://code.google.com/p/iciql/downloads/detail?name=%JAR%)) &nbsp; *released %BUILDDATE%*\r
 \r
 - api change release (API v4)\r
+- DECIMAL(length, scale) support\r
+- unspecified length String fields are now CLOB instead of TEXT.  dialects can intercept this and convert to another type. e.g. MySQL dialect can change CLOB to TEXT.\r
+- Boolean now maps to BOOLEAN instead of BIT\r
+- expressions on unmapped fields will throw an IciqlException\r
+- improved exception reporting\r
 - moved dialects back to main package\r
-- refined dialect loading for pooled connections\r
-- added a MySQL dialect\r
+- improved automatic dialect determination on pooled connections\r
+- moved create table and create index statement generation into dialects\r
+- added HSQL dialect.  HSQL fails 4 unit tests, 2 of which are unimplemented merge, 1 has been filed as a bug in HSQL.\r
+- added MySQL dialect.  Untested.\r
+- renamed <b>_ iq_versions</b> table to *iq_versions* since leading _ character is troublesome for some databases.\r
 - @IQColumn(allowNull=true) -> @IQColumn(nullable=true)\r
 - All columns are assumed NULLABLE unless explicitly set *@IQColumn(nullable = false)*\r
 - allow using objects to assign default values<br/>\r
index c7801c22e696193ac95c9e25286ae1b99fd0c230..a8c0b127a3ed597f6ae792c7e0bc45d1a07a3832 100644 (file)
@@ -73,6 +73,7 @@ public class Db {
                // 2. DatabaseMetaData.getDatabaseProductName()\r
                DIALECTS.put("H2", SQLDialectH2.class);\r
                DIALECTS.put("MySQL", SQLDialectMySQL.class);\r
+               DIALECTS.put("HSQL Database Engine", SQLDialectHSQL.class);\r
        }\r
 \r
        private Db(Connection conn) {\r
@@ -83,7 +84,7 @@ public class Db {
                        data = conn.getMetaData();\r
                        databaseName = data.getDatabaseProductName();\r
                } catch (SQLException s) {\r
-                       throw new IciqlException("Failed to retrieve database metadata!", s);\r
+                       throw new IciqlException(s, "failed to retrieve database metadata!");\r
                }\r
                dialect = getDialect(databaseName, conn.getClass().getName());\r
                dialect.configureDialect(databaseName, data);\r
@@ -140,7 +141,7 @@ public class Db {
                        Connection conn = JdbcUtils.getConnection(null, url, user, password);\r
                        return new Db(conn);\r
                } catch (SQLException e) {\r
-                       throw convert(e);\r
+                       throw new IciqlException(e);\r
                }\r
        }\r
 \r
@@ -156,7 +157,7 @@ public class Db {
                try {\r
                        return new Db(ds.getConnection());\r
                } catch (SQLException e) {\r
-                       throw convert(e);\r
+                       throw new IciqlException(e);\r
                }\r
        }\r
 \r
@@ -172,14 +173,10 @@ public class Db {
                        Connection conn = JdbcUtils.getConnection(null, url, prop);\r
                        return new Db(conn);\r
                } catch (SQLException e) {\r
-                       throw convert(e);\r
+                       throw new IciqlException(e);\r
                }\r
        }\r
 \r
-       private static Error convert(Exception e) {\r
-               return new Error(e);\r
-       }\r
-\r
        public <T> void insert(T t) {\r
                Class<?> clazz = t.getClass();\r
                define(clazz).createTableIfRequired(this).insert(this, t, false);\r
@@ -191,18 +188,15 @@ public class Db {
        }\r
 \r
        /**\r
-        * Merge usually INSERTS if the record does not exist or UPDATES the record\r
-        * if it does exist. Not all databases support MERGE and the syntax varies\r
-        * with the database.\r
+        * Merge INSERTS if the record does not exist or UPDATES the record if it\r
+        * does exist. Not all databases support MERGE and the syntax varies with\r
+        * the database.\r
         * \r
         * If the dialect does not support merge an IciqlException will be thrown.\r
         * \r
         * @param t\r
         */\r
        public <T> void merge(T t) {\r
-               if (!getDialect().supportsMerge()) {\r
-                       throw new IciqlException("Merge is not supported by this SQL dialect");\r
-               }\r
                Class<?> clazz = t.getClass();\r
                define(clazz).createTableIfRequired(this).merge(this, t);\r
        }\r
@@ -383,13 +377,14 @@ public class Db {
        }\r
 \r
        PreparedStatement prepare(String sql, boolean returnGeneratedKeys) {\r
+               IciqlException.checkUnmappedField(sql);\r
                try {\r
                        if (returnGeneratedKeys) {\r
                                return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);\r
                        }\r
                        return conn.prepareStatement(sql);\r
                } catch (SQLException e) {\r
-                       throw new IciqlException(e);\r
+                       throw IciqlException.fromSQL(sql, e);\r
                }\r
        }\r
 \r
index 8ccf66f02912194399a67063d4b595d70e5d89c5..6270e1437456f3173e029cde1ca24311cb1274fa 100644 (file)
@@ -23,7 +23,7 @@ import com.iciql.Iciql.IQTable;
 /**
  * A system table to track database and table versions.
  */
-@IQTable(name = "_iq_versions", primaryKey = { "schemaName", "tableName"}, memoryTable = true)
+@IQTable(name = "iq_versions", primaryKey = { "schemaName", "tableName" }, memoryTable = true)
 public class DbVersion {
 
        @IQColumn(length = 255)
index d7b42ba690e0712b092a21a339a143e0aea58fc4..5e969e1a8d35eeef7ae63fe2b6e931a769dfdee9 100644 (file)
@@ -61,7 +61,12 @@ public class Define {
 \r
        public static void length(Object column, int length) {\r
                checkInDefine();\r
-               currentTableDefinition.setMaxLength(column, length);\r
+               currentTableDefinition.setLength(column, length);\r
+       }\r
+       \r
+       public static void scale(Object column, int scale) {\r
+               checkInDefine();\r
+               currentTableDefinition.setScale(column, scale);\r
        }\r
 \r
        public static void tableName(String tableName) {\r
index 40c412ad6bae3cd97b978a63b16b7c1cd893e69b..33b954bb061091dc6d726a9483841a1bf7d0cbcb 100644 (file)
@@ -40,7 +40,7 @@ import java.lang.annotation.Target;
  * </tr>\r
  * <tr>\r
  * <td>java.lang.String</td>\r
- * <td>VARCHAR (length > 0) or TEXT (length == 0)</td>\r
+ * <td>VARCHAR (length > 0) or CLOB (length == 0)</td>\r
  * </tr>\r
  * <tr>\r
  * <td>java.lang.Boolean</td>\r
@@ -72,7 +72,7 @@ import java.lang.annotation.Target;
  * </tr>\r
  * <tr>\r
  * <td>java.math.BigDecimal</td>\r
- * <td>DECIMAL</td>\r
+ * <td>DECIMAL (length == 0)<br/>DECIMAL(length, scale)  (length > 0)</td>\r
  * </tr>\r
  * <tr>\r
  * <td>java.sql.Date</td>\r
@@ -92,7 +92,7 @@ import java.lang.annotation.Target;
  * </tr>\r
  * <tr>\r
  * <td>java.lang.Enum.name()</td>\r
- * <td>VARCHAR (length > 0) or TEXT (length == 0)<br/>\r
+ * <td>VARCHAR (length > 0) or CLOB (length == 0)<br/>\r
  * EnumType.NAME</td>\r
  * </tr>\r
  * <tr>\r
@@ -391,15 +391,27 @@ public interface Iciql {
                boolean autoIncrement() default false;\r
 \r
                /**\r
-                * If larger than zero, it is used during the CREATE TABLE phase. It may\r
-                * also be used to prevent database exceptions on INSERT and UPDATE\r
-                * statements (see trim).\r
+                * Length is used to define the length of a VARCHAR column or to define\r
+                * the precision of a DECIMAL(precision, scale) expression.\r
+                * <p>\r
+                * If larger than zero, it is used during the CREATE TABLE phase. For\r
+                * string values it may also be used to prevent database exceptions on\r
+                * INSERT and UPDATE statements (see trim).\r
                 * <p>\r
                 * Any length set in define() may override this annotation setting if\r
                 * the model class is not annotated with IQTable. Default: 0.\r
                 */\r
                int length() default 0;\r
 \r
+               /**\r
+                * Scale is used during the CREATE TABLE phase to define the scale of a\r
+                * DECIMAL(precision, scale) expression.\r
+                * <p>\r
+                * Any scale set in define() may override this annotation setting if\r
+                * the model class is not annotated with IQTable. Default: 0.\r
+                */\r
+               int scale() default 0;\r
+\r
                /**\r
                 * If true, iciql will automatically trim the string if it exceeds\r
                 * length (value.substring(0, length)). Default: false.\r
index 1e2a890c5a9674a2629acb1fde7433a3415458d2..6e55c7906b8046aa76fc599adcb7b4f3f66ee5f2 100644 (file)
 \r
 package com.iciql;\r
 \r
+import java.sql.SQLException;\r
 import java.text.MessageFormat;\r
+import java.util.regex.Pattern;\r
 \r
 /**\r
  * Iciql wraps all exceptions with this class.\r
  */\r
 public class IciqlException extends RuntimeException {\r
 \r
+       public static final int CODE_UNMAPPED_FIELD = 1;\r
+       public static final int CODE_DUPLICATE_KEY = 2;\r
+       public static final int CODE_TABLE_NOT_FOUND = 3;\r
+       public static final int CODE_INDEX_ALREADY_EXISTS = 4;\r
+\r
+       private static final String TOKEN_UNMAPPED_FIELD = "\\? (=|\\>|\\<|!=|\\>=|\\<=|LIKE|BETWEEN) \\?";     \r
+\r
        private static final long serialVersionUID = 1L;\r
 \r
+       private String sql;\r
+\r
+       private int iciqlCode;\r
+\r
+       public IciqlException(Throwable t) {\r
+               super(t.getMessage(), t);\r
+               configureCode(t);\r
+       }\r
+\r
        public IciqlException(String message, Object... parameters) {\r
                super(parameters.length > 0 ? MessageFormat.format(message, parameters) : message);\r
-\r
        }\r
 \r
        public IciqlException(Throwable t, String message, Object... parameters) {\r
                super(parameters.length > 0 ? MessageFormat.format(message, parameters) : message, t);\r
-\r
+               configureCode(t);\r
+       }\r
+       \r
+       public static void checkUnmappedField(String sql) {\r
+               if (Pattern.compile(IciqlException.TOKEN_UNMAPPED_FIELD).matcher(sql).find()) {\r
+                       IciqlException e = new IciqlException("unmapped field in statement!");\r
+                       e.sql = sql;\r
+                       e.iciqlCode = CODE_UNMAPPED_FIELD;\r
+                       throw e;\r
+               }\r
+       }\r
+       \r
+       public static IciqlException fromSQL(String sql, Throwable t) {\r
+               if (Pattern.compile(TOKEN_UNMAPPED_FIELD).matcher(sql).find()) {\r
+                       IciqlException e = new IciqlException(t, "unmapped field in statement!");\r
+                       e.sql = sql;\r
+                       e.iciqlCode = CODE_UNMAPPED_FIELD;\r
+                       return e;\r
+               } else {\r
+                       IciqlException e = new IciqlException(t, t.getMessage());\r
+                       e.sql = sql;\r
+                       return e;\r
+               }\r
+       }\r
+       \r
+       public void setSQL(String sql) {\r
+               this.sql = sql;\r
        }\r
 \r
-       public IciqlException(Throwable t) {\r
-               super(t);\r
+       public String getSQL() {\r
+               return sql;\r
        }\r
 \r
-       public IciqlException(String message, Throwable t) {\r
-               super(message, t);\r
+       public int getIciqlCode() {\r
+               return iciqlCode;\r
+       }\r
+       \r
+       private void configureCode(Throwable t) {\r
+               if (t == null) {\r
+                       return;\r
+               }\r
+               if (t instanceof SQLException) {\r
+                       // http://developer.mimer.com/documentation/html_92/Mimer_SQL_Mobile_DocSet/App_Return_Codes2.html\r
+                       SQLException s = (SQLException) t;\r
+                       String state = s.getSQLState();\r
+                       if ("23505".equals(state)) {\r
+                               iciqlCode = CODE_DUPLICATE_KEY;\r
+                       } else if ("42501".equals(state)) {\r
+                               iciqlCode = CODE_TABLE_NOT_FOUND;\r
+                       } else if ("42S02".equals(state)) {\r
+                               iciqlCode = CODE_TABLE_NOT_FOUND;\r
+                       } else if ("42504".equals(state)) {\r
+                               iciqlCode = CODE_INDEX_ALREADY_EXISTS;\r
+                       } else if ("42S11".equals(state)) {\r
+                               iciqlCode = CODE_INDEX_ALREADY_EXISTS;\r
+                       }\r
+               }\r
        }\r
+       \r
+       @Override\r
+    public String toString() {\r
+               StringBuilder sb = new StringBuilder();\r
+               sb.append(getClass().getName());        \r
+        String message = getLocalizedMessage();\r
+        if (message != null) {\r
+               sb.append(": ").append(message);\r
+        }\r
+        if (sql != null) {\r
+               sb.append('\n').append(sql);\r
+        }\r
+        return sb.toString();\r
+    }\r
 }\r
index 2e42a7d4cbfe1739e5b6172a221cc76a28307741..441bca7ad60805e01677ba4281b318d43bcdc2b3 100644 (file)
@@ -49,7 +49,7 @@ class ModelUtils {
        static {
                Map<Class<?>, String> m = SUPPORTED_TYPES;
                m.put(String.class, "VARCHAR");
-               m.put(Boolean.class, "BIT");
+               m.put(Boolean.class, "BOOLEAN");
                m.put(Byte.class, "TINYINT");
                m.put(Short.class, "SMALLINT");
                m.put(Integer.class, "INT");
@@ -100,8 +100,8 @@ class ModelUtils {
                m.put("NCLOB", "VARCHAR");
 
                // logic
-               m.put("BOOL", "BIT");
-               m.put("BOOLEAN", "BIT");
+               m.put("BIT", "BOOLEAN");
+               m.put("BOOL", "BOOLEAN");
 
                // numeric
                m.put("BYTE", "TINYINT");
@@ -159,19 +159,11 @@ class ModelUtils {
                                return "INT";
                        case NAME:
                        default:
-                               if (fieldDef.maxLength <= 0) {
-                                       return "TEXT";
-                               }
                                return "VARCHAR";
                        }
                }
                if (SUPPORTED_TYPES.containsKey(fieldClass)) {
-                       String type = SUPPORTED_TYPES.get(fieldClass);
-                       if (type.equals("VARCHAR") && fieldDef.maxLength <= 0) {
-                               // unspecified length strings are TEXT, not VARCHAR
-                               return "TEXT";
-                       }
-                       return type;
+                       return SUPPORTED_TYPES.get(fieldClass);
                }
                if (!strictTypeMapping) {
                        return "VARCHAR";
index 2e58fe512cc847192caf38697e14d39cb0e65490..5c2d2254177285d5e75947cc0a84603d6add9b77 100644 (file)
@@ -83,7 +83,7 @@ public class Query<T> {
                        long value = rs.getLong(1);\r
                        return value;\r
                } catch (SQLException e) {\r
-                       throw new IciqlException(e);\r
+                       throw IciqlException.fromSQL(stat.getSQL(), e);\r
                } finally {\r
                        JdbcUtils.closeSilently(rs, true);\r
                }\r
@@ -128,7 +128,7 @@ public class Query<T> {
                                result.add(item);\r
                        }\r
                } catch (SQLException e) {\r
-                       throw new IciqlException(e);\r
+                       throw IciqlException.fromSQL(stat.getSQL(), e);\r
                } finally {\r
                        JdbcUtils.closeSilently(rs, true);\r
                }\r
@@ -204,7 +204,7 @@ public class Query<T> {
                                result.add(row);\r
                        }\r
                } catch (SQLException e) {\r
-                       throw new IciqlException(e);\r
+                       throw IciqlException.fromSQL(stat.getSQL(), e);\r
                } finally {\r
                        JdbcUtils.closeSilently(rs, true);\r
                }\r
@@ -220,26 +220,20 @@ public class Query<T> {
                List<X> result = Utils.newArrayList();\r
                try {\r
                        while (rs.next()) {\r
-                               try {\r
-                                       X value;\r
-                                       Object o = rs.getObject(1);\r
-                                       // Convert CLOB and BLOB now because we close the resultset\r
-                                       if (Clob.class.isAssignableFrom(o.getClass())) {\r
-                                               value = (X) Utils.convert(o, String.class);\r
-                                       } else if (Blob.class.isAssignableFrom(o.getClass())) {\r
-                                               value = (X) Utils.convert(o, byte[].class);\r
-                                       } else {\r
-                                               value = (X) o;\r
-                                       }\r
-                                       result.add(value);\r
-                               } catch (IciqlException e) {\r
-                                       throw e;\r
-                               } catch (Exception e) {\r
-                                       throw new IciqlException(e);\r
+                               X value;\r
+                               Object o = rs.getObject(1);\r
+                               // Convert CLOB and BLOB now because we close the resultset\r
+                               if (Clob.class.isAssignableFrom(o.getClass())) {\r
+                                       value = (X) Utils.convert(o, String.class);\r
+                               } else if (Blob.class.isAssignableFrom(o.getClass())) {\r
+                                       value = (X) Utils.convert(o, byte[].class);\r
+                               } else {\r
+                                       value = (X) o;\r
                                }\r
+                               result.add(value);\r
                        }\r
-               } catch (SQLException e) {\r
-                       throw new IciqlException(e);\r
+               } catch (Exception e) {\r
+                       throw IciqlException.fromSQL(stat.getSQL(), e);\r
                } finally {\r
                        JdbcUtils.closeSilently(rs, true);\r
                }\r
index de0951550d97aa16932f413cc863a229127a31ce..8628f40f6b58649151ff38879af2cbe5a0528d0d 100644 (file)
@@ -18,6 +18,10 @@ package com.iciql;
 \r
 /**\r
  * This class represents a "between y and z" condition.\r
+ * @param <T>\r
+ *            the return type of the query\r
+ * @param <A>\r
+ *            the incomplete condition data type\r
  */\r
 public class QueryBetween<T, A> {\r
 \r
index 3ca2c4caca6b42465b59606d711358152134cca9..7821c3fd546ee0e3d51d766105fb90c3540fef2e 100644 (file)
@@ -55,6 +55,14 @@ public interface SQLDialect {
         */
        String prepareColumnName(String name);
 
+       /**
+        * Get the CREATE TABLE statement.
+        * 
+        * @param stat
+        * @param def
+        */
+       <T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def);
+       
        /**
         * Get the CREATE INDEX statement.
         * 
@@ -66,8 +74,8 @@ public interface SQLDialect {
         *            the index definition
         * @return the SQL statement
         */
-       String prepareCreateIndex(String schemaName, String tableName, IndexDefinition index);
-       
+       void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName, IndexDefinition index);
+
        /**
         * Get a MERGE or REPLACE INTO statement.
         * 
@@ -78,7 +86,8 @@ public interface SQLDialect {
         * @param index
         *            the index definition
         */
-       <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition<T> def, Object obj);
+       <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition<T> def,
+                       Object obj);
 
        /**
         * Append "LIMIT limit" to the SQL statement.
@@ -106,18 +115,12 @@ public interface SQLDialect {
         * @return true if they are
         */
        boolean supportsMemoryTables();
-
-       /**
-        * Whether merge is a supported function.
-        * 
-        * @return true if they are
-        */
-       boolean supportsMerge();
-
+       
        /**
         * Whether LIMIT/OFFSET notation is supported.
         * 
         * @return true if they are
         */
        boolean supportsLimitOffset();
+
 }
index 760a1f464c08bdf564c388c6478ee84488725317..3fd206767f69234f01bb580f1314eedd862ade0a 100644 (file)
@@ -20,7 +20,9 @@ package com.iciql;
 import java.sql.DatabaseMetaData;\r
 import java.sql.SQLException;\r
 \r
+import com.iciql.TableDefinition.FieldDefinition;\r
 import com.iciql.TableDefinition.IndexDefinition;\r
+import com.iciql.util.StatementBuilder;\r
 import com.iciql.util.StringUtils;\r
 \r
 /**\r
@@ -49,13 +51,18 @@ public class SQLDialectDefault implements SQLDialect {
                }\r
        }\r
 \r
-       @Override\r
-       public boolean supportsMemoryTables() {\r
-               return false;\r
+       /**\r
+        * Allows subclasses to change the type of a column for a CREATE statement.\r
+        * \r
+        * @param sqlType\r
+        * @return the SQL type or a preferred alternative\r
+        */\r
+       protected String convertSqlType(String sqlType) {\r
+               return sqlType;\r
        }\r
 \r
        @Override\r
-       public boolean supportsMerge() {\r
+       public boolean supportsMemoryTables() {\r
                return false;\r
        }\r
 \r
@@ -78,12 +85,100 @@ public class SQLDialectDefault implements SQLDialect {
        }\r
 \r
        @Override\r
-       public String prepareCreateIndex(String schemaName, String tableName, IndexDefinition index) {\r
+       public <T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def) {\r
+               StatementBuilder buff;\r
+               if (def.memoryTable && supportsMemoryTables()) {\r
+                       buff = new StatementBuilder("CREATE MEMORY TABLE IF NOT EXISTS ");\r
+               } else {\r
+                       buff = new StatementBuilder("CREATE TABLE IF NOT EXISTS ");\r
+               }\r
+\r
+               buff.append(prepareTableName(def.schemaName, def.tableName)).append('(');\r
+\r
+               boolean hasIdentityColumn = false;\r
+               for (FieldDefinition field : def.fields) {\r
+                       buff.appendExceptFirst(", ");\r
+                       buff.append(prepareColumnName(field.columnName)).append(' ');\r
+                       String dataType = field.dataType;\r
+                       if (dataType.equals("VARCHAR")) {               \r
+                               // check to see if we should use VARCHAR or CLOB\r
+                               if (field.length <= 0) {\r
+                                       dataType = "CLOB";\r
+                               }\r
+                               buff.append(convertSqlType(dataType));\r
+                               if (field.length > 0) {\r
+                                       buff.append('(').append(field.length).append(')');\r
+                               }\r
+                       } else if (dataType.equals("DECIMAL")) {\r
+                               // DECIMAL(precision,scale)\r
+                               buff.append(convertSqlType(dataType));                          \r
+                               if (field.length > 0) {\r
+                                       buff.append('(').append(field.length);\r
+                                       if (field.scale > 0) {\r
+                                               buff.append(',').append(field.scale);\r
+                                       }\r
+                                       buff.append(')');\r
+                               }\r
+                       } else {\r
+                               // other\r
+                               buff.append(convertSqlType(dataType));\r
+                       }\r
+\r
+                       hasIdentityColumn |= prepareColumnDefinition(buff, field.isAutoIncrement, field.isPrimaryKey);\r
+\r
+                       if (!field.nullable) {\r
+                               buff.append(" NOT NULL");\r
+                       }\r
+\r
+                       // default values\r
+                       if (!field.isAutoIncrement && !field.isPrimaryKey) {\r
+                               String dv = field.defaultValue;\r
+                               if (!StringUtils.isNullOrEmpty(dv)) {\r
+                                       if (ModelUtils.isProperlyFormattedDefaultValue(dv)\r
+                                                       && ModelUtils.isValidDefaultValue(field.field.getType(), dv)) {\r
+                                               buff.append(" DEFAULT " + dv);\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+\r
+               // if table does not have identity column then specify primary key\r
+               if (!hasIdentityColumn) {\r
+                       if (def.primaryKeyColumnNames != null && def.primaryKeyColumnNames.size() > 0) {\r
+                               buff.append(", PRIMARY KEY(");\r
+                               buff.resetCount();\r
+                               for (String n : def.primaryKeyColumnNames) {\r
+                                       buff.appendExceptFirst(", ");\r
+                                       buff.append(prepareColumnName(n));\r
+                               }\r
+                               buff.append(')');\r
+                       }\r
+               }\r
+               buff.append(')');\r
+               stat.setSQL(buff.toString());\r
+       }\r
+\r
+       protected boolean prepareColumnDefinition(StatementBuilder buff, boolean isAutoIncrement,\r
+                       boolean isPrimaryKey) {\r
+               boolean isIdentity = false;\r
+               if (isAutoIncrement && isPrimaryKey) {\r
+                       buff.append(" IDENTITY");\r
+                       isIdentity = true;\r
+               } else if (isAutoIncrement) {\r
+                       buff.append(" AUTO_INCREMENT");\r
+               }\r
+               return isIdentity;\r
+       }\r
+\r
+       @Override\r
+       public void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName,\r
+                       IndexDefinition index) {\r
                throw new IciqlException("Dialect does not support index creation!");\r
        }\r
 \r
        @Override\r
-       public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition<T> def, Object obj) {\r
+       public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,\r
+                       TableDefinition<T> def, Object obj) {\r
                throw new IciqlException("Dialect does not support merge statements!");\r
        }\r
 \r
index c65c2775c4ce2ca90317b97fb3b04f16172c6a16..80786f98a4a80061b1a0c3ed9c0388197b664bde 100644 (file)
@@ -31,12 +31,7 @@ public class SQLDialectH2 extends SQLDialectDefault {
        }\r
 \r
        @Override\r
-       public boolean supportsMerge() {\r
-               return true;\r
-       }\r
-\r
-       @Override\r
-       public String prepareCreateIndex(String schema, String table, IndexDefinition index) {\r
+       public void prepareCreateIndex(SQLStatement stat, String schema, String table, IndexDefinition index) {\r
                StatementBuilder buff = new StatementBuilder();\r
                buff.append("CREATE ");\r
                switch (index.type) {\r
@@ -62,7 +57,7 @@ public class SQLDialectH2 extends SQLDialectDefault {
                        buff.append(col);\r
                }\r
                buff.append(")");\r
-               return buff.toString();\r
+               stat.setSQL(buff.toString());           \r
        }\r
        \r
        @Override\r
diff --git a/src/com/iciql/SQLDialectHSQL.java b/src/com/iciql/SQLDialectHSQL.java
new file mode 100644 (file)
index 0000000..e617758
--- /dev/null
@@ -0,0 +1,68 @@
+/*\r
+ * Copyright 2011 James Moger.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.iciql;\r
+\r
+import com.iciql.TableDefinition.IndexDefinition;\r
+import com.iciql.util.StatementBuilder;\r
+\r
+/**\r
+ * HyperSQL database dialect.\r
+ */\r
+public class SQLDialectHSQL extends SQLDialectDefault {\r
+               \r
+       @Override\r
+       public boolean supportsMemoryTables() {\r
+               return true;\r
+       }\r
+               \r
+       @Override\r
+       protected boolean prepareColumnDefinition(StatementBuilder buff, boolean isAutoIncrement, boolean isPrimaryKey) {\r
+               boolean isIdentity = false;\r
+               if (isAutoIncrement && isPrimaryKey) {\r
+                       buff.append(" IDENTITY");\r
+                       isIdentity = true;\r
+               }\r
+               return isIdentity;\r
+       }\r
+\r
+       @Override\r
+       public void prepareCreateIndex(SQLStatement stat, String schema, String table, IndexDefinition index) {\r
+               StatementBuilder buff = new StatementBuilder();\r
+               buff.append("CREATE ");\r
+               switch (index.type) {\r
+               case STANDARD:\r
+                       break;\r
+               case UNIQUE:\r
+                       buff.append("UNIQUE ");\r
+                       break;\r
+               case UNIQUE_HASH:\r
+                       buff.append("UNIQUE ");\r
+                       break;\r
+               }\r
+               buff.append("INDEX ");\r
+               buff.append(index.indexName);\r
+               buff.append(" ON ");\r
+               buff.append(table);\r
+               buff.append("(");\r
+               for (String col : index.columnNames) {\r
+                       buff.appendExceptFirst(", ");\r
+                       buff.append(col);\r
+               }\r
+               buff.append(")");\r
+               stat.setSQL(buff.toString());\r
+       }\r
+}
\ No newline at end of file
index 837d77b23e450cb4d9926e6e55247f8ceac8eacd..2593e0ab332e8ee69bed5c80c968f959c1dcf8f3 100644 (file)
@@ -16,7 +16,6 @@
 \r
 package com.iciql;\r
 \r
-import com.iciql.TableDefinition.FieldDefinition;\r
 import com.iciql.TableDefinition.IndexDefinition;\r
 import com.iciql.util.StatementBuilder;\r
 \r
@@ -26,13 +25,16 @@ import com.iciql.util.StatementBuilder;
 public class SQLDialectMySQL extends SQLDialectDefault {\r
 \r
        @Override\r
-       public boolean supportsMemoryTables() {\r
-               return false;\r
+       protected String convertSqlType(String sqlType) {\r
+               if (sqlType.equals("CLOB")) {\r
+                       return "TEXT";\r
+               }\r
+               return sqlType;\r
        }\r
-\r
+       \r
        @Override\r
-       public boolean supportsMerge() {\r
-               return true;\r
+       public boolean supportsMemoryTables() {\r
+               return false;\r
        }\r
 \r
        @Override\r
@@ -41,7 +43,15 @@ public class SQLDialectMySQL extends SQLDialectDefault {
        }\r
        \r
        @Override\r
-       public String prepareCreateIndex(String schema, String table, IndexDefinition index) {\r
+       protected boolean prepareColumnDefinition(StatementBuilder buff, boolean isAutoIncrement, boolean isPrimaryKey) {\r
+               if (isAutoIncrement) {\r
+                       buff.append(" AUTO_INCREMENT");\r
+               }\r
+               return false;\r
+       }\r
+       \r
+       @Override\r
+       public void prepareCreateIndex(SQLStatement stat, String schema, String table, IndexDefinition index) {\r
                StatementBuilder buff = new StatementBuilder();\r
                buff.append("CREATE ");\r
                switch (index.type) {\r
@@ -74,26 +84,6 @@ public class SQLDialectMySQL extends SQLDialectDefault {
                        buff.append("USING HASH");\r
                        break;\r
                }\r
-               return buff.toString().trim();\r
-       }\r
-       \r
-       @Override\r
-       public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition<T> def, Object obj) {              \r
-               StatementBuilder buff = new StatementBuilder("REPLACE INTO ");\r
-               buff.append(prepareTableName(schemaName, tableName)).append('(');\r
-               for (FieldDefinition field : def.fields) {\r
-                       buff.appendExceptFirst(", ");\r
-                       buff.append(prepareColumnName(field.columnName));\r
-               }\r
-               buff.append(") VALUES(");\r
-               buff.resetCount();\r
-               for (FieldDefinition field : def.fields) {\r
-                       buff.appendExceptFirst(", ");\r
-                       buff.append('?');\r
-                       Object value = def.getValue(obj, field);\r
-                       stat.addParameter(value);\r
-               }\r
-               buff.append(')');\r
-               stat.setSQL(buff.toString());\r
+               stat.setSQL(buff.toString().trim());\r
        }\r
 }
\ No newline at end of file
index 49c7627877681d3c47bb3d4c42c4aea257a662b7..496250213a371a44c9b2928d48d2891134d2991c 100644 (file)
@@ -52,7 +52,7 @@ public class SQLStatement {
        public SQLStatement appendTable(String schema, String table) {\r
                return appendSQL(db.getDialect().prepareTableName(schema, table));\r
        }\r
-       \r
+\r
        public SQLStatement appendColumn(String column) {\r
                return appendSQL(db.getDialect().prepareColumnName(column));\r
        }\r
@@ -63,7 +63,7 @@ public class SQLStatement {
                }\r
                return sql;\r
        }\r
-       \r
+\r
        public SQLStatement addParameter(Object o) {\r
                params.add(o);\r
                return this;\r
@@ -73,7 +73,7 @@ public class SQLStatement {
                try {\r
                        return prepare(false).executeQuery();\r
                } catch (SQLException e) {\r
-                       throw new IciqlException(e);\r
+                       throw IciqlException.fromSQL(getSQL(), e);\r
                }\r
        }\r
 \r
@@ -83,7 +83,7 @@ public class SQLStatement {
                        ps = prepare(false);\r
                        return ps.executeUpdate();\r
                } catch (SQLException e) {\r
-                       throw new IciqlException(e);\r
+                       throw IciqlException.fromSQL(getSQL(), e);\r
                } finally {\r
                        JdbcUtils.closeSilently(ps);\r
                }\r
@@ -102,17 +102,19 @@ public class SQLStatement {
                        JdbcUtils.closeSilently(rs);\r
                        return identity;\r
                } catch (SQLException e) {\r
-                       throw new IciqlException(e);\r
+                       throw IciqlException.fromSQL(getSQL(), e);\r
                } finally {\r
                        JdbcUtils.closeSilently(ps);\r
                }\r
        }\r
 \r
-       private static void setValue(PreparedStatement prep, int parameterIndex, Object x) {\r
+       private void setValue(PreparedStatement prep, int parameterIndex, Object x) {\r
                try {\r
                        prep.setObject(parameterIndex, x);\r
                } catch (SQLException e) {\r
-                       throw new IciqlException(e);\r
+                       IciqlException ix = new IciqlException(e, "error setting parameter {0} as {1}", parameterIndex, x.getClass().getSimpleName());\r
+                       ix.setSQL(getSQL());\r
+                       throw ix;\r
                }\r
        }\r
 \r
index c08a032d76a63a7cb93548716740c219213a893f..571ab1fed8681fb307b791a6ed7ab687b9846da8 100644 (file)
@@ -71,10 +71,11 @@ public class TableDefinition<T> {
                String columnName;\r
                Field field;\r
                String dataType;\r
-               int maxLength;\r
+               int length;\r
+               int scale;\r
                boolean isPrimaryKey;\r
                boolean isAutoIncrement;\r
-               boolean trimString;\r
+               boolean trim;\r
                boolean nullable;\r
                String defaultValue;\r
                EnumType enumType;\r
@@ -125,13 +126,14 @@ public class TableDefinition<T> {
        String schemaName;\r
        String tableName;\r
        int tableVersion;\r
-       private boolean createTableIfRequired = true;\r
+       List<String> primaryKeyColumnNames;\r
+       boolean memoryTable;\r
+       \r
+       private boolean createTableIfRequired = true;   \r
        private Class<T> clazz;\r
        private IdentityHashMap<Object, FieldDefinition> fieldMap = Utils.newIdentityHashMap();\r
-\r
-       private List<String> primaryKeyColumnNames;\r
        private ArrayList<IndexDefinition> indexes = Utils.newArrayList();\r
-       private boolean memoryTable;\r
+       \r
 \r
        TableDefinition(Class<T> clazz) {\r
                this.clazz = clazz;\r
@@ -242,10 +244,17 @@ public class TableDefinition<T> {
                }\r
        }\r
 \r
-       public void setMaxLength(Object column, int maxLength) {\r
+       public void setLength(Object column, int length) {\r
+               FieldDefinition def = fieldMap.get(column);\r
+               if (def != null) {\r
+                       def.length = length;\r
+               }\r
+       }\r
+       \r
+       public void setScale(Object column, int scale) {\r
                FieldDefinition def = fieldMap.get(column);\r
                if (def != null) {\r
-                       def.maxLength = maxLength;\r
+                       def.scale = scale;\r
                }\r
        }\r
 \r
@@ -273,8 +282,9 @@ public class TableDefinition<T> {
                        String columnName = f.getName();\r
                        boolean isAutoIncrement = false;\r
                        boolean isPrimaryKey = false;\r
-                       int maxLength = 0;\r
-                       boolean trimString = false;\r
+                       int length = 0;\r
+                       int scale = 0;\r
+                       boolean trim = false;\r
                        boolean nullable = true;\r
                        EnumType enumType = null;\r
                        String defaultValue = "";\r
@@ -301,8 +311,9 @@ public class TableDefinition<T> {
                                }\r
                                isAutoIncrement = col.autoIncrement();\r
                                isPrimaryKey = col.primaryKey();\r
-                               maxLength = col.length();\r
-                               trimString = col.trim();\r
+                               length = col.length();\r
+                               scale = col.scale();\r
+                               trim = col.trim();\r
                                nullable = col.nullable();\r
 \r
                                // try using default object\r
@@ -321,7 +332,7 @@ public class TableDefinition<T> {
                                                }\r
                                        }\r
                                } catch (IllegalAccessException e) {\r
-                                       throw new IciqlException(e, "Failed to get default object for {0}", columnName);\r
+                                       throw new IciqlException(e, "failed to get default object for {0}", columnName);\r
                                }\r
 \r
                                // annotation overrides\r
@@ -338,8 +349,9 @@ public class TableDefinition<T> {
                                fieldDef.columnName = columnName;\r
                                fieldDef.isAutoIncrement = isAutoIncrement;\r
                                fieldDef.isPrimaryKey = isPrimaryKey;\r
-                               fieldDef.maxLength = maxLength;\r
-                               fieldDef.trimString = trimString;\r
+                               fieldDef.length = length;\r
+                               fieldDef.scale = scale;\r
+                               fieldDef.trim = trim;\r
                                fieldDef.nullable = nullable;\r
                                fieldDef.defaultValue = defaultValue;\r
                                fieldDef.enumType = enumType;\r
@@ -372,9 +384,9 @@ public class TableDefinition<T> {
                        Enum<?> iqenum = (Enum<?>) value;\r
                        switch (field.enumType) {\r
                        case NAME:\r
-                               if (field.trimString && field.maxLength > 0) {\r
-                                       if (iqenum.name().length() > field.maxLength) {\r
-                                               return iqenum.name().substring(0, field.maxLength);\r
+                               if (field.trim && field.length > 0) {\r
+                                       if (iqenum.name().length() > field.length) {\r
+                                               return iqenum.name().substring(0, field.length);\r
                                        }\r
                                }\r
                                return iqenum.name();\r
@@ -388,12 +400,13 @@ public class TableDefinition<T> {
                                return enumid.enumId();\r
                        }\r
                }\r
-               if (field.trimString && field.maxLength > 0) {\r
+               \r
+               if (field.trim && field.length > 0) {\r
                        if (value instanceof String) {\r
                                // clip strings\r
                                String s = (String) value;\r
-                               if (s.length() > field.maxLength) {\r
-                                       return s.substring(0, field.maxLength);\r
+                               if (s.length() > field.length) {\r
+                                       return s.substring(0, field.length);\r
                                }\r
                                return s;\r
                        }\r
@@ -514,65 +527,23 @@ public class TableDefinition<T> {
                        db.upgradeTable(this);\r
                        return this;\r
                }\r
-               SQLDialect dialect = db.getDialect();\r
                SQLStatement stat = new SQLStatement(db);\r
-               StatementBuilder buff;\r
-               if (memoryTable && dialect.supportsMemoryTables()) {\r
-                       buff = new StatementBuilder("CREATE MEMORY TABLE IF NOT EXISTS ");\r
-               } else {\r
-                       buff = new StatementBuilder("CREATE TABLE IF NOT EXISTS ");\r
-               }\r
-\r
-               buff.append(dialect.prepareTableName(schemaName, tableName)).append('(');\r
-\r
-               for (FieldDefinition field : fields) {\r
-                       buff.appendExceptFirst(", ");\r
-                       buff.append(dialect.prepareColumnName(field.columnName)).append(' ').append(field.dataType);\r
-                       if (field.maxLength > 0) {\r
-                               buff.append('(').append(field.maxLength).append(')');\r
-                       }\r
-\r
-                       if (field.isAutoIncrement) {\r
-                               buff.append(" AUTO_INCREMENT");\r
-                       }\r
-\r
-                       if (!field.nullable) {\r
-                               buff.append(" NOT NULL");\r
-                       }\r
-\r
-                       // default values\r
-                       if (!field.isAutoIncrement && !field.isPrimaryKey) {\r
-                               String dv = field.defaultValue;\r
-                               if (!StringUtils.isNullOrEmpty(dv)) {\r
-                                       if (ModelUtils.isProperlyFormattedDefaultValue(dv)\r
-                                                       && ModelUtils.isValidDefaultValue(field.field.getType(), dv)) {\r
-                                               buff.append(" DEFAULT " + dv);\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-\r
-               // primary key\r
-               if (primaryKeyColumnNames != null && primaryKeyColumnNames.size() > 0) {\r
-                       buff.append(", PRIMARY KEY(");\r
-                       buff.resetCount();\r
-                       for (String n : primaryKeyColumnNames) {\r
-                               buff.appendExceptFirst(", ");\r
-                               buff.append(n);\r
-                       }\r
-                       buff.append(')');\r
-               }\r
-               buff.append(')');\r
-               stat.setSQL(buff.toString());\r
+               db.getDialect().prepareCreateTable(stat, this);         \r
                StatementLogger.create(stat.getSQL());\r
                stat.executeUpdate();\r
 \r
                // create indexes\r
                for (IndexDefinition index : indexes) {\r
-                       String sql = db.getDialect().prepareCreateIndex(schemaName, tableName, index);\r
-                       stat.setSQL(sql);\r
+                       stat = new SQLStatement(db);\r
+                       db.getDialect().prepareCreateIndex(stat, schemaName, tableName, index);                 \r
                        StatementLogger.create(stat.getSQL());\r
-                       stat.executeUpdate();\r
+                       try {\r
+                               stat.executeUpdate();\r
+                       } catch (IciqlException e) {\r
+                               if (e.getIciqlCode() != IciqlException.CODE_INDEX_ALREADY_EXISTS) {\r
+                                       throw e;\r
+                               }\r
+                       }\r
                }\r
 \r
                // tables are created using IF NOT EXISTS\r
index a5b6dee4991fab2e799977aa5e024fcf170f0d90..e682f2fbfb508095e8a012c0c8d056abafa21832 100644 (file)
@@ -118,9 +118,12 @@ public class TableInspector {
                        indexes = Utils.newHashMap();
                        while (rs.next()) {
                                IndexInspector info = new IndexInspector(rs);
-                               if (info.type.equals(IndexType.UNIQUE) && info.name.toLowerCase().startsWith("primary")) {
-                                       // skip primary key indexes
-                                       continue;
+                               if (info.type.equals(IndexType.UNIQUE)) {
+                                       String name = info.name.toLowerCase();
+                                       if (name.startsWith("primary") || name.startsWith("sys_idx_sys_pk")) {
+                                               // skip primary key indexes
+                                               continue;
+                                       }
                                }
                                if (indexes.containsKey(info.name)) {
                                        indexes.get(info.name).addColumn(rs);
@@ -140,7 +143,20 @@ public class TableInspector {
                                col.clazz = ModelUtils.getClassForSqlType(col.type, dateTimeClass);
                                col.size = rs.getInt("COLUMN_SIZE");
                                col.nullable = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable;
-                               col.isAutoIncrement = rs.getBoolean("IS_AUTOINCREMENT");
+                               try {
+                                       Object autoIncrement = rs.getObject("IS_AUTOINCREMENT");
+                                       if (autoIncrement instanceof Boolean) {
+                                               col.isAutoIncrement = (Boolean) autoIncrement;
+                                       } else if (autoIncrement instanceof String) {
+                                               String val = autoIncrement.toString().toLowerCase();
+                                               col.isAutoIncrement = val.equals("true") | val.equals("yes");
+                                       } else if (autoIncrement instanceof Number) {
+                                               Number n = (Number) autoIncrement;
+                                               col.isAutoIncrement = n.intValue() > 0;
+                                       }
+                               } catch (SQLException s) {
+                                       throw s;
+                               }
                                if (primaryKeys.size() == 1) {
                                        if (col.name.equalsIgnoreCase(primaryKeys.get(0))) {
                                                col.isPrimaryKey = true;
@@ -499,14 +515,14 @@ public class TableInspector {
 
                // string types
                if (fieldClass == String.class) {
-                       if ((fieldDef.maxLength != col.size) && (col.size < Integer.MAX_VALUE)) {
+                       if ((fieldDef.length != col.size) && (col.size < Integer.MAX_VALUE)) {
                                remarks.add(warn(
                                                table,
                                                col,
                                                format("{0}.length={1}, ColumnMaxLength={2}", IQColumn.class.getSimpleName(),
-                                                               fieldDef.maxLength, col.size)));
+                                                               fieldDef.length, col.size)));
                        }
-                       if (fieldDef.maxLength > 0 && !fieldDef.trimString) {
+                       if (fieldDef.length > 0 && !fieldDef.trim) {
                                remarks.add(consider(table, col, format("{0}.trim=true will prevent IciqlExceptions on"
                                                + " INSERT or UPDATE, but will clip data!", IQColumn.class.getSimpleName())));
                        }
@@ -687,7 +703,7 @@ public class TableInspector {
                        }
                }
 
-               void addEnum(String parameter, Enum value) {
+               void addEnum(String parameter, Enum<?> value) {
                        appendExceptFirst(", ");
                        if (!StringUtils.isNullOrEmpty(parameter)) {
                                append(parameter);
index a68bf211ed71c1e48b74d8a0f7e9173ac763736c..33320abc4124165cef4bb1eeaa11bb5fd2fe1bb0 100644 (file)
@@ -35,11 +35,11 @@ public class ValidationRemark {
                CONSIDER, WARN, ERROR;
        }
 
-       private Level level;
-       private String table;
-       private String fieldType;
-       private String fieldName;
-       private String message;
+       public final Level level;
+       public final String table;
+       public final String fieldType;
+       public final String fieldName;
+       public final String message;
 
        private ValidationRemark(Level level, String table, String type, String message) {
                this.level = level;
@@ -104,10 +104,6 @@ public class ValidationRemark {
                return level.equals(Level.ERROR);
        }
 
-       public Level getLevel() {
-               return level;
-       }
-
        public String toString() {
                StringBuilder sb = new StringBuilder();
                sb.append(StringUtils.pad(level.name(), 9, " ", true));
index 5963d161d33de6c7caef7f7a681f329509c296ad..cc3895c5e9cc80455a9b5faaf5ea962a069c72fb 100644 (file)
@@ -56,6 +56,7 @@ public class Build {
        public static void compiletime() {\r
                downloadFromApache(MavenObject.H2, BuildType.RUNTIME);\r
                downloadFromApache(MavenObject.H2, BuildType.COMPILETIME);\r
+               downloadFromApache(MavenObject.HSQLDB, BuildType.RUNTIME);\r
                downloadFromApache(MavenObject.JCOMMANDER, BuildType.RUNTIME);\r
                downloadFromApache(MavenObject.JCOMMANDER, BuildType.COMPILETIME);\r
                downloadFromApache(MavenObject.MARKDOWNPAPERS, BuildType.RUNTIME);\r
@@ -172,6 +173,10 @@ public class Build {
                                "4bac13427caeb32ef6e93b70101e61f370c7b5e2", "6bb165156a0831879fa7797df6e18bdcd4421f2d",\r
                                "446d3f58c44992534cb54f67134532d95961904a");\r
 \r
+               public static final MavenObject HSQLDB = new MavenObject("org/hsqldb", "hsqldb", "2.2.4",\r
+                               "6a6e040b07f5ee409fc825f1c5e5b574b1fa1428", "",\r
+                               "");\r
+\r
                public static final MavenObject JUNIT = new MavenObject("junit", "junit", "4.8.2",\r
                                "c94f54227b08100974c36170dcb53329435fe5ad", "", "");\r
 \r
index fa794cfb3276eb31e1f366a901a562bb43bd0bc9..6f9746d5c04e0949a0d59b4672afe30b9556e095 100644 (file)
@@ -116,7 +116,7 @@ public class Utils {
                                                        }\r
                                                }\r
                                        }\r
-                                       throw new IciqlException("Missing default constructor?  Exception trying to create " + clazz.getName() + ": " + e, e);\r
+                                       throw new IciqlException(e, "Missing default constructor?  Exception trying to instantiate {0}: {1}", clazz.getName(), e.getMessage());\r
                                }\r
                        }\r
                };\r
@@ -190,7 +190,7 @@ public class Utils {
                                        }\r
                                }\r
                        }\r
-                       throw new IciqlException("Exception trying to create " + clazz.getName() + ": " + e, e);\r
+                       throw new IciqlException(e, "Missing default constructor?! Exception trying to instantiate {0}: {1}", clazz.getName(), e.getMessage());\r
                }\r
        }\r
 \r
@@ -220,18 +220,28 @@ public class Utils {
                                        Reader r = c.getCharacterStream();\r
                                        return readStringAndClose(r, -1);\r
                                } catch (Exception e) {\r
-                                       throw new IciqlException("Error converting CLOB to String: " + e.toString(), e);\r
+                                       throw new IciqlException(e, "error converting CLOB to String: ", e.toString());\r
                                }\r
                        }\r
                        return o.toString();\r
                }\r
 \r
-               // convert from number to boolean\r
                if (Boolean.class.isAssignableFrom(targetType) || boolean.class.isAssignableFrom(targetType)) {\r
+                       // convert from number to boolean\r
                        if (Number.class.isAssignableFrom(currentType)) {\r
                                Number n = (Number) o;\r
                                return n.intValue() > 0;\r
                        }\r
+                       // convert from string to boolean\r
+                       if (String.class.isAssignableFrom(currentType)) {\r
+                               String s = o.toString().toLowerCase();\r
+                               float f = 0f;\r
+                               try {\r
+                                       f = Float.parseFloat(s);\r
+                               } catch (Exception e) {                                 \r
+                               }\r
+                               return f > 0 || s.equals("true") || s.equals("yes");\r
+                       }\r
                }\r
 \r
                // convert from boolean to number\r
@@ -271,7 +281,7 @@ public class Utils {
                                        InputStream is = b.getBinaryStream();\r
                                        return readBlobAndClose(is, -1);\r
                                } catch (Exception e) {\r
-                                       throw new IciqlException("Error converting BLOB to byte[]: " + e.toString(), e);\r
+                                       throw new IciqlException(e, "error converting BLOB to byte[]: ", e.toString());\r
                                }\r
                        }\r
                }\r
@@ -315,7 +325,7 @@ public class Utils {
                                Reader r = c.getCharacterStream();\r
                                name = readStringAndClose(r, -1);\r
                        } catch (Exception e) {\r
-                               throw new IciqlException("Error converting CLOB to String: " + e.toString(), e);\r
+                               throw new IciqlException(e, "error converting CLOB to String: ", e.toString());\r
                        }\r
 \r
                        // find name match\r
diff --git a/tests/com/iciql/test/StatementLoggerTest.java b/tests/com/iciql/test/StatementLoggerTest.java
deleted file mode 100644 (file)
index 212faf2..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*\r
- * Copyright 2011 James Moger.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *     http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.iciql.test;\r
-\r
-import org.junit.Test;\r
-\r
-import com.iciql.Db;\r
-import com.iciql.test.models.Product;\r
-import com.iciql.util.StatementLogger;\r
-\r
-/**\r
- * Tests the statement logger. \r
- */\r
-public class StatementLoggerTest {\r
-\r
-       @Test\r
-       public void testStatementLogger() {\r
-               StatementLogger.activateConsoleLogger();\r
-               Db db = Db.open("jdbc:h2:mem:", "sa", "sa");\r
-               db.insertAll(Product.getList());\r
-               db.close();\r
-               StatementLogger.logStats();\r
-               StatementLogger.deactivateConsoleLogger();\r
-       }\r
-}\r