]> source.dussan.org Git - iciql.git/commitdiff
Support for read-only views (issue 8)
authorJames Moger <james.moger@gmail.com>
Tue, 25 Sep 2012 22:05:13 +0000 (18:05 -0400)
committerJames Moger <james.moger@gmail.com>
Tue, 25 Sep 2012 22:05:13 +0000 (18:05 -0400)
22 files changed:
docs/04_examples.mkd
docs/05_releases.mkd
docs/06_jaqu_comparison.mkd
src/com/iciql/Db.java
src/com/iciql/Define.java
src/com/iciql/Iciql.java
src/com/iciql/IciqlException.java
src/com/iciql/Query.java
src/com/iciql/QueryWhere.java
src/com/iciql/SQLDialect.java
src/com/iciql/SQLDialectDefault.java
src/com/iciql/SQLDialectH2.java
src/com/iciql/SQLDialectHSQL.java
src/com/iciql/SQLDialectMySQL.java
src/com/iciql/SQLStatement.java
src/com/iciql/TableDefinition.java
tests/com/iciql/test/IciqlSuite.java
tests/com/iciql/test/ViewsTest.java [new file with mode: 0644]
tests/com/iciql/test/models/ProductView.java [new file with mode: 0644]
tests/com/iciql/test/models/ProductViewFromQuery.java [new file with mode: 0644]
tests/com/iciql/test/models/ProductViewInherited.java [new file with mode: 0644]
tests/com/iciql/test/models/ProductViewInheritedComplex.java [new file with mode: 0644]

index 822bea53febe28b445e002f63c13a85dbaa61a99..6f60ae84cc614b173daba82fe2dba10b57523a88 100644 (file)
@@ -111,6 +111,61 @@ List&lt;CustOrder&gt; orders =
     }});\r
 %ENDCODE%\r
 \r
+## View Statements\r
+\r
+%BEGINCODE%\r
+// the view named "ProductView" is created from the "Products" table\r
+@IQView(viewTableName = "Products")\r
+public class ProductView {\r
+\r
+    @IQColumn\r
+    @IQConstraint("this >= 200 AND this < 300")\r
+    Long id;\r
+       \r
+    @IQColumn\r
+    String name;\r
+}\r
+\r
+final ProductView v = new ProductView();\r
+List&lt;ProductView&gt; allProducts = db.from(v).select();\r
+\r
+// this version of the view model "ProductView" inherits table metadata\r
+// from the Products class which is annotated with IQTable\r
+@IQView(inheritColumns = true)\r
+public class ProductView extends Products {\r
+\r
+    // inherited BUT replaced to define the constraint\r
+    @IQColumn\r
+    @IQConstraint("this >= 200 AND this < 300")\r
+    Long id;\r
+       \r
+    // inherited from Products\r
+    //@IQColumn\r
+    //String name;\r
+}\r
+\r
+final ProductView v = new ProductView();\r
+List&lt;ProductView&gt; allProducts = db.from(v).select();\r
+\r
+// in this example we are creating a view based on a fluent query\r
+// and using 2 levels of inheritance.  IQConstraints are ignored\r
+// when using this approach because we are fluently defining them.\r
+@IQView(inheritColumns = true)\r
+public class ProductViewInherited extends ProductView {\r
+\r
+}\r
+\r
+final Products p = new Products();\r
+db.from(p).where(p.id).atLeast(200L).and(p.id).lessThan(300L).createView(ProductViewInherited.class);\r
+\r
+// now replace the view with a variation\r
+db.from(p).where(p.id).atLeast(250L).and(p.id).lessThan(350L).replaceView(ProductViewInherited.class);\r
+\r
+// now drop the view from the database\r
+db.dropView(ProductViewInherited.class);\r
+\r
+%ENDCODE%\r
+\r
 ## Dynamic Queries\r
 \r
 Dynamic queries skip all field type checking and, depending on which approach you use, may skip model class/table name checking too.\r
index a209f82deacdfaed189553d95f90ebb013ce48c4..b224363ecf65517728700fba0760d8fe2b690d37 100644 (file)
@@ -4,10 +4,16 @@
 \r
 **%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
+- Implemented readonly view support. (issue 8)<br/>\r
+View models may be specified using the IQView annotation or Iciql.define().  Views can either be created automatically as part of a query of the view OR views may be constructed from a fluent statement.\r
+- Support inheriting columns from super.super class, if super.super is annotated.<br/>This allows for an inheritance hierarchy like:<br/>\r
+@IQTable class MyTable -> @IQView abstract class MyBaseView -> @IQView class MyConstrainedView\r
 - Fixed order of DEFAULT value in create table statement (issue 11)\r
 - Support inheritance of IQVersion for DbUpgrader implementations (issue 10)\r
 - Fixed password bug in model generator (issue 7)\r
 \r
+### Older Releases\r
+\r
 **1.1.0** &nbsp; *released 2012-08-20*\r
 \r
 - All bulk operations (insert all, update all, delete all) now use JDBC savepoints to ensure atomicity of the transaction\r
@@ -69,8 +75,6 @@ db.executeUpdate(q, new Date());
 Iciql maps resultset columns by the index of the model class field from a list.  This assumes that *all* columns in the resultset have a corresponding model field definition.  This works fine for most queries because iciql explicitly selects columns from the table (*select alpha, beta...*) when you execute *select()*.  The problem is when iciql issues a dynamic wildcard query and your model does not represent all columns in the resultset: columns and fields may fail to correctly line-up.<p/>\r
 Iciql now maps all fields by their column name, not by their position.\r
 \r
-### Older Releases\r
-\r
 **0.7.3** &nbsp; *released 2011-12-06*\r
 \r
 - api change release (API v8)\r
index 73f8c4a5c39b8b1afd82d4e7a827561d0b61c94a..87629dd2b3b3a2f5afd786ed7ec3b1081b7ce890 100644 (file)
@@ -13,8 +13,9 @@ This is an overview of the fundamental differences between the original JaQu pro
 <tr><td>column mappings</td><td>wildcard queries index result sets by column name</td><td>all result sets built by field index<br/>this can fail for wildcard queries</td></tr>\r
 <tr><td>savepoints</td><td>bulk operations (insert, update, delete) use savepoints with rollback in the event of failure</td><td>--</td></tr>\r
 <tr><th colspan="3">syntax and api</th></tr>\r
+<tr><td>VIEWs</td><td>create readonly views either from a class definition or from a fluent statement</td><td>--</td></tr>\r
 <tr><td>dynamic queries</td><td>methods and where clauses for dynamic queries that build iciql objects</td><td>--</td></tr>\r
-<tr><td>DROP</td><td>syntax to drop a table</td><td></td></tr>\r
+<tr><td>DROP</td><td>syntax to drop a table or view</td><td></td></tr>\r
 <tr><td>BETWEEN</td><td>syntax for specifying a BETWEEN x AND y clause</td><td>--</td></tr>\r
 <tr><th colspan="3">types</th></tr>\r
 <tr><td>primitives</td><td>fully supported</td><td>--</td></tr>\r
index 90e7613dc6b2017ee7ee51959e05986f758959b7..caec637c3a7c39ff83cb29d287dec79d611199a9 100644 (file)
@@ -38,6 +38,7 @@ import javax.sql.DataSource;
 import com.iciql.DbUpgrader.DefaultDbUpgrader;\r
 import com.iciql.Iciql.IQTable;\r
 import com.iciql.Iciql.IQVersion;\r
+import com.iciql.Iciql.IQView;\r
 import com.iciql.util.IciqlLogger;\r
 import com.iciql.util.JdbcUtils;\r
 import com.iciql.util.StringUtils;\r
@@ -284,6 +285,27 @@ public class Db {
                return rc;\r
        }\r
 \r
+       @SuppressWarnings("unchecked")\r
+       public <T> int dropView(Class<? extends T> modelClass) {\r
+               TableDefinition<T> def = (TableDefinition<T>) define(modelClass);\r
+               SQLStatement stat = new SQLStatement(this);\r
+               getDialect().prepareDropView(stat, def);\r
+               IciqlLogger.drop(stat.getSQL());\r
+               int rc = 0;\r
+               try {\r
+                       rc = stat.executeUpdate();\r
+               } catch (IciqlException e) {\r
+                       if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) {\r
+                               throw e;\r
+                       }\r
+               }\r
+               // remove this model class from the table definition cache\r
+               classMap.remove(modelClass);\r
+               // remove this model class from the upgrade checked cache\r
+               upgradeChecked.remove(modelClass);\r
+               return rc;\r
+       }\r
+\r
        public <T> List<T> buildObjects(Class<? extends T> modelClass, ResultSet rs) {\r
                return buildObjects(modelClass, false, rs);\r
        }\r
@@ -400,6 +422,11 @@ public class Db {
                                // initializer\r
                                T t = instance(clazz);\r
                                def.mapObject(t);\r
+                       } else if (clazz.isAnnotationPresent(IQView.class)) {\r
+                               // annotated classes skip the Define().define() static\r
+                               // initializer\r
+                               T t = instance(clazz);\r
+                               def.mapObject(t);\r
                        }\r
                }\r
                return def;\r
index 3b58231bf3f0a9cacd8b5d8f84c9096bddbb9be3..53f9862be4aabe8a00c313a2fe4189abd1357e91 100644 (file)
@@ -59,6 +59,11 @@ public class Define {
                currentTableDefinition.defineTableName(tableName);\r
        }\r
 \r
+       public static void viewTableName(String viewTableName) {\r
+               checkInDefine();\r
+               currentTableDefinition.defineViewTableName(viewTableName);\r
+       }\r
+       \r
        public static void memoryTable() {\r
                checkInDefine();\r
                currentTableDefinition.defineMemoryTable();\r
@@ -98,7 +103,12 @@ public class Define {
                checkInDefine();\r
                currentTableDefinition.defineDefaultValue(column, defaultValue);\r
        }\r
-       \r
+\r
+       public static void constraint(Object column, String constraint) {\r
+               checkInDefine();\r
+               currentTableDefinition.defineConstraint(column, constraint);\r
+       }\r
+\r
        static synchronized <T> void define(TableDefinition<T> tableDefinition, Iciql table) {\r
                currentTableDefinition = tableDefinition;\r
                currentTable = table;\r
index eaa256a2eaeece168e17000012810c57fbee8af6..7b3a7c13b9e26f3f88a275b8abcddf25d7e35df2 100644 (file)
@@ -293,6 +293,67 @@ public interface Iciql {
                String[] value() default {};\r
        }\r
 \r
+       /**\r
+        * Annotation to define a view.\r
+        */\r
+       @Retention(RetentionPolicy.RUNTIME)\r
+       @Target(ElementType.TYPE)\r
+       public @interface IQView {\r
+\r
+               /**\r
+                * The view name. If not specified the class name is used as the view\r
+                * name.\r
+                * <p>\r
+                * The view name may still be overridden in the define() method if the\r
+                * model class is not annotated with IQView. Default: unspecified.\r
+                */\r
+               String name() default "";\r
+               \r
+               /**\r
+                * The source table for the view.\r
+                * <p>\r
+                * The view name may still be overridden in the define() method if the\r
+                * model class is not annotated with IQView. Default: unspecified.\r
+                */\r
+               String tableName() default "";\r
+\r
+               /**\r
+                * The inherit columns allows this model class to inherit columns from\r
+                * its super class. Any IQTable annotation present on the super class is\r
+                * ignored. Default: false.\r
+                */\r
+               boolean inheritColumns() default false;\r
+\r
+               /**\r
+                * Whether or not iciql tries to create the view. Default:\r
+                * true.\r
+                */\r
+               boolean create() default true;\r
+\r
+               /**\r
+                * If true, only fields that are explicitly annotated as IQColumn are\r
+                * mapped. Default: true.\r
+                */\r
+               boolean annotationsOnly() default true;\r
+       }\r
+       \r
+       /**\r
+        * String snippet defining SQL constraints for a field. Use "this" as\r
+        * a placeholder for the column name.  "this" will be substituted at\r
+        * runtime.\r
+        * <p>\r
+        * IQConstraint("this > 2 AND this <= 7")\r
+        * <p>\r
+        * This snippet may still be overridden in the define() method if the\r
+        * model class is not annotated with IQTable or IQView. Default: unspecified.\r
+        */\r
+       @Retention(RetentionPolicy.RUNTIME)\r
+       @Target(ElementType.FIELD)\r
+       public @interface IQConstraint {\r
+\r
+               String value() default "";\r
+       }       \r
+       \r
        /**\r
         * Annotation to specify multiple indexes.\r
         */\r
index 7e7021ea1f48a560262a94185bd52671d8ec3ca8..07fd363f3fe57041a93c4d473eb7405f48d94ea0 100644 (file)
@@ -121,6 +121,9 @@ public class IciqlException extends RuntimeException {
                        } else if ("42P01".equals(state)) {\r
                                // PostgreSQL table not found\r
                                iciqlCode = CODE_OBJECT_NOT_FOUND;\r
+                       } else if ("X0X05".equals(state)) {\r
+                               // Derby view/table not found exists\r
+                               iciqlCode = CODE_OBJECT_NOT_FOUND;\r
                        } else if ("X0Y32".equals(state)) {\r
                                // Derby table already exists\r
                                iciqlCode = CODE_OBJECT_ALREADY_EXISTS;\r
index 6c0ecf9db1d5a41616ac9c837bf1cad88d39c44a..5dc78a5db5abccb12884b015610893de1d024bf5 100644 (file)
@@ -107,6 +107,23 @@ public class Query<T> {
                List<X> list = (List<X>) select(x);\r
                return list.isEmpty() ? null : list.get(0);\r
        }\r
+       \r
+       public <X> void createView(Class<X> viewClass) {\r
+               TableDefinition<X> viewDef = db.define(viewClass);\r
+               \r
+               SQLStatement fromWhere = new SQLStatement(db);\r
+               appendFromWhere(fromWhere, false);\r
+               \r
+               SQLStatement stat = new SQLStatement(db);\r
+               db.getDialect().prepareCreateView(stat, viewDef, fromWhere.toSQL());\r
+               IciqlLogger.create(stat.toSQL());\r
+               stat.execute();\r
+       }\r
+\r
+       public <X> void replaceView(Class<X> viewClass) {\r
+               db.dropView(viewClass);\r
+               createView(viewClass);\r
+       }\r
 \r
        public String getSQL() {\r
                SQLStatement stat = getSelectStatement(false);\r
@@ -803,8 +820,12 @@ public class Query<T> {
                        }\r
                }\r
        }\r
-\r
+       \r
        void appendFromWhere(SQLStatement stat) {\r
+               appendFromWhere(stat, true);\r
+       }\r
+       \r
+       void appendFromWhere(SQLStatement stat, boolean log) {\r
                stat.appendSQL(" FROM ");\r
                from.appendSQL(stat);\r
                for (SelectTable<T> join : joins) {\r
@@ -834,7 +855,9 @@ public class Query<T> {
                        }\r
                }\r
                db.getDialect().appendLimitOffset(stat, limit, offset);\r
-               IciqlLogger.select(stat.getSQL());\r
+               if (log) {\r
+                       IciqlLogger.select(stat.getSQL());\r
+               }\r
        }\r
 \r
        /**\r
index 3f1afe11cf19ba3a96d3b817ebd091927832b83f..5baa5ab893d5331ba8ad1d6225a4fb2bc8cd3345 100644 (file)
@@ -347,6 +347,14 @@ public class QueryWhere<T> {
        public List<T> selectDistinct() {\r
                return query.selectDistinct();\r
        }\r
+       \r
+       public void createView(Class<?> viewClass) {\r
+               query.createView(viewClass);\r
+       }\r
+\r
+       public void replaceView(Class<?> viewClass) {\r
+               query.replaceView(viewClass);\r
+       }\r
 \r
        /**\r
         * Order by primitive boolean field\r
index 28f55664e58c6a8217e1b9e52e8b4e241e24d144..8e3e3d2082c82873989f3c68a27c42f345077579 100644 (file)
@@ -79,6 +79,32 @@ public interface SQLDialect {
         */
        <T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def);
 
+       
+       /**
+        * Get the CREATE VIEW statement.
+        * 
+        * @param stat
+        * @param def
+        */
+       <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def);
+
+       /**
+        * Get the CREATE VIEW statement.
+        * 
+        * @param stat
+        * @param def
+        * @param fromWhere
+        */
+       <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def, String fromWhere);
+
+       /**
+        * Get the DROP VIEW statement.
+        * 
+        * @param stat
+        * @param def
+        */
+       <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def);
+       
        /**
         * Get the CREATE INDEX statement.
         * 
index 7cc6bd7552124a0eedf0e46db5d1933948f3ca7a..0cd0448810c75194b40c5c8f998aa27adca8f194 100644 (file)
@@ -166,6 +166,57 @@ public class SQLDialectDefault implements SQLDialect {
                stat.setSQL(buff.toString());\r
        }\r
 \r
+       @Override\r
+       public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {\r
+               StatementBuilder buff = new StatementBuilder("DROP VIEW "\r
+                               + prepareTableName(def.schemaName, def.tableName));\r
+               stat.setSQL(buff.toString());\r
+               return;\r
+       }\r
+\r
+       protected <T> String prepareCreateView(TableDefinition<T> def) {\r
+               return "CREATE VIEW";\r
+       }\r
+\r
+       @Override\r
+       public <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def) {\r
+               StatementBuilder buff = new StatementBuilder();\r
+               buff.append(" FROM ");\r
+               buff.append(prepareTableName(def.schemaName, def.viewTableName));\r
+\r
+               StatementBuilder where = new StatementBuilder();\r
+               for (FieldDefinition field : def.fields) {\r
+                       if (!StringUtils.isNullOrEmpty(field.constraint)) {\r
+                               where.appendExceptFirst(", ");\r
+                               String col = prepareColumnName(field.columnName);\r
+                               String constraint = field.constraint.replace("{0}", col).replace("this", col);\r
+                               where.append(constraint);\r
+                       }\r
+               }\r
+               if (where.length() > 0) {\r
+                       buff.append(" WHERE ");\r
+                       buff.append(where.toString());\r
+               }\r
+               \r
+               prepareCreateView(stat, def, buff.toString());\r
+       }\r
+       \r
+       @Override\r
+       public <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def, String fromWhere) {\r
+               StatementBuilder buff = new StatementBuilder();\r
+               buff.append(prepareCreateView(def));\r
+               buff.append(" ");\r
+               buff.append(prepareTableName(def.schemaName, def.tableName));\r
+\r
+               buff.append(" AS SELECT ");\r
+               for (FieldDefinition field : def.fields) {\r
+                       buff.appendExceptFirst(", ");\r
+                       buff.append(prepareColumnName(field.columnName));\r
+               }\r
+               buff.append(fromWhere);\r
+               stat.setSQL(buff.toString());\r
+       }\r
+       \r
        protected boolean isIntegerType(String dataType) {\r
                if ("INT".equals(dataType)) {\r
                        return true;\r
index 1da45f63e1318b7327a8aa52ab43968a167d85d4..6b3bab12a488ae5736c19da602b5d34efea18619 100644 (file)
@@ -37,6 +37,19 @@ public class SQLDialectH2 extends SQLDialectDefault {
                        return "CREATE CACHED TABLE IF NOT EXISTS";\r
                }\r
        }\r
+       \r
+       @Override\r
+       protected <T> String prepareCreateView(TableDefinition<T> def) {\r
+               return "CREATE VIEW IF NOT EXISTS";\r
+       }\r
+\r
+       @Override\r
+       public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {\r
+               StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS "\r
+                               + prepareTableName(def.schemaName, def.tableName));\r
+               stat.setSQL(buff.toString());\r
+               return;\r
+       }\r
 \r
        @Override\r
        protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,\r
index 9975be64b66e9221bbd0cef44bf6424d09669b32..82e6833e3a8edd98eb0221b46711992b40e2efa0 100644 (file)
@@ -38,6 +38,14 @@ public class SQLDialectHSQL extends SQLDialectDefault {
                        return "CREATE CACHED TABLE IF NOT EXISTS";\r
                }\r
        }\r
+       \r
+       @Override\r
+       public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {\r
+               StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS "\r
+                               + prepareTableName(def.schemaName, def.tableName));\r
+               stat.setSQL(buff.toString());\r
+               return;\r
+       }\r
 \r
        @Override\r
        protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,\r
index 7fa1fa9fac0dbba3c3bebb1c8f23d088bab2088a..52676d4bb4820d4c20bf0d42bafa92aa1cf913b0 100644 (file)
@@ -36,6 +36,15 @@ public class SQLDialectMySQL extends SQLDialectDefault {
        protected <T> String prepareCreateTable(TableDefinition<T> def) {\r
                return "CREATE TABLE IF NOT EXISTS";\r
        }\r
+       \r
+       @Override\r
+       public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {\r
+               StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS "\r
+                               + prepareTableName(def.schemaName, def.tableName));\r
+               stat.setSQL(buff.toString());\r
+               return;\r
+       }\r
+       \r
        @Override\r
        public String prepareColumnName(String name) {\r
                return "`" + name + "`";\r
index 2f97829b63d244d97a5a85a4cbc488b337f05a50..394fc429c93ed25a73e79ff7ba5a807414dc7979 100644 (file)
@@ -115,6 +115,18 @@ public class SQLStatement {
                params.add(o);\r
                return this;\r
        }\r
+       \r
+       void execute() {\r
+               PreparedStatement ps = null;\r
+               try {\r
+                       ps = prepare(false);\r
+                       ps.execute();\r
+               } catch (SQLException e) {\r
+                       throw IciqlException.fromSQL(getSQL(), e);\r
+               } finally {\r
+                       JdbcUtils.closeSilently(ps);\r
+               }\r
+       }\r
 \r
        ResultSet executeQuery() {\r
                try {\r
index f6a8c26c776af5669c0baa6856e7a27852203295..aa2572298638106d8c7e81775b6de1b10608c0b5 100644 (file)
@@ -24,12 +24,15 @@ import java.sql.SQLException;
 import java.util.ArrayList;\r
 import java.util.Arrays;\r
 import java.util.IdentityHashMap;\r
+import java.util.LinkedHashSet;\r
 import java.util.List;\r
 import java.util.Map;\r
+import java.util.Set;\r
 \r
 import com.iciql.Iciql.EnumId;\r
 import com.iciql.Iciql.EnumType;\r
 import com.iciql.Iciql.IQColumn;\r
+import com.iciql.Iciql.IQConstraint;\r
 import com.iciql.Iciql.IQEnum;\r
 import com.iciql.Iciql.IQIgnore;\r
 import com.iciql.Iciql.IQIndex;\r
@@ -37,6 +40,7 @@ import com.iciql.Iciql.IQIndexes;
 import com.iciql.Iciql.IQSchema;\r
 import com.iciql.Iciql.IQTable;\r
 import com.iciql.Iciql.IQVersion;\r
+import com.iciql.Iciql.IQView;\r
 import com.iciql.Iciql.IndexType;\r
 import com.iciql.util.IciqlLogger;\r
 import com.iciql.util.StatementBuilder;\r
@@ -81,6 +85,7 @@ public class TableDefinition<T> {
                String defaultValue;\r
                EnumType enumType;\r
                boolean isPrimitive;\r
+               String constraint;\r
 \r
                Object getValue(Object obj) {\r
                        try {\r
@@ -140,6 +145,7 @@ public class TableDefinition<T> {
        public ArrayList<FieldDefinition> fields = Utils.newArrayList();\r
        String schemaName;\r
        String tableName;\r
+       String viewTableName;\r
        int tableVersion;\r
        List<String> primaryKeyColumnNames;\r
        boolean memoryTable;\r
@@ -172,6 +178,10 @@ public class TableDefinition<T> {
                this.tableName = tableName;\r
        }\r
 \r
+       void defineViewTableName(String viewTableName) {\r
+               this.viewTableName = viewTableName;\r
+       }\r
+\r
        void defineMemoryTable() {\r
                this.memoryTable = true;\r
        }\r
@@ -302,6 +312,13 @@ public class TableDefinition<T> {
                }\r
        }\r
 \r
+       void defineConstraint(Object column, String constraint) {\r
+               FieldDefinition def = fieldMap.get(column);\r
+               if (def != null) {\r
+                       def.constraint = constraint;\r
+               }\r
+       }\r
+\r
        void mapFields() {\r
                boolean byAnnotationsOnly = false;\r
                boolean inheritColumns = false;\r
@@ -311,11 +328,33 @@ public class TableDefinition<T> {
                        inheritColumns = tableAnnotation.inheritColumns();\r
                }\r
 \r
+               if (clazz.isAnnotationPresent(IQView.class)) {\r
+                       IQView viewAnnotation = clazz.getAnnotation(IQView.class);\r
+                       byAnnotationsOnly = viewAnnotation.annotationsOnly();\r
+                       inheritColumns = viewAnnotation.inheritColumns();\r
+               }\r
+\r
                List<Field> classFields = Utils.newArrayList();\r
                classFields.addAll(Arrays.asList(clazz.getDeclaredFields()));\r
                if (inheritColumns) {\r
                        Class<?> superClass = clazz.getSuperclass();\r
                        classFields.addAll(Arrays.asList(superClass.getDeclaredFields()));\r
+                       \r
+                       if (superClass.isAnnotationPresent(IQView.class)) {\r
+                               IQView superView = superClass.getAnnotation(IQView.class);\r
+                               if (superView.inheritColumns()) {\r
+                                       // inherit columns from super.super.class\r
+                                       Class<?> superSuperClass = superClass.getSuperclass();\r
+                                       classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields()));\r
+                               }\r
+                       } else if (superClass.isAnnotationPresent(IQTable.class)) {\r
+                               IQTable superTable = superClass.getAnnotation(IQTable.class);\r
+                               if (superTable.inheritColumns()) {\r
+                                       // inherit columns from super.super.class\r
+                                       Class<?> superSuperClass = superClass.getSuperclass();\r
+                                       classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields()));\r
+                               }\r
+                       }\r
                }\r
 \r
                Set<FieldDefinition> uniqueFields = new LinkedHashSet<FieldDefinition>();\r
@@ -336,6 +375,7 @@ public class TableDefinition<T> {
                        boolean nullable = !f.getType().isPrimitive();\r
                        EnumType enumType = null;\r
                        String defaultValue = "";\r
+                       String constraint = "";\r
                        // configure Java -> SQL enum mapping\r
                        if (f.getType().isEnum()) {\r
                                enumType = EnumType.DEFAULT_TYPE;\r
@@ -388,9 +428,18 @@ public class TableDefinition<T> {
                                        defaultValue = col.defaultValue();\r
                                }\r
                        }\r
+                       \r
+                       boolean hasConstraint = f.isAnnotationPresent(IQConstraint.class);\r
+                       if (hasConstraint) {\r
+                               IQConstraint con = f.getAnnotation(IQConstraint.class);\r
+                               // annotation overrides\r
+                               if (!StringUtils.isNullOrEmpty(con.value())) {\r
+                                       constraint = con.value();\r
+                               }\r
+                       }\r
 \r
                        boolean reflectiveMatch = !byAnnotationsOnly;\r
-                       if (reflectiveMatch || hasAnnotation) {\r
+                       if (reflectiveMatch || hasAnnotation || hasConstraint) {\r
                                FieldDefinition fieldDef = new FieldDefinition();\r
                                fieldDef.isPrimitive = f.getType().isPrimitive();\r
                                fieldDef.field = f;\r
@@ -404,6 +453,7 @@ public class TableDefinition<T> {
                                fieldDef.defaultValue = defaultValue;\r
                                fieldDef.enumType = enumType;\r
                                fieldDef.dataType = ModelUtils.getDataType(fieldDef);\r
+                               fieldDef.constraint = constraint;\r
                                uniqueFields.add(fieldDef);\r
                        }\r
                }\r
@@ -540,6 +590,9 @@ public class TableDefinition<T> {
        }\r
 \r
        long insert(Db db, Object obj, boolean returnKey) {\r
+               if (!StringUtils.isNullOrEmpty(viewTableName)) {\r
+                       throw new IciqlException("Iciql does not support inserting rows into views!");\r
+               }\r
                SQLStatement stat = new SQLStatement(db);\r
                StatementBuilder buff = new StatementBuilder("INSERT INTO ");\r
                buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');\r
@@ -615,6 +668,9 @@ public class TableDefinition<T> {
        }\r
 \r
        int update(Db db, Object obj) {\r
+               if (!StringUtils.isNullOrEmpty(viewTableName)) {\r
+                       throw new IciqlException("Iciql does not support updating rows in views!");\r
+               }\r
                if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {\r
                        throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()\r
                                        + " - no update possible");\r
@@ -661,6 +717,9 @@ public class TableDefinition<T> {
        }\r
 \r
        int delete(Db db, Object obj) {\r
+               if (!StringUtils.isNullOrEmpty(viewTableName)) {\r
+                       throw new IciqlException("Iciql does not support deleting rows from views!");\r
+               }\r
                if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {\r
                        throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()\r
                                        + " - no update possible");\r
@@ -703,7 +762,11 @@ public class TableDefinition<T> {
                        return this;\r
                }\r
                SQLStatement stat = new SQLStatement(db);\r
-               db.getDialect().prepareCreateTable(stat, this);\r
+               if (StringUtils.isNullOrEmpty(viewTableName)) {\r
+                       db.getDialect().prepareCreateTable(stat, this);\r
+               } else {\r
+                       db.getDialect().prepareCreateView(stat, this);\r
+               }\r
                IciqlLogger.create(stat.getSQL());\r
                try {\r
                        stat.executeUpdate();\r
@@ -773,6 +836,64 @@ public class TableDefinition<T> {
                        }\r
                }\r
 \r
+               if (clazz.isAnnotationPresent(IQView.class)) {\r
+                       IQView viewAnnotation = clazz.getAnnotation(IQView.class);\r
+\r
+                       // setup view name mapping, if properly annotated\r
+                       // set this as the table name so it fits in seemlessly with iciql\r
+                       if (!StringUtils.isNullOrEmpty(viewAnnotation.name())) {\r
+                               tableName = viewAnnotation.name();\r
+                       } else {\r
+                               tableName = clazz.getSimpleName();\r
+                       }\r
+\r
+                       // setup source table name mapping, if properly annotated\r
+                       if (!StringUtils.isNullOrEmpty(viewAnnotation.tableName())) {\r
+                               viewTableName = viewAnnotation.tableName();\r
+                       } else {\r
+                               // check for IQTable annotation on super class\r
+                               Class<?> superClass = clazz.getSuperclass();\r
+                               if (superClass.isAnnotationPresent(IQTable.class)) {\r
+                                       IQTable table = superClass.getAnnotation(IQTable.class);\r
+                                       if (StringUtils.isNullOrEmpty(table.name())) {\r
+                                               // super.SimpleClassName\r
+                                               viewTableName = superClass.getSimpleName();\r
+                                       } else {\r
+                                               // super.IQTable.name()\r
+                                               viewTableName = table.name();\r
+                                       }\r
+                               } else if (superClass.isAnnotationPresent(IQView.class)) {\r
+                                       // super class is a view\r
+                                       IQView parentView = superClass.getAnnotation(IQView.class);\r
+                                       if (StringUtils.isNullOrEmpty(parentView.tableName())) {\r
+                                               // parent view does not define a tableName, must be inherited\r
+                                               Class<?> superParent = superClass.getSuperclass();\r
+                                               if (superParent != null && superParent.isAnnotationPresent(IQTable.class)) {\r
+                                                       IQTable superParentTable = superParent.getAnnotation(IQTable.class);\r
+                                                       if (StringUtils.isNullOrEmpty(superParentTable.name())) {\r
+                                                               // super.super.SimpleClassName\r
+                                                               viewTableName = superParent.getSimpleName();\r
+                                                       } else {\r
+                                                               // super.super.IQTable.name()\r
+                                                               viewTableName = superParentTable.name();\r
+                                                       }\r
+                                               }\r
+                                       } else {\r
+                                               // super.IQView.tableName()\r
+                                               viewTableName = parentView.tableName();\r
+                                       }\r
+                               }\r
+                               \r
+                               if (StringUtils.isNullOrEmpty(viewTableName)) {\r
+                                       // still missing view table name\r
+                                       throw new IciqlException("View model class \"{0}\" is missing a table name!", tableName);\r
+                               }\r
+                       }\r
+                       \r
+                       // allow control over createTableIfRequired()\r
+                       createIfRequired = viewAnnotation.create();\r
+               }\r
+               \r
                if (clazz.isAnnotationPresent(IQIndex.class)) {\r
                        // single table index\r
                        IQIndex index = clazz.getAnnotation(IQIndex.class);\r
index 000b1c8d7fb3be27bfed7e571abbe25ff3c2121e..68156f8948f18536c0a44409184ff4d7278f2d76 100644 (file)
@@ -60,6 +60,10 @@ import com.iciql.test.models.Product;
 import com.iciql.test.models.ProductAnnotationOnly;\r
 import com.iciql.test.models.ProductInheritedAnnotation;\r
 import com.iciql.test.models.ProductMixedAnnotation;\r
+import com.iciql.test.models.ProductView;\r
+import com.iciql.test.models.ProductViewFromQuery;\r
+import com.iciql.test.models.ProductViewInherited;\r
+import com.iciql.test.models.ProductViewInheritedComplex;\r
 import com.iciql.test.models.SupportedTypes;\r
 import com.iciql.util.IciqlLogger;\r
 import com.iciql.util.IciqlLogger.IciqlListener;\r
@@ -85,7 +89,7 @@ import com.iciql.util.Utils;
 @SuiteClasses({ AliasMapTest.class, AnnotationsTest.class, BooleanModelTest.class, ClobTest.class,\r
                ConcurrencyTest.class, EnumsTest.class, ModelsTest.class, PrimitivesTest.class,\r
                RuntimeQueryTest.class, SamplesTest.class, UpdateTest.class, UpgradesTest.class, JoinTest.class,\r
-               UUIDTest.class })\r
+               UUIDTest.class, ViewsTest.class })\r
 public class IciqlSuite {\r
 \r
        private static final TestDb[] TEST_DBS = {\r
@@ -155,6 +159,12 @@ public class IciqlSuite {
                }\r
                db = Db.open(dataSource);\r
 \r
+               // drop views\r
+               db.dropView(ProductView.class);\r
+               db.dropView(ProductViewInherited.class);\r
+               db.dropView(ProductViewFromQuery.class);\r
+               db.dropView(ProductViewInheritedComplex.class);\r
+\r
                // drop tables\r
                db.dropTable(BooleanModel.class);\r
                db.dropTable(ComplexObject.class);\r
diff --git a/tests/com/iciql/test/ViewsTest.java b/tests/com/iciql/test/ViewsTest.java
new file mode 100644 (file)
index 0000000..be2e085
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2012 James Moger.
+ *
+ * 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.iciql.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.iciql.Db;
+import com.iciql.test.models.ProductAnnotationOnly;
+import com.iciql.test.models.ProductView;
+import com.iciql.test.models.ProductViewFromQuery;
+import com.iciql.test.models.ProductViewInherited;
+import com.iciql.test.models.ProductViewInheritedComplex;
+import com.mysql.jdbc.StringUtils;
+
+/**
+ * Test annotation processing.
+ */
+public class ViewsTest {
+
+       /**
+        * This object represents a database (actually a connection to the
+        * database).
+        */
+
+       private Db db;
+
+       @Before
+       public void setUp() {
+               db = IciqlSuite.openNewDb();
+               db.insertAll(ProductAnnotationOnly.getList());
+       }
+
+       @After
+       public void tearDown() {
+               db.close();
+       }
+
+       @Test
+       public void testProductView() {
+               ProductView view = new ProductView();
+               List<ProductView> products = db.from(view).select();
+               assertEquals(5, products.size());
+               for (int i = 0; i < products.size(); i++) {
+                       assertEquals(3 + i, products.get(i).productId.intValue());
+               }
+       }
+
+       @Test
+       public void testProductViewInherited() {
+               ProductViewInherited view = new ProductViewInherited();
+               List<ProductViewInherited> products = db.from(view).select();
+               assertEquals(5, products.size());
+               for (int i = 0; i < products.size(); i++) {
+                       assertEquals(3 + i, products.get(i).productId.intValue());
+               }
+       }
+       
+       @Test
+       public void testComplexInheritance() {
+               ProductViewInheritedComplex view = new ProductViewInheritedComplex();
+               List<ProductViewInheritedComplex> products = db.from(view).select();
+               assertEquals(5, products.size());
+               for (int i = 0; i < products.size(); i++) {
+                       assertEquals(3 + i, products.get(i).productId.intValue());
+                       assertTrue(!StringUtils.isNullOrEmpty(products.get(i).productName));
+               }
+       }
+       
+       @Test
+       public void testCreateViewFromQuery() {
+               // create view from query
+               ProductAnnotationOnly product = new ProductAnnotationOnly();
+               db.from(product).where(product.productId).exceeds(2L).and(product.productId).atMost(7L).createView(ProductViewFromQuery.class);
+               
+               // select from the created view
+               ProductViewFromQuery view = new ProductViewFromQuery();
+               List<ProductViewFromQuery> products = db.from(view).select();
+               assertEquals(5, products.size());
+               for (int i = 0; i < products.size(); i++) {
+                       assertEquals(3 + i, products.get(i).productId.intValue());
+               }
+               
+               // replace the view
+               db.from(product).where(product.productId).exceeds(3L).and(product.productId).atMost(8L).replaceView(ProductViewFromQuery.class);
+               
+               // select from the replaced view
+               products = db.from(view).select();
+               assertEquals(5, products.size());
+               for (int i = 0; i < products.size(); i++) {
+                       assertEquals(4 + i, products.get(i).productId.intValue());
+               }
+       }
+}
diff --git a/tests/com/iciql/test/models/ProductView.java b/tests/com/iciql/test/models/ProductView.java
new file mode 100644 (file)
index 0000000..2efe9eb
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 James Moger.
+ *
+ * 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.iciql.test.models;
+
+import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQConstraint;
+import com.iciql.Iciql.IQView;
+
+/**
+ * A view containing product data.
+ */
+
+@IQView(name = "AnnotatedProductView", tableName = "AnnotatedProduct")
+public class ProductView {
+
+       public String unmappedField;
+
+       @IQColumn(name = "id", autoIncrement = true)
+       @IQConstraint("this <= 7 AND this > 2")
+       public Long productId;
+
+       @IQColumn(name = "name")
+       public String productName;
+
+       public ProductView() {
+               // public constructor
+       }
+
+       public String toString() {
+               return productName + " (" + productId + ")";
+       }
+
+}
diff --git a/tests/com/iciql/test/models/ProductViewFromQuery.java b/tests/com/iciql/test/models/ProductViewFromQuery.java
new file mode 100644 (file)
index 0000000..2f2f194
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 James Moger.
+ *
+ * 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.iciql.test.models;
+
+import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQView;
+
+/**
+ * A view containing product data.
+ */
+
+@IQView(name = "AnnotatedProductViewInherited", inheritColumns = true)
+public class ProductViewFromQuery  extends ProductAnnotationOnly {
+
+       public String unmappedField;
+
+       @IQColumn(name = "id")
+       public Long productId;
+
+       public ProductViewFromQuery() {
+               // public constructor
+       }
+
+       public String toString() {
+               return productName + " (" + productId + ")";
+       }
+
+}
diff --git a/tests/com/iciql/test/models/ProductViewInherited.java b/tests/com/iciql/test/models/ProductViewInherited.java
new file mode 100644 (file)
index 0000000..e9c274b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 James Moger.
+ *
+ * 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.iciql.test.models;
+
+import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQConstraint;
+import com.iciql.Iciql.IQView;
+
+/**
+ * A view containing product data.
+ */
+
+@IQView(name = "AnnotatedProductViewInherited", inheritColumns = true)
+public class ProductViewInherited extends ProductAnnotationOnly {
+
+       public String unmappedField;
+
+       @IQColumn(name = "id", autoIncrement = true)
+       @IQConstraint("this <= 7 AND this > 2")
+       public Long productId;
+
+       public ProductViewInherited() {
+               // public constructor
+       }
+
+       public String toString() {
+               return productName + " (" + productId + ")";
+       }
+
+}
diff --git a/tests/com/iciql/test/models/ProductViewInheritedComplex.java b/tests/com/iciql/test/models/ProductViewInheritedComplex.java
new file mode 100644 (file)
index 0000000..55e7ba8
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 James Moger.
+ *
+ * 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.iciql.test.models;
+
+import com.iciql.Iciql.IQView;
+
+/**
+ * A view containing product data.
+ */
+
+@IQView(inheritColumns = true)
+public class ProductViewInheritedComplex  extends ProductViewInherited {
+
+}