From d8915c7da130b8a6de6f2c911effe0e10dbe4d12 Mon Sep 17 00:00:00 2001 From: James Moger Date: Fri, 8 Mar 2013 21:14:50 -0500 Subject: Conform to Apache standard directory layout --- src/com/iciql/CompareType.java | 45 - src/com/iciql/Condition.java | 55 - src/com/iciql/ConditionAndOr.java | 37 - src/com/iciql/Constants.java | 38 - src/com/iciql/Db.java | 774 ----------- src/com/iciql/DbInspector.java | 204 --- src/com/iciql/DbUpgrader.java | 81 -- src/com/iciql/DbVersion.java | 55 - src/com/iciql/Define.java | 145 -- src/com/iciql/Filter.java | 25 - src/com/iciql/Function.java | 149 -- src/com/iciql/Iciql.java | 731 ---------- src/com/iciql/IciqlException.java | 177 --- src/com/iciql/ModelUtils.java | 499 ------- src/com/iciql/OrderExpression.java | 55 - src/com/iciql/Query.java | 947 ------------- src/com/iciql/QueryBetween.java | 60 - src/com/iciql/QueryCondition.java | 128 -- src/com/iciql/QueryJoin.java | 75 - src/com/iciql/QueryJoinCondition.java | 83 -- src/com/iciql/QueryWhere.java | 501 ------- src/com/iciql/RuntimeParameter.java | 49 - src/com/iciql/RuntimeToken.java | 57 - src/com/iciql/SQLDialect.java | 206 --- src/com/iciql/SQLDialectDefault.java | 445 ------ src/com/iciql/SQLDialectDerby.java | 71 - src/com/iciql/SQLDialectH2.java | 135 -- src/com/iciql/SQLDialectHSQL.java | 149 -- src/com/iciql/SQLDialectMSSQL.java | 53 - src/com/iciql/SQLDialectMySQL.java | 93 -- src/com/iciql/SQLDialectPostgreSQL.java | 103 -- src/com/iciql/SQLStatement.java | 190 --- src/com/iciql/SelectColumn.java | 57 - src/com/iciql/SelectTable.java | 112 -- src/com/iciql/SubQuery.java | 32 - src/com/iciql/SubQueryCondition.java | 41 - src/com/iciql/TableDefinition.java | 1233 ----------------- src/com/iciql/TableInspector.java | 723 ---------- src/com/iciql/TestCondition.java | 115 -- src/com/iciql/Token.java | 35 - src/com/iciql/UpdateColumn.java | 35 - src/com/iciql/UpdateColumnIncrement.java | 55 - src/com/iciql/UpdateColumnSet.java | 63 - src/com/iciql/ValidationRemark.java | 127 -- src/com/iciql/bytecode/And.java | 46 - src/com/iciql/bytecode/ArrayGet.java | 49 - src/com/iciql/bytecode/CaseWhen.java | 62 - src/com/iciql/bytecode/ClassReader.java | 1457 -------------------- src/com/iciql/bytecode/Constant.java | 38 - src/com/iciql/bytecode/ConstantNumber.java | 70 - src/com/iciql/bytecode/ConstantString.java | 55 - src/com/iciql/bytecode/Function.java | 47 - src/com/iciql/bytecode/Not.java | 55 - src/com/iciql/bytecode/Null.java | 44 - src/com/iciql/bytecode/Operation.java | 111 -- src/com/iciql/bytecode/Or.java | 47 - src/com/iciql/bytecode/Variable.java | 51 - src/com/iciql/bytecode/package.html | 25 - src/com/iciql/package.html | 25 - src/com/iciql/util/GenerateModels.java | 193 --- src/com/iciql/util/IciqlLogger.java | 214 --- src/com/iciql/util/JdbcUtils.java | 254 ---- src/com/iciql/util/Slf4jIciqlListener.java | 92 -- src/com/iciql/util/StatementBuilder.java | 166 --- src/com/iciql/util/StringUtils.java | 382 ----- src/com/iciql/util/Utils.java | 459 ------ src/com/iciql/util/WeakIdentityHashMap.java | 243 ---- src/com/iciql/util/package.html | 25 - src/main/java/com/iciql/CompareType.java | 45 + src/main/java/com/iciql/Condition.java | 55 + src/main/java/com/iciql/ConditionAndOr.java | 37 + src/main/java/com/iciql/Constants.java | 38 + src/main/java/com/iciql/Db.java | 774 +++++++++++ src/main/java/com/iciql/DbInspector.java | 204 +++ src/main/java/com/iciql/DbUpgrader.java | 81 ++ src/main/java/com/iciql/DbVersion.java | 55 + src/main/java/com/iciql/Define.java | 145 ++ src/main/java/com/iciql/Filter.java | 25 + src/main/java/com/iciql/Function.java | 149 ++ src/main/java/com/iciql/Iciql.java | 731 ++++++++++ src/main/java/com/iciql/IciqlException.java | 177 +++ src/main/java/com/iciql/ModelUtils.java | 499 +++++++ src/main/java/com/iciql/OrderExpression.java | 55 + src/main/java/com/iciql/Query.java | 947 +++++++++++++ src/main/java/com/iciql/QueryBetween.java | 60 + src/main/java/com/iciql/QueryCondition.java | 128 ++ src/main/java/com/iciql/QueryJoin.java | 75 + src/main/java/com/iciql/QueryJoinCondition.java | 83 ++ src/main/java/com/iciql/QueryWhere.java | 501 +++++++ src/main/java/com/iciql/RuntimeParameter.java | 49 + src/main/java/com/iciql/RuntimeToken.java | 57 + src/main/java/com/iciql/SQLDialect.java | 206 +++ src/main/java/com/iciql/SQLDialectDefault.java | 445 ++++++ src/main/java/com/iciql/SQLDialectDerby.java | 71 + src/main/java/com/iciql/SQLDialectH2.java | 135 ++ src/main/java/com/iciql/SQLDialectHSQL.java | 149 ++ src/main/java/com/iciql/SQLDialectMSSQL.java | 53 + src/main/java/com/iciql/SQLDialectMySQL.java | 93 ++ src/main/java/com/iciql/SQLDialectPostgreSQL.java | 103 ++ src/main/java/com/iciql/SQLStatement.java | 190 +++ src/main/java/com/iciql/SelectColumn.java | 57 + src/main/java/com/iciql/SelectTable.java | 112 ++ src/main/java/com/iciql/SubQuery.java | 32 + src/main/java/com/iciql/SubQueryCondition.java | 41 + src/main/java/com/iciql/TableDefinition.java | 1233 +++++++++++++++++ src/main/java/com/iciql/TableInspector.java | 723 ++++++++++ src/main/java/com/iciql/TestCondition.java | 115 ++ src/main/java/com/iciql/Token.java | 35 + src/main/java/com/iciql/UpdateColumn.java | 35 + src/main/java/com/iciql/UpdateColumnIncrement.java | 55 + src/main/java/com/iciql/UpdateColumnSet.java | 63 + src/main/java/com/iciql/ValidationRemark.java | 127 ++ src/main/java/com/iciql/bytecode/And.java | 46 + src/main/java/com/iciql/bytecode/ArrayGet.java | 49 + src/main/java/com/iciql/bytecode/CaseWhen.java | 62 + src/main/java/com/iciql/bytecode/ClassReader.java | 1457 ++++++++++++++++++++ src/main/java/com/iciql/bytecode/Constant.java | 38 + .../java/com/iciql/bytecode/ConstantNumber.java | 70 + .../java/com/iciql/bytecode/ConstantString.java | 55 + src/main/java/com/iciql/bytecode/Function.java | 47 + src/main/java/com/iciql/bytecode/Not.java | 55 + src/main/java/com/iciql/bytecode/Null.java | 44 + src/main/java/com/iciql/bytecode/Operation.java | 111 ++ src/main/java/com/iciql/bytecode/Or.java | 47 + src/main/java/com/iciql/bytecode/Variable.java | 51 + src/main/java/com/iciql/bytecode/package.html | 25 + src/main/java/com/iciql/package.html | 25 + src/main/java/com/iciql/util/GenerateModels.java | 193 +++ src/main/java/com/iciql/util/IciqlLogger.java | 214 +++ src/main/java/com/iciql/util/JdbcUtils.java | 254 ++++ .../java/com/iciql/util/Slf4jIciqlListener.java | 92 ++ src/main/java/com/iciql/util/StatementBuilder.java | 166 +++ src/main/java/com/iciql/util/StringUtils.java | 382 +++++ src/main/java/com/iciql/util/Utils.java | 459 ++++++ .../java/com/iciql/util/WeakIdentityHashMap.java | 243 ++++ src/main/java/com/iciql/util/package.html | 25 + src/site/00_index.mkd | 63 + src/site/01_model_classes.mkd | 335 +++++ src/site/02_table_versioning.mkd | 29 + src/site/02_usage.mkd | 197 +++ src/site/03_performance.mkd | 22 + src/site/04_examples.mkd | 196 +++ src/site/04_tools.mkd | 95 ++ src/site/05_building.mkd | 35 + src/site/05_javadoc.mkd | 7 + src/site/05_releases.mkd | 223 +++ src/site/06_jaqu_comparison.mkd | 31 + src/site/custom.less | 40 + src/site/resources/.gitignore | 2 + src/site/resources/iciql-favicon.png | Bin 0 -> 386 bytes src/site/resources/iciql.png | Bin 0 -> 1888 bytes src/site/resources/iciql.xcf | Bin 0 -> 6543 bytes src/site/resources/iciql2.png | Bin 0 -> 2128 bytes src/site/resources/iciql2.xcf | Bin 0 -> 7193 bytes src/site/resources/iciql_white.png | Bin 0 -> 1829 bytes src/site/resources/javadoc.css | 52 + src/test/java/com/iciql/test/AliasMapTest.java | 139 ++ src/test/java/com/iciql/test/AnnotationsTest.java | 196 +++ src/test/java/com/iciql/test/BooleanModelTest.java | 128 ++ src/test/java/com/iciql/test/ClobTest.java | 115 ++ src/test/java/com/iciql/test/ConcurrencyTest.java | 199 +++ .../java/com/iciql/test/DefaultValuesTest.java | 61 + src/test/java/com/iciql/test/EnumsTest.java | 129 ++ src/test/java/com/iciql/test/ForeignKeyTest.java | 81 ++ src/test/java/com/iciql/test/IciqlSuite.java | 614 +++++++++ src/test/java/com/iciql/test/JoinTest.java | 173 +++ src/test/java/com/iciql/test/ModelsTest.java | 145 ++ src/test/java/com/iciql/test/PrimitivesTest.java | 102 ++ src/test/java/com/iciql/test/RuntimeQueryTest.java | 199 +++ src/test/java/com/iciql/test/SamplesTest.java | 442 ++++++ src/test/java/com/iciql/test/TransactionTest.java | 119 ++ src/test/java/com/iciql/test/UUIDTest.java | 115 ++ src/test/java/com/iciql/test/UpdateTest.java | 160 +++ src/test/java/com/iciql/test/UpgradesTest.java | 180 +++ src/test/java/com/iciql/test/ViewsTest.java | 114 ++ .../java/com/iciql/test/models/BooleanModel.java | 73 + .../iciql/test/models/CategoryAnnotationOnly.java | 70 + .../java/com/iciql/test/models/ComplexObject.java | 66 + src/test/java/com/iciql/test/models/Customer.java | 57 + .../com/iciql/test/models/DefaultValuesModel.java | 58 + .../java/com/iciql/test/models/EnumModels.java | 157 +++ .../com/iciql/test/models/MultipleBoolsModel.java | 40 + src/test/java/com/iciql/test/models/Order.java | 73 + .../com/iciql/test/models/PrimitivesModel.java | 90 ++ src/test/java/com/iciql/test/models/Product.java | 85 ++ .../iciql/test/models/ProductAnnotationOnly.java | 94 ++ .../ProductAnnotationOnlyWithForeignKey.java | 102 ++ .../test/models/ProductInheritedAnnotation.java | 64 + .../iciql/test/models/ProductMixedAnnotation.java | 104 ++ .../iciql/test/models/ProductNoCreateTable.java | 59 + .../java/com/iciql/test/models/ProductView.java | 47 + .../iciql/test/models/ProductViewFromQuery.java | 42 + .../iciql/test/models/ProductViewInherited.java | 44 + .../test/models/ProductViewInheritedComplex.java | 28 + .../java/com/iciql/test/models/StaticQueries.java | 87 ++ .../java/com/iciql/test/models/SupportedTypes.java | 199 +++ 196 files changed, 19630 insertions(+), 13253 deletions(-) delete mode 100644 src/com/iciql/CompareType.java delete mode 100644 src/com/iciql/Condition.java delete mode 100644 src/com/iciql/ConditionAndOr.java delete mode 100644 src/com/iciql/Constants.java delete mode 100644 src/com/iciql/Db.java delete mode 100644 src/com/iciql/DbInspector.java delete mode 100644 src/com/iciql/DbUpgrader.java delete mode 100644 src/com/iciql/DbVersion.java delete mode 100644 src/com/iciql/Define.java delete mode 100644 src/com/iciql/Filter.java delete mode 100644 src/com/iciql/Function.java delete mode 100644 src/com/iciql/Iciql.java delete mode 100644 src/com/iciql/IciqlException.java delete mode 100644 src/com/iciql/ModelUtils.java delete mode 100644 src/com/iciql/OrderExpression.java delete mode 100644 src/com/iciql/Query.java delete mode 100644 src/com/iciql/QueryBetween.java delete mode 100644 src/com/iciql/QueryCondition.java delete mode 100644 src/com/iciql/QueryJoin.java delete mode 100644 src/com/iciql/QueryJoinCondition.java delete mode 100644 src/com/iciql/QueryWhere.java delete mode 100644 src/com/iciql/RuntimeParameter.java delete mode 100644 src/com/iciql/RuntimeToken.java delete mode 100644 src/com/iciql/SQLDialect.java delete mode 100644 src/com/iciql/SQLDialectDefault.java delete mode 100644 src/com/iciql/SQLDialectDerby.java delete mode 100644 src/com/iciql/SQLDialectH2.java delete mode 100644 src/com/iciql/SQLDialectHSQL.java delete mode 100644 src/com/iciql/SQLDialectMSSQL.java delete mode 100644 src/com/iciql/SQLDialectMySQL.java delete mode 100644 src/com/iciql/SQLDialectPostgreSQL.java delete mode 100644 src/com/iciql/SQLStatement.java delete mode 100644 src/com/iciql/SelectColumn.java delete mode 100644 src/com/iciql/SelectTable.java delete mode 100644 src/com/iciql/SubQuery.java delete mode 100644 src/com/iciql/SubQueryCondition.java delete mode 100644 src/com/iciql/TableDefinition.java delete mode 100644 src/com/iciql/TableInspector.java delete mode 100644 src/com/iciql/TestCondition.java delete mode 100644 src/com/iciql/Token.java delete mode 100644 src/com/iciql/UpdateColumn.java delete mode 100644 src/com/iciql/UpdateColumnIncrement.java delete mode 100644 src/com/iciql/UpdateColumnSet.java delete mode 100644 src/com/iciql/ValidationRemark.java delete mode 100644 src/com/iciql/bytecode/And.java delete mode 100644 src/com/iciql/bytecode/ArrayGet.java delete mode 100644 src/com/iciql/bytecode/CaseWhen.java delete mode 100644 src/com/iciql/bytecode/ClassReader.java delete mode 100644 src/com/iciql/bytecode/Constant.java delete mode 100644 src/com/iciql/bytecode/ConstantNumber.java delete mode 100644 src/com/iciql/bytecode/ConstantString.java delete mode 100644 src/com/iciql/bytecode/Function.java delete mode 100644 src/com/iciql/bytecode/Not.java delete mode 100644 src/com/iciql/bytecode/Null.java delete mode 100644 src/com/iciql/bytecode/Operation.java delete mode 100644 src/com/iciql/bytecode/Or.java delete mode 100644 src/com/iciql/bytecode/Variable.java delete mode 100644 src/com/iciql/bytecode/package.html delete mode 100644 src/com/iciql/package.html delete mode 100644 src/com/iciql/util/GenerateModels.java delete mode 100644 src/com/iciql/util/IciqlLogger.java delete mode 100644 src/com/iciql/util/JdbcUtils.java delete mode 100644 src/com/iciql/util/Slf4jIciqlListener.java delete mode 100644 src/com/iciql/util/StatementBuilder.java delete mode 100644 src/com/iciql/util/StringUtils.java delete mode 100644 src/com/iciql/util/Utils.java delete mode 100644 src/com/iciql/util/WeakIdentityHashMap.java delete mode 100644 src/com/iciql/util/package.html create mode 100644 src/main/java/com/iciql/CompareType.java create mode 100644 src/main/java/com/iciql/Condition.java create mode 100644 src/main/java/com/iciql/ConditionAndOr.java create mode 100644 src/main/java/com/iciql/Constants.java create mode 100644 src/main/java/com/iciql/Db.java create mode 100644 src/main/java/com/iciql/DbInspector.java create mode 100644 src/main/java/com/iciql/DbUpgrader.java create mode 100644 src/main/java/com/iciql/DbVersion.java create mode 100644 src/main/java/com/iciql/Define.java create mode 100644 src/main/java/com/iciql/Filter.java create mode 100644 src/main/java/com/iciql/Function.java create mode 100644 src/main/java/com/iciql/Iciql.java create mode 100644 src/main/java/com/iciql/IciqlException.java create mode 100644 src/main/java/com/iciql/ModelUtils.java create mode 100644 src/main/java/com/iciql/OrderExpression.java create mode 100644 src/main/java/com/iciql/Query.java create mode 100644 src/main/java/com/iciql/QueryBetween.java create mode 100644 src/main/java/com/iciql/QueryCondition.java create mode 100644 src/main/java/com/iciql/QueryJoin.java create mode 100644 src/main/java/com/iciql/QueryJoinCondition.java create mode 100644 src/main/java/com/iciql/QueryWhere.java create mode 100644 src/main/java/com/iciql/RuntimeParameter.java create mode 100644 src/main/java/com/iciql/RuntimeToken.java create mode 100644 src/main/java/com/iciql/SQLDialect.java create mode 100644 src/main/java/com/iciql/SQLDialectDefault.java create mode 100644 src/main/java/com/iciql/SQLDialectDerby.java create mode 100644 src/main/java/com/iciql/SQLDialectH2.java create mode 100644 src/main/java/com/iciql/SQLDialectHSQL.java create mode 100644 src/main/java/com/iciql/SQLDialectMSSQL.java create mode 100644 src/main/java/com/iciql/SQLDialectMySQL.java create mode 100644 src/main/java/com/iciql/SQLDialectPostgreSQL.java create mode 100644 src/main/java/com/iciql/SQLStatement.java create mode 100644 src/main/java/com/iciql/SelectColumn.java create mode 100644 src/main/java/com/iciql/SelectTable.java create mode 100644 src/main/java/com/iciql/SubQuery.java create mode 100644 src/main/java/com/iciql/SubQueryCondition.java create mode 100644 src/main/java/com/iciql/TableDefinition.java create mode 100644 src/main/java/com/iciql/TableInspector.java create mode 100644 src/main/java/com/iciql/TestCondition.java create mode 100644 src/main/java/com/iciql/Token.java create mode 100644 src/main/java/com/iciql/UpdateColumn.java create mode 100644 src/main/java/com/iciql/UpdateColumnIncrement.java create mode 100644 src/main/java/com/iciql/UpdateColumnSet.java create mode 100644 src/main/java/com/iciql/ValidationRemark.java create mode 100644 src/main/java/com/iciql/bytecode/And.java create mode 100644 src/main/java/com/iciql/bytecode/ArrayGet.java create mode 100644 src/main/java/com/iciql/bytecode/CaseWhen.java create mode 100644 src/main/java/com/iciql/bytecode/ClassReader.java create mode 100644 src/main/java/com/iciql/bytecode/Constant.java create mode 100644 src/main/java/com/iciql/bytecode/ConstantNumber.java create mode 100644 src/main/java/com/iciql/bytecode/ConstantString.java create mode 100644 src/main/java/com/iciql/bytecode/Function.java create mode 100644 src/main/java/com/iciql/bytecode/Not.java create mode 100644 src/main/java/com/iciql/bytecode/Null.java create mode 100644 src/main/java/com/iciql/bytecode/Operation.java create mode 100644 src/main/java/com/iciql/bytecode/Or.java create mode 100644 src/main/java/com/iciql/bytecode/Variable.java create mode 100644 src/main/java/com/iciql/bytecode/package.html create mode 100644 src/main/java/com/iciql/package.html create mode 100644 src/main/java/com/iciql/util/GenerateModels.java create mode 100644 src/main/java/com/iciql/util/IciqlLogger.java create mode 100644 src/main/java/com/iciql/util/JdbcUtils.java create mode 100644 src/main/java/com/iciql/util/Slf4jIciqlListener.java create mode 100644 src/main/java/com/iciql/util/StatementBuilder.java create mode 100644 src/main/java/com/iciql/util/StringUtils.java create mode 100644 src/main/java/com/iciql/util/Utils.java create mode 100644 src/main/java/com/iciql/util/WeakIdentityHashMap.java create mode 100644 src/main/java/com/iciql/util/package.html create mode 100644 src/site/00_index.mkd create mode 100644 src/site/01_model_classes.mkd create mode 100644 src/site/02_table_versioning.mkd create mode 100644 src/site/02_usage.mkd create mode 100644 src/site/03_performance.mkd create mode 100644 src/site/04_examples.mkd create mode 100644 src/site/04_tools.mkd create mode 100644 src/site/05_building.mkd create mode 100644 src/site/05_javadoc.mkd create mode 100644 src/site/05_releases.mkd create mode 100644 src/site/06_jaqu_comparison.mkd create mode 100644 src/site/custom.less create mode 100644 src/site/resources/.gitignore create mode 100644 src/site/resources/iciql-favicon.png create mode 100644 src/site/resources/iciql.png create mode 100644 src/site/resources/iciql.xcf create mode 100644 src/site/resources/iciql2.png create mode 100644 src/site/resources/iciql2.xcf create mode 100644 src/site/resources/iciql_white.png create mode 100644 src/site/resources/javadoc.css create mode 100644 src/test/java/com/iciql/test/AliasMapTest.java create mode 100644 src/test/java/com/iciql/test/AnnotationsTest.java create mode 100644 src/test/java/com/iciql/test/BooleanModelTest.java create mode 100644 src/test/java/com/iciql/test/ClobTest.java create mode 100644 src/test/java/com/iciql/test/ConcurrencyTest.java create mode 100644 src/test/java/com/iciql/test/DefaultValuesTest.java create mode 100644 src/test/java/com/iciql/test/EnumsTest.java create mode 100644 src/test/java/com/iciql/test/ForeignKeyTest.java create mode 100644 src/test/java/com/iciql/test/IciqlSuite.java create mode 100644 src/test/java/com/iciql/test/JoinTest.java create mode 100644 src/test/java/com/iciql/test/ModelsTest.java create mode 100644 src/test/java/com/iciql/test/PrimitivesTest.java create mode 100644 src/test/java/com/iciql/test/RuntimeQueryTest.java create mode 100644 src/test/java/com/iciql/test/SamplesTest.java create mode 100644 src/test/java/com/iciql/test/TransactionTest.java create mode 100644 src/test/java/com/iciql/test/UUIDTest.java create mode 100644 src/test/java/com/iciql/test/UpdateTest.java create mode 100644 src/test/java/com/iciql/test/UpgradesTest.java create mode 100644 src/test/java/com/iciql/test/ViewsTest.java create mode 100644 src/test/java/com/iciql/test/models/BooleanModel.java create mode 100644 src/test/java/com/iciql/test/models/CategoryAnnotationOnly.java create mode 100644 src/test/java/com/iciql/test/models/ComplexObject.java create mode 100644 src/test/java/com/iciql/test/models/Customer.java create mode 100644 src/test/java/com/iciql/test/models/DefaultValuesModel.java create mode 100644 src/test/java/com/iciql/test/models/EnumModels.java create mode 100644 src/test/java/com/iciql/test/models/MultipleBoolsModel.java create mode 100644 src/test/java/com/iciql/test/models/Order.java create mode 100644 src/test/java/com/iciql/test/models/PrimitivesModel.java create mode 100644 src/test/java/com/iciql/test/models/Product.java create mode 100644 src/test/java/com/iciql/test/models/ProductAnnotationOnly.java create mode 100644 src/test/java/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java create mode 100644 src/test/java/com/iciql/test/models/ProductInheritedAnnotation.java create mode 100644 src/test/java/com/iciql/test/models/ProductMixedAnnotation.java create mode 100644 src/test/java/com/iciql/test/models/ProductNoCreateTable.java create mode 100644 src/test/java/com/iciql/test/models/ProductView.java create mode 100644 src/test/java/com/iciql/test/models/ProductViewFromQuery.java create mode 100644 src/test/java/com/iciql/test/models/ProductViewInherited.java create mode 100644 src/test/java/com/iciql/test/models/ProductViewInheritedComplex.java create mode 100644 src/test/java/com/iciql/test/models/StaticQueries.java create mode 100644 src/test/java/com/iciql/test/models/SupportedTypes.java (limited to 'src') diff --git a/src/com/iciql/CompareType.java b/src/com/iciql/CompareType.java deleted file mode 100644 index 84e29fe..0000000 --- a/src/com/iciql/CompareType.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * An enumeration of compare operations. - */ - -enum CompareType { - EQUAL("=", true), EXCEEDS(">", true), AT_LEAST(">=", true), LESS_THAN("<", true), AT_MOST("<=", true), NOT_EQUAL( - "<>", true), IS_NOT_NULL("IS NOT NULL", false), IS_NULL("IS NULL", false), LIKE("LIKE", true), BETWEEN( - "BETWEEN", true); - - private String text; - private boolean hasRightExpression; - - CompareType(String text, boolean hasRightExpression) { - this.text = text; - this.hasRightExpression = hasRightExpression; - } - - String getString() { - return text; - } - - boolean hasRightExpression() { - return hasRightExpression; - } - -} diff --git a/src/com/iciql/Condition.java b/src/com/iciql/Condition.java deleted file mode 100644 index 17cb117..0000000 --- a/src/com/iciql/Condition.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * A condition contains one or two operands and a compare operation. - * - * @param - * the operand type - */ - -class Condition implements Token { - CompareType compareType; - A x, y, z; - - Condition(A x, A y, CompareType compareType) { - this(x, y, null, compareType); - } - - Condition(A x, A y, A z, CompareType compareType) { - this.compareType = compareType; - this.x = x; - this.y = y; - this.z = z; - } - - public void appendSQL(SQLStatement stat, Query query) { - query.appendSQL(stat, null, x); - stat.appendSQL(" "); - stat.appendSQL(compareType.getString()); - if (compareType.hasRightExpression()) { - stat.appendSQL(" "); - if (z == null) { - query.appendSQL(stat, x, y); - } else { - query.appendSQL(stat, x, y, z, compareType); - } - } - } -} diff --git a/src/com/iciql/ConditionAndOr.java b/src/com/iciql/ConditionAndOr.java deleted file mode 100644 index 4d1cd0e..0000000 --- a/src/com/iciql/ConditionAndOr.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * An OR or an AND condition. - */ - -enum ConditionAndOr implements Token { - AND("AND"), OR("OR"); - - private String text; - - ConditionAndOr(String text) { - this.text = text; - } - - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL(text); - } - -} diff --git a/src/com/iciql/Constants.java b/src/com/iciql/Constants.java deleted file mode 100644 index 8b54493..0000000 --- a/src/com/iciql/Constants.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2011 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; - -/** - * Iciql constants. - */ -public class Constants { - - public static final String NAME = "iciql"; - - // The build script extracts this exact line so be careful editing it - // and only use A-Z a-z 0-9 .-_ in the string. - public static final String VERSION = "1.2.0-SNAPSHOT"; - - // The build script extracts this exact line so be careful editing it - // and only use A-Z a-z 0-9 .-_ in the string. - public static final String VERSION_DATE = "PENDING"; - - // The build script extracts this exact line so be careful editing it - // and only use A-Z a-z 0-9 .-_ in the string. - public static final String API_CURRENT = "15"; - -} diff --git a/src/com/iciql/Db.java b/src/com/iciql/Db.java deleted file mode 100644 index ecd373c..0000000 --- a/src/com/iciql/Db.java +++ /dev/null @@ -1,774 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 James Moger. - * Copyright 2012 Frédéric Gaillard. - * Copyright 2012 Alex Telepov. - * - * 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; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.sql.Savepoint; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.sql.DataSource; - -import com.iciql.DbUpgrader.DefaultDbUpgrader; -import com.iciql.Iciql.IQTable; -import com.iciql.Iciql.IQVersion; -import com.iciql.Iciql.IQView; -import com.iciql.util.IciqlLogger; -import com.iciql.util.JdbcUtils; -import com.iciql.util.StringUtils; -import com.iciql.util.Utils; -import com.iciql.util.WeakIdentityHashMap; - -/** - * This class represents a connection to a database. - */ - -public class Db { - - /** - * This map It holds unique tokens that are generated by functions such as - * Function.sum(..) in "db.from(p).select(Function.sum(p.unitPrice))". It - * doesn't actually hold column tokens, as those are bound to the query - * itself. - */ - private static final Map TOKENS; - - private static final Map> DIALECTS; - - private final Connection conn; - private final Map, TableDefinition> classMap = Collections - .synchronizedMap(new HashMap, TableDefinition>()); - private final SQLDialect dialect; - private DbUpgrader dbUpgrader = new DefaultDbUpgrader(); - private final Set> upgradeChecked = Collections.synchronizedSet(new HashSet>()); - - private boolean skipCreate; - private boolean autoSavePoint = true; - - static { - TOKENS = Collections.synchronizedMap(new WeakIdentityHashMap()); - DIALECTS = Collections.synchronizedMap(new HashMap>()); - // can register by... - // 1. Connection class name - // 2. DatabaseMetaData.getDatabaseProductName() - DIALECTS.put("Apache Derby", SQLDialectDerby.class); - DIALECTS.put("H2", SQLDialectH2.class); - DIALECTS.put("HSQL Database Engine", SQLDialectHSQL.class); - DIALECTS.put("MySQL", SQLDialectMySQL.class); - DIALECTS.put("PostgreSQL", SQLDialectPostgreSQL.class); - DIALECTS.put("Microsoft SQL Server", SQLDialectMSSQL.class); - } - - private Db(Connection conn) { - this.conn = conn; - String databaseName = null; - DatabaseMetaData data = null; - try { - data = conn.getMetaData(); - databaseName = data.getDatabaseProductName(); - } catch (SQLException s) { - throw new IciqlException(s, "failed to retrieve database metadata!"); - } - dialect = getDialect(databaseName, conn.getClass().getName()); - dialect.configureDialect(databaseName, data); - } - - /** - * Register a new/custom dialect class. You can use this method to replace - * any existing dialect or to add a new one. - * - * @param token - * the fully qualified name of the connection class or the - * expected result of DatabaseMetaData.getDatabaseProductName() - * @param dialectClass - * the dialect class to register - */ - public static void registerDialect(String token, Class dialectClass) { - DIALECTS.put(token, dialectClass); - } - - SQLDialect getDialect(String databaseName, String className) { - Class dialectClass = null; - if (DIALECTS.containsKey(className)) { - // dialect registered by connection class name - dialectClass = DIALECTS.get(className); - } else if (DIALECTS.containsKey(databaseName)) { - // dialect registered by database name - dialectClass = DIALECTS.get(databaseName); - } else { - // did not find a match, use default - dialectClass = SQLDialectDefault.class; - } - return instance(dialectClass); - } - - static X registerToken(X x, Token token) { - TOKENS.put(x, token); - return x; - } - - static Token getToken(Object x) { - return TOKENS.get(x); - } - - static T instance(Class clazz) { - try { - return clazz.newInstance(); - } catch (Exception e) { - throw new IciqlException(e); - } - } - - public static Db open(String url) { - try { - Connection conn = JdbcUtils.getConnection(null, url, null, null); - return new Db(conn); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - public static Db open(String url, String user, String password) { - try { - Connection conn = JdbcUtils.getConnection(null, url, user, password); - return new Db(conn); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - public static Db open(String url, String user, char[] password) { - try { - Connection conn = JdbcUtils.getConnection(null, url, user, password == null ? null : new String(password)); - return new Db(conn); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - /** - * Create a new database instance using a data source. This method is fast, - * so that you can always call open() / close() on usage. - * - * @param ds - * the data source - * @return the database instance. - */ - public static Db open(DataSource ds) { - try { - return new Db(ds.getConnection()); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - public static Db open(Connection conn) { - return new Db(conn); - } - - - - /** - * Convenience function to avoid import statements in application code. - */ - public void activateConsoleLogger() { - IciqlLogger.activateConsoleLogger(); - } - - /** - * Convenience function to avoid import statements in application code. - */ - public void deactivateConsoleLogger() { - IciqlLogger.deactivateConsoleLogger(); - } - - public void insert(T t) { - Class clazz = t.getClass(); - long rc = define(clazz).createIfRequired(this).insert(this, t, false); - if (rc == 0) { - throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t); - } - } - - public long insertAndGetKey(T t) { - Class clazz = t.getClass(); - return define(clazz).createIfRequired(this).insert(this, t, true); - } - - /** - * Merge INSERTS if the record does not exist or UPDATES the record if it - * does exist. Not all databases support MERGE and the syntax varies with - * the database. - * - * If the database does not support a MERGE syntax the dialect can try to - * simulate a merge by implementing: - *

- * INSERT INTO foo... (SELECT ?,... FROM foo WHERE pk=? HAVING count(*)=0) - *

- * iciql will check the affected row count returned by the internal merge - * method and if the affected row count = 0, it will issue an update. - *

- * See the Derby dialect for an implementation of this technique. - *

- * If the dialect does not support merge an IciqlException will be thrown. - * - * @param t - */ - public void merge(T t) { - Class clazz = t.getClass(); - TableDefinition def = define(clazz).createIfRequired(this); - int rc = def.merge(this, t); - if (rc == 0) { - rc = def.update(this, t); - } - if (rc == 0) { - throw new IciqlException("merge failed"); - } - } - - public int update(T t) { - Class clazz = t.getClass(); - return define(clazz).createIfRequired(this).update(this, t); - } - - public int delete(T t) { - Class clazz = t.getClass(); - return define(clazz).createIfRequired(this).delete(this, t); - } - - public Query from(T alias) { - Class clazz = alias.getClass(); - define(clazz).createIfRequired(this); - return Query.from(this, alias); - } - - @SuppressWarnings("unchecked") - public int dropTable(Class modelClass) { - TableDefinition def = (TableDefinition) define(modelClass); - SQLStatement stat = new SQLStatement(this); - getDialect().prepareDropTable(stat, def); - IciqlLogger.drop(stat.getSQL()); - int rc = 0; - try { - rc = stat.executeUpdate(); - } catch (IciqlException e) { - if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) { - throw e; - } - } - // remove this model class from the table definition cache - classMap.remove(modelClass); - // remove this model class from the upgrade checked cache - upgradeChecked.remove(modelClass); - return rc; - } - - @SuppressWarnings("unchecked") - public int dropView(Class modelClass) { - TableDefinition def = (TableDefinition) define(modelClass); - SQLStatement stat = new SQLStatement(this); - getDialect().prepareDropView(stat, def); - IciqlLogger.drop(stat.getSQL()); - int rc = 0; - try { - rc = stat.executeUpdate(); - } catch (IciqlException e) { - if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) { - throw e; - } - } - // remove this model class from the table definition cache - classMap.remove(modelClass); - // remove this model class from the upgrade checked cache - upgradeChecked.remove(modelClass); - return rc; - } - - public List buildObjects(Class modelClass, ResultSet rs) { - return buildObjects(modelClass, false, rs); - } - - @SuppressWarnings("unchecked") - public List buildObjects(Class modelClass, boolean wildcardSelect, ResultSet rs) { - List result = new ArrayList(); - TableDefinition def = (TableDefinition) define(modelClass); - try { - int[] columns = def.mapColumns(wildcardSelect, rs); - while (rs.next()) { - T item = Utils.newObject(modelClass); - def.readRow(item, rs, columns); - result.add(item); - } - } catch (SQLException e) { - throw new IciqlException(e); - } - return result; - } - - Db upgradeDb() { - if (!upgradeChecked.contains(dbUpgrader.getClass())) { - // flag as checked immediately because calls are nested. - upgradeChecked.add(dbUpgrader.getClass()); - - IQVersion model = dbUpgrader.getClass().getAnnotation(IQVersion.class); - if (model.value() == 0) { - // try superclass - Class superClass = dbUpgrader.getClass().getSuperclass(); - if (superClass.isAnnotationPresent(IQVersion.class)) { - model = superClass.getAnnotation(IQVersion.class); - } - } - if (model.value() > 0) { - DbVersion v = new DbVersion(); - // (SCHEMA="" && TABLE="") == DATABASE - DbVersion dbVersion = from(v).where(v.schemaName).is("").and(v.tableName).is("") - .selectFirst(); - if (dbVersion == null) { - // database has no version registration, but model specifies - // version: insert DbVersion entry and return. - DbVersion newDb = new DbVersion(model.value()); - // database is an older version than the model - boolean success = dbUpgrader.upgradeDatabase(this, 0, newDb.version); - if (success) { - insert(newDb); - } - } else { - // database has a version registration: - // check to see if upgrade is required. - if ((model.value() > dbVersion.version) && (dbUpgrader != null)) { - // database is an older version than the model - boolean success = dbUpgrader.upgradeDatabase(this, dbVersion.version, model.value()); - if (success) { - dbVersion.version = model.value(); - update(dbVersion); - } - } - } - } - } - return this; - } - - void upgradeTable(TableDefinition model) { - if (!upgradeChecked.contains(model.getModelClass())) { - // flag is checked immediately because calls are nested - upgradeChecked.add(model.getModelClass()); - - if (model.tableVersion > 0) { - // table is using iciql version tracking. - DbVersion v = new DbVersion(); - String schema = StringUtils.isNullOrEmpty(model.schemaName) ? "" : model.schemaName; - DbVersion dbVersion = from(v).where(v.schemaName).is(schema).and(v.tableName) - .is(model.tableName).selectFirst(); - if (dbVersion == null) { - // table has no version registration, but model specifies - // version: insert DbVersion entry - DbVersion newTable = new DbVersion(model.tableVersion); - newTable.schemaName = schema; - newTable.tableName = model.tableName; - insert(newTable); - } else { - // table has a version registration: - // check if upgrade is required - if ((model.tableVersion > dbVersion.version) && (dbUpgrader != null)) { - // table is an older version than model - boolean success = dbUpgrader.upgradeTable(this, schema, model.tableName, - dbVersion.version, model.tableVersion); - if (success) { - dbVersion.version = model.tableVersion; - update(dbVersion); - } - } - } - } - } - } - - TableDefinition define(Class clazz) { - TableDefinition def = getTableDefinition(clazz); - if (def == null) { - upgradeDb(); - def = new TableDefinition(clazz); - def.mapFields(); - classMap.put(clazz, def); - if (Iciql.class.isAssignableFrom(clazz)) { - T t = instance(clazz); - Iciql table = (Iciql) t; - Define.define(def, table); - } else if (clazz.isAnnotationPresent(IQTable.class)) { - // annotated classes skip the Define().define() static - // initializer - T t = instance(clazz); - def.mapObject(t); - } else if (clazz.isAnnotationPresent(IQView.class)) { - // annotated classes skip the Define().define() static - // initializer - T t = instance(clazz); - def.mapObject(t); - } - } - return def; - } - - boolean hasCreated(Class clazz) { - return upgradeChecked.contains(clazz); - } - - public synchronized void setDbUpgrader(DbUpgrader upgrader) { - if (!upgrader.getClass().isAnnotationPresent(IQVersion.class)) { - throw new IciqlException("DbUpgrader must be annotated with " + IQVersion.class.getSimpleName()); - } - this.dbUpgrader = upgrader; - upgradeChecked.clear(); - } - - public SQLDialect getDialect() { - return dialect; - } - - public Connection getConnection() { - return conn; - } - - public void close() { - try { - conn.close(); - } catch (Exception e) { - throw new IciqlException(e); - } - } - - public TestCondition test(A x) { - return new TestCondition(x); - } - - public void insertAll(List list) { - if (list.size() == 0) { - return; - } - Savepoint savepoint = null; - try { - Class clazz = list.get(0).getClass(); - TableDefinition def = define(clazz).createIfRequired(this); - savepoint = prepareSavepoint(); - for (T t : list) { - PreparedStatement ps = def.createInsertStatement(this, t, false); - int rc = ps.executeUpdate(); - if (rc == 0) { - throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t); - } - } - commit(savepoint); - } catch (SQLException e) { - rollback(savepoint); - throw new IciqlException(e); - } catch (IciqlException e) { - rollback(savepoint); - throw e; - } - } - - public List insertAllAndGetKeys(List list) { - List identities = new ArrayList(); - if (list.size() == 0) { - return identities; - } - Savepoint savepoint = null; - try { - Class clazz = list.get(0).getClass(); - TableDefinition def = define(clazz).createIfRequired(this); - savepoint = prepareSavepoint(); - for (T t : list) { - long key = def.insert(this, t, true); - identities.add(key); - } - commit(savepoint); - } catch (IciqlException e) { - rollback(savepoint); - throw e; - } - return identities; - } - - public void updateAll(List list) { - if (list.size() == 0) { - return; - } - Savepoint savepoint = null; - try { - Class clazz = list.get(0).getClass(); - TableDefinition def = define(clazz).createIfRequired(this); - savepoint = prepareSavepoint(); - for (T t : list) { - def.update(this, t); - } - commit(savepoint); - } catch (IciqlException e) { - rollback(savepoint); - throw e; - } - } - - public void deleteAll(List list) { - if (list.size() == 0) { - return; - } - Savepoint savepoint = null; - try { - Class clazz = list.get(0).getClass(); - TableDefinition def = define(clazz).createIfRequired(this); - savepoint = prepareSavepoint(); - for (T t : list) { - def.delete(this, t); - } - commit(savepoint); - } catch (IciqlException e) { - rollback(savepoint); - throw e; - } - } - - PreparedStatement prepare(String sql, boolean returnGeneratedKeys) { - IciqlException.checkUnmappedField(sql); - try { - if (returnGeneratedKeys) { - return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); - } - return conn.prepareStatement(sql); - } catch (SQLException e) { - throw IciqlException.fromSQL(sql, e); - } - } - - Savepoint prepareSavepoint() { - // don't change auto-commit mode. - // don't create save point. - if (!autoSavePoint) { - return null; - } - // create a savepoint - Savepoint savepoint = null; - try { - conn.setAutoCommit(false); - savepoint = conn.setSavepoint(); - } catch (SQLFeatureNotSupportedException e) { - // jdbc driver does not support save points - } catch (SQLException e) { - throw new IciqlException(e, "Could not create save point"); - } - return savepoint; - } - - void commit(Savepoint savepoint) { - if (savepoint != null) { - try { - conn.commit(); - conn.setAutoCommit(true); - } catch (SQLException e) { - throw new IciqlException(e, "Failed to commit pending transactions"); - } - } - } - - void rollback(Savepoint savepoint) { - if (savepoint != null) { - try { - conn.rollback(savepoint); - conn.setAutoCommit(true); - } catch (SQLException s) { - throw new IciqlException(s, "Failed to rollback transactions"); - } - } - } - - @SuppressWarnings("unchecked") - TableDefinition getTableDefinition(Class clazz) { - return (TableDefinition) classMap.get(clazz); - } - - /** - * Run a SQL query directly against the database. - * - * Be sure to close the ResultSet with - * - *

-	 * JdbcUtils.closeSilently(rs, true);
-	 * 
- * - * @param sql - * the SQL statement - * @param args - * optional object arguments for x=? tokens in query - * @return the result set - */ - public ResultSet executeQuery(String sql, List args) { - return executeQuery(sql, args.toArray()); - } - - /** - * Run a SQL query directly against the database. - * - * Be sure to close the ResultSet with - * - *
-	 * JdbcUtils.closeSilently(rs, true);
-	 * 
- * - * @param sql - * the SQL statement - * @param args - * optional object arguments for x=? tokens in query - * @return the result set - */ - public ResultSet executeQuery(String sql, Object... args) { - try { - if (args.length == 0) { - return conn.createStatement().executeQuery(sql); - } else { - PreparedStatement stat = conn.prepareStatement(sql); - int i = 1; - for (Object arg : args) { - stat.setObject(i++, arg); - } - return stat.executeQuery(); - } - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - /** - * Run a SQL query directly against the database and map the results to the - * model class. - * - * @param modelClass - * the model class to bind the query ResultSet rows into. - * @param sql - * the SQL statement - * @return the result set - */ - public List executeQuery(Class modelClass, String sql, List args) { - return executeQuery(modelClass, sql, args.toArray()); - } - - /** - * Run a SQL query directly against the database and map the results to the - * model class. - * - * @param modelClass - * the model class to bind the query ResultSet rows into. - * @param sql - * the SQL statement - * @return the result set - */ - public List executeQuery(Class modelClass, String sql, Object... args) { - ResultSet rs = null; - try { - if (args.length == 0) { - rs = conn.createStatement().executeQuery(sql); - } else { - PreparedStatement stat = conn.prepareStatement(sql); - int i = 1; - for (Object arg : args) { - stat.setObject(i++, arg); - } - rs = stat.executeQuery(); - } - boolean wildcardSelect = sql.toLowerCase().startsWith("select *") - || sql.toLowerCase().startsWith("select distinct *"); - return buildObjects(modelClass, wildcardSelect, rs); - } catch (SQLException e) { - throw new IciqlException(e); - } finally { - JdbcUtils.closeSilently(rs, true); - } - } - - /** - * Run a SQL statement directly against the database. - * - * @param sql - * the SQL statement - * @return the update count - */ - public int executeUpdate(String sql, Object... args) { - Statement stat = null; - try { - int updateCount; - if (args.length == 0) { - stat = conn.createStatement(); - updateCount = stat.executeUpdate(sql); - } else { - PreparedStatement ps = conn.prepareStatement(sql); - int i = 1; - for (Object arg : args) { - ps.setObject(i++, arg); - } - updateCount = ps.executeUpdate(); - stat = ps; - } - return updateCount; - } catch (SQLException e) { - throw new IciqlException(e); - } finally { - JdbcUtils.closeSilently(stat); - } - } - - /** - * Allow to enable/disable globally createIfRequired in TableDefinition. - * For advanced user wanting to gain full control of transactions. - * Default value is false. - * @param skipCreate - */ - public void setSkipCreate(boolean skipCreate) { - this.skipCreate = skipCreate; - } - - public boolean getSkipCreate() { - return this.skipCreate; - } - - /** - * Allow to enable/disable usage of save point. - * For advanced user wanting to gain full control of transactions. - * Default value is false. - * @param autoSavePoint - */ - public void setAutoSavePoint(boolean autoSavePoint) { - this.autoSavePoint = autoSavePoint; - } - - public boolean getAutoSavePoint() { - return this.autoSavePoint; - } - -} diff --git a/src/com/iciql/DbInspector.java b/src/com/iciql/DbInspector.java deleted file mode 100644 index acaceea..0000000 --- a/src/com/iciql/DbInspector.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; - -import com.iciql.Iciql.IQTable; -import com.iciql.util.JdbcUtils; -import com.iciql.util.StringUtils; -import com.iciql.util.Utils; - -/** - * Class to inspect a model and a database for the purposes of model validation - * and automatic model generation. This class finds the available schemas and - * tables and serves as the entry point for model generation and validation. - */ -public class DbInspector { - - private Db db; - private DatabaseMetaData metaData; - private Class dateTimeClass = java.util.Date.class; - - public DbInspector(Db db) { - this.db = db; - setPreferredDateTimeClass(db.getDialect().getDateTimeClass()); - } - - /** - * Set the preferred class to store date and time. Possible values are: - * java.util.Date (default) and java.sql.Timestamp. - * - * @param dateTimeClass - * the new class - */ - public void setPreferredDateTimeClass(Class dateTimeClass) { - this.dateTimeClass = dateTimeClass; - } - - /** - * Generates models class skeletons for schemas and tables. If the table - * name is undefined, models will be generated for every table within the - * specified schema. Additionally, if no schema is defined, models will be - * generated for all schemas and all tables. - * - * @param schema - * the schema name (optional) - * @param table - * the table name (optional) - * @param packageName - * the package name (optional) - * @param annotateSchema - * (includes schema name in annotation) - * @param trimStrings - * (trims strings to maxLength of column) - * @return a list of complete model classes as strings, each element a class - */ - public List generateModel(String schema, String table, String packageName, - boolean annotateSchema, boolean trimStrings) { - try { - List models = Utils.newArrayList(); - List tables = getTables(schema, table); - for (TableInspector t : tables) { - t.read(metaData); - String model = t.generateModel(packageName, annotateSchema, trimStrings); - models.add(model); - } - return models; - } catch (SQLException s) { - throw new IciqlException(s); - } - } - - /** - * Validates a model. - * - * @param model - * an instance of the model class - * @param throwOnError - * if errors should cause validation to fail - * @return a list of validation remarks - */ - public List validateModel(T model, boolean throwOnError) { - try { - TableInspector inspector = getTable(model); - inspector.read(metaData); - @SuppressWarnings("unchecked") - Class clazz = (Class) model.getClass(); - TableDefinition def = db.define(clazz); - return inspector.validate(def, throwOnError); - } catch (SQLException s) { - throw new IciqlException(s); - } - } - - private DatabaseMetaData getMetaData() throws SQLException { - if (metaData == null) { - metaData = db.getConnection().getMetaData(); - } - return metaData; - } - - /** - * Get the table in the database based on the model definition. - * - * @param model - * an instance of the model class - * @return the table inspector - */ - private TableInspector getTable(T model) throws SQLException { - @SuppressWarnings("unchecked") - Class clazz = (Class) model.getClass(); - TableDefinition def = db.define(clazz); - boolean forceUpperCase = getMetaData().storesUpperCaseIdentifiers(); - String schema = (forceUpperCase && def.schemaName != null) ? def.schemaName.toUpperCase() - : def.schemaName; - String table = forceUpperCase ? def.tableName.toUpperCase() : def.tableName; - List tables = getTables(schema, table); - return tables.get(0); - } - - /** - * Returns a list of tables. This method always returns at least one - * element. If no table is found, an exception is thrown. - * - * @param schema - * the schema name - * @param table - * the table name - * @return a list of table inspectors (always contains at least one element) - */ - private List getTables(String schema, String table) throws SQLException { - ResultSet rs = null; - try { - rs = getMetaData().getSchemas(); - ArrayList schemaList = Utils.newArrayList(); - while (rs.next()) { - schemaList.add(rs.getString("TABLE_SCHEM")); - } - JdbcUtils.closeSilently(rs); - - String iciqlTables = DbVersion.class.getAnnotation(IQTable.class).name(); - - List tables = Utils.newArrayList(); - if (schemaList.size() == 0) { - schemaList.add(null); - } - for (String s : schemaList) { - rs = getMetaData().getTables(null, s, null, new String[] { "TABLE" }); - while (rs.next()) { - String t = rs.getString("TABLE_NAME"); - if (t.charAt(0) == '"') { - t = t.substring(1); - } - if (t.charAt(t.length() - 1) == '"') { - t = t.substring(0, t.length() - 1); - } - if (!t.equalsIgnoreCase(iciqlTables)) { - tables.add(new TableInspector(s, t, dateTimeClass)); - } - } - } - - if (StringUtils.isNullOrEmpty(schema) && StringUtils.isNullOrEmpty(table)) { - // all schemas and tables - return tables; - } - // schema subset OR table subset OR exact match - List matches = Utils.newArrayList(); - for (TableInspector t : tables) { - if (t.matches(schema, table)) { - matches.add(t); - } - } - if (matches.size() == 0) { - throw new IciqlException(MessageFormat.format("Failed to find schema={0} table={1}", - schema == null ? "" : schema, table == null ? "" : table)); - } - return matches; - } finally { - JdbcUtils.closeSilently(rs); - } - } - -} diff --git a/src/com/iciql/DbUpgrader.java b/src/com/iciql/DbUpgrader.java deleted file mode 100644 index 1303f4e..0000000 --- a/src/com/iciql/DbUpgrader.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import com.iciql.Iciql.IQVersion; - -/** - * Interface which defines a class to handle table changes based on model - * versions. An implementation of DbUpgrader must be annotated with the - * IQDatabase annotation, which defines the expected database version - * number. - */ -public interface DbUpgrader { - - /** - * Defines method interface to handle database upgrades. This method is only - * called if your DbUpgrader implementation is annotated with - * IQDatabase. - * - * @param db - * the database - * @param fromVersion - * the old version - * @param toVersion - * the new version - * @return true for successful upgrade. If the upgrade is successful, the - * version registry is automatically updated. - */ - boolean upgradeDatabase(Db db, int fromVersion, int toVersion); - - /** - * Defines method interface to handle table upgrades. - * - * @param db - * the database - * @param schema - * the schema - * @param table - * the table - * @param fromVersion - * the old version - * @param toVersion - * the new version - * @return true for successful upgrade. If the upgrade is successful, the - * version registry is automatically updated. - */ - boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion); - - /** - * The default database upgrader. It throws runtime exception instead of - * handling upgrade requests. - */ - @IQVersion(0) - public static class DefaultDbUpgrader implements DbUpgrader { - - public boolean upgradeDatabase(Db db, int fromVersion, int toVersion) { - throw new IciqlException("Please provide your own DbUpgrader implementation."); - } - - public boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion) { - throw new IciqlException("Please provide your own DbUpgrader implementation."); - } - - } - -} diff --git a/src/com/iciql/DbVersion.java b/src/com/iciql/DbVersion.java deleted file mode 100644 index 6270e14..0000000 --- a/src/com/iciql/DbVersion.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import com.iciql.Iciql.IQColumn; -import com.iciql.Iciql.IQTable; - -/** - * A system table to track database and table versions. - */ -@IQTable(name = "iq_versions", primaryKey = { "schemaName", "tableName" }, memoryTable = true) -public class DbVersion { - - @IQColumn(length = 255) - String schemaName = ""; - - @IQColumn(length = 255) - String tableName = ""; - - @IQColumn - Integer version; - - public DbVersion() { - // nothing to do - } - - /** - * Constructor for defining a version entry. Both the schema and the table - * are empty strings, which means this is the row for the 'database'. - * - * @param version - * the database version - */ - public DbVersion(int version) { - this.schemaName = ""; - this.tableName = ""; - this.version = version; - } - -} diff --git a/src/com/iciql/Define.java b/src/com/iciql/Define.java deleted file mode 100644 index 1810a4b..0000000 --- a/src/com/iciql/Define.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 James Moger. - * Copyright 2012 Frédéric Gaillard. - * - * 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; - -import com.iciql.Iciql.IndexType; - -/** - * This class provides utility methods to define primary keys, indexes, and set - * the name of the table. - */ - -public class Define { - - private static TableDefinition currentTableDefinition; - private static Iciql currentTable; - - public static void skipCreate() { - checkInDefine(); - currentTableDefinition.defineSkipCreate(); - } - - public static void index(IndexType type, Object... columns) { - checkInDefine(); - currentTableDefinition.defineIndex(null, type, columns); - } - - public static void index(String name, IndexType type, Object... columns) { - checkInDefine(); - currentTableDefinition.defineIndex(name, type, columns); - } - - public static void constraintUnique(String name, Object... columns) { - checkInDefine(); - currentTableDefinition.defineConstraintUnique(name, columns); - } - - /* - * The variable argument type Object can't be used twice :-) - */ -// public static void constraintForeignKey(String name, String refTableName, -// ConstraintDeleteType deleteType, ConstraintUpdateType updateType, -// ConstraintDeferrabilityType deferrabilityType, Object... columns, Object... refColumns) { -// checkInDefine(); -// currentTableDefinition.defineForeignKey(name, columns, refTableName, Columns, deleteType, updateType, deferrabilityType); -// } - - public static void primaryKey(Object... columns) { - checkInDefine(); - currentTableDefinition.definePrimaryKey(columns); - } - - public static void schemaName(String schemaName) { - checkInDefine(); - currentTableDefinition.defineSchemaName(schemaName); - } - - public static void tableName(String tableName) { - checkInDefine(); - currentTableDefinition.defineTableName(tableName); - } - - public static void viewTableName(String viewTableName) { - checkInDefine(); - currentTableDefinition.defineViewTableName(viewTableName); - } - - public static void memoryTable() { - checkInDefine(); - currentTableDefinition.defineMemoryTable(); - } - - public static void columnName(Object column, String columnName) { - checkInDefine(); - currentTableDefinition.defineColumnName(column, columnName); - } - - public static void autoIncrement(Object column) { - checkInDefine(); - currentTableDefinition.defineAutoIncrement(column); - } - - public static void length(Object column, int length) { - checkInDefine(); - currentTableDefinition.defineLength(column, length); - } - - public static void scale(Object column, int scale) { - checkInDefine(); - currentTableDefinition.defineScale(column, scale); - } - - public static void trim(Object column) { - checkInDefine(); - currentTableDefinition.defineTrim(column); - } - - public static void nullable(Object column, boolean isNullable) { - checkInDefine(); - currentTableDefinition.defineNullable(column, isNullable); - } - - public static void defaultValue(Object column, String defaultValue) { - checkInDefine(); - currentTableDefinition.defineDefaultValue(column, defaultValue); - } - - public static void constraint(Object column, String constraint) { - checkInDefine(); - currentTableDefinition.defineConstraint(column, constraint); - } - - static synchronized void define(TableDefinition tableDefinition, Iciql table) { - currentTableDefinition = tableDefinition; - currentTable = table; - tableDefinition.mapObject(table); - table.defineIQ(); - currentTable = null; - currentTableDefinition = null; - } - - private static void checkInDefine() { - if (currentTable == null) { - throw new IciqlException("This method may only be called " - + "from within the define() method, and the define() method " - + "is called by the framework."); - } - } - -} diff --git a/src/com/iciql/Filter.java b/src/com/iciql/Filter.java deleted file mode 100644 index 99dbdc3..0000000 --- a/src/com/iciql/Filter.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * Represents the WHERE clause of a query. - */ -public interface Filter { - boolean where(); -} diff --git a/src/com/iciql/Function.java b/src/com/iciql/Function.java deleted file mode 100644 index 3faddb7..0000000 --- a/src/com/iciql/Function.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import com.iciql.util.Utils; - -/** - * This class provides static methods that represents common SQL functions. - */ -public class Function implements Token { - - // must be a new instance - private static final Long COUNT_STAR = new Long(0); - - protected Object[] x; - private String name; - - protected Function(String name, Object... x) { - this.name = name; - this.x = x; - } - - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL(name).appendSQL("("); - int i = 0; - for (Object o : x) { - if (i++ > 0) { - stat.appendSQL(","); - } - query.appendSQL(stat, null, o); - } - stat.appendSQL(")"); - } - - public static Long count() { - return COUNT_STAR; - } - - public static Integer length(Object x) { - return Db.registerToken(Utils.newObject(Integer.class), new Function("LENGTH", x)); - } - - @SuppressWarnings("unchecked") - public static T sum(T x) { - return (T) Db.registerToken(Utils.newObject(x.getClass()), new Function("SUM", x)); - } - - public static Long count(Object x) { - return Db.registerToken(Utils.newObject(Long.class), new Function("COUNT", x)); - } - - public static Boolean isNull(Object x) { - return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) { - public void appendSQL(SQLStatement stat, Query query) { - query.appendSQL(stat, null, x[0]); - stat.appendSQL(" IS NULL"); - } - }); - } - - public static Boolean isNotNull(Object x) { - return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) { - public void appendSQL(SQLStatement stat, Query query) { - query.appendSQL(stat, null, x[0]); - stat.appendSQL(" IS NOT NULL"); - } - }); - } - - public static Boolean not(Boolean x) { - return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) { - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL("NOT "); - query.appendSQL(stat, null, x[0]); - } - }); - } - - public static Boolean or(Boolean... x) { - return Db.registerToken(Utils.newObject(Boolean.class), new Function("", (Object[]) x) { - public void appendSQL(SQLStatement stat, Query query) { - int i = 0; - for (Object o : x) { - if (i++ > 0) { - stat.appendSQL(" OR "); - } - query.appendSQL(stat, null, o); - } - } - }); - } - - public static Boolean and(Boolean... x) { - return Db.registerToken(Utils.newObject(Boolean.class), new Function("", (Object[]) x) { - public void appendSQL(SQLStatement stat, Query query) { - int i = 0; - for (Object o : x) { - if (i++ > 0) { - stat.appendSQL(" AND "); - } - query.appendSQL(stat, null, o); - } - } - }); - } - - @SuppressWarnings("unchecked") - public static X min(X x) { - Class clazz = (Class) x.getClass(); - X o = Utils.newObject(clazz); - return Db.registerToken(o, new Function("MIN", x)); - } - - @SuppressWarnings("unchecked") - public static X max(X x) { - Class clazz = (Class) x.getClass(); - X o = Utils.newObject(clazz); - return Db.registerToken(o, new Function("MAX", x)); - } - - public static Boolean like(String x, String pattern) { - Boolean o = Utils.newObject(Boolean.class); - return Db.registerToken(o, new Function("LIKE", x, pattern) { - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL("("); - query.appendSQL(stat, null, x[0]); - stat.appendSQL(" LIKE "); - query.appendSQL(stat, x[0], x[1]); - stat.appendSQL(")"); - } - }); - } - -} diff --git a/src/com/iciql/Iciql.java b/src/com/iciql/Iciql.java deleted file mode 100644 index 9f73ffa..0000000 --- a/src/com/iciql/Iciql.java +++ /dev/null @@ -1,731 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 James Moger. - * Copyright 2012 Frédéric Gaillard. - * - * 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; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * A class that implements this interface can be used as a database table. - *

- * You may implement the Table interface on your model object and optionally use - * IQColumn annotations (which imposes a compile-time and runtime-dependency on - * iciql), or may choose to use the IQTable and IQColumn annotations only (which - * imposes a compile-time and runtime-dependency on this file only). - *

- * If a class is annotated with IQTable and at the same time implements Table, - * the define() method is not called. - *

- * Fully Supported Data Types: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
All Databases
java.lang.StringVARCHAR (length > 0) or CLOB (length == 0)
java.lang.BooleanBIT
java.lang.ByteTINYINT
java.lang.ShortSMALLINT
java.lang.IntegerINT
java.lang.LongBIGINT
java.lang.FloatREAL
java.lang.DoubleDOUBLE
java.math.BigDecimalDECIMAL (length == 0)
- * DECIMAL(length, scale) (length > 0)
java.sql.DateDATE
java.sql.TimeTIME
java.sql.TimestampTIMESTAMP
java.util.DateTIMESTAMP
java.lang.Enum.name()VARCHAR (length > 0) or CLOB (length == 0)
- * EnumType.NAME
java.lang.Enum.ordinal()INT
- * EnumType.ORDINAL
java.lang.Enum implements
- * com.iciql.Iciql.EnumID.enumId()
INT
- * EnumType.ENUMID
H2 Databases
java.util.UUIDUUID
- *

- * Partially Supported Data Types: - *

- * The following data types can be mapped to columns for all general statements - * BUT these field types may not be used to specify compile-time clauses or - * constraints. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
byte []BLOB
booleanBIT
byteTINYINT
shortSMALLINT
intINT
longBIGINT
floatREAL
doubleDOUBLE
- *

- * Table and field mapping: by default, the mapped table name is the class name - * and the public fields are reflectively mapped, by their name, to columns. As - * an alternative, you may specify both the table and column definition by - * annotations. - *

- * Table Interface: you may set additional parameters such as table name, - * primary key, and indexes in the define() method. - *

- * Annotations: you may use the annotations with or without implementing the - * Table interface. The annotations allow you to decouple your model completely - * from iciql other than this file. - *

- * Automatic model generation: you may automatically generate model classes as - * strings with the Db and DbInspector objects: - * - *

- * Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
- * DbInspector inspector = new DbInspector(db);
- * List<String> models =
- *         inspector.generateModel(schema, table, packageName,
- *         annotateSchema, trimStrings)
- * 
- * - * Or you may use the GenerateModels tool to generate and save your classes to - * the file system: - * - *
- * java -jar iciql.jar
- *      -url "jdbc:h2:mem:"
- *      -user sa -password sa -schema schemaName -table tableName
- *      -package packageName -folder destination
- *      -annotateSchema false -trimStrings true
- * 
- * - * Model validation: you may validate your model class with DbInspector object. - * The DbInspector will report errors, warnings, and suggestions: - * - *
- * Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
- * DbInspector inspector = new DbInspector(db);
- * List<Validation> remarks = inspector.validateModel(new MyModel(), throwOnError);
- * for (Validation remark : remarks) {
- * 	System.out.println(remark);
- * }
- * 
- */ -public interface Iciql { - - /** - * An annotation for an iciql version. - *

- * - * @IQVersion(1) - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQVersion { - - /** - * If set to a non-zero value, iciql maintains a "iq_versions" table - * within your database. The version number is used to call to a - * registered DbUpgrader implementation to perform relevant ALTER - * statements. Default: 0. You must specify a DbUpgrader on your Db - * object to use this parameter. - */ - int value() default 0; - - } - - /** - * An annotation for a schema. - *

- * - * @IQSchema("PUBLIC") - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQSchema { - - /** - * The schema may be optionally specified. Default: unspecified. - */ - String value() default ""; - - } - - /** - * Enumeration defining the four index types. - */ - public static enum IndexType { - STANDARD, UNIQUE, HASH, UNIQUE_HASH; - } - - /** - * An index annotation. - *

- *

    - *
  • @IQIndex("name") - *
  • @IQIndex({"street", "city"}) - *
  • @IQIndex(name="streetidx", value={"street", "city"}) - *
  • @IQIndex(name="addressidx", type=IndexType.UNIQUE, - * value={"house_number", "street", "city"}) - *
- */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQIndex { - - /** - * Index name. If null or empty, iciql will generate one. - */ - String name() default ""; - - /** - * Type of the index. - *
    - *
  • com.iciql.iciql.IndexType.STANDARD - *
  • com.iciql.iciql.IndexType.UNIQUE - *
  • com.iciql.iciql.IndexType.HASH - *
  • com.iciql.iciql.IndexType.UNIQUE_HASH - *
- * - * HASH indexes may only be valid for single column indexes. - * - */ - IndexType type() default IndexType.STANDARD; - - /** - * Columns to include in index. - *
    - *
  • single column index: value = "id" - *
  • multiple column index: value = { "id", "name", "date" } - *
- */ - String[] value() default {}; - } - - /** - * Enumeration defining the ON DELETE actions. - */ - public static enum ConstraintDeleteType { - UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT; - } - - /** - * Enumeration defining the ON UPDATE actions. - */ - public static enum ConstraintUpdateType { - UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT; - } - - /** - * Enumeration defining the deferrability. - */ - public static enum ConstraintDeferrabilityType { - UNSET, DEFERRABLE_INITIALLY_DEFERRED, DEFERRABLE_INITIALLY_IMMEDIATE, NOT_DEFERRABLE; - } - - /** - * A foreign key constraint annotation. - *

- *

    - *
  • @IQContraintForeignKey( - * foreignColumns = { "idaccount"}, - * referenceName = "account", - * referenceColumns = { "id" }, - * deleteType = ConstrainDeleteType.CASCADE, - * updateType = ConstraintUpdateType.NO_ACTION ) - *
- * Note : reference columns should have a unique constraint defined in referenceName table, - * some database used to define a unique index instead of a unique constraint - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQContraintForeignKey { - - /** - * Constraint name. If null or empty, iciql will generate one. - */ - String name() default ""; - - /** - * Type of the action on delete, default to unspecified. - *
    - *
  • com.iciql.iciql.ConstrainDeleteType.CASCADE - *
  • com.iciql.iciql.ConstrainDeleteType.RESTRICT - *
  • com.iciql.iciql.ConstrainDeleteType.SET_NULL - *
  • com.iciql.iciql.ConstrainDeleteType.NO_ACTION - *
  • com.iciql.iciql.ConstrainDeleteType.SET_DEFAULT - *
- */ - ConstraintDeleteType deleteType() default ConstraintDeleteType.UNSET; - - /** - * Type of the action on update, default to unspecified. - *
    - *
  • com.iciql.iciql.ConstrainUpdateType.CASCADE - *
  • com.iciql.iciql.ConstrainUpdateType.RESTRICT - *
  • com.iciql.iciql.ConstrainUpdateType.SET_NULL - *
  • com.iciql.iciql.ConstrainUpdateType.NO_ACTION - *
  • com.iciql.iciql.ConstrainUpdateType.SET_DEFAULT - *
- */ - ConstraintUpdateType updateType() default ConstraintUpdateType.UNSET; - - /** - * Type of the deferrability mode, default to unspecified - *
    - *
  • com.iciql.iciql.ConstrainUpdateType.CASCADE - *
  • ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_DEFERRED - *
  • ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_IMMEDIATE - *
  • ConstraintDeferrabilityType.NOT_DEFERRABLE - *
- */ - ConstraintDeferrabilityType deferrabilityType() default ConstraintDeferrabilityType.UNSET; - - /** - * The source table for the columns defined as foreign. - */ - String tableName() default ""; - - /** - * Columns defined as 'foreign'. - *
    - *
  • single column : foreignColumns = "id" - *
  • multiple column : foreignColumns = { "id", "name", "date" } - *
- */ - String[] foreignColumns() default {}; - - /** - * The reference table for the columns defined as references. - */ - String referenceName() default ""; - - /** - * Columns defined as 'references'. - *
    - *
  • single column : referenceColumns = "id" - *
  • multiple column : referenceColumns = { "id", "name", "date" } - *
- */ - String[] referenceColumns() default {}; - } - - /** - * Annotation to specify multiple foreign keys constraints. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQContraintsForeignKey { - IQContraintForeignKey[] value() default {}; - } - - /** - * A unique constraint annotation. - *

- *

    - *
  • @IQContraintUnique(uniqueColumns = { "street", "city" }) - *
  • @IQContraintUnique(name="streetconstraint", uniqueColumns = { "street", "city" }) - *
- */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQContraintUnique { - - /** - * Constraint name. If null or empty, iciql will generate one. - */ - String name() default ""; - - /** - * Columns defined as 'unique'. - *
    - *
  • single column : uniqueColumns = "id" - *
  • multiple column : uniqueColumns = { "id", "name", "date" } - *
- */ - String[] uniqueColumns() default {}; - - } - - /** - * Annotation to specify multiple unique constraints. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQContraintsUnique { - IQContraintUnique[] value() default {}; - } - - /** - * Annotation to define a view. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQView { - - /** - * The view name. If not specified the class name is used as the view - * name. - *

- * The view name may still be overridden in the define() method if the - * model class is not annotated with IQView. Default: unspecified. - */ - String name() default ""; - - /** - * The source table for the view. - *

- * The view name may still be overridden in the define() method if the - * model class is not annotated with IQView. Default: unspecified. - */ - String tableName() default ""; - - /** - * The inherit columns allows this model class to inherit columns from - * its super class. Any IQTable annotation present on the super class is - * ignored. Default: false. - */ - boolean inheritColumns() default false; - - /** - * Whether or not iciql tries to create the view. Default: - * true. - */ - boolean create() default true; - - /** - * If true, only fields that are explicitly annotated as IQColumn are - * mapped. Default: true. - */ - boolean annotationsOnly() default true; - } - - /** - * String snippet defining SQL constraints for a field. Use "this" as - * a placeholder for the column name. "this" will be substituted at - * runtime. - *

- * IQConstraint("this > 2 AND this <= 7") - *

- * This snippet may still be overridden in the define() method if the - * model class is not annotated with IQTable or IQView. Default: unspecified. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface IQConstraint { - - String value() default ""; - } - - /** - * Annotation to specify multiple indexes. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQIndexes { - IQIndex[] value() default {}; - } - - /** - * Annotation to define a table. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface IQTable { - - /** - * The table name. If not specified the class name is used as the table - * name. - *

- * The table name may still be overridden in the define() method if the - * model class is not annotated with IQTable. Default: unspecified. - */ - String name() default ""; - - /** - * The primary key may be optionally specified. If it is not specified, - * then no primary key is set by the IQTable annotation. You may specify - * a composite primary key. - *

    - *
  • single column primaryKey: value = "id" - *
  • compound primary key: value = { "id", "name" } - *
- * The primary key may still be overridden in the define() method if the - * model class is not annotated with IQTable. Default: unspecified. - */ - String[] primaryKey() default {}; - - /** - * The inherit columns allows this model class to inherit columns from - * its super class. Any IQTable annotation present on the super class is - * ignored. Default: false. - */ - boolean inheritColumns() default false; - - /** - * Whether or not iciql tries to create the table and indexes. Default: - * true. - */ - boolean create() default true; - - /** - * If true, only fields that are explicitly annotated as IQColumn are - * mapped. Default: true. - */ - boolean annotationsOnly() default true; - - /** - * If true, this table is created as a memory table where data is - * persistent, but index data is kept in main memory. Valid only for H2 - * and HSQL databases. Default: false. - */ - boolean memoryTable() default false; - } - - /** - * Annotation to define a column. Annotated fields may have any scope - * (however, the JVM may raise a SecurityException if the SecurityManager - * doesn't allow iciql to access the field.) - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface IQColumn { - - /** - * If not specified, the field name is used as the column name. Default: - * the field name. - */ - String name() default ""; - - /** - * This column is the primary key. Default: false. - */ - boolean primaryKey() default false; - - /** - * The column is created with a sequence as the default value. Default: - * false. - */ - boolean autoIncrement() default false; - - /** - * Length is used to define the length of a VARCHAR column or to define - * the precision of a DECIMAL(precision, scale) expression. - *

- * If larger than zero, it is used during the CREATE TABLE phase. For - * string values it may also be used to prevent database exceptions on - * INSERT and UPDATE statements (see trim). - *

- * Any length set in define() may override this annotation setting if - * the model class is not annotated with IQTable. Default: 0. - */ - int length() default 0; - - /** - * Scale is used during the CREATE TABLE phase to define the scale of a - * DECIMAL(precision, scale) expression. - *

- * Any scale set in define() may override this annotation setting if the - * model class is not annotated with IQTable. Default: 0. - */ - int scale() default 0; - - /** - * If true, iciql will automatically trim the string if it exceeds - * length (value.substring(0, length)). Default: false. - */ - boolean trim() default false; - - /** - * If false, iciql will set the column NOT NULL during the CREATE TABLE - * phase. Default: true. - */ - boolean nullable() default true; - - /** - * The default value assigned to the column during the CREATE TABLE - * phase. This field could contain a literal single-quoted value, or a - * function call. Empty strings are considered NULL. Examples: - *

    - *
  • defaultValue="" (null) - *
  • defaultValue="CURRENT_TIMESTAMP" - *
  • defaultValue="''" (empty string) - *
  • defaultValue="'0'" - *
  • defaultValue="'1970-01-01 00:00:01'" - *
- * if the default value is specified, and auto increment is disabled, - * and primary key is disabled, then this value is included in the - * "DEFAULT ..." phrase of a column during the CREATE TABLE process. - *

- * Alternatively, you may specify a default object value on the field - * and this will be converted to a properly formatted DEFAULT expression - * during the CREATE TABLE process. - *

- * Default: unspecified (null). - */ - String defaultValue() default ""; - - } - - /** - * Interface for using the EnumType.ENUMID enumeration mapping strategy. - *

- * Enumerations wishing to use EnumType.ENUMID must implement this - * interface. - */ - public interface EnumId { - int enumId(); - } - - /** - * Enumeration representing how to map a java.lang.Enum to a column. - *

- *

    - *
  • NAME - name() : string - *
  • ORDINAL - ordinal() : int - *
  • ENUMID - enumId() : int - *
- * - * @see com.iciql.Iciql.EnumId interface - */ - public enum EnumType { - NAME, ORDINAL, ENUMID; - - public static final EnumType DEFAULT_TYPE = NAME; - } - - /** - * Annotation to define how a java.lang.Enum is mapped to a column. - *

- * This annotation can be used on: - *

    - *
  • a field instance of an enumeration type - *
  • on the enumeration class declaration - *
- * If you choose to annotate the class declaration, that will be the default - * mapping strategy for all @IQColumn instances of the enum. This can still - * be overridden for an individual field by specifying the IQEnum - * annotation. - *

- * The default mapping is by NAME. - * - *

-	 * IQEnum(EnumType.NAME)
-	 * 
- * - * A string mapping will generate either a VARCHAR, if IQColumn.length > 0 - * or a TEXT column if IQColumn.length == 0 - * - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.FIELD, ElementType.TYPE }) - public @interface IQEnum { - EnumType value() default EnumType.NAME; - } - - /** - * Annotation to define an ignored field. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface IQIgnore{ - } - - /** - * This method is called to let the table define the primary key, indexes, - * and the table name. - */ - void defineIQ(); -} diff --git a/src/com/iciql/IciqlException.java b/src/com/iciql/IciqlException.java deleted file mode 100644 index 3f27b73..0000000 --- a/src/com/iciql/IciqlException.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2011 James Moger. - * Copyright 2012 Frédéric Gaillard - * - * 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; - -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.regex.Pattern; - -/** - * Iciql wraps all exceptions with this class. - */ -public class IciqlException extends RuntimeException { - - public static final int CODE_UNMAPPED_FIELD = 1; - public static final int CODE_DUPLICATE_KEY = 2; - public static final int CODE_OBJECT_NOT_FOUND = 3; - public static final int CODE_OBJECT_ALREADY_EXISTS = 4; - public static final int CODE_CONSTRAINT_VIOLATION = 5; - public static final int CODE_UNCHARACTERIZED = 6; - - private static final String TOKEN_UNMAPPED_FIELD = "\\? (=|\\>|\\<|\\<\\>|!=|\\>=|\\<=|LIKE|BETWEEN) \\?"; - - private static final long serialVersionUID = 1L; - - private String sql; - - private int iciqlCode; - - public IciqlException(Throwable t) { - super(t.getMessage(), t); - configureCode(t); - } - - public IciqlException(String message, Object... parameters) { - super(parameters.length > 0 ? MessageFormat.format(message, parameters) : message); - } - - public IciqlException(Throwable t, String message, Object... parameters) { - super(parameters.length > 0 ? MessageFormat.format(message, parameters) : message, t); - configureCode(t); - } - - public static void checkUnmappedField(String sql) { - if (Pattern.compile(IciqlException.TOKEN_UNMAPPED_FIELD).matcher(sql).find()) { - IciqlException e = new IciqlException("unmapped field in statement!"); - e.sql = sql; - e.iciqlCode = CODE_UNMAPPED_FIELD; - throw e; - } - } - - public static IciqlException fromSQL(String sql, Throwable t) { - if (Pattern.compile(TOKEN_UNMAPPED_FIELD).matcher(sql).find()) { - IciqlException e = new IciqlException(t, "unmapped field in statement!"); - e.sql = sql; - e.iciqlCode = CODE_UNMAPPED_FIELD; - return e; - } else { - IciqlException e = new IciqlException(t, t.getMessage()); - e.sql = sql; - return e; - } - } - - public void setSQL(String sql) { - this.sql = sql; - } - - public String getSQL() { - return sql; - } - - public int getIciqlCode() { - return iciqlCode; - } - - private void configureCode(Throwable t) { - if (t == null) { - return; - } - if (t instanceof SQLException) { - // http://developer.mimer.com/documentation/html_92/Mimer_SQL_Mobile_DocSet/App_Return_Codes2.html - SQLException s = (SQLException) t; - String state = s.getSQLState(); - if ("23000".equals(state)) { - // MySQL duplicate primary key on insert - iciqlCode = CODE_DUPLICATE_KEY; - if (s.getErrorCode() == 1217) { - iciqlCode = CODE_CONSTRAINT_VIOLATION; - } - } else if ("23505".equals(state)) { - // Derby duplicate primary key on insert - iciqlCode = CODE_DUPLICATE_KEY; - } else if ("42000".equals(state)) { - // MySQL duplicate unique index value on insert - iciqlCode = CODE_DUPLICATE_KEY; - } else if ("42Y07".equals(state)) { - // Derby schema not found - iciqlCode = CODE_OBJECT_NOT_FOUND; - } else if ("42X05".equals(state)) { - // Derby table not found - iciqlCode = CODE_OBJECT_NOT_FOUND; - } else if ("42Y55".equals(state)) { - // Derby table not found - iciqlCode = CODE_OBJECT_NOT_FOUND; - } else if ("42S02".equals(state)) { - // H2 table not found - iciqlCode = CODE_OBJECT_NOT_FOUND; - } else if ("42501".equals(state)) { - // HSQL table not found - iciqlCode = CODE_OBJECT_NOT_FOUND; - } else if ("42P01".equals(state)) { - // PostgreSQL table not found - iciqlCode = CODE_OBJECT_NOT_FOUND; - } else if ("X0X05".equals(state)) { - // Derby view/table not found exists - iciqlCode = CODE_OBJECT_NOT_FOUND; - } else if ("X0Y32".equals(state)) { - // Derby table already exists - iciqlCode = CODE_OBJECT_ALREADY_EXISTS; - } else if ("42P07".equals(state)) { - // PostgreSQL table or index already exists - iciqlCode = CODE_OBJECT_ALREADY_EXISTS; - } else if ("42S01".equals(state)) { - // MySQL view already exists - iciqlCode = CODE_OBJECT_ALREADY_EXISTS; - } else if ("42S11".equals(state)) { - // H2 index already exists - iciqlCode = CODE_OBJECT_ALREADY_EXISTS; - } else if ("42504".equals(state)) { - // HSQL index already exists - iciqlCode = CODE_OBJECT_ALREADY_EXISTS; - } else if ("2BP01".equals(state)) { - // PostgreSQL constraint violation - iciqlCode = CODE_CONSTRAINT_VIOLATION; - } else if ("42533".equals(state)) { - // HSQL constraint violation - iciqlCode = CODE_CONSTRAINT_VIOLATION; - } else if ("X0Y25".equals(state)) { - // Derby constraint violation - iciqlCode = CODE_CONSTRAINT_VIOLATION; - } else { - // uncharacterized SQL code, we can always rely on iciqlCode != 0 in IciqlException - iciqlCode = s.getErrorCode() == 0 ? CODE_UNCHARACTERIZED : s.getErrorCode(); - } - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getName()); - String message = getLocalizedMessage(); - if (message != null) { - sb.append(": ").append(message); - } - if (sql != null) { - sb.append('\n').append(sql); - } - return sb.toString(); - } -} diff --git a/src/com/iciql/ModelUtils.java b/src/com/iciql/ModelUtils.java deleted file mode 100644 index 56e6440..0000000 --- a/src/com/iciql/ModelUtils.java +++ /dev/null @@ -1,499 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import static com.iciql.util.StringUtils.isNullOrEmpty; - -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.text.DateFormat; -import java.text.MessageFormat; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.regex.Pattern; - -import com.iciql.TableDefinition.FieldDefinition; -import com.iciql.util.StringUtils; - -/** - * Utility methods for models related to type mapping, default value validation, - * and class or field name creation. - */ -class ModelUtils { - - /** - * The list of supported data types. It is used by the runtime mapping for - * CREATE statements. - */ - private static final Map, String> SUPPORTED_TYPES = new HashMap, String>(); - - static { - Map, String> m = SUPPORTED_TYPES; - m.put(String.class, "VARCHAR"); - m.put(Boolean.class, "BOOLEAN"); - m.put(Byte.class, "TINYINT"); - m.put(Short.class, "SMALLINT"); - m.put(Integer.class, "INT"); - m.put(Long.class, "BIGINT"); - m.put(Float.class, "REAL"); - m.put(Double.class, "DOUBLE"); - m.put(BigDecimal.class, "DECIMAL"); - m.put(java.sql.Timestamp.class, "TIMESTAMP"); - m.put(java.util.Date.class, "TIMESTAMP"); - m.put(java.sql.Date.class, "DATE"); - m.put(java.sql.Time.class, "TIME"); - m.put(byte[].class, "BLOB"); - m.put(UUID.class, "UUID"); - - // map primitives - m.put(boolean.class, m.get(Boolean.class)); - m.put(byte.class, m.get(Byte.class)); - m.put(short.class, m.get(Short.class)); - m.put(int.class, m.get(Integer.class)); - m.put(long.class, m.get(Long.class)); - m.put(float.class, m.get(Float.class)); - m.put(double.class, m.get(Double.class)); - } - - /** - * Convert SQL type aliases to the list of supported types. This map is used - * by generation and validation. - */ - private static final Map SQL_TYPES = new HashMap(); - - static { - Map m = SQL_TYPES; - m.put("CHAR", "VARCHAR"); - m.put("CHARACTER", "VARCHAR"); - m.put("NCHAR", "VARCHAR"); - m.put("VARCHAR_CASESENSITIVE", "VARCHAR"); - m.put("VARCHAR_IGNORECASE", "VARCHAR"); - m.put("LONGVARCHAR", "VARCHAR"); - m.put("VARCHAR2", "VARCHAR"); - m.put("NVARCHAR", "VARCHAR"); - m.put("NVARCHAR2", "VARCHAR"); - m.put("TEXT", "VARCHAR"); - m.put("NTEXT", "VARCHAR"); - m.put("TINYTEXT", "VARCHAR"); - m.put("MEDIUMTEXT", "VARCHAR"); - m.put("LONGTEXT", "VARCHAR"); - m.put("CLOB", "VARCHAR"); - m.put("NCLOB", "VARCHAR"); - - // logic - m.put("BIT", "BOOLEAN"); - m.put("BOOL", "BOOLEAN"); - - // numeric - m.put("BYTE", "TINYINT"); - m.put("INT2", "SMALLINT"); - m.put("YEAR", "SMALLINT"); - m.put("INTEGER", "INT"); - m.put("MEDIUMINT", "INT"); - m.put("INT4", "INT"); - m.put("SIGNED", "INT"); - m.put("INT8", "BIGINT"); - m.put("IDENTITY", "BIGINT"); - m.put("SERIAL", "INT"); - m.put("BIGSERIAL", "BIGINT"); - - // decimal - m.put("NUMBER", "DECIMAL"); - m.put("DEC", "DECIMAL"); - m.put("NUMERIC", "DECIMAL"); - m.put("FLOAT", "DOUBLE"); - m.put("FLOAT4", "DOUBLE"); - m.put("FLOAT8", "DOUBLE"); - m.put("DOUBLE PRECISION", "DOUBLE"); - - // date - m.put("DATETIME", "TIMESTAMP"); - m.put("SMALLDATETIME", "TIMESTAMP"); - - // binary types - m.put("TINYBLOB", "BLOB"); - m.put("MEDIUMBLOB", "BLOB"); - m.put("LONGBLOB", "BLOB"); - m.put("IMAGE", "BLOB"); - m.put("OID", "BLOB"); - } - - private static final List KEYWORDS = Arrays.asList("abstract", "assert", "boolean", "break", - "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", - "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", - "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", - "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", - "throw", "throws", "transient", "try", "void", "volatile", "while", "false", "null", "true"); - - /** - * Returns a SQL type mapping for a Java class. - * - * @param fieldDef - * the field to map - * @return - */ - static String getDataType(FieldDefinition fieldDef) { - Class fieldClass = fieldDef.field.getType(); - if (fieldClass.isEnum()) { - switch (fieldDef.enumType) { - case ORDINAL: - case ENUMID: - return "INT"; - case NAME: - default: - return "VARCHAR"; - } - } - if (SUPPORTED_TYPES.containsKey(fieldClass)) { - return SUPPORTED_TYPES.get(fieldClass); - } - throw new IciqlException("Unsupported type " + fieldClass.getName()); - } - - /** - * Returns the Java class for a given SQL type. - * - * @param sqlType - * @param dateTimeClass - * the preferred date class (java.util.Date or - * java.sql.Timestamp) - * @return - */ - static Class getClassForSqlType(String sqlType, Class dateTimeClass) { - sqlType = sqlType.toUpperCase(); - // XXX dropping "UNSIGNED" or parts like that could be trouble - sqlType = sqlType.split(" ")[0].trim(); - - if (SQL_TYPES.containsKey(sqlType)) { - // convert the sqlType to a standard type - sqlType = SQL_TYPES.get(sqlType); - } - Class mappedClass = null; - for (Class clazz : SUPPORTED_TYPES.keySet()) { - if (clazz.isPrimitive()) { - // do not map from SQL TYPE to primitive type - continue; - } - if (SUPPORTED_TYPES.get(clazz).equalsIgnoreCase(sqlType)) { - mappedClass = clazz; - - break; - } - } - if (mappedClass != null) { - if (mappedClass.equals(java.util.Date.class) || mappedClass.equals(java.sql.Timestamp.class)) { - return dateTimeClass; - } - return mappedClass; - } - return null; - } - - /** - * Tries to create a convert a SQL table name to a camel case class name. - * - * @param tableName - * the SQL table name - * @return the class name - */ - static String convertTableToClassName(String tableName) { - String[] chunks = StringUtils.arraySplit(tableName, '_', false); - StringBuilder className = new StringBuilder(); - for (String chunk : chunks) { - if (chunk.length() == 0) { - // leading or trailing _ - continue; - } - String[] subchunks = StringUtils.arraySplit(chunk, ' ', false); - for (String subchunk : subchunks) { - if (subchunk.length() == 0) { - // leading or trailing space - continue; - } - className.append(Character.toUpperCase(subchunk.charAt(0))); - className.append(subchunk.substring(1).toLowerCase()); - } - } - return className.toString(); - } - - /** - * Ensures that SQL column names don't collide with Java keywords. - * - * @param columnName - * the column name - * @return the Java field name - */ - static String convertColumnToFieldName(String columnName) { - String lower = columnName.toLowerCase(); - if (KEYWORDS.contains(lower)) { - lower += "Value"; - } - return lower; - } - - /** - * Converts a DEFAULT clause value into an object. - * - * @param field - * definition - * @return object - */ - static Object getDefaultValue(FieldDefinition def, Class dateTimeClass) { - Class valueType = getClassForSqlType(def.dataType, dateTimeClass); - if (String.class.isAssignableFrom(valueType)) { - if (StringUtils.isNullOrEmpty(def.defaultValue)) { - // literal default must be specified within single quotes - return null; - } - if (def.defaultValue.charAt(0) == '\'' - && def.defaultValue.charAt(def.defaultValue.length() - 1) == '\'') { - // strip leading and trailing single quotes - return def.defaultValue.substring(1, def.defaultValue.length() - 1).trim(); - } - return def.defaultValue; - } - - if (StringUtils.isNullOrEmpty(def.defaultValue)) { - // can not create object from empty string - return null; - } - - // strip leading and trailing single quotes - String content = def.defaultValue; - if (content.charAt(0) == '\'') { - content = content.substring(1); - } - if (content.charAt(content.length() - 1) == '\'') { - content = content.substring(0, content.length() - 2); - } - - if (StringUtils.isNullOrEmpty(content)) { - // can not create object from empty string - return null; - } - - if (Boolean.class.isAssignableFrom(valueType) || boolean.class.isAssignableFrom(valueType)) { - return Boolean.parseBoolean(content); - } - - if (Number.class.isAssignableFrom(valueType)) { - try { - // delegate to static valueOf() method to parse string - Method m = valueType.getMethod("valueOf", String.class); - return m.invoke(null, content); - } catch (NumberFormatException e) { - throw new IciqlException(e, "Failed to parse {0} as a number!", def.defaultValue); - } catch (Throwable t) { - } - } - - String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}"; - String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}"; - - if (java.sql.Date.class.isAssignableFrom(valueType)) { - // this may be a little loose.... - // 00-00-00 - // 00/00/00 - // 00.00.00 - Pattern pattern = Pattern.compile(dateRegex); - if (pattern.matcher(content).matches()) { - DateFormat df = DateFormat.getDateInstance(); - try { - return df.parse(content); - } catch (Exception e) { - throw new IciqlException(e, "Failed to parse {0} as a date!", def.defaultValue); - } - } - } - - if (java.sql.Time.class.isAssignableFrom(valueType)) { - // 00:00:00 - Pattern pattern = Pattern.compile(timeRegex); - if (pattern.matcher(content).matches()) { - DateFormat df = DateFormat.getTimeInstance(); - try { - return df.parse(content); - } catch (Exception e) { - throw new IciqlException(e, "Failed to parse {0} as a time!", def.defaultValue); - } - } - } - - if (java.util.Date.class.isAssignableFrom(valueType)) { - // this may be a little loose.... - // 00-00-00 00:00:00 - // 00/00/00T00:00:00 - // 00.00.00T00:00:00 - Pattern pattern = Pattern.compile(dateRegex + "." + timeRegex); - if (pattern.matcher(content).matches()) { - DateFormat df = DateFormat.getDateTimeInstance(); - try { - return df.parse(content); - } catch (Exception e) { - throw new IciqlException(e, "Failed to parse {0} as a datetimestamp!", def.defaultValue); - } - } - } - return content; - } - - /** - * Converts the object into a DEFAULT clause value. - * - * @param o - * the default object - * @return the value formatted for a DEFAULT clause - */ - static String formatDefaultValue(Object o) { - Class objectClass = o.getClass(); - String value = null; - if (Number.class.isAssignableFrom(objectClass)) { - // NUMBER - return ((Number) o).toString(); - } else if (Boolean.class.isAssignableFrom(objectClass)) { - // BOOLEAN - return o.toString(); - } else if (java.sql.Date.class.isAssignableFrom(objectClass)) { - // DATE - value = new SimpleDateFormat("yyyy-MM-dd").format((Date) o); - } else if (java.sql.Time.class.isAssignableFrom(objectClass)) { - // TIME - value = new SimpleDateFormat("HH:mm:ss").format((Date) o); - } else if (Date.class.isAssignableFrom(objectClass)) { - // DATETIME - value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) o); - } else if (String.class.isAssignableFrom(objectClass)) { - // STRING - value = o.toString(); - } - if (value == null) { - return "''"; - } - return MessageFormat.format("''{0}''", value); - } - - /** - * Checks the formatting of IQColumn.defaultValue(). - * - * @param defaultValue - * the default value - * @return true if it is - */ - static boolean isProperlyFormattedDefaultValue(String defaultValue) { - if (isNullOrEmpty(defaultValue)) { - return true; - } - Pattern literalDefault = Pattern.compile("'.*'"); - Pattern functionDefault = Pattern.compile("[^'].*[^']"); - return literalDefault.matcher(defaultValue).matches() - || functionDefault.matcher(defaultValue).matches(); - } - - /** - * Checks to see if the default value matches the class. - * - * @param modelClass - * the class - * @param defaultValue - * the value - * @return true if it does - */ - static boolean isValidDefaultValue(Class modelClass, String defaultValue) { - - if (defaultValue == null) { - // NULL - return true; - } - if (defaultValue.trim().length() == 0) { - // NULL (effectively) - return true; - } - - // function / variable - Pattern functionDefault = Pattern.compile("[^'].*[^']"); - if (functionDefault.matcher(defaultValue).matches()) { - // hard to validate this since its in the database - // assume it is good - return true; - } - - // STRING - if (modelClass == String.class) { - Pattern stringDefault = Pattern.compile("'(.|\\n)*'"); - return stringDefault.matcher(defaultValue).matches(); - } - - String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}"; - String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}"; - - // TIMESTAMP - if (modelClass == java.util.Date.class || modelClass == java.sql.Timestamp.class) { - // this may be a little loose.... - // 00-00-00 00:00:00 - // 00/00/00T00:00:00 - // 00.00.00T00:00:00 - Pattern pattern = Pattern.compile("'" + dateRegex + "." + timeRegex + "'"); - return pattern.matcher(defaultValue).matches(); - } - - // DATE - if (modelClass == java.sql.Date.class) { - // this may be a little loose.... - // 00-00-00 - // 00/00/00 - // 00.00.00 - Pattern pattern = Pattern.compile("'" + dateRegex + "'"); - return pattern.matcher(defaultValue).matches(); - } - - // TIME - if (modelClass == java.sql.Time.class) { - // 00:00:00 - Pattern pattern = Pattern.compile("'" + timeRegex + "'"); - return pattern.matcher(defaultValue).matches(); - } - - // NUMBER - if (Number.class.isAssignableFrom(modelClass)) { - // strip single quotes - String unquoted = defaultValue; - if (unquoted.charAt(0) == '\'') { - unquoted = unquoted.substring(1); - } - if (unquoted.charAt(unquoted.length() - 1) == '\'') { - unquoted = unquoted.substring(0, unquoted.length() - 1); - } - - try { - // delegate to static valueOf() method to parse string - Method m = modelClass.getMethod("valueOf", String.class); - m.invoke(null, unquoted); - } catch (NumberFormatException ex) { - return false; - } catch (Throwable t) { - } - } - return true; - } -} diff --git a/src/com/iciql/OrderExpression.java b/src/com/iciql/OrderExpression.java deleted file mode 100644 index f450bfb..0000000 --- a/src/com/iciql/OrderExpression.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * An expression to order by in a query. - * - * @param - * the query data type - */ - -class OrderExpression { - private Query query; - private Object expression; - private boolean desc; - private boolean nullsFirst; - private boolean nullsLast; - - OrderExpression(Query query, Object expression, boolean desc, boolean nullsFirst, boolean nullsLast) { - this.query = query; - this.expression = expression; - this.desc = desc; - this.nullsFirst = nullsFirst; - this.nullsLast = nullsLast; - } - - void appendSQL(SQLStatement stat) { - query.appendSQL(stat, null, expression); - if (desc) { - stat.appendSQL(" DESC"); - } - if (nullsLast) { - stat.appendSQL(" NULLS LAST"); - } - if (nullsFirst) { - stat.appendSQL(" NULLS FIRST"); - } - } - -} diff --git a/src/com/iciql/Query.java b/src/com/iciql/Query.java deleted file mode 100644 index 5dc78a5..0000000 --- a/src/com/iciql/Query.java +++ /dev/null @@ -1,947 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import java.lang.reflect.Field; -import java.sql.Blob; -import java.sql.Clob; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; - -import com.iciql.Iciql.EnumType; -import com.iciql.bytecode.ClassReader; -import com.iciql.util.JdbcUtils; -import com.iciql.util.IciqlLogger; -import com.iciql.util.Utils; - -/** - * This class represents a query. - * - * @param - * the return type - */ - -public class Query { - - private Db db; - private SelectTable from; - private ArrayList conditions = Utils.newArrayList(); - private ArrayList updateColumnDeclarations = Utils.newArrayList(); - private ArrayList> joins = Utils.newArrayList(); - private final IdentityHashMap> aliasMap = Utils.newIdentityHashMap(); - private ArrayList> orderByList = Utils.newArrayList(); - private ArrayList groupByExpressions = Utils.newArrayList(); - private long limit; - private long offset; - - private Query(Db db) { - this.db = db; - } - - /** - * from() is a static factory method to build a Query object. - * - * @param db - * @param alias - * @return a query object - */ - @SuppressWarnings("unchecked") - static Query from(Db db, T alias) { - Query query = new Query(db); - TableDefinition def = (TableDefinition) db.define(alias.getClass()); - query.from = new SelectTable(db, query, alias, false); - def.initSelectObject(query.from, alias, query.aliasMap); - return query; - } - - public long selectCount() { - SQLStatement stat = getSelectStatement(false); - stat.appendSQL("COUNT(*) "); - appendFromWhere(stat); - ResultSet rs = stat.executeQuery(); - try { - rs.next(); - long value = rs.getLong(1); - return value; - } catch (SQLException e) { - throw IciqlException.fromSQL(stat.getSQL(), e); - } finally { - JdbcUtils.closeSilently(rs, true); - } - } - - public List select() { - return select(false); - } - - public T selectFirst() { - return select(false).get(0); - } - - public List selectDistinct() { - return select(true); - } - - @SuppressWarnings("unchecked") - public X selectFirst(Z x) { - List list = (List) select(x); - return list.isEmpty() ? null : list.get(0); - } - - public void createView(Class viewClass) { - TableDefinition viewDef = db.define(viewClass); - - SQLStatement fromWhere = new SQLStatement(db); - appendFromWhere(fromWhere, false); - - SQLStatement stat = new SQLStatement(db); - db.getDialect().prepareCreateView(stat, viewDef, fromWhere.toSQL()); - IciqlLogger.create(stat.toSQL()); - stat.execute(); - } - - public void replaceView(Class viewClass) { - db.dropView(viewClass); - createView(viewClass); - } - - public String getSQL() { - SQLStatement stat = getSelectStatement(false); - stat.appendSQL("*"); - appendFromWhere(stat); - return stat.getSQL().trim(); - } - - /** - * toSQL returns a static string version of the query with runtime variables - * properly encoded. This method is also useful when combined with the where - * clause methods like isParameter() or atLeastParameter() which allows - * iciql to generate re-usable parameterized string statements. - * - * @return the sql query as plain text - */ - public String toSQL() { - return toSQL(false); - } - - /** - * toSQL returns a static string version of the query with runtime variables - * properly encoded. This method is also useful when combined with the where - * clause methods like isParameter() or atLeastParameter() which allows - * iciql to generate re-usable parameterized string statements. - * - * @param distinct - * if true SELECT DISTINCT is used for the query - * @return the sql query as plain text - */ - public String toSQL(boolean distinct) { - return toSQL(distinct, null); - } - - /** - * toSQL returns a static string version of the query with runtime variables - * properly encoded. This method is also useful when combined with the where - * clause methods like isParameter() or atLeastParameter() which allows - * iciql to generate re-usable parameterized string statements. - * - * @param distinct - * if true SELECT DISTINCT is used for the query - * @param k - * k is used to select only the columns of the specified alias - * for an inner join statement. An example of a generated - * statement is: SELECT DISTINCT t1.* FROM sometable AS t1 INNER - * JOIN othertable AS t2 ON t1.id = t2.id WHERE t2.flag = true - * without the alias parameter the statement would start with - * SELECT DISTINCT * FROM... - * @return the sql query as plain text - */ - public String toSQL(boolean distinct, K k) { - SQLStatement stat = new SQLStatement(getDb()); - if (updateColumnDeclarations.size() > 0) { - stat.appendSQL("UPDATE "); - from.appendSQL(stat); - stat.appendSQL(" SET "); - int i = 0; - for (UpdateColumn declaration : updateColumnDeclarations) { - if (i++ > 0) { - stat.appendSQL(", "); - } - declaration.appendSQL(stat); - } - appendWhere(stat); - } else { - stat.appendSQL("SELECT "); - if (distinct) { - stat.appendSQL("DISTINCT "); - } - if (k != null) { - SelectTable sel = getSelectTable(k); - if (sel == null) { - // unknown alias, use wildcard - IciqlLogger.warn("Alias {0} is not defined in the statement!", k.getClass()); - stat.appendSQL("*"); - } else if (isJoin()) { - // join query, use AS alias - String as = sel.getAs(); - stat.appendSQL(as + ".*"); - } else { - // schema.table.* - String schema = sel.getAliasDefinition().schemaName; - String table = sel.getAliasDefinition().tableName; - String as = getDb().getDialect().prepareTableName(schema, table); - stat.appendSQL(as + ".*"); - } - } else { - // alias unspecified, use wildcard - stat.appendSQL("*"); - } - appendFromWhere(stat); - } - return stat.toSQL().trim(); - } - - String toSubQuery(Z z) { - SQLStatement stat = getSelectStatement(false); - SelectColumn col = aliasMap.get(z); - String columnName = col.getFieldDefinition().columnName; - stat.appendColumn(columnName); - appendFromWhere(stat); - return stat.toSQL(); - } - - private List select(boolean distinct) { - List result = Utils.newArrayList(); - TableDefinition def = from.getAliasDefinition(); - SQLStatement stat = getSelectStatement(distinct); - def.appendSelectList(stat); - appendFromWhere(stat); - ResultSet rs = stat.executeQuery(); - try { - int[] columns = def.mapColumns(false, rs); - while (rs.next()) { - T item = from.newObject(); - def.readRow(item, rs, columns); - result.add(item); - } - } catch (SQLException e) { - throw IciqlException.fromSQL(stat.getSQL(), e); - } finally { - JdbcUtils.closeSilently(rs, true); - } - return result; - } - - public int delete() { - SQLStatement stat = new SQLStatement(db); - stat.appendSQL("DELETE FROM "); - from.appendSQL(stat); - appendWhere(stat); - IciqlLogger.delete(stat.getSQL()); - return stat.executeUpdate(); - } - - public UpdateColumnSet set(A field) { - from.getAliasDefinition().checkMultipleEnums(field); - return new UpdateColumnSet(this, field); - } - - public UpdateColumnSet set(boolean field) { - from.getAliasDefinition().checkMultipleBooleans(); - return setPrimitive(field); - } - - public UpdateColumnSet set(byte field) { - return setPrimitive(field); - } - - public UpdateColumnSet set(short field) { - return setPrimitive(field); - } - - public UpdateColumnSet set(int field) { - return setPrimitive(field); - } - - public UpdateColumnSet set(long field) { - return setPrimitive(field); - } - - public UpdateColumnSet set(float field) { - return setPrimitive(field); - } - - public UpdateColumnSet set(double field) { - return setPrimitive(field); - } - - private UpdateColumnSet setPrimitive(A field) { - A alias = getPrimitiveAliasByValue(field); - if (alias == null) { - // this will result in an unmapped field exception - return set(field); - } - return set(alias); - } - - public UpdateColumnIncrement increment(A field) { - return new UpdateColumnIncrement(this, field); - } - - public UpdateColumnIncrement increment(byte field) { - return incrementPrimitive(field); - } - - public UpdateColumnIncrement increment(short field) { - return incrementPrimitive(field); - } - - public UpdateColumnIncrement increment(int field) { - return incrementPrimitive(field); - } - - public UpdateColumnIncrement increment(long field) { - return incrementPrimitive(field); - } - - public UpdateColumnIncrement increment(float field) { - return incrementPrimitive(field); - } - - public UpdateColumnIncrement increment(double field) { - return incrementPrimitive(field); - } - - private UpdateColumnIncrement incrementPrimitive(A field) { - A alias = getPrimitiveAliasByValue(field); - if (alias == null) { - // this will result in an unmapped field exception - return increment(field); - } - return increment(alias); - } - - public int update() { - if (updateColumnDeclarations.size() == 0) { - throw new IciqlException("Missing set or increment call."); - } - SQLStatement stat = new SQLStatement(db); - stat.appendSQL("UPDATE "); - from.appendSQL(stat); - stat.appendSQL(" SET "); - int i = 0; - for (UpdateColumn declaration : updateColumnDeclarations) { - if (i++ > 0) { - stat.appendSQL(", "); - } - declaration.appendSQL(stat); - } - appendWhere(stat); - IciqlLogger.update(stat.getSQL()); - return stat.executeUpdate(); - } - - public List selectDistinct(Z x) { - return select(x, true); - } - - public List select(Z x) { - return select(x, false); - } - - @SuppressWarnings("unchecked") - private List select(Z x, boolean distinct) { - Class clazz = x.getClass(); - if (Utils.isSimpleType(clazz)) { - return selectSimple((X) x, distinct); - } - Class enclosingClass = clazz.getEnclosingClass(); - if (enclosingClass != null) { - // anonymous inner class - clazz = clazz.getSuperclass(); - } - return select((Class) clazz, (X) x, distinct); - } - - private List select(Class clazz, X x, boolean distinct) { - List result = Utils.newArrayList(); - TableDefinition def = db.define(clazz); - SQLStatement stat = getSelectStatement(distinct); - def.appendSelectList(stat, this, x); - appendFromWhere(stat); - ResultSet rs = stat.executeQuery(); - try { - int[] columns = def.mapColumns(false, rs); - while (rs.next()) { - X row = Utils.newObject(clazz); - def.readRow(row, rs, columns); - result.add(row); - } - } catch (SQLException e) { - throw IciqlException.fromSQL(stat.getSQL(), e); - } finally { - JdbcUtils.closeSilently(rs, true); - } - return result; - } - - @SuppressWarnings("unchecked") - private List selectSimple(X x, boolean distinct) { - SQLStatement stat = getSelectStatement(distinct); - appendSQL(stat, null, x); - appendFromWhere(stat); - ResultSet rs = stat.executeQuery(); - List result = Utils.newArrayList(); - try { - while (rs.next()) { - X value; - Object o = rs.getObject(1); - // Convert CLOB and BLOB now because we close the resultset - if (Clob.class.isAssignableFrom(o.getClass())) { - value = (X) Utils.convert(o, String.class); - } else if (Blob.class.isAssignableFrom(o.getClass())) { - value = (X) Utils.convert(o, byte[].class); - } else { - value = (X) o; - } - result.add(value); - } - } catch (Exception e) { - throw IciqlException.fromSQL(stat.getSQL(), e); - } finally { - JdbcUtils.closeSilently(rs, true); - } - return result; - } - - private SQLStatement getSelectStatement(boolean distinct) { - SQLStatement stat = new SQLStatement(db); - stat.appendSQL("SELECT "); - if (distinct) { - stat.appendSQL("DISTINCT "); - } - return stat; - } - - /** - * Begin a primitive boolean field condition clause. - * - * @param x - * the primitive boolean field to query - * @return a query condition to continue building the condition - */ - public QueryCondition where(boolean x) { - from.getAliasDefinition().checkMultipleBooleans(); - return wherePrimitive(x); - } - - /** - * Begin a primitive short field condition clause. - * - * @param x - * the primitive short field to query - * @return a query condition to continue building the condition - */ - public QueryCondition where(byte x) { - return wherePrimitive(x); - } - - /** - * Begin a primitive short field condition clause. - * - * @param x - * the primitive short field to query - * @return a query condition to continue building the condition - */ - public QueryCondition where(short x) { - return wherePrimitive(x); - } - - /** - * Begin a primitive int field condition clause. - * - * @param x - * the primitive int field to query - * @return a query condition to continue building the condition - */ - public QueryCondition where(int x) { - return wherePrimitive(x); - } - - /** - * Begin a primitive long field condition clause. - * - * @param x - * the primitive long field to query - * @return a query condition to continue building the condition - */ - public QueryCondition where(long x) { - return wherePrimitive(x); - } - - /** - * Begin a primitive float field condition clause. - * - * @param x - * the primitive float field to query - * @return a query condition to continue building the condition - */ - public QueryCondition where(float x) { - return wherePrimitive(x); - } - - /** - * Begin a primitive double field condition clause. - * - * @param x - * the primitive double field to query - * @return a query condition to continue building the condition - */ - public QueryCondition where(double x) { - return wherePrimitive(x); - } - - /** - * Begins a primitive field condition clause. - * - * @param value - * @return a query condition to continue building the condition - */ - private QueryCondition wherePrimitive(A value) { - A alias = getPrimitiveAliasByValue(value); - if (alias == null) { - // this will result in an unmapped field exception - return where(value); - } - return where(alias); - } - - /** - * Begin an Object field condition clause. - * - * @param x - * the mapped object to query - * @return a query condition to continue building the condition - */ - public QueryCondition where(A x) { - from.getAliasDefinition().checkMultipleEnums(x); - return new QueryCondition(this, x); - } - - public QueryWhere where(Filter filter) { - HashMap fieldMap = Utils.newHashMap(); - for (Field f : filter.getClass().getDeclaredFields()) { - f.setAccessible(true); - try { - Object obj = f.get(filter); - if (obj == from.getAlias()) { - List fields = from.getAliasDefinition().getFields(); - String name = f.getName(); - for (TableDefinition.FieldDefinition field : fields) { - String n = name + "." + field.field.getName(); - Object o = field.field.get(obj); - fieldMap.put(n, o); - } - } - fieldMap.put(f.getName(), f.get(filter)); - } catch (Exception e) { - throw new IciqlException(e); - } - } - Token filterCode = new ClassReader().decompile(filter, fieldMap, "where"); - // String filterQuery = filterCode.toString(); - conditions.add(filterCode); - return new QueryWhere(this); - } - - public QueryWhere where(String fragment, List args) { - return this.where(fragment, args.toArray()); - } - - public QueryWhere where(String fragment, Object... args) { - conditions.add(new RuntimeToken(fragment, args)); - return new QueryWhere(this); - } - - public QueryWhere whereTrue(Boolean condition) { - Token token = new Function("", condition); - addConditionToken(token); - return new QueryWhere(this); - } - - /** - * Sets the Limit and Offset of a query. - * - * @return the query - */ - - public Query limit(long limit) { - this.limit = limit; - return this; - } - - public Query offset(long offset) { - this.offset = offset; - return this; - } - - public Query orderBy(boolean field) { - from.getAliasDefinition().checkMultipleBooleans(); - return orderByPrimitive(field); - } - - public Query orderBy(byte field) { - return orderByPrimitive(field); - } - - public Query orderBy(short field) { - return orderByPrimitive(field); - } - - public Query orderBy(int field) { - return orderByPrimitive(field); - } - - public Query orderBy(long field) { - return orderByPrimitive(field); - } - - public Query orderBy(float field) { - return orderByPrimitive(field); - } - - public Query orderBy(double field) { - return orderByPrimitive(field); - } - - Query orderByPrimitive(Object field) { - Object alias = getPrimitiveAliasByValue(field); - if (alias == null) { - return orderBy(field); - } - return orderBy(alias); - } - - public Query orderBy(Object expr) { - from.getAliasDefinition().checkMultipleEnums(expr); - OrderExpression e = new OrderExpression(this, expr, false, false, false); - addOrderBy(e); - return this; - } - - /** - * Order by a number of columns. - * - * @param expressions - * the columns - * @return the query - */ - - public Query orderBy(Object... expressions) { - for (Object expr : expressions) { - from.getAliasDefinition().checkMultipleEnums(expr); - OrderExpression e = new OrderExpression(this, expr, false, false, false); - addOrderBy(e); - } - return this; - } - - public Query orderByDesc(Object expr) { - OrderExpression e = new OrderExpression(this, expr, true, false, false); - addOrderBy(e); - return this; - } - - public Query groupBy(boolean field) { - from.getAliasDefinition().checkMultipleBooleans(); - return groupByPrimitive(field); - } - - public Query groupBy(byte field) { - return groupByPrimitive(field); - } - - public Query groupBy(short field) { - return groupByPrimitive(field); - } - - public Query groupBy(int field) { - return groupByPrimitive(field); - } - - public Query groupBy(long field) { - return groupByPrimitive(field); - } - - public Query groupBy(float field) { - return groupByPrimitive(field); - } - - public Query groupBy(double field) { - return groupByPrimitive(field); - } - - Query groupByPrimitive(Object field) { - Object alias = getPrimitiveAliasByValue(field); - if (alias == null) { - return groupBy(field); - } - return groupBy(alias); - } - - public Query groupBy(Object expr) { - from.getAliasDefinition().checkMultipleEnums(expr); - groupByExpressions.add(expr); - return this; - } - - public Query groupBy(Object... groupBy) { - this.groupByExpressions.addAll(Arrays.asList(groupBy)); - return this; - } - - /** - * INTERNAL - * - * @param stat - * the statement - * @param alias - * the alias object (can be null) - * @param value - * the value - */ - public void appendSQL(SQLStatement stat, Object alias, Object value) { - if (Function.count() == value) { - stat.appendSQL("COUNT(*)"); - return; - } - if (RuntimeParameter.PARAMETER == value) { - stat.appendSQL("?"); - addParameter(stat, alias, value); - return; - } - Token token = Db.getToken(value); - if (token != null) { - token.appendSQL(stat, this); - return; - } - if (alias != null && value.getClass().isEnum()) { - // special case: - // value is first enum constant which is also the alias object. - // the first enum constant is used as the alias because we can not - // instantiate an enum reflectively. - stat.appendSQL("?"); - addParameter(stat, alias, value); - return; - } - SelectColumn col = getColumnByReference(value); - if (col != null) { - col.appendSQL(stat); - return; - } - stat.appendSQL("?"); - addParameter(stat, alias, value); - } - - /** - * INTERNAL - * - * @param stat - * the statement - * @param alias - * the alias object (can be null) - * @param valueLeft - * the value on the left of the compound clause - * @param valueRight - * the value on the right of the compound clause - * @param compareType - * the current compare type (e.g. BETWEEN) - */ - public void appendSQL(SQLStatement stat, Object alias, Object valueLeft, Object valueRight, - CompareType compareType) { - stat.appendSQL("?"); - stat.appendSQL(" "); - switch (compareType) { - case BETWEEN: - stat.appendSQL("AND"); - break; - } - stat.appendSQL(" "); - stat.appendSQL("?"); - addParameter(stat, alias, valueLeft); - addParameter(stat, alias, valueRight); - } - - private void addParameter(SQLStatement stat, Object alias, Object value) { - if (alias != null && value.getClass().isEnum()) { - SelectColumn col = getColumnByReference(alias); - EnumType type = col.getFieldDefinition().enumType; - Enum anEnum = (Enum) value; - Object y = Utils.convertEnum(anEnum, type); - stat.addParameter(y); - } else { - stat.addParameter(value); - } - } - - void addConditionToken(Token condition) { - conditions.add(condition); - } - - void addUpdateColumnDeclaration(UpdateColumn declaration) { - updateColumnDeclarations.add(declaration); - } - - void appendWhere(SQLStatement stat) { - if (!conditions.isEmpty()) { - stat.appendSQL(" WHERE "); - for (Token token : conditions) { - token.appendSQL(stat, this); - stat.appendSQL(" "); - } - } - } - - void appendFromWhere(SQLStatement stat) { - appendFromWhere(stat, true); - } - - void appendFromWhere(SQLStatement stat, boolean log) { - stat.appendSQL(" FROM "); - from.appendSQL(stat); - for (SelectTable join : joins) { - join.appendSQLAsJoin(stat, this); - } - appendWhere(stat); - if (!groupByExpressions.isEmpty()) { - stat.appendSQL(" GROUP BY "); - int i = 0; - for (Object obj : groupByExpressions) { - if (i++ > 0) { - stat.appendSQL(", "); - } - appendSQL(stat, null, obj); - stat.appendSQL(" "); - } - } - if (!orderByList.isEmpty()) { - stat.appendSQL(" ORDER BY "); - int i = 0; - for (OrderExpression o : orderByList) { - if (i++ > 0) { - stat.appendSQL(", "); - } - o.appendSQL(stat); - stat.appendSQL(" "); - } - } - db.getDialect().appendLimitOffset(stat, limit, offset); - if (log) { - IciqlLogger.select(stat.getSQL()); - } - } - - /** - * Join another table. - * - * @param alias - * an alias for the table to join - * @return the joined query - */ - - public QueryJoin innerJoin(A alias) { - return join(alias, false); - } - - public QueryJoin leftJoin(A alias) { - return join(alias, true); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private QueryJoin join(A alias, boolean outerJoin) { - TableDefinition def = (TableDefinition) db.define(alias.getClass()); - SelectTable join = new SelectTable(db, this, alias, outerJoin); - def.initSelectObject(join, alias, aliasMap); - joins.add(join); - return new QueryJoin(this, join); - } - - Db getDb() { - return db; - } - - SelectTable getFrom() { - return from; - } - - boolean isJoin() { - return !joins.isEmpty(); - } - - SelectTable getSelectTable(Object alias) { - if (from.getAlias() == alias) { - return from; - } else { - for (SelectTable join : joins) { - if (join.getAlias() == alias) { - return join; - } - } - } - return null; - } - - /** - * This method returns a mapped Object field by its reference. - * - * @param obj - * @return - */ - private SelectColumn getColumnByReference(Object obj) { - SelectColumn col = aliasMap.get(obj); - return col; - } - - /** - * This method returns the alias of a mapped primitive field by its value. - * - * @param obj - * @return - */ - @SuppressWarnings("unchecked") - A getPrimitiveAliasByValue(A obj) { - for (Object alias : aliasMap.keySet()) { - if (alias.equals(obj)) { - SelectColumn match = aliasMap.get(alias); - if (match.getFieldDefinition().isPrimitive) { - return (A) alias; - } - } - } - return null; - } - - void addOrderBy(OrderExpression expr) { - orderByList.add(expr); - } - -} diff --git a/src/com/iciql/QueryBetween.java b/src/com/iciql/QueryBetween.java deleted file mode 100644 index 72d19dc..0000000 --- a/src/com/iciql/QueryBetween.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2011 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; - -/** - * This class represents a "between y and z" condition. - * - * @param - * the return type of the query - * @param - * the incomplete condition data type - */ -public class QueryBetween { - - private Query query; - private A x; - private A y; - - /** - * Construct a between condition. - * - * @param query - * the query - * @param x - * the alias - * @param y - * the lower bound of the between condition - */ - public QueryBetween(Query query, A x, A y) { - this.query = query; - this.x = x; - this.y = y; - } - - /** - * Set the upper bound of the between condition. - * - * @param z - * the upper bound of the between condition - * @return the query - */ - public QueryWhere and(A z) { - query.addConditionToken(new Condition(x, y, z, CompareType.BETWEEN)); - return new QueryWhere(query); - } -} diff --git a/src/com/iciql/QueryCondition.java b/src/com/iciql/QueryCondition.java deleted file mode 100644 index 9613b1b..0000000 --- a/src/com/iciql/QueryCondition.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * This class represents a query with an incomplete condition. - * - * @param - * the return type of the query - * @param - * the incomplete condition data type - */ - -public class QueryCondition { - - private Query query; - private A x; - - QueryCondition(Query query, A x) { - this.query = query; - this.x = x; - } - - public QueryWhere in(SubQuery q) { - query.addConditionToken(new SubQueryCondition(x, q)); - return new QueryWhere(query); - } - - public QueryWhere is(A y) { - query.addConditionToken(new Condition(x, y, CompareType.EQUAL)); - return new QueryWhere(query); - } - - public QueryWhere isNot(A y) { - query.addConditionToken(new Condition(x, y, CompareType.NOT_EQUAL)); - return new QueryWhere(query); - } - - public QueryWhere isNull() { - query.addConditionToken(new Condition(x, null, CompareType.IS_NULL)); - return new QueryWhere(query); - } - - public QueryWhere isNotNull() { - query.addConditionToken(new Condition(x, null, CompareType.IS_NOT_NULL)); - return new QueryWhere(query); - } - - public QueryWhere exceeds(A y) { - query.addConditionToken(new Condition(x, y, CompareType.EXCEEDS)); - return new QueryWhere(query); - } - - public QueryWhere atLeast(A y) { - query.addConditionToken(new Condition(x, y, CompareType.AT_LEAST)); - return new QueryWhere(query); - } - - public QueryWhere lessThan(A y) { - query.addConditionToken(new Condition(x, y, CompareType.LESS_THAN)); - return new QueryWhere(query); - } - - public QueryWhere atMost(A y) { - query.addConditionToken(new Condition(x, y, CompareType.AT_MOST)); - return new QueryWhere(query); - } - - public QueryBetween between(A y) { - return new QueryBetween(query, x, y); - } - - public QueryWhere like(A pattern) { - query.addConditionToken(new Condition(x, pattern, CompareType.LIKE)); - return new QueryWhere(query); - } - - /* - * These method allows you to generate "x=?", "x!=?", etc where conditions. - * Parameter substitution must be done manually later with db.executeQuery. - * This allows for building re-usable SQL string statements from your model - * classes. - */ - public QueryWhere isParameter() { - query.addConditionToken(new RuntimeParameter(x, CompareType.EQUAL)); - return new QueryWhere(query); - } - - public QueryWhere isNotParameter() { - query.addConditionToken(new RuntimeParameter(x, CompareType.NOT_EQUAL)); - return new QueryWhere(query); - } - - public QueryWhere exceedsParameter() { - query.addConditionToken(new RuntimeParameter(x, CompareType.EXCEEDS)); - return new QueryWhere(query); - } - - public QueryWhere lessThanParameter() { - query.addConditionToken(new RuntimeParameter(x, CompareType.LESS_THAN)); - return new QueryWhere(query); - } - - public QueryWhere atMostParameter() { - query.addConditionToken(new RuntimeParameter(x, CompareType.AT_MOST)); - return new QueryWhere(query); - } - - public QueryWhere likeParameter() { - query.addConditionToken(new RuntimeParameter(x, CompareType.LIKE)); - return new QueryWhere(query); - } -} diff --git a/src/com/iciql/QueryJoin.java b/src/com/iciql/QueryJoin.java deleted file mode 100644 index 6d0484e..0000000 --- a/src/com/iciql/QueryJoin.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * This class represents a query with a join. - */ - -public class QueryJoin { - - private Query query; - private SelectTable join; - - QueryJoin(Query query, SelectTable join) { - this.query = query; - this.join = join; - } - - public QueryJoinCondition on(boolean x) { - query.getFrom().getAliasDefinition().checkMultipleBooleans(); - return addPrimitive(x); - } - - public QueryJoinCondition on(byte x) { - return addPrimitive(x); - } - - public QueryJoinCondition on(short x) { - return addPrimitive(x); - } - - public QueryJoinCondition on(int x) { - return addPrimitive(x); - } - - public QueryJoinCondition on(long x) { - return addPrimitive(x); - } - - public QueryJoinCondition on(float x) { - return addPrimitive(x); - } - - public QueryJoinCondition on(double x) { - return addPrimitive(x); - } - - private QueryJoinCondition addPrimitive(A x) { - A alias = query.getPrimitiveAliasByValue(x); - if (alias == null) { - // this will result in an unmapped field exception - return new QueryJoinCondition(query, join, x); - } - return new QueryJoinCondition(query, join, alias); - } - - public QueryJoinCondition on(A x) { - return new QueryJoinCondition(query, join, x); - } -} diff --git a/src/com/iciql/QueryJoinCondition.java b/src/com/iciql/QueryJoinCondition.java deleted file mode 100644 index 6dfd218..0000000 --- a/src/com/iciql/QueryJoinCondition.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - - -/** - * This class represents a query with join and an incomplete condition. - * - * @param - * the incomplete condition data type - */ - -public class QueryJoinCondition { - - private Query query; - private SelectTable join; - private A x; - - QueryJoinCondition(Query query, SelectTable join, A x) { - this.query = query; - this.join = join; - this.x = x; - } - - public Query is(boolean y) { - return addPrimitive(y); - } - - public Query is(byte y) { - return addPrimitive(y); - } - - public Query is(short y) { - return addPrimitive(y); - } - - public Query is(int y) { - return addPrimitive(y); - } - - public Query is(long y) { - return addPrimitive(y); - } - - public Query is(float y) { - return addPrimitive(y); - } - - public Query is(double y) { - return addPrimitive(y); - } - - @SuppressWarnings("unchecked") - private Query addPrimitive(Object o) { - A alias = query.getPrimitiveAliasByValue((A) o); - if (alias == null) { - join.addConditionToken(new Condition(x, (A) o, CompareType.EQUAL)); - } else { - join.addConditionToken(new Condition(x, alias, CompareType.EQUAL)); - } - return query; - } - - public Query is(A y) { - join.addConditionToken(new Condition(x, y, CompareType.EQUAL)); - return query; - } -} diff --git a/src/com/iciql/QueryWhere.java b/src/com/iciql/QueryWhere.java deleted file mode 100644 index 5baa5ab..0000000 --- a/src/com/iciql/QueryWhere.java +++ /dev/null @@ -1,501 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import java.util.List; - -/** - * This class represents a query with a condition. - * - * @param - * the return type - */ - -public class QueryWhere { - - Query query; - - QueryWhere(Query query) { - this.query = query; - } - - /** - * Specify an AND condition with a mapped primitive boolean. - * - * @param x - * the primitive boolean field to query - * @return a query condition to continue building the condition - */ - public QueryCondition and(boolean x) { - query.getFrom().getAliasDefinition().checkMultipleBooleans(); - return addPrimitive(ConditionAndOr.AND, x); - } - - /** - * Specify an AND condition with a mapped primitive byte. - * - * @param x - * the primitive byte field to query - * @return a query condition to continue building the condition - */ - public QueryCondition and(byte x) { - return addPrimitive(ConditionAndOr.AND, x); - } - - /** - * Specify an AND condition with a mapped primitive short. - * - * @param x - * the primitive short field to query - * @return a query condition to continue building the condition - */ - public QueryCondition and(short x) { - return addPrimitive(ConditionAndOr.AND, x); - } - - /** - * Specify an AND condition with a mapped primitive int. - * - * @param x - * the primitive int field to query - * @return a query condition to continue building the condition - */ - public QueryCondition and(int x) { - return addPrimitive(ConditionAndOr.AND, x); - } - - /** - * Specify an AND condition with a mapped primitive long. - * - * @param x - * the primitive long field to query - * @return a query condition to continue building the condition - */ - public QueryCondition and(long x) { - return addPrimitive(ConditionAndOr.AND, x); - } - - /** - * Specify an AND condition with a mapped primitive float. - * - * @param x - * the primitive float field to query - * @return a query condition to continue building the condition - */ - public QueryCondition and(float x) { - return addPrimitive(ConditionAndOr.AND, x); - } - - /** - * Specify an AND condition with a mapped primitive double. - * - * @param x - * the primitive double field to query - * @return a query condition to continue building the condition - */ - public QueryCondition and(double x) { - return addPrimitive(ConditionAndOr.AND, x); - } - - private QueryCondition addPrimitive(ConditionAndOr condition, A x) { - query.addConditionToken(condition); - A alias = query.getPrimitiveAliasByValue(x); - if (alias == null) { - // this will result in an unmapped field exception - return new QueryCondition(query, x); - } - return new QueryCondition(query, alias); - } - - /** - * Specify an AND condition with a mapped Object field. - * - * @param x - * the Object field to query - * @return a query condition to continue building the condition - */ - public QueryCondition and(A x) { - query.getFrom().getAliasDefinition().checkMultipleEnums(x); - query.addConditionToken(ConditionAndOr.AND); - return new QueryCondition(query, x); - } - - /** - * Specify an OR condition with a mapped primitive boolean. - * - * @param x - * the primitive boolean field to query - * @return a query condition to continue building the condition - */ - public QueryCondition or(boolean x) { - query.getFrom().getAliasDefinition().checkMultipleBooleans(); - return addPrimitive(ConditionAndOr.OR, x); - } - - /** - * Specify an OR condition with a mapped primitive byte. - * - * @param x - * the primitive byte field to query - * @return a query condition to continue building the condition - */ - public QueryCondition or(byte x) { - return addPrimitive(ConditionAndOr.OR, x); - } - - /** - * Specify an OR condition with a mapped primitive short. - * - * @param x - * the primitive short field to query - * @return a query condition to continue building the condition - */ - public QueryCondition or(short x) { - return addPrimitive(ConditionAndOr.OR, x); - } - - /** - * Specify an OR condition with a mapped primitive int. - * - * @param x - * the primitive int field to query - * @return a query condition to continue building the condition - */ - public QueryCondition or(int x) { - return addPrimitive(ConditionAndOr.OR, x); - } - - /** - * Specify an OR condition with a mapped primitive long. - * - * @param x - * the primitive long field to query - * @return a query condition to continue building the condition - */ - public QueryCondition or(long x) { - return addPrimitive(ConditionAndOr.OR, x); - } - - /** - * Specify an OR condition with a mapped primitive float. - * - * @param x - * the primitive float field to query - * @return a query condition to continue building the condition - */ - public QueryCondition or(float x) { - return addPrimitive(ConditionAndOr.OR, x); - } - - /** - * Specify an OR condition with a mapped primitive double. - * - * @param x - * the primitive double field to query - * @return a query condition to continue building the condition - */ - public QueryCondition or(double x) { - return addPrimitive(ConditionAndOr.OR, x); - } - - /** - * Specify an OR condition with a mapped Object field. - * - * @param x - * the Object field to query - * @return a query condition to continue building the condition - */ - public QueryCondition or(A x) { - query.getFrom().getAliasDefinition().checkMultipleEnums(x); - query.addConditionToken(ConditionAndOr.OR); - return new QueryCondition(query, x); - } - - public QueryWhere limit(long limit) { - query.limit(limit); - return this; - } - - public QueryWhere offset(long offset) { - query.offset(offset); - return this; - } - - public String getSQL() { - SQLStatement stat = new SQLStatement(query.getDb()); - stat.appendSQL("SELECT *"); - query.appendFromWhere(stat); - return stat.getSQL().trim(); - } - - /** - * toSQL returns a static string version of the query with runtime variables - * properly encoded. This method is also useful when combined with the where - * clause methods like isParameter() or atLeastParameter() which allows - * iciql to generate re-usable parameterized string statements. - * - * @return the sql query as plain text - */ - public String toSQL() { - return query.toSQL(false); - } - - /** - * toSQL returns a static string version of the query with runtime variables - * properly encoded. This method is also useful when combined with the where - * clause methods like isParameter() or atLeastParameter() which allows - * iciql to generate re-usable parameterized string statements. - * - * @param distinct - * if true SELECT DISTINCT is used for the query - * @return the sql query as plain text - */ - public String toSQL(boolean distinct) { - return query.toSQL(distinct); - } - - /** - * toSQL returns a static string version of the query with runtime variables - * properly encoded. This method is also useful when combined with the where - * clause methods like isParameter() or atLeastParameter() which allows - * iciql to generate re-usable parameterized string statements. - * - * @param distinct - * if true SELECT DISTINCT is used for the query - * @param k - * k is used to select only the columns of the specified alias - * for an inner join statement. An example of a generated - * statement is: SELECT DISTINCT t1.* FROM sometable AS t1 INNER - * JOIN othertable AS t2 ON t1.id = t2.id WHERE t2.flag = true - * without the alias parameter the statement would start with - * SELECT DISTINCT * FROM... - * @return the sql query as plain text - */ - public String toSQL(boolean distinct, K k) { - return query.toSQL(distinct, k); - } - - public SubQuery subQuery(Z x) { - return new SubQuery(query, x); - } - - public SubQuery subQuery(boolean x) { - return subQuery(query.getPrimitiveAliasByValue(x)); - } - - public SubQuery subQuery(byte x) { - return subQuery(query.getPrimitiveAliasByValue(x)); - } - - public SubQuery subQuery(short x) { - return subQuery(query.getPrimitiveAliasByValue(x)); - } - - public SubQuery subQuery(int x) { - return subQuery(query.getPrimitiveAliasByValue(x)); - } - - public SubQuery subQuery(long x) { - return subQuery(query.getPrimitiveAliasByValue(x)); - } - - public SubQuery subQuery(float x) { - return subQuery(query.getPrimitiveAliasByValue(x)); - } - - public SubQuery subQuery(double x) { - return subQuery(query.getPrimitiveAliasByValue(x)); - } - - public List select(Z x) { - return query.select(x); - } - - public List selectDistinct(Z x) { - return query.selectDistinct(x); - } - - public X selectFirst(Z x) { - List list = query.select(x); - return list.isEmpty() ? null : list.get(0); - } - - public List select() { - return query.select(); - } - - public T selectFirst() { - List list = select(); - return list.isEmpty() ? null : list.get(0); - } - - public List selectDistinct() { - return query.selectDistinct(); - } - - public void createView(Class viewClass) { - query.createView(viewClass); - } - - public void replaceView(Class viewClass) { - query.replaceView(viewClass); - } - - /** - * Order by primitive boolean field - * - * @param field - * a primitive boolean field - * @return the query - */ - public QueryWhere orderBy(boolean field) { - query.getFrom().getAliasDefinition().checkMultipleBooleans(); - return orderByPrimitive(field); - } - - /** - * Order by primitive byte field - * - * @param field - * a primitive byte field - * @return the query - */ - public QueryWhere orderBy(byte field) { - return orderByPrimitive(field); - } - - /** - * Order by primitive short field - * - * @param field - * a primitive short field - * @return the query - */ - public QueryWhere orderBy(short field) { - return orderByPrimitive(field); - } - - public QueryWhere orderBy(int field) { - return orderByPrimitive(field); - } - - /** - * Order by primitive long field - * - * @param field - * a primitive long field - * @return the query - */ - public QueryWhere orderBy(long field) { - return orderByPrimitive(field); - } - - /** - * Order by primitive float field - * - * @param field - * a primitive float field - * @return the query - */ - public QueryWhere orderBy(float field) { - return orderByPrimitive(field); - } - - /** - * Order by primitive double field - * - * @param field - * a primitive double field - * @return the query - */ - public QueryWhere orderBy(double field) { - return orderByPrimitive(field); - } - - private QueryWhere orderByPrimitive(Object field) { - query.orderByPrimitive(field); - return this; - } - - public QueryWhere orderBy(Object field) { - query.getFrom().getAliasDefinition().checkMultipleEnums(field); - query.orderBy(field); - return this; - } - - /** - * Order by a number of Object columns. - * - * @param expressions - * the order by expressions - * @return the query - */ - - public QueryWhere orderBy(Object... expressions) { - query.orderBy(expressions); - return this; - } - - public QueryWhere orderByNullsFirst(Object expr) { - query.getFrom().getAliasDefinition().checkMultipleEnums(expr); - OrderExpression e = new OrderExpression(query, expr, false, true, false); - query.addOrderBy(e); - return this; - } - - public QueryWhere orderByNullsLast(Object expr) { - query.getFrom().getAliasDefinition().checkMultipleEnums(expr); - OrderExpression e = new OrderExpression(query, expr, false, false, true); - query.addOrderBy(e); - return this; - } - - public QueryWhere orderByDesc(Object expr) { - query.getFrom().getAliasDefinition().checkMultipleEnums(expr); - OrderExpression e = new OrderExpression(query, expr, true, false, false); - query.addOrderBy(e); - return this; - } - - public QueryWhere orderByDescNullsFirst(Object expr) { - query.getFrom().getAliasDefinition().checkMultipleEnums(expr); - OrderExpression e = new OrderExpression(query, expr, true, true, false); - query.addOrderBy(e); - return this; - } - - public QueryWhere orderByDescNullsLast(Object expr) { - query.getFrom().getAliasDefinition().checkMultipleEnums(expr); - OrderExpression e = new OrderExpression(query, expr, true, false, true); - query.addOrderBy(e); - return this; - } - - public int delete() { - return query.delete(); - } - - public int update() { - return query.update(); - } - - public long selectCount() { - return query.selectCount(); - } - -} diff --git a/src/com/iciql/RuntimeParameter.java b/src/com/iciql/RuntimeParameter.java deleted file mode 100644 index 0fbedba..0000000 --- a/src/com/iciql/RuntimeParameter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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; - -/** - * A runtime parameter is used to generate x=? conditions so that iciql can - * build re-usable dynamic queries with parameter substitution done manually at - * runtime. - * - * @param - * the operand type - */ - -class RuntimeParameter implements Token { - - public final static String PARAMETER = ""; - - A x; - CompareType compareType; - - RuntimeParameter(A x, CompareType type) { - this.x = x; - this.compareType = type; - } - - public void appendSQL(SQLStatement stat, Query query) { - query.appendSQL(stat, null, x); - stat.appendSQL(" "); - stat.appendSQL(compareType.getString()); - if (compareType.hasRightExpression()) { - stat.appendSQL(" "); - query.appendSQL(stat, x, PARAMETER); - } - } -} diff --git a/src/com/iciql/RuntimeToken.java b/src/com/iciql/RuntimeToken.java deleted file mode 100644 index cbfd882..0000000 --- a/src/com/iciql/RuntimeToken.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2011 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; - -import java.text.MessageFormat; - -import com.iciql.util.StringUtils; - -/** - * Represents a traditional PreparedStatment fragment like "id=?, name=?". - * - */ -public class RuntimeToken implements Token { - - final String fragment; - final Object[] args; - - public RuntimeToken(String fragment, Object... args) { - this.fragment = fragment; - this.args = args == null ? new Object[0] : args; - } - - /** - * Append the SQL to the given statement using the given query. - * - * @param stat - * the statement to append the SQL to - * @param query - * the query to use - */ - @Override - public void appendSQL(SQLStatement stat, Query query) { - int tokenCount = StringUtils.count('?', fragment); - if (tokenCount != args.length) { - throw new IciqlException(MessageFormat.format( - "Fragment \"{0}\" specifies {1} tokens but you supplied {2} args", fragment, tokenCount, - args.length)); - } - stat.appendSQL(fragment); - for (Object arg : args) { - stat.addParameter(arg); - } - } -} diff --git a/src/com/iciql/SQLDialect.java b/src/com/iciql/SQLDialect.java deleted file mode 100644 index f62168e..0000000 --- a/src/com/iciql/SQLDialect.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 James Moger. - * Copyright 2012 Frédéric Gaillard. - * - * 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; - -import java.sql.DatabaseMetaData; - -import com.iciql.TableDefinition.ConstraintForeignKeyDefinition; -import com.iciql.TableDefinition.ConstraintUniqueDefinition; -import com.iciql.TableDefinition.IndexDefinition; - -/** - * This interface defines points where iciql can build different statements - * depending on the database used. - */ -public interface SQLDialect { - - /** - * Configure the dialect from the database metadata. - * - * @param databaseName - * @param data - */ - void configureDialect(String databaseName, DatabaseMetaData data); - - /** - * Allows a dialect to substitute an SQL type. - * - * @param sqlType - * @return the dialect-safe type - */ - String convertSqlType(String sqlType); - - /** - * Returns a properly formatted table name for the dialect. - * - * @param schemaName - * the schema name, or null for no schema - * @param tableName - * the properly formatted table name - * @return the SQL snippet - */ - String prepareTableName(String schemaName, String tableName); - - /** - * Returns a properly formatted column name for the dialect. - * - * @param name - * the column name - * @return the properly formatted column name - */ - String prepareColumnName(String name); - - /** - * Get the CREATE TABLE statement. - * - * @param stat - * @param def - */ - void prepareCreateTable(SQLStatement stat, TableDefinition def); - - /** - * Get the DROP TABLE statement. - * - * @param stat - * @param def - */ - void prepareDropTable(SQLStatement stat, TableDefinition def); - - - /** - * Get the CREATE VIEW statement. - * - * @param stat - * return the SQL statement - * @param def - * table definition - */ - void prepareCreateView(SQLStatement stat, TableDefinition def); - - /** - * Get the CREATE VIEW statement. - * - * @param stat - * return the SQL statement - * @param def - * table definition - * @param fromWhere - */ - void prepareCreateView(SQLStatement stat, TableDefinition def, String fromWhere); - - /** - * Get the DROP VIEW statement. - * - * @param stat - * return the SQL statement - * @param def - * table definition - */ - void prepareDropView(SQLStatement stat, TableDefinition def); - - /** - * Get the CREATE INDEX statement. - * - * @param stat - * return the SQL statement - * @param schemaName - * the schema name - * @param tableName - * the table name - * @param index - * the index definition - */ - void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName, IndexDefinition index); - - /** - * Get the ALTER statement. - * - * @param stat - * return the SQL statement - * @param schemaName - * the schema name - * @param tableName - * the table name - * @param constraint - * the constraint definition - */ - void prepareCreateConstraintForeignKey(SQLStatement stat, String schemaName, String tableName, ConstraintForeignKeyDefinition constraint); - - /** - * Get the ALTER statement. - * - * @param stat - * return the SQL statement - * @param schemaName - * the schema name - * @param tableName - * the table name - * @param constraint - * the constraint definition - * return the SQL statement - */ - void prepareCreateConstraintUnique(SQLStatement stat, String schemaName, String tableName, ConstraintUniqueDefinition constraint); - - /** - * Get a MERGE or REPLACE INTO statement. - * - * @param stat - * return the SQL statement - * @param schemaName - * the schema name - * @param tableName - * the table name - * @param def - * the table definition - * @param obj - * values - */ - void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition def, - Object obj); - - /** - * Append "LIMIT limit OFFSET offset" to the SQL statement. - * - * @param stat - * the statement - * @param limit - * the limit - * @param offset - * the offset - */ - void appendLimitOffset(SQLStatement stat, long limit, long offset); - - /** - * Returns the preferred DATETIME class for the database. - *

- * Either java.util.Date or java.sql.Timestamp - * - * @return preferred DATETIME class - */ - Class getDateTimeClass(); - - /** - * When building static string statements this method flattens an object to - * a string representation suitable for a static string statement. - * - * @param o - * @return the string equivalent of this object - */ - String prepareParameter(Object o); -} diff --git a/src/com/iciql/SQLDialectDefault.java b/src/com/iciql/SQLDialectDefault.java deleted file mode 100644 index 364db7b..0000000 --- a/src/com/iciql/SQLDialectDefault.java +++ /dev/null @@ -1,445 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 James Moger. - * Copyright 2012 Frédéric Gaillard. - * - * 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; - -import java.sql.DatabaseMetaData; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.text.SimpleDateFormat; - -import com.iciql.Iciql.ConstraintDeleteType; -import com.iciql.Iciql.ConstraintUpdateType; -import com.iciql.TableDefinition.ConstraintForeignKeyDefinition; -import com.iciql.TableDefinition.ConstraintUniqueDefinition; -import com.iciql.TableDefinition.FieldDefinition; -import com.iciql.TableDefinition.IndexDefinition; -import com.iciql.util.IciqlLogger; -import com.iciql.util.StatementBuilder; -import com.iciql.util.StringUtils; - -/** - * Default implementation of an SQL dialect. - */ -public class SQLDialectDefault implements SQLDialect { - - final String LITERAL = "'"; - - float databaseVersion; - String databaseName; - String productVersion; - - @Override - public String toString() { - return getClass().getName() + ": " + databaseName + " " + productVersion; - } - - @Override - public void configureDialect(String databaseName, DatabaseMetaData data) { - this.databaseName = databaseName; - try { - databaseVersion = Float.parseFloat(data.getDatabaseMajorVersion() + "." - + data.getDatabaseMinorVersion()); - productVersion = data.getDatabaseProductVersion(); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - /** - * Allows subclasses to change the type of a column for a CREATE statement. - * - * @param sqlType - * @return the SQL type or a preferred alternative - */ - @Override - public String convertSqlType(String sqlType) { - return sqlType; - } - - @Override - public Class getDateTimeClass() { - return java.util.Date.class; - } - - @Override - public String prepareTableName(String schemaName, String tableName) { - if (StringUtils.isNullOrEmpty(schemaName)) { - return tableName; - } - return schemaName + "." + tableName; - } - - @Override - public String prepareColumnName(String name) { - return name; - } - - @Override - public void prepareDropTable(SQLStatement stat, TableDefinition def) { - StatementBuilder buff = new StatementBuilder("DROP TABLE IF EXISTS " - + prepareTableName(def.schemaName, def.tableName)); - stat.setSQL(buff.toString()); - return; - } - - protected String prepareCreateTable(TableDefinition def) { - return "CREATE TABLE"; - } - - @Override - public void prepareCreateTable(SQLStatement stat, TableDefinition def) { - StatementBuilder buff = new StatementBuilder(); - buff.append(prepareCreateTable(def)); - buff.append(" "); - buff.append(prepareTableName(def.schemaName, def.tableName)).append('('); - - boolean hasIdentityColumn = false; - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(field.columnName)).append(' '); - String dataType = field.dataType; - if (dataType.equals("VARCHAR")) { - // check to see if we should use VARCHAR or CLOB - if (field.length <= 0) { - dataType = "CLOB"; - } - buff.append(convertSqlType(dataType)); - if (field.length > 0) { - buff.append('(').append(field.length).append(')'); - } - } else if (dataType.equals("DECIMAL")) { - // DECIMAL(precision,scale) - buff.append(convertSqlType(dataType)); - if (field.length > 0) { - buff.append('(').append(field.length); - if (field.scale > 0) { - buff.append(',').append(field.scale); - } - buff.append(')'); - } - } else { - // other - hasIdentityColumn |= prepareColumnDefinition(buff, convertSqlType(dataType), - field.isAutoIncrement, field.isPrimaryKey); - } - - // default values - if (!field.isAutoIncrement && !field.isPrimaryKey) { - String dv = field.defaultValue; - if (!StringUtils.isNullOrEmpty(dv)) { - if (ModelUtils.isProperlyFormattedDefaultValue(dv) - && ModelUtils.isValidDefaultValue(field.field.getType(), dv)) { - buff.append(" DEFAULT " + dv); - } - } - } - - if (!field.nullable) { - buff.append(" NOT NULL"); - } - } - - // if table does not have identity column then specify primary key - if (!hasIdentityColumn) { - if (def.primaryKeyColumnNames != null && def.primaryKeyColumnNames.size() > 0) { - buff.append(", PRIMARY KEY("); - buff.resetCount(); - for (String n : def.primaryKeyColumnNames) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(n)); - } - buff.append(')'); - } - } - buff.append(')'); - stat.setSQL(buff.toString()); - } - - @Override - public void prepareDropView(SQLStatement stat, TableDefinition def) { - StatementBuilder buff = new StatementBuilder("DROP VIEW " - + prepareTableName(def.schemaName, def.tableName)); - stat.setSQL(buff.toString()); - return; - } - - protected String prepareCreateView(TableDefinition def) { - return "CREATE VIEW"; - } - - @Override - public void prepareCreateView(SQLStatement stat, TableDefinition def) { - StatementBuilder buff = new StatementBuilder(); - buff.append(" FROM "); - buff.append(prepareTableName(def.schemaName, def.viewTableName)); - - StatementBuilder where = new StatementBuilder(); - for (FieldDefinition field : def.fields) { - if (!StringUtils.isNullOrEmpty(field.constraint)) { - where.appendExceptFirst(", "); - String col = prepareColumnName(field.columnName); - String constraint = field.constraint.replace("{0}", col).replace("this", col); - where.append(constraint); - } - } - if (where.length() > 0) { - buff.append(" WHERE "); - buff.append(where.toString()); - } - - prepareCreateView(stat, def, buff.toString()); - } - - @Override - public void prepareCreateView(SQLStatement stat, TableDefinition def, String fromWhere) { - StatementBuilder buff = new StatementBuilder(); - buff.append(prepareCreateView(def)); - buff.append(" "); - buff.append(prepareTableName(def.schemaName, def.tableName)); - - buff.append(" AS SELECT "); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(field.columnName)); - } - buff.append(fromWhere); - stat.setSQL(buff.toString()); - } - - protected boolean isIntegerType(String dataType) { - if ("INT".equals(dataType)) { - return true; - } else if ("BIGINT".equals(dataType)) { - return true; - } else if ("TINYINT".equals(dataType)) { - return true; - } else if ("SMALLINT".equals(dataType)) { - return true; - } - return false; - } - - protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, - boolean isAutoIncrement, boolean isPrimaryKey) { - buff.append(dataType); - if (isAutoIncrement) { - buff.append(" AUTO_INCREMENT"); - } - return false; - } - - @Override - public void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName, - IndexDefinition index) { - StatementBuilder buff = new StatementBuilder(); - buff.append("CREATE "); - switch (index.type) { - case UNIQUE: - buff.append("UNIQUE "); - break; - case UNIQUE_HASH: - buff.append("UNIQUE "); - break; - default: - IciqlLogger.warn("{0} does not support hash indexes", getClass().getSimpleName()); - } - buff.append("INDEX "); - buff.append(index.indexName); - buff.append(" ON "); - // FIXME maybe we can use schemaName ? - // buff.append(prepareTableName(schemaName, tableName)); - buff.append(tableName); - buff.append("("); - for (String col : index.columnNames) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(col)); - } - buff.append(") "); - - stat.setSQL(buff.toString().trim()); - } - - /** - * PostgreSQL and Derby do not support the SQL2003 MERGE syntax, but we can - * use a trick to insert a row if it does not exist and call update() in - * Db.merge() if the affected row count is 0. - *

- * Databases that do support a MERGE syntax should override this method. - *

- * http://stackoverflow.com/questions/407688 - */ - @Override - public void prepareMerge(SQLStatement stat, String schemaName, String tableName, - TableDefinition def, Object obj) { - StatementBuilder buff = new StatementBuilder("INSERT INTO "); - buff.append(prepareTableName(schemaName, tableName)); - buff.append(" ("); - buff.resetCount(); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(field.columnName)); - } - buff.append(") (SELECT "); - buff.resetCount(); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append('?'); - Object value = def.getValue(obj, field); - stat.addParameter(value); - } - buff.append(" FROM "); - buff.append(prepareTableName(schemaName, tableName)); - buff.append(" WHERE "); - buff.resetCount(); - for (FieldDefinition field : def.fields) { - if (field.isPrimaryKey) { - buff.appendExceptFirst(" AND "); - buff.append(MessageFormat.format("{0} = ?", prepareColumnName(field.columnName))); - Object value = def.getValue(obj, field); - stat.addParameter(value); - } - } - buff.append(" HAVING count(*)=0)"); - stat.setSQL(buff.toString()); - } - - @Override - public void appendLimitOffset(SQLStatement stat, long limit, long offset) { - if (limit > 0) { - stat.appendSQL(" LIMIT " + limit); - } - if (offset > 0) { - stat.appendSQL(" OFFSET " + offset); - } - } - - @Override - public String prepareParameter(Object o) { - if (o instanceof String) { - return LITERAL + o.toString().replace(LITERAL, "''") + LITERAL; - } else if (o instanceof Character) { - return LITERAL + o.toString() + LITERAL; - } else if (o instanceof java.sql.Time) { - return LITERAL + new SimpleDateFormat("HH:mm:ss").format(o) + LITERAL; - } else if (o instanceof java.sql.Date) { - return LITERAL + new SimpleDateFormat("yyyy-MM-dd").format(o) + LITERAL; - } else if (o instanceof java.util.Date) { - return LITERAL + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(o) + LITERAL; - } - return o.toString(); - } - - @SuppressWarnings("incomplete-switch") - @Override - public void prepareCreateConstraintForeignKey(SQLStatement stat, String schemaName, String tableName, ConstraintForeignKeyDefinition constraint) { - StatementBuilder buff = new StatementBuilder(); - buff.append("ALTER TABLE "); - buff.append(prepareTableName(schemaName, tableName)); - buff.append(" ADD CONSTRAINT "); - buff.append(constraint.constraintName); - buff.append(" FOREIGN KEY "); - buff.append(" ("); - for (String col : constraint.foreignColumns) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(col)); - } - buff.append(") "); - buff.append(" REFERENCES "); - buff.append(constraint.referenceTable); - buff.append(" ("); - buff.resetCount(); - for (String col : constraint.referenceColumns) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(col)); - } - buff.append(") "); - if (constraint.deleteType != ConstraintDeleteType.UNSET) { - buff.append(" ON DELETE "); - switch (constraint.deleteType) { - case CASCADE: - buff.append("CASCADE "); - break; - case RESTRICT: - buff.append("RESTRICT "); - break; - case SET_NULL: - buff.append("SET NULL "); - break; - case NO_ACTION: - buff.append("NO ACTION "); - break; - case SET_DEFAULT: - buff.append("SET DEFAULT "); - break; - } - } - if (constraint.updateType != ConstraintUpdateType.UNSET) { - buff.append(" ON UPDATE "); - switch (constraint.updateType) { - case CASCADE: - buff.append("CASCADE "); - break; - case RESTRICT: - buff.append("RESTRICT "); - break; - case SET_NULL: - buff.append("SET NULL "); - break; - case NO_ACTION: - buff.append("NO ACTION "); - break; - case SET_DEFAULT: - buff.append("SET DEFAULT "); - break; - } - } - switch (constraint.deferrabilityType) { - case DEFERRABLE_INITIALLY_DEFERRED: - buff.append("DEFERRABLE INITIALLY DEFERRED "); - break; - case DEFERRABLE_INITIALLY_IMMEDIATE: - buff.append("DEFERRABLE INITIALLY IMMEDIATE "); - break; - case NOT_DEFERRABLE: - buff.append("NOT DEFERRABLE "); - break; - case UNSET: - break; - } - stat.setSQL(buff.toString().trim()); - } - - @Override - public void prepareCreateConstraintUnique(SQLStatement stat, String schemaName, String tableName, ConstraintUniqueDefinition constraint) { - StatementBuilder buff = new StatementBuilder(); - buff.append("ALTER TABLE "); - buff.append(prepareTableName(schemaName, tableName)); - buff.append(" ADD CONSTRAINT "); - buff.append(constraint.constraintName); - buff.append(" UNIQUE "); - buff.append(" ("); - for (String col : constraint.uniqueColumns) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(col)); - } - buff.append(") "); - stat.setSQL(buff.toString().trim()); - } - -} \ No newline at end of file diff --git a/src/com/iciql/SQLDialectDerby.java b/src/com/iciql/SQLDialectDerby.java deleted file mode 100644 index f954a7c..0000000 --- a/src/com/iciql/SQLDialectDerby.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2011 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; - -import com.iciql.util.StatementBuilder; - -/** - * Derby database dialect. - */ -public class SQLDialectDerby extends SQLDialectDefault { - - @Override - public Class getDateTimeClass() { - return java.sql.Timestamp.class; - } - - @Override - public String convertSqlType(String sqlType) { - if ("TINYINT".equals(sqlType)) { - // Derby does not have a TINYINT/BYTE type - return "SMALLINT"; - } - return sqlType; - } - - @Override - public void appendLimitOffset(SQLStatement stat, long limit, long offset) { - // FETCH/OFFSET added in 10.5 - if (databaseVersion >= 10.5f) { - if (offset > 0) { - stat.appendSQL(" OFFSET " + offset + (offset == 1 ? " ROW" : " ROWS")); - } - if (limit > 0) { - stat.appendSQL(" FETCH NEXT " + limit + (limit == 1 ? " ROW" : " ROWS") + " ONLY"); - } - } - } - - @Override - protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, - boolean isAutoIncrement, boolean isPrimaryKey) { - String convertedType = convertSqlType(dataType); - buff.append(convertedType); - if (isIntegerType(dataType) && isAutoIncrement) { - buff.append(" GENERATED BY DEFAULT AS IDENTITY"); - } - return false; - } - - @Override - public void prepareDropTable(SQLStatement stat, TableDefinition def) { - StatementBuilder buff = new StatementBuilder("DROP TABLE " - + prepareTableName(def.schemaName, def.tableName)); - stat.setSQL(buff.toString()); - return; - } -} \ No newline at end of file diff --git a/src/com/iciql/SQLDialectH2.java b/src/com/iciql/SQLDialectH2.java deleted file mode 100644 index 6b3bab1..0000000 --- a/src/com/iciql/SQLDialectH2.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2011 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; - -import com.iciql.TableDefinition.FieldDefinition; -import com.iciql.TableDefinition.IndexDefinition; -import com.iciql.util.StatementBuilder; - -/** - * H2 database dialect. - */ -public class SQLDialectH2 extends SQLDialectDefault { - - /** - * CACHED tables are created by default. MEMORY tables are created upon - * request. - */ - @Override - protected String prepareCreateTable(TableDefinition def) { - if (def.memoryTable) { - return "CREATE MEMORY TABLE IF NOT EXISTS"; - } else { - return "CREATE CACHED TABLE IF NOT EXISTS"; - } - } - - @Override - protected String prepareCreateView(TableDefinition def) { - return "CREATE VIEW IF NOT EXISTS"; - } - - @Override - public void prepareDropView(SQLStatement stat, TableDefinition def) { - StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " - + prepareTableName(def.schemaName, def.tableName)); - stat.setSQL(buff.toString()); - return; - } - - @Override - protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, - boolean isAutoIncrement, boolean isPrimaryKey) { - String convertedType = convertSqlType(dataType); - boolean isIdentity = false; - if (isIntegerType(dataType)) { - if (isAutoIncrement && isPrimaryKey) { - buff.append("IDENTITY"); - isIdentity = true; - } else if (isAutoIncrement) { - buff.append(convertedType); - buff.append(" AUTO_INCREMENT"); - } else { - buff.append(convertedType); - } - } else { - buff.append(convertedType); - } - return isIdentity; - } - - @Override - public void prepareCreateIndex(SQLStatement stat, String schema, String table, IndexDefinition index) { - StatementBuilder buff = new StatementBuilder(); - buff.append("CREATE "); - switch (index.type) { - case STANDARD: - break; - case UNIQUE: - buff.append("UNIQUE "); - break; - case HASH: - buff.append("HASH "); - break; - case UNIQUE_HASH: - buff.append("UNIQUE HASH "); - break; - } - buff.append("INDEX IF NOT EXISTS "); - buff.append(index.indexName); - buff.append(" ON "); - buff.append(table); - buff.append("("); - for (String col : index.columnNames) { - buff.appendExceptFirst(", "); - buff.append(col); - } - buff.append(")"); - stat.setSQL(buff.toString()); - } - - @Override - public void prepareMerge(SQLStatement stat, String schemaName, String tableName, - TableDefinition def, Object obj) { - StatementBuilder buff = new StatementBuilder("MERGE INTO "); - buff.append(prepareTableName(schemaName, tableName)).append(" ("); - buff.resetCount(); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append(field.columnName); - } - buff.append(") KEY("); - buff.resetCount(); - for (FieldDefinition field : def.fields) { - if (field.isPrimaryKey) { - buff.appendExceptFirst(", "); - buff.append(field.columnName); - } - } - buff.append(") "); - buff.resetCount(); - buff.append("VALUES ("); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append('?'); - Object value = def.getValue(obj, field); - stat.addParameter(value); - } - buff.append(')'); - stat.setSQL(buff.toString()); - } -} \ No newline at end of file diff --git a/src/com/iciql/SQLDialectHSQL.java b/src/com/iciql/SQLDialectHSQL.java deleted file mode 100644 index 82e6833..0000000 --- a/src/com/iciql/SQLDialectHSQL.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2011 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; - -import java.text.MessageFormat; - -import com.iciql.TableDefinition.FieldDefinition; -import com.iciql.util.StatementBuilder; - -/** - * HyperSQL database dialect. - */ -public class SQLDialectHSQL extends SQLDialectDefault { - - /** - * CACHED tables are created by default. MEMORY tables are created upon - * request. - */ - @Override - protected String prepareCreateTable(TableDefinition def) { - if (def.memoryTable) { - return "CREATE MEMORY TABLE IF NOT EXISTS"; - } else { - return "CREATE CACHED TABLE IF NOT EXISTS"; - } - } - - @Override - public void prepareDropView(SQLStatement stat, TableDefinition def) { - StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " - + prepareTableName(def.schemaName, def.tableName)); - stat.setSQL(buff.toString()); - return; - } - - @Override - protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, - boolean isAutoIncrement, boolean isPrimaryKey) { - boolean isIdentity = false; - String convertedType = convertSqlType(dataType); - buff.append(convertedType); - if (isIntegerType(dataType) && isAutoIncrement && isPrimaryKey) { - buff.append(" IDENTITY"); - isIdentity = true; - } - return isIdentity; - } - - @Override - public void prepareMerge(SQLStatement stat, String schemaName, String tableName, - TableDefinition def, Object obj) { - final String valuePrefix = "v"; - StatementBuilder buff = new StatementBuilder("MERGE INTO "); - buff.append(prepareTableName(schemaName, tableName)); - // a, b, c.... - buff.append(" USING (VALUES("); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append("CAST(? AS "); - String dataType = convertSqlType(field.dataType); - buff.append(dataType); - if ("VARCHAR".equals(dataType)) { - if (field.length > 0) { - // VARCHAR(x) - buff.append(MessageFormat.format("({0})", field.length)); - } - } else if ("DECIMAL".equals(dataType)) { - if (field.length > 0) { - if (field.scale > 0) { - // DECIMAL(x,y) - buff.append(MessageFormat.format("({0},{1})", field.length, field.scale)); - } else { - // DECIMAL(x) - buff.append(MessageFormat.format("({0})", field.length)); - } - } - } - buff.append(')'); - Object value = def.getValue(obj, field); - stat.addParameter(value); - } - - // map to temporary table - buff.resetCount(); - buff.append(")) AS vals ("); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(valuePrefix + field.columnName)); - } - - buff.append(") ON "); - - // create the ON condition - // (va, vb) = (va,vb) - String[] prefixes = { "", valuePrefix }; - for (int i = 0; i < prefixes.length; i++) { - String prefix = prefixes[i]; - buff.resetCount(); - buff.append('('); - for (FieldDefinition field : def.fields) { - if (field.isPrimaryKey) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(prefix + field.columnName)); - } - } - buff.append(")"); - if (i == 0) { - buff.append('='); - } - } - - // UPDATE - // set a=va - buff.append(" WHEN MATCHED THEN UPDATE SET "); - buff.resetCount(); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(field.columnName)); - buff.append('='); - buff.append(prepareColumnName(valuePrefix + field.columnName)); - } - - // INSERT - // insert va, vb, vc.... - buff.append(" WHEN NOT MATCHED THEN INSERT "); - buff.resetCount(); - buff.append(" VALUES ("); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(valuePrefix + field.columnName)); - } - buff.append(')'); - stat.setSQL(buff.toString()); - } -} \ No newline at end of file diff --git a/src/com/iciql/SQLDialectMSSQL.java b/src/com/iciql/SQLDialectMSSQL.java deleted file mode 100644 index 92b1297..0000000 --- a/src/com/iciql/SQLDialectMSSQL.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012 Alex Telepov. - * 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; - -/** - * MS SQL Server database dialect. - */ -public class SQLDialectMSSQL extends SQLDialectDefault { - - /** - * Append limit and offset rows - * - * @param stat Statement - * @param limit Limit rows - * @param offset Offset rows - */ - @Override - public void appendLimitOffset(SQLStatement stat, long limit, long offset) { - if (offset > 0) { - throw new IciqlException("iciql does not support offset for MSSQL dialect!"); - } - StringBuilder query = new StringBuilder(stat.getSQL()); - - // for databaseVersion >= 2012 need Offset - if (limit > 0) { - int indexSelect = query.indexOf("SELECT"); - - if (indexSelect >= 0) { - StringBuilder subPathQuery = new StringBuilder(" TOP "); - subPathQuery.append(Long.toString(limit)); - - query.insert(indexSelect + "SELECT".length(), subPathQuery); - - stat.setSQL(query.toString()); - } - } - } -} diff --git a/src/com/iciql/SQLDialectMySQL.java b/src/com/iciql/SQLDialectMySQL.java deleted file mode 100644 index 52676d4..0000000 --- a/src/com/iciql/SQLDialectMySQL.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2011 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; - -import com.iciql.TableDefinition.FieldDefinition; -import com.iciql.util.StatementBuilder; - -/** - * MySQL database dialect. - */ -public class SQLDialectMySQL extends SQLDialectDefault { - - @Override - public String convertSqlType(String sqlType) { - if (sqlType.equals("CLOB")) { - return "TEXT"; - } - return sqlType; - } - - @Override - protected String prepareCreateTable(TableDefinition def) { - return "CREATE TABLE IF NOT EXISTS"; - } - - @Override - public void prepareDropView(SQLStatement stat, TableDefinition def) { - StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " - + prepareTableName(def.schemaName, def.tableName)); - stat.setSQL(buff.toString()); - return; - } - - @Override - public String prepareColumnName(String name) { - return "`" + name + "`"; - } - - @Override - protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, boolean isAutoIncrement, - boolean isPrimaryKey) { - String convertedType = convertSqlType(dataType); - buff.append(convertedType); - if (isIntegerType(dataType) && isAutoIncrement) { - buff.append(" AUTO_INCREMENT"); - } - return false; - } - - @Override - public void prepareMerge(SQLStatement stat, String schemaName, String tableName, - TableDefinition def, Object obj) { - StatementBuilder buff = new StatementBuilder("INSERT INTO "); - buff.append(prepareTableName(schemaName, tableName)).append(" ("); - buff.resetCount(); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append(field.columnName); - } - buff.resetCount(); - buff.append(") VALUES ("); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append('?'); - Object value = def.getValue(obj, field); - stat.addParameter(value); - } - buff.append(") ON DUPLICATE KEY UPDATE "); - buff.resetCount(); - for (FieldDefinition field : def.fields) { - buff.appendExceptFirst(", "); - buff.append(field.columnName); - buff.append("=VALUES("); - buff.append(field.columnName); - buff.append(')'); - } - stat.setSQL(buff.toString()); - } -} \ No newline at end of file diff --git a/src/com/iciql/SQLDialectPostgreSQL.java b/src/com/iciql/SQLDialectPostgreSQL.java deleted file mode 100644 index fc115ab..0000000 --- a/src/com/iciql/SQLDialectPostgreSQL.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2011 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; - -import com.iciql.TableDefinition.IndexDefinition; -import com.iciql.util.StatementBuilder; - -/** - * PostgreSQL database dialect. - */ -public class SQLDialectPostgreSQL extends SQLDialectDefault { - - @Override - public Class getDateTimeClass() { - return java.sql.Timestamp.class; - } - - @Override - public String convertSqlType(String sqlType) { - if ("DOUBLE".equals(sqlType)) { - return "DOUBLE PRECISION"; - } else if ("TINYINT".equals(sqlType)) { - // PostgreSQL does not have a byte type - return "SMALLINT"; - } else if ("CLOB".equals(sqlType)) { - return "TEXT"; - } else if ("BLOB".equals(sqlType)) { - return "BYTEA"; - } - return sqlType; - } - - @Override - protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, - boolean isAutoIncrement, boolean isPrimaryKey) { - String convertedType = convertSqlType(dataType); - if (isIntegerType(dataType)) { - if (isAutoIncrement) { - if ("BIGINT".equals(dataType)) { - buff.append("BIGSERIAL"); - } else { - buff.append("SERIAL"); - } - } else { - buff.append(convertedType); - } - } else { - buff.append(convertedType); - } - return false; - } - - @Override - public void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName, - IndexDefinition index) { - StatementBuilder buff = new StatementBuilder(); - buff.append("CREATE "); - switch (index.type) { - case UNIQUE: - buff.append("UNIQUE "); - break; - case UNIQUE_HASH: - buff.append("UNIQUE "); - break; - } - buff.append("INDEX "); - buff.append(index.indexName); - buff.append(" ON "); - buff.append(tableName); - - switch (index.type) { - case HASH: - buff.append(" USING HASH"); - break; - case UNIQUE_HASH: - buff.append(" USING HASH"); - break; - } - - buff.append(" ("); - for (String col : index.columnNames) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(col)); - } - buff.append(") "); - - stat.setSQL(buff.toString().trim()); - } -} \ No newline at end of file diff --git a/src/com/iciql/SQLStatement.java b/src/com/iciql/SQLStatement.java deleted file mode 100644 index 394fc42..0000000 --- a/src/com/iciql/SQLStatement.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.StringTokenizer; - -import com.iciql.util.JdbcUtils; - -/** - * This class represents a parameterized SQL statement. - */ - -public class SQLStatement { - private Db db; - private StringBuilder buff = new StringBuilder(); - private String sql; - private ArrayList params = new ArrayList(); - - SQLStatement(Db db) { - this.db = db; - } - - public void setSQL(String sql) { - this.sql = sql; - buff = new StringBuilder(sql); - } - - public SQLStatement appendSQL(String s) { - buff.append(s); - sql = null; - return this; - } - - public SQLStatement appendTable(String schema, String table) { - return appendSQL(db.getDialect().prepareTableName(schema, table)); - } - - public SQLStatement appendColumn(String column) { - return appendSQL(db.getDialect().prepareColumnName(column)); - } - - /** - * getSQL returns a simple string representation of the parameterized - * statement which will be used later, internally, with prepareStatement. - * - * @return a simple sql statement - */ - String getSQL() { - if (sql == null) { - sql = buff.toString(); - } - return sql; - } - - /** - * toSQL creates a static sql statement with the referenced parameters - * encoded in the statement. - * - * @return a complete sql statement - */ - String toSQL() { - if (sql == null) { - sql = buff.toString(); - } - if (params.size() == 0) { - return sql; - } - StringBuilder sb = new StringBuilder(); - // TODO this needs to me more sophisticated - StringTokenizer st = new StringTokenizer(sql, "?", false); - int i = 0; - while (st.hasMoreTokens()) { - sb.append(st.nextToken()); - if (i < params.size()) { - Object o = params.get(i); - if (RuntimeParameter.PARAMETER == o) { - // dynamic parameter - sb.append('?'); - } else { - // static parameter - sb.append(db.getDialect().prepareParameter(o)); - } - i++; - } - } - return sb.toString(); - } - - public SQLStatement addParameter(Object o) { - // Automatically convert java.util.Date to java.sql.Timestamp - // if the dialect requires java.sql.Timestamp objects (e.g. Derby) - if (o != null && o.getClass().equals(java.util.Date.class) - && db.getDialect().getDateTimeClass().equals(java.sql.Timestamp.class)) { - o = new java.sql.Timestamp(((java.util.Date) o).getTime()); - } - params.add(o); - return this; - } - - void execute() { - PreparedStatement ps = null; - try { - ps = prepare(false); - ps.execute(); - } catch (SQLException e) { - throw IciqlException.fromSQL(getSQL(), e); - } finally { - JdbcUtils.closeSilently(ps); - } - } - - ResultSet executeQuery() { - try { - return prepare(false).executeQuery(); - } catch (SQLException e) { - throw IciqlException.fromSQL(getSQL(), e); - } - } - - int executeUpdate() { - PreparedStatement ps = null; - try { - ps = prepare(false); - return ps.executeUpdate(); - } catch (SQLException e) { - throw IciqlException.fromSQL(getSQL(), e); - } finally { - JdbcUtils.closeSilently(ps); - } - } - - long executeInsert() { - PreparedStatement ps = null; - try { - ps = prepare(true); - ps.executeUpdate(); - long identity = -1; - ResultSet rs = ps.getGeneratedKeys(); - if (rs != null && rs.next()) { - identity = rs.getLong(1); - } - JdbcUtils.closeSilently(rs); - return identity; - } catch (SQLException e) { - throw IciqlException.fromSQL(getSQL(), e); - } finally { - JdbcUtils.closeSilently(ps); - } - } - - private void setValue(PreparedStatement prep, int parameterIndex, Object x) { - try { - prep.setObject(parameterIndex, x); - } catch (SQLException e) { - IciqlException ix = new IciqlException(e, "error setting parameter {0} as {1}", parameterIndex, x - .getClass().getSimpleName()); - ix.setSQL(getSQL()); - throw ix; - } - } - - PreparedStatement prepare(boolean returnGeneratedKeys) { - PreparedStatement prep = db.prepare(getSQL(), returnGeneratedKeys); - for (int i = 0; i < params.size(); i++) { - Object o = params.get(i); - setValue(prep, i + 1, o); - } - return prep; - } - -} diff --git a/src/com/iciql/SelectColumn.java b/src/com/iciql/SelectColumn.java deleted file mode 100644 index 43a1a93..0000000 --- a/src/com/iciql/SelectColumn.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import com.iciql.TableDefinition.FieldDefinition; - -/** - * This class represents a column of a table in a query. - * - * @param - * the table data type - */ - -class SelectColumn { - private SelectTable selectTable; - private FieldDefinition fieldDef; - - SelectColumn(SelectTable table, FieldDefinition fieldDef) { - this.selectTable = table; - this.fieldDef = fieldDef; - } - - void appendSQL(SQLStatement stat) { - if (selectTable.getQuery().isJoin()) { - stat.appendSQL(selectTable.getAs() + "." + fieldDef.columnName); - } else { - stat.appendColumn(fieldDef.columnName); - } - } - - FieldDefinition getFieldDefinition() { - return fieldDef; - } - - SelectTable getSelectTable() { - return selectTable; - } - - Object getCurrentValue() { - return fieldDef.getValue(selectTable.getCurrent()); - } -} diff --git a/src/com/iciql/SelectTable.java b/src/com/iciql/SelectTable.java deleted file mode 100644 index 37b42c4..0000000 --- a/src/com/iciql/SelectTable.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import java.util.ArrayList; - -import com.iciql.util.Utils; - -/** - * This class represents a table in a query. - * - * @param - * the table class - */ - -class SelectTable { - - private Query query; - private Class clazz; - private T current; - private String as; - private TableDefinition aliasDef; - private boolean outerJoin; - private ArrayList joinConditions = Utils.newArrayList(); - private T alias; - - @SuppressWarnings("unchecked") - SelectTable(Db db, Query query, T alias, boolean outerJoin) { - this.alias = alias; - this.query = query; - this.outerJoin = outerJoin; - aliasDef = (TableDefinition) db.getTableDefinition(alias.getClass()); - clazz = Utils.getClass(alias); - as = "T" + Utils.nextAsCount(); - } - - T getAlias() { - return alias; - } - - T newObject() { - return Utils.newObject(clazz); - } - - TableDefinition getAliasDefinition() { - return aliasDef; - } - - void appendSQL(SQLStatement stat) { - if (query.isJoin()) { - stat.appendTable(aliasDef.schemaName, aliasDef.tableName).appendSQL(" AS " + as); - } else { - stat.appendTable(aliasDef.schemaName, aliasDef.tableName); - } - } - - void appendSQLAsJoin(SQLStatement stat, Query q) { - if (outerJoin) { - stat.appendSQL(" LEFT OUTER JOIN "); - } else { - stat.appendSQL(" INNER JOIN "); - } - appendSQL(stat); - if (!joinConditions.isEmpty()) { - stat.appendSQL(" ON "); - for (Token token : joinConditions) { - token.appendSQL(stat, q); - stat.appendSQL(" "); - } - } - } - - boolean getOuterJoin() { - return outerJoin; - } - - Query getQuery() { - return query; - } - - String getAs() { - return as; - } - - void addConditionToken(Token condition) { - joinConditions.add(condition); - } - - T getCurrent() { - return current; - } - - void setCurrent(T current) { - this.current = current; - } - -} diff --git a/src/com/iciql/SubQuery.java b/src/com/iciql/SubQuery.java deleted file mode 100644 index 398d214..0000000 --- a/src/com/iciql/SubQuery.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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; - -public class SubQuery { - - final Query query; - final Z z; - - public SubQuery(Query query, Z x) { - this.query = query; - this.z = x; - } - - public void appendSQL(SQLStatement stat) { - stat.appendSQL(query.toSubQuery(z)); - } -} diff --git a/src/com/iciql/SubQueryCondition.java b/src/com/iciql/SubQueryCondition.java deleted file mode 100644 index effea3b..0000000 --- a/src/com/iciql/SubQueryCondition.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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; - -/** - * A condition that contains a subquery. - * - * @param - * the operand type - */ - -class SubQueryCondition implements Token { - A x; - SubQuery subquery; - - SubQueryCondition(A x, SubQuery subquery) { - this.x = x; - this.subquery = subquery; - } - - public void appendSQL(SQLStatement stat, Query query) { - query.appendSQL(stat, null, x); - stat.appendSQL(" in ("); - subquery.appendSQL(stat); - stat.appendSQL(")"); - } -} diff --git a/src/com/iciql/TableDefinition.java b/src/com/iciql/TableDefinition.java deleted file mode 100644 index 6d8cb6e..0000000 --- a/src/com/iciql/TableDefinition.java +++ /dev/null @@ -1,1233 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 James Moger. - * Copyright 2012 Frédéric Gaillard. - * - * 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; - -import java.lang.reflect.Field; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.iciql.Iciql.ConstraintDeferrabilityType; -import com.iciql.Iciql.ConstraintDeleteType; -import com.iciql.Iciql.ConstraintUpdateType; -import com.iciql.Iciql.EnumId; -import com.iciql.Iciql.EnumType; -import com.iciql.Iciql.IQColumn; -import com.iciql.Iciql.IQConstraint; -import com.iciql.Iciql.IQContraintUnique; -import com.iciql.Iciql.IQContraintsUnique; -import com.iciql.Iciql.IQEnum; -import com.iciql.Iciql.IQContraintForeignKey; -import com.iciql.Iciql.IQContraintsForeignKey; -import com.iciql.Iciql.IQIgnore; -import com.iciql.Iciql.IQIndex; -import com.iciql.Iciql.IQIndexes; -import com.iciql.Iciql.IQSchema; -import com.iciql.Iciql.IQTable; -import com.iciql.Iciql.IQVersion; -import com.iciql.Iciql.IQView; -import com.iciql.Iciql.IndexType; -import com.iciql.util.IciqlLogger; -import com.iciql.util.StatementBuilder; -import com.iciql.util.StringUtils; -import com.iciql.util.Utils; - -/** - * A table definition contains the index definitions of a table, the field - * definitions, the table name, and other meta data. - * - * @param - * the table type - */ - -public class TableDefinition { - - /** - * The meta data of an index. - */ - - public static class IndexDefinition { - public IndexType type; - public String indexName; - - public List columnNames; - } - - /** - * The meta data of a constraint on foreign key. - */ - - public static class ConstraintForeignKeyDefinition { - - public String constraintName; - public List foreignColumns; - public String referenceTable; - public List referenceColumns; - public ConstraintDeleteType deleteType = ConstraintDeleteType.UNSET; - public ConstraintUpdateType updateType = ConstraintUpdateType.UNSET; - public ConstraintDeferrabilityType deferrabilityType = ConstraintDeferrabilityType.UNSET; - } - - /** - * The meta data of a unique constraint. - */ - - public static class ConstraintUniqueDefinition { - - public String constraintName; - public List uniqueColumns; - } - - - /** - * The meta data of a field. - */ - - static class FieldDefinition { - String columnName; - Field field; - String dataType; - int length; - int scale; - boolean isPrimaryKey; - boolean isAutoIncrement; - boolean trim; - boolean nullable; - String defaultValue; - EnumType enumType; - boolean isPrimitive; - String constraint; - - Object getValue(Object obj) { - try { - return field.get(obj); - } catch (Exception e) { - throw new IciqlException(e); - } - } - - private Object initWithNewObject(Object obj) { - Object o = Utils.newObject(field.getType()); - setValue(obj, o); - return o; - } - - private void setValue(Object obj, Object o) { - try { - if (!field.isAccessible()) { - field.setAccessible(true); - } - Class targetType = field.getType(); - if (targetType.isEnum()) { - o = Utils.convertEnum(o, targetType, enumType); - } else { - o = Utils.convert(o, targetType); - } - field.set(obj, o); - } catch (IciqlException e) { - throw e; - } catch (Exception e) { - throw new IciqlException(e); - } - } - - private Object read(ResultSet rs, int columnIndex) { - try { - return rs.getObject(columnIndex); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - @Override - public int hashCode() { - return columnName.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof FieldDefinition) { - return o.hashCode() == hashCode(); - } - return false; - } - } - - public ArrayList fields = Utils.newArrayList(); - String schemaName; - String tableName; - String viewTableName; - int tableVersion; - List primaryKeyColumnNames; - boolean memoryTable; - boolean multiplePrimitiveBools; - - private boolean createIfRequired = true; - private Class clazz; - private IdentityHashMap fieldMap = Utils.newIdentityHashMap(); - private ArrayList indexes = Utils.newArrayList(); - private ArrayList constraintsForeignKey = Utils.newArrayList(); - private ArrayList constraintsUnique = Utils.newArrayList(); - - TableDefinition(Class clazz) { - this.clazz = clazz; - schemaName = null; - tableName = clazz.getSimpleName(); - } - - Class getModelClass() { - return clazz; - } - - List getFields() { - return fields; - } - - void defineSchemaName(String schemaName) { - this.schemaName = schemaName; - } - - void defineTableName(String tableName) { - this.tableName = tableName; - } - - void defineViewTableName(String viewTableName) { - this.viewTableName = viewTableName; - } - - void defineMemoryTable() { - this.memoryTable = true; - } - - void defineSkipCreate() { - this.createIfRequired = false; - } - - /** - * Define a primary key by the specified model fields. - * - * @param modelFields - * the ordered list of model fields - */ - void definePrimaryKey(Object[] modelFields) { - List columnNames = mapColumnNames(modelFields); - setPrimaryKey(columnNames); - } - - /** - * Define a primary key by the specified column names. - * - * @param columnNames - * the ordered list of column names - */ - private void setPrimaryKey(List columnNames) { - primaryKeyColumnNames = Utils.newArrayList(columnNames); - List pkNames = Utils.newArrayList(); - for (String name : columnNames) { - pkNames.add(name.toLowerCase()); - } - // set isPrimaryKey flag for all field definitions - for (FieldDefinition fieldDefinition : fieldMap.values()) { - fieldDefinition.isPrimaryKey = pkNames.contains(fieldDefinition.columnName.toLowerCase()); - } - } - - private String getColumnName(A fieldObject) { - FieldDefinition def = fieldMap.get(fieldObject); - return def == null ? null : def.columnName; - } - - private ArrayList mapColumnNames(Object[] columns) { - ArrayList columnNames = Utils.newArrayList(); - for (Object column : columns) { - columnNames.add(getColumnName(column)); - } - return columnNames; - } - - /** - * Defines an index with the specified model fields. - * - * @param name - * the index name (optional) - * @param type - * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH) - * @param modelFields - * the ordered list of model fields - */ - void defineIndex(String name, IndexType type, Object[] modelFields) { - List columnNames = mapColumnNames(modelFields); - addIndex(name, type, columnNames); - } - - /** - * Defines an index with the specified column names. - * - * @param type - * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH) - * @param columnNames - * the ordered list of column names - */ - private void addIndex(String name, IndexType type, List columnNames) { - IndexDefinition index = new IndexDefinition(); - if (StringUtils.isNullOrEmpty(name)) { - index.indexName = tableName + "_idx_" + indexes.size(); - } else { - index.indexName = name; - } - index.columnNames = Utils.newArrayList(columnNames); - index.type = type; - indexes.add(index); - } - - /** - * Defines an unique constraint with the specified model fields. - * - * @param name - * the constraint name (optional) - * @param modelFields - * the ordered list of model fields - */ - void defineConstraintUnique(String name, Object[] modelFields) { - List columnNames = mapColumnNames(modelFields); - addConstraintUnique(name, columnNames); - } - - /** - * Defines an unique constraint. - * - * @param name - * @param columnNames - */ - private void addConstraintUnique(String name, List columnNames) { - ConstraintUniqueDefinition constraint = new ConstraintUniqueDefinition(); - if (StringUtils.isNullOrEmpty(name)) { - constraint.constraintName = tableName + "_unique_" + constraintsUnique.size(); - } else { - constraint.constraintName = name; - } - constraint.uniqueColumns = Utils.newArrayList(columnNames); - constraintsUnique.add(constraint); - } - - /** - * Defines a foreign key constraint with the specified model fields. - * - * @param name - * the constraint name (optional) - * @param modelFields - * the ordered list of model fields - */ - void defineForeignKey(String name, Object[] modelFields, String refTableName, Object[] refModelFields, - ConstraintDeleteType deleteType, ConstraintUpdateType updateType, - ConstraintDeferrabilityType deferrabilityType) { - List columnNames = mapColumnNames(modelFields); - List referenceColumnNames = mapColumnNames(refModelFields); - addConstraintForeignKey(name, columnNames, refTableName, referenceColumnNames, - deleteType, updateType, deferrabilityType); - } - - void defineColumnName(Object column, String columnName) { - FieldDefinition def = fieldMap.get(column); - if (def != null) { - def.columnName = columnName; - } - } - - void defineAutoIncrement(Object column) { - FieldDefinition def = fieldMap.get(column); - if (def != null) { - def.isAutoIncrement = true; - } - } - - void defineLength(Object column, int length) { - FieldDefinition def = fieldMap.get(column); - if (def != null) { - def.length = length; - } - } - - void defineScale(Object column, int scale) { - FieldDefinition def = fieldMap.get(column); - if (def != null) { - def.scale = scale; - } - } - - void defineTrim(Object column) { - FieldDefinition def = fieldMap.get(column); - if (def != null) { - def.trim = true; - } - } - - void defineNullable(Object column, boolean isNullable) { - FieldDefinition def = fieldMap.get(column); - if (def != null) { - def.nullable = isNullable; - } - } - - void defineDefaultValue(Object column, String defaultValue) { - FieldDefinition def = fieldMap.get(column); - if (def != null) { - def.defaultValue = defaultValue; - } - } - - void defineConstraint(Object column, String constraint) { - FieldDefinition def = fieldMap.get(column); - if (def != null) { - def.constraint = constraint; - } - } - - void mapFields() { - boolean byAnnotationsOnly = false; - boolean inheritColumns = false; - if (clazz.isAnnotationPresent(IQTable.class)) { - IQTable tableAnnotation = clazz.getAnnotation(IQTable.class); - byAnnotationsOnly = tableAnnotation.annotationsOnly(); - inheritColumns = tableAnnotation.inheritColumns(); - } - - if (clazz.isAnnotationPresent(IQView.class)) { - IQView viewAnnotation = clazz.getAnnotation(IQView.class); - byAnnotationsOnly = viewAnnotation.annotationsOnly(); - inheritColumns = viewAnnotation.inheritColumns(); - } - - List classFields = Utils.newArrayList(); - classFields.addAll(Arrays.asList(clazz.getDeclaredFields())); - if (inheritColumns) { - Class superClass = clazz.getSuperclass(); - classFields.addAll(Arrays.asList(superClass.getDeclaredFields())); - - if (superClass.isAnnotationPresent(IQView.class)) { - IQView superView = superClass.getAnnotation(IQView.class); - if (superView.inheritColumns()) { - // inherit columns from super.super.class - Class superSuperClass = superClass.getSuperclass(); - classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields())); - } - } else if (superClass.isAnnotationPresent(IQTable.class)) { - IQTable superTable = superClass.getAnnotation(IQTable.class); - if (superTable.inheritColumns()) { - // inherit columns from super.super.class - Class superSuperClass = superClass.getSuperclass(); - classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields())); - } - } - } - - Set uniqueFields = new LinkedHashSet(); - T defaultObject = Db.instance(clazz); - for (Field f : classFields) { - // check if we should skip this field - if (f.isAnnotationPresent(IQIgnore.class)) { - continue; - } - - // default to field name - String columnName = f.getName(); - boolean isAutoIncrement = false; - boolean isPrimaryKey = false; - int length = 0; - int scale = 0; - boolean trim = false; - boolean nullable = !f.getType().isPrimitive(); - EnumType enumType = null; - String defaultValue = ""; - String constraint = ""; - // configure Java -> SQL enum mapping - if (f.getType().isEnum()) { - enumType = EnumType.DEFAULT_TYPE; - if (f.getType().isAnnotationPresent(IQEnum.class)) { - // enum definition is annotated for all instances - IQEnum iqenum = f.getType().getAnnotation(IQEnum.class); - enumType = iqenum.value(); - } - if (f.isAnnotationPresent(IQEnum.class)) { - // this instance of the enum is annotated - IQEnum iqenum = f.getAnnotation(IQEnum.class); - enumType = iqenum.value(); - } - } - - // try using default object - try { - f.setAccessible(true); - Object value = f.get(defaultObject); - if (value != null) { - if (value.getClass().isEnum()) { - // enum default, convert to target type - Enum anEnum = (Enum) value; - Object o = Utils.convertEnum(anEnum, enumType); - defaultValue = ModelUtils.formatDefaultValue(o); - } else { - // object default - defaultValue = ModelUtils.formatDefaultValue(value); - } - } - } catch (IllegalAccessException e) { - throw new IciqlException(e, "failed to get default object for {0}", columnName); - } - - boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class); - if (hasAnnotation) { - IQColumn col = f.getAnnotation(IQColumn.class); - if (!StringUtils.isNullOrEmpty(col.name())) { - columnName = col.name(); - } - isAutoIncrement = col.autoIncrement(); - isPrimaryKey = col.primaryKey(); - length = col.length(); - scale = col.scale(); - trim = col.trim(); - nullable = col.nullable(); - - // annotation overrides - if (!StringUtils.isNullOrEmpty(col.defaultValue())) { - defaultValue = col.defaultValue(); - } - } - - boolean hasConstraint = f.isAnnotationPresent(IQConstraint.class); - if (hasConstraint) { - IQConstraint con = f.getAnnotation(IQConstraint.class); - // annotation overrides - if (!StringUtils.isNullOrEmpty(con.value())) { - constraint = con.value(); - } - } - - boolean reflectiveMatch = !byAnnotationsOnly; - if (reflectiveMatch || hasAnnotation || hasConstraint) { - FieldDefinition fieldDef = new FieldDefinition(); - fieldDef.isPrimitive = f.getType().isPrimitive(); - fieldDef.field = f; - fieldDef.columnName = columnName; - fieldDef.isAutoIncrement = isAutoIncrement; - fieldDef.isPrimaryKey = isPrimaryKey; - fieldDef.length = length; - fieldDef.scale = scale; - fieldDef.trim = trim; - fieldDef.nullable = nullable; - fieldDef.defaultValue = defaultValue; - fieldDef.enumType = enumType; - fieldDef.dataType = ModelUtils.getDataType(fieldDef); - fieldDef.constraint = constraint; - uniqueFields.add(fieldDef); - } - } - fields.addAll(uniqueFields); - - List primaryKey = Utils.newArrayList(); - int primitiveBoolean = 0; - for (FieldDefinition fieldDef : fields) { - if (fieldDef.isPrimaryKey) { - primaryKey.add(fieldDef.columnName); - } - if (fieldDef.isPrimitive && fieldDef.field.getType().equals(boolean.class)) { - primitiveBoolean++; - } - } - if (primitiveBoolean > 1) { - multiplePrimitiveBools = true; - IciqlLogger - .warn("Model {0} has multiple primitive booleans! Possible where,set,join clause problem!"); - } - if (primaryKey.size() > 0) { - setPrimaryKey(primaryKey); - } - } - - void checkMultipleBooleans() { - if (multiplePrimitiveBools) { - throw new IciqlException( - "Can not explicitly reference a primitive boolean if there are multiple boolean fields in your model class!"); - } - } - - void checkMultipleEnums(Object o) { - if (o == null) { - return; - } - Class clazz = o.getClass(); - if (!clazz.isEnum()) { - return; - } - - int fieldCount = 0; - for (FieldDefinition fieldDef : fields) { - Class targetType = fieldDef.field.getType(); - if (clazz.equals(targetType)) { - fieldCount++; - } - } - - if (fieldCount > 1) { - throw new IciqlException( - "Can not explicitly reference {0} because there are {1} {0} fields in your model class!", - clazz.getSimpleName(), fieldCount); - } - } - - /** - * Optionally truncates strings to the maximum length and converts - * java.lang.Enum types to Strings or Integers. - */ - Object getValue(Object obj, FieldDefinition field) { - Object value = field.getValue(obj); - if (value == null) { - return value; - } - if (field.enumType != null) { - // convert enumeration to INT or STRING - Enum iqenum = (Enum) value; - switch (field.enumType) { - case NAME: - if (field.trim && field.length > 0) { - if (iqenum.name().length() > field.length) { - return iqenum.name().substring(0, field.length); - } - } - return iqenum.name(); - case ORDINAL: - return iqenum.ordinal(); - case ENUMID: - if (!EnumId.class.isAssignableFrom(value.getClass())) { - throw new IciqlException(field.field.getName() + " does not implement EnumId!"); - } - EnumId enumid = (EnumId) value; - return enumid.enumId(); - } - } - - if (field.trim && field.length > 0) { - if (value instanceof String) { - // clip strings - String s = (String) value; - if (s.length() > field.length) { - return s.substring(0, field.length); - } - return s; - } - return value; - } - - // return the value unchanged - return value; - } - - PreparedStatement createInsertStatement(Db db, Object obj, boolean returnKey) { - SQLStatement stat = new SQLStatement(db); - StatementBuilder buff = new StatementBuilder("INSERT INTO "); - buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('('); - for (FieldDefinition field : fields) { - if (skipInsertField(field, obj)) { - continue; - } - buff.appendExceptFirst(", "); - buff.append(db.getDialect().prepareColumnName(field.columnName)); - } - buff.append(") VALUES("); - buff.resetCount(); - for (FieldDefinition field : fields) { - if (skipInsertField(field, obj)) { - continue; - } - buff.appendExceptFirst(", "); - buff.append('?'); - Object value = getValue(obj, field); - if (value == null && !field.nullable) { - // try to interpret and instantiate a default value - value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); - } - stat.addParameter(value); - } - buff.append(')'); - stat.setSQL(buff.toString()); - IciqlLogger.insert(stat.getSQL()); - return stat.prepare(returnKey); - } - - long insert(Db db, Object obj, boolean returnKey) { - if (!StringUtils.isNullOrEmpty(viewTableName)) { - throw new IciqlException("Iciql does not support inserting rows into views!"); - } - SQLStatement stat = new SQLStatement(db); - StatementBuilder buff = new StatementBuilder("INSERT INTO "); - buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('('); - for (FieldDefinition field : fields) { - if (skipInsertField(field, obj)) { - continue; - } - buff.appendExceptFirst(", "); - buff.append(db.getDialect().prepareColumnName(field.columnName)); - } - buff.append(") VALUES("); - buff.resetCount(); - for (FieldDefinition field : fields) { - if (skipInsertField(field, obj)) { - continue; - } - buff.appendExceptFirst(", "); - buff.append('?'); - Object value = getValue(obj, field); - if (value == null && !field.nullable) { - // try to interpret and instantiate a default value - value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); - } - stat.addParameter(value); - } - buff.append(')'); - stat.setSQL(buff.toString()); - IciqlLogger.insert(stat.getSQL()); - if (returnKey) { - return stat.executeInsert(); - } - return stat.executeUpdate(); - } - - private boolean skipInsertField(FieldDefinition field, Object obj) { - if (field.isAutoIncrement) { - Object value = getValue(obj, field); - if (field.isPrimitive) { - // skip uninitialized primitive autoincrement values - if (value.toString().equals("0")) { - return true; - } - } else if (value == null) { - // skip null object autoincrement values - return true; - } - } else { - // conditionally skip insert of null - Object value = getValue(obj, field); - if (value == null) { - if (field.nullable) { - // skip null assignment, field is nullable - return true; - } else if (StringUtils.isNullOrEmpty(field.defaultValue)) { - IciqlLogger.warn("no default value, skipping null insert assignment for {0}.{1}", - tableName, field.columnName); - return true; - } - } - } - return false; - } - - int merge(Db db, Object obj) { - if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) { - throw new IllegalStateException("No primary key columns defined for table " + obj.getClass() - + " - no update possible"); - } - SQLStatement stat = new SQLStatement(db); - db.getDialect().prepareMerge(stat, schemaName, tableName, this, obj); - IciqlLogger.merge(stat.getSQL()); - return stat.executeUpdate(); - } - - int update(Db db, Object obj) { - if (!StringUtils.isNullOrEmpty(viewTableName)) { - throw new IciqlException("Iciql does not support updating rows in views!"); - } - if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) { - throw new IllegalStateException("No primary key columns defined for table " + obj.getClass() - + " - no update possible"); - } - SQLStatement stat = new SQLStatement(db); - StatementBuilder buff = new StatementBuilder("UPDATE "); - buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" SET "); - buff.resetCount(); - - for (FieldDefinition field : fields) { - if (!field.isPrimaryKey) { - Object value = getValue(obj, field); - if (value == null && !field.nullable) { - // try to interpret and instantiate a default value - value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); - } - buff.appendExceptFirst(", "); - buff.append(db.getDialect().prepareColumnName(field.columnName)); - buff.append(" = ?"); - stat.addParameter(value); - } - } - Object alias = Utils.newObject(obj.getClass()); - Query query = Query.from(db, alias); - boolean firstCondition = true; - for (FieldDefinition field : fields) { - if (field.isPrimaryKey) { - Object fieldAlias = field.getValue(alias); - Object value = field.getValue(obj); - if (field.isPrimitive) { - fieldAlias = query.getPrimitiveAliasByValue(fieldAlias); - } - if (!firstCondition) { - query.addConditionToken(ConditionAndOr.AND); - } - firstCondition = false; - query.addConditionToken(new Condition(fieldAlias, value, CompareType.EQUAL)); - } - } - stat.setSQL(buff.toString()); - query.appendWhere(stat); - IciqlLogger.update(stat.getSQL()); - return stat.executeUpdate(); - } - - int delete(Db db, Object obj) { - if (!StringUtils.isNullOrEmpty(viewTableName)) { - throw new IciqlException("Iciql does not support deleting rows from views!"); - } - if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) { - throw new IllegalStateException("No primary key columns defined for table " + obj.getClass() - + " - no update possible"); - } - SQLStatement stat = new SQLStatement(db); - StatementBuilder buff = new StatementBuilder("DELETE FROM "); - buff.append(db.getDialect().prepareTableName(schemaName, tableName)); - buff.resetCount(); - Object alias = Utils.newObject(obj.getClass()); - Query query = Query.from(db, alias); - boolean firstCondition = true; - for (FieldDefinition field : fields) { - if (field.isPrimaryKey) { - Object fieldAlias = field.getValue(alias); - Object value = field.getValue(obj); - if (field.isPrimitive) { - fieldAlias = query.getPrimitiveAliasByValue(fieldAlias); - } - if (!firstCondition) { - query.addConditionToken(ConditionAndOr.AND); - } - firstCondition = false; - query.addConditionToken(new Condition(fieldAlias, value, CompareType.EQUAL)); - } - } - stat.setSQL(buff.toString()); - query.appendWhere(stat); - IciqlLogger.delete(stat.getSQL()); - return stat.executeUpdate(); - } - - TableDefinition createIfRequired(Db db) { - // globally enable/disable check of create if required - if (db.getSkipCreate()) { - return this; - } - if (!createIfRequired) { - // skip table and index creation - // but still check for upgrades - db.upgradeTable(this); - return this; - } - if (db.hasCreated(clazz)) { - return this; - } - SQLStatement stat = new SQLStatement(db); - if (StringUtils.isNullOrEmpty(viewTableName)) { - db.getDialect().prepareCreateTable(stat, this); - } else { - db.getDialect().prepareCreateView(stat, this); - } - IciqlLogger.create(stat.getSQL()); - try { - stat.executeUpdate(); - } catch (IciqlException e) { - if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) { - throw e; - } - } - - // create indexes - for (IndexDefinition index : indexes) { - stat = new SQLStatement(db); - db.getDialect().prepareCreateIndex(stat, schemaName, tableName, index); - IciqlLogger.create(stat.getSQL()); - try { - stat.executeUpdate(); - } catch (IciqlException e) { - if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS - && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) { - throw e; - } - } - } - - // create unique constraints - for (ConstraintUniqueDefinition constraint : constraintsUnique) { - stat = new SQLStatement(db); - db.getDialect().prepareCreateConstraintUnique(stat, schemaName, tableName, constraint); - IciqlLogger.create(stat.getSQL()); - try { - stat.executeUpdate(); - } catch (IciqlException e) { - if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS - && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) { - throw e; - } - } - } - - // create foreign keys constraints - for (ConstraintForeignKeyDefinition constraint : constraintsForeignKey) { - stat = new SQLStatement(db); - db.getDialect().prepareCreateConstraintForeignKey(stat, schemaName, tableName, constraint); - IciqlLogger.create(stat.getSQL()); - try { - stat.executeUpdate(); - } catch (IciqlException e) { - if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS - && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) { - throw e; - } - } - } - - // tables are created using IF NOT EXISTS - // but we may still need to upgrade - db.upgradeTable(this); - return this; - } - - void mapObject(Object obj) { - fieldMap.clear(); - initObject(obj, fieldMap); - - if (clazz.isAnnotationPresent(IQSchema.class)) { - IQSchema schemaAnnotation = clazz.getAnnotation(IQSchema.class); - // setup schema name mapping, if properly annotated - if (!StringUtils.isNullOrEmpty(schemaAnnotation.value())) { - schemaName = schemaAnnotation.value(); - } - } - - if (clazz.isAnnotationPresent(IQTable.class)) { - IQTable tableAnnotation = clazz.getAnnotation(IQTable.class); - - // setup table name mapping, if properly annotated - if (!StringUtils.isNullOrEmpty(tableAnnotation.name())) { - tableName = tableAnnotation.name(); - } - - // allow control over createTableIfRequired() - createIfRequired = tableAnnotation.create(); - - // model version - if (clazz.isAnnotationPresent(IQVersion.class)) { - IQVersion versionAnnotation = clazz.getAnnotation(IQVersion.class); - if (versionAnnotation.value() > 0) { - tableVersion = versionAnnotation.value(); - } - } - - // setup the primary index, if properly annotated - if (tableAnnotation.primaryKey().length > 0) { - List primaryKey = Utils.newArrayList(); - primaryKey.addAll(Arrays.asList(tableAnnotation.primaryKey())); - setPrimaryKey(primaryKey); - } - } - - if (clazz.isAnnotationPresent(IQView.class)) { - IQView viewAnnotation = clazz.getAnnotation(IQView.class); - - // setup view name mapping, if properly annotated - // set this as the table name so it fits in seemlessly with iciql - if (!StringUtils.isNullOrEmpty(viewAnnotation.name())) { - tableName = viewAnnotation.name(); - } else { - tableName = clazz.getSimpleName(); - } - - // setup source table name mapping, if properly annotated - if (!StringUtils.isNullOrEmpty(viewAnnotation.tableName())) { - viewTableName = viewAnnotation.tableName(); - } else { - // check for IQTable annotation on super class - Class superClass = clazz.getSuperclass(); - if (superClass.isAnnotationPresent(IQTable.class)) { - IQTable table = superClass.getAnnotation(IQTable.class); - if (StringUtils.isNullOrEmpty(table.name())) { - // super.SimpleClassName - viewTableName = superClass.getSimpleName(); - } else { - // super.IQTable.name() - viewTableName = table.name(); - } - } else if (superClass.isAnnotationPresent(IQView.class)) { - // super class is a view - IQView parentView = superClass.getAnnotation(IQView.class); - if (StringUtils.isNullOrEmpty(parentView.tableName())) { - // parent view does not define a tableName, must be inherited - Class superParent = superClass.getSuperclass(); - if (superParent != null && superParent.isAnnotationPresent(IQTable.class)) { - IQTable superParentTable = superParent.getAnnotation(IQTable.class); - if (StringUtils.isNullOrEmpty(superParentTable.name())) { - // super.super.SimpleClassName - viewTableName = superParent.getSimpleName(); - } else { - // super.super.IQTable.name() - viewTableName = superParentTable.name(); - } - } - } else { - // super.IQView.tableName() - viewTableName = parentView.tableName(); - } - } - - if (StringUtils.isNullOrEmpty(viewTableName)) { - // still missing view table name - throw new IciqlException("View model class \"{0}\" is missing a table name!", tableName); - } - } - - // allow control over createTableIfRequired() - createIfRequired = viewAnnotation.create(); - } - - if (clazz.isAnnotationPresent(IQIndex.class)) { - // single table index - IQIndex index = clazz.getAnnotation(IQIndex.class); - addIndex(index); - } - - if (clazz.isAnnotationPresent(IQIndexes.class)) { - // multiple table indexes - IQIndexes indexes = clazz.getAnnotation(IQIndexes.class); - for (IQIndex index : indexes.value()) { - addIndex(index); - } - } - - if (clazz.isAnnotationPresent(IQContraintUnique.class)) { - // single table unique constraint - IQContraintUnique constraint = clazz.getAnnotation(IQContraintUnique.class); - addConstraintUnique(constraint); - } - - if (clazz.isAnnotationPresent(IQContraintsUnique.class)) { - // multiple table unique constraints - IQContraintsUnique constraints = clazz.getAnnotation(IQContraintsUnique.class); - for (IQContraintUnique constraint : constraints.value()) { - addConstraintUnique(constraint); - } - } - - if (clazz.isAnnotationPresent(IQContraintForeignKey.class)) { - // single table constraint - IQContraintForeignKey constraint = clazz.getAnnotation(IQContraintForeignKey.class); - addConstraintForeignKey(constraint); - } - - if (clazz.isAnnotationPresent(IQContraintsForeignKey.class)) { - // multiple table constraints - IQContraintsForeignKey constraints = clazz.getAnnotation(IQContraintsForeignKey.class); - for (IQContraintForeignKey constraint : constraints.value()) { - addConstraintForeignKey(constraint); - } - } - - } - - private void addConstraintForeignKey(IQContraintForeignKey constraint) { - List foreignColumns = Arrays.asList(constraint.foreignColumns()); - List referenceColumns = Arrays.asList(constraint.referenceColumns()); - addConstraintForeignKey(constraint.name(), foreignColumns, constraint.referenceName(), referenceColumns, constraint.deleteType(), constraint.updateType(), constraint.deferrabilityType()); - } - - private void addConstraintUnique(IQContraintUnique constraint) { - List uniqueColumns = Arrays.asList(constraint.uniqueColumns()); - addConstraintUnique(constraint.name(), uniqueColumns); - } - - /** - * Defines a foreign key constraint with the specified parameters. - * - * @param name - * name of the constraint - * @param foreignColumns - * list of columns declared as foreign - * @param referenceName - * reference table name - * @param referenceColumns - * list of columns used in reference table - * @param deleteType - * action on delete - * @param updateType - * action on update - * @param deferrabilityType - * deferrability mode - */ - private void addConstraintForeignKey(String name, - List foreignColumns, String referenceName, - List referenceColumns, ConstraintDeleteType deleteType, - ConstraintUpdateType updateType, ConstraintDeferrabilityType deferrabilityType) { - ConstraintForeignKeyDefinition constraint = new ConstraintForeignKeyDefinition(); - if (StringUtils.isNullOrEmpty(name)) { - constraint.constraintName = tableName + "_fkey_" + constraintsForeignKey.size(); - } else { - constraint.constraintName = name; - } - constraint.foreignColumns = Utils.newArrayList(foreignColumns); - constraint.referenceColumns = Utils.newArrayList(referenceColumns); - constraint.referenceTable = referenceName; - constraint.deleteType = deleteType; - constraint.updateType = updateType; - constraint.deferrabilityType = deferrabilityType; - constraintsForeignKey.add(constraint); - } - - private void addIndex(IQIndex index) { - List columns = Arrays.asList(index.value()); - addIndex(index.name(), index.type(), columns); - } - - List getIndexes() { - return indexes; - } - - List getContraintsUnique() { - return constraintsUnique; - } - - List getContraintsForeignKey() { - return constraintsForeignKey; - } - - private void initObject(Object obj, Map map) { - for (FieldDefinition def : fields) { - Object newValue = def.initWithNewObject(obj); - map.put(newValue, def); - } - } - - void initSelectObject(SelectTable table, Object obj, Map> map) { - for (FieldDefinition def : fields) { - Object newValue = def.initWithNewObject(obj); - SelectColumn column = new SelectColumn(table, def); - map.put(newValue, column); - } - } - - /** - * Most queries executed by iciql have named select lists (select alpha, - * beta where...) but sometimes a wildcard select is executed (select *). - * When a wildcard query is executed on a table that has more columns than - * are mapped in your model object, this creates a column mapping issue. - * JaQu assumed that you can always use the integer index of the - * reflectively mapped field definition to determine position in the result - * set. - * - * This is not always true. - * - * iciql identifies when a select * query is executed and maps column names - * to a column index from the result set. If the select statement is - * explicit, then the standard assumed column index is used instead. - * - * @param rs - * @return - */ - int[] mapColumns(boolean wildcardSelect, ResultSet rs) { - int[] columns = new int[fields.size()]; - for (int i = 0; i < fields.size(); i++) { - try { - FieldDefinition def = fields.get(i); - int columnIndex; - if (wildcardSelect) { - // select * - // create column index by field name - columnIndex = rs.findColumn(def.columnName); - } else { - // select alpha, beta, gamma, etc - // explicit select order - columnIndex = i + 1; - } - columns[i] = columnIndex; - } catch (SQLException s) { - throw new IciqlException(s); - } - } - return columns; - } - - void readRow(Object item, ResultSet rs, int[] columns) { - for (int i = 0; i < fields.size(); i++) { - FieldDefinition def = fields.get(i); - int index = columns[i]; - Object o = def.read(rs, index); - def.setValue(item, o); - } - } - - void appendSelectList(SQLStatement stat) { - for (int i = 0; i < fields.size(); i++) { - if (i > 0) { - stat.appendSQL(", "); - } - FieldDefinition def = fields.get(i); - stat.appendColumn(def.columnName); - } - } - - void appendSelectList(SQLStatement stat, Query query, X x) { - // select t0.col1, t0.col2, t0.col3... - // select table1.col1, table1.col2, table1.col3... - String selectDot = ""; - SelectTable sel = query.getSelectTable(x); - if (sel != null) { - if (query.isJoin()) { - selectDot = sel.getAs() + "."; - } else { - String sn = sel.getAliasDefinition().schemaName; - String tn = sel.getAliasDefinition().tableName; - selectDot = query.getDb().getDialect().prepareTableName(sn, tn) + "."; - } - } - - for (int i = 0; i < fields.size(); i++) { - if (i > 0) { - stat.appendSQL(", "); - } - stat.appendSQL(selectDot); - FieldDefinition def = fields.get(i); - if (def.isPrimitive) { - Object obj = def.getValue(x); - Object alias = query.getPrimitiveAliasByValue(obj); - query.appendSQL(stat, x, alias); - } else { - Object obj = def.getValue(x); - query.appendSQL(stat, x, obj); - } - } - } -} diff --git a/src/com/iciql/TableInspector.java b/src/com/iciql/TableInspector.java deleted file mode 100644 index b717203..0000000 --- a/src/com/iciql/TableInspector.java +++ /dev/null @@ -1,723 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 James Moger. - * Copyright 2012 Frédéric Gaillard. - * - * 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; - -import static com.iciql.ValidationRemark.consider; -import static com.iciql.ValidationRemark.error; -import static com.iciql.ValidationRemark.warn; -import static com.iciql.util.JdbcUtils.closeSilently; -import static com.iciql.util.StringUtils.isNullOrEmpty; -import static java.text.MessageFormat.format; - -import java.io.Serializable; -import java.lang.reflect.Modifier; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.iciql.Iciql.IQColumn; -import com.iciql.Iciql.IQIndex; -import com.iciql.Iciql.IQIndexes; -import com.iciql.Iciql.IQSchema; -import com.iciql.Iciql.IQTable; -import com.iciql.Iciql.IndexType; -import com.iciql.TableDefinition.ConstraintForeignKeyDefinition; -import com.iciql.TableDefinition.ConstraintUniqueDefinition; -import com.iciql.TableDefinition.FieldDefinition; -import com.iciql.TableDefinition.IndexDefinition; -import com.iciql.util.StatementBuilder; -import com.iciql.util.StringUtils; -import com.iciql.util.Utils; - -/** - * Class to inspect the contents of a particular table including its indexes. - * This class does the bulk of the work in terms of model generation and model - * validation. - */ -public class TableInspector { - - private String schema; - private String table; - private Class dateTimeClass; - private List primaryKeys = Utils.newArrayList(); - private Map indexes; - private Map columns; - private final String eol = "\n"; - - TableInspector(String schema, String table, Class dateTimeClass) { - this.schema = schema; - this.table = table; - this.dateTimeClass = dateTimeClass; - } - - /** - * Tests to see if this TableInspector represents schema.table. - *

- * - * @param schema - * the schema name - * @param table - * the table name - * @return true if the table matches - */ - boolean matches(String schema, String table) { - if (isNullOrEmpty(schema)) { - // table name matching - return this.table.equalsIgnoreCase(table); - } else if (isNullOrEmpty(table)) { - // schema name matching - return this.schema.equalsIgnoreCase(schema); - } else { - // exact table matching - return this.schema.equalsIgnoreCase(schema) && this.table.equalsIgnoreCase(table); - } - } - - /** - * Reads the DatabaseMetaData for the details of this table including - * primary keys and indexes. - * - * @param metaData - * the database meta data - */ - void read(DatabaseMetaData metaData) throws SQLException { - ResultSet rs = null; - - // primary keys - try { - rs = metaData.getPrimaryKeys(null, schema, table); - while (rs.next()) { - String c = rs.getString("COLUMN_NAME"); - primaryKeys.add(c); - } - closeSilently(rs); - - // indexes - rs = metaData.getIndexInfo(null, schema, table, false, true); - indexes = Utils.newHashMap(); - while (rs.next()) { - IndexInspector info = new IndexInspector(rs); - if (info.type.equals(IndexType.UNIQUE)) { - String name = info.name.toLowerCase(); - if (name.startsWith("primary") || name.startsWith("sys_idx_sys_pk") - || name.startsWith("sql") || name.endsWith("_pkey")) { - // skip primary key indexes - continue; - } - } - if (indexes.containsKey(info.name)) { - indexes.get(info.name).addColumn(rs); - } else { - indexes.put(info.name, info); - } - } - closeSilently(rs); - - // columns - rs = metaData.getColumns(null, schema, table, null); - columns = Utils.newHashMap(); - while (rs.next()) { - ColumnInspector col = new ColumnInspector(); - col.name = rs.getString("COLUMN_NAME"); - col.type = rs.getString("TYPE_NAME"); - col.clazz = ModelUtils.getClassForSqlType(col.type, dateTimeClass); - col.size = rs.getInt("COLUMN_SIZE"); - col.nullable = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable; - 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; - } - } - if (!col.isAutoIncrement) { - col.defaultValue = rs.getString("COLUMN_DEF"); - } - columns.put(col.name.toLowerCase(), col); - } - } finally { - closeSilently(rs); - } - } - - /** - * Generates a model (class definition) from this table. The model includes - * indexes, primary keys, default values, lengths, and nullables. - * information. - *

- * The caller may optionally set a destination package name, whether or not - * to include the schema name (setting schema can be a problem when using - * the model between databases), and if to automatically trim strings for - * those that have a maximum length. - *

- * - * @param packageName - * @param annotateSchema - * @param trimStrings - * @return a complete model (class definition) for this table as a string - */ - String generateModel(String packageName, boolean annotateSchema, boolean trimStrings) { - - // import statements - Set imports = Utils.newHashSet(); - imports.add(Serializable.class.getCanonicalName()); - imports.add(IQSchema.class.getCanonicalName()); - imports.add(IQTable.class.getCanonicalName()); - imports.add(IQIndexes.class.getCanonicalName()); - imports.add(IQIndex.class.getCanonicalName()); - imports.add(IQColumn.class.getCanonicalName()); - imports.add(IndexType.class.getCanonicalName()); - - // fields - StringBuilder fields = new StringBuilder(); - List sortedColumns = Utils.newArrayList(columns.values()); - Collections.sort(sortedColumns); - for (ColumnInspector col : sortedColumns) { - fields.append(generateColumn(imports, col, trimStrings)); - } - - // build complete class definition - StringBuilder model = new StringBuilder(); - if (!isNullOrEmpty(packageName)) { - // package - model.append("package " + packageName + ";"); - model.append(eol).append(eol); - } - - // imports - List sortedImports = new ArrayList(imports); - Collections.sort(sortedImports); - for (String imp : sortedImports) { - model.append("import ").append(imp).append(';').append(eol); - } - model.append(eol); - - // @IQSchema - if (annotateSchema && !isNullOrEmpty(schema)) { - model.append('@').append(IQSchema.class.getSimpleName()); - model.append('('); - AnnotationBuilder ap = new AnnotationBuilder(); - ap.addParameter(null, schema); - model.append(ap); - model.append(')').append(eol); - } - - // @IQTable - model.append('@').append(IQTable.class.getSimpleName()); - model.append('('); - - // IQTable annotation parameters - AnnotationBuilder ap = new AnnotationBuilder(); - ap.addParameter("name", table); - - if (primaryKeys.size() > 1) { - ap.addParameter("primaryKey", primaryKeys); - } - - // finish @IQTable annotation - model.append(ap); - model.append(')').append(eol); - - // @IQIndexes - // @IQIndex - String indexAnnotations = generateIndexAnnotations(); - if (!StringUtils.isNullOrEmpty(indexAnnotations)) { - model.append(indexAnnotations); - } - - // class declaration - String clazzName = ModelUtils.convertTableToClassName(table); - model.append(format("public class {0} implements Serializable '{'", clazzName)).append(eol); - model.append(eol); - model.append("\tprivate static final long serialVersionUID = 1L;").append(eol); - model.append(eol); - - // field declarations - model.append(fields); - - // default constructor - model.append("\t" + "public ").append(clazzName).append("() {").append(eol); - model.append("\t}").append(eol); - - // end of class body - model.append('}'); - model.trimToSize(); - return model.toString(); - } - - /** - * Generates the specified index annotation. - * - * @param ap - */ - String generateIndexAnnotations() { - if (indexes == null || indexes.size() == 0) { - // no matching indexes - return null; - } - AnnotationBuilder ap = new AnnotationBuilder(); - if (indexes.size() == 1) { - // single index - IndexInspector index = indexes.values().toArray(new IndexInspector[1])[0]; - ap.append(generateIndexAnnotation(index)); - ap.append(eol); - } else { - // multiple indexes - ap.append('@').append(IQIndexes.class.getSimpleName()); - ap.append("({"); - ap.resetCount(); - for (IndexInspector index : indexes.values()) { - ap.appendExceptFirst(", "); - ap.append(generateIndexAnnotation(index)); - } - ap.append("})").append(eol); - } - return ap.toString(); - } - - private String generateIndexAnnotation(IndexInspector index) { - AnnotationBuilder ap = new AnnotationBuilder(); - ap.append('@').append(IQIndex.class.getSimpleName()); - ap.append('('); - ap.resetCount(); - if (!StringUtils.isNullOrEmpty(index.name)) { - ap.addParameter("name", index.name); - } - if (!index.type.equals(IndexType.STANDARD)) { - ap.addEnum("type", index.type); - } - if (ap.getCount() > 0) { - // multiple fields specified - ap.addParameter("value", index.columns); - } else { - // default value - ap.addParameter(null, index.columns); - } - ap.append(')'); - return ap.toString(); - } - - private StatementBuilder generateColumn(Set imports, ColumnInspector col, boolean trimStrings) { - StatementBuilder sb = new StatementBuilder(); - Class clazz = col.clazz; - String column = ModelUtils.convertColumnToFieldName(col.name.toLowerCase()); - sb.append('\t'); - if (clazz == null) { - // unsupported type - clazz = Object.class; - sb.append("// unsupported type " + col.type); - } else { - // Imports - // don't import primitives, java.lang classes, or byte [] - if (clazz.getPackage() == null) { - } else if (clazz.getPackage().getName().equals("java.lang")) { - } else if (clazz.equals(byte[].class)) { - } else { - imports.add(clazz.getCanonicalName()); - } - // @IQColumn - sb.append('@').append(IQColumn.class.getSimpleName()); - - // IQColumn annotation parameters - AnnotationBuilder ap = new AnnotationBuilder(); - - // IQColumn.name - if (!col.name.equalsIgnoreCase(column)) { - ap.addParameter("name", col.name); - } - - // IQColumn.primaryKey - // composite primary keys are annotated on the table - if (col.isPrimaryKey && primaryKeys.size() == 1) { - ap.addParameter("primaryKey=true"); - } - - // IQColumn.length - if ((clazz == String.class) && (col.size > 0) && (col.size < Integer.MAX_VALUE)) { - ap.addParameter("length", col.size); - - // IQColumn.trim - if (trimStrings) { - ap.addParameter("trim=true"); - } - } else { - // IQColumn.AutoIncrement - if (col.isAutoIncrement) { - ap.addParameter("autoIncrement=true"); - } - } - - // IQColumn.nullable - if (!col.nullable) { - ap.addParameter("nullable=false"); - } - - // IQColumn.defaultValue - if (!isNullOrEmpty(col.defaultValue)) { - ap.addParameter("defaultValue=\"" + col.defaultValue + "\""); - } - - // add leading and trailing () - if (ap.length() > 0) { - ap.insert(0, '('); - ap.append(')'); - } - sb.append(ap); - } - sb.append(eol); - - // variable declaration - sb.append("\t" + "public "); - sb.append(clazz.getSimpleName()); - sb.append(' '); - sb.append(column); - sb.append(';'); - sb.append(eol).append(eol); - return sb; - } - - /** - * Validates that a table definition (annotated, interface, or both) matches - * the current state of the table and indexes in the database. Results are - * returned as a list of validation remarks which includes recommendations, - * warnings, and errors about the model. The caller may choose to have - * validate throw an exception on any validation ERROR. - * - * @param def - * the table definition - * @param throwError - * whether or not to throw an exception if an error was found - * @return a list if validation remarks - */ - List validate(TableDefinition def, boolean throwError) { - List remarks = Utils.newArrayList(); - - // model class definition validation - if (!Modifier.isPublic(def.getModelClass().getModifiers())) { - remarks.add(error(table, "SCHEMA", - format("Class {0} MUST BE PUBLIC!", def.getModelClass().getCanonicalName())).throwError( - throwError)); - } - - // Schema Validation - if (!isNullOrEmpty(schema)) { - if (isNullOrEmpty(def.schemaName)) { - remarks.add(consider(table, "SCHEMA", - format("@{0}(\"{1}\")", IQSchema.class.getSimpleName(), schema))); - } else if (!schema.equalsIgnoreCase(def.schemaName)) { - remarks.add(error( - table, - "SCHEMA", - format("@{0}(\"{1}\") != {2}", IQSchema.class.getSimpleName(), def.schemaName, schema)) - .throwError(throwError)); - } - } - - // index validation - for (IndexInspector index : indexes.values()) { - validate(remarks, def, index, throwError); - } - - // field column validation - for (FieldDefinition fieldDef : def.getFields()) { - validate(remarks, fieldDef, throwError); - } - return remarks; - } - - /** - * Validates an inspected index from the database against the - * IndexDefinition within the TableDefinition. - */ - private void validate(List remarks, TableDefinition def, IndexInspector index, - boolean throwError) { - List defIndexes = def.getIndexes(); - if (defIndexes.size() > indexes.size()) { - remarks.add(warn(table, IndexType.STANDARD.name(), "More model indexes than database indexes")); - } else if (defIndexes.size() < indexes.size()) { - remarks.add(warn(table, IndexType.STANDARD.name(), "Model class is missing indexes")); - } - // TODO complete index validation. - // need to actually compare index types and columns within each index. - - // TODO add constraints validation - List defContraintsU = def.getContraintsUnique(); - List defContraintsFK = def.getContraintsForeignKey(); - } - - /** - * Validates a column against the model's field definition. Checks for - * existence, supported type, type mapping, default value, defined lengths, - * primary key, autoincrement. - */ - private void validate(List remarks, FieldDefinition fieldDef, boolean throwError) { - // unknown field - if (!columns.containsKey(fieldDef.columnName.toLowerCase())) { - // unknown column mapping - remarks.add(error(table, fieldDef, "Does not exist in database!").throwError(throwError)); - return; - } - ColumnInspector col = columns.get(fieldDef.columnName.toLowerCase()); - Class fieldClass = fieldDef.field.getType(); - Class jdbcClass = ModelUtils.getClassForSqlType(col.type, dateTimeClass); - - // supported type check - // iciql maps to VARCHAR for unsupported types. - if (fieldDef.dataType.equals("VARCHAR") && (fieldClass != String.class)) { - remarks.add(error(table, fieldDef, - "iciql does not currently implement support for " + fieldClass.getName()).throwError( - throwError)); - } - // number types - if (!fieldClass.equals(jdbcClass)) { - if (Number.class.isAssignableFrom(fieldClass)) { - remarks.add(warn( - table, - col, - format("Precision mismatch: ModelObject={0}, ColumnObject={1}", - fieldClass.getSimpleName(), jdbcClass.getSimpleName()))); - } else { - if (!Date.class.isAssignableFrom(jdbcClass)) { - remarks.add(warn( - table, - col, - format("Object Mismatch: ModelObject={0}, ColumnObject={1}", - fieldClass.getSimpleName(), jdbcClass.getSimpleName()))); - } - } - } - - // string types - if (fieldClass == String.class) { - 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.length, col.size))); - } - 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()))); - } - } - - // numeric autoIncrement - if (fieldDef.isAutoIncrement != col.isAutoIncrement) { - remarks.add(warn( - table, - col, - format("{0}.autoIncrement={1}" + " while Column autoIncrement={2}", - IQColumn.class.getSimpleName(), fieldDef.isAutoIncrement, col.isAutoIncrement))); - } - // default value - if (!col.isAutoIncrement && !col.isPrimaryKey) { - String defaultValue = null; - if (fieldDef.defaultValue != null && fieldDef.defaultValue instanceof String) { - defaultValue = fieldDef.defaultValue.toString(); - } - // check Model.defaultValue format - if (!ModelUtils.isProperlyFormattedDefaultValue(defaultValue)) { - remarks.add(error( - table, - col, - format("{0}.defaultValue=\"{1}\"" + " is improperly formatted!", - IQColumn.class.getSimpleName(), defaultValue)).throwError(throwError)); - // next field - return; - } - // compare Model.defaultValue to Column.defaultValue - if (isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) { - // Model.defaultValue is NULL, Column.defaultValue is NOT NULL - remarks.add(warn( - table, - col, - format("{0}.defaultValue=\"\"" + " while column default=\"{1}\"", - IQColumn.class.getSimpleName(), col.defaultValue))); - } else if (!isNullOrEmpty(defaultValue) && isNullOrEmpty(col.defaultValue)) { - // Column.defaultValue is NULL, Model.defaultValue is NOT NULL - remarks.add(warn( - table, - col, - format("{0}.defaultValue=\"{1}\"" + " while column default=\"\"", - IQColumn.class.getSimpleName(), defaultValue))); - } else if (!isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) { - if (!defaultValue.equals(col.defaultValue)) { - // Model.defaultValue != Column.defaultValue - remarks.add(warn( - table, - col, - format("{0}.defaultValue=\"{1}\"" + " while column default=\"{2}\"", - IQColumn.class.getSimpleName(), defaultValue, col.defaultValue))); - } - } - - // sanity check Model.defaultValue literal value - if (!ModelUtils.isValidDefaultValue(fieldDef.field.getType(), defaultValue)) { - remarks.add(error( - table, - col, - format("{0}.defaultValue=\"{1}\" is invalid!", IQColumn.class.getSimpleName(), - defaultValue))); - } - } - } - - /** - * Represents an index as it exists in the database. - */ - private static class IndexInspector { - - String name; - IndexType type; - private List columns = new ArrayList(); - - public IndexInspector(ResultSet rs) throws SQLException { - name = rs.getString("INDEX_NAME"); - - // determine index type - boolean hash = rs.getInt("TYPE") == DatabaseMetaData.tableIndexHashed; - boolean unique = !rs.getBoolean("NON_UNIQUE"); - - if (!hash && !unique) { - type = IndexType.STANDARD; - } else if (hash && unique) { - type = IndexType.UNIQUE_HASH; - } else if (unique) { - type = IndexType.UNIQUE; - } else if (hash) { - type = IndexType.HASH; - } - columns.add(rs.getString("COLUMN_NAME")); - } - - public void addColumn(ResultSet rs) throws SQLException { - columns.add(rs.getString("COLUMN_NAME")); - } - } - - /** - * Represents a column as it exists in the database. - */ - static class ColumnInspector implements Comparable { - String name; - String type; - int size; - boolean nullable; - Class clazz; - boolean isPrimaryKey; - boolean isAutoIncrement; - String defaultValue; - - public int compareTo(ColumnInspector o) { - if (isPrimaryKey && o.isPrimaryKey) { - // both primary sort by name - return name.compareTo(o.name); - } else if (isPrimaryKey && !o.isPrimaryKey) { - // primary first - return -1; - } else if (!isPrimaryKey && o.isPrimaryKey) { - // primary first - return 1; - } else { - // neither primary, sort by name - return name.compareTo(o.name); - } - } - } - - /** - * Convenience class based on StatementBuilder for creating the annotation - * parameter list. - */ - private static class AnnotationBuilder extends StatementBuilder { - - AnnotationBuilder() { - super(); - } - - void addParameter(String parameter) { - - appendExceptFirst(", "); - append(parameter); - } - - void addParameter(String parameter, T value) { - appendExceptFirst(", "); - if (!StringUtils.isNullOrEmpty(parameter)) { - append(parameter); - append('='); - } - if (value instanceof List) { - append("{ "); - List list = (List) value; - StatementBuilder flat = new StatementBuilder(); - for (Object o : list) { - flat.appendExceptFirst(", "); - if (o instanceof String) { - flat.append('\"'); - } - // TODO escape string - flat.append(o.toString().trim()); - if (o instanceof String) { - flat.append('\"'); - } - } - append(flat); - append(" }"); - } else { - if (value instanceof String) { - append('\"'); - } - // TODO escape - append(value.toString().trim()); - if (value instanceof String) { - append('\"'); - } - } - } - - void addEnum(String parameter, Enum value) { - appendExceptFirst(", "); - if (!StringUtils.isNullOrEmpty(parameter)) { - append(parameter); - append('='); - } - append(value.getClass().getSimpleName() + "." + value.name()); - } - } -} \ No newline at end of file diff --git a/src/com/iciql/TestCondition.java b/src/com/iciql/TestCondition.java deleted file mode 100644 index 010f5a1..0000000 --- a/src/com/iciql/TestCondition.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import com.iciql.util.Utils; - -/** - * This class represents an incomplete condition. - * - * @param - * the incomplete condition data type - */ - -public class TestCondition { - - private A x; - - public TestCondition(A x) { - this.x = x; - } - - public Boolean is(A y) { - Boolean o = Utils.newObject(Boolean.class); - return Db.registerToken(o, new Function("=", x, y) { - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL("("); - query.appendSQL(stat, null, x[0]); - stat.appendSQL(" = "); - query.appendSQL(stat, x[0], x[1]); - stat.appendSQL(")"); - } - }); - } - - public Boolean exceeds(A y) { - Boolean o = Utils.newObject(Boolean.class); - return Db.registerToken(o, new Function(">", x, y) { - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL("("); - query.appendSQL(stat, null, x[0]); - stat.appendSQL(" > "); - query.appendSQL(stat, x[0], x[1]); - stat.appendSQL(")"); - } - }); - } - - public Boolean atLeast(A y) { - Boolean o = Utils.newObject(Boolean.class); - return Db.registerToken(o, new Function(">=", x, y) { - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL("("); - query.appendSQL(stat, null, x[0]); - stat.appendSQL(" >= "); - query.appendSQL(stat, x[0], x[1]); - stat.appendSQL(")"); - } - }); - } - - public Boolean lessThan(A y) { - Boolean o = Utils.newObject(Boolean.class); - return Db.registerToken(o, new Function("<", x, y) { - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL("("); - query.appendSQL(stat, null, x[0]); - stat.appendSQL(" < "); - query.appendSQL(stat, x[0], x[1]); - stat.appendSQL(")"); - } - }); - } - - public Boolean atMost(A y) { - Boolean o = Utils.newObject(Boolean.class); - return Db.registerToken(o, new Function("<=", x, y) { - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL("("); - query.appendSQL(stat, null, x[0]); - stat.appendSQL(" <= "); - query.appendSQL(stat, x[0], x[1]); - stat.appendSQL(")"); - } - }); - } - - public Boolean like(A pattern) { - Boolean o = Utils.newObject(Boolean.class); - return Db.registerToken(o, new Function("LIKE", x, pattern) { - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL("("); - query.appendSQL(stat, null, x[0]); - stat.appendSQL(" LIKE "); - query.appendSQL(stat, x[0], x[1]); - stat.appendSQL(")"); - } - }); - } - -} diff --git a/src/com/iciql/Token.java b/src/com/iciql/Token.java deleted file mode 100644 index cc2203c..0000000 --- a/src/com/iciql/Token.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * Classes implementing this interface can be used as a token in a statement. - */ -public interface Token { - /** - * Append the SQL to the given statement using the given query. - * - * @param stat - * the statement to append the SQL to - * @param query - * the query to use - */ - - void appendSQL(SQLStatement stat, Query query); - -} diff --git a/src/com/iciql/UpdateColumn.java b/src/com/iciql/UpdateColumn.java deleted file mode 100644 index 1eaf14c..0000000 --- a/src/com/iciql/UpdateColumn.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * Classes implementing this interface can be used as a declaration in an update - * statement. - */ -public interface UpdateColumn { - - /** - * Append the SQL to the given statement using the given query. - * - * @param stat - * the statement to append the SQL to - */ - - void appendSQL(SQLStatement stat); - -} diff --git a/src/com/iciql/UpdateColumnIncrement.java b/src/com/iciql/UpdateColumnIncrement.java deleted file mode 100644 index 143ce48..0000000 --- a/src/com/iciql/UpdateColumnIncrement.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * This class represents "SET column = (column + x)" in an UPDATE statement. - * - * @param - * the query type - * @param - * the new value data type - */ - -public class UpdateColumnIncrement implements UpdateColumn { - - private Query query; - private A x; - private A y; - - UpdateColumnIncrement(Query query, A x) { - this.query = query; - this.x = x; - } - - public Query by(A y) { - query.addUpdateColumnDeclaration(this); - this.y = y; - return query; - } - - public void appendSQL(SQLStatement stat) { - query.appendSQL(stat, null, x); - stat.appendSQL("=("); - query.appendSQL(stat, null, x); - stat.appendSQL("+"); - query.appendSQL(stat, x, y); - stat.appendSQL(")"); - } - -} diff --git a/src/com/iciql/UpdateColumnSet.java b/src/com/iciql/UpdateColumnSet.java deleted file mode 100644 index a961480..0000000 --- a/src/com/iciql/UpdateColumnSet.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -/** - * This class represents "SET column = value" in an UPDATE statement. - * - * @param - * the query type - * @param - * the new value data type - */ - -public class UpdateColumnSet implements UpdateColumn { - - private Query query; - private A x; - private A y; - private boolean isParameter; - - UpdateColumnSet(Query query, A x) { - this.query = query; - this.x = x; - } - - public Query to(A y) { - query.addUpdateColumnDeclaration(this); - this.y = y; - return query; - } - - public Query toParameter() { - query.addUpdateColumnDeclaration(this); - isParameter = true; - return query; - } - - public void appendSQL(SQLStatement stat) { - query.appendSQL(stat, null, x); - stat.appendSQL(" = "); - if (isParameter) { - query.appendSQL(stat, x, RuntimeParameter.PARAMETER); - } else { - query.appendSQL(stat, x, y); - } - } - -} diff --git a/src/com/iciql/ValidationRemark.java b/src/com/iciql/ValidationRemark.java deleted file mode 100644 index 33320ab..0000000 --- a/src/com/iciql/ValidationRemark.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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; - -import com.iciql.TableDefinition.FieldDefinition; -import com.iciql.TableInspector.ColumnInspector; -import com.iciql.util.StringUtils; - -/** - * A validation remark is a result of running a model validation. Each remark - * has a level, associated component (schema, table, column, index), and a - * message. - */ -public class ValidationRemark { - - /** - * The validation message level. - */ - public static enum Level { - CONSIDER, WARN, ERROR; - } - - 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; - this.table = table; - this.fieldType = type; - this.fieldName = ""; - this.message = message; - } - - private ValidationRemark(Level level, String table, FieldDefinition field, String message) { - this.level = level; - this.table = table; - this.fieldType = field.dataType; - this.fieldName = field.columnName; - this.message = message; - } - - private ValidationRemark(Level level, String table, ColumnInspector col, String message) { - this.level = level; - this.table = table; - this.fieldType = col.type; - this.fieldName = col.name; - this.message = message; - } - - public static ValidationRemark consider(String table, String type, String message) { - return new ValidationRemark(Level.CONSIDER, table, type, message); - } - - public static ValidationRemark consider(String table, ColumnInspector col, String message) { - return new ValidationRemark(Level.CONSIDER, table, col, message); - } - - public static ValidationRemark warn(String table, ColumnInspector col, String message) { - return new ValidationRemark(Level.WARN, table, col, message); - } - - public static ValidationRemark warn(String table, String type, String message) { - return new ValidationRemark(Level.WARN, table, type, message); - } - - public static ValidationRemark error(String table, ColumnInspector col, String message) { - return new ValidationRemark(Level.ERROR, table, col, message); - } - - public static ValidationRemark error(String table, String type, String message) { - return new ValidationRemark(Level.ERROR, table, type, message); - } - - public static ValidationRemark error(String table, FieldDefinition field, String message) { - return new ValidationRemark(Level.ERROR, table, field, message); - } - - public ValidationRemark throwError(boolean throwOnError) { - if (throwOnError && isError()) { - throw new IciqlException(toString()); - } - return this; - } - - public boolean isError() { - return level.equals(Level.ERROR); - } - - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(StringUtils.pad(level.name(), 9, " ", true)); - sb.append(StringUtils.pad(table, 25, " ", true)); - sb.append(StringUtils.pad(fieldName, 20, " ", true)); - sb.append(' '); - sb.append(message); - return sb.toString(); - } - - public String toCSVString() { - StringBuilder sb = new StringBuilder(); - sb.append(level.name()).append(','); - sb.append(table).append(','); - sb.append(fieldType).append(','); - sb.append(fieldName).append(','); - sb.append(message); - return sb.toString(); - } - -} diff --git a/src/com/iciql/bytecode/And.java b/src/com/iciql/bytecode/And.java deleted file mode 100644 index 808a9c1..0000000 --- a/src/com/iciql/bytecode/And.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.Token; - -/** - * An AND expression. - */ -public class And implements Token { - - private final Token left, right; - - private And(Token left, Token right) { - this.left = left; - this.right = right; - } - - static And get(Token left, Token right) { - return new And(left, right); - } - - public void appendSQL(SQLStatement stat, Query query) { - left.appendSQL(stat, query); - stat.appendSQL(" AND "); - right.appendSQL(stat, query); - } - -} diff --git a/src/com/iciql/bytecode/ArrayGet.java b/src/com/iciql/bytecode/ArrayGet.java deleted file mode 100644 index 29516c2..0000000 --- a/src/com/iciql/bytecode/ArrayGet.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.Token; - -/** - * An array access operation. - */ -public class ArrayGet implements Token { - - private final Token variable; - private final Token index; - - private ArrayGet(Token variable, Token index) { - this.variable = variable; - this.index = index; - } - - static ArrayGet get(Token variable, Token index) { - return new ArrayGet(variable, index); - } - - public void appendSQL(SQLStatement stat, Query query) { - // untested - variable.appendSQL(stat, query); - stat.appendSQL("["); - index.appendSQL(stat, query); - stat.appendSQL("]"); - } - -} diff --git a/src/com/iciql/bytecode/CaseWhen.java b/src/com/iciql/bytecode/CaseWhen.java deleted file mode 100644 index 2a1d69e..0000000 --- a/src/com/iciql/bytecode/CaseWhen.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.Token; - -/** - * A conditional expression. - */ -public class CaseWhen implements Token { - - private final Token condition, ifTrue, ifFalse; - - private CaseWhen(Token condition, Token ifTrue, Token ifFalse) { - this.condition = condition; - this.ifTrue = ifTrue; - this.ifFalse = ifFalse; - } - - static Token get(Token condition, Token ifTrue, Token ifFalse) { - if ("0".equals(ifTrue.toString()) && "1".equals(ifFalse.toString())) { - return Not.get(condition); - } else if ("1".equals(ifTrue.toString()) && "0".equals(ifFalse.toString())) { - return condition; - } else if ("0".equals(ifTrue.toString())) { - return And.get(Not.get(condition), ifFalse); - } - return new CaseWhen(condition, ifTrue, ifFalse); - } - - public String toString() { - return "CASEWHEN(" + condition + ", " + ifTrue + ", " + ifFalse + ")"; - } - - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL("CASEWHEN "); - condition.appendSQL(stat, query); - stat.appendSQL(" THEN "); - ifTrue.appendSQL(stat, query); - stat.appendSQL(" ELSE "); - ifFalse.appendSQL(stat, query); - stat.appendSQL(" END"); - } - -} diff --git a/src/com/iciql/bytecode/ClassReader.java b/src/com/iciql/bytecode/ClassReader.java deleted file mode 100644 index 38fd2f5..0000000 --- a/src/com/iciql/bytecode/ClassReader.java +++ /dev/null @@ -1,1457 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.Stack; - -import com.iciql.IciqlException; -import com.iciql.Token; - -/** - * This class converts a method to a SQL Token by interpreting (decompiling) the - * bytecode of the class. - */ -public class ClassReader { - - private static final boolean DEBUG = false; - - private byte[] data; - private int pos; - private Constant[] constantPool; - private int startByteCode; - private String methodName; - - private String convertMethodName; - private Token result; - private Stack stack = new Stack(); - private ArrayList variables = new ArrayList(); - private boolean endOfMethod; - private boolean condition; - private int nextPc; - private Map fieldMap = new HashMap(); - - private static void debug(String s) { - if (DEBUG) { - System.out.println(s); - } - } - - public Token decompile(Object instance, Map fields, String method) { - this.fieldMap = fields; - this.convertMethodName = method; - Class clazz = instance.getClass(); - String className = clazz.getName(); - debug("class name " + className); - ByteArrayOutputStream buff = new ByteArrayOutputStream(); - try { - InputStream in = clazz.getClassLoader().getResource(className.replace('.', '/') + ".class") - .openStream(); - while (true) { - int x = in.read(); - if (x < 0) { - break; - } - buff.write(x); - } - } catch (IOException e) { - throw new IciqlException("Could not read class bytecode", e); - } - data = buff.toByteArray(); - int header = readInt(); - debug("header: " + Integer.toHexString(header)); - int minorVersion = readShort(); - int majorVersion = readShort(); - debug("version: " + majorVersion + "." + minorVersion); - int constantPoolCount = readShort(); - constantPool = new Constant[constantPoolCount]; - for (int i = 1; i < constantPoolCount; i++) { - int type = readByte(); - switch (type) { - case 1: - constantPool[i] = ConstantString.get(readString()); - break; - case 3: { - int x = readInt(); - constantPool[i] = ConstantNumber.get(x); - break; - } - case 4: { - int x = readInt(); - constantPool[i] = ConstantNumber.get("" + Float.intBitsToFloat(x), x, Constant.Type.FLOAT); - break; - } - case 5: { - long x = readLong(); - constantPool[i] = ConstantNumber.get(x); - i++; - break; - } - case 6: { - long x = readLong(); - constantPool[i] = ConstantNumber - .get("" + Double.longBitsToDouble(x), x, Constant.Type.DOUBLE); - i++; - break; - } - case 7: { - int x = readShort(); - constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.CLASS_REF); - break; - } - case 8: { - int x = readShort(); - constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.STRING_REF); - break; - } - case 9: { - int x = readInt(); - constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.FIELD_REF); - break; - } - case 10: { - int x = readInt(); - constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.METHOD_REF); - break; - } - case 11: { - int x = readInt(); - constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.INTERFACE_METHOD_REF); - break; - } - case 12: { - int x = readInt(); - constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.NAME_AND_TYPE); - break; - } - default: - throw new IciqlException("Unsupported constant pool tag: " + type); - } - } - int accessFlags = readShort(); - debug("access flags: " + accessFlags); - int classRef = readShort(); - debug("class: " + constantPool[constantPool[classRef].intValue()]); - int superClassRef = readShort(); - debug(" extends " + constantPool[constantPool[superClassRef].intValue()]); - int interfaceCount = readShort(); - for (int i = 0; i < interfaceCount; i++) { - int interfaceRef = readShort(); - debug(" implements " + constantPool[constantPool[interfaceRef].intValue()]); - } - int fieldCount = readShort(); - for (int i = 0; i < fieldCount; i++) { - readField(); - } - int methodCount = readShort(); - for (int i = 0; i < methodCount; i++) { - readMethod(); - } - readAttributes(); - return result; - } - - private void readField() { - int accessFlags = readShort(); - int nameIndex = readShort(); - int descIndex = readShort(); - debug(" " + constantPool[descIndex] + " " + constantPool[nameIndex] + " " + accessFlags); - readAttributes(); - } - - private void readMethod() { - int accessFlags = readShort(); - int nameIndex = readShort(); - int descIndex = readShort(); - String desc = constantPool[descIndex].toString(); - methodName = constantPool[nameIndex].toString(); - debug(" " + desc + " " + methodName + " " + accessFlags); - readAttributes(); - } - - private void readAttributes() { - int attributeCount = readShort(); - for (int i = 0; i < attributeCount; i++) { - int attributeNameIndex = readShort(); - String attributeName = constantPool[attributeNameIndex].toString(); - debug(" attribute " + attributeName); - int attributeLength = readInt(); - int end = pos + attributeLength; - if ("Code".equals(attributeName)) { - readCode(); - } - pos = end; - } - } - - void decompile() { - int maxStack = readShort(); - int maxLocals = readShort(); - debug("stack: " + maxStack + " locals: " + maxLocals); - int codeLength = readInt(); - startByteCode = pos; - int end = pos + codeLength; - while (pos < end) { - readByteCode(); - } - debug(""); - pos = startByteCode + codeLength; - int exceptionTableLength = readShort(); - pos += 2 * exceptionTableLength; - readAttributes(); - } - - private void readCode() { - variables.clear(); - stack.clear(); - int maxStack = readShort(); - int maxLocals = readShort(); - debug("stack: " + maxStack + " locals: " + maxLocals); - int codeLength = readInt(); - startByteCode = pos; - if (methodName.startsWith(convertMethodName)) { - result = getResult(); - } - pos = startByteCode + codeLength; - int exceptionTableLength = readShort(); - pos += 2 * exceptionTableLength; - readAttributes(); - } - - private Token getResult() { - while (true) { - readByteCode(); - if (endOfMethod) { - return stack.pop(); - } - if (condition) { - Token c = stack.pop(); - Stack currentStack = new Stack(); - currentStack.addAll(stack); - ArrayList currentVariables = new ArrayList(); - currentVariables.addAll(variables); - int branch = nextPc; - Token a = getResult(); - stack = currentStack; - variables = currentVariables; - pos = branch + startByteCode; - Token b = getResult(); - if (a.equals("0") && b.equals("1")) { - return c; - } else if (a.equals("1") && b.equals("0")) { - return Not.get(c); - } else if (b.equals("0")) { - return And.get(Not.get(c), a); - } else if (a.equals("0")) { - return And.get(c, b); - } else if (b.equals("1")) { - return Or.get(c, a); - } else if (a.equals("1")) { - return And.get(Not.get(c), b); - } - return CaseWhen.get(c, b, a); - } - if (nextPc != 0) { - pos = nextPc + startByteCode; - } - } - } - - private void readByteCode() { - int startPos = pos - startByteCode; - int opCode = readByte(); - String op; - endOfMethod = false; - condition = false; - nextPc = 0; - switch (opCode) { - case 0: - op = "nop"; - break; - case 1: - op = "aconst_null"; - stack.push(Null.INSTANCE); - break; - case 2: - op = "iconst_m1"; - stack.push(ConstantNumber.get("-1")); - break; - case 3: - op = "iconst_0"; - stack.push(ConstantNumber.get("0")); - break; - case 4: - op = "iconst_1"; - stack.push(ConstantNumber.get("1")); - break; - case 5: - op = "iconst_2"; - stack.push(ConstantNumber.get("2")); - break; - case 6: - op = "iconst_3"; - stack.push(ConstantNumber.get("3")); - break; - case 7: - op = "iconst_4"; - stack.push(ConstantNumber.get("4")); - break; - case 8: - op = "iconst_5"; - stack.push(ConstantNumber.get("5")); - break; - case 9: - op = "lconst_0"; - stack.push(ConstantNumber.get("0")); - break; - case 10: - op = "lconst_1"; - stack.push(ConstantNumber.get("1")); - break; - case 11: - op = "fconst_0"; - stack.push(ConstantNumber.get("0.0")); - break; - case 12: - op = "fconst_1"; - stack.push(ConstantNumber.get("1.0")); - break; - case 13: - op = "fconst_2"; - stack.push(ConstantNumber.get("2.0")); - break; - case 14: - op = "dconst_0"; - stack.push(ConstantNumber.get("0.0")); - break; - case 15: - op = "dconst_1"; - stack.push(ConstantNumber.get("1.0")); - break; - case 16: { - int x = (byte) readByte(); - op = "bipush " + x; - stack.push(ConstantNumber.get(x)); - break; - } - case 17: { - int x = (short) readShort(); - op = "sipush " + x; - stack.push(ConstantNumber.get(x)); - break; - } - case 18: { - Token s = getConstant(readByte()); - op = "ldc " + s; - stack.push(s); - break; - } - case 19: { - Token s = getConstant(readShort()); - op = "ldc_w " + s; - stack.push(s); - break; - } - case 20: { - Token s = getConstant(readShort()); - op = "ldc2_w " + s; - stack.push(s); - break; - } - case 21: { - int x = readByte(); - op = "iload " + x; - stack.push(getVariable(x)); - break; - } - case 22: { - int x = readByte(); - op = "lload " + x; - stack.push(getVariable(x)); - break; - } - case 23: { - int x = readByte(); - op = "fload " + x; - stack.push(getVariable(x)); - break; - } - case 24: { - int x = readByte(); - op = "dload " + x; - stack.push(getVariable(x)); - break; - } - case 25: { - int x = readByte(); - op = "aload " + x; - stack.push(getVariable(x)); - break; - } - case 26: - op = "iload_0"; - stack.push(getVariable(0)); - break; - case 27: - op = "iload_1"; - stack.push(getVariable(1)); - break; - case 28: - op = "iload_2"; - stack.push(getVariable(2)); - break; - case 29: - op = "iload_3"; - stack.push(getVariable(3)); - break; - case 30: - op = "lload_0"; - stack.push(getVariable(0)); - break; - case 31: - op = "lload_1"; - stack.push(getVariable(1)); - break; - case 32: - op = "lload_2"; - stack.push(getVariable(2)); - break; - case 33: - op = "lload_3"; - stack.push(getVariable(3)); - break; - case 34: - op = "fload_0"; - stack.push(getVariable(0)); - break; - case 35: - op = "fload_1"; - stack.push(getVariable(1)); - break; - case 36: - op = "fload_2"; - stack.push(getVariable(2)); - break; - case 37: - op = "fload_3"; - stack.push(getVariable(3)); - break; - case 38: - op = "dload_0"; - stack.push(getVariable(0)); - break; - case 39: - op = "dload_1"; - stack.push(getVariable(1)); - break; - case 40: - op = "dload_2"; - stack.push(getVariable(2)); - break; - case 41: - op = "dload_3"; - stack.push(getVariable(3)); - break; - case 42: - op = "aload_0"; - stack.push(getVariable(0)); - break; - case 43: - op = "aload_1"; - stack.push(getVariable(1)); - break; - case 44: - op = "aload_2"; - stack.push(getVariable(2)); - break; - case 45: - op = "aload_3"; - stack.push(getVariable(3)); - break; - case 46: { - Token index = stack.pop(); - Token ref = stack.pop(); - op = "iaload"; - stack.push(ArrayGet.get(ref, index)); - break; - } - case 47: { - Token index = stack.pop(); - Token ref = stack.pop(); - op = "laload"; - stack.push(ArrayGet.get(ref, index)); - break; - } - case 48: { - Token index = stack.pop(); - Token ref = stack.pop(); - op = "faload"; - stack.push(ArrayGet.get(ref, index)); - break; - } - case 49: { - Token index = stack.pop(); - Token ref = stack.pop(); - op = "daload"; - stack.push(ArrayGet.get(ref, index)); - break; - } - case 50: { - Token index = stack.pop(); - Token ref = stack.pop(); - op = "aaload"; - stack.push(ArrayGet.get(ref, index)); - break; - } - case 51: { - Token index = stack.pop(); - Token ref = stack.pop(); - op = "baload"; - stack.push(ArrayGet.get(ref, index)); - break; - } - case 52: { - Token index = stack.pop(); - Token ref = stack.pop(); - op = "caload"; - stack.push(ArrayGet.get(ref, index)); - break; - } - case 53: { - Token index = stack.pop(); - Token ref = stack.pop(); - op = "saload"; - stack.push(ArrayGet.get(ref, index)); - break; - } - case 54: { - int var = readByte(); - op = "istore " + var; - setVariable(var, stack.pop()); - break; - } - case 55: { - int var = readByte(); - op = "lstore " + var; - setVariable(var, stack.pop()); - break; - } - case 56: { - int var = readByte(); - op = "fstore " + var; - setVariable(var, stack.pop()); - break; - } - case 57: { - int var = readByte(); - op = "dstore " + var; - setVariable(var, stack.pop()); - break; - } - case 58: { - int var = readByte(); - op = "astore " + var; - setVariable(var, stack.pop()); - break; - } - case 59: - op = "istore_0"; - setVariable(0, stack.pop()); - break; - case 60: - op = "istore_1"; - setVariable(1, stack.pop()); - break; - case 61: - op = "istore_2"; - setVariable(2, stack.pop()); - break; - case 62: - op = "istore_3"; - setVariable(3, stack.pop()); - break; - case 63: - op = "lstore_0"; - setVariable(0, stack.pop()); - break; - case 64: - op = "lstore_1"; - setVariable(1, stack.pop()); - break; - case 65: - op = "lstore_2"; - setVariable(2, stack.pop()); - break; - case 66: - op = "lstore_3"; - setVariable(3, stack.pop()); - break; - case 67: - op = "fstore_0"; - setVariable(0, stack.pop()); - break; - case 68: - op = "fstore_1"; - setVariable(1, stack.pop()); - break; - case 69: - op = "fstore_2"; - setVariable(2, stack.pop()); - break; - case 70: - op = "fstore_3"; - setVariable(3, stack.pop()); - break; - case 71: - op = "dstore_0"; - setVariable(0, stack.pop()); - break; - case 72: - op = "dstore_1"; - setVariable(1, stack.pop()); - break; - case 73: - op = "dstore_2"; - setVariable(2, stack.pop()); - break; - case 74: - op = "dstore_3"; - setVariable(3, stack.pop()); - break; - case 75: - op = "astore_0"; - setVariable(0, stack.pop()); - break; - case 76: - op = "astore_1"; - setVariable(1, stack.pop()); - break; - case 77: - op = "astore_2"; - setVariable(2, stack.pop()); - break; - case 78: - op = "astore_3"; - setVariable(3, stack.pop()); - break; - case 79: { - // String value = stack.pop(); - // String index = stack.pop(); - // String ref = stack.pop(); - op = "iastore"; - // TODO side effect - not supported - break; - } - case 80: - op = "lastore"; - // TODO side effect - not supported - break; - case 81: - op = "fastore"; - // TODO side effect - not supported - break; - case 82: - op = "dastore"; - // TODO side effect - not supported - break; - case 83: - op = "aastore"; - // TODO side effect - not supported - break; - case 84: - op = "bastore"; - // TODO side effect - not supported - break; - case 85: - op = "castore"; - // TODO side effect - not supported - break; - case 86: - op = "sastore"; - // TODO side effect - not supported - break; - case 87: - op = "pop"; - stack.pop(); - break; - case 88: - op = "pop2"; - // TODO currently we don't know the stack types - stack.pop(); - stack.pop(); - break; - case 89: { - op = "dup"; - Token x = stack.pop(); - stack.push(x); - stack.push(x); - break; - } - case 90: { - op = "dup_x1"; - Token a = stack.pop(); - Token b = stack.pop(); - stack.push(a); - stack.push(b); - stack.push(a); - break; - } - case 91: { - // TODO currently we don't know the stack types - op = "dup_x2"; - Token a = stack.pop(); - Token b = stack.pop(); - Token c = stack.pop(); - stack.push(a); - stack.push(c); - stack.push(b); - stack.push(a); - break; - } - case 92: { - // TODO currently we don't know the stack types - op = "dup2"; - Token a = stack.pop(); - Token b = stack.pop(); - stack.push(b); - stack.push(a); - stack.push(b); - stack.push(a); - break; - } - case 93: { - // TODO currently we don't know the stack types - op = "dup2_x1"; - Token a = stack.pop(); - Token b = stack.pop(); - Token c = stack.pop(); - stack.push(b); - stack.push(a); - stack.push(c); - stack.push(b); - stack.push(a); - break; - } - case 94: { - // TODO currently we don't know the stack types - op = "dup2_x2"; - Token a = stack.pop(); - Token b = stack.pop(); - Token c = stack.pop(); - Token d = stack.pop(); - stack.push(b); - stack.push(a); - stack.push(d); - stack.push(c); - stack.push(b); - stack.push(a); - break; - } - case 95: { - op = "swap"; - Token a = stack.pop(); - Token b = stack.pop(); - stack.push(a); - stack.push(b); - break; - } - case 96: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "iadd"; - stack.push(Operation.get(a, Operation.Type.ADD, b)); - break; - } - case 97: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "ladd"; - stack.push(Operation.get(a, Operation.Type.ADD, b)); - break; - } - case 98: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "fadd"; - stack.push(Operation.get(a, Operation.Type.ADD, b)); - break; - } - case 99: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "dadd"; - stack.push(Operation.get(a, Operation.Type.ADD, b)); - break; - } - case 100: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "isub"; - stack.push(Operation.get(a, Operation.Type.SUBTRACT, b)); - break; - } - case 101: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "lsub"; - stack.push(Operation.get(a, Operation.Type.SUBTRACT, b)); - break; - } - case 102: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "fsub"; - stack.push(Operation.get(a, Operation.Type.SUBTRACT, b)); - break; - } - case 103: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "dsub"; - stack.push(Operation.get(a, Operation.Type.SUBTRACT, b)); - break; - } - case 104: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "imul"; - stack.push(Operation.get(a, Operation.Type.MULTIPLY, b)); - break; - } - case 105: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "lmul"; - stack.push(Operation.get(a, Operation.Type.MULTIPLY, b)); - break; - } - case 106: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "fmul"; - stack.push(Operation.get(a, Operation.Type.MULTIPLY, b)); - break; - } - case 107: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "dmul"; - stack.push(Operation.get(a, Operation.Type.MULTIPLY, b)); - break; - } - case 108: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "idiv"; - stack.push(Operation.get(a, Operation.Type.DIVIDE, b)); - break; - } - case 109: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "ldiv"; - stack.push(Operation.get(a, Operation.Type.DIVIDE, b)); - break; - } - case 110: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "fdiv"; - stack.push(Operation.get(a, Operation.Type.DIVIDE, b)); - break; - } - case 111: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "ddiv"; - stack.push(Operation.get(a, Operation.Type.DIVIDE, b)); - break; - } - case 112: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "irem"; - stack.push(Operation.get(a, Operation.Type.MOD, b)); - break; - } - case 113: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "lrem"; - stack.push(Operation.get(a, Operation.Type.MOD, b)); - break; - } - case 114: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "frem"; - stack.push(Operation.get(a, Operation.Type.MOD, b)); - break; - } - case 115: { - Token b = stack.pop(); - Token a = stack.pop(); - op = "drem"; - stack.push(Operation.get(a, Operation.Type.MOD, b)); - break; - } - // case 116: - // op = "ineg"; - // break; - // case 117: - // op = "lneg"; - // break; - // case 118: - // op = "fneg"; - // break; - // case 119: - // op = "dneg"; - // break; - // case 120: - // op = "ishl"; - // break; - // case 121: - // op = "lshl"; - // break; - // case 122: - // op = "ishr"; - // break; - // case 123: - // op = "lshr"; - // break; - // case 124: - // op = "iushr"; - // break; - // case 125: - // op = "lushr"; - // break; - // case 126: - // op = "iand"; - // break; - // case 127: - // op = "land"; - // break; - // case 128: - // op = "ior"; - // break; - // case 129: - // op = "lor"; - // break; - // case 130: - // op = "ixor"; - // break; - // case 131: - // op = "lxor"; - // break; - // case 132: { - // int var = readByte(); - // int off = (byte) readByte(); - // op = "iinc " + var + " " + off; - // break; - // } - // case 133: - // op = "i2l"; - // break; - // case 134: - // op = "i2f"; - // break; - // case 135: - // op = "i2d"; - // break; - // case 136: - // op = "l2i"; - // break; - // case 137: - // op = "l2f"; - // break; - // case 138: - // op = "l2d"; - // break; - // case 139: - // op = "f2i"; - // break; - // case 140: - // op = "f2l"; - // break; - // case 141: - // op = "f2d"; - // break; - // case 142: - // op = "d2i"; - // break; - // case 143: - // op = "d2l"; - // break; - // case 144: - // op = "d2f"; - // break; - // case 145: - // op = "i2b"; - // break; - // case 146: - // op = "i2c"; - // break; - // case 147: - // op = "i2s"; - // break; - case 148: { - Token b = stack.pop(), a = stack.pop(); - stack.push(new Function("SIGN", Operation.get(a, Operation.Type.SUBTRACT, b))); - op = "lcmp"; - break; - } - // case 149: - // op = "fcmpl"; - // break; - // case 150: - // op = "fcmpg"; - // break; - // case 151: - // op = "dcmpl"; - // break; - // case 152: - // op = "dcmpg"; - // break; - case 153: - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - stack.push(Operation.get(stack.pop(), Operation.Type.EQUALS, ConstantNumber.get(0))); - op = "ifeq " + nextPc; - break; - case 154: - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - stack.push(Operation.get(stack.pop(), Operation.Type.NOT_EQUALS, ConstantNumber.get(0))); - op = "ifne " + nextPc; - break; - case 155: - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER, ConstantNumber.get(0))); - op = "iflt " + nextPc; - break; - case 156: - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER_EQUALS, ConstantNumber.get(0))); - op = "ifge " + nextPc; - break; - case 157: - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER, ConstantNumber.get(0))); - op = "ifgt " + nextPc; - break; - case 158: - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER_EQUALS, ConstantNumber.get(0))); - op = "ifle " + nextPc; - break; - case 159: { - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - Token b = stack.pop(), a = stack.pop(); - stack.push(Operation.get(a, Operation.Type.EQUALS, b)); - op = "if_icmpeq " + nextPc; - break; - } - case 160: { - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - Token b = stack.pop(), a = stack.pop(); - stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b)); - op = "if_icmpne " + nextPc; - break; - } - case 161: { - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - Token b = stack.pop(), a = stack.pop(); - stack.push(Operation.get(a, Operation.Type.SMALLER, b)); - op = "if_icmplt " + nextPc; - break; - } - case 162: { - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - Token b = stack.pop(), a = stack.pop(); - stack.push(Operation.get(a, Operation.Type.BIGGER_EQUALS, b)); - op = "if_icmpge " + nextPc; - break; - } - case 163: { - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - Token b = stack.pop(), a = stack.pop(); - stack.push(Operation.get(a, Operation.Type.BIGGER, b)); - op = "if_icmpgt " + nextPc; - break; - } - case 164: { - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - Token b = stack.pop(), a = stack.pop(); - stack.push(Operation.get(a, Operation.Type.SMALLER_EQUALS, b)); - op = "if_icmple " + nextPc; - break; - } - case 165: { - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - Token b = stack.pop(), a = stack.pop(); - stack.push(Operation.get(a, Operation.Type.EQUALS, b)); - op = "if_acmpeq " + nextPc; - break; - } - case 166: { - condition = true; - nextPc = getAbsolutePos(pos, readShort()); - Token b = stack.pop(), a = stack.pop(); - stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b)); - op = "if_acmpne " + nextPc; - break; - } - case 167: - nextPc = getAbsolutePos(pos, readShort()); - op = "goto " + nextPc; - break; - // case 168: - // // TODO not supported yet - // op = "jsr " + getAbsolutePos(pos, readShort()); - // break; - // case 169: - // // TODO not supported yet - // op = "ret " + readByte(); - // break; - // case 170: { - // int start = pos; - // pos += 4 - ((pos - startByteCode) & 3); - // int def = readInt(); - // int low = readInt(), high = readInt(); - // int n = high - low + 1; - // op = "tableswitch default:" + getAbsolutePos(start, def); - // StringBuilder buff = new StringBuilder(); - // for (int i = 0; i < n; i++) { - // buff.append(' ').append(low++). - // append(":"). - // append(getAbsolutePos(start, readInt())); - // } - // op += buff.toString(); - // // pos += n * 4; - // break; - // } - // case 171: { - // int start = pos; - // pos += 4 - ((pos - startByteCode) & 3); - // int def = readInt(); - // int n = readInt(); - // op = "lookupswitch default:" + getAbsolutePos(start, def); - // StringBuilder buff = new StringBuilder(); - // for (int i = 0; i < n; i++) { - // buff.append(' '). - // append(readInt()). - // append(":"). - // append(getAbsolutePos(start, readInt())); - // } - // op += buff.toString(); - // // pos += n * 8; - // break; - // } - case 172: - op = "ireturn"; - endOfMethod = true; - break; - case 173: - op = "lreturn"; - endOfMethod = true; - break; - case 174: - op = "freturn"; - endOfMethod = true; - break; - case 175: - op = "dreturn"; - endOfMethod = true; - break; - case 176: - op = "areturn"; - endOfMethod = true; - break; - case 177: - op = "return"; - // no value returned - stack.push(null); - endOfMethod = true; - break; - // case 178: - // op = "getstatic " + getField(readShort()); - // break; - // case 179: - // op = "putstatic " + getField(readShort()); - // break; - case 180: { - String field = getField(readShort()); - Token p = stack.pop(); - String s = p + "." + field.substring(field.lastIndexOf('.') + 1, field.indexOf(' ')); - if (s.startsWith("this.")) { - s = s.substring(5); - } - stack.push(Variable.get(s, fieldMap.get(s))); - op = "getfield " + field; - break; - } - // case 181: - // op = "putfield " + getField(readShort()); - // break; - case 182: { - String method = getMethod(readShort()); - op = "invokevirtual " + method; - if (method.equals("java/lang/String.equals (Ljava/lang/Object;)Z")) { - Token a = stack.pop(); - Token b = stack.pop(); - stack.push(Operation.get(a, Operation.Type.EQUALS, b)); - } else if (method.equals("java/lang/Integer.intValue ()I")) { - // ignore - } else if (method.equals("java/lang/Long.longValue ()J")) { - // ignore - } - break; - } - case 183: { - String method = getMethod(readShort()); - op = "invokespecial " + method; - break; - } - case 184: - op = "invokestatic " + getMethod(readShort()); - break; - // case 185: { - // int methodRef = readShort(); - // readByte(); - // readByte(); - // op = "invokeinterface " + getMethod(methodRef); - // break; - // } - case 187: { - String className = constantPool[constantPool[readShort()].intValue()].toString(); - op = "new " + className; - break; - } - // case 188: - // op = "newarray " + readByte(); - // break; - // case 189: - // op = "anewarray " + cpString[readShort()]; - // break; - // case 190: - // op = "arraylength"; - // break; - // case 191: - // op = "athrow"; - // break; - // case 192: - // op = "checkcast " + cpString[readShort()]; - // break; - // case 193: - // op = "instanceof " + cpString[readShort()]; - // break; - // case 194: - // op = "monitorenter"; - // break; - // case 195: - // op = "monitorexit"; - // break; - // case 196: { - // opCode = readByte(); - // switch (opCode) { - // case 21: - // op = "wide iload " + readShort(); - // break; - // case 22: - // op = "wide lload " + readShort(); - // break; - // case 23: - // op = "wide fload " + readShort(); - // break; - // case 24: - // op = "wide dload " + readShort(); - // break; - // case 25: - // op = "wide aload " + readShort(); - // break; - // case 54: - // op = "wide istore " + readShort(); - // break; - // case 55: - // op = "wide lstore " + readShort(); - // break; - // case 56: - // op = "wide fstore " + readShort(); - // break; - // case 57: - // op = "wide dstore " + readShort(); - // break; - // case 58: - // op = "wide astore " + readShort(); - // break; - // case 132: { - // int var = readShort(); - // int off = (short) readShort(); - // op = "wide iinc " + var + " " + off; - // break; - // } - // case 169: - // op = "wide ret " + readShort(); - // break; - // default: - // throw new IciqlException( - // "Unsupported wide opCode " + opCode); - // } - // break; - // } - // case 197: - // op = "multianewarray " + cpString[readShort()] + " " + readByte(); - // break; - // case 198: { - // condition = true; - // nextPc = getAbsolutePos(pos, readShort()); - // Token a = stack.pop(); - // stack.push("(" + a + " IS NULL)"); - // op = "ifnull " + nextPc; - // break; - // } - // case 199: { - // condition = true; - // nextPc = getAbsolutePos(pos, readShort()); - // Token a = stack.pop(); - // stack.push("(" + a + " IS NOT NULL)"); - // op = "ifnonnull " + nextPc; - // break; - // } - case 200: - op = "goto_w " + getAbsolutePos(pos, readInt()); - break; - case 201: - op = "jsr_w " + getAbsolutePos(pos, readInt()); - break; - default: - throw new IciqlException("Unsupported opCode " + opCode); - } - debug(" " + startPos + ": " + op); - } - - private void setVariable(int x, Token value) { - while (x >= variables.size()) { - variables.add(Variable.get("p" + variables.size(), null)); - } - variables.set(x, value); - } - - private Token getVariable(int x) { - if (x == 0) { - return Variable.THIS; - } - while (x >= variables.size()) { - variables.add(Variable.get("p" + variables.size(), null)); - } - return variables.get(x); - } - - private String getField(int fieldRef) { - int field = constantPool[fieldRef].intValue(); - int classIndex = field >>> 16; - int nameAndType = constantPool[field & 0xffff].intValue(); - String className = constantPool[constantPool[classIndex].intValue()] + "." - + constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff]; - return className; - } - - private String getMethod(int methodRef) { - int method = constantPool[methodRef].intValue(); - int classIndex = method >>> 16; - int nameAndType = constantPool[method & 0xffff].intValue(); - String className = constantPool[constantPool[classIndex].intValue()] + "." - + constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff]; - return className; - } - - private Constant getConstant(int constantRef) { - Constant c = constantPool[constantRef]; - switch (c.getType()) { - case INT: - case FLOAT: - case DOUBLE: - case LONG: - return c; - case STRING_REF: - return constantPool[c.intValue()]; - default: - throw new IciqlException("Not a constant: " + constantRef); - } - } - - private String readString() { - int size = readShort(); - byte[] buff = data; - int p = pos, end = p + size; - char[] chars = new char[size]; - int j = 0; - for (; p < end; j++) { - int x = buff[p++] & 0xff; - if (x < 0x80) { - chars[j] = (char) x; - } else if (x >= 0xe0) { - chars[j] = (char) (((x & 0xf) << 12) + ((buff[p++] & 0x3f) << 6) + (buff[p++] & 0x3f)); - } else { - chars[j] = (char) (((x & 0x1f) << 6) + (buff[p++] & 0x3f)); - } - } - pos = p; - return new String(chars, 0, j); - } - - private int getAbsolutePos(int start, int offset) { - return start - startByteCode - 1 + (short) offset; - } - - private int readByte() { - return data[pos++] & 0xff; - } - - private int readShort() { - byte[] buff = data; - return ((buff[pos++] & 0xff) << 8) + (buff[pos++] & 0xff); - } - - private int readInt() { - byte[] buff = data; - return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8) - + (buff[pos++] & 0xff); - } - - private long readLong() { - return ((long) (readInt()) << 32) + (readInt() & 0xffffffffL); - } - -} diff --git a/src/com/iciql/bytecode/Constant.java b/src/com/iciql/bytecode/Constant.java deleted file mode 100644 index 65cd66b..0000000 --- a/src/com/iciql/bytecode/Constant.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Token; - -/** - * An expression in the constant pool. - */ -public interface Constant extends Token { - - /** - * The constant pool type. - */ - enum Type { - STRING, INT, FLOAT, DOUBLE, LONG, CLASS_REF, STRING_REF, FIELD_REF, METHOD_REF, INTERFACE_METHOD_REF, NAME_AND_TYPE - } - - Constant.Type getType(); - - int intValue(); - -} diff --git a/src/com/iciql/bytecode/ConstantNumber.java b/src/com/iciql/bytecode/ConstantNumber.java deleted file mode 100644 index 934de3d..0000000 --- a/src/com/iciql/bytecode/ConstantNumber.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; - -/** - * A literal number. - */ -public class ConstantNumber implements Constant { - - private final String value; - private final Type type; - private final long longValue; - - private ConstantNumber(String value, long longValue, Type type) { - this.value = value; - this.longValue = longValue; - this.type = type; - } - - static ConstantNumber get(String v) { - return new ConstantNumber(v, 0, Type.STRING); - } - - static ConstantNumber get(int v) { - return new ConstantNumber("" + v, v, Type.INT); - } - - static ConstantNumber get(long v) { - return new ConstantNumber("" + v, v, Type.LONG); - } - - static ConstantNumber get(String s, long x, Type type) { - return new ConstantNumber(s, x, type); - } - - public int intValue() { - return (int) longValue; - } - - public String toString() { - return value; - } - - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL(toString()); - } - - public Constant.Type getType() { - return type; - } - -} diff --git a/src/com/iciql/bytecode/ConstantString.java b/src/com/iciql/bytecode/ConstantString.java deleted file mode 100644 index 985f97d..0000000 --- a/src/com/iciql/bytecode/ConstantString.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.util.StringUtils; - -/** - * A string constant. - */ -public class ConstantString implements Constant { - - private final String value; - - private ConstantString(String value) { - this.value = value; - } - - static ConstantString get(String v) { - return new ConstantString(v); - } - - public String toString() { - return value; - } - - public int intValue() { - return 0; - } - - public void appendSQL(SQLStatement stat, Query query) { - stat.appendSQL(StringUtils.quoteStringSQL(value)); - } - - public Constant.Type getType() { - return Constant.Type.STRING; - } - -} diff --git a/src/com/iciql/bytecode/Function.java b/src/com/iciql/bytecode/Function.java deleted file mode 100644 index 56a55ea..0000000 --- a/src/com/iciql/bytecode/Function.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.Token; - -/** - * A method call. - */ -class Function implements Token { - - private final String name; - private final Token expr; - - Function(String name, Token expr) { - this.name = name; - this.expr = expr; - } - - public String toString() { - return name + "(" + expr + ")"; - } - - public void appendSQL(SQLStatement stat, Query query) { - // untested - stat.appendSQL(name + "("); - expr.appendSQL(stat, query); - stat.appendSQL(")"); - } -} diff --git a/src/com/iciql/bytecode/Not.java b/src/com/iciql/bytecode/Not.java deleted file mode 100644 index ab5ab84..0000000 --- a/src/com/iciql/bytecode/Not.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.Token; - -/** - * A NOT condition. - */ -public class Not implements Token { - - private Token expr; - - private Not(Token expr) { - this.expr = expr; - } - - static Token get(Token expr) { - if (expr instanceof Not) { - return ((Not) expr).expr; - } else if (expr instanceof Operation) { - return ((Operation) expr).reverse(); - } - return new Not(expr); - } - - Token not() { - return expr; - } - - public void appendSQL(SQLStatement stat, Query query) { - // untested - stat.appendSQL("NOT("); - expr.appendSQL(stat, query); - stat.appendSQL(")"); - } - -} diff --git a/src/com/iciql/bytecode/Null.java b/src/com/iciql/bytecode/Null.java deleted file mode 100644 index a28de56..0000000 --- a/src/com/iciql/bytecode/Null.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.Token; - -/** - * The Java 'null'. - */ -public class Null implements Token { - - static final Null INSTANCE = new Null(); - - private Null() { - // don't allow to create new instances - } - - public String toString() { - return "null"; - } - - public void appendSQL(SQLStatement stat, Query query) { - // untested - stat.appendSQL("NULL"); - } - -} diff --git a/src/com/iciql/bytecode/Operation.java b/src/com/iciql/bytecode/Operation.java deleted file mode 100644 index 7cd42d9..0000000 --- a/src/com/iciql/bytecode/Operation.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.Token; - -/** - * A mathematical or comparison operation. - */ -class Operation implements Token { - - /** - * The operation type. - */ - enum Type { - EQUALS("=") { - Type reverse() { - return NOT_EQUALS; - } - }, - NOT_EQUALS("<>") { - Type reverse() { - return EQUALS; - } - }, - BIGGER(">") { - Type reverse() { - return SMALLER_EQUALS; - } - }, - BIGGER_EQUALS(">=") { - Type reverse() { - return SMALLER; - } - }, - SMALLER_EQUALS("<=") { - Type reverse() { - return BIGGER; - } - }, - SMALLER("<") { - Type reverse() { - return BIGGER_EQUALS; - } - }, - ADD("+"), SUBTRACT("-"), MULTIPLY("*"), DIVIDE("/"), MOD("%"); - - private String name; - - Type(String name) { - this.name = name; - } - - public String toString() { - return name; - } - - Type reverse() { - return null; - } - - } - - private final Token left, right; - private final Type op; - - private Operation(Token left, Type op, Token right) { - this.left = left; - this.op = op; - this.right = right; - } - - static Token get(Token left, Type op, Token right) { - if (op == Type.NOT_EQUALS && "0".equals(right.toString())) { - return left; - } - return new Operation(left, op, right); - } - - public String toString() { - return left + " " + op + " " + right; - } - - public Token reverse() { - return get(left, op.reverse(), right); - } - - public void appendSQL(SQLStatement stat, Query query) { - left.appendSQL(stat, query); - stat.appendSQL(op.toString()); - right.appendSQL(stat, query); - } - -} diff --git a/src/com/iciql/bytecode/Or.java b/src/com/iciql/bytecode/Or.java deleted file mode 100644 index 37da2a6..0000000 --- a/src/com/iciql/bytecode/Or.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.Token; - -/** - * An OR expression. - */ -public class Or implements Token { - - private final Token left, right; - - private Or(Token left, Token right) { - this.left = left; - this.right = right; - } - - static Or get(Token left, Token right) { - return new Or(left, right); - } - - public void appendSQL(SQLStatement stat, Query query) { - // untested - left.appendSQL(stat, query); - stat.appendSQL(" OR "); - right.appendSQL(stat, query); - } - -} diff --git a/src/com/iciql/bytecode/Variable.java b/src/com/iciql/bytecode/Variable.java deleted file mode 100644 index f3dbc01..0000000 --- a/src/com/iciql/bytecode/Variable.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.bytecode; - -import com.iciql.Query; -import com.iciql.SQLStatement; -import com.iciql.Token; - -/** - * A variable. - */ -public class Variable implements Token { - - static final Variable THIS = new Variable("this", null); - - private final String name; - private final Object obj; - - private Variable(String name, Object obj) { - this.name = name; - this.obj = obj; - } - - static Variable get(String name, Object obj) { - return new Variable(name, obj); - } - - public String toString() { - return name; - } - - public void appendSQL(SQLStatement stat, Query query) { - query.appendSQL(stat, null, obj); - } - -} diff --git a/src/com/iciql/bytecode/package.html b/src/com/iciql/bytecode/package.html deleted file mode 100644 index 5107481..0000000 --- a/src/com/iciql/bytecode/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - -Javadoc package documentation - - -The class decompiler for natural syntax iciql clauses. - - \ No newline at end of file diff --git a/src/com/iciql/package.html b/src/com/iciql/package.html deleted file mode 100644 index 769837b..0000000 --- a/src/com/iciql/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - -Javadoc package documentation - - -iciql (pronounced "icicle") is a Java JDBC SQL statement generator and simple object mapper - - \ No newline at end of file diff --git a/src/com/iciql/util/GenerateModels.java b/src/com/iciql/util/GenerateModels.java deleted file mode 100644 index eac9f6c..0000000 --- a/src/com/iciql/util/GenerateModels.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.util; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.io.Writer; -import java.sql.SQLException; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.iciql.Db; -import com.iciql.DbInspector; - -/** - * Generates iciql models. - */ -public class GenerateModels { - - /** - * The output stream where this tool writes to. - */ - protected PrintStream out = System.out; - - public static void main(String... args) { - GenerateModels tool = new GenerateModels(); - try { - tool.runTool(args); - } catch (SQLException e) { - tool.out.print("Error: "); - tool.out.println(e.getMessage()); - tool.out.println(); - tool.showUsage(); - } - } - - public void runTool(String... args) throws SQLException { - String url = null; - String user = "sa"; - String password = ""; - String schema = null; - String table = null; - String packageName = ""; - String folder = null; - boolean annotateSchema = true; - boolean trimStrings = false; - for (int i = 0; args != null && i < args.length; i++) { - String arg = args[i]; - if (arg.equals("-url")) { - url = args[++i]; - } else if (arg.equals("-user")) { - user = args[++i]; - } else if (arg.equals("-password")) { - password = args[++i]; - } else if (arg.equals("-schema")) { - schema = args[++i]; - } else if (arg.equals("-table")) { - table = args[++i]; - } else if (arg.equals("-package")) { - packageName = args[++i]; - } else if (arg.equals("-folder")) { - folder = args[++i]; - } else if (arg.equals("-annotateSchema")) { - try { - annotateSchema = Boolean.parseBoolean(args[++i]); - } catch (Throwable t) { - throw new SQLException("Can not parse -annotateSchema value"); - } - } else if (arg.equals("-trimStrings")) { - try { - trimStrings = Boolean.parseBoolean(args[++i]); - } catch (Throwable t) { - throw new SQLException("Can not parse -trimStrings value"); - } - } else { - throwUnsupportedOption(arg); - } - } - if (url == null) { - throw new SQLException("URL not set"); - } - execute(url, user, password, schema, table, packageName, folder, annotateSchema, trimStrings); - } - - /** - * Generates models from the database. - * - * @param url - * the database URL - * @param user - * the user name - * @param password - * the password - * @param schema - * the schema to read from. null for all schemas. - * @param table - * the table to model. null for all tables within schema. - * @param packageName - * the package name of the model classes. - * @param folder - * destination folder for model classes (package path not - * included) - * @param annotateSchema - * includes the schema in the table model annotations - * @param trimStrings - * automatically trim strings that exceed maxLength - */ - public static void execute(String url, String user, String password, String schema, String table, - String packageName, String folder, boolean annotateSchema, boolean trimStrings) - throws SQLException { - try { - Db db; - if (password == null) { - db = Db.open(url, user, (String) null); - } else { - db = Db.open(url, user, password); - } - DbInspector inspector = new DbInspector(db); - List models = inspector.generateModel(schema, table, packageName, annotateSchema, - trimStrings); - File parentFile; - if (StringUtils.isNullOrEmpty(folder)) { - parentFile = new File(System.getProperty("user.dir")); - } else { - parentFile = new File(folder); - } - parentFile.mkdirs(); - Pattern p = Pattern.compile("class ([a-zA-Z0-9]+)"); - for (String model : models) { - Matcher m = p.matcher(model); - if (m.find()) { - String className = m.group().substring("class".length()).trim(); - File classFile = new File(parentFile, className + ".java"); - Writer o = new FileWriter(classFile, false); - PrintWriter writer = new PrintWriter(new BufferedWriter(o)); - writer.write(model); - writer.close(); - System.out.println("Generated " + classFile.getAbsolutePath()); - } - } - } catch (IOException io) { - throw new SQLException("could not generate model", io); - } - } - - /** - * Throw a SQLException saying this command line option is not supported. - * - * @param option - * the unsupported option - * @return this method never returns normally - */ - protected SQLException throwUnsupportedOption(String option) throws SQLException { - showUsage(); - throw new SQLException("Unsupported option: " + option); - } - - protected void showUsage() { - out.println("GenerateModels"); - out.println("Usage:"); - out.println(); - out.println("(*) -url jdbc:h2:~test"); - out.println(" -user "); - out.println(" -password "); - out.println(" -schema "); - out.println(" -table "); - out.println(" -package "); - out.println(" -folder "); - out.println(" -annotateSchema "); - out.println(" -trimStrings "); - } - -} diff --git a/src/com/iciql/util/IciqlLogger.java b/src/com/iciql/util/IciqlLogger.java deleted file mode 100644 index d8005bb..0000000 --- a/src/com/iciql/util/IciqlLogger.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2011 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.util; - -import java.text.DecimalFormat; -import java.text.MessageFormat; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicLong; - -import com.iciql.IciqlException; - -/** - * Utility class to optionally log generated statements to IciqlListeners.
- * Statement logging is disabled by default. - *

- * This class also tracks the counts for generated statements by major type. - * - */ -public class IciqlLogger { - - /** - * Enumeration of the different statement types that are logged. - */ - public enum StatementType { - STAT, TOTAL, CREATE, INSERT, UPDATE, MERGE, DELETE, SELECT, DROP, WARN; - } - - /** - * Interface that defines an iciql listener. - */ - public interface IciqlListener { - void logIciql(StatementType type, String statement); - } - - private static final ExecutorService EXEC = Executors.newSingleThreadExecutor(); - private static final Set LISTENERS = Utils.newHashSet(); - private static final IciqlListener CONSOLE = new IciqlListener() { - - @Override - public void logIciql(StatementType type, String message) { - System.out.println(message); - } - }; - - private static final AtomicLong SELECT_COUNT = new AtomicLong(); - private static final AtomicLong CREATE_COUNT = new AtomicLong(); - private static final AtomicLong INSERT_COUNT = new AtomicLong(); - private static final AtomicLong UPDATE_COUNT = new AtomicLong(); - private static final AtomicLong MERGE_COUNT = new AtomicLong(); - private static final AtomicLong DELETE_COUNT = new AtomicLong(); - private static final AtomicLong DROP_COUNT = new AtomicLong(); - private static final AtomicLong WARN_COUNT = new AtomicLong(); - - /** - * Activates the Console Logger. - */ - public static void activateConsoleLogger() { - registerListener(CONSOLE); - } - - /** - * Deactivates the Console Logger. - */ - public static void deactivateConsoleLogger() { - unregisterListener(CONSOLE); - } - - /** - * Registers a listener with the relay. - * - * @param listener - */ - public static void registerListener(IciqlListener listener) { - LISTENERS.add(listener); - } - - /** - * Unregisters a listener with the relay. - * - * @param listener - */ - public static void unregisterListener(IciqlListener listener) { - if (!LISTENERS.remove(listener)) { - throw new IciqlException("Failed to remove iciql listener {0}", listener); - } - } - - public static void create(String statement) { - CREATE_COUNT.incrementAndGet(); - logStatement(StatementType.CREATE, statement); - } - - public static void insert(String statement) { - INSERT_COUNT.incrementAndGet(); - logStatement(StatementType.INSERT, statement); - } - - public static void update(String statement) { - UPDATE_COUNT.incrementAndGet(); - logStatement(StatementType.UPDATE, statement); - } - - public static void merge(String statement) { - MERGE_COUNT.incrementAndGet(); - logStatement(StatementType.MERGE, statement); - } - - public static void delete(String statement) { - DELETE_COUNT.incrementAndGet(); - logStatement(StatementType.DELETE, statement); - } - - public static void select(String statement) { - SELECT_COUNT.incrementAndGet(); - logStatement(StatementType.SELECT, statement); - } - - public static void drop(String statement) { - DROP_COUNT.incrementAndGet(); - logStatement(StatementType.DROP, statement); - } - - public static void warn(String message, Object... args) { - WARN_COUNT.incrementAndGet(); - logStatement(StatementType.WARN, args.length > 0 ? MessageFormat.format(message, args) : message); - } - - private static void logStatement(final StatementType type, final String statement) { - for (final IciqlListener listener : LISTENERS) { - EXEC.execute(new Runnable() { - public void run() { - listener.logIciql(type, statement); - } - }); - } - } - - public static long getCreateCount() { - return CREATE_COUNT.longValue(); - } - - public static long getInsertCount() { - return INSERT_COUNT.longValue(); - } - - public static long getUpdateCount() { - return UPDATE_COUNT.longValue(); - } - - public static long getMergeCount() { - return MERGE_COUNT.longValue(); - } - - public static long getDeleteCount() { - return DELETE_COUNT.longValue(); - } - - public static long getSelectCount() { - return SELECT_COUNT.longValue(); - } - - public static long getDropCount() { - return DROP_COUNT.longValue(); - } - - public static long getWarnCount() { - return WARN_COUNT.longValue(); - } - - public static long getTotalCount() { - return getCreateCount() + getInsertCount() + getUpdateCount() + getDeleteCount() + getMergeCount() - + getSelectCount() + getDropCount(); - } - - public static void logStats() { - logStatement(StatementType.STAT, "iciql Runtime Statistics"); - logStatement(StatementType.STAT, "========================"); - logStat(StatementType.WARN, getWarnCount()); - logStatement(StatementType.STAT, "========================"); - logStat(StatementType.CREATE, getCreateCount()); - logStat(StatementType.INSERT, getInsertCount()); - logStat(StatementType.UPDATE, getUpdateCount()); - logStat(StatementType.MERGE, getMergeCount()); - logStat(StatementType.DELETE, getDeleteCount()); - logStat(StatementType.SELECT, getSelectCount()); - logStat(StatementType.DROP, getDropCount()); - logStatement(StatementType.STAT, "========================"); - logStat(StatementType.TOTAL, getTotalCount()); - } - - private static void logStat(StatementType type, long value) { - if (value > 0) { - DecimalFormat df = new DecimalFormat("###,###,###,###"); - logStatement(StatementType.STAT, - StringUtils.pad(type.name(), 6, " ", true) + " = " + df.format(value)); - } - } -} \ No newline at end of file diff --git a/src/com/iciql/util/JdbcUtils.java b/src/com/iciql/util/JdbcUtils.java deleted file mode 100644 index 4a4a2b6..0000000 --- a/src/com/iciql/util/JdbcUtils.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.util; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.Properties; - -import javax.naming.Context; -import javax.sql.DataSource; -import javax.sql.XAConnection; - -/** - * This is a utility class with JDBC helper functions. - */ -public class JdbcUtils { - - private static final String[] DRIVERS = { "h2:", "org.h2.Driver", "Cache:", - "com.intersys.jdbc.CacheDriver", "daffodilDB://", "in.co.daffodil.db.rmi.RmiDaffodilDBDriver", - "daffodil", "in.co.daffodil.db.jdbc.DaffodilDBDriver", "db2:", "COM.ibm.db2.jdbc.net.DB2Driver", - "derby:net:", "org.apache.derby.jdbc.ClientDriver", "derby://", - "org.apache.derby.jdbc.ClientDriver", "derby:", "org.apache.derby.jdbc.EmbeddedDriver", - "FrontBase:", "com.frontbase.jdbc.FBJDriver", "firebirdsql:", "org.firebirdsql.jdbc.FBDriver", - "hsqldb:", "org.hsqldb.jdbcDriver", "informix-sqli:", "com.informix.jdbc.IfxDriver", "jtds:", - "net.sourceforge.jtds.jdbc.Driver", "microsoft:", "com.microsoft.jdbc.sqlserver.SQLServerDriver", - "mimer:", "com.mimer.jdbc.Driver", "mysql:", "com.mysql.jdbc.Driver", "odbc:", - "sun.jdbc.odbc.JdbcOdbcDriver", "oracle:", "oracle.jdbc.driver.OracleDriver", "pervasive:", - "com.pervasive.jdbc.v2.Driver", "pointbase:micro:", "com.pointbase.me.jdbc.jdbcDriver", - "pointbase:", "com.pointbase.jdbc.jdbcUniversalDriver", "postgresql:", "org.postgresql.Driver", - "sybase:", "com.sybase.jdbc3.jdbc.SybDriver", "sqlserver:", - "com.microsoft.sqlserver.jdbc.SQLServerDriver", "teradata:", "com.ncr.teradata.TeraDriver", }; - - private JdbcUtils() { - // utility class - } - - /** - * Close a statement without throwing an exception. - * - * @param stat - * the statement or null - */ - public static void closeSilently(Statement stat) { - if (stat != null) { - try { - stat.close(); - } catch (SQLException e) { - // ignore - } - } - } - - /** - * Close a connection without throwing an exception. - * - * @param conn - * the connection or null - */ - public static void closeSilently(Connection conn) { - if (conn != null) { - try { - conn.close(); - } catch (SQLException e) { - // ignore - } - } - } - - /** - * Close a result set without throwing an exception. - * - * @param rs - * the result set or null - */ - public static void closeSilently(ResultSet rs) { - closeSilently(rs, false); - } - - /** - * Close a result set, and optionally its statement without throwing an - * exception. - * - * @param rs - * the result set or null - */ - public static void closeSilently(ResultSet rs, boolean closeStatement) { - if (rs != null) { - Statement stat = null; - if (closeStatement) { - try { - stat = rs.getStatement(); - } catch (SQLException e) { - // ignore - } - } - try { - rs.close(); - } catch (SQLException e) { - // ignore - } - closeSilently(stat); - } - } - - /** - * Close an XA connection set without throwing an exception. - * - * @param conn - * the XA connection or null - */ - public static void closeSilently(XAConnection conn) { - if (conn != null) { - try { - conn.close(); - } catch (SQLException e) { - // ignore - } - } - } - - /** - * Open a new database connection with the given settings. - * - * @param driver - * the driver class name - * @param url - * the database URL - * @param user - * the user name - * @param password - * the password - * @return the database connection - */ - public static Connection getConnection(String driver, String url, String user, String password) - throws SQLException { - Properties prop = new Properties(); - if (user != null) { - prop.setProperty("user", user); - } - if (password != null) { - prop.setProperty("password", password); - } - return getConnection(driver, url, prop); - } - - /** - * Escape table or schema patterns used for DatabaseMetaData functions. - * - * @param pattern - * the pattern - * @return the escaped pattern - */ - public static String escapeMetaDataPattern(String pattern) { - if (pattern == null || pattern.length() == 0) { - return pattern; - } - return StringUtils.replaceAll(pattern, "\\", "\\\\"); - } - - /** - * Open a new database connection with the given settings. - * - * @param driver - * the driver class name - * @param url - * the database URL - * @param prop - * the properties containing at least the user name and password - * @return the database connection - */ - public static Connection getConnection(String driver, String url, Properties prop) throws SQLException { - if (StringUtils.isNullOrEmpty(driver)) { - JdbcUtils.load(url); - } else { - Class d = Utils.loadClass(driver); - if (java.sql.Driver.class.isAssignableFrom(d)) { - return DriverManager.getConnection(url, prop); - } else if (javax.naming.Context.class.isAssignableFrom(d)) { - // JNDI context - try { - Context context = (Context) d.newInstance(); - DataSource ds = (DataSource) context.lookup(url); - String user = prop.getProperty("user"); - String password = prop.getProperty("password"); - if (StringUtils.isNullOrEmpty(user) && StringUtils.isNullOrEmpty(password)) { - return ds.getConnection(); - } - return ds.getConnection(user, password); - } catch (SQLException e) { - throw e; - } catch (Exception e) { - throw new SQLException("Failed to get connection for " + url, e); - } - } else { - // Don't know, but maybe it loaded a JDBC Driver - return DriverManager.getConnection(url, prop); - } - } - return DriverManager.getConnection(url, prop); - } - - /** - * Get the driver class name for the given URL, or null if the URL is - * unknown. - * - * @param url - * the database URL - * @return the driver class name - */ - public static String getDriver(String url) { - if (url.startsWith("jdbc:")) { - url = url.substring("jdbc:".length()); - for (int i = 0; i < DRIVERS.length; i += 2) { - String prefix = DRIVERS[i]; - if (url.startsWith(prefix)) { - return DRIVERS[i + 1]; - } - } - } - return null; - } - - /** - * Load the driver class for the given URL, if the database URL is known. - * - * @param url - * the database URL - */ - public static void load(String url) { - String driver = getDriver(url); - if (driver != null) { - Utils.loadClass(driver); - } - } - -} diff --git a/src/com/iciql/util/Slf4jIciqlListener.java b/src/com/iciql/util/Slf4jIciqlListener.java deleted file mode 100644 index ded393f..0000000 --- a/src/com/iciql/util/Slf4jIciqlListener.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2011 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.util; - -import java.util.HashMap; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.iciql.Iciql; -import com.iciql.util.IciqlLogger.IciqlListener; -import com.iciql.util.IciqlLogger.StatementType; - -/** - * Slf4jIciqlListener interfaces the IciqlLogger to the SLF4J logging framework. - */ -public class Slf4jIciqlListener implements IciqlListener { - - private Logger logger = LoggerFactory.getLogger(Iciql.class); - - /** - * Enumeration representing the SLF4J log levels. - */ - public enum Level { - ERROR, WARN, INFO, DEBUG, TRACE, OFF; - } - - private final Level defaultLevel; - - private final Map levels; - - public Slf4jIciqlListener() { - this(Level.TRACE); - } - - public Slf4jIciqlListener(Level defaultLevel) { - this.defaultLevel = defaultLevel; - levels = new HashMap(); - for (StatementType type : StatementType.values()) { - levels.put(type, defaultLevel); - } - } - - /** - * Sets the logging level for a particular statement type. - * - * @param type - * @param level - */ - public void setLevel(StatementType type, Level level) { - levels.put(type, defaultLevel); - } - - @Override - public void logIciql(StatementType type, String statement) { - Level level = levels.get(type); - switch (level) { - case ERROR: - logger.error(statement); - break; - case WARN: - logger.warn(statement); - break; - case INFO: - logger.info(statement); - break; - case DEBUG: - logger.debug(statement); - break; - case TRACE: - logger.trace(statement); - break; - case OFF: - break; - } - } -} diff --git a/src/com/iciql/util/StatementBuilder.java b/src/com/iciql/util/StatementBuilder.java deleted file mode 100644 index 47e8054..0000000 --- a/src/com/iciql/util/StatementBuilder.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.util; - -/** - * A utility class to build a statement. In addition to the methods supported by - * StringBuilder, it allows to add a text only in the second iteration. This - * simplified constructs such as: - * - *

- * StringBuilder buff = new StringBuilder();
- * for (int i = 0; i < args.length; i++) {
- * 	if (i > 0) {
- * 		buff.append(", ");
- * 	}
- * 	buff.append(args[i]);
- * }
- * 
- * - * to - * - *
- * StatementBuilder buff = new StatementBuilder();
- * for (String s : args) {
- * 	buff.appendExceptFirst(", ");
- * 	buff.append(a);
- * }
- * 
- */ -public class StatementBuilder { - - private final StringBuilder builder = new StringBuilder(); - private int index; - - /** - * Create a new builder. - */ - public StatementBuilder() { - // nothing to do - } - - /** - * Create a new builder. - * - * @param string - * the initial string - */ - public StatementBuilder(String string) { - builder.append(string); - } - - /** - * Append a text. - * - * @param s - * the text to append - * @return itself - */ - public StatementBuilder append(String s) { - builder.append(s); - return this; - } - - /** - * Append a character. - * - * @param c - * the character to append - * @return itself - */ - public StatementBuilder append(char c) { - builder.append(c); - return this; - } - - /** - * Append a number. - * - * @param x - * the number to append - * @return itself - */ - public StatementBuilder append(long x) { - builder.append(x); - return this; - } - - /** - * Returns the current value of the loop counter. - * - * @return the loop counter - */ - public int getCount() { - return index; - } - - /** - * Reset the loop counter. - * - * @return itself - */ - public StatementBuilder resetCount() { - index = 0; - return this; - } - - /** - * Append a text, but only if appendExceptFirst was never called. - * - * @param s - * the text to append - */ - public void appendOnlyFirst(String s) { - if (index == 0) { - builder.append(s); - } - } - - /** - * Append a text, except when this method is called the first time. - * - * @param s - * the text to append - */ - public void appendExceptFirst(String s) { - if (index++ > 0) { - builder.append(s); - } - } - - public void append(StatementBuilder sb) { - builder.append(sb); - } - - public void insert(int offset, char c) { - builder.insert(offset, c); - } - - public String toString() { - return builder.toString(); - } - - /** - * Get the length. - * - * @return the length - */ - public int length() { - return builder.length(); - } -} diff --git a/src/com/iciql/util/StringUtils.java b/src/com/iciql/util/StringUtils.java deleted file mode 100644 index dd3f180..0000000 --- a/src/com/iciql/util/StringUtils.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.util; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; - -/** - * Common string utilities. - * - */ -public class StringUtils { - - /** - * Replace all occurrences of the before string with the after string. - * - * @param s - * the string - * @param before - * the old text - * @param after - * the new text - * @return the string with the before string replaced - */ - public static String replaceAll(String s, String before, String after) { - int next = s.indexOf(before); - if (next < 0) { - return s; - } - StringBuilder buff = new StringBuilder(s.length() - before.length() + after.length()); - int index = 0; - while (true) { - buff.append(s.substring(index, next)).append(after); - index = next + before.length(); - next = s.indexOf(before, index); - if (next < 0) { - buff.append(s.substring(index)); - break; - } - } - return buff.toString(); - } - - /** - * Check if a String is null or empty (the length is null). - * - * @param s - * the string to check - * @return true if it is null or empty - */ - public static boolean isNullOrEmpty(String s) { - return s == null || s.length() == 0; - } - - /** - * Convert a string to a Java literal using the correct escape sequences. - * The literal is not enclosed in double quotes. The result can be used in - * properties files or in Java source code. - * - * @param s - * the text to convert - * @return the Java representation - */ - public static String javaEncode(String s) { - int length = s.length(); - StringBuilder buff = new StringBuilder(length); - for (int i = 0; i < length; i++) { - char c = s.charAt(i); - switch (c) { - // case '\b': - // // BS backspace - // // not supported in properties files - // buff.append("\\b"); - // break; - case '\t': - // HT horizontal tab - buff.append("\\t"); - break; - case '\n': - // LF linefeed - buff.append("\\n"); - break; - case '\f': - // FF form feed - buff.append("\\f"); - break; - case '\r': - // CR carriage return - buff.append("\\r"); - break; - case '"': - // double quote - buff.append("\\\""); - break; - case '\\': - // backslash - buff.append("\\\\"); - break; - default: - int ch = c & 0xffff; - if (ch >= ' ' && (ch < 0x80)) { - buff.append(c); - // not supported in properties files - // } else if(ch < 0xff) { - // buff.append("\\"); - // // make sure it's three characters (0x200 is octal 1000) - // buff.append(Integer.toOctalString(0x200 | - // ch).substring(1)); - } else { - buff.append("\\u"); - // make sure it's four characters - buff.append(Integer.toHexString(0x10000 | ch).substring(1)); - } - } - } - return buff.toString(); - } - - /** - * Pad a string. This method is used for the SQL function RPAD and LPAD. - * - * @param string - * the original string - * @param n - * the target length - * @param padding - * the padding string - * @param right - * true if the padding should be appended at the end - * @return the padded string - */ - public static String pad(String string, int n, String padding, boolean right) { - if (n < 0) { - n = 0; - } - if (n < string.length()) { - return string.substring(0, n); - } else if (n == string.length()) { - return string; - } - char paddingChar; - if (padding == null || padding.length() == 0) { - paddingChar = ' '; - } else { - paddingChar = padding.charAt(0); - } - StringBuilder buff = new StringBuilder(n); - n -= string.length(); - if (right) { - buff.append(string); - } - for (int i = 0; i < n; i++) { - buff.append(paddingChar); - } - if (!right) { - buff.append(string); - } - return buff.toString(); - } - - /** - * Convert a string to a SQL literal. Null is converted to NULL. The text is - * enclosed in single quotes. If there are any special characters, the - * method STRINGDECODE is used. - * - * @param s - * the text to convert. - * @return the SQL literal - */ - public static String quoteStringSQL(String s) { - if (s == null) { - return "NULL"; - } - int length = s.length(); - StringBuilder buff = new StringBuilder(length + 2); - buff.append('\''); - for (int i = 0; i < length; i++) { - char c = s.charAt(i); - if (c == '\'') { - buff.append(c); - } else if (c < ' ' || c > 127) { - // need to start from the beginning because maybe there was a \ - // that was not quoted - return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")"; - } - buff.append(c); - } - buff.append('\''); - return buff.toString(); - } - - /** - * Split a string into an array of strings using the given separator. A null - * string will result in a null array, and an empty string in a zero element - * array. - * - * @param s - * the string to split - * @param separatorChar - * the separator character - * @param trim - * whether each element should be trimmed - * @return the array list - */ - public static String[] arraySplit(String s, char separatorChar, boolean trim) { - if (s == null) { - return null; - } - int length = s.length(); - if (length == 0) { - return new String[0]; - } - ArrayList list = Utils.newArrayList(); - StringBuilder buff = new StringBuilder(length); - for (int i = 0; i < length; i++) { - char c = s.charAt(i); - if (c == separatorChar) { - String e = buff.toString(); - list.add(trim ? e.trim() : e); - buff.setLength(0); - } else if (c == '\\' && i < length - 1) { - buff.append(s.charAt(++i)); - } else { - buff.append(c); - } - } - String e = buff.toString(); - list.add(trim ? e.trim() : e); - String[] array = new String[list.size()]; - list.toArray(array); - return array; - } - - /** - * Calculates the SHA1 of the string. - * - * @param text - * @return sha1 of the string - */ - public static String calculateSHA1(String text) { - try { - byte[] bytes = text.getBytes("iso-8859-1"); - return calculateSHA1(bytes); - } catch (UnsupportedEncodingException u) { - throw new RuntimeException(u); - } - } - - /** - * Calculates the SHA1 of the byte array. - * - * @param bytes - * @return sha1 of the byte array - */ - public static String calculateSHA1(byte[] bytes) { - try { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - md.update(bytes, 0, bytes.length); - byte[] digest = md.digest(); - StringBuilder sb = new StringBuilder(digest.length * 2); - for (int i = 0; i < digest.length; i++) { - if (((int) digest[i] & 0xff) < 0x10) { - sb.append('0'); - } - sb.append(Integer.toHexString((int) digest[i] & 0xff)); - } - return sb.toString(); - } catch (NoSuchAlgorithmException t) { - throw new RuntimeException(t); - } - } - - /** - * Counts the occurrences of char c in the given string. - * - * @param c - * the character to count - * @param value - * the source string - * @return the count of c in value - */ - public static int count(char c, String value) { - int count = 0; - for (char cv : value.toCharArray()) { - if (cv == c) { - count++; - } - } - return count; - } - - /** - * Prepare text for html presentation. Replace sensitive characters with - * html entities. - * - * @param inStr - * @param changeSpace - * @return plain text escaped for html - */ - public static String escapeForHtml(String inStr, boolean changeSpace) { - StringBuffer retStr = new StringBuffer(); - int i = 0; - while (i < inStr.length()) { - if (inStr.charAt(i) == '&') { - retStr.append("&"); - } else if (inStr.charAt(i) == '<') { - retStr.append("<"); - } else if (inStr.charAt(i) == '>') { - retStr.append(">"); - } else if (inStr.charAt(i) == '\"') { - retStr.append("""); - } else if (changeSpace && inStr.charAt(i) == ' ') { - retStr.append(" "); - } else if (changeSpace && inStr.charAt(i) == '\t') { - retStr.append("    "); - } else { - retStr.append(inStr.charAt(i)); - } - i++; - } - return retStr.toString(); - } - - /** - * Replaces carriage returns and line feeds with html line breaks. - * - * @param string - * @return plain text with html line breaks - */ - public static String breakLinesForHtml(String string) { - return string.replace("\r\n", "
").replace("\r", "
").replace("\n", "
"); - } - - /** - * Returns the string content of the specified file. - * - * @param file - * @param lineEnding - * @return the string content of the file - */ - public static String readContent(File file, String lineEnding) { - StringBuilder sb = new StringBuilder(); - try { - InputStreamReader is = new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")); - BufferedReader reader = new BufferedReader(is); - String line = null; - while ((line = reader.readLine()) != null) { - sb.append(line); - if (lineEnding != null) { - sb.append(lineEnding); - } - } - reader.close(); - } catch (Throwable t) { - System.err.println("Failed to read content of " + file.getAbsolutePath()); - t.printStackTrace(); - } - return sb.toString(); - } -} diff --git a/src/com/iciql/util/Utils.java b/src/com/iciql/util/Utils.java deleted file mode 100644 index 77110b8..0000000 --- a/src/com/iciql/util/Utils.java +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.io.StringWriter; -import java.lang.reflect.Constructor; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.sql.Blob; -import java.sql.Clob; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -import com.iciql.Iciql.EnumId; -import com.iciql.Iciql.EnumType; -import com.iciql.IciqlException; - -/** - * Generic utility methods. - */ -public class Utils { - - public static final AtomicLong COUNTER = new AtomicLong(0); - - public static final AtomicInteger AS_COUNTER = new AtomicInteger(0); - - private static final boolean MAKE_ACCESSIBLE = true; - - private static final int BUFFER_BLOCK_SIZE = 4 * 1024; - - public static synchronized int nextAsCount() { - // prevent negative values and use a threadsafe counter - int count = AS_COUNTER.incrementAndGet(); - if (count == Integer.MAX_VALUE) { - count = 0; - AS_COUNTER.set(count); - } - return count; - } - - @SuppressWarnings("unchecked") - public static Class getClass(X x) { - return (Class) x.getClass(); - } - - public static Class loadClass(String className) { - try { - return Class.forName(className); - } catch (Exception e) { - throw new IciqlException(e); - } - } - - public static ArrayList newArrayList() { - return new ArrayList(); - } - - public static ArrayList newArrayList(Collection c) { - return new ArrayList(c); - } - - public static HashSet newHashSet() { - return new HashSet(); - } - - public static HashSet newHashSet(Collection list) { - return new HashSet(list); - } - - public static HashMap newHashMap() { - return new HashMap(); - } - - public static Map newSynchronizedHashMap() { - HashMap map = newHashMap(); - return Collections.synchronizedMap(map); - } - - public static IdentityHashMap newIdentityHashMap() { - return new IdentityHashMap(); - } - - public static ThreadLocal newThreadLocal(final Class clazz) { - return new ThreadLocal() { - @SuppressWarnings("rawtypes") - @Override - protected T initialValue() { - try { - return clazz.newInstance(); - } catch (Exception e) { - if (MAKE_ACCESSIBLE) { - Constructor[] constructors = clazz.getDeclaredConstructors(); - // try 0 length constructors - for (Constructor c : constructors) { - if (c.getParameterTypes().length == 0) { - c.setAccessible(true); - try { - return clazz.newInstance(); - } catch (Exception e2) { - // ignore - } - } - } - } - throw new IciqlException(e, - "Missing default constructor? Exception trying to instantiate {0}: {1}", - clazz.getName(), e.getMessage()); - } - } - }; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static T newObject(Class clazz) { - // must create new instances - if (clazz == int.class || clazz == Integer.class) { - return (T) new Integer((int) COUNTER.getAndIncrement()); - } else if (clazz == String.class) { - return (T) ("" + COUNTER.getAndIncrement()); - } else if (clazz == long.class || clazz == Long.class) { - return (T) new Long(COUNTER.getAndIncrement()); - } else if (clazz == short.class || clazz == Short.class) { - return (T) new Short((short) COUNTER.getAndIncrement()); - } else if (clazz == byte.class || clazz == Byte.class) { - return (T) new Byte((byte) COUNTER.getAndIncrement()); - } else if (clazz == float.class || clazz == Float.class) { - return (T) new Float(COUNTER.getAndIncrement()); - } else if (clazz == double.class || clazz == Double.class) { - return (T) new Double(COUNTER.getAndIncrement()); - } else if (clazz == boolean.class || clazz == Boolean.class) { - COUNTER.getAndIncrement(); - return (T) new Boolean(false); - } else if (clazz == BigDecimal.class) { - return (T) new BigDecimal(COUNTER.getAndIncrement()); - } else if (clazz == BigInteger.class) { - return (T) new BigInteger("" + COUNTER.getAndIncrement()); - } else if (clazz == java.sql.Date.class) { - return (T) new java.sql.Date(COUNTER.getAndIncrement()); - } else if (clazz == java.sql.Time.class) { - return (T) new java.sql.Time(COUNTER.getAndIncrement()); - } else if (clazz == java.sql.Timestamp.class) { - return (T) new java.sql.Timestamp(COUNTER.getAndIncrement()); - } else if (clazz == java.util.Date.class) { - return (T) new java.util.Date(COUNTER.getAndIncrement()); - } else if (clazz == byte[].class) { - COUNTER.getAndIncrement(); - return (T) new byte[0]; - } else if (clazz.isEnum()) { - COUNTER.getAndIncrement(); - // enums can not be instantiated reflectively - // return first constant as reference - return clazz.getEnumConstants()[0]; - } else if (clazz == java.util.UUID.class) { - COUNTER.getAndIncrement(); - return (T) UUID.randomUUID(); - } - try { - return clazz.newInstance(); - } catch (Exception e) { - if (MAKE_ACCESSIBLE) { - Constructor[] constructors = clazz.getDeclaredConstructors(); - // try 0 length constructors - for (Constructor c : constructors) { - if (c.getParameterTypes().length == 0) { - c.setAccessible(true); - try { - return clazz.newInstance(); - } catch (Exception e2) { - // ignore - } - } - } - // try 1 length constructors - for (Constructor c : constructors) { - if (c.getParameterTypes().length == 1) { - c.setAccessible(true); - try { - return (T) c.newInstance(new Object[1]); - } catch (Exception e2) { - // ignore - } - } - } - } - throw new IciqlException(e, - "Missing default constructor?! Exception trying to instantiate {0}: {1}", - clazz.getName(), e.getMessage()); - } - } - - public static boolean isSimpleType(Class clazz) { - if (Number.class.isAssignableFrom(clazz)) { - return true; - } else if (clazz == String.class) { - return true; - } - return false; - } - - public static Object convert(Object o, Class targetType) { - if (o == null) { - return null; - } - Class currentType = o.getClass(); - if (targetType.isAssignableFrom(currentType)) { - return o; - } - - // convert from CLOB/TEXT/VARCHAR to String - if (targetType == String.class) { - if (Clob.class.isAssignableFrom(currentType)) { - Clob c = (Clob) o; - try { - Reader r = c.getCharacterStream(); - return readStringAndClose(r, -1); - } catch (Exception e) { - throw new IciqlException(e, "error converting CLOB to String: ", e.toString()); - } - } - return o.toString(); - } - - if (Boolean.class.isAssignableFrom(targetType) || boolean.class.isAssignableFrom(targetType)) { - // convert from number to boolean - if (Number.class.isAssignableFrom(currentType)) { - Number n = (Number) o; - return n.intValue() > 0; - } - // convert from string to boolean - if (String.class.isAssignableFrom(currentType)) { - String s = o.toString().toLowerCase(); - float f = 0f; - try { - f = Float.parseFloat(s); - } catch (Exception e) { - } - return f > 0 || s.equals("true") || s.equals("yes") || s.equals("y") || s.equals("on"); - } - } - - // convert from boolean to number - if (Boolean.class.isAssignableFrom(currentType)) { - Boolean b = (Boolean) o; - if (Number.class.isAssignableFrom(targetType)) { - return b ? 1 : 0; - } - if (boolean.class.isAssignableFrom(targetType)) { - return b.booleanValue(); - } - } - - // convert from number to number - if (Number.class.isAssignableFrom(currentType)) { - Number n = (Number) o; - if (targetType == byte.class || targetType == Byte.class) { - return n.byteValue(); - } else if (targetType == short.class || targetType == Short.class) { - return n.shortValue(); - } else if (targetType == int.class || targetType == Integer.class) { - return n.intValue(); - } else if (targetType == long.class || targetType == Long.class) { - return n.longValue(); - } else if (targetType == double.class || targetType == Double.class) { - return n.doubleValue(); - } else if (targetType == float.class || targetType == Float.class) { - return n.floatValue(); - } - } - - // convert from BLOB - if (targetType == byte[].class) { - if (Blob.class.isAssignableFrom(currentType)) { - Blob b = (Blob) o; - try { - InputStream is = b.getBinaryStream(); - return readBlobAndClose(is, -1); - } catch (Exception e) { - throw new IciqlException(e, "error converting BLOB to byte[]: ", e.toString()); - } - } - } - throw new IciqlException("Can not convert the value {0} from {1} to {2}", o, currentType, targetType); - } - - public static Object convertEnum(Enum o, EnumType type) { - if (o == null) { - return null; - } - switch (type) { - case ORDINAL: - return o.ordinal(); - case ENUMID: - if (!EnumId.class.isAssignableFrom(o.getClass())) { - throw new IciqlException("Can not convert the enum {0} using ENUMID", o); - } - EnumId enumid = (EnumId) o; - return enumid.enumId(); - case NAME: - default: - return o.name(); - } - } - - public static Object convertEnum(Object o, Class targetType, EnumType type) { - if (o == null) { - return null; - } - Class currentType = o.getClass(); - if (targetType.isAssignableFrom(currentType)) { - return o; - } - // convert from VARCHAR/TEXT/INT to Enum - Enum[] values = (Enum[]) targetType.getEnumConstants(); - if (Clob.class.isAssignableFrom(currentType)) { - // TEXT/CLOB field - Clob c = (Clob) o; - String name = null; - try { - Reader r = c.getCharacterStream(); - name = readStringAndClose(r, -1); - } catch (Exception e) { - throw new IciqlException(e, "error converting CLOB to String: ", e.toString()); - } - - // find name match - for (Enum value : values) { - if (value.name().equalsIgnoreCase(name)) { - return value; - } - } - } else if (String.class.isAssignableFrom(currentType)) { - // VARCHAR field - String name = (String) o; - for (Enum value : values) { - if (value.name().equalsIgnoreCase(name)) { - return value; - } - } - } else if (Number.class.isAssignableFrom(currentType)) { - // INT field - int n = ((Number) o).intValue(); - if (type.equals(EnumType.ORDINAL)) { - // ORDINAL mapping - for (Enum value : values) { - if (value.ordinal() == n) { - return value; - } - } - } else if (type.equals(EnumType.ENUMID)) { - if (!EnumId.class.isAssignableFrom(targetType)) { - throw new IciqlException("Can not convert the value {0} from {1} to {2} using ENUMID", o, - currentType, targetType); - } - // ENUMID mapping - for (Enum value : values) { - EnumId enumid = (EnumId) value; - if (enumid.enumId() == n) { - return value; - } - } - } - } - throw new IciqlException("Can not convert the value {0} from {1} to {2}", o, currentType, targetType); - } - - /** - * Read a number of characters from a reader and close it. - * - * @param in - * the reader - * @param length - * the maximum number of characters to read, or -1 to read until - * the end of file - * @return the string read - */ - public static String readStringAndClose(Reader in, int length) throws IOException { - try { - if (length <= 0) { - length = Integer.MAX_VALUE; - } - int block = Math.min(BUFFER_BLOCK_SIZE, length); - StringWriter out = new StringWriter(length == Integer.MAX_VALUE ? block : length); - char[] buff = new char[block]; - while (length > 0) { - int len = Math.min(block, length); - len = in.read(buff, 0, len); - if (len < 0) { - break; - } - out.write(buff, 0, len); - length -= len; - } - return out.toString(); - } finally { - in.close(); - } - } - - /** - * Read a number of bytes from a stream and close it. - * - * @param in - * the stream - * @param length - * the maximum number of bytes to read, or -1 to read until the - * end of file - * @return the string read - */ - public static byte[] readBlobAndClose(InputStream in, int length) throws IOException { - try { - if (length <= 0) { - length = Integer.MAX_VALUE; - } - int block = Math.min(BUFFER_BLOCK_SIZE, length); - ByteArrayOutputStream out = new ByteArrayOutputStream(length == Integer.MAX_VALUE ? block - : length); - byte[] buff = new byte[block]; - while (length > 0) { - int len = Math.min(block, length); - len = in.read(buff, 0, len); - if (len < 0) { - break; - } - out.write(buff, 0, len); - length -= len; - } - return out.toByteArray(); - } finally { - in.close(); - } - } -} diff --git a/src/com/iciql/util/WeakIdentityHashMap.java b/src/com/iciql/util/WeakIdentityHashMap.java deleted file mode 100644 index bc03cd0..0000000 --- a/src/com/iciql/util/WeakIdentityHashMap.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2004-2011 H2 Group. - * Copyright 2011 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.util; - -import java.lang.ref.WeakReference; -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -import com.iciql.IciqlException; - -/** - * This hash map uses weak references, so that elements that are no longer - * referenced elsewhere can be garbage collected. It also uses object identity - * to compare keys. The garbage collection happens when trying to add new data, - * or when resizing. - * - * @param - * the keys - * @param - * the value - */ - -public class WeakIdentityHashMap implements Map { - - private static final int MAX_LOAD = 90; - private static final WeakReference DELETED_KEY = new WeakReference(null); - private int mask, len, size, deletedCount, level; - private int maxSize, minSize, maxDeleted; - private WeakReference[] keys; - private V[] values; - - public WeakIdentityHashMap() { - reset(2); - } - - public int size() { - return size; - } - - private void checkSizePut() { - if (deletedCount > size) { - rehash(level); - } - if (size + deletedCount >= maxSize) { - rehash(level + 1); - } - } - - private void checkSizeRemove() { - if (size < minSize && level > 0) { - rehash(level - 1); - } else if (deletedCount > maxDeleted) { - rehash(level); - } - } - - private int getIndex(Object key) { - return System.identityHashCode(key) & mask; - } - - @SuppressWarnings("unchecked") - private void reset(int newLevel) { - minSize = size * 3 / 4; - size = 0; - level = newLevel; - len = 2 << level; - mask = len - 1; - maxSize = (int) (len * MAX_LOAD / 100L); - deletedCount = 0; - maxDeleted = 20 + len / 2; - keys = new WeakReference[len]; - values = (V[]) new Object[len]; - } - - public V put(K key, V value) { - checkSizePut(); - int index = getIndex(key); - int plus = 1; - int deleted = -1; - do { - WeakReference k = keys[index]; - if (k == null) { - // found an empty record - if (deleted >= 0) { - index = deleted; - deletedCount--; - } - size++; - keys[index] = new WeakReference(key); - values[index] = value; - return null; - } else if (k == DELETED_KEY) { - if (deleted < 0) { - // found the first deleted record - deleted = index; - } - } else { - Object r = k.get(); - if (r == null) { - delete(index); - } else if (r == key) { - // update existing - V old = values[index]; - values[index] = value; - return old; - } - } - index = (index + plus++) & mask; - } while (plus <= len); - throw new IciqlException("Hashmap is full"); - } - - public V remove(Object key) { - checkSizeRemove(); - int index = getIndex(key); - int plus = 1; - do { - WeakReference k = keys[index]; - if (k == null) { - // found an empty record - return null; - } else if (k == DELETED_KEY) { - // continue - } else { - Object r = k.get(); - if (r == null) { - delete(index); - } else if (r == key) { - // found the record - V old = values[index]; - delete(index); - return old; - } - } - index = (index + plus++) & mask; - k = keys[index]; - } while (plus <= len); - // not found - return null; - } - - @SuppressWarnings("unchecked") - private void delete(int index) { - keys[index] = (WeakReference) DELETED_KEY; - values[index] = null; - deletedCount++; - size--; - } - - private void rehash(int newLevel) { - WeakReference[] oldKeys = keys; - V[] oldValues = values; - reset(newLevel); - for (int i = 0; i < oldKeys.length; i++) { - WeakReference k = oldKeys[i]; - if (k != null && k != DELETED_KEY) { - K key = k.get(); - if (key != null) { - put(key, oldValues[i]); - } - } - } - } - - public V get(Object key) { - int index = getIndex(key); - int plus = 1; - do { - WeakReference k = keys[index]; - if (k == null) { - return null; - } else if (k == DELETED_KEY) { - // continue - } else { - Object r = k.get(); - if (r == null) { - delete(index); - } else if (r == key) { - return values[index]; - } - } - index = (index + plus++) & mask; - } while (plus <= len); - return null; - } - - public void clear() { - reset(2); - } - - public boolean containsKey(Object key) { - return get(key) != null; - } - - public boolean containsValue(Object value) { - if (value == null) { - return false; - } - for (V item : values) { - if (value.equals(item)) { - return true; - } - } - return false; - } - - public Set> entrySet() { - throw new UnsupportedOperationException(); - } - - public boolean isEmpty() { - return size == 0; - } - - public Set keySet() { - throw new UnsupportedOperationException(); - } - - public void putAll(Map m) { - throw new UnsupportedOperationException(); - } - - public Collection values() { - throw new UnsupportedOperationException(); - } - -} diff --git a/src/com/iciql/util/package.html b/src/com/iciql/util/package.html deleted file mode 100644 index 3d24dee..0000000 --- a/src/com/iciql/util/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - -Javadoc package documentation - - -Utility classes for iciql. - - \ No newline at end of file diff --git a/src/main/java/com/iciql/CompareType.java b/src/main/java/com/iciql/CompareType.java new file mode 100644 index 0000000..84e29fe --- /dev/null +++ b/src/main/java/com/iciql/CompareType.java @@ -0,0 +1,45 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * An enumeration of compare operations. + */ + +enum CompareType { + EQUAL("=", true), EXCEEDS(">", true), AT_LEAST(">=", true), LESS_THAN("<", true), AT_MOST("<=", true), NOT_EQUAL( + "<>", true), IS_NOT_NULL("IS NOT NULL", false), IS_NULL("IS NULL", false), LIKE("LIKE", true), BETWEEN( + "BETWEEN", true); + + private String text; + private boolean hasRightExpression; + + CompareType(String text, boolean hasRightExpression) { + this.text = text; + this.hasRightExpression = hasRightExpression; + } + + String getString() { + return text; + } + + boolean hasRightExpression() { + return hasRightExpression; + } + +} diff --git a/src/main/java/com/iciql/Condition.java b/src/main/java/com/iciql/Condition.java new file mode 100644 index 0000000..17cb117 --- /dev/null +++ b/src/main/java/com/iciql/Condition.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * A condition contains one or two operands and a compare operation. + * + * @param + * the operand type + */ + +class Condition implements Token { + CompareType compareType; + A x, y, z; + + Condition(A x, A y, CompareType compareType) { + this(x, y, null, compareType); + } + + Condition(A x, A y, A z, CompareType compareType) { + this.compareType = compareType; + this.x = x; + this.y = y; + this.z = z; + } + + public void appendSQL(SQLStatement stat, Query query) { + query.appendSQL(stat, null, x); + stat.appendSQL(" "); + stat.appendSQL(compareType.getString()); + if (compareType.hasRightExpression()) { + stat.appendSQL(" "); + if (z == null) { + query.appendSQL(stat, x, y); + } else { + query.appendSQL(stat, x, y, z, compareType); + } + } + } +} diff --git a/src/main/java/com/iciql/ConditionAndOr.java b/src/main/java/com/iciql/ConditionAndOr.java new file mode 100644 index 0000000..4d1cd0e --- /dev/null +++ b/src/main/java/com/iciql/ConditionAndOr.java @@ -0,0 +1,37 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * An OR or an AND condition. + */ + +enum ConditionAndOr implements Token { + AND("AND"), OR("OR"); + + private String text; + + ConditionAndOr(String text) { + this.text = text; + } + + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL(text); + } + +} diff --git a/src/main/java/com/iciql/Constants.java b/src/main/java/com/iciql/Constants.java new file mode 100644 index 0000000..8b54493 --- /dev/null +++ b/src/main/java/com/iciql/Constants.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 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; + +/** + * Iciql constants. + */ +public class Constants { + + public static final String NAME = "iciql"; + + // The build script extracts this exact line so be careful editing it + // and only use A-Z a-z 0-9 .-_ in the string. + public static final String VERSION = "1.2.0-SNAPSHOT"; + + // The build script extracts this exact line so be careful editing it + // and only use A-Z a-z 0-9 .-_ in the string. + public static final String VERSION_DATE = "PENDING"; + + // The build script extracts this exact line so be careful editing it + // and only use A-Z a-z 0-9 .-_ in the string. + public static final String API_CURRENT = "15"; + +} diff --git a/src/main/java/com/iciql/Db.java b/src/main/java/com/iciql/Db.java new file mode 100644 index 0000000..ecd373c --- /dev/null +++ b/src/main/java/com/iciql/Db.java @@ -0,0 +1,774 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * Copyright 2012 Frédéric Gaillard. + * Copyright 2012 Alex Telepov. + * + * 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; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.sql.DataSource; + +import com.iciql.DbUpgrader.DefaultDbUpgrader; +import com.iciql.Iciql.IQTable; +import com.iciql.Iciql.IQVersion; +import com.iciql.Iciql.IQView; +import com.iciql.util.IciqlLogger; +import com.iciql.util.JdbcUtils; +import com.iciql.util.StringUtils; +import com.iciql.util.Utils; +import com.iciql.util.WeakIdentityHashMap; + +/** + * This class represents a connection to a database. + */ + +public class Db { + + /** + * This map It holds unique tokens that are generated by functions such as + * Function.sum(..) in "db.from(p).select(Function.sum(p.unitPrice))". It + * doesn't actually hold column tokens, as those are bound to the query + * itself. + */ + private static final Map TOKENS; + + private static final Map> DIALECTS; + + private final Connection conn; + private final Map, TableDefinition> classMap = Collections + .synchronizedMap(new HashMap, TableDefinition>()); + private final SQLDialect dialect; + private DbUpgrader dbUpgrader = new DefaultDbUpgrader(); + private final Set> upgradeChecked = Collections.synchronizedSet(new HashSet>()); + + private boolean skipCreate; + private boolean autoSavePoint = true; + + static { + TOKENS = Collections.synchronizedMap(new WeakIdentityHashMap()); + DIALECTS = Collections.synchronizedMap(new HashMap>()); + // can register by... + // 1. Connection class name + // 2. DatabaseMetaData.getDatabaseProductName() + DIALECTS.put("Apache Derby", SQLDialectDerby.class); + DIALECTS.put("H2", SQLDialectH2.class); + DIALECTS.put("HSQL Database Engine", SQLDialectHSQL.class); + DIALECTS.put("MySQL", SQLDialectMySQL.class); + DIALECTS.put("PostgreSQL", SQLDialectPostgreSQL.class); + DIALECTS.put("Microsoft SQL Server", SQLDialectMSSQL.class); + } + + private Db(Connection conn) { + this.conn = conn; + String databaseName = null; + DatabaseMetaData data = null; + try { + data = conn.getMetaData(); + databaseName = data.getDatabaseProductName(); + } catch (SQLException s) { + throw new IciqlException(s, "failed to retrieve database metadata!"); + } + dialect = getDialect(databaseName, conn.getClass().getName()); + dialect.configureDialect(databaseName, data); + } + + /** + * Register a new/custom dialect class. You can use this method to replace + * any existing dialect or to add a new one. + * + * @param token + * the fully qualified name of the connection class or the + * expected result of DatabaseMetaData.getDatabaseProductName() + * @param dialectClass + * the dialect class to register + */ + public static void registerDialect(String token, Class dialectClass) { + DIALECTS.put(token, dialectClass); + } + + SQLDialect getDialect(String databaseName, String className) { + Class dialectClass = null; + if (DIALECTS.containsKey(className)) { + // dialect registered by connection class name + dialectClass = DIALECTS.get(className); + } else if (DIALECTS.containsKey(databaseName)) { + // dialect registered by database name + dialectClass = DIALECTS.get(databaseName); + } else { + // did not find a match, use default + dialectClass = SQLDialectDefault.class; + } + return instance(dialectClass); + } + + static X registerToken(X x, Token token) { + TOKENS.put(x, token); + return x; + } + + static Token getToken(Object x) { + return TOKENS.get(x); + } + + static T instance(Class clazz) { + try { + return clazz.newInstance(); + } catch (Exception e) { + throw new IciqlException(e); + } + } + + public static Db open(String url) { + try { + Connection conn = JdbcUtils.getConnection(null, url, null, null); + return new Db(conn); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + public static Db open(String url, String user, String password) { + try { + Connection conn = JdbcUtils.getConnection(null, url, user, password); + return new Db(conn); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + public static Db open(String url, String user, char[] password) { + try { + Connection conn = JdbcUtils.getConnection(null, url, user, password == null ? null : new String(password)); + return new Db(conn); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + /** + * Create a new database instance using a data source. This method is fast, + * so that you can always call open() / close() on usage. + * + * @param ds + * the data source + * @return the database instance. + */ + public static Db open(DataSource ds) { + try { + return new Db(ds.getConnection()); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + public static Db open(Connection conn) { + return new Db(conn); + } + + + + /** + * Convenience function to avoid import statements in application code. + */ + public void activateConsoleLogger() { + IciqlLogger.activateConsoleLogger(); + } + + /** + * Convenience function to avoid import statements in application code. + */ + public void deactivateConsoleLogger() { + IciqlLogger.deactivateConsoleLogger(); + } + + public void insert(T t) { + Class clazz = t.getClass(); + long rc = define(clazz).createIfRequired(this).insert(this, t, false); + if (rc == 0) { + throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t); + } + } + + public long insertAndGetKey(T t) { + Class clazz = t.getClass(); + return define(clazz).createIfRequired(this).insert(this, t, true); + } + + /** + * Merge INSERTS if the record does not exist or UPDATES the record if it + * does exist. Not all databases support MERGE and the syntax varies with + * the database. + * + * If the database does not support a MERGE syntax the dialect can try to + * simulate a merge by implementing: + *

+ * INSERT INTO foo... (SELECT ?,... FROM foo WHERE pk=? HAVING count(*)=0) + *

+ * iciql will check the affected row count returned by the internal merge + * method and if the affected row count = 0, it will issue an update. + *

+ * See the Derby dialect for an implementation of this technique. + *

+ * If the dialect does not support merge an IciqlException will be thrown. + * + * @param t + */ + public void merge(T t) { + Class clazz = t.getClass(); + TableDefinition def = define(clazz).createIfRequired(this); + int rc = def.merge(this, t); + if (rc == 0) { + rc = def.update(this, t); + } + if (rc == 0) { + throw new IciqlException("merge failed"); + } + } + + public int update(T t) { + Class clazz = t.getClass(); + return define(clazz).createIfRequired(this).update(this, t); + } + + public int delete(T t) { + Class clazz = t.getClass(); + return define(clazz).createIfRequired(this).delete(this, t); + } + + public Query from(T alias) { + Class clazz = alias.getClass(); + define(clazz).createIfRequired(this); + return Query.from(this, alias); + } + + @SuppressWarnings("unchecked") + public int dropTable(Class modelClass) { + TableDefinition def = (TableDefinition) define(modelClass); + SQLStatement stat = new SQLStatement(this); + getDialect().prepareDropTable(stat, def); + IciqlLogger.drop(stat.getSQL()); + int rc = 0; + try { + rc = stat.executeUpdate(); + } catch (IciqlException e) { + if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) { + throw e; + } + } + // remove this model class from the table definition cache + classMap.remove(modelClass); + // remove this model class from the upgrade checked cache + upgradeChecked.remove(modelClass); + return rc; + } + + @SuppressWarnings("unchecked") + public int dropView(Class modelClass) { + TableDefinition def = (TableDefinition) define(modelClass); + SQLStatement stat = new SQLStatement(this); + getDialect().prepareDropView(stat, def); + IciqlLogger.drop(stat.getSQL()); + int rc = 0; + try { + rc = stat.executeUpdate(); + } catch (IciqlException e) { + if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) { + throw e; + } + } + // remove this model class from the table definition cache + classMap.remove(modelClass); + // remove this model class from the upgrade checked cache + upgradeChecked.remove(modelClass); + return rc; + } + + public List buildObjects(Class modelClass, ResultSet rs) { + return buildObjects(modelClass, false, rs); + } + + @SuppressWarnings("unchecked") + public List buildObjects(Class modelClass, boolean wildcardSelect, ResultSet rs) { + List result = new ArrayList(); + TableDefinition def = (TableDefinition) define(modelClass); + try { + int[] columns = def.mapColumns(wildcardSelect, rs); + while (rs.next()) { + T item = Utils.newObject(modelClass); + def.readRow(item, rs, columns); + result.add(item); + } + } catch (SQLException e) { + throw new IciqlException(e); + } + return result; + } + + Db upgradeDb() { + if (!upgradeChecked.contains(dbUpgrader.getClass())) { + // flag as checked immediately because calls are nested. + upgradeChecked.add(dbUpgrader.getClass()); + + IQVersion model = dbUpgrader.getClass().getAnnotation(IQVersion.class); + if (model.value() == 0) { + // try superclass + Class superClass = dbUpgrader.getClass().getSuperclass(); + if (superClass.isAnnotationPresent(IQVersion.class)) { + model = superClass.getAnnotation(IQVersion.class); + } + } + if (model.value() > 0) { + DbVersion v = new DbVersion(); + // (SCHEMA="" && TABLE="") == DATABASE + DbVersion dbVersion = from(v).where(v.schemaName).is("").and(v.tableName).is("") + .selectFirst(); + if (dbVersion == null) { + // database has no version registration, but model specifies + // version: insert DbVersion entry and return. + DbVersion newDb = new DbVersion(model.value()); + // database is an older version than the model + boolean success = dbUpgrader.upgradeDatabase(this, 0, newDb.version); + if (success) { + insert(newDb); + } + } else { + // database has a version registration: + // check to see if upgrade is required. + if ((model.value() > dbVersion.version) && (dbUpgrader != null)) { + // database is an older version than the model + boolean success = dbUpgrader.upgradeDatabase(this, dbVersion.version, model.value()); + if (success) { + dbVersion.version = model.value(); + update(dbVersion); + } + } + } + } + } + return this; + } + + void upgradeTable(TableDefinition model) { + if (!upgradeChecked.contains(model.getModelClass())) { + // flag is checked immediately because calls are nested + upgradeChecked.add(model.getModelClass()); + + if (model.tableVersion > 0) { + // table is using iciql version tracking. + DbVersion v = new DbVersion(); + String schema = StringUtils.isNullOrEmpty(model.schemaName) ? "" : model.schemaName; + DbVersion dbVersion = from(v).where(v.schemaName).is(schema).and(v.tableName) + .is(model.tableName).selectFirst(); + if (dbVersion == null) { + // table has no version registration, but model specifies + // version: insert DbVersion entry + DbVersion newTable = new DbVersion(model.tableVersion); + newTable.schemaName = schema; + newTable.tableName = model.tableName; + insert(newTable); + } else { + // table has a version registration: + // check if upgrade is required + if ((model.tableVersion > dbVersion.version) && (dbUpgrader != null)) { + // table is an older version than model + boolean success = dbUpgrader.upgradeTable(this, schema, model.tableName, + dbVersion.version, model.tableVersion); + if (success) { + dbVersion.version = model.tableVersion; + update(dbVersion); + } + } + } + } + } + } + + TableDefinition define(Class clazz) { + TableDefinition def = getTableDefinition(clazz); + if (def == null) { + upgradeDb(); + def = new TableDefinition(clazz); + def.mapFields(); + classMap.put(clazz, def); + if (Iciql.class.isAssignableFrom(clazz)) { + T t = instance(clazz); + Iciql table = (Iciql) t; + Define.define(def, table); + } else if (clazz.isAnnotationPresent(IQTable.class)) { + // annotated classes skip the Define().define() static + // initializer + T t = instance(clazz); + def.mapObject(t); + } else if (clazz.isAnnotationPresent(IQView.class)) { + // annotated classes skip the Define().define() static + // initializer + T t = instance(clazz); + def.mapObject(t); + } + } + return def; + } + + boolean hasCreated(Class clazz) { + return upgradeChecked.contains(clazz); + } + + public synchronized void setDbUpgrader(DbUpgrader upgrader) { + if (!upgrader.getClass().isAnnotationPresent(IQVersion.class)) { + throw new IciqlException("DbUpgrader must be annotated with " + IQVersion.class.getSimpleName()); + } + this.dbUpgrader = upgrader; + upgradeChecked.clear(); + } + + public SQLDialect getDialect() { + return dialect; + } + + public Connection getConnection() { + return conn; + } + + public void close() { + try { + conn.close(); + } catch (Exception e) { + throw new IciqlException(e); + } + } + + public TestCondition test(A x) { + return new TestCondition(x); + } + + public void insertAll(List list) { + if (list.size() == 0) { + return; + } + Savepoint savepoint = null; + try { + Class clazz = list.get(0).getClass(); + TableDefinition def = define(clazz).createIfRequired(this); + savepoint = prepareSavepoint(); + for (T t : list) { + PreparedStatement ps = def.createInsertStatement(this, t, false); + int rc = ps.executeUpdate(); + if (rc == 0) { + throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t); + } + } + commit(savepoint); + } catch (SQLException e) { + rollback(savepoint); + throw new IciqlException(e); + } catch (IciqlException e) { + rollback(savepoint); + throw e; + } + } + + public List insertAllAndGetKeys(List list) { + List identities = new ArrayList(); + if (list.size() == 0) { + return identities; + } + Savepoint savepoint = null; + try { + Class clazz = list.get(0).getClass(); + TableDefinition def = define(clazz).createIfRequired(this); + savepoint = prepareSavepoint(); + for (T t : list) { + long key = def.insert(this, t, true); + identities.add(key); + } + commit(savepoint); + } catch (IciqlException e) { + rollback(savepoint); + throw e; + } + return identities; + } + + public void updateAll(List list) { + if (list.size() == 0) { + return; + } + Savepoint savepoint = null; + try { + Class clazz = list.get(0).getClass(); + TableDefinition def = define(clazz).createIfRequired(this); + savepoint = prepareSavepoint(); + for (T t : list) { + def.update(this, t); + } + commit(savepoint); + } catch (IciqlException e) { + rollback(savepoint); + throw e; + } + } + + public void deleteAll(List list) { + if (list.size() == 0) { + return; + } + Savepoint savepoint = null; + try { + Class clazz = list.get(0).getClass(); + TableDefinition def = define(clazz).createIfRequired(this); + savepoint = prepareSavepoint(); + for (T t : list) { + def.delete(this, t); + } + commit(savepoint); + } catch (IciqlException e) { + rollback(savepoint); + throw e; + } + } + + PreparedStatement prepare(String sql, boolean returnGeneratedKeys) { + IciqlException.checkUnmappedField(sql); + try { + if (returnGeneratedKeys) { + return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + } + return conn.prepareStatement(sql); + } catch (SQLException e) { + throw IciqlException.fromSQL(sql, e); + } + } + + Savepoint prepareSavepoint() { + // don't change auto-commit mode. + // don't create save point. + if (!autoSavePoint) { + return null; + } + // create a savepoint + Savepoint savepoint = null; + try { + conn.setAutoCommit(false); + savepoint = conn.setSavepoint(); + } catch (SQLFeatureNotSupportedException e) { + // jdbc driver does not support save points + } catch (SQLException e) { + throw new IciqlException(e, "Could not create save point"); + } + return savepoint; + } + + void commit(Savepoint savepoint) { + if (savepoint != null) { + try { + conn.commit(); + conn.setAutoCommit(true); + } catch (SQLException e) { + throw new IciqlException(e, "Failed to commit pending transactions"); + } + } + } + + void rollback(Savepoint savepoint) { + if (savepoint != null) { + try { + conn.rollback(savepoint); + conn.setAutoCommit(true); + } catch (SQLException s) { + throw new IciqlException(s, "Failed to rollback transactions"); + } + } + } + + @SuppressWarnings("unchecked") + TableDefinition getTableDefinition(Class clazz) { + return (TableDefinition) classMap.get(clazz); + } + + /** + * Run a SQL query directly against the database. + * + * Be sure to close the ResultSet with + * + *

+	 * JdbcUtils.closeSilently(rs, true);
+	 * 
+ * + * @param sql + * the SQL statement + * @param args + * optional object arguments for x=? tokens in query + * @return the result set + */ + public ResultSet executeQuery(String sql, List args) { + return executeQuery(sql, args.toArray()); + } + + /** + * Run a SQL query directly against the database. + * + * Be sure to close the ResultSet with + * + *
+	 * JdbcUtils.closeSilently(rs, true);
+	 * 
+ * + * @param sql + * the SQL statement + * @param args + * optional object arguments for x=? tokens in query + * @return the result set + */ + public ResultSet executeQuery(String sql, Object... args) { + try { + if (args.length == 0) { + return conn.createStatement().executeQuery(sql); + } else { + PreparedStatement stat = conn.prepareStatement(sql); + int i = 1; + for (Object arg : args) { + stat.setObject(i++, arg); + } + return stat.executeQuery(); + } + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + /** + * Run a SQL query directly against the database and map the results to the + * model class. + * + * @param modelClass + * the model class to bind the query ResultSet rows into. + * @param sql + * the SQL statement + * @return the result set + */ + public List executeQuery(Class modelClass, String sql, List args) { + return executeQuery(modelClass, sql, args.toArray()); + } + + /** + * Run a SQL query directly against the database and map the results to the + * model class. + * + * @param modelClass + * the model class to bind the query ResultSet rows into. + * @param sql + * the SQL statement + * @return the result set + */ + public List executeQuery(Class modelClass, String sql, Object... args) { + ResultSet rs = null; + try { + if (args.length == 0) { + rs = conn.createStatement().executeQuery(sql); + } else { + PreparedStatement stat = conn.prepareStatement(sql); + int i = 1; + for (Object arg : args) { + stat.setObject(i++, arg); + } + rs = stat.executeQuery(); + } + boolean wildcardSelect = sql.toLowerCase().startsWith("select *") + || sql.toLowerCase().startsWith("select distinct *"); + return buildObjects(modelClass, wildcardSelect, rs); + } catch (SQLException e) { + throw new IciqlException(e); + } finally { + JdbcUtils.closeSilently(rs, true); + } + } + + /** + * Run a SQL statement directly against the database. + * + * @param sql + * the SQL statement + * @return the update count + */ + public int executeUpdate(String sql, Object... args) { + Statement stat = null; + try { + int updateCount; + if (args.length == 0) { + stat = conn.createStatement(); + updateCount = stat.executeUpdate(sql); + } else { + PreparedStatement ps = conn.prepareStatement(sql); + int i = 1; + for (Object arg : args) { + ps.setObject(i++, arg); + } + updateCount = ps.executeUpdate(); + stat = ps; + } + return updateCount; + } catch (SQLException e) { + throw new IciqlException(e); + } finally { + JdbcUtils.closeSilently(stat); + } + } + + /** + * Allow to enable/disable globally createIfRequired in TableDefinition. + * For advanced user wanting to gain full control of transactions. + * Default value is false. + * @param skipCreate + */ + public void setSkipCreate(boolean skipCreate) { + this.skipCreate = skipCreate; + } + + public boolean getSkipCreate() { + return this.skipCreate; + } + + /** + * Allow to enable/disable usage of save point. + * For advanced user wanting to gain full control of transactions. + * Default value is false. + * @param autoSavePoint + */ + public void setAutoSavePoint(boolean autoSavePoint) { + this.autoSavePoint = autoSavePoint; + } + + public boolean getAutoSavePoint() { + return this.autoSavePoint; + } + +} diff --git a/src/main/java/com/iciql/DbInspector.java b/src/main/java/com/iciql/DbInspector.java new file mode 100644 index 0000000..acaceea --- /dev/null +++ b/src/main/java/com/iciql/DbInspector.java @@ -0,0 +1,204 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import com.iciql.Iciql.IQTable; +import com.iciql.util.JdbcUtils; +import com.iciql.util.StringUtils; +import com.iciql.util.Utils; + +/** + * Class to inspect a model and a database for the purposes of model validation + * and automatic model generation. This class finds the available schemas and + * tables and serves as the entry point for model generation and validation. + */ +public class DbInspector { + + private Db db; + private DatabaseMetaData metaData; + private Class dateTimeClass = java.util.Date.class; + + public DbInspector(Db db) { + this.db = db; + setPreferredDateTimeClass(db.getDialect().getDateTimeClass()); + } + + /** + * Set the preferred class to store date and time. Possible values are: + * java.util.Date (default) and java.sql.Timestamp. + * + * @param dateTimeClass + * the new class + */ + public void setPreferredDateTimeClass(Class dateTimeClass) { + this.dateTimeClass = dateTimeClass; + } + + /** + * Generates models class skeletons for schemas and tables. If the table + * name is undefined, models will be generated for every table within the + * specified schema. Additionally, if no schema is defined, models will be + * generated for all schemas and all tables. + * + * @param schema + * the schema name (optional) + * @param table + * the table name (optional) + * @param packageName + * the package name (optional) + * @param annotateSchema + * (includes schema name in annotation) + * @param trimStrings + * (trims strings to maxLength of column) + * @return a list of complete model classes as strings, each element a class + */ + public List generateModel(String schema, String table, String packageName, + boolean annotateSchema, boolean trimStrings) { + try { + List models = Utils.newArrayList(); + List tables = getTables(schema, table); + for (TableInspector t : tables) { + t.read(metaData); + String model = t.generateModel(packageName, annotateSchema, trimStrings); + models.add(model); + } + return models; + } catch (SQLException s) { + throw new IciqlException(s); + } + } + + /** + * Validates a model. + * + * @param model + * an instance of the model class + * @param throwOnError + * if errors should cause validation to fail + * @return a list of validation remarks + */ + public List validateModel(T model, boolean throwOnError) { + try { + TableInspector inspector = getTable(model); + inspector.read(metaData); + @SuppressWarnings("unchecked") + Class clazz = (Class) model.getClass(); + TableDefinition def = db.define(clazz); + return inspector.validate(def, throwOnError); + } catch (SQLException s) { + throw new IciqlException(s); + } + } + + private DatabaseMetaData getMetaData() throws SQLException { + if (metaData == null) { + metaData = db.getConnection().getMetaData(); + } + return metaData; + } + + /** + * Get the table in the database based on the model definition. + * + * @param model + * an instance of the model class + * @return the table inspector + */ + private TableInspector getTable(T model) throws SQLException { + @SuppressWarnings("unchecked") + Class clazz = (Class) model.getClass(); + TableDefinition def = db.define(clazz); + boolean forceUpperCase = getMetaData().storesUpperCaseIdentifiers(); + String schema = (forceUpperCase && def.schemaName != null) ? def.schemaName.toUpperCase() + : def.schemaName; + String table = forceUpperCase ? def.tableName.toUpperCase() : def.tableName; + List tables = getTables(schema, table); + return tables.get(0); + } + + /** + * Returns a list of tables. This method always returns at least one + * element. If no table is found, an exception is thrown. + * + * @param schema + * the schema name + * @param table + * the table name + * @return a list of table inspectors (always contains at least one element) + */ + private List getTables(String schema, String table) throws SQLException { + ResultSet rs = null; + try { + rs = getMetaData().getSchemas(); + ArrayList schemaList = Utils.newArrayList(); + while (rs.next()) { + schemaList.add(rs.getString("TABLE_SCHEM")); + } + JdbcUtils.closeSilently(rs); + + String iciqlTables = DbVersion.class.getAnnotation(IQTable.class).name(); + + List tables = Utils.newArrayList(); + if (schemaList.size() == 0) { + schemaList.add(null); + } + for (String s : schemaList) { + rs = getMetaData().getTables(null, s, null, new String[] { "TABLE" }); + while (rs.next()) { + String t = rs.getString("TABLE_NAME"); + if (t.charAt(0) == '"') { + t = t.substring(1); + } + if (t.charAt(t.length() - 1) == '"') { + t = t.substring(0, t.length() - 1); + } + if (!t.equalsIgnoreCase(iciqlTables)) { + tables.add(new TableInspector(s, t, dateTimeClass)); + } + } + } + + if (StringUtils.isNullOrEmpty(schema) && StringUtils.isNullOrEmpty(table)) { + // all schemas and tables + return tables; + } + // schema subset OR table subset OR exact match + List matches = Utils.newArrayList(); + for (TableInspector t : tables) { + if (t.matches(schema, table)) { + matches.add(t); + } + } + if (matches.size() == 0) { + throw new IciqlException(MessageFormat.format("Failed to find schema={0} table={1}", + schema == null ? "" : schema, table == null ? "" : table)); + } + return matches; + } finally { + JdbcUtils.closeSilently(rs); + } + } + +} diff --git a/src/main/java/com/iciql/DbUpgrader.java b/src/main/java/com/iciql/DbUpgrader.java new file mode 100644 index 0000000..1303f4e --- /dev/null +++ b/src/main/java/com/iciql/DbUpgrader.java @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import com.iciql.Iciql.IQVersion; + +/** + * Interface which defines a class to handle table changes based on model + * versions. An implementation of DbUpgrader must be annotated with the + * IQDatabase annotation, which defines the expected database version + * number. + */ +public interface DbUpgrader { + + /** + * Defines method interface to handle database upgrades. This method is only + * called if your DbUpgrader implementation is annotated with + * IQDatabase. + * + * @param db + * the database + * @param fromVersion + * the old version + * @param toVersion + * the new version + * @return true for successful upgrade. If the upgrade is successful, the + * version registry is automatically updated. + */ + boolean upgradeDatabase(Db db, int fromVersion, int toVersion); + + /** + * Defines method interface to handle table upgrades. + * + * @param db + * the database + * @param schema + * the schema + * @param table + * the table + * @param fromVersion + * the old version + * @param toVersion + * the new version + * @return true for successful upgrade. If the upgrade is successful, the + * version registry is automatically updated. + */ + boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion); + + /** + * The default database upgrader. It throws runtime exception instead of + * handling upgrade requests. + */ + @IQVersion(0) + public static class DefaultDbUpgrader implements DbUpgrader { + + public boolean upgradeDatabase(Db db, int fromVersion, int toVersion) { + throw new IciqlException("Please provide your own DbUpgrader implementation."); + } + + public boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion) { + throw new IciqlException("Please provide your own DbUpgrader implementation."); + } + + } + +} diff --git a/src/main/java/com/iciql/DbVersion.java b/src/main/java/com/iciql/DbVersion.java new file mode 100644 index 0000000..6270e14 --- /dev/null +++ b/src/main/java/com/iciql/DbVersion.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQTable; + +/** + * A system table to track database and table versions. + */ +@IQTable(name = "iq_versions", primaryKey = { "schemaName", "tableName" }, memoryTable = true) +public class DbVersion { + + @IQColumn(length = 255) + String schemaName = ""; + + @IQColumn(length = 255) + String tableName = ""; + + @IQColumn + Integer version; + + public DbVersion() { + // nothing to do + } + + /** + * Constructor for defining a version entry. Both the schema and the table + * are empty strings, which means this is the row for the 'database'. + * + * @param version + * the database version + */ + public DbVersion(int version) { + this.schemaName = ""; + this.tableName = ""; + this.version = version; + } + +} diff --git a/src/main/java/com/iciql/Define.java b/src/main/java/com/iciql/Define.java new file mode 100644 index 0000000..1810a4b --- /dev/null +++ b/src/main/java/com/iciql/Define.java @@ -0,0 +1,145 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * Copyright 2012 Frédéric Gaillard. + * + * 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; + +import com.iciql.Iciql.IndexType; + +/** + * This class provides utility methods to define primary keys, indexes, and set + * the name of the table. + */ + +public class Define { + + private static TableDefinition currentTableDefinition; + private static Iciql currentTable; + + public static void skipCreate() { + checkInDefine(); + currentTableDefinition.defineSkipCreate(); + } + + public static void index(IndexType type, Object... columns) { + checkInDefine(); + currentTableDefinition.defineIndex(null, type, columns); + } + + public static void index(String name, IndexType type, Object... columns) { + checkInDefine(); + currentTableDefinition.defineIndex(name, type, columns); + } + + public static void constraintUnique(String name, Object... columns) { + checkInDefine(); + currentTableDefinition.defineConstraintUnique(name, columns); + } + + /* + * The variable argument type Object can't be used twice :-) + */ +// public static void constraintForeignKey(String name, String refTableName, +// ConstraintDeleteType deleteType, ConstraintUpdateType updateType, +// ConstraintDeferrabilityType deferrabilityType, Object... columns, Object... refColumns) { +// checkInDefine(); +// currentTableDefinition.defineForeignKey(name, columns, refTableName, Columns, deleteType, updateType, deferrabilityType); +// } + + public static void primaryKey(Object... columns) { + checkInDefine(); + currentTableDefinition.definePrimaryKey(columns); + } + + public static void schemaName(String schemaName) { + checkInDefine(); + currentTableDefinition.defineSchemaName(schemaName); + } + + public static void tableName(String tableName) { + checkInDefine(); + currentTableDefinition.defineTableName(tableName); + } + + public static void viewTableName(String viewTableName) { + checkInDefine(); + currentTableDefinition.defineViewTableName(viewTableName); + } + + public static void memoryTable() { + checkInDefine(); + currentTableDefinition.defineMemoryTable(); + } + + public static void columnName(Object column, String columnName) { + checkInDefine(); + currentTableDefinition.defineColumnName(column, columnName); + } + + public static void autoIncrement(Object column) { + checkInDefine(); + currentTableDefinition.defineAutoIncrement(column); + } + + public static void length(Object column, int length) { + checkInDefine(); + currentTableDefinition.defineLength(column, length); + } + + public static void scale(Object column, int scale) { + checkInDefine(); + currentTableDefinition.defineScale(column, scale); + } + + public static void trim(Object column) { + checkInDefine(); + currentTableDefinition.defineTrim(column); + } + + public static void nullable(Object column, boolean isNullable) { + checkInDefine(); + currentTableDefinition.defineNullable(column, isNullable); + } + + public static void defaultValue(Object column, String defaultValue) { + checkInDefine(); + currentTableDefinition.defineDefaultValue(column, defaultValue); + } + + public static void constraint(Object column, String constraint) { + checkInDefine(); + currentTableDefinition.defineConstraint(column, constraint); + } + + static synchronized void define(TableDefinition tableDefinition, Iciql table) { + currentTableDefinition = tableDefinition; + currentTable = table; + tableDefinition.mapObject(table); + table.defineIQ(); + currentTable = null; + currentTableDefinition = null; + } + + private static void checkInDefine() { + if (currentTable == null) { + throw new IciqlException("This method may only be called " + + "from within the define() method, and the define() method " + + "is called by the framework."); + } + } + +} diff --git a/src/main/java/com/iciql/Filter.java b/src/main/java/com/iciql/Filter.java new file mode 100644 index 0000000..99dbdc3 --- /dev/null +++ b/src/main/java/com/iciql/Filter.java @@ -0,0 +1,25 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * Represents the WHERE clause of a query. + */ +public interface Filter { + boolean where(); +} diff --git a/src/main/java/com/iciql/Function.java b/src/main/java/com/iciql/Function.java new file mode 100644 index 0000000..3faddb7 --- /dev/null +++ b/src/main/java/com/iciql/Function.java @@ -0,0 +1,149 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import com.iciql.util.Utils; + +/** + * This class provides static methods that represents common SQL functions. + */ +public class Function implements Token { + + // must be a new instance + private static final Long COUNT_STAR = new Long(0); + + protected Object[] x; + private String name; + + protected Function(String name, Object... x) { + this.name = name; + this.x = x; + } + + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL(name).appendSQL("("); + int i = 0; + for (Object o : x) { + if (i++ > 0) { + stat.appendSQL(","); + } + query.appendSQL(stat, null, o); + } + stat.appendSQL(")"); + } + + public static Long count() { + return COUNT_STAR; + } + + public static Integer length(Object x) { + return Db.registerToken(Utils.newObject(Integer.class), new Function("LENGTH", x)); + } + + @SuppressWarnings("unchecked") + public static T sum(T x) { + return (T) Db.registerToken(Utils.newObject(x.getClass()), new Function("SUM", x)); + } + + public static Long count(Object x) { + return Db.registerToken(Utils.newObject(Long.class), new Function("COUNT", x)); + } + + public static Boolean isNull(Object x) { + return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) { + public void appendSQL(SQLStatement stat, Query query) { + query.appendSQL(stat, null, x[0]); + stat.appendSQL(" IS NULL"); + } + }); + } + + public static Boolean isNotNull(Object x) { + return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) { + public void appendSQL(SQLStatement stat, Query query) { + query.appendSQL(stat, null, x[0]); + stat.appendSQL(" IS NOT NULL"); + } + }); + } + + public static Boolean not(Boolean x) { + return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) { + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL("NOT "); + query.appendSQL(stat, null, x[0]); + } + }); + } + + public static Boolean or(Boolean... x) { + return Db.registerToken(Utils.newObject(Boolean.class), new Function("", (Object[]) x) { + public void appendSQL(SQLStatement stat, Query query) { + int i = 0; + for (Object o : x) { + if (i++ > 0) { + stat.appendSQL(" OR "); + } + query.appendSQL(stat, null, o); + } + } + }); + } + + public static Boolean and(Boolean... x) { + return Db.registerToken(Utils.newObject(Boolean.class), new Function("", (Object[]) x) { + public void appendSQL(SQLStatement stat, Query query) { + int i = 0; + for (Object o : x) { + if (i++ > 0) { + stat.appendSQL(" AND "); + } + query.appendSQL(stat, null, o); + } + } + }); + } + + @SuppressWarnings("unchecked") + public static X min(X x) { + Class clazz = (Class) x.getClass(); + X o = Utils.newObject(clazz); + return Db.registerToken(o, new Function("MIN", x)); + } + + @SuppressWarnings("unchecked") + public static X max(X x) { + Class clazz = (Class) x.getClass(); + X o = Utils.newObject(clazz); + return Db.registerToken(o, new Function("MAX", x)); + } + + public static Boolean like(String x, String pattern) { + Boolean o = Utils.newObject(Boolean.class); + return Db.registerToken(o, new Function("LIKE", x, pattern) { + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL("("); + query.appendSQL(stat, null, x[0]); + stat.appendSQL(" LIKE "); + query.appendSQL(stat, x[0], x[1]); + stat.appendSQL(")"); + } + }); + } + +} diff --git a/src/main/java/com/iciql/Iciql.java b/src/main/java/com/iciql/Iciql.java new file mode 100644 index 0000000..9f73ffa --- /dev/null +++ b/src/main/java/com/iciql/Iciql.java @@ -0,0 +1,731 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * Copyright 2012 Frédéric Gaillard. + * + * 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A class that implements this interface can be used as a database table. + *

+ * You may implement the Table interface on your model object and optionally use + * IQColumn annotations (which imposes a compile-time and runtime-dependency on + * iciql), or may choose to use the IQTable and IQColumn annotations only (which + * imposes a compile-time and runtime-dependency on this file only). + *

+ * If a class is annotated with IQTable and at the same time implements Table, + * the define() method is not called. + *

+ * Fully Supported Data Types: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
All Databases
java.lang.StringVARCHAR (length > 0) or CLOB (length == 0)
java.lang.BooleanBIT
java.lang.ByteTINYINT
java.lang.ShortSMALLINT
java.lang.IntegerINT
java.lang.LongBIGINT
java.lang.FloatREAL
java.lang.DoubleDOUBLE
java.math.BigDecimalDECIMAL (length == 0)
+ * DECIMAL(length, scale) (length > 0)
java.sql.DateDATE
java.sql.TimeTIME
java.sql.TimestampTIMESTAMP
java.util.DateTIMESTAMP
java.lang.Enum.name()VARCHAR (length > 0) or CLOB (length == 0)
+ * EnumType.NAME
java.lang.Enum.ordinal()INT
+ * EnumType.ORDINAL
java.lang.Enum implements
+ * com.iciql.Iciql.EnumID.enumId()
INT
+ * EnumType.ENUMID
H2 Databases
java.util.UUIDUUID
+ *

+ * Partially Supported Data Types: + *

+ * The following data types can be mapped to columns for all general statements + * BUT these field types may not be used to specify compile-time clauses or + * constraints. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
byte []BLOB
booleanBIT
byteTINYINT
shortSMALLINT
intINT
longBIGINT
floatREAL
doubleDOUBLE
+ *

+ * Table and field mapping: by default, the mapped table name is the class name + * and the public fields are reflectively mapped, by their name, to columns. As + * an alternative, you may specify both the table and column definition by + * annotations. + *

+ * Table Interface: you may set additional parameters such as table name, + * primary key, and indexes in the define() method. + *

+ * Annotations: you may use the annotations with or without implementing the + * Table interface. The annotations allow you to decouple your model completely + * from iciql other than this file. + *

+ * Automatic model generation: you may automatically generate model classes as + * strings with the Db and DbInspector objects: + * + *

+ * Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
+ * DbInspector inspector = new DbInspector(db);
+ * List<String> models =
+ *         inspector.generateModel(schema, table, packageName,
+ *         annotateSchema, trimStrings)
+ * 
+ * + * Or you may use the GenerateModels tool to generate and save your classes to + * the file system: + * + *
+ * java -jar iciql.jar
+ *      -url "jdbc:h2:mem:"
+ *      -user sa -password sa -schema schemaName -table tableName
+ *      -package packageName -folder destination
+ *      -annotateSchema false -trimStrings true
+ * 
+ * + * Model validation: you may validate your model class with DbInspector object. + * The DbInspector will report errors, warnings, and suggestions: + * + *
+ * Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
+ * DbInspector inspector = new DbInspector(db);
+ * List<Validation> remarks = inspector.validateModel(new MyModel(), throwOnError);
+ * for (Validation remark : remarks) {
+ * 	System.out.println(remark);
+ * }
+ * 
+ */ +public interface Iciql { + + /** + * An annotation for an iciql version. + *

+ * + * @IQVersion(1) + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQVersion { + + /** + * If set to a non-zero value, iciql maintains a "iq_versions" table + * within your database. The version number is used to call to a + * registered DbUpgrader implementation to perform relevant ALTER + * statements. Default: 0. You must specify a DbUpgrader on your Db + * object to use this parameter. + */ + int value() default 0; + + } + + /** + * An annotation for a schema. + *

+ * + * @IQSchema("PUBLIC") + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQSchema { + + /** + * The schema may be optionally specified. Default: unspecified. + */ + String value() default ""; + + } + + /** + * Enumeration defining the four index types. + */ + public static enum IndexType { + STANDARD, UNIQUE, HASH, UNIQUE_HASH; + } + + /** + * An index annotation. + *

+ *

    + *
  • @IQIndex("name") + *
  • @IQIndex({"street", "city"}) + *
  • @IQIndex(name="streetidx", value={"street", "city"}) + *
  • @IQIndex(name="addressidx", type=IndexType.UNIQUE, + * value={"house_number", "street", "city"}) + *
+ */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQIndex { + + /** + * Index name. If null or empty, iciql will generate one. + */ + String name() default ""; + + /** + * Type of the index. + *
    + *
  • com.iciql.iciql.IndexType.STANDARD + *
  • com.iciql.iciql.IndexType.UNIQUE + *
  • com.iciql.iciql.IndexType.HASH + *
  • com.iciql.iciql.IndexType.UNIQUE_HASH + *
+ * + * HASH indexes may only be valid for single column indexes. + * + */ + IndexType type() default IndexType.STANDARD; + + /** + * Columns to include in index. + *
    + *
  • single column index: value = "id" + *
  • multiple column index: value = { "id", "name", "date" } + *
+ */ + String[] value() default {}; + } + + /** + * Enumeration defining the ON DELETE actions. + */ + public static enum ConstraintDeleteType { + UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT; + } + + /** + * Enumeration defining the ON UPDATE actions. + */ + public static enum ConstraintUpdateType { + UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT; + } + + /** + * Enumeration defining the deferrability. + */ + public static enum ConstraintDeferrabilityType { + UNSET, DEFERRABLE_INITIALLY_DEFERRED, DEFERRABLE_INITIALLY_IMMEDIATE, NOT_DEFERRABLE; + } + + /** + * A foreign key constraint annotation. + *

+ *

    + *
  • @IQContraintForeignKey( + * foreignColumns = { "idaccount"}, + * referenceName = "account", + * referenceColumns = { "id" }, + * deleteType = ConstrainDeleteType.CASCADE, + * updateType = ConstraintUpdateType.NO_ACTION ) + *
+ * Note : reference columns should have a unique constraint defined in referenceName table, + * some database used to define a unique index instead of a unique constraint + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQContraintForeignKey { + + /** + * Constraint name. If null or empty, iciql will generate one. + */ + String name() default ""; + + /** + * Type of the action on delete, default to unspecified. + *
    + *
  • com.iciql.iciql.ConstrainDeleteType.CASCADE + *
  • com.iciql.iciql.ConstrainDeleteType.RESTRICT + *
  • com.iciql.iciql.ConstrainDeleteType.SET_NULL + *
  • com.iciql.iciql.ConstrainDeleteType.NO_ACTION + *
  • com.iciql.iciql.ConstrainDeleteType.SET_DEFAULT + *
+ */ + ConstraintDeleteType deleteType() default ConstraintDeleteType.UNSET; + + /** + * Type of the action on update, default to unspecified. + *
    + *
  • com.iciql.iciql.ConstrainUpdateType.CASCADE + *
  • com.iciql.iciql.ConstrainUpdateType.RESTRICT + *
  • com.iciql.iciql.ConstrainUpdateType.SET_NULL + *
  • com.iciql.iciql.ConstrainUpdateType.NO_ACTION + *
  • com.iciql.iciql.ConstrainUpdateType.SET_DEFAULT + *
+ */ + ConstraintUpdateType updateType() default ConstraintUpdateType.UNSET; + + /** + * Type of the deferrability mode, default to unspecified + *
    + *
  • com.iciql.iciql.ConstrainUpdateType.CASCADE + *
  • ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_DEFERRED + *
  • ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_IMMEDIATE + *
  • ConstraintDeferrabilityType.NOT_DEFERRABLE + *
+ */ + ConstraintDeferrabilityType deferrabilityType() default ConstraintDeferrabilityType.UNSET; + + /** + * The source table for the columns defined as foreign. + */ + String tableName() default ""; + + /** + * Columns defined as 'foreign'. + *
    + *
  • single column : foreignColumns = "id" + *
  • multiple column : foreignColumns = { "id", "name", "date" } + *
+ */ + String[] foreignColumns() default {}; + + /** + * The reference table for the columns defined as references. + */ + String referenceName() default ""; + + /** + * Columns defined as 'references'. + *
    + *
  • single column : referenceColumns = "id" + *
  • multiple column : referenceColumns = { "id", "name", "date" } + *
+ */ + String[] referenceColumns() default {}; + } + + /** + * Annotation to specify multiple foreign keys constraints. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQContraintsForeignKey { + IQContraintForeignKey[] value() default {}; + } + + /** + * A unique constraint annotation. + *

+ *

    + *
  • @IQContraintUnique(uniqueColumns = { "street", "city" }) + *
  • @IQContraintUnique(name="streetconstraint", uniqueColumns = { "street", "city" }) + *
+ */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQContraintUnique { + + /** + * Constraint name. If null or empty, iciql will generate one. + */ + String name() default ""; + + /** + * Columns defined as 'unique'. + *
    + *
  • single column : uniqueColumns = "id" + *
  • multiple column : uniqueColumns = { "id", "name", "date" } + *
+ */ + String[] uniqueColumns() default {}; + + } + + /** + * Annotation to specify multiple unique constraints. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQContraintsUnique { + IQContraintUnique[] value() default {}; + } + + /** + * Annotation to define a view. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQView { + + /** + * The view name. If not specified the class name is used as the view + * name. + *

+ * The view name may still be overridden in the define() method if the + * model class is not annotated with IQView. Default: unspecified. + */ + String name() default ""; + + /** + * The source table for the view. + *

+ * The view name may still be overridden in the define() method if the + * model class is not annotated with IQView. Default: unspecified. + */ + String tableName() default ""; + + /** + * The inherit columns allows this model class to inherit columns from + * its super class. Any IQTable annotation present on the super class is + * ignored. Default: false. + */ + boolean inheritColumns() default false; + + /** + * Whether or not iciql tries to create the view. Default: + * true. + */ + boolean create() default true; + + /** + * If true, only fields that are explicitly annotated as IQColumn are + * mapped. Default: true. + */ + boolean annotationsOnly() default true; + } + + /** + * String snippet defining SQL constraints for a field. Use "this" as + * a placeholder for the column name. "this" will be substituted at + * runtime. + *

+ * IQConstraint("this > 2 AND this <= 7") + *

+ * This snippet may still be overridden in the define() method if the + * model class is not annotated with IQTable or IQView. Default: unspecified. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface IQConstraint { + + String value() default ""; + } + + /** + * Annotation to specify multiple indexes. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQIndexes { + IQIndex[] value() default {}; + } + + /** + * Annotation to define a table. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface IQTable { + + /** + * The table name. If not specified the class name is used as the table + * name. + *

+ * The table name may still be overridden in the define() method if the + * model class is not annotated with IQTable. Default: unspecified. + */ + String name() default ""; + + /** + * The primary key may be optionally specified. If it is not specified, + * then no primary key is set by the IQTable annotation. You may specify + * a composite primary key. + *

    + *
  • single column primaryKey: value = "id" + *
  • compound primary key: value = { "id", "name" } + *
+ * The primary key may still be overridden in the define() method if the + * model class is not annotated with IQTable. Default: unspecified. + */ + String[] primaryKey() default {}; + + /** + * The inherit columns allows this model class to inherit columns from + * its super class. Any IQTable annotation present on the super class is + * ignored. Default: false. + */ + boolean inheritColumns() default false; + + /** + * Whether or not iciql tries to create the table and indexes. Default: + * true. + */ + boolean create() default true; + + /** + * If true, only fields that are explicitly annotated as IQColumn are + * mapped. Default: true. + */ + boolean annotationsOnly() default true; + + /** + * If true, this table is created as a memory table where data is + * persistent, but index data is kept in main memory. Valid only for H2 + * and HSQL databases. Default: false. + */ + boolean memoryTable() default false; + } + + /** + * Annotation to define a column. Annotated fields may have any scope + * (however, the JVM may raise a SecurityException if the SecurityManager + * doesn't allow iciql to access the field.) + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface IQColumn { + + /** + * If not specified, the field name is used as the column name. Default: + * the field name. + */ + String name() default ""; + + /** + * This column is the primary key. Default: false. + */ + boolean primaryKey() default false; + + /** + * The column is created with a sequence as the default value. Default: + * false. + */ + boolean autoIncrement() default false; + + /** + * Length is used to define the length of a VARCHAR column or to define + * the precision of a DECIMAL(precision, scale) expression. + *

+ * If larger than zero, it is used during the CREATE TABLE phase. For + * string values it may also be used to prevent database exceptions on + * INSERT and UPDATE statements (see trim). + *

+ * Any length set in define() may override this annotation setting if + * the model class is not annotated with IQTable. Default: 0. + */ + int length() default 0; + + /** + * Scale is used during the CREATE TABLE phase to define the scale of a + * DECIMAL(precision, scale) expression. + *

+ * Any scale set in define() may override this annotation setting if the + * model class is not annotated with IQTable. Default: 0. + */ + int scale() default 0; + + /** + * If true, iciql will automatically trim the string if it exceeds + * length (value.substring(0, length)). Default: false. + */ + boolean trim() default false; + + /** + * If false, iciql will set the column NOT NULL during the CREATE TABLE + * phase. Default: true. + */ + boolean nullable() default true; + + /** + * The default value assigned to the column during the CREATE TABLE + * phase. This field could contain a literal single-quoted value, or a + * function call. Empty strings are considered NULL. Examples: + *

    + *
  • defaultValue="" (null) + *
  • defaultValue="CURRENT_TIMESTAMP" + *
  • defaultValue="''" (empty string) + *
  • defaultValue="'0'" + *
  • defaultValue="'1970-01-01 00:00:01'" + *
+ * if the default value is specified, and auto increment is disabled, + * and primary key is disabled, then this value is included in the + * "DEFAULT ..." phrase of a column during the CREATE TABLE process. + *

+ * Alternatively, you may specify a default object value on the field + * and this will be converted to a properly formatted DEFAULT expression + * during the CREATE TABLE process. + *

+ * Default: unspecified (null). + */ + String defaultValue() default ""; + + } + + /** + * Interface for using the EnumType.ENUMID enumeration mapping strategy. + *

+ * Enumerations wishing to use EnumType.ENUMID must implement this + * interface. + */ + public interface EnumId { + int enumId(); + } + + /** + * Enumeration representing how to map a java.lang.Enum to a column. + *

+ *

    + *
  • NAME - name() : string + *
  • ORDINAL - ordinal() : int + *
  • ENUMID - enumId() : int + *
+ * + * @see com.iciql.Iciql.EnumId interface + */ + public enum EnumType { + NAME, ORDINAL, ENUMID; + + public static final EnumType DEFAULT_TYPE = NAME; + } + + /** + * Annotation to define how a java.lang.Enum is mapped to a column. + *

+ * This annotation can be used on: + *

    + *
  • a field instance of an enumeration type + *
  • on the enumeration class declaration + *
+ * If you choose to annotate the class declaration, that will be the default + * mapping strategy for all @IQColumn instances of the enum. This can still + * be overridden for an individual field by specifying the IQEnum + * annotation. + *

+ * The default mapping is by NAME. + * + *

+	 * IQEnum(EnumType.NAME)
+	 * 
+ * + * A string mapping will generate either a VARCHAR, if IQColumn.length > 0 + * or a TEXT column if IQColumn.length == 0 + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD, ElementType.TYPE }) + public @interface IQEnum { + EnumType value() default EnumType.NAME; + } + + /** + * Annotation to define an ignored field. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface IQIgnore{ + } + + /** + * This method is called to let the table define the primary key, indexes, + * and the table name. + */ + void defineIQ(); +} diff --git a/src/main/java/com/iciql/IciqlException.java b/src/main/java/com/iciql/IciqlException.java new file mode 100644 index 0000000..3f27b73 --- /dev/null +++ b/src/main/java/com/iciql/IciqlException.java @@ -0,0 +1,177 @@ +/* + * Copyright 2011 James Moger. + * Copyright 2012 Frédéric Gaillard + * + * 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; + +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.regex.Pattern; + +/** + * Iciql wraps all exceptions with this class. + */ +public class IciqlException extends RuntimeException { + + public static final int CODE_UNMAPPED_FIELD = 1; + public static final int CODE_DUPLICATE_KEY = 2; + public static final int CODE_OBJECT_NOT_FOUND = 3; + public static final int CODE_OBJECT_ALREADY_EXISTS = 4; + public static final int CODE_CONSTRAINT_VIOLATION = 5; + public static final int CODE_UNCHARACTERIZED = 6; + + private static final String TOKEN_UNMAPPED_FIELD = "\\? (=|\\>|\\<|\\<\\>|!=|\\>=|\\<=|LIKE|BETWEEN) \\?"; + + private static final long serialVersionUID = 1L; + + private String sql; + + private int iciqlCode; + + public IciqlException(Throwable t) { + super(t.getMessage(), t); + configureCode(t); + } + + public IciqlException(String message, Object... parameters) { + super(parameters.length > 0 ? MessageFormat.format(message, parameters) : message); + } + + public IciqlException(Throwable t, String message, Object... parameters) { + super(parameters.length > 0 ? MessageFormat.format(message, parameters) : message, t); + configureCode(t); + } + + public static void checkUnmappedField(String sql) { + if (Pattern.compile(IciqlException.TOKEN_UNMAPPED_FIELD).matcher(sql).find()) { + IciqlException e = new IciqlException("unmapped field in statement!"); + e.sql = sql; + e.iciqlCode = CODE_UNMAPPED_FIELD; + throw e; + } + } + + public static IciqlException fromSQL(String sql, Throwable t) { + if (Pattern.compile(TOKEN_UNMAPPED_FIELD).matcher(sql).find()) { + IciqlException e = new IciqlException(t, "unmapped field in statement!"); + e.sql = sql; + e.iciqlCode = CODE_UNMAPPED_FIELD; + return e; + } else { + IciqlException e = new IciqlException(t, t.getMessage()); + e.sql = sql; + return e; + } + } + + public void setSQL(String sql) { + this.sql = sql; + } + + public String getSQL() { + return sql; + } + + public int getIciqlCode() { + return iciqlCode; + } + + private void configureCode(Throwable t) { + if (t == null) { + return; + } + if (t instanceof SQLException) { + // http://developer.mimer.com/documentation/html_92/Mimer_SQL_Mobile_DocSet/App_Return_Codes2.html + SQLException s = (SQLException) t; + String state = s.getSQLState(); + if ("23000".equals(state)) { + // MySQL duplicate primary key on insert + iciqlCode = CODE_DUPLICATE_KEY; + if (s.getErrorCode() == 1217) { + iciqlCode = CODE_CONSTRAINT_VIOLATION; + } + } else if ("23505".equals(state)) { + // Derby duplicate primary key on insert + iciqlCode = CODE_DUPLICATE_KEY; + } else if ("42000".equals(state)) { + // MySQL duplicate unique index value on insert + iciqlCode = CODE_DUPLICATE_KEY; + } else if ("42Y07".equals(state)) { + // Derby schema not found + iciqlCode = CODE_OBJECT_NOT_FOUND; + } else if ("42X05".equals(state)) { + // Derby table not found + iciqlCode = CODE_OBJECT_NOT_FOUND; + } else if ("42Y55".equals(state)) { + // Derby table not found + iciqlCode = CODE_OBJECT_NOT_FOUND; + } else if ("42S02".equals(state)) { + // H2 table not found + iciqlCode = CODE_OBJECT_NOT_FOUND; + } else if ("42501".equals(state)) { + // HSQL table not found + iciqlCode = CODE_OBJECT_NOT_FOUND; + } else if ("42P01".equals(state)) { + // PostgreSQL table not found + iciqlCode = CODE_OBJECT_NOT_FOUND; + } else if ("X0X05".equals(state)) { + // Derby view/table not found exists + iciqlCode = CODE_OBJECT_NOT_FOUND; + } else if ("X0Y32".equals(state)) { + // Derby table already exists + iciqlCode = CODE_OBJECT_ALREADY_EXISTS; + } else if ("42P07".equals(state)) { + // PostgreSQL table or index already exists + iciqlCode = CODE_OBJECT_ALREADY_EXISTS; + } else if ("42S01".equals(state)) { + // MySQL view already exists + iciqlCode = CODE_OBJECT_ALREADY_EXISTS; + } else if ("42S11".equals(state)) { + // H2 index already exists + iciqlCode = CODE_OBJECT_ALREADY_EXISTS; + } else if ("42504".equals(state)) { + // HSQL index already exists + iciqlCode = CODE_OBJECT_ALREADY_EXISTS; + } else if ("2BP01".equals(state)) { + // PostgreSQL constraint violation + iciqlCode = CODE_CONSTRAINT_VIOLATION; + } else if ("42533".equals(state)) { + // HSQL constraint violation + iciqlCode = CODE_CONSTRAINT_VIOLATION; + } else if ("X0Y25".equals(state)) { + // Derby constraint violation + iciqlCode = CODE_CONSTRAINT_VIOLATION; + } else { + // uncharacterized SQL code, we can always rely on iciqlCode != 0 in IciqlException + iciqlCode = s.getErrorCode() == 0 ? CODE_UNCHARACTERIZED : s.getErrorCode(); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getName()); + String message = getLocalizedMessage(); + if (message != null) { + sb.append(": ").append(message); + } + if (sql != null) { + sb.append('\n').append(sql); + } + return sb.toString(); + } +} diff --git a/src/main/java/com/iciql/ModelUtils.java b/src/main/java/com/iciql/ModelUtils.java new file mode 100644 index 0000000..56e6440 --- /dev/null +++ b/src/main/java/com/iciql/ModelUtils.java @@ -0,0 +1,499 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import static com.iciql.util.StringUtils.isNullOrEmpty; + +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.text.DateFormat; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Pattern; + +import com.iciql.TableDefinition.FieldDefinition; +import com.iciql.util.StringUtils; + +/** + * Utility methods for models related to type mapping, default value validation, + * and class or field name creation. + */ +class ModelUtils { + + /** + * The list of supported data types. It is used by the runtime mapping for + * CREATE statements. + */ + private static final Map, String> SUPPORTED_TYPES = new HashMap, String>(); + + static { + Map, String> m = SUPPORTED_TYPES; + m.put(String.class, "VARCHAR"); + m.put(Boolean.class, "BOOLEAN"); + m.put(Byte.class, "TINYINT"); + m.put(Short.class, "SMALLINT"); + m.put(Integer.class, "INT"); + m.put(Long.class, "BIGINT"); + m.put(Float.class, "REAL"); + m.put(Double.class, "DOUBLE"); + m.put(BigDecimal.class, "DECIMAL"); + m.put(java.sql.Timestamp.class, "TIMESTAMP"); + m.put(java.util.Date.class, "TIMESTAMP"); + m.put(java.sql.Date.class, "DATE"); + m.put(java.sql.Time.class, "TIME"); + m.put(byte[].class, "BLOB"); + m.put(UUID.class, "UUID"); + + // map primitives + m.put(boolean.class, m.get(Boolean.class)); + m.put(byte.class, m.get(Byte.class)); + m.put(short.class, m.get(Short.class)); + m.put(int.class, m.get(Integer.class)); + m.put(long.class, m.get(Long.class)); + m.put(float.class, m.get(Float.class)); + m.put(double.class, m.get(Double.class)); + } + + /** + * Convert SQL type aliases to the list of supported types. This map is used + * by generation and validation. + */ + private static final Map SQL_TYPES = new HashMap(); + + static { + Map m = SQL_TYPES; + m.put("CHAR", "VARCHAR"); + m.put("CHARACTER", "VARCHAR"); + m.put("NCHAR", "VARCHAR"); + m.put("VARCHAR_CASESENSITIVE", "VARCHAR"); + m.put("VARCHAR_IGNORECASE", "VARCHAR"); + m.put("LONGVARCHAR", "VARCHAR"); + m.put("VARCHAR2", "VARCHAR"); + m.put("NVARCHAR", "VARCHAR"); + m.put("NVARCHAR2", "VARCHAR"); + m.put("TEXT", "VARCHAR"); + m.put("NTEXT", "VARCHAR"); + m.put("TINYTEXT", "VARCHAR"); + m.put("MEDIUMTEXT", "VARCHAR"); + m.put("LONGTEXT", "VARCHAR"); + m.put("CLOB", "VARCHAR"); + m.put("NCLOB", "VARCHAR"); + + // logic + m.put("BIT", "BOOLEAN"); + m.put("BOOL", "BOOLEAN"); + + // numeric + m.put("BYTE", "TINYINT"); + m.put("INT2", "SMALLINT"); + m.put("YEAR", "SMALLINT"); + m.put("INTEGER", "INT"); + m.put("MEDIUMINT", "INT"); + m.put("INT4", "INT"); + m.put("SIGNED", "INT"); + m.put("INT8", "BIGINT"); + m.put("IDENTITY", "BIGINT"); + m.put("SERIAL", "INT"); + m.put("BIGSERIAL", "BIGINT"); + + // decimal + m.put("NUMBER", "DECIMAL"); + m.put("DEC", "DECIMAL"); + m.put("NUMERIC", "DECIMAL"); + m.put("FLOAT", "DOUBLE"); + m.put("FLOAT4", "DOUBLE"); + m.put("FLOAT8", "DOUBLE"); + m.put("DOUBLE PRECISION", "DOUBLE"); + + // date + m.put("DATETIME", "TIMESTAMP"); + m.put("SMALLDATETIME", "TIMESTAMP"); + + // binary types + m.put("TINYBLOB", "BLOB"); + m.put("MEDIUMBLOB", "BLOB"); + m.put("LONGBLOB", "BLOB"); + m.put("IMAGE", "BLOB"); + m.put("OID", "BLOB"); + } + + private static final List KEYWORDS = Arrays.asList("abstract", "assert", "boolean", "break", + "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", + "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", + "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", + "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", + "throw", "throws", "transient", "try", "void", "volatile", "while", "false", "null", "true"); + + /** + * Returns a SQL type mapping for a Java class. + * + * @param fieldDef + * the field to map + * @return + */ + static String getDataType(FieldDefinition fieldDef) { + Class fieldClass = fieldDef.field.getType(); + if (fieldClass.isEnum()) { + switch (fieldDef.enumType) { + case ORDINAL: + case ENUMID: + return "INT"; + case NAME: + default: + return "VARCHAR"; + } + } + if (SUPPORTED_TYPES.containsKey(fieldClass)) { + return SUPPORTED_TYPES.get(fieldClass); + } + throw new IciqlException("Unsupported type " + fieldClass.getName()); + } + + /** + * Returns the Java class for a given SQL type. + * + * @param sqlType + * @param dateTimeClass + * the preferred date class (java.util.Date or + * java.sql.Timestamp) + * @return + */ + static Class getClassForSqlType(String sqlType, Class dateTimeClass) { + sqlType = sqlType.toUpperCase(); + // XXX dropping "UNSIGNED" or parts like that could be trouble + sqlType = sqlType.split(" ")[0].trim(); + + if (SQL_TYPES.containsKey(sqlType)) { + // convert the sqlType to a standard type + sqlType = SQL_TYPES.get(sqlType); + } + Class mappedClass = null; + for (Class clazz : SUPPORTED_TYPES.keySet()) { + if (clazz.isPrimitive()) { + // do not map from SQL TYPE to primitive type + continue; + } + if (SUPPORTED_TYPES.get(clazz).equalsIgnoreCase(sqlType)) { + mappedClass = clazz; + + break; + } + } + if (mappedClass != null) { + if (mappedClass.equals(java.util.Date.class) || mappedClass.equals(java.sql.Timestamp.class)) { + return dateTimeClass; + } + return mappedClass; + } + return null; + } + + /** + * Tries to create a convert a SQL table name to a camel case class name. + * + * @param tableName + * the SQL table name + * @return the class name + */ + static String convertTableToClassName(String tableName) { + String[] chunks = StringUtils.arraySplit(tableName, '_', false); + StringBuilder className = new StringBuilder(); + for (String chunk : chunks) { + if (chunk.length() == 0) { + // leading or trailing _ + continue; + } + String[] subchunks = StringUtils.arraySplit(chunk, ' ', false); + for (String subchunk : subchunks) { + if (subchunk.length() == 0) { + // leading or trailing space + continue; + } + className.append(Character.toUpperCase(subchunk.charAt(0))); + className.append(subchunk.substring(1).toLowerCase()); + } + } + return className.toString(); + } + + /** + * Ensures that SQL column names don't collide with Java keywords. + * + * @param columnName + * the column name + * @return the Java field name + */ + static String convertColumnToFieldName(String columnName) { + String lower = columnName.toLowerCase(); + if (KEYWORDS.contains(lower)) { + lower += "Value"; + } + return lower; + } + + /** + * Converts a DEFAULT clause value into an object. + * + * @param field + * definition + * @return object + */ + static Object getDefaultValue(FieldDefinition def, Class dateTimeClass) { + Class valueType = getClassForSqlType(def.dataType, dateTimeClass); + if (String.class.isAssignableFrom(valueType)) { + if (StringUtils.isNullOrEmpty(def.defaultValue)) { + // literal default must be specified within single quotes + return null; + } + if (def.defaultValue.charAt(0) == '\'' + && def.defaultValue.charAt(def.defaultValue.length() - 1) == '\'') { + // strip leading and trailing single quotes + return def.defaultValue.substring(1, def.defaultValue.length() - 1).trim(); + } + return def.defaultValue; + } + + if (StringUtils.isNullOrEmpty(def.defaultValue)) { + // can not create object from empty string + return null; + } + + // strip leading and trailing single quotes + String content = def.defaultValue; + if (content.charAt(0) == '\'') { + content = content.substring(1); + } + if (content.charAt(content.length() - 1) == '\'') { + content = content.substring(0, content.length() - 2); + } + + if (StringUtils.isNullOrEmpty(content)) { + // can not create object from empty string + return null; + } + + if (Boolean.class.isAssignableFrom(valueType) || boolean.class.isAssignableFrom(valueType)) { + return Boolean.parseBoolean(content); + } + + if (Number.class.isAssignableFrom(valueType)) { + try { + // delegate to static valueOf() method to parse string + Method m = valueType.getMethod("valueOf", String.class); + return m.invoke(null, content); + } catch (NumberFormatException e) { + throw new IciqlException(e, "Failed to parse {0} as a number!", def.defaultValue); + } catch (Throwable t) { + } + } + + String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}"; + String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}"; + + if (java.sql.Date.class.isAssignableFrom(valueType)) { + // this may be a little loose.... + // 00-00-00 + // 00/00/00 + // 00.00.00 + Pattern pattern = Pattern.compile(dateRegex); + if (pattern.matcher(content).matches()) { + DateFormat df = DateFormat.getDateInstance(); + try { + return df.parse(content); + } catch (Exception e) { + throw new IciqlException(e, "Failed to parse {0} as a date!", def.defaultValue); + } + } + } + + if (java.sql.Time.class.isAssignableFrom(valueType)) { + // 00:00:00 + Pattern pattern = Pattern.compile(timeRegex); + if (pattern.matcher(content).matches()) { + DateFormat df = DateFormat.getTimeInstance(); + try { + return df.parse(content); + } catch (Exception e) { + throw new IciqlException(e, "Failed to parse {0} as a time!", def.defaultValue); + } + } + } + + if (java.util.Date.class.isAssignableFrom(valueType)) { + // this may be a little loose.... + // 00-00-00 00:00:00 + // 00/00/00T00:00:00 + // 00.00.00T00:00:00 + Pattern pattern = Pattern.compile(dateRegex + "." + timeRegex); + if (pattern.matcher(content).matches()) { + DateFormat df = DateFormat.getDateTimeInstance(); + try { + return df.parse(content); + } catch (Exception e) { + throw new IciqlException(e, "Failed to parse {0} as a datetimestamp!", def.defaultValue); + } + } + } + return content; + } + + /** + * Converts the object into a DEFAULT clause value. + * + * @param o + * the default object + * @return the value formatted for a DEFAULT clause + */ + static String formatDefaultValue(Object o) { + Class objectClass = o.getClass(); + String value = null; + if (Number.class.isAssignableFrom(objectClass)) { + // NUMBER + return ((Number) o).toString(); + } else if (Boolean.class.isAssignableFrom(objectClass)) { + // BOOLEAN + return o.toString(); + } else if (java.sql.Date.class.isAssignableFrom(objectClass)) { + // DATE + value = new SimpleDateFormat("yyyy-MM-dd").format((Date) o); + } else if (java.sql.Time.class.isAssignableFrom(objectClass)) { + // TIME + value = new SimpleDateFormat("HH:mm:ss").format((Date) o); + } else if (Date.class.isAssignableFrom(objectClass)) { + // DATETIME + value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) o); + } else if (String.class.isAssignableFrom(objectClass)) { + // STRING + value = o.toString(); + } + if (value == null) { + return "''"; + } + return MessageFormat.format("''{0}''", value); + } + + /** + * Checks the formatting of IQColumn.defaultValue(). + * + * @param defaultValue + * the default value + * @return true if it is + */ + static boolean isProperlyFormattedDefaultValue(String defaultValue) { + if (isNullOrEmpty(defaultValue)) { + return true; + } + Pattern literalDefault = Pattern.compile("'.*'"); + Pattern functionDefault = Pattern.compile("[^'].*[^']"); + return literalDefault.matcher(defaultValue).matches() + || functionDefault.matcher(defaultValue).matches(); + } + + /** + * Checks to see if the default value matches the class. + * + * @param modelClass + * the class + * @param defaultValue + * the value + * @return true if it does + */ + static boolean isValidDefaultValue(Class modelClass, String defaultValue) { + + if (defaultValue == null) { + // NULL + return true; + } + if (defaultValue.trim().length() == 0) { + // NULL (effectively) + return true; + } + + // function / variable + Pattern functionDefault = Pattern.compile("[^'].*[^']"); + if (functionDefault.matcher(defaultValue).matches()) { + // hard to validate this since its in the database + // assume it is good + return true; + } + + // STRING + if (modelClass == String.class) { + Pattern stringDefault = Pattern.compile("'(.|\\n)*'"); + return stringDefault.matcher(defaultValue).matches(); + } + + String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}"; + String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}"; + + // TIMESTAMP + if (modelClass == java.util.Date.class || modelClass == java.sql.Timestamp.class) { + // this may be a little loose.... + // 00-00-00 00:00:00 + // 00/00/00T00:00:00 + // 00.00.00T00:00:00 + Pattern pattern = Pattern.compile("'" + dateRegex + "." + timeRegex + "'"); + return pattern.matcher(defaultValue).matches(); + } + + // DATE + if (modelClass == java.sql.Date.class) { + // this may be a little loose.... + // 00-00-00 + // 00/00/00 + // 00.00.00 + Pattern pattern = Pattern.compile("'" + dateRegex + "'"); + return pattern.matcher(defaultValue).matches(); + } + + // TIME + if (modelClass == java.sql.Time.class) { + // 00:00:00 + Pattern pattern = Pattern.compile("'" + timeRegex + "'"); + return pattern.matcher(defaultValue).matches(); + } + + // NUMBER + if (Number.class.isAssignableFrom(modelClass)) { + // strip single quotes + String unquoted = defaultValue; + if (unquoted.charAt(0) == '\'') { + unquoted = unquoted.substring(1); + } + if (unquoted.charAt(unquoted.length() - 1) == '\'') { + unquoted = unquoted.substring(0, unquoted.length() - 1); + } + + try { + // delegate to static valueOf() method to parse string + Method m = modelClass.getMethod("valueOf", String.class); + m.invoke(null, unquoted); + } catch (NumberFormatException ex) { + return false; + } catch (Throwable t) { + } + } + return true; + } +} diff --git a/src/main/java/com/iciql/OrderExpression.java b/src/main/java/com/iciql/OrderExpression.java new file mode 100644 index 0000000..f450bfb --- /dev/null +++ b/src/main/java/com/iciql/OrderExpression.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * An expression to order by in a query. + * + * @param + * the query data type + */ + +class OrderExpression { + private Query query; + private Object expression; + private boolean desc; + private boolean nullsFirst; + private boolean nullsLast; + + OrderExpression(Query query, Object expression, boolean desc, boolean nullsFirst, boolean nullsLast) { + this.query = query; + this.expression = expression; + this.desc = desc; + this.nullsFirst = nullsFirst; + this.nullsLast = nullsLast; + } + + void appendSQL(SQLStatement stat) { + query.appendSQL(stat, null, expression); + if (desc) { + stat.appendSQL(" DESC"); + } + if (nullsLast) { + stat.appendSQL(" NULLS LAST"); + } + if (nullsFirst) { + stat.appendSQL(" NULLS FIRST"); + } + } + +} diff --git a/src/main/java/com/iciql/Query.java b/src/main/java/com/iciql/Query.java new file mode 100644 index 0000000..5dc78a5 --- /dev/null +++ b/src/main/java/com/iciql/Query.java @@ -0,0 +1,947 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import java.lang.reflect.Field; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; + +import com.iciql.Iciql.EnumType; +import com.iciql.bytecode.ClassReader; +import com.iciql.util.JdbcUtils; +import com.iciql.util.IciqlLogger; +import com.iciql.util.Utils; + +/** + * This class represents a query. + * + * @param + * the return type + */ + +public class Query { + + private Db db; + private SelectTable from; + private ArrayList conditions = Utils.newArrayList(); + private ArrayList updateColumnDeclarations = Utils.newArrayList(); + private ArrayList> joins = Utils.newArrayList(); + private final IdentityHashMap> aliasMap = Utils.newIdentityHashMap(); + private ArrayList> orderByList = Utils.newArrayList(); + private ArrayList groupByExpressions = Utils.newArrayList(); + private long limit; + private long offset; + + private Query(Db db) { + this.db = db; + } + + /** + * from() is a static factory method to build a Query object. + * + * @param db + * @param alias + * @return a query object + */ + @SuppressWarnings("unchecked") + static Query from(Db db, T alias) { + Query query = new Query(db); + TableDefinition def = (TableDefinition) db.define(alias.getClass()); + query.from = new SelectTable(db, query, alias, false); + def.initSelectObject(query.from, alias, query.aliasMap); + return query; + } + + public long selectCount() { + SQLStatement stat = getSelectStatement(false); + stat.appendSQL("COUNT(*) "); + appendFromWhere(stat); + ResultSet rs = stat.executeQuery(); + try { + rs.next(); + long value = rs.getLong(1); + return value; + } catch (SQLException e) { + throw IciqlException.fromSQL(stat.getSQL(), e); + } finally { + JdbcUtils.closeSilently(rs, true); + } + } + + public List select() { + return select(false); + } + + public T selectFirst() { + return select(false).get(0); + } + + public List selectDistinct() { + return select(true); + } + + @SuppressWarnings("unchecked") + public X selectFirst(Z x) { + List list = (List) select(x); + return list.isEmpty() ? null : list.get(0); + } + + public void createView(Class viewClass) { + TableDefinition viewDef = db.define(viewClass); + + SQLStatement fromWhere = new SQLStatement(db); + appendFromWhere(fromWhere, false); + + SQLStatement stat = new SQLStatement(db); + db.getDialect().prepareCreateView(stat, viewDef, fromWhere.toSQL()); + IciqlLogger.create(stat.toSQL()); + stat.execute(); + } + + public void replaceView(Class viewClass) { + db.dropView(viewClass); + createView(viewClass); + } + + public String getSQL() { + SQLStatement stat = getSelectStatement(false); + stat.appendSQL("*"); + appendFromWhere(stat); + return stat.getSQL().trim(); + } + + /** + * toSQL returns a static string version of the query with runtime variables + * properly encoded. This method is also useful when combined with the where + * clause methods like isParameter() or atLeastParameter() which allows + * iciql to generate re-usable parameterized string statements. + * + * @return the sql query as plain text + */ + public String toSQL() { + return toSQL(false); + } + + /** + * toSQL returns a static string version of the query with runtime variables + * properly encoded. This method is also useful when combined with the where + * clause methods like isParameter() or atLeastParameter() which allows + * iciql to generate re-usable parameterized string statements. + * + * @param distinct + * if true SELECT DISTINCT is used for the query + * @return the sql query as plain text + */ + public String toSQL(boolean distinct) { + return toSQL(distinct, null); + } + + /** + * toSQL returns a static string version of the query with runtime variables + * properly encoded. This method is also useful when combined with the where + * clause methods like isParameter() or atLeastParameter() which allows + * iciql to generate re-usable parameterized string statements. + * + * @param distinct + * if true SELECT DISTINCT is used for the query + * @param k + * k is used to select only the columns of the specified alias + * for an inner join statement. An example of a generated + * statement is: SELECT DISTINCT t1.* FROM sometable AS t1 INNER + * JOIN othertable AS t2 ON t1.id = t2.id WHERE t2.flag = true + * without the alias parameter the statement would start with + * SELECT DISTINCT * FROM... + * @return the sql query as plain text + */ + public String toSQL(boolean distinct, K k) { + SQLStatement stat = new SQLStatement(getDb()); + if (updateColumnDeclarations.size() > 0) { + stat.appendSQL("UPDATE "); + from.appendSQL(stat); + stat.appendSQL(" SET "); + int i = 0; + for (UpdateColumn declaration : updateColumnDeclarations) { + if (i++ > 0) { + stat.appendSQL(", "); + } + declaration.appendSQL(stat); + } + appendWhere(stat); + } else { + stat.appendSQL("SELECT "); + if (distinct) { + stat.appendSQL("DISTINCT "); + } + if (k != null) { + SelectTable sel = getSelectTable(k); + if (sel == null) { + // unknown alias, use wildcard + IciqlLogger.warn("Alias {0} is not defined in the statement!", k.getClass()); + stat.appendSQL("*"); + } else if (isJoin()) { + // join query, use AS alias + String as = sel.getAs(); + stat.appendSQL(as + ".*"); + } else { + // schema.table.* + String schema = sel.getAliasDefinition().schemaName; + String table = sel.getAliasDefinition().tableName; + String as = getDb().getDialect().prepareTableName(schema, table); + stat.appendSQL(as + ".*"); + } + } else { + // alias unspecified, use wildcard + stat.appendSQL("*"); + } + appendFromWhere(stat); + } + return stat.toSQL().trim(); + } + + String toSubQuery(Z z) { + SQLStatement stat = getSelectStatement(false); + SelectColumn col = aliasMap.get(z); + String columnName = col.getFieldDefinition().columnName; + stat.appendColumn(columnName); + appendFromWhere(stat); + return stat.toSQL(); + } + + private List select(boolean distinct) { + List result = Utils.newArrayList(); + TableDefinition def = from.getAliasDefinition(); + SQLStatement stat = getSelectStatement(distinct); + def.appendSelectList(stat); + appendFromWhere(stat); + ResultSet rs = stat.executeQuery(); + try { + int[] columns = def.mapColumns(false, rs); + while (rs.next()) { + T item = from.newObject(); + def.readRow(item, rs, columns); + result.add(item); + } + } catch (SQLException e) { + throw IciqlException.fromSQL(stat.getSQL(), e); + } finally { + JdbcUtils.closeSilently(rs, true); + } + return result; + } + + public int delete() { + SQLStatement stat = new SQLStatement(db); + stat.appendSQL("DELETE FROM "); + from.appendSQL(stat); + appendWhere(stat); + IciqlLogger.delete(stat.getSQL()); + return stat.executeUpdate(); + } + + public UpdateColumnSet set(A field) { + from.getAliasDefinition().checkMultipleEnums(field); + return new UpdateColumnSet(this, field); + } + + public UpdateColumnSet set(boolean field) { + from.getAliasDefinition().checkMultipleBooleans(); + return setPrimitive(field); + } + + public UpdateColumnSet set(byte field) { + return setPrimitive(field); + } + + public UpdateColumnSet set(short field) { + return setPrimitive(field); + } + + public UpdateColumnSet set(int field) { + return setPrimitive(field); + } + + public UpdateColumnSet set(long field) { + return setPrimitive(field); + } + + public UpdateColumnSet set(float field) { + return setPrimitive(field); + } + + public UpdateColumnSet set(double field) { + return setPrimitive(field); + } + + private UpdateColumnSet setPrimitive(A field) { + A alias = getPrimitiveAliasByValue(field); + if (alias == null) { + // this will result in an unmapped field exception + return set(field); + } + return set(alias); + } + + public UpdateColumnIncrement increment(A field) { + return new UpdateColumnIncrement(this, field); + } + + public UpdateColumnIncrement increment(byte field) { + return incrementPrimitive(field); + } + + public UpdateColumnIncrement increment(short field) { + return incrementPrimitive(field); + } + + public UpdateColumnIncrement increment(int field) { + return incrementPrimitive(field); + } + + public UpdateColumnIncrement increment(long field) { + return incrementPrimitive(field); + } + + public UpdateColumnIncrement increment(float field) { + return incrementPrimitive(field); + } + + public UpdateColumnIncrement increment(double field) { + return incrementPrimitive(field); + } + + private UpdateColumnIncrement incrementPrimitive(A field) { + A alias = getPrimitiveAliasByValue(field); + if (alias == null) { + // this will result in an unmapped field exception + return increment(field); + } + return increment(alias); + } + + public int update() { + if (updateColumnDeclarations.size() == 0) { + throw new IciqlException("Missing set or increment call."); + } + SQLStatement stat = new SQLStatement(db); + stat.appendSQL("UPDATE "); + from.appendSQL(stat); + stat.appendSQL(" SET "); + int i = 0; + for (UpdateColumn declaration : updateColumnDeclarations) { + if (i++ > 0) { + stat.appendSQL(", "); + } + declaration.appendSQL(stat); + } + appendWhere(stat); + IciqlLogger.update(stat.getSQL()); + return stat.executeUpdate(); + } + + public List selectDistinct(Z x) { + return select(x, true); + } + + public List select(Z x) { + return select(x, false); + } + + @SuppressWarnings("unchecked") + private List select(Z x, boolean distinct) { + Class clazz = x.getClass(); + if (Utils.isSimpleType(clazz)) { + return selectSimple((X) x, distinct); + } + Class enclosingClass = clazz.getEnclosingClass(); + if (enclosingClass != null) { + // anonymous inner class + clazz = clazz.getSuperclass(); + } + return select((Class) clazz, (X) x, distinct); + } + + private List select(Class clazz, X x, boolean distinct) { + List result = Utils.newArrayList(); + TableDefinition def = db.define(clazz); + SQLStatement stat = getSelectStatement(distinct); + def.appendSelectList(stat, this, x); + appendFromWhere(stat); + ResultSet rs = stat.executeQuery(); + try { + int[] columns = def.mapColumns(false, rs); + while (rs.next()) { + X row = Utils.newObject(clazz); + def.readRow(row, rs, columns); + result.add(row); + } + } catch (SQLException e) { + throw IciqlException.fromSQL(stat.getSQL(), e); + } finally { + JdbcUtils.closeSilently(rs, true); + } + return result; + } + + @SuppressWarnings("unchecked") + private List selectSimple(X x, boolean distinct) { + SQLStatement stat = getSelectStatement(distinct); + appendSQL(stat, null, x); + appendFromWhere(stat); + ResultSet rs = stat.executeQuery(); + List result = Utils.newArrayList(); + try { + while (rs.next()) { + X value; + Object o = rs.getObject(1); + // Convert CLOB and BLOB now because we close the resultset + if (Clob.class.isAssignableFrom(o.getClass())) { + value = (X) Utils.convert(o, String.class); + } else if (Blob.class.isAssignableFrom(o.getClass())) { + value = (X) Utils.convert(o, byte[].class); + } else { + value = (X) o; + } + result.add(value); + } + } catch (Exception e) { + throw IciqlException.fromSQL(stat.getSQL(), e); + } finally { + JdbcUtils.closeSilently(rs, true); + } + return result; + } + + private SQLStatement getSelectStatement(boolean distinct) { + SQLStatement stat = new SQLStatement(db); + stat.appendSQL("SELECT "); + if (distinct) { + stat.appendSQL("DISTINCT "); + } + return stat; + } + + /** + * Begin a primitive boolean field condition clause. + * + * @param x + * the primitive boolean field to query + * @return a query condition to continue building the condition + */ + public QueryCondition where(boolean x) { + from.getAliasDefinition().checkMultipleBooleans(); + return wherePrimitive(x); + } + + /** + * Begin a primitive short field condition clause. + * + * @param x + * the primitive short field to query + * @return a query condition to continue building the condition + */ + public QueryCondition where(byte x) { + return wherePrimitive(x); + } + + /** + * Begin a primitive short field condition clause. + * + * @param x + * the primitive short field to query + * @return a query condition to continue building the condition + */ + public QueryCondition where(short x) { + return wherePrimitive(x); + } + + /** + * Begin a primitive int field condition clause. + * + * @param x + * the primitive int field to query + * @return a query condition to continue building the condition + */ + public QueryCondition where(int x) { + return wherePrimitive(x); + } + + /** + * Begin a primitive long field condition clause. + * + * @param x + * the primitive long field to query + * @return a query condition to continue building the condition + */ + public QueryCondition where(long x) { + return wherePrimitive(x); + } + + /** + * Begin a primitive float field condition clause. + * + * @param x + * the primitive float field to query + * @return a query condition to continue building the condition + */ + public QueryCondition where(float x) { + return wherePrimitive(x); + } + + /** + * Begin a primitive double field condition clause. + * + * @param x + * the primitive double field to query + * @return a query condition to continue building the condition + */ + public QueryCondition where(double x) { + return wherePrimitive(x); + } + + /** + * Begins a primitive field condition clause. + * + * @param value + * @return a query condition to continue building the condition + */ + private QueryCondition wherePrimitive(A value) { + A alias = getPrimitiveAliasByValue(value); + if (alias == null) { + // this will result in an unmapped field exception + return where(value); + } + return where(alias); + } + + /** + * Begin an Object field condition clause. + * + * @param x + * the mapped object to query + * @return a query condition to continue building the condition + */ + public QueryCondition where(A x) { + from.getAliasDefinition().checkMultipleEnums(x); + return new QueryCondition(this, x); + } + + public QueryWhere where(Filter filter) { + HashMap fieldMap = Utils.newHashMap(); + for (Field f : filter.getClass().getDeclaredFields()) { + f.setAccessible(true); + try { + Object obj = f.get(filter); + if (obj == from.getAlias()) { + List fields = from.getAliasDefinition().getFields(); + String name = f.getName(); + for (TableDefinition.FieldDefinition field : fields) { + String n = name + "." + field.field.getName(); + Object o = field.field.get(obj); + fieldMap.put(n, o); + } + } + fieldMap.put(f.getName(), f.get(filter)); + } catch (Exception e) { + throw new IciqlException(e); + } + } + Token filterCode = new ClassReader().decompile(filter, fieldMap, "where"); + // String filterQuery = filterCode.toString(); + conditions.add(filterCode); + return new QueryWhere(this); + } + + public QueryWhere where(String fragment, List args) { + return this.where(fragment, args.toArray()); + } + + public QueryWhere where(String fragment, Object... args) { + conditions.add(new RuntimeToken(fragment, args)); + return new QueryWhere(this); + } + + public QueryWhere whereTrue(Boolean condition) { + Token token = new Function("", condition); + addConditionToken(token); + return new QueryWhere(this); + } + + /** + * Sets the Limit and Offset of a query. + * + * @return the query + */ + + public Query limit(long limit) { + this.limit = limit; + return this; + } + + public Query offset(long offset) { + this.offset = offset; + return this; + } + + public Query orderBy(boolean field) { + from.getAliasDefinition().checkMultipleBooleans(); + return orderByPrimitive(field); + } + + public Query orderBy(byte field) { + return orderByPrimitive(field); + } + + public Query orderBy(short field) { + return orderByPrimitive(field); + } + + public Query orderBy(int field) { + return orderByPrimitive(field); + } + + public Query orderBy(long field) { + return orderByPrimitive(field); + } + + public Query orderBy(float field) { + return orderByPrimitive(field); + } + + public Query orderBy(double field) { + return orderByPrimitive(field); + } + + Query orderByPrimitive(Object field) { + Object alias = getPrimitiveAliasByValue(field); + if (alias == null) { + return orderBy(field); + } + return orderBy(alias); + } + + public Query orderBy(Object expr) { + from.getAliasDefinition().checkMultipleEnums(expr); + OrderExpression e = new OrderExpression(this, expr, false, false, false); + addOrderBy(e); + return this; + } + + /** + * Order by a number of columns. + * + * @param expressions + * the columns + * @return the query + */ + + public Query orderBy(Object... expressions) { + for (Object expr : expressions) { + from.getAliasDefinition().checkMultipleEnums(expr); + OrderExpression e = new OrderExpression(this, expr, false, false, false); + addOrderBy(e); + } + return this; + } + + public Query orderByDesc(Object expr) { + OrderExpression e = new OrderExpression(this, expr, true, false, false); + addOrderBy(e); + return this; + } + + public Query groupBy(boolean field) { + from.getAliasDefinition().checkMultipleBooleans(); + return groupByPrimitive(field); + } + + public Query groupBy(byte field) { + return groupByPrimitive(field); + } + + public Query groupBy(short field) { + return groupByPrimitive(field); + } + + public Query groupBy(int field) { + return groupByPrimitive(field); + } + + public Query groupBy(long field) { + return groupByPrimitive(field); + } + + public Query groupBy(float field) { + return groupByPrimitive(field); + } + + public Query groupBy(double field) { + return groupByPrimitive(field); + } + + Query groupByPrimitive(Object field) { + Object alias = getPrimitiveAliasByValue(field); + if (alias == null) { + return groupBy(field); + } + return groupBy(alias); + } + + public Query groupBy(Object expr) { + from.getAliasDefinition().checkMultipleEnums(expr); + groupByExpressions.add(expr); + return this; + } + + public Query groupBy(Object... groupBy) { + this.groupByExpressions.addAll(Arrays.asList(groupBy)); + return this; + } + + /** + * INTERNAL + * + * @param stat + * the statement + * @param alias + * the alias object (can be null) + * @param value + * the value + */ + public void appendSQL(SQLStatement stat, Object alias, Object value) { + if (Function.count() == value) { + stat.appendSQL("COUNT(*)"); + return; + } + if (RuntimeParameter.PARAMETER == value) { + stat.appendSQL("?"); + addParameter(stat, alias, value); + return; + } + Token token = Db.getToken(value); + if (token != null) { + token.appendSQL(stat, this); + return; + } + if (alias != null && value.getClass().isEnum()) { + // special case: + // value is first enum constant which is also the alias object. + // the first enum constant is used as the alias because we can not + // instantiate an enum reflectively. + stat.appendSQL("?"); + addParameter(stat, alias, value); + return; + } + SelectColumn col = getColumnByReference(value); + if (col != null) { + col.appendSQL(stat); + return; + } + stat.appendSQL("?"); + addParameter(stat, alias, value); + } + + /** + * INTERNAL + * + * @param stat + * the statement + * @param alias + * the alias object (can be null) + * @param valueLeft + * the value on the left of the compound clause + * @param valueRight + * the value on the right of the compound clause + * @param compareType + * the current compare type (e.g. BETWEEN) + */ + public void appendSQL(SQLStatement stat, Object alias, Object valueLeft, Object valueRight, + CompareType compareType) { + stat.appendSQL("?"); + stat.appendSQL(" "); + switch (compareType) { + case BETWEEN: + stat.appendSQL("AND"); + break; + } + stat.appendSQL(" "); + stat.appendSQL("?"); + addParameter(stat, alias, valueLeft); + addParameter(stat, alias, valueRight); + } + + private void addParameter(SQLStatement stat, Object alias, Object value) { + if (alias != null && value.getClass().isEnum()) { + SelectColumn col = getColumnByReference(alias); + EnumType type = col.getFieldDefinition().enumType; + Enum anEnum = (Enum) value; + Object y = Utils.convertEnum(anEnum, type); + stat.addParameter(y); + } else { + stat.addParameter(value); + } + } + + void addConditionToken(Token condition) { + conditions.add(condition); + } + + void addUpdateColumnDeclaration(UpdateColumn declaration) { + updateColumnDeclarations.add(declaration); + } + + void appendWhere(SQLStatement stat) { + if (!conditions.isEmpty()) { + stat.appendSQL(" WHERE "); + for (Token token : conditions) { + token.appendSQL(stat, this); + stat.appendSQL(" "); + } + } + } + + void appendFromWhere(SQLStatement stat) { + appendFromWhere(stat, true); + } + + void appendFromWhere(SQLStatement stat, boolean log) { + stat.appendSQL(" FROM "); + from.appendSQL(stat); + for (SelectTable join : joins) { + join.appendSQLAsJoin(stat, this); + } + appendWhere(stat); + if (!groupByExpressions.isEmpty()) { + stat.appendSQL(" GROUP BY "); + int i = 0; + for (Object obj : groupByExpressions) { + if (i++ > 0) { + stat.appendSQL(", "); + } + appendSQL(stat, null, obj); + stat.appendSQL(" "); + } + } + if (!orderByList.isEmpty()) { + stat.appendSQL(" ORDER BY "); + int i = 0; + for (OrderExpression o : orderByList) { + if (i++ > 0) { + stat.appendSQL(", "); + } + o.appendSQL(stat); + stat.appendSQL(" "); + } + } + db.getDialect().appendLimitOffset(stat, limit, offset); + if (log) { + IciqlLogger.select(stat.getSQL()); + } + } + + /** + * Join another table. + * + * @param alias + * an alias for the table to join + * @return the joined query + */ + + public QueryJoin innerJoin(A alias) { + return join(alias, false); + } + + public QueryJoin leftJoin(A alias) { + return join(alias, true); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private QueryJoin join(A alias, boolean outerJoin) { + TableDefinition def = (TableDefinition) db.define(alias.getClass()); + SelectTable join = new SelectTable(db, this, alias, outerJoin); + def.initSelectObject(join, alias, aliasMap); + joins.add(join); + return new QueryJoin(this, join); + } + + Db getDb() { + return db; + } + + SelectTable getFrom() { + return from; + } + + boolean isJoin() { + return !joins.isEmpty(); + } + + SelectTable getSelectTable(Object alias) { + if (from.getAlias() == alias) { + return from; + } else { + for (SelectTable join : joins) { + if (join.getAlias() == alias) { + return join; + } + } + } + return null; + } + + /** + * This method returns a mapped Object field by its reference. + * + * @param obj + * @return + */ + private SelectColumn getColumnByReference(Object obj) { + SelectColumn col = aliasMap.get(obj); + return col; + } + + /** + * This method returns the alias of a mapped primitive field by its value. + * + * @param obj + * @return + */ + @SuppressWarnings("unchecked") + A getPrimitiveAliasByValue(A obj) { + for (Object alias : aliasMap.keySet()) { + if (alias.equals(obj)) { + SelectColumn match = aliasMap.get(alias); + if (match.getFieldDefinition().isPrimitive) { + return (A) alias; + } + } + } + return null; + } + + void addOrderBy(OrderExpression expr) { + orderByList.add(expr); + } + +} diff --git a/src/main/java/com/iciql/QueryBetween.java b/src/main/java/com/iciql/QueryBetween.java new file mode 100644 index 0000000..72d19dc --- /dev/null +++ b/src/main/java/com/iciql/QueryBetween.java @@ -0,0 +1,60 @@ +/* + * Copyright 2011 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; + +/** + * This class represents a "between y and z" condition. + * + * @param + * the return type of the query + * @param + * the incomplete condition data type + */ +public class QueryBetween { + + private Query query; + private A x; + private A y; + + /** + * Construct a between condition. + * + * @param query + * the query + * @param x + * the alias + * @param y + * the lower bound of the between condition + */ + public QueryBetween(Query query, A x, A y) { + this.query = query; + this.x = x; + this.y = y; + } + + /** + * Set the upper bound of the between condition. + * + * @param z + * the upper bound of the between condition + * @return the query + */ + public QueryWhere and(A z) { + query.addConditionToken(new Condition(x, y, z, CompareType.BETWEEN)); + return new QueryWhere(query); + } +} diff --git a/src/main/java/com/iciql/QueryCondition.java b/src/main/java/com/iciql/QueryCondition.java new file mode 100644 index 0000000..9613b1b --- /dev/null +++ b/src/main/java/com/iciql/QueryCondition.java @@ -0,0 +1,128 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * This class represents a query with an incomplete condition. + * + * @param + * the return type of the query + * @param + * the incomplete condition data type + */ + +public class QueryCondition { + + private Query query; + private A x; + + QueryCondition(Query query, A x) { + this.query = query; + this.x = x; + } + + public QueryWhere in(SubQuery q) { + query.addConditionToken(new SubQueryCondition(x, q)); + return new QueryWhere(query); + } + + public QueryWhere is(A y) { + query.addConditionToken(new Condition(x, y, CompareType.EQUAL)); + return new QueryWhere(query); + } + + public QueryWhere isNot(A y) { + query.addConditionToken(new Condition(x, y, CompareType.NOT_EQUAL)); + return new QueryWhere(query); + } + + public QueryWhere isNull() { + query.addConditionToken(new Condition(x, null, CompareType.IS_NULL)); + return new QueryWhere(query); + } + + public QueryWhere isNotNull() { + query.addConditionToken(new Condition(x, null, CompareType.IS_NOT_NULL)); + return new QueryWhere(query); + } + + public QueryWhere exceeds(A y) { + query.addConditionToken(new Condition(x, y, CompareType.EXCEEDS)); + return new QueryWhere(query); + } + + public QueryWhere atLeast(A y) { + query.addConditionToken(new Condition(x, y, CompareType.AT_LEAST)); + return new QueryWhere(query); + } + + public QueryWhere lessThan(A y) { + query.addConditionToken(new Condition(x, y, CompareType.LESS_THAN)); + return new QueryWhere(query); + } + + public QueryWhere atMost(A y) { + query.addConditionToken(new Condition(x, y, CompareType.AT_MOST)); + return new QueryWhere(query); + } + + public QueryBetween between(A y) { + return new QueryBetween(query, x, y); + } + + public QueryWhere like(A pattern) { + query.addConditionToken(new Condition(x, pattern, CompareType.LIKE)); + return new QueryWhere(query); + } + + /* + * These method allows you to generate "x=?", "x!=?", etc where conditions. + * Parameter substitution must be done manually later with db.executeQuery. + * This allows for building re-usable SQL string statements from your model + * classes. + */ + public QueryWhere isParameter() { + query.addConditionToken(new RuntimeParameter(x, CompareType.EQUAL)); + return new QueryWhere(query); + } + + public QueryWhere isNotParameter() { + query.addConditionToken(new RuntimeParameter(x, CompareType.NOT_EQUAL)); + return new QueryWhere(query); + } + + public QueryWhere exceedsParameter() { + query.addConditionToken(new RuntimeParameter(x, CompareType.EXCEEDS)); + return new QueryWhere(query); + } + + public QueryWhere lessThanParameter() { + query.addConditionToken(new RuntimeParameter(x, CompareType.LESS_THAN)); + return new QueryWhere(query); + } + + public QueryWhere atMostParameter() { + query.addConditionToken(new RuntimeParameter(x, CompareType.AT_MOST)); + return new QueryWhere(query); + } + + public QueryWhere likeParameter() { + query.addConditionToken(new RuntimeParameter(x, CompareType.LIKE)); + return new QueryWhere(query); + } +} diff --git a/src/main/java/com/iciql/QueryJoin.java b/src/main/java/com/iciql/QueryJoin.java new file mode 100644 index 0000000..6d0484e --- /dev/null +++ b/src/main/java/com/iciql/QueryJoin.java @@ -0,0 +1,75 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * This class represents a query with a join. + */ + +public class QueryJoin { + + private Query query; + private SelectTable join; + + QueryJoin(Query query, SelectTable join) { + this.query = query; + this.join = join; + } + + public QueryJoinCondition on(boolean x) { + query.getFrom().getAliasDefinition().checkMultipleBooleans(); + return addPrimitive(x); + } + + public QueryJoinCondition on(byte x) { + return addPrimitive(x); + } + + public QueryJoinCondition on(short x) { + return addPrimitive(x); + } + + public QueryJoinCondition on(int x) { + return addPrimitive(x); + } + + public QueryJoinCondition on(long x) { + return addPrimitive(x); + } + + public QueryJoinCondition on(float x) { + return addPrimitive(x); + } + + public QueryJoinCondition on(double x) { + return addPrimitive(x); + } + + private QueryJoinCondition addPrimitive(A x) { + A alias = query.getPrimitiveAliasByValue(x); + if (alias == null) { + // this will result in an unmapped field exception + return new QueryJoinCondition(query, join, x); + } + return new QueryJoinCondition(query, join, alias); + } + + public QueryJoinCondition on(A x) { + return new QueryJoinCondition(query, join, x); + } +} diff --git a/src/main/java/com/iciql/QueryJoinCondition.java b/src/main/java/com/iciql/QueryJoinCondition.java new file mode 100644 index 0000000..6dfd218 --- /dev/null +++ b/src/main/java/com/iciql/QueryJoinCondition.java @@ -0,0 +1,83 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + + +/** + * This class represents a query with join and an incomplete condition. + * + * @param + * the incomplete condition data type + */ + +public class QueryJoinCondition { + + private Query query; + private SelectTable join; + private A x; + + QueryJoinCondition(Query query, SelectTable join, A x) { + this.query = query; + this.join = join; + this.x = x; + } + + public Query is(boolean y) { + return addPrimitive(y); + } + + public Query is(byte y) { + return addPrimitive(y); + } + + public Query is(short y) { + return addPrimitive(y); + } + + public Query is(int y) { + return addPrimitive(y); + } + + public Query is(long y) { + return addPrimitive(y); + } + + public Query is(float y) { + return addPrimitive(y); + } + + public Query is(double y) { + return addPrimitive(y); + } + + @SuppressWarnings("unchecked") + private Query addPrimitive(Object o) { + A alias = query.getPrimitiveAliasByValue((A) o); + if (alias == null) { + join.addConditionToken(new Condition(x, (A) o, CompareType.EQUAL)); + } else { + join.addConditionToken(new Condition(x, alias, CompareType.EQUAL)); + } + return query; + } + + public Query is(A y) { + join.addConditionToken(new Condition(x, y, CompareType.EQUAL)); + return query; + } +} diff --git a/src/main/java/com/iciql/QueryWhere.java b/src/main/java/com/iciql/QueryWhere.java new file mode 100644 index 0000000..5baa5ab --- /dev/null +++ b/src/main/java/com/iciql/QueryWhere.java @@ -0,0 +1,501 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import java.util.List; + +/** + * This class represents a query with a condition. + * + * @param + * the return type + */ + +public class QueryWhere { + + Query query; + + QueryWhere(Query query) { + this.query = query; + } + + /** + * Specify an AND condition with a mapped primitive boolean. + * + * @param x + * the primitive boolean field to query + * @return a query condition to continue building the condition + */ + public QueryCondition and(boolean x) { + query.getFrom().getAliasDefinition().checkMultipleBooleans(); + return addPrimitive(ConditionAndOr.AND, x); + } + + /** + * Specify an AND condition with a mapped primitive byte. + * + * @param x + * the primitive byte field to query + * @return a query condition to continue building the condition + */ + public QueryCondition and(byte x) { + return addPrimitive(ConditionAndOr.AND, x); + } + + /** + * Specify an AND condition with a mapped primitive short. + * + * @param x + * the primitive short field to query + * @return a query condition to continue building the condition + */ + public QueryCondition and(short x) { + return addPrimitive(ConditionAndOr.AND, x); + } + + /** + * Specify an AND condition with a mapped primitive int. + * + * @param x + * the primitive int field to query + * @return a query condition to continue building the condition + */ + public QueryCondition and(int x) { + return addPrimitive(ConditionAndOr.AND, x); + } + + /** + * Specify an AND condition with a mapped primitive long. + * + * @param x + * the primitive long field to query + * @return a query condition to continue building the condition + */ + public QueryCondition and(long x) { + return addPrimitive(ConditionAndOr.AND, x); + } + + /** + * Specify an AND condition with a mapped primitive float. + * + * @param x + * the primitive float field to query + * @return a query condition to continue building the condition + */ + public QueryCondition and(float x) { + return addPrimitive(ConditionAndOr.AND, x); + } + + /** + * Specify an AND condition with a mapped primitive double. + * + * @param x + * the primitive double field to query + * @return a query condition to continue building the condition + */ + public QueryCondition and(double x) { + return addPrimitive(ConditionAndOr.AND, x); + } + + private QueryCondition addPrimitive(ConditionAndOr condition, A x) { + query.addConditionToken(condition); + A alias = query.getPrimitiveAliasByValue(x); + if (alias == null) { + // this will result in an unmapped field exception + return new QueryCondition(query, x); + } + return new QueryCondition(query, alias); + } + + /** + * Specify an AND condition with a mapped Object field. + * + * @param x + * the Object field to query + * @return a query condition to continue building the condition + */ + public QueryCondition and(A x) { + query.getFrom().getAliasDefinition().checkMultipleEnums(x); + query.addConditionToken(ConditionAndOr.AND); + return new QueryCondition(query, x); + } + + /** + * Specify an OR condition with a mapped primitive boolean. + * + * @param x + * the primitive boolean field to query + * @return a query condition to continue building the condition + */ + public QueryCondition or(boolean x) { + query.getFrom().getAliasDefinition().checkMultipleBooleans(); + return addPrimitive(ConditionAndOr.OR, x); + } + + /** + * Specify an OR condition with a mapped primitive byte. + * + * @param x + * the primitive byte field to query + * @return a query condition to continue building the condition + */ + public QueryCondition or(byte x) { + return addPrimitive(ConditionAndOr.OR, x); + } + + /** + * Specify an OR condition with a mapped primitive short. + * + * @param x + * the primitive short field to query + * @return a query condition to continue building the condition + */ + public QueryCondition or(short x) { + return addPrimitive(ConditionAndOr.OR, x); + } + + /** + * Specify an OR condition with a mapped primitive int. + * + * @param x + * the primitive int field to query + * @return a query condition to continue building the condition + */ + public QueryCondition or(int x) { + return addPrimitive(ConditionAndOr.OR, x); + } + + /** + * Specify an OR condition with a mapped primitive long. + * + * @param x + * the primitive long field to query + * @return a query condition to continue building the condition + */ + public QueryCondition or(long x) { + return addPrimitive(ConditionAndOr.OR, x); + } + + /** + * Specify an OR condition with a mapped primitive float. + * + * @param x + * the primitive float field to query + * @return a query condition to continue building the condition + */ + public QueryCondition or(float x) { + return addPrimitive(ConditionAndOr.OR, x); + } + + /** + * Specify an OR condition with a mapped primitive double. + * + * @param x + * the primitive double field to query + * @return a query condition to continue building the condition + */ + public QueryCondition or(double x) { + return addPrimitive(ConditionAndOr.OR, x); + } + + /** + * Specify an OR condition with a mapped Object field. + * + * @param x + * the Object field to query + * @return a query condition to continue building the condition + */ + public QueryCondition or(A x) { + query.getFrom().getAliasDefinition().checkMultipleEnums(x); + query.addConditionToken(ConditionAndOr.OR); + return new QueryCondition(query, x); + } + + public QueryWhere limit(long limit) { + query.limit(limit); + return this; + } + + public QueryWhere offset(long offset) { + query.offset(offset); + return this; + } + + public String getSQL() { + SQLStatement stat = new SQLStatement(query.getDb()); + stat.appendSQL("SELECT *"); + query.appendFromWhere(stat); + return stat.getSQL().trim(); + } + + /** + * toSQL returns a static string version of the query with runtime variables + * properly encoded. This method is also useful when combined with the where + * clause methods like isParameter() or atLeastParameter() which allows + * iciql to generate re-usable parameterized string statements. + * + * @return the sql query as plain text + */ + public String toSQL() { + return query.toSQL(false); + } + + /** + * toSQL returns a static string version of the query with runtime variables + * properly encoded. This method is also useful when combined with the where + * clause methods like isParameter() or atLeastParameter() which allows + * iciql to generate re-usable parameterized string statements. + * + * @param distinct + * if true SELECT DISTINCT is used for the query + * @return the sql query as plain text + */ + public String toSQL(boolean distinct) { + return query.toSQL(distinct); + } + + /** + * toSQL returns a static string version of the query with runtime variables + * properly encoded. This method is also useful when combined with the where + * clause methods like isParameter() or atLeastParameter() which allows + * iciql to generate re-usable parameterized string statements. + * + * @param distinct + * if true SELECT DISTINCT is used for the query + * @param k + * k is used to select only the columns of the specified alias + * for an inner join statement. An example of a generated + * statement is: SELECT DISTINCT t1.* FROM sometable AS t1 INNER + * JOIN othertable AS t2 ON t1.id = t2.id WHERE t2.flag = true + * without the alias parameter the statement would start with + * SELECT DISTINCT * FROM... + * @return the sql query as plain text + */ + public String toSQL(boolean distinct, K k) { + return query.toSQL(distinct, k); + } + + public SubQuery subQuery(Z x) { + return new SubQuery(query, x); + } + + public SubQuery subQuery(boolean x) { + return subQuery(query.getPrimitiveAliasByValue(x)); + } + + public SubQuery subQuery(byte x) { + return subQuery(query.getPrimitiveAliasByValue(x)); + } + + public SubQuery subQuery(short x) { + return subQuery(query.getPrimitiveAliasByValue(x)); + } + + public SubQuery subQuery(int x) { + return subQuery(query.getPrimitiveAliasByValue(x)); + } + + public SubQuery subQuery(long x) { + return subQuery(query.getPrimitiveAliasByValue(x)); + } + + public SubQuery subQuery(float x) { + return subQuery(query.getPrimitiveAliasByValue(x)); + } + + public SubQuery subQuery(double x) { + return subQuery(query.getPrimitiveAliasByValue(x)); + } + + public List select(Z x) { + return query.select(x); + } + + public List selectDistinct(Z x) { + return query.selectDistinct(x); + } + + public X selectFirst(Z x) { + List list = query.select(x); + return list.isEmpty() ? null : list.get(0); + } + + public List select() { + return query.select(); + } + + public T selectFirst() { + List list = select(); + return list.isEmpty() ? null : list.get(0); + } + + public List selectDistinct() { + return query.selectDistinct(); + } + + public void createView(Class viewClass) { + query.createView(viewClass); + } + + public void replaceView(Class viewClass) { + query.replaceView(viewClass); + } + + /** + * Order by primitive boolean field + * + * @param field + * a primitive boolean field + * @return the query + */ + public QueryWhere orderBy(boolean field) { + query.getFrom().getAliasDefinition().checkMultipleBooleans(); + return orderByPrimitive(field); + } + + /** + * Order by primitive byte field + * + * @param field + * a primitive byte field + * @return the query + */ + public QueryWhere orderBy(byte field) { + return orderByPrimitive(field); + } + + /** + * Order by primitive short field + * + * @param field + * a primitive short field + * @return the query + */ + public QueryWhere orderBy(short field) { + return orderByPrimitive(field); + } + + public QueryWhere orderBy(int field) { + return orderByPrimitive(field); + } + + /** + * Order by primitive long field + * + * @param field + * a primitive long field + * @return the query + */ + public QueryWhere orderBy(long field) { + return orderByPrimitive(field); + } + + /** + * Order by primitive float field + * + * @param field + * a primitive float field + * @return the query + */ + public QueryWhere orderBy(float field) { + return orderByPrimitive(field); + } + + /** + * Order by primitive double field + * + * @param field + * a primitive double field + * @return the query + */ + public QueryWhere orderBy(double field) { + return orderByPrimitive(field); + } + + private QueryWhere orderByPrimitive(Object field) { + query.orderByPrimitive(field); + return this; + } + + public QueryWhere orderBy(Object field) { + query.getFrom().getAliasDefinition().checkMultipleEnums(field); + query.orderBy(field); + return this; + } + + /** + * Order by a number of Object columns. + * + * @param expressions + * the order by expressions + * @return the query + */ + + public QueryWhere orderBy(Object... expressions) { + query.orderBy(expressions); + return this; + } + + public QueryWhere orderByNullsFirst(Object expr) { + query.getFrom().getAliasDefinition().checkMultipleEnums(expr); + OrderExpression e = new OrderExpression(query, expr, false, true, false); + query.addOrderBy(e); + return this; + } + + public QueryWhere orderByNullsLast(Object expr) { + query.getFrom().getAliasDefinition().checkMultipleEnums(expr); + OrderExpression e = new OrderExpression(query, expr, false, false, true); + query.addOrderBy(e); + return this; + } + + public QueryWhere orderByDesc(Object expr) { + query.getFrom().getAliasDefinition().checkMultipleEnums(expr); + OrderExpression e = new OrderExpression(query, expr, true, false, false); + query.addOrderBy(e); + return this; + } + + public QueryWhere orderByDescNullsFirst(Object expr) { + query.getFrom().getAliasDefinition().checkMultipleEnums(expr); + OrderExpression e = new OrderExpression(query, expr, true, true, false); + query.addOrderBy(e); + return this; + } + + public QueryWhere orderByDescNullsLast(Object expr) { + query.getFrom().getAliasDefinition().checkMultipleEnums(expr); + OrderExpression e = new OrderExpression(query, expr, true, false, true); + query.addOrderBy(e); + return this; + } + + public int delete() { + return query.delete(); + } + + public int update() { + return query.update(); + } + + public long selectCount() { + return query.selectCount(); + } + +} diff --git a/src/main/java/com/iciql/RuntimeParameter.java b/src/main/java/com/iciql/RuntimeParameter.java new file mode 100644 index 0000000..0fbedba --- /dev/null +++ b/src/main/java/com/iciql/RuntimeParameter.java @@ -0,0 +1,49 @@ +/* + * 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; + +/** + * A runtime parameter is used to generate x=? conditions so that iciql can + * build re-usable dynamic queries with parameter substitution done manually at + * runtime. + * + * @param + * the operand type + */ + +class RuntimeParameter implements Token { + + public final static String PARAMETER = ""; + + A x; + CompareType compareType; + + RuntimeParameter(A x, CompareType type) { + this.x = x; + this.compareType = type; + } + + public void appendSQL(SQLStatement stat, Query query) { + query.appendSQL(stat, null, x); + stat.appendSQL(" "); + stat.appendSQL(compareType.getString()); + if (compareType.hasRightExpression()) { + stat.appendSQL(" "); + query.appendSQL(stat, x, PARAMETER); + } + } +} diff --git a/src/main/java/com/iciql/RuntimeToken.java b/src/main/java/com/iciql/RuntimeToken.java new file mode 100644 index 0000000..cbfd882 --- /dev/null +++ b/src/main/java/com/iciql/RuntimeToken.java @@ -0,0 +1,57 @@ +/* + * Copyright 2011 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; + +import java.text.MessageFormat; + +import com.iciql.util.StringUtils; + +/** + * Represents a traditional PreparedStatment fragment like "id=?, name=?". + * + */ +public class RuntimeToken implements Token { + + final String fragment; + final Object[] args; + + public RuntimeToken(String fragment, Object... args) { + this.fragment = fragment; + this.args = args == null ? new Object[0] : args; + } + + /** + * Append the SQL to the given statement using the given query. + * + * @param stat + * the statement to append the SQL to + * @param query + * the query to use + */ + @Override + public void appendSQL(SQLStatement stat, Query query) { + int tokenCount = StringUtils.count('?', fragment); + if (tokenCount != args.length) { + throw new IciqlException(MessageFormat.format( + "Fragment \"{0}\" specifies {1} tokens but you supplied {2} args", fragment, tokenCount, + args.length)); + } + stat.appendSQL(fragment); + for (Object arg : args) { + stat.addParameter(arg); + } + } +} diff --git a/src/main/java/com/iciql/SQLDialect.java b/src/main/java/com/iciql/SQLDialect.java new file mode 100644 index 0000000..f62168e --- /dev/null +++ b/src/main/java/com/iciql/SQLDialect.java @@ -0,0 +1,206 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * Copyright 2012 Frédéric Gaillard. + * + * 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; + +import java.sql.DatabaseMetaData; + +import com.iciql.TableDefinition.ConstraintForeignKeyDefinition; +import com.iciql.TableDefinition.ConstraintUniqueDefinition; +import com.iciql.TableDefinition.IndexDefinition; + +/** + * This interface defines points where iciql can build different statements + * depending on the database used. + */ +public interface SQLDialect { + + /** + * Configure the dialect from the database metadata. + * + * @param databaseName + * @param data + */ + void configureDialect(String databaseName, DatabaseMetaData data); + + /** + * Allows a dialect to substitute an SQL type. + * + * @param sqlType + * @return the dialect-safe type + */ + String convertSqlType(String sqlType); + + /** + * Returns a properly formatted table name for the dialect. + * + * @param schemaName + * the schema name, or null for no schema + * @param tableName + * the properly formatted table name + * @return the SQL snippet + */ + String prepareTableName(String schemaName, String tableName); + + /** + * Returns a properly formatted column name for the dialect. + * + * @param name + * the column name + * @return the properly formatted column name + */ + String prepareColumnName(String name); + + /** + * Get the CREATE TABLE statement. + * + * @param stat + * @param def + */ + void prepareCreateTable(SQLStatement stat, TableDefinition def); + + /** + * Get the DROP TABLE statement. + * + * @param stat + * @param def + */ + void prepareDropTable(SQLStatement stat, TableDefinition def); + + + /** + * Get the CREATE VIEW statement. + * + * @param stat + * return the SQL statement + * @param def + * table definition + */ + void prepareCreateView(SQLStatement stat, TableDefinition def); + + /** + * Get the CREATE VIEW statement. + * + * @param stat + * return the SQL statement + * @param def + * table definition + * @param fromWhere + */ + void prepareCreateView(SQLStatement stat, TableDefinition def, String fromWhere); + + /** + * Get the DROP VIEW statement. + * + * @param stat + * return the SQL statement + * @param def + * table definition + */ + void prepareDropView(SQLStatement stat, TableDefinition def); + + /** + * Get the CREATE INDEX statement. + * + * @param stat + * return the SQL statement + * @param schemaName + * the schema name + * @param tableName + * the table name + * @param index + * the index definition + */ + void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName, IndexDefinition index); + + /** + * Get the ALTER statement. + * + * @param stat + * return the SQL statement + * @param schemaName + * the schema name + * @param tableName + * the table name + * @param constraint + * the constraint definition + */ + void prepareCreateConstraintForeignKey(SQLStatement stat, String schemaName, String tableName, ConstraintForeignKeyDefinition constraint); + + /** + * Get the ALTER statement. + * + * @param stat + * return the SQL statement + * @param schemaName + * the schema name + * @param tableName + * the table name + * @param constraint + * the constraint definition + * return the SQL statement + */ + void prepareCreateConstraintUnique(SQLStatement stat, String schemaName, String tableName, ConstraintUniqueDefinition constraint); + + /** + * Get a MERGE or REPLACE INTO statement. + * + * @param stat + * return the SQL statement + * @param schemaName + * the schema name + * @param tableName + * the table name + * @param def + * the table definition + * @param obj + * values + */ + void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition def, + Object obj); + + /** + * Append "LIMIT limit OFFSET offset" to the SQL statement. + * + * @param stat + * the statement + * @param limit + * the limit + * @param offset + * the offset + */ + void appendLimitOffset(SQLStatement stat, long limit, long offset); + + /** + * Returns the preferred DATETIME class for the database. + *

+ * Either java.util.Date or java.sql.Timestamp + * + * @return preferred DATETIME class + */ + Class getDateTimeClass(); + + /** + * When building static string statements this method flattens an object to + * a string representation suitable for a static string statement. + * + * @param o + * @return the string equivalent of this object + */ + String prepareParameter(Object o); +} diff --git a/src/main/java/com/iciql/SQLDialectDefault.java b/src/main/java/com/iciql/SQLDialectDefault.java new file mode 100644 index 0000000..364db7b --- /dev/null +++ b/src/main/java/com/iciql/SQLDialectDefault.java @@ -0,0 +1,445 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * Copyright 2012 Frédéric Gaillard. + * + * 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; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; + +import com.iciql.Iciql.ConstraintDeleteType; +import com.iciql.Iciql.ConstraintUpdateType; +import com.iciql.TableDefinition.ConstraintForeignKeyDefinition; +import com.iciql.TableDefinition.ConstraintUniqueDefinition; +import com.iciql.TableDefinition.FieldDefinition; +import com.iciql.TableDefinition.IndexDefinition; +import com.iciql.util.IciqlLogger; +import com.iciql.util.StatementBuilder; +import com.iciql.util.StringUtils; + +/** + * Default implementation of an SQL dialect. + */ +public class SQLDialectDefault implements SQLDialect { + + final String LITERAL = "'"; + + float databaseVersion; + String databaseName; + String productVersion; + + @Override + public String toString() { + return getClass().getName() + ": " + databaseName + " " + productVersion; + } + + @Override + public void configureDialect(String databaseName, DatabaseMetaData data) { + this.databaseName = databaseName; + try { + databaseVersion = Float.parseFloat(data.getDatabaseMajorVersion() + "." + + data.getDatabaseMinorVersion()); + productVersion = data.getDatabaseProductVersion(); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + /** + * Allows subclasses to change the type of a column for a CREATE statement. + * + * @param sqlType + * @return the SQL type or a preferred alternative + */ + @Override + public String convertSqlType(String sqlType) { + return sqlType; + } + + @Override + public Class getDateTimeClass() { + return java.util.Date.class; + } + + @Override + public String prepareTableName(String schemaName, String tableName) { + if (StringUtils.isNullOrEmpty(schemaName)) { + return tableName; + } + return schemaName + "." + tableName; + } + + @Override + public String prepareColumnName(String name) { + return name; + } + + @Override + public void prepareDropTable(SQLStatement stat, TableDefinition def) { + StatementBuilder buff = new StatementBuilder("DROP TABLE IF EXISTS " + + prepareTableName(def.schemaName, def.tableName)); + stat.setSQL(buff.toString()); + return; + } + + protected String prepareCreateTable(TableDefinition def) { + return "CREATE TABLE"; + } + + @Override + public void prepareCreateTable(SQLStatement stat, TableDefinition def) { + StatementBuilder buff = new StatementBuilder(); + buff.append(prepareCreateTable(def)); + buff.append(" "); + buff.append(prepareTableName(def.schemaName, def.tableName)).append('('); + + boolean hasIdentityColumn = false; + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(field.columnName)).append(' '); + String dataType = field.dataType; + if (dataType.equals("VARCHAR")) { + // check to see if we should use VARCHAR or CLOB + if (field.length <= 0) { + dataType = "CLOB"; + } + buff.append(convertSqlType(dataType)); + if (field.length > 0) { + buff.append('(').append(field.length).append(')'); + } + } else if (dataType.equals("DECIMAL")) { + // DECIMAL(precision,scale) + buff.append(convertSqlType(dataType)); + if (field.length > 0) { + buff.append('(').append(field.length); + if (field.scale > 0) { + buff.append(',').append(field.scale); + } + buff.append(')'); + } + } else { + // other + hasIdentityColumn |= prepareColumnDefinition(buff, convertSqlType(dataType), + field.isAutoIncrement, field.isPrimaryKey); + } + + // default values + if (!field.isAutoIncrement && !field.isPrimaryKey) { + String dv = field.defaultValue; + if (!StringUtils.isNullOrEmpty(dv)) { + if (ModelUtils.isProperlyFormattedDefaultValue(dv) + && ModelUtils.isValidDefaultValue(field.field.getType(), dv)) { + buff.append(" DEFAULT " + dv); + } + } + } + + if (!field.nullable) { + buff.append(" NOT NULL"); + } + } + + // if table does not have identity column then specify primary key + if (!hasIdentityColumn) { + if (def.primaryKeyColumnNames != null && def.primaryKeyColumnNames.size() > 0) { + buff.append(", PRIMARY KEY("); + buff.resetCount(); + for (String n : def.primaryKeyColumnNames) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(n)); + } + buff.append(')'); + } + } + buff.append(')'); + stat.setSQL(buff.toString()); + } + + @Override + public void prepareDropView(SQLStatement stat, TableDefinition def) { + StatementBuilder buff = new StatementBuilder("DROP VIEW " + + prepareTableName(def.schemaName, def.tableName)); + stat.setSQL(buff.toString()); + return; + } + + protected String prepareCreateView(TableDefinition def) { + return "CREATE VIEW"; + } + + @Override + public void prepareCreateView(SQLStatement stat, TableDefinition def) { + StatementBuilder buff = new StatementBuilder(); + buff.append(" FROM "); + buff.append(prepareTableName(def.schemaName, def.viewTableName)); + + StatementBuilder where = new StatementBuilder(); + for (FieldDefinition field : def.fields) { + if (!StringUtils.isNullOrEmpty(field.constraint)) { + where.appendExceptFirst(", "); + String col = prepareColumnName(field.columnName); + String constraint = field.constraint.replace("{0}", col).replace("this", col); + where.append(constraint); + } + } + if (where.length() > 0) { + buff.append(" WHERE "); + buff.append(where.toString()); + } + + prepareCreateView(stat, def, buff.toString()); + } + + @Override + public void prepareCreateView(SQLStatement stat, TableDefinition def, String fromWhere) { + StatementBuilder buff = new StatementBuilder(); + buff.append(prepareCreateView(def)); + buff.append(" "); + buff.append(prepareTableName(def.schemaName, def.tableName)); + + buff.append(" AS SELECT "); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(field.columnName)); + } + buff.append(fromWhere); + stat.setSQL(buff.toString()); + } + + protected boolean isIntegerType(String dataType) { + if ("INT".equals(dataType)) { + return true; + } else if ("BIGINT".equals(dataType)) { + return true; + } else if ("TINYINT".equals(dataType)) { + return true; + } else if ("SMALLINT".equals(dataType)) { + return true; + } + return false; + } + + protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, + boolean isAutoIncrement, boolean isPrimaryKey) { + buff.append(dataType); + if (isAutoIncrement) { + buff.append(" AUTO_INCREMENT"); + } + return false; + } + + @Override + public void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName, + IndexDefinition index) { + StatementBuilder buff = new StatementBuilder(); + buff.append("CREATE "); + switch (index.type) { + case UNIQUE: + buff.append("UNIQUE "); + break; + case UNIQUE_HASH: + buff.append("UNIQUE "); + break; + default: + IciqlLogger.warn("{0} does not support hash indexes", getClass().getSimpleName()); + } + buff.append("INDEX "); + buff.append(index.indexName); + buff.append(" ON "); + // FIXME maybe we can use schemaName ? + // buff.append(prepareTableName(schemaName, tableName)); + buff.append(tableName); + buff.append("("); + for (String col : index.columnNames) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(col)); + } + buff.append(") "); + + stat.setSQL(buff.toString().trim()); + } + + /** + * PostgreSQL and Derby do not support the SQL2003 MERGE syntax, but we can + * use a trick to insert a row if it does not exist and call update() in + * Db.merge() if the affected row count is 0. + *

+ * Databases that do support a MERGE syntax should override this method. + *

+ * http://stackoverflow.com/questions/407688 + */ + @Override + public void prepareMerge(SQLStatement stat, String schemaName, String tableName, + TableDefinition def, Object obj) { + StatementBuilder buff = new StatementBuilder("INSERT INTO "); + buff.append(prepareTableName(schemaName, tableName)); + buff.append(" ("); + buff.resetCount(); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(field.columnName)); + } + buff.append(") (SELECT "); + buff.resetCount(); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append('?'); + Object value = def.getValue(obj, field); + stat.addParameter(value); + } + buff.append(" FROM "); + buff.append(prepareTableName(schemaName, tableName)); + buff.append(" WHERE "); + buff.resetCount(); + for (FieldDefinition field : def.fields) { + if (field.isPrimaryKey) { + buff.appendExceptFirst(" AND "); + buff.append(MessageFormat.format("{0} = ?", prepareColumnName(field.columnName))); + Object value = def.getValue(obj, field); + stat.addParameter(value); + } + } + buff.append(" HAVING count(*)=0)"); + stat.setSQL(buff.toString()); + } + + @Override + public void appendLimitOffset(SQLStatement stat, long limit, long offset) { + if (limit > 0) { + stat.appendSQL(" LIMIT " + limit); + } + if (offset > 0) { + stat.appendSQL(" OFFSET " + offset); + } + } + + @Override + public String prepareParameter(Object o) { + if (o instanceof String) { + return LITERAL + o.toString().replace(LITERAL, "''") + LITERAL; + } else if (o instanceof Character) { + return LITERAL + o.toString() + LITERAL; + } else if (o instanceof java.sql.Time) { + return LITERAL + new SimpleDateFormat("HH:mm:ss").format(o) + LITERAL; + } else if (o instanceof java.sql.Date) { + return LITERAL + new SimpleDateFormat("yyyy-MM-dd").format(o) + LITERAL; + } else if (o instanceof java.util.Date) { + return LITERAL + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(o) + LITERAL; + } + return o.toString(); + } + + @SuppressWarnings("incomplete-switch") + @Override + public void prepareCreateConstraintForeignKey(SQLStatement stat, String schemaName, String tableName, ConstraintForeignKeyDefinition constraint) { + StatementBuilder buff = new StatementBuilder(); + buff.append("ALTER TABLE "); + buff.append(prepareTableName(schemaName, tableName)); + buff.append(" ADD CONSTRAINT "); + buff.append(constraint.constraintName); + buff.append(" FOREIGN KEY "); + buff.append(" ("); + for (String col : constraint.foreignColumns) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(col)); + } + buff.append(") "); + buff.append(" REFERENCES "); + buff.append(constraint.referenceTable); + buff.append(" ("); + buff.resetCount(); + for (String col : constraint.referenceColumns) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(col)); + } + buff.append(") "); + if (constraint.deleteType != ConstraintDeleteType.UNSET) { + buff.append(" ON DELETE "); + switch (constraint.deleteType) { + case CASCADE: + buff.append("CASCADE "); + break; + case RESTRICT: + buff.append("RESTRICT "); + break; + case SET_NULL: + buff.append("SET NULL "); + break; + case NO_ACTION: + buff.append("NO ACTION "); + break; + case SET_DEFAULT: + buff.append("SET DEFAULT "); + break; + } + } + if (constraint.updateType != ConstraintUpdateType.UNSET) { + buff.append(" ON UPDATE "); + switch (constraint.updateType) { + case CASCADE: + buff.append("CASCADE "); + break; + case RESTRICT: + buff.append("RESTRICT "); + break; + case SET_NULL: + buff.append("SET NULL "); + break; + case NO_ACTION: + buff.append("NO ACTION "); + break; + case SET_DEFAULT: + buff.append("SET DEFAULT "); + break; + } + } + switch (constraint.deferrabilityType) { + case DEFERRABLE_INITIALLY_DEFERRED: + buff.append("DEFERRABLE INITIALLY DEFERRED "); + break; + case DEFERRABLE_INITIALLY_IMMEDIATE: + buff.append("DEFERRABLE INITIALLY IMMEDIATE "); + break; + case NOT_DEFERRABLE: + buff.append("NOT DEFERRABLE "); + break; + case UNSET: + break; + } + stat.setSQL(buff.toString().trim()); + } + + @Override + public void prepareCreateConstraintUnique(SQLStatement stat, String schemaName, String tableName, ConstraintUniqueDefinition constraint) { + StatementBuilder buff = new StatementBuilder(); + buff.append("ALTER TABLE "); + buff.append(prepareTableName(schemaName, tableName)); + buff.append(" ADD CONSTRAINT "); + buff.append(constraint.constraintName); + buff.append(" UNIQUE "); + buff.append(" ("); + for (String col : constraint.uniqueColumns) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(col)); + } + buff.append(") "); + stat.setSQL(buff.toString().trim()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/iciql/SQLDialectDerby.java b/src/main/java/com/iciql/SQLDialectDerby.java new file mode 100644 index 0000000..f954a7c --- /dev/null +++ b/src/main/java/com/iciql/SQLDialectDerby.java @@ -0,0 +1,71 @@ +/* + * Copyright 2011 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; + +import com.iciql.util.StatementBuilder; + +/** + * Derby database dialect. + */ +public class SQLDialectDerby extends SQLDialectDefault { + + @Override + public Class getDateTimeClass() { + return java.sql.Timestamp.class; + } + + @Override + public String convertSqlType(String sqlType) { + if ("TINYINT".equals(sqlType)) { + // Derby does not have a TINYINT/BYTE type + return "SMALLINT"; + } + return sqlType; + } + + @Override + public void appendLimitOffset(SQLStatement stat, long limit, long offset) { + // FETCH/OFFSET added in 10.5 + if (databaseVersion >= 10.5f) { + if (offset > 0) { + stat.appendSQL(" OFFSET " + offset + (offset == 1 ? " ROW" : " ROWS")); + } + if (limit > 0) { + stat.appendSQL(" FETCH NEXT " + limit + (limit == 1 ? " ROW" : " ROWS") + " ONLY"); + } + } + } + + @Override + protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, + boolean isAutoIncrement, boolean isPrimaryKey) { + String convertedType = convertSqlType(dataType); + buff.append(convertedType); + if (isIntegerType(dataType) && isAutoIncrement) { + buff.append(" GENERATED BY DEFAULT AS IDENTITY"); + } + return false; + } + + @Override + public void prepareDropTable(SQLStatement stat, TableDefinition def) { + StatementBuilder buff = new StatementBuilder("DROP TABLE " + + prepareTableName(def.schemaName, def.tableName)); + stat.setSQL(buff.toString()); + return; + } +} \ No newline at end of file diff --git a/src/main/java/com/iciql/SQLDialectH2.java b/src/main/java/com/iciql/SQLDialectH2.java new file mode 100644 index 0000000..6b3bab1 --- /dev/null +++ b/src/main/java/com/iciql/SQLDialectH2.java @@ -0,0 +1,135 @@ +/* + * Copyright 2011 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; + +import com.iciql.TableDefinition.FieldDefinition; +import com.iciql.TableDefinition.IndexDefinition; +import com.iciql.util.StatementBuilder; + +/** + * H2 database dialect. + */ +public class SQLDialectH2 extends SQLDialectDefault { + + /** + * CACHED tables are created by default. MEMORY tables are created upon + * request. + */ + @Override + protected String prepareCreateTable(TableDefinition def) { + if (def.memoryTable) { + return "CREATE MEMORY TABLE IF NOT EXISTS"; + } else { + return "CREATE CACHED TABLE IF NOT EXISTS"; + } + } + + @Override + protected String prepareCreateView(TableDefinition def) { + return "CREATE VIEW IF NOT EXISTS"; + } + + @Override + public void prepareDropView(SQLStatement stat, TableDefinition def) { + StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " + + prepareTableName(def.schemaName, def.tableName)); + stat.setSQL(buff.toString()); + return; + } + + @Override + protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, + boolean isAutoIncrement, boolean isPrimaryKey) { + String convertedType = convertSqlType(dataType); + boolean isIdentity = false; + if (isIntegerType(dataType)) { + if (isAutoIncrement && isPrimaryKey) { + buff.append("IDENTITY"); + isIdentity = true; + } else if (isAutoIncrement) { + buff.append(convertedType); + buff.append(" AUTO_INCREMENT"); + } else { + buff.append(convertedType); + } + } else { + buff.append(convertedType); + } + return isIdentity; + } + + @Override + public void prepareCreateIndex(SQLStatement stat, String schema, String table, IndexDefinition index) { + StatementBuilder buff = new StatementBuilder(); + buff.append("CREATE "); + switch (index.type) { + case STANDARD: + break; + case UNIQUE: + buff.append("UNIQUE "); + break; + case HASH: + buff.append("HASH "); + break; + case UNIQUE_HASH: + buff.append("UNIQUE HASH "); + break; + } + buff.append("INDEX IF NOT EXISTS "); + buff.append(index.indexName); + buff.append(" ON "); + buff.append(table); + buff.append("("); + for (String col : index.columnNames) { + buff.appendExceptFirst(", "); + buff.append(col); + } + buff.append(")"); + stat.setSQL(buff.toString()); + } + + @Override + public void prepareMerge(SQLStatement stat, String schemaName, String tableName, + TableDefinition def, Object obj) { + StatementBuilder buff = new StatementBuilder("MERGE INTO "); + buff.append(prepareTableName(schemaName, tableName)).append(" ("); + buff.resetCount(); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append(field.columnName); + } + buff.append(") KEY("); + buff.resetCount(); + for (FieldDefinition field : def.fields) { + if (field.isPrimaryKey) { + buff.appendExceptFirst(", "); + buff.append(field.columnName); + } + } + buff.append(") "); + buff.resetCount(); + buff.append("VALUES ("); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append('?'); + Object value = def.getValue(obj, field); + stat.addParameter(value); + } + buff.append(')'); + stat.setSQL(buff.toString()); + } +} \ No newline at end of file diff --git a/src/main/java/com/iciql/SQLDialectHSQL.java b/src/main/java/com/iciql/SQLDialectHSQL.java new file mode 100644 index 0000000..82e6833 --- /dev/null +++ b/src/main/java/com/iciql/SQLDialectHSQL.java @@ -0,0 +1,149 @@ +/* + * Copyright 2011 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; + +import java.text.MessageFormat; + +import com.iciql.TableDefinition.FieldDefinition; +import com.iciql.util.StatementBuilder; + +/** + * HyperSQL database dialect. + */ +public class SQLDialectHSQL extends SQLDialectDefault { + + /** + * CACHED tables are created by default. MEMORY tables are created upon + * request. + */ + @Override + protected String prepareCreateTable(TableDefinition def) { + if (def.memoryTable) { + return "CREATE MEMORY TABLE IF NOT EXISTS"; + } else { + return "CREATE CACHED TABLE IF NOT EXISTS"; + } + } + + @Override + public void prepareDropView(SQLStatement stat, TableDefinition def) { + StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " + + prepareTableName(def.schemaName, def.tableName)); + stat.setSQL(buff.toString()); + return; + } + + @Override + protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, + boolean isAutoIncrement, boolean isPrimaryKey) { + boolean isIdentity = false; + String convertedType = convertSqlType(dataType); + buff.append(convertedType); + if (isIntegerType(dataType) && isAutoIncrement && isPrimaryKey) { + buff.append(" IDENTITY"); + isIdentity = true; + } + return isIdentity; + } + + @Override + public void prepareMerge(SQLStatement stat, String schemaName, String tableName, + TableDefinition def, Object obj) { + final String valuePrefix = "v"; + StatementBuilder buff = new StatementBuilder("MERGE INTO "); + buff.append(prepareTableName(schemaName, tableName)); + // a, b, c.... + buff.append(" USING (VALUES("); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append("CAST(? AS "); + String dataType = convertSqlType(field.dataType); + buff.append(dataType); + if ("VARCHAR".equals(dataType)) { + if (field.length > 0) { + // VARCHAR(x) + buff.append(MessageFormat.format("({0})", field.length)); + } + } else if ("DECIMAL".equals(dataType)) { + if (field.length > 0) { + if (field.scale > 0) { + // DECIMAL(x,y) + buff.append(MessageFormat.format("({0},{1})", field.length, field.scale)); + } else { + // DECIMAL(x) + buff.append(MessageFormat.format("({0})", field.length)); + } + } + } + buff.append(')'); + Object value = def.getValue(obj, field); + stat.addParameter(value); + } + + // map to temporary table + buff.resetCount(); + buff.append(")) AS vals ("); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(valuePrefix + field.columnName)); + } + + buff.append(") ON "); + + // create the ON condition + // (va, vb) = (va,vb) + String[] prefixes = { "", valuePrefix }; + for (int i = 0; i < prefixes.length; i++) { + String prefix = prefixes[i]; + buff.resetCount(); + buff.append('('); + for (FieldDefinition field : def.fields) { + if (field.isPrimaryKey) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(prefix + field.columnName)); + } + } + buff.append(")"); + if (i == 0) { + buff.append('='); + } + } + + // UPDATE + // set a=va + buff.append(" WHEN MATCHED THEN UPDATE SET "); + buff.resetCount(); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(field.columnName)); + buff.append('='); + buff.append(prepareColumnName(valuePrefix + field.columnName)); + } + + // INSERT + // insert va, vb, vc.... + buff.append(" WHEN NOT MATCHED THEN INSERT "); + buff.resetCount(); + buff.append(" VALUES ("); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(valuePrefix + field.columnName)); + } + buff.append(')'); + stat.setSQL(buff.toString()); + } +} \ No newline at end of file diff --git a/src/main/java/com/iciql/SQLDialectMSSQL.java b/src/main/java/com/iciql/SQLDialectMSSQL.java new file mode 100644 index 0000000..92b1297 --- /dev/null +++ b/src/main/java/com/iciql/SQLDialectMSSQL.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012 Alex Telepov. + * 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; + +/** + * MS SQL Server database dialect. + */ +public class SQLDialectMSSQL extends SQLDialectDefault { + + /** + * Append limit and offset rows + * + * @param stat Statement + * @param limit Limit rows + * @param offset Offset rows + */ + @Override + public void appendLimitOffset(SQLStatement stat, long limit, long offset) { + if (offset > 0) { + throw new IciqlException("iciql does not support offset for MSSQL dialect!"); + } + StringBuilder query = new StringBuilder(stat.getSQL()); + + // for databaseVersion >= 2012 need Offset + if (limit > 0) { + int indexSelect = query.indexOf("SELECT"); + + if (indexSelect >= 0) { + StringBuilder subPathQuery = new StringBuilder(" TOP "); + subPathQuery.append(Long.toString(limit)); + + query.insert(indexSelect + "SELECT".length(), subPathQuery); + + stat.setSQL(query.toString()); + } + } + } +} diff --git a/src/main/java/com/iciql/SQLDialectMySQL.java b/src/main/java/com/iciql/SQLDialectMySQL.java new file mode 100644 index 0000000..52676d4 --- /dev/null +++ b/src/main/java/com/iciql/SQLDialectMySQL.java @@ -0,0 +1,93 @@ +/* + * Copyright 2011 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; + +import com.iciql.TableDefinition.FieldDefinition; +import com.iciql.util.StatementBuilder; + +/** + * MySQL database dialect. + */ +public class SQLDialectMySQL extends SQLDialectDefault { + + @Override + public String convertSqlType(String sqlType) { + if (sqlType.equals("CLOB")) { + return "TEXT"; + } + return sqlType; + } + + @Override + protected String prepareCreateTable(TableDefinition def) { + return "CREATE TABLE IF NOT EXISTS"; + } + + @Override + public void prepareDropView(SQLStatement stat, TableDefinition def) { + StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " + + prepareTableName(def.schemaName, def.tableName)); + stat.setSQL(buff.toString()); + return; + } + + @Override + public String prepareColumnName(String name) { + return "`" + name + "`"; + } + + @Override + protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, boolean isAutoIncrement, + boolean isPrimaryKey) { + String convertedType = convertSqlType(dataType); + buff.append(convertedType); + if (isIntegerType(dataType) && isAutoIncrement) { + buff.append(" AUTO_INCREMENT"); + } + return false; + } + + @Override + public void prepareMerge(SQLStatement stat, String schemaName, String tableName, + TableDefinition def, Object obj) { + StatementBuilder buff = new StatementBuilder("INSERT INTO "); + buff.append(prepareTableName(schemaName, tableName)).append(" ("); + buff.resetCount(); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append(field.columnName); + } + buff.resetCount(); + buff.append(") VALUES ("); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append('?'); + Object value = def.getValue(obj, field); + stat.addParameter(value); + } + buff.append(") ON DUPLICATE KEY UPDATE "); + buff.resetCount(); + for (FieldDefinition field : def.fields) { + buff.appendExceptFirst(", "); + buff.append(field.columnName); + buff.append("=VALUES("); + buff.append(field.columnName); + buff.append(')'); + } + stat.setSQL(buff.toString()); + } +} \ No newline at end of file diff --git a/src/main/java/com/iciql/SQLDialectPostgreSQL.java b/src/main/java/com/iciql/SQLDialectPostgreSQL.java new file mode 100644 index 0000000..fc115ab --- /dev/null +++ b/src/main/java/com/iciql/SQLDialectPostgreSQL.java @@ -0,0 +1,103 @@ +/* + * Copyright 2011 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; + +import com.iciql.TableDefinition.IndexDefinition; +import com.iciql.util.StatementBuilder; + +/** + * PostgreSQL database dialect. + */ +public class SQLDialectPostgreSQL extends SQLDialectDefault { + + @Override + public Class getDateTimeClass() { + return java.sql.Timestamp.class; + } + + @Override + public String convertSqlType(String sqlType) { + if ("DOUBLE".equals(sqlType)) { + return "DOUBLE PRECISION"; + } else if ("TINYINT".equals(sqlType)) { + // PostgreSQL does not have a byte type + return "SMALLINT"; + } else if ("CLOB".equals(sqlType)) { + return "TEXT"; + } else if ("BLOB".equals(sqlType)) { + return "BYTEA"; + } + return sqlType; + } + + @Override + protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, + boolean isAutoIncrement, boolean isPrimaryKey) { + String convertedType = convertSqlType(dataType); + if (isIntegerType(dataType)) { + if (isAutoIncrement) { + if ("BIGINT".equals(dataType)) { + buff.append("BIGSERIAL"); + } else { + buff.append("SERIAL"); + } + } else { + buff.append(convertedType); + } + } else { + buff.append(convertedType); + } + return false; + } + + @Override + public void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName, + IndexDefinition index) { + StatementBuilder buff = new StatementBuilder(); + buff.append("CREATE "); + switch (index.type) { + case UNIQUE: + buff.append("UNIQUE "); + break; + case UNIQUE_HASH: + buff.append("UNIQUE "); + break; + } + buff.append("INDEX "); + buff.append(index.indexName); + buff.append(" ON "); + buff.append(tableName); + + switch (index.type) { + case HASH: + buff.append(" USING HASH"); + break; + case UNIQUE_HASH: + buff.append(" USING HASH"); + break; + } + + buff.append(" ("); + for (String col : index.columnNames) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(col)); + } + buff.append(") "); + + stat.setSQL(buff.toString().trim()); + } +} \ No newline at end of file diff --git a/src/main/java/com/iciql/SQLStatement.java b/src/main/java/com/iciql/SQLStatement.java new file mode 100644 index 0000000..394fc42 --- /dev/null +++ b/src/main/java/com/iciql/SQLStatement.java @@ -0,0 +1,190 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.StringTokenizer; + +import com.iciql.util.JdbcUtils; + +/** + * This class represents a parameterized SQL statement. + */ + +public class SQLStatement { + private Db db; + private StringBuilder buff = new StringBuilder(); + private String sql; + private ArrayList params = new ArrayList(); + + SQLStatement(Db db) { + this.db = db; + } + + public void setSQL(String sql) { + this.sql = sql; + buff = new StringBuilder(sql); + } + + public SQLStatement appendSQL(String s) { + buff.append(s); + sql = null; + return this; + } + + public SQLStatement appendTable(String schema, String table) { + return appendSQL(db.getDialect().prepareTableName(schema, table)); + } + + public SQLStatement appendColumn(String column) { + return appendSQL(db.getDialect().prepareColumnName(column)); + } + + /** + * getSQL returns a simple string representation of the parameterized + * statement which will be used later, internally, with prepareStatement. + * + * @return a simple sql statement + */ + String getSQL() { + if (sql == null) { + sql = buff.toString(); + } + return sql; + } + + /** + * toSQL creates a static sql statement with the referenced parameters + * encoded in the statement. + * + * @return a complete sql statement + */ + String toSQL() { + if (sql == null) { + sql = buff.toString(); + } + if (params.size() == 0) { + return sql; + } + StringBuilder sb = new StringBuilder(); + // TODO this needs to me more sophisticated + StringTokenizer st = new StringTokenizer(sql, "?", false); + int i = 0; + while (st.hasMoreTokens()) { + sb.append(st.nextToken()); + if (i < params.size()) { + Object o = params.get(i); + if (RuntimeParameter.PARAMETER == o) { + // dynamic parameter + sb.append('?'); + } else { + // static parameter + sb.append(db.getDialect().prepareParameter(o)); + } + i++; + } + } + return sb.toString(); + } + + public SQLStatement addParameter(Object o) { + // Automatically convert java.util.Date to java.sql.Timestamp + // if the dialect requires java.sql.Timestamp objects (e.g. Derby) + if (o != null && o.getClass().equals(java.util.Date.class) + && db.getDialect().getDateTimeClass().equals(java.sql.Timestamp.class)) { + o = new java.sql.Timestamp(((java.util.Date) o).getTime()); + } + params.add(o); + return this; + } + + void execute() { + PreparedStatement ps = null; + try { + ps = prepare(false); + ps.execute(); + } catch (SQLException e) { + throw IciqlException.fromSQL(getSQL(), e); + } finally { + JdbcUtils.closeSilently(ps); + } + } + + ResultSet executeQuery() { + try { + return prepare(false).executeQuery(); + } catch (SQLException e) { + throw IciqlException.fromSQL(getSQL(), e); + } + } + + int executeUpdate() { + PreparedStatement ps = null; + try { + ps = prepare(false); + return ps.executeUpdate(); + } catch (SQLException e) { + throw IciqlException.fromSQL(getSQL(), e); + } finally { + JdbcUtils.closeSilently(ps); + } + } + + long executeInsert() { + PreparedStatement ps = null; + try { + ps = prepare(true); + ps.executeUpdate(); + long identity = -1; + ResultSet rs = ps.getGeneratedKeys(); + if (rs != null && rs.next()) { + identity = rs.getLong(1); + } + JdbcUtils.closeSilently(rs); + return identity; + } catch (SQLException e) { + throw IciqlException.fromSQL(getSQL(), e); + } finally { + JdbcUtils.closeSilently(ps); + } + } + + private void setValue(PreparedStatement prep, int parameterIndex, Object x) { + try { + prep.setObject(parameterIndex, x); + } catch (SQLException e) { + IciqlException ix = new IciqlException(e, "error setting parameter {0} as {1}", parameterIndex, x + .getClass().getSimpleName()); + ix.setSQL(getSQL()); + throw ix; + } + } + + PreparedStatement prepare(boolean returnGeneratedKeys) { + PreparedStatement prep = db.prepare(getSQL(), returnGeneratedKeys); + for (int i = 0; i < params.size(); i++) { + Object o = params.get(i); + setValue(prep, i + 1, o); + } + return prep; + } + +} diff --git a/src/main/java/com/iciql/SelectColumn.java b/src/main/java/com/iciql/SelectColumn.java new file mode 100644 index 0000000..43a1a93 --- /dev/null +++ b/src/main/java/com/iciql/SelectColumn.java @@ -0,0 +1,57 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import com.iciql.TableDefinition.FieldDefinition; + +/** + * This class represents a column of a table in a query. + * + * @param + * the table data type + */ + +class SelectColumn { + private SelectTable selectTable; + private FieldDefinition fieldDef; + + SelectColumn(SelectTable table, FieldDefinition fieldDef) { + this.selectTable = table; + this.fieldDef = fieldDef; + } + + void appendSQL(SQLStatement stat) { + if (selectTable.getQuery().isJoin()) { + stat.appendSQL(selectTable.getAs() + "." + fieldDef.columnName); + } else { + stat.appendColumn(fieldDef.columnName); + } + } + + FieldDefinition getFieldDefinition() { + return fieldDef; + } + + SelectTable getSelectTable() { + return selectTable; + } + + Object getCurrentValue() { + return fieldDef.getValue(selectTable.getCurrent()); + } +} diff --git a/src/main/java/com/iciql/SelectTable.java b/src/main/java/com/iciql/SelectTable.java new file mode 100644 index 0000000..37b42c4 --- /dev/null +++ b/src/main/java/com/iciql/SelectTable.java @@ -0,0 +1,112 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import java.util.ArrayList; + +import com.iciql.util.Utils; + +/** + * This class represents a table in a query. + * + * @param + * the table class + */ + +class SelectTable { + + private Query query; + private Class clazz; + private T current; + private String as; + private TableDefinition aliasDef; + private boolean outerJoin; + private ArrayList joinConditions = Utils.newArrayList(); + private T alias; + + @SuppressWarnings("unchecked") + SelectTable(Db db, Query query, T alias, boolean outerJoin) { + this.alias = alias; + this.query = query; + this.outerJoin = outerJoin; + aliasDef = (TableDefinition) db.getTableDefinition(alias.getClass()); + clazz = Utils.getClass(alias); + as = "T" + Utils.nextAsCount(); + } + + T getAlias() { + return alias; + } + + T newObject() { + return Utils.newObject(clazz); + } + + TableDefinition getAliasDefinition() { + return aliasDef; + } + + void appendSQL(SQLStatement stat) { + if (query.isJoin()) { + stat.appendTable(aliasDef.schemaName, aliasDef.tableName).appendSQL(" AS " + as); + } else { + stat.appendTable(aliasDef.schemaName, aliasDef.tableName); + } + } + + void appendSQLAsJoin(SQLStatement stat, Query q) { + if (outerJoin) { + stat.appendSQL(" LEFT OUTER JOIN "); + } else { + stat.appendSQL(" INNER JOIN "); + } + appendSQL(stat); + if (!joinConditions.isEmpty()) { + stat.appendSQL(" ON "); + for (Token token : joinConditions) { + token.appendSQL(stat, q); + stat.appendSQL(" "); + } + } + } + + boolean getOuterJoin() { + return outerJoin; + } + + Query getQuery() { + return query; + } + + String getAs() { + return as; + } + + void addConditionToken(Token condition) { + joinConditions.add(condition); + } + + T getCurrent() { + return current; + } + + void setCurrent(T current) { + this.current = current; + } + +} diff --git a/src/main/java/com/iciql/SubQuery.java b/src/main/java/com/iciql/SubQuery.java new file mode 100644 index 0000000..398d214 --- /dev/null +++ b/src/main/java/com/iciql/SubQuery.java @@ -0,0 +1,32 @@ +/* + * 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; + +public class SubQuery { + + final Query query; + final Z z; + + public SubQuery(Query query, Z x) { + this.query = query; + this.z = x; + } + + public void appendSQL(SQLStatement stat) { + stat.appendSQL(query.toSubQuery(z)); + } +} diff --git a/src/main/java/com/iciql/SubQueryCondition.java b/src/main/java/com/iciql/SubQueryCondition.java new file mode 100644 index 0000000..effea3b --- /dev/null +++ b/src/main/java/com/iciql/SubQueryCondition.java @@ -0,0 +1,41 @@ +/* + * 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; + +/** + * A condition that contains a subquery. + * + * @param + * the operand type + */ + +class SubQueryCondition implements Token { + A x; + SubQuery subquery; + + SubQueryCondition(A x, SubQuery subquery) { + this.x = x; + this.subquery = subquery; + } + + public void appendSQL(SQLStatement stat, Query query) { + query.appendSQL(stat, null, x); + stat.appendSQL(" in ("); + subquery.appendSQL(stat); + stat.appendSQL(")"); + } +} diff --git a/src/main/java/com/iciql/TableDefinition.java b/src/main/java/com/iciql/TableDefinition.java new file mode 100644 index 0000000..6d8cb6e --- /dev/null +++ b/src/main/java/com/iciql/TableDefinition.java @@ -0,0 +1,1233 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * Copyright 2012 Frédéric Gaillard. + * + * 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; + +import java.lang.reflect.Field; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.iciql.Iciql.ConstraintDeferrabilityType; +import com.iciql.Iciql.ConstraintDeleteType; +import com.iciql.Iciql.ConstraintUpdateType; +import com.iciql.Iciql.EnumId; +import com.iciql.Iciql.EnumType; +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQConstraint; +import com.iciql.Iciql.IQContraintUnique; +import com.iciql.Iciql.IQContraintsUnique; +import com.iciql.Iciql.IQEnum; +import com.iciql.Iciql.IQContraintForeignKey; +import com.iciql.Iciql.IQContraintsForeignKey; +import com.iciql.Iciql.IQIgnore; +import com.iciql.Iciql.IQIndex; +import com.iciql.Iciql.IQIndexes; +import com.iciql.Iciql.IQSchema; +import com.iciql.Iciql.IQTable; +import com.iciql.Iciql.IQVersion; +import com.iciql.Iciql.IQView; +import com.iciql.Iciql.IndexType; +import com.iciql.util.IciqlLogger; +import com.iciql.util.StatementBuilder; +import com.iciql.util.StringUtils; +import com.iciql.util.Utils; + +/** + * A table definition contains the index definitions of a table, the field + * definitions, the table name, and other meta data. + * + * @param + * the table type + */ + +public class TableDefinition { + + /** + * The meta data of an index. + */ + + public static class IndexDefinition { + public IndexType type; + public String indexName; + + public List columnNames; + } + + /** + * The meta data of a constraint on foreign key. + */ + + public static class ConstraintForeignKeyDefinition { + + public String constraintName; + public List foreignColumns; + public String referenceTable; + public List referenceColumns; + public ConstraintDeleteType deleteType = ConstraintDeleteType.UNSET; + public ConstraintUpdateType updateType = ConstraintUpdateType.UNSET; + public ConstraintDeferrabilityType deferrabilityType = ConstraintDeferrabilityType.UNSET; + } + + /** + * The meta data of a unique constraint. + */ + + public static class ConstraintUniqueDefinition { + + public String constraintName; + public List uniqueColumns; + } + + + /** + * The meta data of a field. + */ + + static class FieldDefinition { + String columnName; + Field field; + String dataType; + int length; + int scale; + boolean isPrimaryKey; + boolean isAutoIncrement; + boolean trim; + boolean nullable; + String defaultValue; + EnumType enumType; + boolean isPrimitive; + String constraint; + + Object getValue(Object obj) { + try { + return field.get(obj); + } catch (Exception e) { + throw new IciqlException(e); + } + } + + private Object initWithNewObject(Object obj) { + Object o = Utils.newObject(field.getType()); + setValue(obj, o); + return o; + } + + private void setValue(Object obj, Object o) { + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + Class targetType = field.getType(); + if (targetType.isEnum()) { + o = Utils.convertEnum(o, targetType, enumType); + } else { + o = Utils.convert(o, targetType); + } + field.set(obj, o); + } catch (IciqlException e) { + throw e; + } catch (Exception e) { + throw new IciqlException(e); + } + } + + private Object read(ResultSet rs, int columnIndex) { + try { + return rs.getObject(columnIndex); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + @Override + public int hashCode() { + return columnName.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof FieldDefinition) { + return o.hashCode() == hashCode(); + } + return false; + } + } + + public ArrayList fields = Utils.newArrayList(); + String schemaName; + String tableName; + String viewTableName; + int tableVersion; + List primaryKeyColumnNames; + boolean memoryTable; + boolean multiplePrimitiveBools; + + private boolean createIfRequired = true; + private Class clazz; + private IdentityHashMap fieldMap = Utils.newIdentityHashMap(); + private ArrayList indexes = Utils.newArrayList(); + private ArrayList constraintsForeignKey = Utils.newArrayList(); + private ArrayList constraintsUnique = Utils.newArrayList(); + + TableDefinition(Class clazz) { + this.clazz = clazz; + schemaName = null; + tableName = clazz.getSimpleName(); + } + + Class getModelClass() { + return clazz; + } + + List getFields() { + return fields; + } + + void defineSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + void defineTableName(String tableName) { + this.tableName = tableName; + } + + void defineViewTableName(String viewTableName) { + this.viewTableName = viewTableName; + } + + void defineMemoryTable() { + this.memoryTable = true; + } + + void defineSkipCreate() { + this.createIfRequired = false; + } + + /** + * Define a primary key by the specified model fields. + * + * @param modelFields + * the ordered list of model fields + */ + void definePrimaryKey(Object[] modelFields) { + List columnNames = mapColumnNames(modelFields); + setPrimaryKey(columnNames); + } + + /** + * Define a primary key by the specified column names. + * + * @param columnNames + * the ordered list of column names + */ + private void setPrimaryKey(List columnNames) { + primaryKeyColumnNames = Utils.newArrayList(columnNames); + List pkNames = Utils.newArrayList(); + for (String name : columnNames) { + pkNames.add(name.toLowerCase()); + } + // set isPrimaryKey flag for all field definitions + for (FieldDefinition fieldDefinition : fieldMap.values()) { + fieldDefinition.isPrimaryKey = pkNames.contains(fieldDefinition.columnName.toLowerCase()); + } + } + + private String getColumnName(A fieldObject) { + FieldDefinition def = fieldMap.get(fieldObject); + return def == null ? null : def.columnName; + } + + private ArrayList mapColumnNames(Object[] columns) { + ArrayList columnNames = Utils.newArrayList(); + for (Object column : columns) { + columnNames.add(getColumnName(column)); + } + return columnNames; + } + + /** + * Defines an index with the specified model fields. + * + * @param name + * the index name (optional) + * @param type + * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH) + * @param modelFields + * the ordered list of model fields + */ + void defineIndex(String name, IndexType type, Object[] modelFields) { + List columnNames = mapColumnNames(modelFields); + addIndex(name, type, columnNames); + } + + /** + * Defines an index with the specified column names. + * + * @param type + * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH) + * @param columnNames + * the ordered list of column names + */ + private void addIndex(String name, IndexType type, List columnNames) { + IndexDefinition index = new IndexDefinition(); + if (StringUtils.isNullOrEmpty(name)) { + index.indexName = tableName + "_idx_" + indexes.size(); + } else { + index.indexName = name; + } + index.columnNames = Utils.newArrayList(columnNames); + index.type = type; + indexes.add(index); + } + + /** + * Defines an unique constraint with the specified model fields. + * + * @param name + * the constraint name (optional) + * @param modelFields + * the ordered list of model fields + */ + void defineConstraintUnique(String name, Object[] modelFields) { + List columnNames = mapColumnNames(modelFields); + addConstraintUnique(name, columnNames); + } + + /** + * Defines an unique constraint. + * + * @param name + * @param columnNames + */ + private void addConstraintUnique(String name, List columnNames) { + ConstraintUniqueDefinition constraint = new ConstraintUniqueDefinition(); + if (StringUtils.isNullOrEmpty(name)) { + constraint.constraintName = tableName + "_unique_" + constraintsUnique.size(); + } else { + constraint.constraintName = name; + } + constraint.uniqueColumns = Utils.newArrayList(columnNames); + constraintsUnique.add(constraint); + } + + /** + * Defines a foreign key constraint with the specified model fields. + * + * @param name + * the constraint name (optional) + * @param modelFields + * the ordered list of model fields + */ + void defineForeignKey(String name, Object[] modelFields, String refTableName, Object[] refModelFields, + ConstraintDeleteType deleteType, ConstraintUpdateType updateType, + ConstraintDeferrabilityType deferrabilityType) { + List columnNames = mapColumnNames(modelFields); + List referenceColumnNames = mapColumnNames(refModelFields); + addConstraintForeignKey(name, columnNames, refTableName, referenceColumnNames, + deleteType, updateType, deferrabilityType); + } + + void defineColumnName(Object column, String columnName) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.columnName = columnName; + } + } + + void defineAutoIncrement(Object column) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.isAutoIncrement = true; + } + } + + void defineLength(Object column, int length) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.length = length; + } + } + + void defineScale(Object column, int scale) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.scale = scale; + } + } + + void defineTrim(Object column) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.trim = true; + } + } + + void defineNullable(Object column, boolean isNullable) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.nullable = isNullable; + } + } + + void defineDefaultValue(Object column, String defaultValue) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.defaultValue = defaultValue; + } + } + + void defineConstraint(Object column, String constraint) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.constraint = constraint; + } + } + + void mapFields() { + boolean byAnnotationsOnly = false; + boolean inheritColumns = false; + if (clazz.isAnnotationPresent(IQTable.class)) { + IQTable tableAnnotation = clazz.getAnnotation(IQTable.class); + byAnnotationsOnly = tableAnnotation.annotationsOnly(); + inheritColumns = tableAnnotation.inheritColumns(); + } + + if (clazz.isAnnotationPresent(IQView.class)) { + IQView viewAnnotation = clazz.getAnnotation(IQView.class); + byAnnotationsOnly = viewAnnotation.annotationsOnly(); + inheritColumns = viewAnnotation.inheritColumns(); + } + + List classFields = Utils.newArrayList(); + classFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + if (inheritColumns) { + Class superClass = clazz.getSuperclass(); + classFields.addAll(Arrays.asList(superClass.getDeclaredFields())); + + if (superClass.isAnnotationPresent(IQView.class)) { + IQView superView = superClass.getAnnotation(IQView.class); + if (superView.inheritColumns()) { + // inherit columns from super.super.class + Class superSuperClass = superClass.getSuperclass(); + classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields())); + } + } else if (superClass.isAnnotationPresent(IQTable.class)) { + IQTable superTable = superClass.getAnnotation(IQTable.class); + if (superTable.inheritColumns()) { + // inherit columns from super.super.class + Class superSuperClass = superClass.getSuperclass(); + classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields())); + } + } + } + + Set uniqueFields = new LinkedHashSet(); + T defaultObject = Db.instance(clazz); + for (Field f : classFields) { + // check if we should skip this field + if (f.isAnnotationPresent(IQIgnore.class)) { + continue; + } + + // default to field name + String columnName = f.getName(); + boolean isAutoIncrement = false; + boolean isPrimaryKey = false; + int length = 0; + int scale = 0; + boolean trim = false; + boolean nullable = !f.getType().isPrimitive(); + EnumType enumType = null; + String defaultValue = ""; + String constraint = ""; + // configure Java -> SQL enum mapping + if (f.getType().isEnum()) { + enumType = EnumType.DEFAULT_TYPE; + if (f.getType().isAnnotationPresent(IQEnum.class)) { + // enum definition is annotated for all instances + IQEnum iqenum = f.getType().getAnnotation(IQEnum.class); + enumType = iqenum.value(); + } + if (f.isAnnotationPresent(IQEnum.class)) { + // this instance of the enum is annotated + IQEnum iqenum = f.getAnnotation(IQEnum.class); + enumType = iqenum.value(); + } + } + + // try using default object + try { + f.setAccessible(true); + Object value = f.get(defaultObject); + if (value != null) { + if (value.getClass().isEnum()) { + // enum default, convert to target type + Enum anEnum = (Enum) value; + Object o = Utils.convertEnum(anEnum, enumType); + defaultValue = ModelUtils.formatDefaultValue(o); + } else { + // object default + defaultValue = ModelUtils.formatDefaultValue(value); + } + } + } catch (IllegalAccessException e) { + throw new IciqlException(e, "failed to get default object for {0}", columnName); + } + + boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class); + if (hasAnnotation) { + IQColumn col = f.getAnnotation(IQColumn.class); + if (!StringUtils.isNullOrEmpty(col.name())) { + columnName = col.name(); + } + isAutoIncrement = col.autoIncrement(); + isPrimaryKey = col.primaryKey(); + length = col.length(); + scale = col.scale(); + trim = col.trim(); + nullable = col.nullable(); + + // annotation overrides + if (!StringUtils.isNullOrEmpty(col.defaultValue())) { + defaultValue = col.defaultValue(); + } + } + + boolean hasConstraint = f.isAnnotationPresent(IQConstraint.class); + if (hasConstraint) { + IQConstraint con = f.getAnnotation(IQConstraint.class); + // annotation overrides + if (!StringUtils.isNullOrEmpty(con.value())) { + constraint = con.value(); + } + } + + boolean reflectiveMatch = !byAnnotationsOnly; + if (reflectiveMatch || hasAnnotation || hasConstraint) { + FieldDefinition fieldDef = new FieldDefinition(); + fieldDef.isPrimitive = f.getType().isPrimitive(); + fieldDef.field = f; + fieldDef.columnName = columnName; + fieldDef.isAutoIncrement = isAutoIncrement; + fieldDef.isPrimaryKey = isPrimaryKey; + fieldDef.length = length; + fieldDef.scale = scale; + fieldDef.trim = trim; + fieldDef.nullable = nullable; + fieldDef.defaultValue = defaultValue; + fieldDef.enumType = enumType; + fieldDef.dataType = ModelUtils.getDataType(fieldDef); + fieldDef.constraint = constraint; + uniqueFields.add(fieldDef); + } + } + fields.addAll(uniqueFields); + + List primaryKey = Utils.newArrayList(); + int primitiveBoolean = 0; + for (FieldDefinition fieldDef : fields) { + if (fieldDef.isPrimaryKey) { + primaryKey.add(fieldDef.columnName); + } + if (fieldDef.isPrimitive && fieldDef.field.getType().equals(boolean.class)) { + primitiveBoolean++; + } + } + if (primitiveBoolean > 1) { + multiplePrimitiveBools = true; + IciqlLogger + .warn("Model {0} has multiple primitive booleans! Possible where,set,join clause problem!"); + } + if (primaryKey.size() > 0) { + setPrimaryKey(primaryKey); + } + } + + void checkMultipleBooleans() { + if (multiplePrimitiveBools) { + throw new IciqlException( + "Can not explicitly reference a primitive boolean if there are multiple boolean fields in your model class!"); + } + } + + void checkMultipleEnums(Object o) { + if (o == null) { + return; + } + Class clazz = o.getClass(); + if (!clazz.isEnum()) { + return; + } + + int fieldCount = 0; + for (FieldDefinition fieldDef : fields) { + Class targetType = fieldDef.field.getType(); + if (clazz.equals(targetType)) { + fieldCount++; + } + } + + if (fieldCount > 1) { + throw new IciqlException( + "Can not explicitly reference {0} because there are {1} {0} fields in your model class!", + clazz.getSimpleName(), fieldCount); + } + } + + /** + * Optionally truncates strings to the maximum length and converts + * java.lang.Enum types to Strings or Integers. + */ + Object getValue(Object obj, FieldDefinition field) { + Object value = field.getValue(obj); + if (value == null) { + return value; + } + if (field.enumType != null) { + // convert enumeration to INT or STRING + Enum iqenum = (Enum) value; + switch (field.enumType) { + case NAME: + if (field.trim && field.length > 0) { + if (iqenum.name().length() > field.length) { + return iqenum.name().substring(0, field.length); + } + } + return iqenum.name(); + case ORDINAL: + return iqenum.ordinal(); + case ENUMID: + if (!EnumId.class.isAssignableFrom(value.getClass())) { + throw new IciqlException(field.field.getName() + " does not implement EnumId!"); + } + EnumId enumid = (EnumId) value; + return enumid.enumId(); + } + } + + if (field.trim && field.length > 0) { + if (value instanceof String) { + // clip strings + String s = (String) value; + if (s.length() > field.length) { + return s.substring(0, field.length); + } + return s; + } + return value; + } + + // return the value unchanged + return value; + } + + PreparedStatement createInsertStatement(Db db, Object obj, boolean returnKey) { + SQLStatement stat = new SQLStatement(db); + StatementBuilder buff = new StatementBuilder("INSERT INTO "); + buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('('); + for (FieldDefinition field : fields) { + if (skipInsertField(field, obj)) { + continue; + } + buff.appendExceptFirst(", "); + buff.append(db.getDialect().prepareColumnName(field.columnName)); + } + buff.append(") VALUES("); + buff.resetCount(); + for (FieldDefinition field : fields) { + if (skipInsertField(field, obj)) { + continue; + } + buff.appendExceptFirst(", "); + buff.append('?'); + Object value = getValue(obj, field); + if (value == null && !field.nullable) { + // try to interpret and instantiate a default value + value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); + } + stat.addParameter(value); + } + buff.append(')'); + stat.setSQL(buff.toString()); + IciqlLogger.insert(stat.getSQL()); + return stat.prepare(returnKey); + } + + long insert(Db db, Object obj, boolean returnKey) { + if (!StringUtils.isNullOrEmpty(viewTableName)) { + throw new IciqlException("Iciql does not support inserting rows into views!"); + } + SQLStatement stat = new SQLStatement(db); + StatementBuilder buff = new StatementBuilder("INSERT INTO "); + buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('('); + for (FieldDefinition field : fields) { + if (skipInsertField(field, obj)) { + continue; + } + buff.appendExceptFirst(", "); + buff.append(db.getDialect().prepareColumnName(field.columnName)); + } + buff.append(") VALUES("); + buff.resetCount(); + for (FieldDefinition field : fields) { + if (skipInsertField(field, obj)) { + continue; + } + buff.appendExceptFirst(", "); + buff.append('?'); + Object value = getValue(obj, field); + if (value == null && !field.nullable) { + // try to interpret and instantiate a default value + value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); + } + stat.addParameter(value); + } + buff.append(')'); + stat.setSQL(buff.toString()); + IciqlLogger.insert(stat.getSQL()); + if (returnKey) { + return stat.executeInsert(); + } + return stat.executeUpdate(); + } + + private boolean skipInsertField(FieldDefinition field, Object obj) { + if (field.isAutoIncrement) { + Object value = getValue(obj, field); + if (field.isPrimitive) { + // skip uninitialized primitive autoincrement values + if (value.toString().equals("0")) { + return true; + } + } else if (value == null) { + // skip null object autoincrement values + return true; + } + } else { + // conditionally skip insert of null + Object value = getValue(obj, field); + if (value == null) { + if (field.nullable) { + // skip null assignment, field is nullable + return true; + } else if (StringUtils.isNullOrEmpty(field.defaultValue)) { + IciqlLogger.warn("no default value, skipping null insert assignment for {0}.{1}", + tableName, field.columnName); + return true; + } + } + } + return false; + } + + int merge(Db db, Object obj) { + if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) { + throw new IllegalStateException("No primary key columns defined for table " + obj.getClass() + + " - no update possible"); + } + SQLStatement stat = new SQLStatement(db); + db.getDialect().prepareMerge(stat, schemaName, tableName, this, obj); + IciqlLogger.merge(stat.getSQL()); + return stat.executeUpdate(); + } + + int update(Db db, Object obj) { + if (!StringUtils.isNullOrEmpty(viewTableName)) { + throw new IciqlException("Iciql does not support updating rows in views!"); + } + if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) { + throw new IllegalStateException("No primary key columns defined for table " + obj.getClass() + + " - no update possible"); + } + SQLStatement stat = new SQLStatement(db); + StatementBuilder buff = new StatementBuilder("UPDATE "); + buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" SET "); + buff.resetCount(); + + for (FieldDefinition field : fields) { + if (!field.isPrimaryKey) { + Object value = getValue(obj, field); + if (value == null && !field.nullable) { + // try to interpret and instantiate a default value + value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); + } + buff.appendExceptFirst(", "); + buff.append(db.getDialect().prepareColumnName(field.columnName)); + buff.append(" = ?"); + stat.addParameter(value); + } + } + Object alias = Utils.newObject(obj.getClass()); + Query query = Query.from(db, alias); + boolean firstCondition = true; + for (FieldDefinition field : fields) { + if (field.isPrimaryKey) { + Object fieldAlias = field.getValue(alias); + Object value = field.getValue(obj); + if (field.isPrimitive) { + fieldAlias = query.getPrimitiveAliasByValue(fieldAlias); + } + if (!firstCondition) { + query.addConditionToken(ConditionAndOr.AND); + } + firstCondition = false; + query.addConditionToken(new Condition(fieldAlias, value, CompareType.EQUAL)); + } + } + stat.setSQL(buff.toString()); + query.appendWhere(stat); + IciqlLogger.update(stat.getSQL()); + return stat.executeUpdate(); + } + + int delete(Db db, Object obj) { + if (!StringUtils.isNullOrEmpty(viewTableName)) { + throw new IciqlException("Iciql does not support deleting rows from views!"); + } + if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) { + throw new IllegalStateException("No primary key columns defined for table " + obj.getClass() + + " - no update possible"); + } + SQLStatement stat = new SQLStatement(db); + StatementBuilder buff = new StatementBuilder("DELETE FROM "); + buff.append(db.getDialect().prepareTableName(schemaName, tableName)); + buff.resetCount(); + Object alias = Utils.newObject(obj.getClass()); + Query query = Query.from(db, alias); + boolean firstCondition = true; + for (FieldDefinition field : fields) { + if (field.isPrimaryKey) { + Object fieldAlias = field.getValue(alias); + Object value = field.getValue(obj); + if (field.isPrimitive) { + fieldAlias = query.getPrimitiveAliasByValue(fieldAlias); + } + if (!firstCondition) { + query.addConditionToken(ConditionAndOr.AND); + } + firstCondition = false; + query.addConditionToken(new Condition(fieldAlias, value, CompareType.EQUAL)); + } + } + stat.setSQL(buff.toString()); + query.appendWhere(stat); + IciqlLogger.delete(stat.getSQL()); + return stat.executeUpdate(); + } + + TableDefinition createIfRequired(Db db) { + // globally enable/disable check of create if required + if (db.getSkipCreate()) { + return this; + } + if (!createIfRequired) { + // skip table and index creation + // but still check for upgrades + db.upgradeTable(this); + return this; + } + if (db.hasCreated(clazz)) { + return this; + } + SQLStatement stat = new SQLStatement(db); + if (StringUtils.isNullOrEmpty(viewTableName)) { + db.getDialect().prepareCreateTable(stat, this); + } else { + db.getDialect().prepareCreateView(stat, this); + } + IciqlLogger.create(stat.getSQL()); + try { + stat.executeUpdate(); + } catch (IciqlException e) { + if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) { + throw e; + } + } + + // create indexes + for (IndexDefinition index : indexes) { + stat = new SQLStatement(db); + db.getDialect().prepareCreateIndex(stat, schemaName, tableName, index); + IciqlLogger.create(stat.getSQL()); + try { + stat.executeUpdate(); + } catch (IciqlException e) { + if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS + && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) { + throw e; + } + } + } + + // create unique constraints + for (ConstraintUniqueDefinition constraint : constraintsUnique) { + stat = new SQLStatement(db); + db.getDialect().prepareCreateConstraintUnique(stat, schemaName, tableName, constraint); + IciqlLogger.create(stat.getSQL()); + try { + stat.executeUpdate(); + } catch (IciqlException e) { + if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS + && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) { + throw e; + } + } + } + + // create foreign keys constraints + for (ConstraintForeignKeyDefinition constraint : constraintsForeignKey) { + stat = new SQLStatement(db); + db.getDialect().prepareCreateConstraintForeignKey(stat, schemaName, tableName, constraint); + IciqlLogger.create(stat.getSQL()); + try { + stat.executeUpdate(); + } catch (IciqlException e) { + if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS + && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) { + throw e; + } + } + } + + // tables are created using IF NOT EXISTS + // but we may still need to upgrade + db.upgradeTable(this); + return this; + } + + void mapObject(Object obj) { + fieldMap.clear(); + initObject(obj, fieldMap); + + if (clazz.isAnnotationPresent(IQSchema.class)) { + IQSchema schemaAnnotation = clazz.getAnnotation(IQSchema.class); + // setup schema name mapping, if properly annotated + if (!StringUtils.isNullOrEmpty(schemaAnnotation.value())) { + schemaName = schemaAnnotation.value(); + } + } + + if (clazz.isAnnotationPresent(IQTable.class)) { + IQTable tableAnnotation = clazz.getAnnotation(IQTable.class); + + // setup table name mapping, if properly annotated + if (!StringUtils.isNullOrEmpty(tableAnnotation.name())) { + tableName = tableAnnotation.name(); + } + + // allow control over createTableIfRequired() + createIfRequired = tableAnnotation.create(); + + // model version + if (clazz.isAnnotationPresent(IQVersion.class)) { + IQVersion versionAnnotation = clazz.getAnnotation(IQVersion.class); + if (versionAnnotation.value() > 0) { + tableVersion = versionAnnotation.value(); + } + } + + // setup the primary index, if properly annotated + if (tableAnnotation.primaryKey().length > 0) { + List primaryKey = Utils.newArrayList(); + primaryKey.addAll(Arrays.asList(tableAnnotation.primaryKey())); + setPrimaryKey(primaryKey); + } + } + + if (clazz.isAnnotationPresent(IQView.class)) { + IQView viewAnnotation = clazz.getAnnotation(IQView.class); + + // setup view name mapping, if properly annotated + // set this as the table name so it fits in seemlessly with iciql + if (!StringUtils.isNullOrEmpty(viewAnnotation.name())) { + tableName = viewAnnotation.name(); + } else { + tableName = clazz.getSimpleName(); + } + + // setup source table name mapping, if properly annotated + if (!StringUtils.isNullOrEmpty(viewAnnotation.tableName())) { + viewTableName = viewAnnotation.tableName(); + } else { + // check for IQTable annotation on super class + Class superClass = clazz.getSuperclass(); + if (superClass.isAnnotationPresent(IQTable.class)) { + IQTable table = superClass.getAnnotation(IQTable.class); + if (StringUtils.isNullOrEmpty(table.name())) { + // super.SimpleClassName + viewTableName = superClass.getSimpleName(); + } else { + // super.IQTable.name() + viewTableName = table.name(); + } + } else if (superClass.isAnnotationPresent(IQView.class)) { + // super class is a view + IQView parentView = superClass.getAnnotation(IQView.class); + if (StringUtils.isNullOrEmpty(parentView.tableName())) { + // parent view does not define a tableName, must be inherited + Class superParent = superClass.getSuperclass(); + if (superParent != null && superParent.isAnnotationPresent(IQTable.class)) { + IQTable superParentTable = superParent.getAnnotation(IQTable.class); + if (StringUtils.isNullOrEmpty(superParentTable.name())) { + // super.super.SimpleClassName + viewTableName = superParent.getSimpleName(); + } else { + // super.super.IQTable.name() + viewTableName = superParentTable.name(); + } + } + } else { + // super.IQView.tableName() + viewTableName = parentView.tableName(); + } + } + + if (StringUtils.isNullOrEmpty(viewTableName)) { + // still missing view table name + throw new IciqlException("View model class \"{0}\" is missing a table name!", tableName); + } + } + + // allow control over createTableIfRequired() + createIfRequired = viewAnnotation.create(); + } + + if (clazz.isAnnotationPresent(IQIndex.class)) { + // single table index + IQIndex index = clazz.getAnnotation(IQIndex.class); + addIndex(index); + } + + if (clazz.isAnnotationPresent(IQIndexes.class)) { + // multiple table indexes + IQIndexes indexes = clazz.getAnnotation(IQIndexes.class); + for (IQIndex index : indexes.value()) { + addIndex(index); + } + } + + if (clazz.isAnnotationPresent(IQContraintUnique.class)) { + // single table unique constraint + IQContraintUnique constraint = clazz.getAnnotation(IQContraintUnique.class); + addConstraintUnique(constraint); + } + + if (clazz.isAnnotationPresent(IQContraintsUnique.class)) { + // multiple table unique constraints + IQContraintsUnique constraints = clazz.getAnnotation(IQContraintsUnique.class); + for (IQContraintUnique constraint : constraints.value()) { + addConstraintUnique(constraint); + } + } + + if (clazz.isAnnotationPresent(IQContraintForeignKey.class)) { + // single table constraint + IQContraintForeignKey constraint = clazz.getAnnotation(IQContraintForeignKey.class); + addConstraintForeignKey(constraint); + } + + if (clazz.isAnnotationPresent(IQContraintsForeignKey.class)) { + // multiple table constraints + IQContraintsForeignKey constraints = clazz.getAnnotation(IQContraintsForeignKey.class); + for (IQContraintForeignKey constraint : constraints.value()) { + addConstraintForeignKey(constraint); + } + } + + } + + private void addConstraintForeignKey(IQContraintForeignKey constraint) { + List foreignColumns = Arrays.asList(constraint.foreignColumns()); + List referenceColumns = Arrays.asList(constraint.referenceColumns()); + addConstraintForeignKey(constraint.name(), foreignColumns, constraint.referenceName(), referenceColumns, constraint.deleteType(), constraint.updateType(), constraint.deferrabilityType()); + } + + private void addConstraintUnique(IQContraintUnique constraint) { + List uniqueColumns = Arrays.asList(constraint.uniqueColumns()); + addConstraintUnique(constraint.name(), uniqueColumns); + } + + /** + * Defines a foreign key constraint with the specified parameters. + * + * @param name + * name of the constraint + * @param foreignColumns + * list of columns declared as foreign + * @param referenceName + * reference table name + * @param referenceColumns + * list of columns used in reference table + * @param deleteType + * action on delete + * @param updateType + * action on update + * @param deferrabilityType + * deferrability mode + */ + private void addConstraintForeignKey(String name, + List foreignColumns, String referenceName, + List referenceColumns, ConstraintDeleteType deleteType, + ConstraintUpdateType updateType, ConstraintDeferrabilityType deferrabilityType) { + ConstraintForeignKeyDefinition constraint = new ConstraintForeignKeyDefinition(); + if (StringUtils.isNullOrEmpty(name)) { + constraint.constraintName = tableName + "_fkey_" + constraintsForeignKey.size(); + } else { + constraint.constraintName = name; + } + constraint.foreignColumns = Utils.newArrayList(foreignColumns); + constraint.referenceColumns = Utils.newArrayList(referenceColumns); + constraint.referenceTable = referenceName; + constraint.deleteType = deleteType; + constraint.updateType = updateType; + constraint.deferrabilityType = deferrabilityType; + constraintsForeignKey.add(constraint); + } + + private void addIndex(IQIndex index) { + List columns = Arrays.asList(index.value()); + addIndex(index.name(), index.type(), columns); + } + + List getIndexes() { + return indexes; + } + + List getContraintsUnique() { + return constraintsUnique; + } + + List getContraintsForeignKey() { + return constraintsForeignKey; + } + + private void initObject(Object obj, Map map) { + for (FieldDefinition def : fields) { + Object newValue = def.initWithNewObject(obj); + map.put(newValue, def); + } + } + + void initSelectObject(SelectTable table, Object obj, Map> map) { + for (FieldDefinition def : fields) { + Object newValue = def.initWithNewObject(obj); + SelectColumn column = new SelectColumn(table, def); + map.put(newValue, column); + } + } + + /** + * Most queries executed by iciql have named select lists (select alpha, + * beta where...) but sometimes a wildcard select is executed (select *). + * When a wildcard query is executed on a table that has more columns than + * are mapped in your model object, this creates a column mapping issue. + * JaQu assumed that you can always use the integer index of the + * reflectively mapped field definition to determine position in the result + * set. + * + * This is not always true. + * + * iciql identifies when a select * query is executed and maps column names + * to a column index from the result set. If the select statement is + * explicit, then the standard assumed column index is used instead. + * + * @param rs + * @return + */ + int[] mapColumns(boolean wildcardSelect, ResultSet rs) { + int[] columns = new int[fields.size()]; + for (int i = 0; i < fields.size(); i++) { + try { + FieldDefinition def = fields.get(i); + int columnIndex; + if (wildcardSelect) { + // select * + // create column index by field name + columnIndex = rs.findColumn(def.columnName); + } else { + // select alpha, beta, gamma, etc + // explicit select order + columnIndex = i + 1; + } + columns[i] = columnIndex; + } catch (SQLException s) { + throw new IciqlException(s); + } + } + return columns; + } + + void readRow(Object item, ResultSet rs, int[] columns) { + for (int i = 0; i < fields.size(); i++) { + FieldDefinition def = fields.get(i); + int index = columns[i]; + Object o = def.read(rs, index); + def.setValue(item, o); + } + } + + void appendSelectList(SQLStatement stat) { + for (int i = 0; i < fields.size(); i++) { + if (i > 0) { + stat.appendSQL(", "); + } + FieldDefinition def = fields.get(i); + stat.appendColumn(def.columnName); + } + } + + void appendSelectList(SQLStatement stat, Query query, X x) { + // select t0.col1, t0.col2, t0.col3... + // select table1.col1, table1.col2, table1.col3... + String selectDot = ""; + SelectTable sel = query.getSelectTable(x); + if (sel != null) { + if (query.isJoin()) { + selectDot = sel.getAs() + "."; + } else { + String sn = sel.getAliasDefinition().schemaName; + String tn = sel.getAliasDefinition().tableName; + selectDot = query.getDb().getDialect().prepareTableName(sn, tn) + "."; + } + } + + for (int i = 0; i < fields.size(); i++) { + if (i > 0) { + stat.appendSQL(", "); + } + stat.appendSQL(selectDot); + FieldDefinition def = fields.get(i); + if (def.isPrimitive) { + Object obj = def.getValue(x); + Object alias = query.getPrimitiveAliasByValue(obj); + query.appendSQL(stat, x, alias); + } else { + Object obj = def.getValue(x); + query.appendSQL(stat, x, obj); + } + } + } +} diff --git a/src/main/java/com/iciql/TableInspector.java b/src/main/java/com/iciql/TableInspector.java new file mode 100644 index 0000000..b717203 --- /dev/null +++ b/src/main/java/com/iciql/TableInspector.java @@ -0,0 +1,723 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * Copyright 2012 Frédéric Gaillard. + * + * 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; + +import static com.iciql.ValidationRemark.consider; +import static com.iciql.ValidationRemark.error; +import static com.iciql.ValidationRemark.warn; +import static com.iciql.util.JdbcUtils.closeSilently; +import static com.iciql.util.StringUtils.isNullOrEmpty; +import static java.text.MessageFormat.format; + +import java.io.Serializable; +import java.lang.reflect.Modifier; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQIndex; +import com.iciql.Iciql.IQIndexes; +import com.iciql.Iciql.IQSchema; +import com.iciql.Iciql.IQTable; +import com.iciql.Iciql.IndexType; +import com.iciql.TableDefinition.ConstraintForeignKeyDefinition; +import com.iciql.TableDefinition.ConstraintUniqueDefinition; +import com.iciql.TableDefinition.FieldDefinition; +import com.iciql.TableDefinition.IndexDefinition; +import com.iciql.util.StatementBuilder; +import com.iciql.util.StringUtils; +import com.iciql.util.Utils; + +/** + * Class to inspect the contents of a particular table including its indexes. + * This class does the bulk of the work in terms of model generation and model + * validation. + */ +public class TableInspector { + + private String schema; + private String table; + private Class dateTimeClass; + private List primaryKeys = Utils.newArrayList(); + private Map indexes; + private Map columns; + private final String eol = "\n"; + + TableInspector(String schema, String table, Class dateTimeClass) { + this.schema = schema; + this.table = table; + this.dateTimeClass = dateTimeClass; + } + + /** + * Tests to see if this TableInspector represents schema.table. + *

+ * + * @param schema + * the schema name + * @param table + * the table name + * @return true if the table matches + */ + boolean matches(String schema, String table) { + if (isNullOrEmpty(schema)) { + // table name matching + return this.table.equalsIgnoreCase(table); + } else if (isNullOrEmpty(table)) { + // schema name matching + return this.schema.equalsIgnoreCase(schema); + } else { + // exact table matching + return this.schema.equalsIgnoreCase(schema) && this.table.equalsIgnoreCase(table); + } + } + + /** + * Reads the DatabaseMetaData for the details of this table including + * primary keys and indexes. + * + * @param metaData + * the database meta data + */ + void read(DatabaseMetaData metaData) throws SQLException { + ResultSet rs = null; + + // primary keys + try { + rs = metaData.getPrimaryKeys(null, schema, table); + while (rs.next()) { + String c = rs.getString("COLUMN_NAME"); + primaryKeys.add(c); + } + closeSilently(rs); + + // indexes + rs = metaData.getIndexInfo(null, schema, table, false, true); + indexes = Utils.newHashMap(); + while (rs.next()) { + IndexInspector info = new IndexInspector(rs); + if (info.type.equals(IndexType.UNIQUE)) { + String name = info.name.toLowerCase(); + if (name.startsWith("primary") || name.startsWith("sys_idx_sys_pk") + || name.startsWith("sql") || name.endsWith("_pkey")) { + // skip primary key indexes + continue; + } + } + if (indexes.containsKey(info.name)) { + indexes.get(info.name).addColumn(rs); + } else { + indexes.put(info.name, info); + } + } + closeSilently(rs); + + // columns + rs = metaData.getColumns(null, schema, table, null); + columns = Utils.newHashMap(); + while (rs.next()) { + ColumnInspector col = new ColumnInspector(); + col.name = rs.getString("COLUMN_NAME"); + col.type = rs.getString("TYPE_NAME"); + col.clazz = ModelUtils.getClassForSqlType(col.type, dateTimeClass); + col.size = rs.getInt("COLUMN_SIZE"); + col.nullable = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable; + 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; + } + } + if (!col.isAutoIncrement) { + col.defaultValue = rs.getString("COLUMN_DEF"); + } + columns.put(col.name.toLowerCase(), col); + } + } finally { + closeSilently(rs); + } + } + + /** + * Generates a model (class definition) from this table. The model includes + * indexes, primary keys, default values, lengths, and nullables. + * information. + *

+ * The caller may optionally set a destination package name, whether or not + * to include the schema name (setting schema can be a problem when using + * the model between databases), and if to automatically trim strings for + * those that have a maximum length. + *

+ * + * @param packageName + * @param annotateSchema + * @param trimStrings + * @return a complete model (class definition) for this table as a string + */ + String generateModel(String packageName, boolean annotateSchema, boolean trimStrings) { + + // import statements + Set imports = Utils.newHashSet(); + imports.add(Serializable.class.getCanonicalName()); + imports.add(IQSchema.class.getCanonicalName()); + imports.add(IQTable.class.getCanonicalName()); + imports.add(IQIndexes.class.getCanonicalName()); + imports.add(IQIndex.class.getCanonicalName()); + imports.add(IQColumn.class.getCanonicalName()); + imports.add(IndexType.class.getCanonicalName()); + + // fields + StringBuilder fields = new StringBuilder(); + List sortedColumns = Utils.newArrayList(columns.values()); + Collections.sort(sortedColumns); + for (ColumnInspector col : sortedColumns) { + fields.append(generateColumn(imports, col, trimStrings)); + } + + // build complete class definition + StringBuilder model = new StringBuilder(); + if (!isNullOrEmpty(packageName)) { + // package + model.append("package " + packageName + ";"); + model.append(eol).append(eol); + } + + // imports + List sortedImports = new ArrayList(imports); + Collections.sort(sortedImports); + for (String imp : sortedImports) { + model.append("import ").append(imp).append(';').append(eol); + } + model.append(eol); + + // @IQSchema + if (annotateSchema && !isNullOrEmpty(schema)) { + model.append('@').append(IQSchema.class.getSimpleName()); + model.append('('); + AnnotationBuilder ap = new AnnotationBuilder(); + ap.addParameter(null, schema); + model.append(ap); + model.append(')').append(eol); + } + + // @IQTable + model.append('@').append(IQTable.class.getSimpleName()); + model.append('('); + + // IQTable annotation parameters + AnnotationBuilder ap = new AnnotationBuilder(); + ap.addParameter("name", table); + + if (primaryKeys.size() > 1) { + ap.addParameter("primaryKey", primaryKeys); + } + + // finish @IQTable annotation + model.append(ap); + model.append(')').append(eol); + + // @IQIndexes + // @IQIndex + String indexAnnotations = generateIndexAnnotations(); + if (!StringUtils.isNullOrEmpty(indexAnnotations)) { + model.append(indexAnnotations); + } + + // class declaration + String clazzName = ModelUtils.convertTableToClassName(table); + model.append(format("public class {0} implements Serializable '{'", clazzName)).append(eol); + model.append(eol); + model.append("\tprivate static final long serialVersionUID = 1L;").append(eol); + model.append(eol); + + // field declarations + model.append(fields); + + // default constructor + model.append("\t" + "public ").append(clazzName).append("() {").append(eol); + model.append("\t}").append(eol); + + // end of class body + model.append('}'); + model.trimToSize(); + return model.toString(); + } + + /** + * Generates the specified index annotation. + * + * @param ap + */ + String generateIndexAnnotations() { + if (indexes == null || indexes.size() == 0) { + // no matching indexes + return null; + } + AnnotationBuilder ap = new AnnotationBuilder(); + if (indexes.size() == 1) { + // single index + IndexInspector index = indexes.values().toArray(new IndexInspector[1])[0]; + ap.append(generateIndexAnnotation(index)); + ap.append(eol); + } else { + // multiple indexes + ap.append('@').append(IQIndexes.class.getSimpleName()); + ap.append("({"); + ap.resetCount(); + for (IndexInspector index : indexes.values()) { + ap.appendExceptFirst(", "); + ap.append(generateIndexAnnotation(index)); + } + ap.append("})").append(eol); + } + return ap.toString(); + } + + private String generateIndexAnnotation(IndexInspector index) { + AnnotationBuilder ap = new AnnotationBuilder(); + ap.append('@').append(IQIndex.class.getSimpleName()); + ap.append('('); + ap.resetCount(); + if (!StringUtils.isNullOrEmpty(index.name)) { + ap.addParameter("name", index.name); + } + if (!index.type.equals(IndexType.STANDARD)) { + ap.addEnum("type", index.type); + } + if (ap.getCount() > 0) { + // multiple fields specified + ap.addParameter("value", index.columns); + } else { + // default value + ap.addParameter(null, index.columns); + } + ap.append(')'); + return ap.toString(); + } + + private StatementBuilder generateColumn(Set imports, ColumnInspector col, boolean trimStrings) { + StatementBuilder sb = new StatementBuilder(); + Class clazz = col.clazz; + String column = ModelUtils.convertColumnToFieldName(col.name.toLowerCase()); + sb.append('\t'); + if (clazz == null) { + // unsupported type + clazz = Object.class; + sb.append("// unsupported type " + col.type); + } else { + // Imports + // don't import primitives, java.lang classes, or byte [] + if (clazz.getPackage() == null) { + } else if (clazz.getPackage().getName().equals("java.lang")) { + } else if (clazz.equals(byte[].class)) { + } else { + imports.add(clazz.getCanonicalName()); + } + // @IQColumn + sb.append('@').append(IQColumn.class.getSimpleName()); + + // IQColumn annotation parameters + AnnotationBuilder ap = new AnnotationBuilder(); + + // IQColumn.name + if (!col.name.equalsIgnoreCase(column)) { + ap.addParameter("name", col.name); + } + + // IQColumn.primaryKey + // composite primary keys are annotated on the table + if (col.isPrimaryKey && primaryKeys.size() == 1) { + ap.addParameter("primaryKey=true"); + } + + // IQColumn.length + if ((clazz == String.class) && (col.size > 0) && (col.size < Integer.MAX_VALUE)) { + ap.addParameter("length", col.size); + + // IQColumn.trim + if (trimStrings) { + ap.addParameter("trim=true"); + } + } else { + // IQColumn.AutoIncrement + if (col.isAutoIncrement) { + ap.addParameter("autoIncrement=true"); + } + } + + // IQColumn.nullable + if (!col.nullable) { + ap.addParameter("nullable=false"); + } + + // IQColumn.defaultValue + if (!isNullOrEmpty(col.defaultValue)) { + ap.addParameter("defaultValue=\"" + col.defaultValue + "\""); + } + + // add leading and trailing () + if (ap.length() > 0) { + ap.insert(0, '('); + ap.append(')'); + } + sb.append(ap); + } + sb.append(eol); + + // variable declaration + sb.append("\t" + "public "); + sb.append(clazz.getSimpleName()); + sb.append(' '); + sb.append(column); + sb.append(';'); + sb.append(eol).append(eol); + return sb; + } + + /** + * Validates that a table definition (annotated, interface, or both) matches + * the current state of the table and indexes in the database. Results are + * returned as a list of validation remarks which includes recommendations, + * warnings, and errors about the model. The caller may choose to have + * validate throw an exception on any validation ERROR. + * + * @param def + * the table definition + * @param throwError + * whether or not to throw an exception if an error was found + * @return a list if validation remarks + */ + List validate(TableDefinition def, boolean throwError) { + List remarks = Utils.newArrayList(); + + // model class definition validation + if (!Modifier.isPublic(def.getModelClass().getModifiers())) { + remarks.add(error(table, "SCHEMA", + format("Class {0} MUST BE PUBLIC!", def.getModelClass().getCanonicalName())).throwError( + throwError)); + } + + // Schema Validation + if (!isNullOrEmpty(schema)) { + if (isNullOrEmpty(def.schemaName)) { + remarks.add(consider(table, "SCHEMA", + format("@{0}(\"{1}\")", IQSchema.class.getSimpleName(), schema))); + } else if (!schema.equalsIgnoreCase(def.schemaName)) { + remarks.add(error( + table, + "SCHEMA", + format("@{0}(\"{1}\") != {2}", IQSchema.class.getSimpleName(), def.schemaName, schema)) + .throwError(throwError)); + } + } + + // index validation + for (IndexInspector index : indexes.values()) { + validate(remarks, def, index, throwError); + } + + // field column validation + for (FieldDefinition fieldDef : def.getFields()) { + validate(remarks, fieldDef, throwError); + } + return remarks; + } + + /** + * Validates an inspected index from the database against the + * IndexDefinition within the TableDefinition. + */ + private void validate(List remarks, TableDefinition def, IndexInspector index, + boolean throwError) { + List defIndexes = def.getIndexes(); + if (defIndexes.size() > indexes.size()) { + remarks.add(warn(table, IndexType.STANDARD.name(), "More model indexes than database indexes")); + } else if (defIndexes.size() < indexes.size()) { + remarks.add(warn(table, IndexType.STANDARD.name(), "Model class is missing indexes")); + } + // TODO complete index validation. + // need to actually compare index types and columns within each index. + + // TODO add constraints validation + List defContraintsU = def.getContraintsUnique(); + List defContraintsFK = def.getContraintsForeignKey(); + } + + /** + * Validates a column against the model's field definition. Checks for + * existence, supported type, type mapping, default value, defined lengths, + * primary key, autoincrement. + */ + private void validate(List remarks, FieldDefinition fieldDef, boolean throwError) { + // unknown field + if (!columns.containsKey(fieldDef.columnName.toLowerCase())) { + // unknown column mapping + remarks.add(error(table, fieldDef, "Does not exist in database!").throwError(throwError)); + return; + } + ColumnInspector col = columns.get(fieldDef.columnName.toLowerCase()); + Class fieldClass = fieldDef.field.getType(); + Class jdbcClass = ModelUtils.getClassForSqlType(col.type, dateTimeClass); + + // supported type check + // iciql maps to VARCHAR for unsupported types. + if (fieldDef.dataType.equals("VARCHAR") && (fieldClass != String.class)) { + remarks.add(error(table, fieldDef, + "iciql does not currently implement support for " + fieldClass.getName()).throwError( + throwError)); + } + // number types + if (!fieldClass.equals(jdbcClass)) { + if (Number.class.isAssignableFrom(fieldClass)) { + remarks.add(warn( + table, + col, + format("Precision mismatch: ModelObject={0}, ColumnObject={1}", + fieldClass.getSimpleName(), jdbcClass.getSimpleName()))); + } else { + if (!Date.class.isAssignableFrom(jdbcClass)) { + remarks.add(warn( + table, + col, + format("Object Mismatch: ModelObject={0}, ColumnObject={1}", + fieldClass.getSimpleName(), jdbcClass.getSimpleName()))); + } + } + } + + // string types + if (fieldClass == String.class) { + 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.length, col.size))); + } + 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()))); + } + } + + // numeric autoIncrement + if (fieldDef.isAutoIncrement != col.isAutoIncrement) { + remarks.add(warn( + table, + col, + format("{0}.autoIncrement={1}" + " while Column autoIncrement={2}", + IQColumn.class.getSimpleName(), fieldDef.isAutoIncrement, col.isAutoIncrement))); + } + // default value + if (!col.isAutoIncrement && !col.isPrimaryKey) { + String defaultValue = null; + if (fieldDef.defaultValue != null && fieldDef.defaultValue instanceof String) { + defaultValue = fieldDef.defaultValue.toString(); + } + // check Model.defaultValue format + if (!ModelUtils.isProperlyFormattedDefaultValue(defaultValue)) { + remarks.add(error( + table, + col, + format("{0}.defaultValue=\"{1}\"" + " is improperly formatted!", + IQColumn.class.getSimpleName(), defaultValue)).throwError(throwError)); + // next field + return; + } + // compare Model.defaultValue to Column.defaultValue + if (isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) { + // Model.defaultValue is NULL, Column.defaultValue is NOT NULL + remarks.add(warn( + table, + col, + format("{0}.defaultValue=\"\"" + " while column default=\"{1}\"", + IQColumn.class.getSimpleName(), col.defaultValue))); + } else if (!isNullOrEmpty(defaultValue) && isNullOrEmpty(col.defaultValue)) { + // Column.defaultValue is NULL, Model.defaultValue is NOT NULL + remarks.add(warn( + table, + col, + format("{0}.defaultValue=\"{1}\"" + " while column default=\"\"", + IQColumn.class.getSimpleName(), defaultValue))); + } else if (!isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) { + if (!defaultValue.equals(col.defaultValue)) { + // Model.defaultValue != Column.defaultValue + remarks.add(warn( + table, + col, + format("{0}.defaultValue=\"{1}\"" + " while column default=\"{2}\"", + IQColumn.class.getSimpleName(), defaultValue, col.defaultValue))); + } + } + + // sanity check Model.defaultValue literal value + if (!ModelUtils.isValidDefaultValue(fieldDef.field.getType(), defaultValue)) { + remarks.add(error( + table, + col, + format("{0}.defaultValue=\"{1}\" is invalid!", IQColumn.class.getSimpleName(), + defaultValue))); + } + } + } + + /** + * Represents an index as it exists in the database. + */ + private static class IndexInspector { + + String name; + IndexType type; + private List columns = new ArrayList(); + + public IndexInspector(ResultSet rs) throws SQLException { + name = rs.getString("INDEX_NAME"); + + // determine index type + boolean hash = rs.getInt("TYPE") == DatabaseMetaData.tableIndexHashed; + boolean unique = !rs.getBoolean("NON_UNIQUE"); + + if (!hash && !unique) { + type = IndexType.STANDARD; + } else if (hash && unique) { + type = IndexType.UNIQUE_HASH; + } else if (unique) { + type = IndexType.UNIQUE; + } else if (hash) { + type = IndexType.HASH; + } + columns.add(rs.getString("COLUMN_NAME")); + } + + public void addColumn(ResultSet rs) throws SQLException { + columns.add(rs.getString("COLUMN_NAME")); + } + } + + /** + * Represents a column as it exists in the database. + */ + static class ColumnInspector implements Comparable { + String name; + String type; + int size; + boolean nullable; + Class clazz; + boolean isPrimaryKey; + boolean isAutoIncrement; + String defaultValue; + + public int compareTo(ColumnInspector o) { + if (isPrimaryKey && o.isPrimaryKey) { + // both primary sort by name + return name.compareTo(o.name); + } else if (isPrimaryKey && !o.isPrimaryKey) { + // primary first + return -1; + } else if (!isPrimaryKey && o.isPrimaryKey) { + // primary first + return 1; + } else { + // neither primary, sort by name + return name.compareTo(o.name); + } + } + } + + /** + * Convenience class based on StatementBuilder for creating the annotation + * parameter list. + */ + private static class AnnotationBuilder extends StatementBuilder { + + AnnotationBuilder() { + super(); + } + + void addParameter(String parameter) { + + appendExceptFirst(", "); + append(parameter); + } + + void addParameter(String parameter, T value) { + appendExceptFirst(", "); + if (!StringUtils.isNullOrEmpty(parameter)) { + append(parameter); + append('='); + } + if (value instanceof List) { + append("{ "); + List list = (List) value; + StatementBuilder flat = new StatementBuilder(); + for (Object o : list) { + flat.appendExceptFirst(", "); + if (o instanceof String) { + flat.append('\"'); + } + // TODO escape string + flat.append(o.toString().trim()); + if (o instanceof String) { + flat.append('\"'); + } + } + append(flat); + append(" }"); + } else { + if (value instanceof String) { + append('\"'); + } + // TODO escape + append(value.toString().trim()); + if (value instanceof String) { + append('\"'); + } + } + } + + void addEnum(String parameter, Enum value) { + appendExceptFirst(", "); + if (!StringUtils.isNullOrEmpty(parameter)) { + append(parameter); + append('='); + } + append(value.getClass().getSimpleName() + "." + value.name()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/iciql/TestCondition.java b/src/main/java/com/iciql/TestCondition.java new file mode 100644 index 0000000..010f5a1 --- /dev/null +++ b/src/main/java/com/iciql/TestCondition.java @@ -0,0 +1,115 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import com.iciql.util.Utils; + +/** + * This class represents an incomplete condition. + * + * @param + * the incomplete condition data type + */ + +public class TestCondition { + + private A x; + + public TestCondition(A x) { + this.x = x; + } + + public Boolean is(A y) { + Boolean o = Utils.newObject(Boolean.class); + return Db.registerToken(o, new Function("=", x, y) { + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL("("); + query.appendSQL(stat, null, x[0]); + stat.appendSQL(" = "); + query.appendSQL(stat, x[0], x[1]); + stat.appendSQL(")"); + } + }); + } + + public Boolean exceeds(A y) { + Boolean o = Utils.newObject(Boolean.class); + return Db.registerToken(o, new Function(">", x, y) { + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL("("); + query.appendSQL(stat, null, x[0]); + stat.appendSQL(" > "); + query.appendSQL(stat, x[0], x[1]); + stat.appendSQL(")"); + } + }); + } + + public Boolean atLeast(A y) { + Boolean o = Utils.newObject(Boolean.class); + return Db.registerToken(o, new Function(">=", x, y) { + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL("("); + query.appendSQL(stat, null, x[0]); + stat.appendSQL(" >= "); + query.appendSQL(stat, x[0], x[1]); + stat.appendSQL(")"); + } + }); + } + + public Boolean lessThan(A y) { + Boolean o = Utils.newObject(Boolean.class); + return Db.registerToken(o, new Function("<", x, y) { + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL("("); + query.appendSQL(stat, null, x[0]); + stat.appendSQL(" < "); + query.appendSQL(stat, x[0], x[1]); + stat.appendSQL(")"); + } + }); + } + + public Boolean atMost(A y) { + Boolean o = Utils.newObject(Boolean.class); + return Db.registerToken(o, new Function("<=", x, y) { + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL("("); + query.appendSQL(stat, null, x[0]); + stat.appendSQL(" <= "); + query.appendSQL(stat, x[0], x[1]); + stat.appendSQL(")"); + } + }); + } + + public Boolean like(A pattern) { + Boolean o = Utils.newObject(Boolean.class); + return Db.registerToken(o, new Function("LIKE", x, pattern) { + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL("("); + query.appendSQL(stat, null, x[0]); + stat.appendSQL(" LIKE "); + query.appendSQL(stat, x[0], x[1]); + stat.appendSQL(")"); + } + }); + } + +} diff --git a/src/main/java/com/iciql/Token.java b/src/main/java/com/iciql/Token.java new file mode 100644 index 0000000..cc2203c --- /dev/null +++ b/src/main/java/com/iciql/Token.java @@ -0,0 +1,35 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * Classes implementing this interface can be used as a token in a statement. + */ +public interface Token { + /** + * Append the SQL to the given statement using the given query. + * + * @param stat + * the statement to append the SQL to + * @param query + * the query to use + */ + + void appendSQL(SQLStatement stat, Query query); + +} diff --git a/src/main/java/com/iciql/UpdateColumn.java b/src/main/java/com/iciql/UpdateColumn.java new file mode 100644 index 0000000..1eaf14c --- /dev/null +++ b/src/main/java/com/iciql/UpdateColumn.java @@ -0,0 +1,35 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * Classes implementing this interface can be used as a declaration in an update + * statement. + */ +public interface UpdateColumn { + + /** + * Append the SQL to the given statement using the given query. + * + * @param stat + * the statement to append the SQL to + */ + + void appendSQL(SQLStatement stat); + +} diff --git a/src/main/java/com/iciql/UpdateColumnIncrement.java b/src/main/java/com/iciql/UpdateColumnIncrement.java new file mode 100644 index 0000000..143ce48 --- /dev/null +++ b/src/main/java/com/iciql/UpdateColumnIncrement.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * This class represents "SET column = (column + x)" in an UPDATE statement. + * + * @param + * the query type + * @param + * the new value data type + */ + +public class UpdateColumnIncrement implements UpdateColumn { + + private Query query; + private A x; + private A y; + + UpdateColumnIncrement(Query query, A x) { + this.query = query; + this.x = x; + } + + public Query by(A y) { + query.addUpdateColumnDeclaration(this); + this.y = y; + return query; + } + + public void appendSQL(SQLStatement stat) { + query.appendSQL(stat, null, x); + stat.appendSQL("=("); + query.appendSQL(stat, null, x); + stat.appendSQL("+"); + query.appendSQL(stat, x, y); + stat.appendSQL(")"); + } + +} diff --git a/src/main/java/com/iciql/UpdateColumnSet.java b/src/main/java/com/iciql/UpdateColumnSet.java new file mode 100644 index 0000000..a961480 --- /dev/null +++ b/src/main/java/com/iciql/UpdateColumnSet.java @@ -0,0 +1,63 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +/** + * This class represents "SET column = value" in an UPDATE statement. + * + * @param + * the query type + * @param + * the new value data type + */ + +public class UpdateColumnSet implements UpdateColumn { + + private Query query; + private A x; + private A y; + private boolean isParameter; + + UpdateColumnSet(Query query, A x) { + this.query = query; + this.x = x; + } + + public Query to(A y) { + query.addUpdateColumnDeclaration(this); + this.y = y; + return query; + } + + public Query toParameter() { + query.addUpdateColumnDeclaration(this); + isParameter = true; + return query; + } + + public void appendSQL(SQLStatement stat) { + query.appendSQL(stat, null, x); + stat.appendSQL(" = "); + if (isParameter) { + query.appendSQL(stat, x, RuntimeParameter.PARAMETER); + } else { + query.appendSQL(stat, x, y); + } + } + +} diff --git a/src/main/java/com/iciql/ValidationRemark.java b/src/main/java/com/iciql/ValidationRemark.java new file mode 100644 index 0000000..33320ab --- /dev/null +++ b/src/main/java/com/iciql/ValidationRemark.java @@ -0,0 +1,127 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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; + +import com.iciql.TableDefinition.FieldDefinition; +import com.iciql.TableInspector.ColumnInspector; +import com.iciql.util.StringUtils; + +/** + * A validation remark is a result of running a model validation. Each remark + * has a level, associated component (schema, table, column, index), and a + * message. + */ +public class ValidationRemark { + + /** + * The validation message level. + */ + public static enum Level { + CONSIDER, WARN, ERROR; + } + + 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; + this.table = table; + this.fieldType = type; + this.fieldName = ""; + this.message = message; + } + + private ValidationRemark(Level level, String table, FieldDefinition field, String message) { + this.level = level; + this.table = table; + this.fieldType = field.dataType; + this.fieldName = field.columnName; + this.message = message; + } + + private ValidationRemark(Level level, String table, ColumnInspector col, String message) { + this.level = level; + this.table = table; + this.fieldType = col.type; + this.fieldName = col.name; + this.message = message; + } + + public static ValidationRemark consider(String table, String type, String message) { + return new ValidationRemark(Level.CONSIDER, table, type, message); + } + + public static ValidationRemark consider(String table, ColumnInspector col, String message) { + return new ValidationRemark(Level.CONSIDER, table, col, message); + } + + public static ValidationRemark warn(String table, ColumnInspector col, String message) { + return new ValidationRemark(Level.WARN, table, col, message); + } + + public static ValidationRemark warn(String table, String type, String message) { + return new ValidationRemark(Level.WARN, table, type, message); + } + + public static ValidationRemark error(String table, ColumnInspector col, String message) { + return new ValidationRemark(Level.ERROR, table, col, message); + } + + public static ValidationRemark error(String table, String type, String message) { + return new ValidationRemark(Level.ERROR, table, type, message); + } + + public static ValidationRemark error(String table, FieldDefinition field, String message) { + return new ValidationRemark(Level.ERROR, table, field, message); + } + + public ValidationRemark throwError(boolean throwOnError) { + if (throwOnError && isError()) { + throw new IciqlException(toString()); + } + return this; + } + + public boolean isError() { + return level.equals(Level.ERROR); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(StringUtils.pad(level.name(), 9, " ", true)); + sb.append(StringUtils.pad(table, 25, " ", true)); + sb.append(StringUtils.pad(fieldName, 20, " ", true)); + sb.append(' '); + sb.append(message); + return sb.toString(); + } + + public String toCSVString() { + StringBuilder sb = new StringBuilder(); + sb.append(level.name()).append(','); + sb.append(table).append(','); + sb.append(fieldType).append(','); + sb.append(fieldName).append(','); + sb.append(message); + return sb.toString(); + } + +} diff --git a/src/main/java/com/iciql/bytecode/And.java b/src/main/java/com/iciql/bytecode/And.java new file mode 100644 index 0000000..808a9c1 --- /dev/null +++ b/src/main/java/com/iciql/bytecode/And.java @@ -0,0 +1,46 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.Token; + +/** + * An AND expression. + */ +public class And implements Token { + + private final Token left, right; + + private And(Token left, Token right) { + this.left = left; + this.right = right; + } + + static And get(Token left, Token right) { + return new And(left, right); + } + + public void appendSQL(SQLStatement stat, Query query) { + left.appendSQL(stat, query); + stat.appendSQL(" AND "); + right.appendSQL(stat, query); + } + +} diff --git a/src/main/java/com/iciql/bytecode/ArrayGet.java b/src/main/java/com/iciql/bytecode/ArrayGet.java new file mode 100644 index 0000000..29516c2 --- /dev/null +++ b/src/main/java/com/iciql/bytecode/ArrayGet.java @@ -0,0 +1,49 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.Token; + +/** + * An array access operation. + */ +public class ArrayGet implements Token { + + private final Token variable; + private final Token index; + + private ArrayGet(Token variable, Token index) { + this.variable = variable; + this.index = index; + } + + static ArrayGet get(Token variable, Token index) { + return new ArrayGet(variable, index); + } + + public void appendSQL(SQLStatement stat, Query query) { + // untested + variable.appendSQL(stat, query); + stat.appendSQL("["); + index.appendSQL(stat, query); + stat.appendSQL("]"); + } + +} diff --git a/src/main/java/com/iciql/bytecode/CaseWhen.java b/src/main/java/com/iciql/bytecode/CaseWhen.java new file mode 100644 index 0000000..2a1d69e --- /dev/null +++ b/src/main/java/com/iciql/bytecode/CaseWhen.java @@ -0,0 +1,62 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.Token; + +/** + * A conditional expression. + */ +public class CaseWhen implements Token { + + private final Token condition, ifTrue, ifFalse; + + private CaseWhen(Token condition, Token ifTrue, Token ifFalse) { + this.condition = condition; + this.ifTrue = ifTrue; + this.ifFalse = ifFalse; + } + + static Token get(Token condition, Token ifTrue, Token ifFalse) { + if ("0".equals(ifTrue.toString()) && "1".equals(ifFalse.toString())) { + return Not.get(condition); + } else if ("1".equals(ifTrue.toString()) && "0".equals(ifFalse.toString())) { + return condition; + } else if ("0".equals(ifTrue.toString())) { + return And.get(Not.get(condition), ifFalse); + } + return new CaseWhen(condition, ifTrue, ifFalse); + } + + public String toString() { + return "CASEWHEN(" + condition + ", " + ifTrue + ", " + ifFalse + ")"; + } + + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL("CASEWHEN "); + condition.appendSQL(stat, query); + stat.appendSQL(" THEN "); + ifTrue.appendSQL(stat, query); + stat.appendSQL(" ELSE "); + ifFalse.appendSQL(stat, query); + stat.appendSQL(" END"); + } + +} diff --git a/src/main/java/com/iciql/bytecode/ClassReader.java b/src/main/java/com/iciql/bytecode/ClassReader.java new file mode 100644 index 0000000..38fd2f5 --- /dev/null +++ b/src/main/java/com/iciql/bytecode/ClassReader.java @@ -0,0 +1,1457 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +import com.iciql.IciqlException; +import com.iciql.Token; + +/** + * This class converts a method to a SQL Token by interpreting (decompiling) the + * bytecode of the class. + */ +public class ClassReader { + + private static final boolean DEBUG = false; + + private byte[] data; + private int pos; + private Constant[] constantPool; + private int startByteCode; + private String methodName; + + private String convertMethodName; + private Token result; + private Stack stack = new Stack(); + private ArrayList variables = new ArrayList(); + private boolean endOfMethod; + private boolean condition; + private int nextPc; + private Map fieldMap = new HashMap(); + + private static void debug(String s) { + if (DEBUG) { + System.out.println(s); + } + } + + public Token decompile(Object instance, Map fields, String method) { + this.fieldMap = fields; + this.convertMethodName = method; + Class clazz = instance.getClass(); + String className = clazz.getName(); + debug("class name " + className); + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + try { + InputStream in = clazz.getClassLoader().getResource(className.replace('.', '/') + ".class") + .openStream(); + while (true) { + int x = in.read(); + if (x < 0) { + break; + } + buff.write(x); + } + } catch (IOException e) { + throw new IciqlException("Could not read class bytecode", e); + } + data = buff.toByteArray(); + int header = readInt(); + debug("header: " + Integer.toHexString(header)); + int minorVersion = readShort(); + int majorVersion = readShort(); + debug("version: " + majorVersion + "." + minorVersion); + int constantPoolCount = readShort(); + constantPool = new Constant[constantPoolCount]; + for (int i = 1; i < constantPoolCount; i++) { + int type = readByte(); + switch (type) { + case 1: + constantPool[i] = ConstantString.get(readString()); + break; + case 3: { + int x = readInt(); + constantPool[i] = ConstantNumber.get(x); + break; + } + case 4: { + int x = readInt(); + constantPool[i] = ConstantNumber.get("" + Float.intBitsToFloat(x), x, Constant.Type.FLOAT); + break; + } + case 5: { + long x = readLong(); + constantPool[i] = ConstantNumber.get(x); + i++; + break; + } + case 6: { + long x = readLong(); + constantPool[i] = ConstantNumber + .get("" + Double.longBitsToDouble(x), x, Constant.Type.DOUBLE); + i++; + break; + } + case 7: { + int x = readShort(); + constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.CLASS_REF); + break; + } + case 8: { + int x = readShort(); + constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.STRING_REF); + break; + } + case 9: { + int x = readInt(); + constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.FIELD_REF); + break; + } + case 10: { + int x = readInt(); + constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.METHOD_REF); + break; + } + case 11: { + int x = readInt(); + constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.INTERFACE_METHOD_REF); + break; + } + case 12: { + int x = readInt(); + constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.NAME_AND_TYPE); + break; + } + default: + throw new IciqlException("Unsupported constant pool tag: " + type); + } + } + int accessFlags = readShort(); + debug("access flags: " + accessFlags); + int classRef = readShort(); + debug("class: " + constantPool[constantPool[classRef].intValue()]); + int superClassRef = readShort(); + debug(" extends " + constantPool[constantPool[superClassRef].intValue()]); + int interfaceCount = readShort(); + for (int i = 0; i < interfaceCount; i++) { + int interfaceRef = readShort(); + debug(" implements " + constantPool[constantPool[interfaceRef].intValue()]); + } + int fieldCount = readShort(); + for (int i = 0; i < fieldCount; i++) { + readField(); + } + int methodCount = readShort(); + for (int i = 0; i < methodCount; i++) { + readMethod(); + } + readAttributes(); + return result; + } + + private void readField() { + int accessFlags = readShort(); + int nameIndex = readShort(); + int descIndex = readShort(); + debug(" " + constantPool[descIndex] + " " + constantPool[nameIndex] + " " + accessFlags); + readAttributes(); + } + + private void readMethod() { + int accessFlags = readShort(); + int nameIndex = readShort(); + int descIndex = readShort(); + String desc = constantPool[descIndex].toString(); + methodName = constantPool[nameIndex].toString(); + debug(" " + desc + " " + methodName + " " + accessFlags); + readAttributes(); + } + + private void readAttributes() { + int attributeCount = readShort(); + for (int i = 0; i < attributeCount; i++) { + int attributeNameIndex = readShort(); + String attributeName = constantPool[attributeNameIndex].toString(); + debug(" attribute " + attributeName); + int attributeLength = readInt(); + int end = pos + attributeLength; + if ("Code".equals(attributeName)) { + readCode(); + } + pos = end; + } + } + + void decompile() { + int maxStack = readShort(); + int maxLocals = readShort(); + debug("stack: " + maxStack + " locals: " + maxLocals); + int codeLength = readInt(); + startByteCode = pos; + int end = pos + codeLength; + while (pos < end) { + readByteCode(); + } + debug(""); + pos = startByteCode + codeLength; + int exceptionTableLength = readShort(); + pos += 2 * exceptionTableLength; + readAttributes(); + } + + private void readCode() { + variables.clear(); + stack.clear(); + int maxStack = readShort(); + int maxLocals = readShort(); + debug("stack: " + maxStack + " locals: " + maxLocals); + int codeLength = readInt(); + startByteCode = pos; + if (methodName.startsWith(convertMethodName)) { + result = getResult(); + } + pos = startByteCode + codeLength; + int exceptionTableLength = readShort(); + pos += 2 * exceptionTableLength; + readAttributes(); + } + + private Token getResult() { + while (true) { + readByteCode(); + if (endOfMethod) { + return stack.pop(); + } + if (condition) { + Token c = stack.pop(); + Stack currentStack = new Stack(); + currentStack.addAll(stack); + ArrayList currentVariables = new ArrayList(); + currentVariables.addAll(variables); + int branch = nextPc; + Token a = getResult(); + stack = currentStack; + variables = currentVariables; + pos = branch + startByteCode; + Token b = getResult(); + if (a.equals("0") && b.equals("1")) { + return c; + } else if (a.equals("1") && b.equals("0")) { + return Not.get(c); + } else if (b.equals("0")) { + return And.get(Not.get(c), a); + } else if (a.equals("0")) { + return And.get(c, b); + } else if (b.equals("1")) { + return Or.get(c, a); + } else if (a.equals("1")) { + return And.get(Not.get(c), b); + } + return CaseWhen.get(c, b, a); + } + if (nextPc != 0) { + pos = nextPc + startByteCode; + } + } + } + + private void readByteCode() { + int startPos = pos - startByteCode; + int opCode = readByte(); + String op; + endOfMethod = false; + condition = false; + nextPc = 0; + switch (opCode) { + case 0: + op = "nop"; + break; + case 1: + op = "aconst_null"; + stack.push(Null.INSTANCE); + break; + case 2: + op = "iconst_m1"; + stack.push(ConstantNumber.get("-1")); + break; + case 3: + op = "iconst_0"; + stack.push(ConstantNumber.get("0")); + break; + case 4: + op = "iconst_1"; + stack.push(ConstantNumber.get("1")); + break; + case 5: + op = "iconst_2"; + stack.push(ConstantNumber.get("2")); + break; + case 6: + op = "iconst_3"; + stack.push(ConstantNumber.get("3")); + break; + case 7: + op = "iconst_4"; + stack.push(ConstantNumber.get("4")); + break; + case 8: + op = "iconst_5"; + stack.push(ConstantNumber.get("5")); + break; + case 9: + op = "lconst_0"; + stack.push(ConstantNumber.get("0")); + break; + case 10: + op = "lconst_1"; + stack.push(ConstantNumber.get("1")); + break; + case 11: + op = "fconst_0"; + stack.push(ConstantNumber.get("0.0")); + break; + case 12: + op = "fconst_1"; + stack.push(ConstantNumber.get("1.0")); + break; + case 13: + op = "fconst_2"; + stack.push(ConstantNumber.get("2.0")); + break; + case 14: + op = "dconst_0"; + stack.push(ConstantNumber.get("0.0")); + break; + case 15: + op = "dconst_1"; + stack.push(ConstantNumber.get("1.0")); + break; + case 16: { + int x = (byte) readByte(); + op = "bipush " + x; + stack.push(ConstantNumber.get(x)); + break; + } + case 17: { + int x = (short) readShort(); + op = "sipush " + x; + stack.push(ConstantNumber.get(x)); + break; + } + case 18: { + Token s = getConstant(readByte()); + op = "ldc " + s; + stack.push(s); + break; + } + case 19: { + Token s = getConstant(readShort()); + op = "ldc_w " + s; + stack.push(s); + break; + } + case 20: { + Token s = getConstant(readShort()); + op = "ldc2_w " + s; + stack.push(s); + break; + } + case 21: { + int x = readByte(); + op = "iload " + x; + stack.push(getVariable(x)); + break; + } + case 22: { + int x = readByte(); + op = "lload " + x; + stack.push(getVariable(x)); + break; + } + case 23: { + int x = readByte(); + op = "fload " + x; + stack.push(getVariable(x)); + break; + } + case 24: { + int x = readByte(); + op = "dload " + x; + stack.push(getVariable(x)); + break; + } + case 25: { + int x = readByte(); + op = "aload " + x; + stack.push(getVariable(x)); + break; + } + case 26: + op = "iload_0"; + stack.push(getVariable(0)); + break; + case 27: + op = "iload_1"; + stack.push(getVariable(1)); + break; + case 28: + op = "iload_2"; + stack.push(getVariable(2)); + break; + case 29: + op = "iload_3"; + stack.push(getVariable(3)); + break; + case 30: + op = "lload_0"; + stack.push(getVariable(0)); + break; + case 31: + op = "lload_1"; + stack.push(getVariable(1)); + break; + case 32: + op = "lload_2"; + stack.push(getVariable(2)); + break; + case 33: + op = "lload_3"; + stack.push(getVariable(3)); + break; + case 34: + op = "fload_0"; + stack.push(getVariable(0)); + break; + case 35: + op = "fload_1"; + stack.push(getVariable(1)); + break; + case 36: + op = "fload_2"; + stack.push(getVariable(2)); + break; + case 37: + op = "fload_3"; + stack.push(getVariable(3)); + break; + case 38: + op = "dload_0"; + stack.push(getVariable(0)); + break; + case 39: + op = "dload_1"; + stack.push(getVariable(1)); + break; + case 40: + op = "dload_2"; + stack.push(getVariable(2)); + break; + case 41: + op = "dload_3"; + stack.push(getVariable(3)); + break; + case 42: + op = "aload_0"; + stack.push(getVariable(0)); + break; + case 43: + op = "aload_1"; + stack.push(getVariable(1)); + break; + case 44: + op = "aload_2"; + stack.push(getVariable(2)); + break; + case 45: + op = "aload_3"; + stack.push(getVariable(3)); + break; + case 46: { + Token index = stack.pop(); + Token ref = stack.pop(); + op = "iaload"; + stack.push(ArrayGet.get(ref, index)); + break; + } + case 47: { + Token index = stack.pop(); + Token ref = stack.pop(); + op = "laload"; + stack.push(ArrayGet.get(ref, index)); + break; + } + case 48: { + Token index = stack.pop(); + Token ref = stack.pop(); + op = "faload"; + stack.push(ArrayGet.get(ref, index)); + break; + } + case 49: { + Token index = stack.pop(); + Token ref = stack.pop(); + op = "daload"; + stack.push(ArrayGet.get(ref, index)); + break; + } + case 50: { + Token index = stack.pop(); + Token ref = stack.pop(); + op = "aaload"; + stack.push(ArrayGet.get(ref, index)); + break; + } + case 51: { + Token index = stack.pop(); + Token ref = stack.pop(); + op = "baload"; + stack.push(ArrayGet.get(ref, index)); + break; + } + case 52: { + Token index = stack.pop(); + Token ref = stack.pop(); + op = "caload"; + stack.push(ArrayGet.get(ref, index)); + break; + } + case 53: { + Token index = stack.pop(); + Token ref = stack.pop(); + op = "saload"; + stack.push(ArrayGet.get(ref, index)); + break; + } + case 54: { + int var = readByte(); + op = "istore " + var; + setVariable(var, stack.pop()); + break; + } + case 55: { + int var = readByte(); + op = "lstore " + var; + setVariable(var, stack.pop()); + break; + } + case 56: { + int var = readByte(); + op = "fstore " + var; + setVariable(var, stack.pop()); + break; + } + case 57: { + int var = readByte(); + op = "dstore " + var; + setVariable(var, stack.pop()); + break; + } + case 58: { + int var = readByte(); + op = "astore " + var; + setVariable(var, stack.pop()); + break; + } + case 59: + op = "istore_0"; + setVariable(0, stack.pop()); + break; + case 60: + op = "istore_1"; + setVariable(1, stack.pop()); + break; + case 61: + op = "istore_2"; + setVariable(2, stack.pop()); + break; + case 62: + op = "istore_3"; + setVariable(3, stack.pop()); + break; + case 63: + op = "lstore_0"; + setVariable(0, stack.pop()); + break; + case 64: + op = "lstore_1"; + setVariable(1, stack.pop()); + break; + case 65: + op = "lstore_2"; + setVariable(2, stack.pop()); + break; + case 66: + op = "lstore_3"; + setVariable(3, stack.pop()); + break; + case 67: + op = "fstore_0"; + setVariable(0, stack.pop()); + break; + case 68: + op = "fstore_1"; + setVariable(1, stack.pop()); + break; + case 69: + op = "fstore_2"; + setVariable(2, stack.pop()); + break; + case 70: + op = "fstore_3"; + setVariable(3, stack.pop()); + break; + case 71: + op = "dstore_0"; + setVariable(0, stack.pop()); + break; + case 72: + op = "dstore_1"; + setVariable(1, stack.pop()); + break; + case 73: + op = "dstore_2"; + setVariable(2, stack.pop()); + break; + case 74: + op = "dstore_3"; + setVariable(3, stack.pop()); + break; + case 75: + op = "astore_0"; + setVariable(0, stack.pop()); + break; + case 76: + op = "astore_1"; + setVariable(1, stack.pop()); + break; + case 77: + op = "astore_2"; + setVariable(2, stack.pop()); + break; + case 78: + op = "astore_3"; + setVariable(3, stack.pop()); + break; + case 79: { + // String value = stack.pop(); + // String index = stack.pop(); + // String ref = stack.pop(); + op = "iastore"; + // TODO side effect - not supported + break; + } + case 80: + op = "lastore"; + // TODO side effect - not supported + break; + case 81: + op = "fastore"; + // TODO side effect - not supported + break; + case 82: + op = "dastore"; + // TODO side effect - not supported + break; + case 83: + op = "aastore"; + // TODO side effect - not supported + break; + case 84: + op = "bastore"; + // TODO side effect - not supported + break; + case 85: + op = "castore"; + // TODO side effect - not supported + break; + case 86: + op = "sastore"; + // TODO side effect - not supported + break; + case 87: + op = "pop"; + stack.pop(); + break; + case 88: + op = "pop2"; + // TODO currently we don't know the stack types + stack.pop(); + stack.pop(); + break; + case 89: { + op = "dup"; + Token x = stack.pop(); + stack.push(x); + stack.push(x); + break; + } + case 90: { + op = "dup_x1"; + Token a = stack.pop(); + Token b = stack.pop(); + stack.push(a); + stack.push(b); + stack.push(a); + break; + } + case 91: { + // TODO currently we don't know the stack types + op = "dup_x2"; + Token a = stack.pop(); + Token b = stack.pop(); + Token c = stack.pop(); + stack.push(a); + stack.push(c); + stack.push(b); + stack.push(a); + break; + } + case 92: { + // TODO currently we don't know the stack types + op = "dup2"; + Token a = stack.pop(); + Token b = stack.pop(); + stack.push(b); + stack.push(a); + stack.push(b); + stack.push(a); + break; + } + case 93: { + // TODO currently we don't know the stack types + op = "dup2_x1"; + Token a = stack.pop(); + Token b = stack.pop(); + Token c = stack.pop(); + stack.push(b); + stack.push(a); + stack.push(c); + stack.push(b); + stack.push(a); + break; + } + case 94: { + // TODO currently we don't know the stack types + op = "dup2_x2"; + Token a = stack.pop(); + Token b = stack.pop(); + Token c = stack.pop(); + Token d = stack.pop(); + stack.push(b); + stack.push(a); + stack.push(d); + stack.push(c); + stack.push(b); + stack.push(a); + break; + } + case 95: { + op = "swap"; + Token a = stack.pop(); + Token b = stack.pop(); + stack.push(a); + stack.push(b); + break; + } + case 96: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "iadd"; + stack.push(Operation.get(a, Operation.Type.ADD, b)); + break; + } + case 97: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "ladd"; + stack.push(Operation.get(a, Operation.Type.ADD, b)); + break; + } + case 98: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "fadd"; + stack.push(Operation.get(a, Operation.Type.ADD, b)); + break; + } + case 99: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "dadd"; + stack.push(Operation.get(a, Operation.Type.ADD, b)); + break; + } + case 100: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "isub"; + stack.push(Operation.get(a, Operation.Type.SUBTRACT, b)); + break; + } + case 101: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "lsub"; + stack.push(Operation.get(a, Operation.Type.SUBTRACT, b)); + break; + } + case 102: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "fsub"; + stack.push(Operation.get(a, Operation.Type.SUBTRACT, b)); + break; + } + case 103: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "dsub"; + stack.push(Operation.get(a, Operation.Type.SUBTRACT, b)); + break; + } + case 104: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "imul"; + stack.push(Operation.get(a, Operation.Type.MULTIPLY, b)); + break; + } + case 105: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "lmul"; + stack.push(Operation.get(a, Operation.Type.MULTIPLY, b)); + break; + } + case 106: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "fmul"; + stack.push(Operation.get(a, Operation.Type.MULTIPLY, b)); + break; + } + case 107: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "dmul"; + stack.push(Operation.get(a, Operation.Type.MULTIPLY, b)); + break; + } + case 108: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "idiv"; + stack.push(Operation.get(a, Operation.Type.DIVIDE, b)); + break; + } + case 109: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "ldiv"; + stack.push(Operation.get(a, Operation.Type.DIVIDE, b)); + break; + } + case 110: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "fdiv"; + stack.push(Operation.get(a, Operation.Type.DIVIDE, b)); + break; + } + case 111: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "ddiv"; + stack.push(Operation.get(a, Operation.Type.DIVIDE, b)); + break; + } + case 112: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "irem"; + stack.push(Operation.get(a, Operation.Type.MOD, b)); + break; + } + case 113: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "lrem"; + stack.push(Operation.get(a, Operation.Type.MOD, b)); + break; + } + case 114: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "frem"; + stack.push(Operation.get(a, Operation.Type.MOD, b)); + break; + } + case 115: { + Token b = stack.pop(); + Token a = stack.pop(); + op = "drem"; + stack.push(Operation.get(a, Operation.Type.MOD, b)); + break; + } + // case 116: + // op = "ineg"; + // break; + // case 117: + // op = "lneg"; + // break; + // case 118: + // op = "fneg"; + // break; + // case 119: + // op = "dneg"; + // break; + // case 120: + // op = "ishl"; + // break; + // case 121: + // op = "lshl"; + // break; + // case 122: + // op = "ishr"; + // break; + // case 123: + // op = "lshr"; + // break; + // case 124: + // op = "iushr"; + // break; + // case 125: + // op = "lushr"; + // break; + // case 126: + // op = "iand"; + // break; + // case 127: + // op = "land"; + // break; + // case 128: + // op = "ior"; + // break; + // case 129: + // op = "lor"; + // break; + // case 130: + // op = "ixor"; + // break; + // case 131: + // op = "lxor"; + // break; + // case 132: { + // int var = readByte(); + // int off = (byte) readByte(); + // op = "iinc " + var + " " + off; + // break; + // } + // case 133: + // op = "i2l"; + // break; + // case 134: + // op = "i2f"; + // break; + // case 135: + // op = "i2d"; + // break; + // case 136: + // op = "l2i"; + // break; + // case 137: + // op = "l2f"; + // break; + // case 138: + // op = "l2d"; + // break; + // case 139: + // op = "f2i"; + // break; + // case 140: + // op = "f2l"; + // break; + // case 141: + // op = "f2d"; + // break; + // case 142: + // op = "d2i"; + // break; + // case 143: + // op = "d2l"; + // break; + // case 144: + // op = "d2f"; + // break; + // case 145: + // op = "i2b"; + // break; + // case 146: + // op = "i2c"; + // break; + // case 147: + // op = "i2s"; + // break; + case 148: { + Token b = stack.pop(), a = stack.pop(); + stack.push(new Function("SIGN", Operation.get(a, Operation.Type.SUBTRACT, b))); + op = "lcmp"; + break; + } + // case 149: + // op = "fcmpl"; + // break; + // case 150: + // op = "fcmpg"; + // break; + // case 151: + // op = "dcmpl"; + // break; + // case 152: + // op = "dcmpg"; + // break; + case 153: + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + stack.push(Operation.get(stack.pop(), Operation.Type.EQUALS, ConstantNumber.get(0))); + op = "ifeq " + nextPc; + break; + case 154: + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + stack.push(Operation.get(stack.pop(), Operation.Type.NOT_EQUALS, ConstantNumber.get(0))); + op = "ifne " + nextPc; + break; + case 155: + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER, ConstantNumber.get(0))); + op = "iflt " + nextPc; + break; + case 156: + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER_EQUALS, ConstantNumber.get(0))); + op = "ifge " + nextPc; + break; + case 157: + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER, ConstantNumber.get(0))); + op = "ifgt " + nextPc; + break; + case 158: + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER_EQUALS, ConstantNumber.get(0))); + op = "ifle " + nextPc; + break; + case 159: { + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + Token b = stack.pop(), a = stack.pop(); + stack.push(Operation.get(a, Operation.Type.EQUALS, b)); + op = "if_icmpeq " + nextPc; + break; + } + case 160: { + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + Token b = stack.pop(), a = stack.pop(); + stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b)); + op = "if_icmpne " + nextPc; + break; + } + case 161: { + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + Token b = stack.pop(), a = stack.pop(); + stack.push(Operation.get(a, Operation.Type.SMALLER, b)); + op = "if_icmplt " + nextPc; + break; + } + case 162: { + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + Token b = stack.pop(), a = stack.pop(); + stack.push(Operation.get(a, Operation.Type.BIGGER_EQUALS, b)); + op = "if_icmpge " + nextPc; + break; + } + case 163: { + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + Token b = stack.pop(), a = stack.pop(); + stack.push(Operation.get(a, Operation.Type.BIGGER, b)); + op = "if_icmpgt " + nextPc; + break; + } + case 164: { + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + Token b = stack.pop(), a = stack.pop(); + stack.push(Operation.get(a, Operation.Type.SMALLER_EQUALS, b)); + op = "if_icmple " + nextPc; + break; + } + case 165: { + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + Token b = stack.pop(), a = stack.pop(); + stack.push(Operation.get(a, Operation.Type.EQUALS, b)); + op = "if_acmpeq " + nextPc; + break; + } + case 166: { + condition = true; + nextPc = getAbsolutePos(pos, readShort()); + Token b = stack.pop(), a = stack.pop(); + stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b)); + op = "if_acmpne " + nextPc; + break; + } + case 167: + nextPc = getAbsolutePos(pos, readShort()); + op = "goto " + nextPc; + break; + // case 168: + // // TODO not supported yet + // op = "jsr " + getAbsolutePos(pos, readShort()); + // break; + // case 169: + // // TODO not supported yet + // op = "ret " + readByte(); + // break; + // case 170: { + // int start = pos; + // pos += 4 - ((pos - startByteCode) & 3); + // int def = readInt(); + // int low = readInt(), high = readInt(); + // int n = high - low + 1; + // op = "tableswitch default:" + getAbsolutePos(start, def); + // StringBuilder buff = new StringBuilder(); + // for (int i = 0; i < n; i++) { + // buff.append(' ').append(low++). + // append(":"). + // append(getAbsolutePos(start, readInt())); + // } + // op += buff.toString(); + // // pos += n * 4; + // break; + // } + // case 171: { + // int start = pos; + // pos += 4 - ((pos - startByteCode) & 3); + // int def = readInt(); + // int n = readInt(); + // op = "lookupswitch default:" + getAbsolutePos(start, def); + // StringBuilder buff = new StringBuilder(); + // for (int i = 0; i < n; i++) { + // buff.append(' '). + // append(readInt()). + // append(":"). + // append(getAbsolutePos(start, readInt())); + // } + // op += buff.toString(); + // // pos += n * 8; + // break; + // } + case 172: + op = "ireturn"; + endOfMethod = true; + break; + case 173: + op = "lreturn"; + endOfMethod = true; + break; + case 174: + op = "freturn"; + endOfMethod = true; + break; + case 175: + op = "dreturn"; + endOfMethod = true; + break; + case 176: + op = "areturn"; + endOfMethod = true; + break; + case 177: + op = "return"; + // no value returned + stack.push(null); + endOfMethod = true; + break; + // case 178: + // op = "getstatic " + getField(readShort()); + // break; + // case 179: + // op = "putstatic " + getField(readShort()); + // break; + case 180: { + String field = getField(readShort()); + Token p = stack.pop(); + String s = p + "." + field.substring(field.lastIndexOf('.') + 1, field.indexOf(' ')); + if (s.startsWith("this.")) { + s = s.substring(5); + } + stack.push(Variable.get(s, fieldMap.get(s))); + op = "getfield " + field; + break; + } + // case 181: + // op = "putfield " + getField(readShort()); + // break; + case 182: { + String method = getMethod(readShort()); + op = "invokevirtual " + method; + if (method.equals("java/lang/String.equals (Ljava/lang/Object;)Z")) { + Token a = stack.pop(); + Token b = stack.pop(); + stack.push(Operation.get(a, Operation.Type.EQUALS, b)); + } else if (method.equals("java/lang/Integer.intValue ()I")) { + // ignore + } else if (method.equals("java/lang/Long.longValue ()J")) { + // ignore + } + break; + } + case 183: { + String method = getMethod(readShort()); + op = "invokespecial " + method; + break; + } + case 184: + op = "invokestatic " + getMethod(readShort()); + break; + // case 185: { + // int methodRef = readShort(); + // readByte(); + // readByte(); + // op = "invokeinterface " + getMethod(methodRef); + // break; + // } + case 187: { + String className = constantPool[constantPool[readShort()].intValue()].toString(); + op = "new " + className; + break; + } + // case 188: + // op = "newarray " + readByte(); + // break; + // case 189: + // op = "anewarray " + cpString[readShort()]; + // break; + // case 190: + // op = "arraylength"; + // break; + // case 191: + // op = "athrow"; + // break; + // case 192: + // op = "checkcast " + cpString[readShort()]; + // break; + // case 193: + // op = "instanceof " + cpString[readShort()]; + // break; + // case 194: + // op = "monitorenter"; + // break; + // case 195: + // op = "monitorexit"; + // break; + // case 196: { + // opCode = readByte(); + // switch (opCode) { + // case 21: + // op = "wide iload " + readShort(); + // break; + // case 22: + // op = "wide lload " + readShort(); + // break; + // case 23: + // op = "wide fload " + readShort(); + // break; + // case 24: + // op = "wide dload " + readShort(); + // break; + // case 25: + // op = "wide aload " + readShort(); + // break; + // case 54: + // op = "wide istore " + readShort(); + // break; + // case 55: + // op = "wide lstore " + readShort(); + // break; + // case 56: + // op = "wide fstore " + readShort(); + // break; + // case 57: + // op = "wide dstore " + readShort(); + // break; + // case 58: + // op = "wide astore " + readShort(); + // break; + // case 132: { + // int var = readShort(); + // int off = (short) readShort(); + // op = "wide iinc " + var + " " + off; + // break; + // } + // case 169: + // op = "wide ret " + readShort(); + // break; + // default: + // throw new IciqlException( + // "Unsupported wide opCode " + opCode); + // } + // break; + // } + // case 197: + // op = "multianewarray " + cpString[readShort()] + " " + readByte(); + // break; + // case 198: { + // condition = true; + // nextPc = getAbsolutePos(pos, readShort()); + // Token a = stack.pop(); + // stack.push("(" + a + " IS NULL)"); + // op = "ifnull " + nextPc; + // break; + // } + // case 199: { + // condition = true; + // nextPc = getAbsolutePos(pos, readShort()); + // Token a = stack.pop(); + // stack.push("(" + a + " IS NOT NULL)"); + // op = "ifnonnull " + nextPc; + // break; + // } + case 200: + op = "goto_w " + getAbsolutePos(pos, readInt()); + break; + case 201: + op = "jsr_w " + getAbsolutePos(pos, readInt()); + break; + default: + throw new IciqlException("Unsupported opCode " + opCode); + } + debug(" " + startPos + ": " + op); + } + + private void setVariable(int x, Token value) { + while (x >= variables.size()) { + variables.add(Variable.get("p" + variables.size(), null)); + } + variables.set(x, value); + } + + private Token getVariable(int x) { + if (x == 0) { + return Variable.THIS; + } + while (x >= variables.size()) { + variables.add(Variable.get("p" + variables.size(), null)); + } + return variables.get(x); + } + + private String getField(int fieldRef) { + int field = constantPool[fieldRef].intValue(); + int classIndex = field >>> 16; + int nameAndType = constantPool[field & 0xffff].intValue(); + String className = constantPool[constantPool[classIndex].intValue()] + "." + + constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff]; + return className; + } + + private String getMethod(int methodRef) { + int method = constantPool[methodRef].intValue(); + int classIndex = method >>> 16; + int nameAndType = constantPool[method & 0xffff].intValue(); + String className = constantPool[constantPool[classIndex].intValue()] + "." + + constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff]; + return className; + } + + private Constant getConstant(int constantRef) { + Constant c = constantPool[constantRef]; + switch (c.getType()) { + case INT: + case FLOAT: + case DOUBLE: + case LONG: + return c; + case STRING_REF: + return constantPool[c.intValue()]; + default: + throw new IciqlException("Not a constant: " + constantRef); + } + } + + private String readString() { + int size = readShort(); + byte[] buff = data; + int p = pos, end = p + size; + char[] chars = new char[size]; + int j = 0; + for (; p < end; j++) { + int x = buff[p++] & 0xff; + if (x < 0x80) { + chars[j] = (char) x; + } else if (x >= 0xe0) { + chars[j] = (char) (((x & 0xf) << 12) + ((buff[p++] & 0x3f) << 6) + (buff[p++] & 0x3f)); + } else { + chars[j] = (char) (((x & 0x1f) << 6) + (buff[p++] & 0x3f)); + } + } + pos = p; + return new String(chars, 0, j); + } + + private int getAbsolutePos(int start, int offset) { + return start - startByteCode - 1 + (short) offset; + } + + private int readByte() { + return data[pos++] & 0xff; + } + + private int readShort() { + byte[] buff = data; + return ((buff[pos++] & 0xff) << 8) + (buff[pos++] & 0xff); + } + + private int readInt() { + byte[] buff = data; + return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8) + + (buff[pos++] & 0xff); + } + + private long readLong() { + return ((long) (readInt()) << 32) + (readInt() & 0xffffffffL); + } + +} diff --git a/src/main/java/com/iciql/bytecode/Constant.java b/src/main/java/com/iciql/bytecode/Constant.java new file mode 100644 index 0000000..65cd66b --- /dev/null +++ b/src/main/java/com/iciql/bytecode/Constant.java @@ -0,0 +1,38 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Token; + +/** + * An expression in the constant pool. + */ +public interface Constant extends Token { + + /** + * The constant pool type. + */ + enum Type { + STRING, INT, FLOAT, DOUBLE, LONG, CLASS_REF, STRING_REF, FIELD_REF, METHOD_REF, INTERFACE_METHOD_REF, NAME_AND_TYPE + } + + Constant.Type getType(); + + int intValue(); + +} diff --git a/src/main/java/com/iciql/bytecode/ConstantNumber.java b/src/main/java/com/iciql/bytecode/ConstantNumber.java new file mode 100644 index 0000000..934de3d --- /dev/null +++ b/src/main/java/com/iciql/bytecode/ConstantNumber.java @@ -0,0 +1,70 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; + +/** + * A literal number. + */ +public class ConstantNumber implements Constant { + + private final String value; + private final Type type; + private final long longValue; + + private ConstantNumber(String value, long longValue, Type type) { + this.value = value; + this.longValue = longValue; + this.type = type; + } + + static ConstantNumber get(String v) { + return new ConstantNumber(v, 0, Type.STRING); + } + + static ConstantNumber get(int v) { + return new ConstantNumber("" + v, v, Type.INT); + } + + static ConstantNumber get(long v) { + return new ConstantNumber("" + v, v, Type.LONG); + } + + static ConstantNumber get(String s, long x, Type type) { + return new ConstantNumber(s, x, type); + } + + public int intValue() { + return (int) longValue; + } + + public String toString() { + return value; + } + + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL(toString()); + } + + public Constant.Type getType() { + return type; + } + +} diff --git a/src/main/java/com/iciql/bytecode/ConstantString.java b/src/main/java/com/iciql/bytecode/ConstantString.java new file mode 100644 index 0000000..985f97d --- /dev/null +++ b/src/main/java/com/iciql/bytecode/ConstantString.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.util.StringUtils; + +/** + * A string constant. + */ +public class ConstantString implements Constant { + + private final String value; + + private ConstantString(String value) { + this.value = value; + } + + static ConstantString get(String v) { + return new ConstantString(v); + } + + public String toString() { + return value; + } + + public int intValue() { + return 0; + } + + public void appendSQL(SQLStatement stat, Query query) { + stat.appendSQL(StringUtils.quoteStringSQL(value)); + } + + public Constant.Type getType() { + return Constant.Type.STRING; + } + +} diff --git a/src/main/java/com/iciql/bytecode/Function.java b/src/main/java/com/iciql/bytecode/Function.java new file mode 100644 index 0000000..56a55ea --- /dev/null +++ b/src/main/java/com/iciql/bytecode/Function.java @@ -0,0 +1,47 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.Token; + +/** + * A method call. + */ +class Function implements Token { + + private final String name; + private final Token expr; + + Function(String name, Token expr) { + this.name = name; + this.expr = expr; + } + + public String toString() { + return name + "(" + expr + ")"; + } + + public void appendSQL(SQLStatement stat, Query query) { + // untested + stat.appendSQL(name + "("); + expr.appendSQL(stat, query); + stat.appendSQL(")"); + } +} diff --git a/src/main/java/com/iciql/bytecode/Not.java b/src/main/java/com/iciql/bytecode/Not.java new file mode 100644 index 0000000..ab5ab84 --- /dev/null +++ b/src/main/java/com/iciql/bytecode/Not.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.Token; + +/** + * A NOT condition. + */ +public class Not implements Token { + + private Token expr; + + private Not(Token expr) { + this.expr = expr; + } + + static Token get(Token expr) { + if (expr instanceof Not) { + return ((Not) expr).expr; + } else if (expr instanceof Operation) { + return ((Operation) expr).reverse(); + } + return new Not(expr); + } + + Token not() { + return expr; + } + + public void appendSQL(SQLStatement stat, Query query) { + // untested + stat.appendSQL("NOT("); + expr.appendSQL(stat, query); + stat.appendSQL(")"); + } + +} diff --git a/src/main/java/com/iciql/bytecode/Null.java b/src/main/java/com/iciql/bytecode/Null.java new file mode 100644 index 0000000..a28de56 --- /dev/null +++ b/src/main/java/com/iciql/bytecode/Null.java @@ -0,0 +1,44 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.Token; + +/** + * The Java 'null'. + */ +public class Null implements Token { + + static final Null INSTANCE = new Null(); + + private Null() { + // don't allow to create new instances + } + + public String toString() { + return "null"; + } + + public void appendSQL(SQLStatement stat, Query query) { + // untested + stat.appendSQL("NULL"); + } + +} diff --git a/src/main/java/com/iciql/bytecode/Operation.java b/src/main/java/com/iciql/bytecode/Operation.java new file mode 100644 index 0000000..7cd42d9 --- /dev/null +++ b/src/main/java/com/iciql/bytecode/Operation.java @@ -0,0 +1,111 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.Token; + +/** + * A mathematical or comparison operation. + */ +class Operation implements Token { + + /** + * The operation type. + */ + enum Type { + EQUALS("=") { + Type reverse() { + return NOT_EQUALS; + } + }, + NOT_EQUALS("<>") { + Type reverse() { + return EQUALS; + } + }, + BIGGER(">") { + Type reverse() { + return SMALLER_EQUALS; + } + }, + BIGGER_EQUALS(">=") { + Type reverse() { + return SMALLER; + } + }, + SMALLER_EQUALS("<=") { + Type reverse() { + return BIGGER; + } + }, + SMALLER("<") { + Type reverse() { + return BIGGER_EQUALS; + } + }, + ADD("+"), SUBTRACT("-"), MULTIPLY("*"), DIVIDE("/"), MOD("%"); + + private String name; + + Type(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + Type reverse() { + return null; + } + + } + + private final Token left, right; + private final Type op; + + private Operation(Token left, Type op, Token right) { + this.left = left; + this.op = op; + this.right = right; + } + + static Token get(Token left, Type op, Token right) { + if (op == Type.NOT_EQUALS && "0".equals(right.toString())) { + return left; + } + return new Operation(left, op, right); + } + + public String toString() { + return left + " " + op + " " + right; + } + + public Token reverse() { + return get(left, op.reverse(), right); + } + + public void appendSQL(SQLStatement stat, Query query) { + left.appendSQL(stat, query); + stat.appendSQL(op.toString()); + right.appendSQL(stat, query); + } + +} diff --git a/src/main/java/com/iciql/bytecode/Or.java b/src/main/java/com/iciql/bytecode/Or.java new file mode 100644 index 0000000..37da2a6 --- /dev/null +++ b/src/main/java/com/iciql/bytecode/Or.java @@ -0,0 +1,47 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.Token; + +/** + * An OR expression. + */ +public class Or implements Token { + + private final Token left, right; + + private Or(Token left, Token right) { + this.left = left; + this.right = right; + } + + static Or get(Token left, Token right) { + return new Or(left, right); + } + + public void appendSQL(SQLStatement stat, Query query) { + // untested + left.appendSQL(stat, query); + stat.appendSQL(" OR "); + right.appendSQL(stat, query); + } + +} diff --git a/src/main/java/com/iciql/bytecode/Variable.java b/src/main/java/com/iciql/bytecode/Variable.java new file mode 100644 index 0000000..f3dbc01 --- /dev/null +++ b/src/main/java/com/iciql/bytecode/Variable.java @@ -0,0 +1,51 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.bytecode; + +import com.iciql.Query; +import com.iciql.SQLStatement; +import com.iciql.Token; + +/** + * A variable. + */ +public class Variable implements Token { + + static final Variable THIS = new Variable("this", null); + + private final String name; + private final Object obj; + + private Variable(String name, Object obj) { + this.name = name; + this.obj = obj; + } + + static Variable get(String name, Object obj) { + return new Variable(name, obj); + } + + public String toString() { + return name; + } + + public void appendSQL(SQLStatement stat, Query query) { + query.appendSQL(stat, null, obj); + } + +} diff --git a/src/main/java/com/iciql/bytecode/package.html b/src/main/java/com/iciql/bytecode/package.html new file mode 100644 index 0000000..5107481 --- /dev/null +++ b/src/main/java/com/iciql/bytecode/package.html @@ -0,0 +1,25 @@ + + + + +Javadoc package documentation + + +The class decompiler for natural syntax iciql clauses. + + \ No newline at end of file diff --git a/src/main/java/com/iciql/package.html b/src/main/java/com/iciql/package.html new file mode 100644 index 0000000..769837b --- /dev/null +++ b/src/main/java/com/iciql/package.html @@ -0,0 +1,25 @@ + + + + +Javadoc package documentation + + +iciql (pronounced "icicle") is a Java JDBC SQL statement generator and simple object mapper + + \ No newline at end of file diff --git a/src/main/java/com/iciql/util/GenerateModels.java b/src/main/java/com/iciql/util/GenerateModels.java new file mode 100644 index 0000000..eac9f6c --- /dev/null +++ b/src/main/java/com/iciql/util/GenerateModels.java @@ -0,0 +1,193 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.util; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.sql.SQLException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.iciql.Db; +import com.iciql.DbInspector; + +/** + * Generates iciql models. + */ +public class GenerateModels { + + /** + * The output stream where this tool writes to. + */ + protected PrintStream out = System.out; + + public static void main(String... args) { + GenerateModels tool = new GenerateModels(); + try { + tool.runTool(args); + } catch (SQLException e) { + tool.out.print("Error: "); + tool.out.println(e.getMessage()); + tool.out.println(); + tool.showUsage(); + } + } + + public void runTool(String... args) throws SQLException { + String url = null; + String user = "sa"; + String password = ""; + String schema = null; + String table = null; + String packageName = ""; + String folder = null; + boolean annotateSchema = true; + boolean trimStrings = false; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-url")) { + url = args[++i]; + } else if (arg.equals("-user")) { + user = args[++i]; + } else if (arg.equals("-password")) { + password = args[++i]; + } else if (arg.equals("-schema")) { + schema = args[++i]; + } else if (arg.equals("-table")) { + table = args[++i]; + } else if (arg.equals("-package")) { + packageName = args[++i]; + } else if (arg.equals("-folder")) { + folder = args[++i]; + } else if (arg.equals("-annotateSchema")) { + try { + annotateSchema = Boolean.parseBoolean(args[++i]); + } catch (Throwable t) { + throw new SQLException("Can not parse -annotateSchema value"); + } + } else if (arg.equals("-trimStrings")) { + try { + trimStrings = Boolean.parseBoolean(args[++i]); + } catch (Throwable t) { + throw new SQLException("Can not parse -trimStrings value"); + } + } else { + throwUnsupportedOption(arg); + } + } + if (url == null) { + throw new SQLException("URL not set"); + } + execute(url, user, password, schema, table, packageName, folder, annotateSchema, trimStrings); + } + + /** + * Generates models from the database. + * + * @param url + * the database URL + * @param user + * the user name + * @param password + * the password + * @param schema + * the schema to read from. null for all schemas. + * @param table + * the table to model. null for all tables within schema. + * @param packageName + * the package name of the model classes. + * @param folder + * destination folder for model classes (package path not + * included) + * @param annotateSchema + * includes the schema in the table model annotations + * @param trimStrings + * automatically trim strings that exceed maxLength + */ + public static void execute(String url, String user, String password, String schema, String table, + String packageName, String folder, boolean annotateSchema, boolean trimStrings) + throws SQLException { + try { + Db db; + if (password == null) { + db = Db.open(url, user, (String) null); + } else { + db = Db.open(url, user, password); + } + DbInspector inspector = new DbInspector(db); + List models = inspector.generateModel(schema, table, packageName, annotateSchema, + trimStrings); + File parentFile; + if (StringUtils.isNullOrEmpty(folder)) { + parentFile = new File(System.getProperty("user.dir")); + } else { + parentFile = new File(folder); + } + parentFile.mkdirs(); + Pattern p = Pattern.compile("class ([a-zA-Z0-9]+)"); + for (String model : models) { + Matcher m = p.matcher(model); + if (m.find()) { + String className = m.group().substring("class".length()).trim(); + File classFile = new File(parentFile, className + ".java"); + Writer o = new FileWriter(classFile, false); + PrintWriter writer = new PrintWriter(new BufferedWriter(o)); + writer.write(model); + writer.close(); + System.out.println("Generated " + classFile.getAbsolutePath()); + } + } + } catch (IOException io) { + throw new SQLException("could not generate model", io); + } + } + + /** + * Throw a SQLException saying this command line option is not supported. + * + * @param option + * the unsupported option + * @return this method never returns normally + */ + protected SQLException throwUnsupportedOption(String option) throws SQLException { + showUsage(); + throw new SQLException("Unsupported option: " + option); + } + + protected void showUsage() { + out.println("GenerateModels"); + out.println("Usage:"); + out.println(); + out.println("(*) -url jdbc:h2:~test"); + out.println(" -user "); + out.println(" -password "); + out.println(" -schema "); + out.println(" -table "); + out.println(" -package "); + out.println(" -folder "); + out.println(" -annotateSchema "); + out.println(" -trimStrings "); + } + +} diff --git a/src/main/java/com/iciql/util/IciqlLogger.java b/src/main/java/com/iciql/util/IciqlLogger.java new file mode 100644 index 0000000..d8005bb --- /dev/null +++ b/src/main/java/com/iciql/util/IciqlLogger.java @@ -0,0 +1,214 @@ +/* + * Copyright 2011 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.util; + +import java.text.DecimalFormat; +import java.text.MessageFormat; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; + +import com.iciql.IciqlException; + +/** + * Utility class to optionally log generated statements to IciqlListeners.
+ * Statement logging is disabled by default. + *

+ * This class also tracks the counts for generated statements by major type. + * + */ +public class IciqlLogger { + + /** + * Enumeration of the different statement types that are logged. + */ + public enum StatementType { + STAT, TOTAL, CREATE, INSERT, UPDATE, MERGE, DELETE, SELECT, DROP, WARN; + } + + /** + * Interface that defines an iciql listener. + */ + public interface IciqlListener { + void logIciql(StatementType type, String statement); + } + + private static final ExecutorService EXEC = Executors.newSingleThreadExecutor(); + private static final Set LISTENERS = Utils.newHashSet(); + private static final IciqlListener CONSOLE = new IciqlListener() { + + @Override + public void logIciql(StatementType type, String message) { + System.out.println(message); + } + }; + + private static final AtomicLong SELECT_COUNT = new AtomicLong(); + private static final AtomicLong CREATE_COUNT = new AtomicLong(); + private static final AtomicLong INSERT_COUNT = new AtomicLong(); + private static final AtomicLong UPDATE_COUNT = new AtomicLong(); + private static final AtomicLong MERGE_COUNT = new AtomicLong(); + private static final AtomicLong DELETE_COUNT = new AtomicLong(); + private static final AtomicLong DROP_COUNT = new AtomicLong(); + private static final AtomicLong WARN_COUNT = new AtomicLong(); + + /** + * Activates the Console Logger. + */ + public static void activateConsoleLogger() { + registerListener(CONSOLE); + } + + /** + * Deactivates the Console Logger. + */ + public static void deactivateConsoleLogger() { + unregisterListener(CONSOLE); + } + + /** + * Registers a listener with the relay. + * + * @param listener + */ + public static void registerListener(IciqlListener listener) { + LISTENERS.add(listener); + } + + /** + * Unregisters a listener with the relay. + * + * @param listener + */ + public static void unregisterListener(IciqlListener listener) { + if (!LISTENERS.remove(listener)) { + throw new IciqlException("Failed to remove iciql listener {0}", listener); + } + } + + public static void create(String statement) { + CREATE_COUNT.incrementAndGet(); + logStatement(StatementType.CREATE, statement); + } + + public static void insert(String statement) { + INSERT_COUNT.incrementAndGet(); + logStatement(StatementType.INSERT, statement); + } + + public static void update(String statement) { + UPDATE_COUNT.incrementAndGet(); + logStatement(StatementType.UPDATE, statement); + } + + public static void merge(String statement) { + MERGE_COUNT.incrementAndGet(); + logStatement(StatementType.MERGE, statement); + } + + public static void delete(String statement) { + DELETE_COUNT.incrementAndGet(); + logStatement(StatementType.DELETE, statement); + } + + public static void select(String statement) { + SELECT_COUNT.incrementAndGet(); + logStatement(StatementType.SELECT, statement); + } + + public static void drop(String statement) { + DROP_COUNT.incrementAndGet(); + logStatement(StatementType.DROP, statement); + } + + public static void warn(String message, Object... args) { + WARN_COUNT.incrementAndGet(); + logStatement(StatementType.WARN, args.length > 0 ? MessageFormat.format(message, args) : message); + } + + private static void logStatement(final StatementType type, final String statement) { + for (final IciqlListener listener : LISTENERS) { + EXEC.execute(new Runnable() { + public void run() { + listener.logIciql(type, statement); + } + }); + } + } + + public static long getCreateCount() { + return CREATE_COUNT.longValue(); + } + + public static long getInsertCount() { + return INSERT_COUNT.longValue(); + } + + public static long getUpdateCount() { + return UPDATE_COUNT.longValue(); + } + + public static long getMergeCount() { + return MERGE_COUNT.longValue(); + } + + public static long getDeleteCount() { + return DELETE_COUNT.longValue(); + } + + public static long getSelectCount() { + return SELECT_COUNT.longValue(); + } + + public static long getDropCount() { + return DROP_COUNT.longValue(); + } + + public static long getWarnCount() { + return WARN_COUNT.longValue(); + } + + public static long getTotalCount() { + return getCreateCount() + getInsertCount() + getUpdateCount() + getDeleteCount() + getMergeCount() + + getSelectCount() + getDropCount(); + } + + public static void logStats() { + logStatement(StatementType.STAT, "iciql Runtime Statistics"); + logStatement(StatementType.STAT, "========================"); + logStat(StatementType.WARN, getWarnCount()); + logStatement(StatementType.STAT, "========================"); + logStat(StatementType.CREATE, getCreateCount()); + logStat(StatementType.INSERT, getInsertCount()); + logStat(StatementType.UPDATE, getUpdateCount()); + logStat(StatementType.MERGE, getMergeCount()); + logStat(StatementType.DELETE, getDeleteCount()); + logStat(StatementType.SELECT, getSelectCount()); + logStat(StatementType.DROP, getDropCount()); + logStatement(StatementType.STAT, "========================"); + logStat(StatementType.TOTAL, getTotalCount()); + } + + private static void logStat(StatementType type, long value) { + if (value > 0) { + DecimalFormat df = new DecimalFormat("###,###,###,###"); + logStatement(StatementType.STAT, + StringUtils.pad(type.name(), 6, " ", true) + " = " + df.format(value)); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/iciql/util/JdbcUtils.java b/src/main/java/com/iciql/util/JdbcUtils.java new file mode 100644 index 0000000..4a4a2b6 --- /dev/null +++ b/src/main/java/com/iciql/util/JdbcUtils.java @@ -0,0 +1,254 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.util; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import javax.naming.Context; +import javax.sql.DataSource; +import javax.sql.XAConnection; + +/** + * This is a utility class with JDBC helper functions. + */ +public class JdbcUtils { + + private static final String[] DRIVERS = { "h2:", "org.h2.Driver", "Cache:", + "com.intersys.jdbc.CacheDriver", "daffodilDB://", "in.co.daffodil.db.rmi.RmiDaffodilDBDriver", + "daffodil", "in.co.daffodil.db.jdbc.DaffodilDBDriver", "db2:", "COM.ibm.db2.jdbc.net.DB2Driver", + "derby:net:", "org.apache.derby.jdbc.ClientDriver", "derby://", + "org.apache.derby.jdbc.ClientDriver", "derby:", "org.apache.derby.jdbc.EmbeddedDriver", + "FrontBase:", "com.frontbase.jdbc.FBJDriver", "firebirdsql:", "org.firebirdsql.jdbc.FBDriver", + "hsqldb:", "org.hsqldb.jdbcDriver", "informix-sqli:", "com.informix.jdbc.IfxDriver", "jtds:", + "net.sourceforge.jtds.jdbc.Driver", "microsoft:", "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "mimer:", "com.mimer.jdbc.Driver", "mysql:", "com.mysql.jdbc.Driver", "odbc:", + "sun.jdbc.odbc.JdbcOdbcDriver", "oracle:", "oracle.jdbc.driver.OracleDriver", "pervasive:", + "com.pervasive.jdbc.v2.Driver", "pointbase:micro:", "com.pointbase.me.jdbc.jdbcDriver", + "pointbase:", "com.pointbase.jdbc.jdbcUniversalDriver", "postgresql:", "org.postgresql.Driver", + "sybase:", "com.sybase.jdbc3.jdbc.SybDriver", "sqlserver:", + "com.microsoft.sqlserver.jdbc.SQLServerDriver", "teradata:", "com.ncr.teradata.TeraDriver", }; + + private JdbcUtils() { + // utility class + } + + /** + * Close a statement without throwing an exception. + * + * @param stat + * the statement or null + */ + public static void closeSilently(Statement stat) { + if (stat != null) { + try { + stat.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Close a connection without throwing an exception. + * + * @param conn + * the connection or null + */ + public static void closeSilently(Connection conn) { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Close a result set without throwing an exception. + * + * @param rs + * the result set or null + */ + public static void closeSilently(ResultSet rs) { + closeSilently(rs, false); + } + + /** + * Close a result set, and optionally its statement without throwing an + * exception. + * + * @param rs + * the result set or null + */ + public static void closeSilently(ResultSet rs, boolean closeStatement) { + if (rs != null) { + Statement stat = null; + if (closeStatement) { + try { + stat = rs.getStatement(); + } catch (SQLException e) { + // ignore + } + } + try { + rs.close(); + } catch (SQLException e) { + // ignore + } + closeSilently(stat); + } + } + + /** + * Close an XA connection set without throwing an exception. + * + * @param conn + * the XA connection or null + */ + public static void closeSilently(XAConnection conn) { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Open a new database connection with the given settings. + * + * @param driver + * the driver class name + * @param url + * the database URL + * @param user + * the user name + * @param password + * the password + * @return the database connection + */ + public static Connection getConnection(String driver, String url, String user, String password) + throws SQLException { + Properties prop = new Properties(); + if (user != null) { + prop.setProperty("user", user); + } + if (password != null) { + prop.setProperty("password", password); + } + return getConnection(driver, url, prop); + } + + /** + * Escape table or schema patterns used for DatabaseMetaData functions. + * + * @param pattern + * the pattern + * @return the escaped pattern + */ + public static String escapeMetaDataPattern(String pattern) { + if (pattern == null || pattern.length() == 0) { + return pattern; + } + return StringUtils.replaceAll(pattern, "\\", "\\\\"); + } + + /** + * Open a new database connection with the given settings. + * + * @param driver + * the driver class name + * @param url + * the database URL + * @param prop + * the properties containing at least the user name and password + * @return the database connection + */ + public static Connection getConnection(String driver, String url, Properties prop) throws SQLException { + if (StringUtils.isNullOrEmpty(driver)) { + JdbcUtils.load(url); + } else { + Class d = Utils.loadClass(driver); + if (java.sql.Driver.class.isAssignableFrom(d)) { + return DriverManager.getConnection(url, prop); + } else if (javax.naming.Context.class.isAssignableFrom(d)) { + // JNDI context + try { + Context context = (Context) d.newInstance(); + DataSource ds = (DataSource) context.lookup(url); + String user = prop.getProperty("user"); + String password = prop.getProperty("password"); + if (StringUtils.isNullOrEmpty(user) && StringUtils.isNullOrEmpty(password)) { + return ds.getConnection(); + } + return ds.getConnection(user, password); + } catch (SQLException e) { + throw e; + } catch (Exception e) { + throw new SQLException("Failed to get connection for " + url, e); + } + } else { + // Don't know, but maybe it loaded a JDBC Driver + return DriverManager.getConnection(url, prop); + } + } + return DriverManager.getConnection(url, prop); + } + + /** + * Get the driver class name for the given URL, or null if the URL is + * unknown. + * + * @param url + * the database URL + * @return the driver class name + */ + public static String getDriver(String url) { + if (url.startsWith("jdbc:")) { + url = url.substring("jdbc:".length()); + for (int i = 0; i < DRIVERS.length; i += 2) { + String prefix = DRIVERS[i]; + if (url.startsWith(prefix)) { + return DRIVERS[i + 1]; + } + } + } + return null; + } + + /** + * Load the driver class for the given URL, if the database URL is known. + * + * @param url + * the database URL + */ + public static void load(String url) { + String driver = getDriver(url); + if (driver != null) { + Utils.loadClass(driver); + } + } + +} diff --git a/src/main/java/com/iciql/util/Slf4jIciqlListener.java b/src/main/java/com/iciql/util/Slf4jIciqlListener.java new file mode 100644 index 0000000..ded393f --- /dev/null +++ b/src/main/java/com/iciql/util/Slf4jIciqlListener.java @@ -0,0 +1,92 @@ +/* + * Copyright 2011 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.util; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iciql.Iciql; +import com.iciql.util.IciqlLogger.IciqlListener; +import com.iciql.util.IciqlLogger.StatementType; + +/** + * Slf4jIciqlListener interfaces the IciqlLogger to the SLF4J logging framework. + */ +public class Slf4jIciqlListener implements IciqlListener { + + private Logger logger = LoggerFactory.getLogger(Iciql.class); + + /** + * Enumeration representing the SLF4J log levels. + */ + public enum Level { + ERROR, WARN, INFO, DEBUG, TRACE, OFF; + } + + private final Level defaultLevel; + + private final Map levels; + + public Slf4jIciqlListener() { + this(Level.TRACE); + } + + public Slf4jIciqlListener(Level defaultLevel) { + this.defaultLevel = defaultLevel; + levels = new HashMap(); + for (StatementType type : StatementType.values()) { + levels.put(type, defaultLevel); + } + } + + /** + * Sets the logging level for a particular statement type. + * + * @param type + * @param level + */ + public void setLevel(StatementType type, Level level) { + levels.put(type, defaultLevel); + } + + @Override + public void logIciql(StatementType type, String statement) { + Level level = levels.get(type); + switch (level) { + case ERROR: + logger.error(statement); + break; + case WARN: + logger.warn(statement); + break; + case INFO: + logger.info(statement); + break; + case DEBUG: + logger.debug(statement); + break; + case TRACE: + logger.trace(statement); + break; + case OFF: + break; + } + } +} diff --git a/src/main/java/com/iciql/util/StatementBuilder.java b/src/main/java/com/iciql/util/StatementBuilder.java new file mode 100644 index 0000000..47e8054 --- /dev/null +++ b/src/main/java/com/iciql/util/StatementBuilder.java @@ -0,0 +1,166 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.util; + +/** + * A utility class to build a statement. In addition to the methods supported by + * StringBuilder, it allows to add a text only in the second iteration. This + * simplified constructs such as: + * + *

+ * StringBuilder buff = new StringBuilder();
+ * for (int i = 0; i < args.length; i++) {
+ * 	if (i > 0) {
+ * 		buff.append(", ");
+ * 	}
+ * 	buff.append(args[i]);
+ * }
+ * 
+ * + * to + * + *
+ * StatementBuilder buff = new StatementBuilder();
+ * for (String s : args) {
+ * 	buff.appendExceptFirst(", ");
+ * 	buff.append(a);
+ * }
+ * 
+ */ +public class StatementBuilder { + + private final StringBuilder builder = new StringBuilder(); + private int index; + + /** + * Create a new builder. + */ + public StatementBuilder() { + // nothing to do + } + + /** + * Create a new builder. + * + * @param string + * the initial string + */ + public StatementBuilder(String string) { + builder.append(string); + } + + /** + * Append a text. + * + * @param s + * the text to append + * @return itself + */ + public StatementBuilder append(String s) { + builder.append(s); + return this; + } + + /** + * Append a character. + * + * @param c + * the character to append + * @return itself + */ + public StatementBuilder append(char c) { + builder.append(c); + return this; + } + + /** + * Append a number. + * + * @param x + * the number to append + * @return itself + */ + public StatementBuilder append(long x) { + builder.append(x); + return this; + } + + /** + * Returns the current value of the loop counter. + * + * @return the loop counter + */ + public int getCount() { + return index; + } + + /** + * Reset the loop counter. + * + * @return itself + */ + public StatementBuilder resetCount() { + index = 0; + return this; + } + + /** + * Append a text, but only if appendExceptFirst was never called. + * + * @param s + * the text to append + */ + public void appendOnlyFirst(String s) { + if (index == 0) { + builder.append(s); + } + } + + /** + * Append a text, except when this method is called the first time. + * + * @param s + * the text to append + */ + public void appendExceptFirst(String s) { + if (index++ > 0) { + builder.append(s); + } + } + + public void append(StatementBuilder sb) { + builder.append(sb); + } + + public void insert(int offset, char c) { + builder.insert(offset, c); + } + + public String toString() { + return builder.toString(); + } + + /** + * Get the length. + * + * @return the length + */ + public int length() { + return builder.length(); + } +} diff --git a/src/main/java/com/iciql/util/StringUtils.java b/src/main/java/com/iciql/util/StringUtils.java new file mode 100644 index 0000000..dd3f180 --- /dev/null +++ b/src/main/java/com/iciql/util/StringUtils.java @@ -0,0 +1,382 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; + +/** + * Common string utilities. + * + */ +public class StringUtils { + + /** + * Replace all occurrences of the before string with the after string. + * + * @param s + * the string + * @param before + * the old text + * @param after + * the new text + * @return the string with the before string replaced + */ + public static String replaceAll(String s, String before, String after) { + int next = s.indexOf(before); + if (next < 0) { + return s; + } + StringBuilder buff = new StringBuilder(s.length() - before.length() + after.length()); + int index = 0; + while (true) { + buff.append(s.substring(index, next)).append(after); + index = next + before.length(); + next = s.indexOf(before, index); + if (next < 0) { + buff.append(s.substring(index)); + break; + } + } + return buff.toString(); + } + + /** + * Check if a String is null or empty (the length is null). + * + * @param s + * the string to check + * @return true if it is null or empty + */ + public static boolean isNullOrEmpty(String s) { + return s == null || s.length() == 0; + } + + /** + * Convert a string to a Java literal using the correct escape sequences. + * The literal is not enclosed in double quotes. The result can be used in + * properties files or in Java source code. + * + * @param s + * the text to convert + * @return the Java representation + */ + public static String javaEncode(String s) { + int length = s.length(); + StringBuilder buff = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + switch (c) { + // case '\b': + // // BS backspace + // // not supported in properties files + // buff.append("\\b"); + // break; + case '\t': + // HT horizontal tab + buff.append("\\t"); + break; + case '\n': + // LF linefeed + buff.append("\\n"); + break; + case '\f': + // FF form feed + buff.append("\\f"); + break; + case '\r': + // CR carriage return + buff.append("\\r"); + break; + case '"': + // double quote + buff.append("\\\""); + break; + case '\\': + // backslash + buff.append("\\\\"); + break; + default: + int ch = c & 0xffff; + if (ch >= ' ' && (ch < 0x80)) { + buff.append(c); + // not supported in properties files + // } else if(ch < 0xff) { + // buff.append("\\"); + // // make sure it's three characters (0x200 is octal 1000) + // buff.append(Integer.toOctalString(0x200 | + // ch).substring(1)); + } else { + buff.append("\\u"); + // make sure it's four characters + buff.append(Integer.toHexString(0x10000 | ch).substring(1)); + } + } + } + return buff.toString(); + } + + /** + * Pad a string. This method is used for the SQL function RPAD and LPAD. + * + * @param string + * the original string + * @param n + * the target length + * @param padding + * the padding string + * @param right + * true if the padding should be appended at the end + * @return the padded string + */ + public static String pad(String string, int n, String padding, boolean right) { + if (n < 0) { + n = 0; + } + if (n < string.length()) { + return string.substring(0, n); + } else if (n == string.length()) { + return string; + } + char paddingChar; + if (padding == null || padding.length() == 0) { + paddingChar = ' '; + } else { + paddingChar = padding.charAt(0); + } + StringBuilder buff = new StringBuilder(n); + n -= string.length(); + if (right) { + buff.append(string); + } + for (int i = 0; i < n; i++) { + buff.append(paddingChar); + } + if (!right) { + buff.append(string); + } + return buff.toString(); + } + + /** + * Convert a string to a SQL literal. Null is converted to NULL. The text is + * enclosed in single quotes. If there are any special characters, the + * method STRINGDECODE is used. + * + * @param s + * the text to convert. + * @return the SQL literal + */ + public static String quoteStringSQL(String s) { + if (s == null) { + return "NULL"; + } + int length = s.length(); + StringBuilder buff = new StringBuilder(length + 2); + buff.append('\''); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (c == '\'') { + buff.append(c); + } else if (c < ' ' || c > 127) { + // need to start from the beginning because maybe there was a \ + // that was not quoted + return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")"; + } + buff.append(c); + } + buff.append('\''); + return buff.toString(); + } + + /** + * Split a string into an array of strings using the given separator. A null + * string will result in a null array, and an empty string in a zero element + * array. + * + * @param s + * the string to split + * @param separatorChar + * the separator character + * @param trim + * whether each element should be trimmed + * @return the array list + */ + public static String[] arraySplit(String s, char separatorChar, boolean trim) { + if (s == null) { + return null; + } + int length = s.length(); + if (length == 0) { + return new String[0]; + } + ArrayList list = Utils.newArrayList(); + StringBuilder buff = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (c == separatorChar) { + String e = buff.toString(); + list.add(trim ? e.trim() : e); + buff.setLength(0); + } else if (c == '\\' && i < length - 1) { + buff.append(s.charAt(++i)); + } else { + buff.append(c); + } + } + String e = buff.toString(); + list.add(trim ? e.trim() : e); + String[] array = new String[list.size()]; + list.toArray(array); + return array; + } + + /** + * Calculates the SHA1 of the string. + * + * @param text + * @return sha1 of the string + */ + public static String calculateSHA1(String text) { + try { + byte[] bytes = text.getBytes("iso-8859-1"); + return calculateSHA1(bytes); + } catch (UnsupportedEncodingException u) { + throw new RuntimeException(u); + } + } + + /** + * Calculates the SHA1 of the byte array. + * + * @param bytes + * @return sha1 of the byte array + */ + public static String calculateSHA1(byte[] bytes) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(bytes, 0, bytes.length); + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(digest.length * 2); + for (int i = 0; i < digest.length; i++) { + if (((int) digest[i] & 0xff) < 0x10) { + sb.append('0'); + } + sb.append(Integer.toHexString((int) digest[i] & 0xff)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException t) { + throw new RuntimeException(t); + } + } + + /** + * Counts the occurrences of char c in the given string. + * + * @param c + * the character to count + * @param value + * the source string + * @return the count of c in value + */ + public static int count(char c, String value) { + int count = 0; + for (char cv : value.toCharArray()) { + if (cv == c) { + count++; + } + } + return count; + } + + /** + * Prepare text for html presentation. Replace sensitive characters with + * html entities. + * + * @param inStr + * @param changeSpace + * @return plain text escaped for html + */ + public static String escapeForHtml(String inStr, boolean changeSpace) { + StringBuffer retStr = new StringBuffer(); + int i = 0; + while (i < inStr.length()) { + if (inStr.charAt(i) == '&') { + retStr.append("&"); + } else if (inStr.charAt(i) == '<') { + retStr.append("<"); + } else if (inStr.charAt(i) == '>') { + retStr.append(">"); + } else if (inStr.charAt(i) == '\"') { + retStr.append("""); + } else if (changeSpace && inStr.charAt(i) == ' ') { + retStr.append(" "); + } else if (changeSpace && inStr.charAt(i) == '\t') { + retStr.append("    "); + } else { + retStr.append(inStr.charAt(i)); + } + i++; + } + return retStr.toString(); + } + + /** + * Replaces carriage returns and line feeds with html line breaks. + * + * @param string + * @return plain text with html line breaks + */ + public static String breakLinesForHtml(String string) { + return string.replace("\r\n", "
").replace("\r", "
").replace("\n", "
"); + } + + /** + * Returns the string content of the specified file. + * + * @param file + * @param lineEnding + * @return the string content of the file + */ + public static String readContent(File file, String lineEnding) { + StringBuilder sb = new StringBuilder(); + try { + InputStreamReader is = new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")); + BufferedReader reader = new BufferedReader(is); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line); + if (lineEnding != null) { + sb.append(lineEnding); + } + } + reader.close(); + } catch (Throwable t) { + System.err.println("Failed to read content of " + file.getAbsolutePath()); + t.printStackTrace(); + } + return sb.toString(); + } +} diff --git a/src/main/java/com/iciql/util/Utils.java b/src/main/java/com/iciql/util/Utils.java new file mode 100644 index 0000000..77110b8 --- /dev/null +++ b/src/main/java/com/iciql/util/Utils.java @@ -0,0 +1,459 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.lang.reflect.Constructor; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Blob; +import java.sql.Clob; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import com.iciql.Iciql.EnumId; +import com.iciql.Iciql.EnumType; +import com.iciql.IciqlException; + +/** + * Generic utility methods. + */ +public class Utils { + + public static final AtomicLong COUNTER = new AtomicLong(0); + + public static final AtomicInteger AS_COUNTER = new AtomicInteger(0); + + private static final boolean MAKE_ACCESSIBLE = true; + + private static final int BUFFER_BLOCK_SIZE = 4 * 1024; + + public static synchronized int nextAsCount() { + // prevent negative values and use a threadsafe counter + int count = AS_COUNTER.incrementAndGet(); + if (count == Integer.MAX_VALUE) { + count = 0; + AS_COUNTER.set(count); + } + return count; + } + + @SuppressWarnings("unchecked") + public static Class getClass(X x) { + return (Class) x.getClass(); + } + + public static Class loadClass(String className) { + try { + return Class.forName(className); + } catch (Exception e) { + throw new IciqlException(e); + } + } + + public static ArrayList newArrayList() { + return new ArrayList(); + } + + public static ArrayList newArrayList(Collection c) { + return new ArrayList(c); + } + + public static HashSet newHashSet() { + return new HashSet(); + } + + public static HashSet newHashSet(Collection list) { + return new HashSet(list); + } + + public static HashMap newHashMap() { + return new HashMap(); + } + + public static Map newSynchronizedHashMap() { + HashMap map = newHashMap(); + return Collections.synchronizedMap(map); + } + + public static IdentityHashMap newIdentityHashMap() { + return new IdentityHashMap(); + } + + public static ThreadLocal newThreadLocal(final Class clazz) { + return new ThreadLocal() { + @SuppressWarnings("rawtypes") + @Override + protected T initialValue() { + try { + return clazz.newInstance(); + } catch (Exception e) { + if (MAKE_ACCESSIBLE) { + Constructor[] constructors = clazz.getDeclaredConstructors(); + // try 0 length constructors + for (Constructor c : constructors) { + if (c.getParameterTypes().length == 0) { + c.setAccessible(true); + try { + return clazz.newInstance(); + } catch (Exception e2) { + // ignore + } + } + } + } + throw new IciqlException(e, + "Missing default constructor? Exception trying to instantiate {0}: {1}", + clazz.getName(), e.getMessage()); + } + } + }; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static T newObject(Class clazz) { + // must create new instances + if (clazz == int.class || clazz == Integer.class) { + return (T) new Integer((int) COUNTER.getAndIncrement()); + } else if (clazz == String.class) { + return (T) ("" + COUNTER.getAndIncrement()); + } else if (clazz == long.class || clazz == Long.class) { + return (T) new Long(COUNTER.getAndIncrement()); + } else if (clazz == short.class || clazz == Short.class) { + return (T) new Short((short) COUNTER.getAndIncrement()); + } else if (clazz == byte.class || clazz == Byte.class) { + return (T) new Byte((byte) COUNTER.getAndIncrement()); + } else if (clazz == float.class || clazz == Float.class) { + return (T) new Float(COUNTER.getAndIncrement()); + } else if (clazz == double.class || clazz == Double.class) { + return (T) new Double(COUNTER.getAndIncrement()); + } else if (clazz == boolean.class || clazz == Boolean.class) { + COUNTER.getAndIncrement(); + return (T) new Boolean(false); + } else if (clazz == BigDecimal.class) { + return (T) new BigDecimal(COUNTER.getAndIncrement()); + } else if (clazz == BigInteger.class) { + return (T) new BigInteger("" + COUNTER.getAndIncrement()); + } else if (clazz == java.sql.Date.class) { + return (T) new java.sql.Date(COUNTER.getAndIncrement()); + } else if (clazz == java.sql.Time.class) { + return (T) new java.sql.Time(COUNTER.getAndIncrement()); + } else if (clazz == java.sql.Timestamp.class) { + return (T) new java.sql.Timestamp(COUNTER.getAndIncrement()); + } else if (clazz == java.util.Date.class) { + return (T) new java.util.Date(COUNTER.getAndIncrement()); + } else if (clazz == byte[].class) { + COUNTER.getAndIncrement(); + return (T) new byte[0]; + } else if (clazz.isEnum()) { + COUNTER.getAndIncrement(); + // enums can not be instantiated reflectively + // return first constant as reference + return clazz.getEnumConstants()[0]; + } else if (clazz == java.util.UUID.class) { + COUNTER.getAndIncrement(); + return (T) UUID.randomUUID(); + } + try { + return clazz.newInstance(); + } catch (Exception e) { + if (MAKE_ACCESSIBLE) { + Constructor[] constructors = clazz.getDeclaredConstructors(); + // try 0 length constructors + for (Constructor c : constructors) { + if (c.getParameterTypes().length == 0) { + c.setAccessible(true); + try { + return clazz.newInstance(); + } catch (Exception e2) { + // ignore + } + } + } + // try 1 length constructors + for (Constructor c : constructors) { + if (c.getParameterTypes().length == 1) { + c.setAccessible(true); + try { + return (T) c.newInstance(new Object[1]); + } catch (Exception e2) { + // ignore + } + } + } + } + throw new IciqlException(e, + "Missing default constructor?! Exception trying to instantiate {0}: {1}", + clazz.getName(), e.getMessage()); + } + } + + public static boolean isSimpleType(Class clazz) { + if (Number.class.isAssignableFrom(clazz)) { + return true; + } else if (clazz == String.class) { + return true; + } + return false; + } + + public static Object convert(Object o, Class targetType) { + if (o == null) { + return null; + } + Class currentType = o.getClass(); + if (targetType.isAssignableFrom(currentType)) { + return o; + } + + // convert from CLOB/TEXT/VARCHAR to String + if (targetType == String.class) { + if (Clob.class.isAssignableFrom(currentType)) { + Clob c = (Clob) o; + try { + Reader r = c.getCharacterStream(); + return readStringAndClose(r, -1); + } catch (Exception e) { + throw new IciqlException(e, "error converting CLOB to String: ", e.toString()); + } + } + return o.toString(); + } + + if (Boolean.class.isAssignableFrom(targetType) || boolean.class.isAssignableFrom(targetType)) { + // convert from number to boolean + if (Number.class.isAssignableFrom(currentType)) { + Number n = (Number) o; + return n.intValue() > 0; + } + // convert from string to boolean + if (String.class.isAssignableFrom(currentType)) { + String s = o.toString().toLowerCase(); + float f = 0f; + try { + f = Float.parseFloat(s); + } catch (Exception e) { + } + return f > 0 || s.equals("true") || s.equals("yes") || s.equals("y") || s.equals("on"); + } + } + + // convert from boolean to number + if (Boolean.class.isAssignableFrom(currentType)) { + Boolean b = (Boolean) o; + if (Number.class.isAssignableFrom(targetType)) { + return b ? 1 : 0; + } + if (boolean.class.isAssignableFrom(targetType)) { + return b.booleanValue(); + } + } + + // convert from number to number + if (Number.class.isAssignableFrom(currentType)) { + Number n = (Number) o; + if (targetType == byte.class || targetType == Byte.class) { + return n.byteValue(); + } else if (targetType == short.class || targetType == Short.class) { + return n.shortValue(); + } else if (targetType == int.class || targetType == Integer.class) { + return n.intValue(); + } else if (targetType == long.class || targetType == Long.class) { + return n.longValue(); + } else if (targetType == double.class || targetType == Double.class) { + return n.doubleValue(); + } else if (targetType == float.class || targetType == Float.class) { + return n.floatValue(); + } + } + + // convert from BLOB + if (targetType == byte[].class) { + if (Blob.class.isAssignableFrom(currentType)) { + Blob b = (Blob) o; + try { + InputStream is = b.getBinaryStream(); + return readBlobAndClose(is, -1); + } catch (Exception e) { + throw new IciqlException(e, "error converting BLOB to byte[]: ", e.toString()); + } + } + } + throw new IciqlException("Can not convert the value {0} from {1} to {2}", o, currentType, targetType); + } + + public static Object convertEnum(Enum o, EnumType type) { + if (o == null) { + return null; + } + switch (type) { + case ORDINAL: + return o.ordinal(); + case ENUMID: + if (!EnumId.class.isAssignableFrom(o.getClass())) { + throw new IciqlException("Can not convert the enum {0} using ENUMID", o); + } + EnumId enumid = (EnumId) o; + return enumid.enumId(); + case NAME: + default: + return o.name(); + } + } + + public static Object convertEnum(Object o, Class targetType, EnumType type) { + if (o == null) { + return null; + } + Class currentType = o.getClass(); + if (targetType.isAssignableFrom(currentType)) { + return o; + } + // convert from VARCHAR/TEXT/INT to Enum + Enum[] values = (Enum[]) targetType.getEnumConstants(); + if (Clob.class.isAssignableFrom(currentType)) { + // TEXT/CLOB field + Clob c = (Clob) o; + String name = null; + try { + Reader r = c.getCharacterStream(); + name = readStringAndClose(r, -1); + } catch (Exception e) { + throw new IciqlException(e, "error converting CLOB to String: ", e.toString()); + } + + // find name match + for (Enum value : values) { + if (value.name().equalsIgnoreCase(name)) { + return value; + } + } + } else if (String.class.isAssignableFrom(currentType)) { + // VARCHAR field + String name = (String) o; + for (Enum value : values) { + if (value.name().equalsIgnoreCase(name)) { + return value; + } + } + } else if (Number.class.isAssignableFrom(currentType)) { + // INT field + int n = ((Number) o).intValue(); + if (type.equals(EnumType.ORDINAL)) { + // ORDINAL mapping + for (Enum value : values) { + if (value.ordinal() == n) { + return value; + } + } + } else if (type.equals(EnumType.ENUMID)) { + if (!EnumId.class.isAssignableFrom(targetType)) { + throw new IciqlException("Can not convert the value {0} from {1} to {2} using ENUMID", o, + currentType, targetType); + } + // ENUMID mapping + for (Enum value : values) { + EnumId enumid = (EnumId) value; + if (enumid.enumId() == n) { + return value; + } + } + } + } + throw new IciqlException("Can not convert the value {0} from {1} to {2}", o, currentType, targetType); + } + + /** + * Read a number of characters from a reader and close it. + * + * @param in + * the reader + * @param length + * the maximum number of characters to read, or -1 to read until + * the end of file + * @return the string read + */ + public static String readStringAndClose(Reader in, int length) throws IOException { + try { + if (length <= 0) { + length = Integer.MAX_VALUE; + } + int block = Math.min(BUFFER_BLOCK_SIZE, length); + StringWriter out = new StringWriter(length == Integer.MAX_VALUE ? block : length); + char[] buff = new char[block]; + while (length > 0) { + int len = Math.min(block, length); + len = in.read(buff, 0, len); + if (len < 0) { + break; + } + out.write(buff, 0, len); + length -= len; + } + return out.toString(); + } finally { + in.close(); + } + } + + /** + * Read a number of bytes from a stream and close it. + * + * @param in + * the stream + * @param length + * the maximum number of bytes to read, or -1 to read until the + * end of file + * @return the string read + */ + public static byte[] readBlobAndClose(InputStream in, int length) throws IOException { + try { + if (length <= 0) { + length = Integer.MAX_VALUE; + } + int block = Math.min(BUFFER_BLOCK_SIZE, length); + ByteArrayOutputStream out = new ByteArrayOutputStream(length == Integer.MAX_VALUE ? block + : length); + byte[] buff = new byte[block]; + while (length > 0) { + int len = Math.min(block, length); + len = in.read(buff, 0, len); + if (len < 0) { + break; + } + out.write(buff, 0, len); + length -= len; + } + return out.toByteArray(); + } finally { + in.close(); + } + } +} diff --git a/src/main/java/com/iciql/util/WeakIdentityHashMap.java b/src/main/java/com/iciql/util/WeakIdentityHashMap.java new file mode 100644 index 0000000..bc03cd0 --- /dev/null +++ b/src/main/java/com/iciql/util/WeakIdentityHashMap.java @@ -0,0 +1,243 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.util; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import com.iciql.IciqlException; + +/** + * This hash map uses weak references, so that elements that are no longer + * referenced elsewhere can be garbage collected. It also uses object identity + * to compare keys. The garbage collection happens when trying to add new data, + * or when resizing. + * + * @param + * the keys + * @param + * the value + */ + +public class WeakIdentityHashMap implements Map { + + private static final int MAX_LOAD = 90; + private static final WeakReference DELETED_KEY = new WeakReference(null); + private int mask, len, size, deletedCount, level; + private int maxSize, minSize, maxDeleted; + private WeakReference[] keys; + private V[] values; + + public WeakIdentityHashMap() { + reset(2); + } + + public int size() { + return size; + } + + private void checkSizePut() { + if (deletedCount > size) { + rehash(level); + } + if (size + deletedCount >= maxSize) { + rehash(level + 1); + } + } + + private void checkSizeRemove() { + if (size < minSize && level > 0) { + rehash(level - 1); + } else if (deletedCount > maxDeleted) { + rehash(level); + } + } + + private int getIndex(Object key) { + return System.identityHashCode(key) & mask; + } + + @SuppressWarnings("unchecked") + private void reset(int newLevel) { + minSize = size * 3 / 4; + size = 0; + level = newLevel; + len = 2 << level; + mask = len - 1; + maxSize = (int) (len * MAX_LOAD / 100L); + deletedCount = 0; + maxDeleted = 20 + len / 2; + keys = new WeakReference[len]; + values = (V[]) new Object[len]; + } + + public V put(K key, V value) { + checkSizePut(); + int index = getIndex(key); + int plus = 1; + int deleted = -1; + do { + WeakReference k = keys[index]; + if (k == null) { + // found an empty record + if (deleted >= 0) { + index = deleted; + deletedCount--; + } + size++; + keys[index] = new WeakReference(key); + values[index] = value; + return null; + } else if (k == DELETED_KEY) { + if (deleted < 0) { + // found the first deleted record + deleted = index; + } + } else { + Object r = k.get(); + if (r == null) { + delete(index); + } else if (r == key) { + // update existing + V old = values[index]; + values[index] = value; + return old; + } + } + index = (index + plus++) & mask; + } while (plus <= len); + throw new IciqlException("Hashmap is full"); + } + + public V remove(Object key) { + checkSizeRemove(); + int index = getIndex(key); + int plus = 1; + do { + WeakReference k = keys[index]; + if (k == null) { + // found an empty record + return null; + } else if (k == DELETED_KEY) { + // continue + } else { + Object r = k.get(); + if (r == null) { + delete(index); + } else if (r == key) { + // found the record + V old = values[index]; + delete(index); + return old; + } + } + index = (index + plus++) & mask; + k = keys[index]; + } while (plus <= len); + // not found + return null; + } + + @SuppressWarnings("unchecked") + private void delete(int index) { + keys[index] = (WeakReference) DELETED_KEY; + values[index] = null; + deletedCount++; + size--; + } + + private void rehash(int newLevel) { + WeakReference[] oldKeys = keys; + V[] oldValues = values; + reset(newLevel); + for (int i = 0; i < oldKeys.length; i++) { + WeakReference k = oldKeys[i]; + if (k != null && k != DELETED_KEY) { + K key = k.get(); + if (key != null) { + put(key, oldValues[i]); + } + } + } + } + + public V get(Object key) { + int index = getIndex(key); + int plus = 1; + do { + WeakReference k = keys[index]; + if (k == null) { + return null; + } else if (k == DELETED_KEY) { + // continue + } else { + Object r = k.get(); + if (r == null) { + delete(index); + } else if (r == key) { + return values[index]; + } + } + index = (index + plus++) & mask; + } while (plus <= len); + return null; + } + + public void clear() { + reset(2); + } + + public boolean containsKey(Object key) { + return get(key) != null; + } + + public boolean containsValue(Object value) { + if (value == null) { + return false; + } + for (V item : values) { + if (value.equals(item)) { + return true; + } + } + return false; + } + + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + public boolean isEmpty() { + return size == 0; + } + + public Set keySet() { + throw new UnsupportedOperationException(); + } + + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + public Collection values() { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/com/iciql/util/package.html b/src/main/java/com/iciql/util/package.html new file mode 100644 index 0000000..3d24dee --- /dev/null +++ b/src/main/java/com/iciql/util/package.html @@ -0,0 +1,25 @@ + + + + +Javadoc package documentation + + +Utility classes for iciql. + + \ No newline at end of file diff --git a/src/site/00_index.mkd b/src/site/00_index.mkd new file mode 100644 index 0000000..1c2bbd3 --- /dev/null +++ b/src/site/00_index.mkd @@ -0,0 +1,63 @@ +## Overview + +iciql **is**... + +- a model-based, database access wrapper for JDBC +- for modest database schemas and basic statement generation +- for those who want to write code, instead of SQL, using IDE completion and compile-time type-safety +- small (200KB) with debug symbols and no runtime dependencies +- pronounced *icicle* (although it could be French: *ici ql* - here query language) +- a friendly fork of the H2 [JaQu][jaqu] project + +iciql **is not**... + +- a complete alternative to JDBC +- designed to compete with more powerful database query tools like [jOOQ][jooq] or [Querydsl][querydsl] +- designed to compete with enterprise [ORM][orm] tools like [Hibernate][hibernate] or [mybatis][mybatis] + +### Example Usage + + + + + + + +
iciqlsql
+%BEGINCODE% +Product p = new Product(); +List restock = db.from(p).where(p.unitsInStock).is(0).select(); +List all = db.executeQuery(Product.class, "select * from products"); +%ENDCODE% + + +
+select * from products p where p.unitsInStock = 0
+select * from products +
+ +### Supported Databases (Unit-Tested) +- [H2](http://h2database.com) ${h2.version} +- [HSQLDB](http://hsqldb.org) ${hsqldb.version} +- [Derby](http://db.apache.org/derby) ${derby.version} +- [MySQL](http://mysql.com) ${mysql.version} +- [PostgreSQL](http://postgresql.org) ${postgresql.version} + +Support for others is possible and may only require creating a simple "dialect" class. + +### Java Runtime Requirement + +iciql requires a Java 6 Runtime Environment (JRE) or a Java 6 Development Kit (JDK). + +### License +iciql is distributed under the terms of the [Apache Software Foundation license, version 2.0][apachelicense] + +[jaqu]: http://h2database.com/html/jaqu.html "H2 JaQu project" +[orm]: http://en.wikipedia.org/wiki/Object-relational_mapping "Object Relational Mapping" +[jooq]: http://jooq.sourceforge.net "jOOQ" +[querydsl]: http://source.mysema.com/display/querydsl/Querydsl "Querydsl" +[hibernate]: http://www.hibernate.org "Hibernate" +[mybatis]: http://www.mybatis.org "mybatis" +[github]: http://github.com/gitblit/iciql "iciql git repository" +[googlecode]: http://code.google.com/p/iciql "iciql project management" +[apachelicense]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License, Version 2.0" \ No newline at end of file diff --git a/src/site/01_model_classes.mkd b/src/site/01_model_classes.mkd new file mode 100644 index 0000000..8fedf18 --- /dev/null +++ b/src/site/01_model_classes.mkd @@ -0,0 +1,335 @@ +## Model Classes +A model class represents a single table within your database. Fields within your model class represent columns in the table. The object types of your fields are reflectively mapped to SQL types by iciql at runtime. + +Models can be manually written using one of three approaches: *annotation configuration*, *interface configuration*, or *POJO configuration*. All approaches can be used within a project and all can be used within a single model class, although that is discouraged. + +Alternatively, model classes can be automatically generated by iciql using the model generation tool. Please see the [tools](tools.html) page for details. + +### Configuration Requirements and Limitations + +1. Your model class **must** provide a public default constructor. +2. All **Object** fields are assumed NULLABLE unless explicitly set *@IQColumn(nullable = false)* or *Define.nullable(field, false)*. +3. All **Primitive** fields are assumed NOT NULLABLE unless explicitly set *@IQColumn(nullable = true)* or *Define.nullable(field, true)*. +4. Only the specified types are supported. Any other types are not supported. +5. Triggers, views, and other advanced database features are not supported. + +### Supported Data Types + +---NOMARKDOWN--- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fully Supported Types
+can be used for all iciql expressions +
ObjectPrimitiveSQL Type
java.lang.StringVARCHAR (length > 0) or CLOB (length == 0)
java.lang.BooleanbooleanBOOLEAN
can only declare and explicitly reference one primitive boolean per model
multiple primitives are allowed if not using where/set/on/and/or/groupBy/orderBy(boolean)
java.lang.BytebyteTINYINT
java.lang.ShortshortSMALLINT
java.lang.IntegerintINT
java.lang.LonglongBIGINT
java.lang.FloatfloatREAL
java.lang.DoubledoubleDOUBLE
java.math.BigDecimal DECIMAL (length == 0) or DECIMAL(length,scale) (length > 0)
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP
java.util.Date TIMESTAMP
java.lang.Enum.name()
default type
VARCHAR (length > 0) or CLOB (length == 0)
EnumType.NAME
can only declare and explicitly reference one instance of each enum type per model
multiple instances of an enum type within a model is allowed if not using where/set/on/and/or/groupBy/orderBy(enum)
java.lang.Enum.ordinal() INT
EnumType.ORDINAL
can only declare and explicitly reference one instance of each enum type per model
multiple instances of an enum type within a model is allowed if not using where/set/on/and/or/groupBy/orderBy(enum)
java.lang.Enum implements
com.iciql.Iciql.EnumId.enumId()
INT
EnumType.ENUMID
can only declare and explicitly reference one instance of each enum type per model
multiple instances of an enum type within a model is allowed if not using where/set/on/and/or/groupBy/orderBy(enum)
Partially Supported Types
+can not be directly referenced in an expression
byte [] BLOB
H2 Database Types
+fully supported when paired with an H2 database +
java.util.UUID UUID
+---NOMARKDOWN--- +**NOTE:**
+The reverse lookup used for model generation, SQL type -> Java type, contains more mappings.
+Please consult the `com.iciql.ModelUtils` class for details. + +## Annotation Configuration +The recommended approach to setup a model class is to annotate the class and field declarations. + +### advantages + +- annotated models support annotated field inheritance making it possible to design a single base class that defines the fields and then create table subclasses that specify the table mappings. +- model runtime dependency is limited to the small, portable `com.iciql.Iciql` class file which contains the annotation definitions + +### disadvantages + +- more verbose model classes +- indexes are defined using "fragile" string column names +- compound primary keys are defined using "fragile" string column names + +### field mapping + +- By default, **ONLY** fields annotated with *@IQColumn* are mapped. +- scope is irrelevant. +- transient is irrelevant. + +### default values + +You may specify default values for an *@IQColumn* by either: + +1. specifying the default value string within your annotation
+**NOTE:**
+The annotated default value always takes priority over a field default value. +%BEGINCODE% +// notice the single ticks! +@IQColumn(defaultValue="'2000-01-01 00:00:00'") +Date myDate; +%ENDCODE% + +2. setting a default value on the field
+**NOTE:**
+Primitive types have an implicit default value of *0* or *false*. +%BEGINCODE% +@IQColumn +Date myDate = new Date(100, 0, 1); + +@IQColumn +int myId; +%ENDCODE% + +If you want to specify a database-specific variable or function as your default value (e.g. CURRENT_TIMESTAMP) you must do that within the annotation. Also note that the *IQColumn.defaultValue* must be a well-formatted SQL DEFAULT expression whereas object defaults will be automatically converted to an SQL DEFAULT expression. + +### Special Case: primitive autoincrement fields and 0 +%BEGINCODE% +@IQColumn(autoIncrement = true) +int myId; +%ENDCODE% + +Because primitive types have implicit default values, this field will be excluded from an INSERT statement if its value is 0. Iciql can not differentiate an implicit/uninitialized 0 from a explicitly assigned 0. + +### Example Annotated Model +%BEGINCODE% +import com.iciql.Iciql.EnumType; +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQEnum; +import com.iciql.Iciql.IQIndex; +import com.iciql.Iciql.IQTable; + +@IQTable +@IQIndexes({ + @IQIndex({"productName", "category"}), + @IQIndex(name="nameindex", value="productName") +}) +public class Product { + + @IQEnum(EnumType.ORDINAL) + public enum Availability { + ACTIVE, DISCONTINUED; + } + + @IQColumn(primaryKey = true) + public Integer productId; + + @IQColumn(length = 200, trim = true) + public String productName; + + @IQColumn(length = 50, trim = true) + public String category; + + @IQColumn + public Double unitPrice; + + @IQColumn(name = "units") + public Integer unitsInStock; + + @IQColumn + private Integer reorderQuantity; + + @IQColumn + private Availability availability; + + // ignored because it is not annotated AND the class is @IQTable annotated + private Integer ignoredField; + + public Product() { + // default constructor + } +} +%ENDCODE% + +## Interface Configuration +Alternatively, you may map your model classes using the interface approach by implementing the `com.iciql.Iciql` interface. + +This is a less verbose configuration style, but it comes at the expense of introducing a compile-time dependency on the logic of the iciql library. This might be a deterrent, for example, if you were serializing your model classes to another process that may not have the iciql library. + +The `com.iciql.Iciql` interface specifies a single method, *defineIQ()*. In your implementation of *defineIQ()* you would use static method calls to set: + +- the schema name +- the table name (if it's not the class name) +- the column name (if it's not the field name) +- the max length and trim of a string field +- the precision and scale of a decimal field +- the autoincrement flag of a long or integer field +- the nullable flag of a field +- the primaryKey (single field or compound) +- any indexes (single field or compound) + +### advantages + +- less verbose model class +- compile-time index definitions +- compile-time compound primary key definitions + +### disadvantages + +- model runtime dependency on entire iciql library +- *defineIQ()* is called from a static synchronized block which may be a bottleneck for highly concurrent systems + +### field mapping + +- **ALL** fields are mapped unless annotated with *@IQIgnore*. +- scope is irrelevant. +- transient is irrelevant. + +### default values + +You may specify default values for an field by either: + +1. specifying the default value string within your *defineIQ()* method
+**NOTE:**
+The defineIQ() value always takes priority over a field default value. +%BEGINCODE% +Date myDate; + +public void defineIQ() { + // notice the single ticks! + Define.defaultValue(myDate, "'2000-01-01 00:00:00'"); +} +%ENDCODE% + +2. setting a default value on the field
+**NOTE:**
+Primitive types have an implicit default value of *0* or *false*. +%BEGINCODE% +Date myDate = new Date(100, 0, 1); + +int myId; +%ENDCODE% + +### Example Interface Model +%BEGINCODE% +import com.iciql.Iciql; +import com.iciql.Iciql.IQIgnore; + +public class Product implements Iciql { + public Integer productId; + public String productName; + public String category; + public Double unitPrice; + public Integer unitsInStock; + + @IQIgnore + Integer reorderQuantity; + + public Product() { + } + + @Override + public void defineIQ() { + com.iciql.Define.primaryKey(productId); + com.iciql.Define.columnName(unitsInStock, "units"); + com.iciql.Define.length(productName, 200); + com.iciql.Define.length(category, 50); + com.iciql.Define.index(productName, category); + } +} +%ENDCODE% + + +## POJO (Plain Old Java Object) Configuration + +This approach is very similar to the *interface configuration* approach; it is the least verbose and also the least useful. + +This approach would be suitable for quickly modeling an existing table where only SELECT and INSERT statements will be generated. + +### advantages + +- nearly zero-configuration + +### disadvantages + +- can not execute DELETE, UPDATE, or MERGE statements (they require a primary key specification) +- table name MUST MATCH model class name +- column names MUST MATCH model field names +- can not specify any column attributes +- can not specify indexes + +### field mapping + +- **ALL** fields are mapped unless annotated with *@IQIgnore*. +- scope is irrelevant. +- transient is irrelevant. + +### default values + +You may specify a default value on the field. + +**NOTE:**
+Primitive types have an implicit default value of *0* or *false*. +%BEGINCODE% +Date myDate = new Date(100, 0, 1); + +int myId; +%ENDCODE% + +### Example POJO Model +%BEGINCODE% +import com.iciql.Iciql.IQIgnore; + +public class Product { + public Integer productId; + public String productName; + public String category; + public Double unitPrice; + public Integer units; + + @IQIgnore + Integer reorderQuantity; + + public Product() { + } +} +%ENDCODE% \ No newline at end of file diff --git a/src/site/02_table_versioning.mkd b/src/site/02_table_versioning.mkd new file mode 100644 index 0000000..2e95aaa --- /dev/null +++ b/src/site/02_table_versioning.mkd @@ -0,0 +1,29 @@ +## Database & Table Versioning + +Iciql supports an optional, simple versioning mechanism. There are two parts to the mechanism. + +1. You must supply an implementation of `com.iciql.DbUpgrader` to your `com.iciql.Db` instance. +2. One or more of your table model classes must specify the `IQVersion(version)` annotation
+AND/OR
+Your `com.iciql.DbUpgrader` implementation must specify the `IQVersion(version)` annotation + +### How does it work? +If you choose to use versioning, iciql will maintain a table within your database named *iq_versions* which is defined as: + + CREATE TABLE IQ_VERSIONS(SCHEMANAME VARCHAR(255) NOT NULL, TABLENAME VARCHAR(255) NOT NULL, VERSION INT NOT NULL) + +This database table is automatically created if and only if at least one of your model classes specifies a *version* > 0. + +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. + +When an upgrade scenario is identified, the current version and the annotated version information is passed to either: + +- `DbUpgrader.upgradeDatabase(db, fromVersion, toVersion)` +- `DbUpgrader.upgradeTable(db, schema, table, fromVersion, toVersion)` + +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. + +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. + +**NOTE:**
+The database entry of the *iq_versions* table is specified as SCHEMANAME='' and TABLENAME=''. \ No newline at end of file diff --git a/src/site/02_usage.mkd b/src/site/02_usage.mkd new file mode 100644 index 0000000..7b9d89d --- /dev/null +++ b/src/site/02_usage.mkd @@ -0,0 +1,197 @@ +## Usage + +Aside from this brief usage guide, please consult the [examples](examples.html), the [javadoc](javadoc.html) and the [source code](${project.scmUrl}). + +### Instantiating a Db + +Use one of the static utility methods to instantiate a Db instance: + + Db.open(String url, String user, String password); + Db.open(String url, String user, char[] password); + Db.open(Connection conn); + Db.open(DataSource dataSource); + +### Compile-Time Statements + +You compose your statements using the builder pattern where each method call returns an object that is used to specify the next part of the statement. Through clever usage of generics, pioneered by the original JaQu project, compile-time safety flows through the statement. + +%BEGINCODE% +Db db = Db.open("jdbc:h2:mem:", "sa", "sa"); +db.insertAll(Product.getList()); +db.insertAll(Customer.getList()); + +List restock = + db.from(p). + where(p.unitsInStock). + is(0).orderBy(p.productId).select(); + +for (Product product : restock) { + db.from(p). + set(p.unitsInStock).to(25). + where(p.productId).is(product.productId).update(); +} +db.close(); +%ENDCODE% + +Please see the [examples](examples.html) page for more code samples. + +### Dynamic Runtime Queries + +Iciql gives you compile-time type-safety, but it becomes inconvenient if your design requires more dynamic statement generation. For these scenarios iciql offers some runtime query support through a hybrid approach or a pure JDBC approach. + +#### Where String Fragment Approach +This approach is a mixture of iciql and jdbc. It uses the traditional prepared statement *field=?* tokens with iciql compile-time model class type checking. There is no field token type-safety checking. +%BEGINCODE% +List restock = db.from(p).where("unitsInStock=? and productName like ? order by productId", 0, "Chef%").select(); +%ENDCODE% + +#### Db.executeQuery Approaches +There may be times when the hybrid approach is still too restrictive and you'd prefer to write straight SQL. You can do that too and use iciql to build objects from your ResultSet, but be careful: + +1. Make sure to _select *_ in your query otherwise db.buildObjects() will throw a RuntimeException +2. There is no model class type checking nor field type checking. + +%BEGINCODE% +List allProducts = db.executeQuery(Product.class, "select * from products"); +List restock = db.executeQuery(Product.class, "select * from products where unitsInStock=?", 0); + +// parameterized query which can be cached and re-used later +String q = db.from(p).where(p.unitsInStock).isParameter().toSQL(); +List restock = db.executeQuery(Product.class, q, 0); + +%ENDCODE% + +Or if you want access to the raw *ResultSet* before building your model object instances... + +%BEGINCODE% +ResultSet rs = db.executeQuery("select * from products"); +List allProducts = db.buildObjects(Product.class, rs); +// This method ensures the creating statement is closed +JdbcUtils.closeSilently(rs, true); +%ENDCODE% + +### Natural Syntax + +work-in-progress + +The original JaQu source offers partial support for Java expressions in *where* clauses. + +This works by decompiling a Java expression, at runtime, to an SQL condition. The expression is written as an anonymous inner class implementation of the `com.iciql.Filter` interface. +A proof-of-concept decompiler is included, but is incomplete. + +The proposed syntax is: +%BEGINCODE% +long count = db.from(co). + where(new Filter() { public boolean where() { + return co.id == x + && co.name.equals(name) + && co.value == new BigDecimal("1") + && co.amount == 1L + && co.birthday.before(new java.util.Date()) + && co.created.before(java.sql.Timestamp.valueOf("2005-05-05 05:05:05")) + && co.time.before(java.sql.Time.valueOf("23:23:23")); + } + }).selectCount(); +%ENDCODE% + +### JDBC Statements, ResultSets, and Exception Handling + +Iciql opens and closes all JDBC objects automatically. SQLExceptions thrown during execution of a statement (except for *close()* calls), will be caught, wrapped, and rethrown as an `IciqlException`, which is a RuntimeException. + +Iciql does not throw any [checked exceptions](http://en.wikipedia.org/wiki/Exception_handling#Checked_exceptions). + +### Statement Logging + +Iciql provides a mechanism to log generated statements and warnings to the console, to SLF4J, or to your own logging framework. Exceptions are not logged using this mechanism; exceptions are wrapped and rethrown as `IciqlException`, which is a RuntimeException. + +#### Console Logging +%BEGINCODE% +IciqlLogger.activeConsoleLogger(); +IciqlLogger.deactiveConsoleLogger(); +%ENDCODE% + +#### SLF4J Logging +%BEGINCODE% +Slf4jIciqlListener slf4j = new Slf4jIciqlListener(); +slf4j.setLevel(StatementType.CREATE, Level.WARN); +slf4j.setLevel(StatementType.DELETE, Level.WARN); +slf4j.setLevel(StatementType.MERGE, Level.OFF); +IciqlLogger.registerListener(slf4j); +IciqlLogger.unregisterListener(slf4j); +%ENDCODE% + +#### Custom Logging +%BEGINCODE% +IciqlListener custom = new IciqlListener() { + public void logIciql(StatementType type, String statement) { + // do log + } +}; +IciqlLogger.registerListener(custom); +IciqlLogger.unregisterListener(custom); +%ENDCODE% + +## Understanding Aliases and Model Classes +Consider the following example: +%BEGINCODE% +Product p = new Product(); +List restock = db.from(p).where(p.unitsInStock).is(0).select(); +%ENDCODE% + +The Product model class instance named **p** is an *alias* object. An *alias* is simply an instance of your model class that is only used to build the compile-time/runtime representation of your table. + +1. *Alias* instances are **NOT** thread-safe and must not be used concurrently. +2. *Alias* instances have no other purpose than to provide a compile-time/runtime map of your table. +3. If you inspected an *alias* instance after using one you would find that it's fields have been assigned numeric values.
These values are assigned from a static counter in `com.iciql.Utils.newObject()` during execution of the *db.from()* method.

For *Object* fields, these values are meaningless since objects are mapped by reference.
For *Primitive* fields these values do matter because primitives are mapped by value. The proper alias is selected as long as the primitive variant methods are used. e.g. db.from(p).where(int).is(Integer).select() + +If your statement is a query, like in the above example, iciql will generate new instances of your *alias* model class and return them as a list where each entry of the list represents a row from the JDBC `ResultSet`. + +### Why are Aliases not thread-safe? + +The _db.from(p)_ call reinstantiates each member field of p. Those reinstantiated fields are then subsequently used in clauses like _where(p.unitsInStock)_. If your *alias* instance is shared concurrently then its highly probable that when _queryA_ executes, _queryC_ has reinstantiated all the *alias* fields and broken _queryA's_ runtime field mapping. + +Depending on your design, you might consider using a [ThreadLocal](http://download.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html) variable if you do not want to keep instantiating *alias* instances. A utility function is included for easily creating ThreadLocal variables. + +%BEGINCODE% +final ThreadLocal p = Utils.newThreadLocal(Product.class); +db.from(p.get()).select(); +%ENDCODE% + +## Best Practices + +1. Close your *Db* instances when you are done with them, this closes the underlying connection or directs the pool to "close" the connection. +2. Aliases instances are not thread-safe so DO NOT SHARE an alias!
Consider using a *ThreadLocal* alias instance with the `com.iciql.Utils.newThreadLocal()` utility method. + +

+ + + +
Not Thread-SafeThread-Safe
+%BEGINCODE% +final Product p = new Product(); +for (int i = 0; i < 5; i++) { + Thread thread = new Thread(new Runnable() { + public void run() { + // from(p) reinstantiates p's fields + db.from(p).select(); + } + }, "Thread-" + i); + thread.start(); +} +%ENDCODE% + + +%BEGINCODE% +final ThreadLocal p = Utils.newThreadLocal(Product.class); +for (int i = 0; i < 5; i++) { + Thread thread = new Thread(new Runnable() { + public void run() { + // a unique p for this thread + db.from(p.get()).select(); + } + }, "Thread-" + i); + thread.start(); +} +%ENDCODE% + +
\ No newline at end of file diff --git a/src/site/03_performance.mkd b/src/site/03_performance.mkd new file mode 100644 index 0000000..34e545c --- /dev/null +++ b/src/site/03_performance.mkd @@ -0,0 +1,22 @@ + +## Performance + +The information provided here may be based on flawed test procedures. You have to be the judge of what is performant and non-performant. + +### iciql statement generation + +Performance of iciql statement generation is not currently benchmarked. + +### iciql+database performance comparison + +The following data was generated by running the *single-threaded* iciql test suite. All database connections are pooled and re-used within each execution of the test suite using [Apache Commons DBCP](http://commons.apache.org/dbcp). + +Connections are pooled to normalize embedded database performance with out-of-process database performance. Some of the Java embedded database configurations have a very high startup-time penalty. Notably, H2 is slow to open a database and its performance is substantially affected if connection pooling is not enabled to keep the embedded database open. + +All tables are created as CACHED when the database distinguishes between CACHED and MEMORY tables. + +All performance numbers include the combined overhead of iciql statement generation and JUnit 4 test framework execution so they are not bare-metal database metrics. + +

+%DBPERFORMANCE%
+
\ No newline at end of file diff --git a/src/site/04_examples.mkd b/src/site/04_examples.mkd new file mode 100644 index 0000000..33cb9c4 --- /dev/null +++ b/src/site/04_examples.mkd @@ -0,0 +1,196 @@ +## Select Statements + +%BEGINCODE% +// select * from products +List allProducts = db.from(p).select(); + +// select * from customers where region='WA' +Customer c = new Customer(); +List waCustomers = db.from(c). where(c.region).is("WA").select(); + +public static class ProductPrice { + public String productName; + public String category; + public Double price; +} + +// select with generation of new anonymous inner class +List productPrices = + db.from(p). + orderBy(p.productId). + select(new ProductPrice() {{ + productName = p.productName; + category = p.category; + price = p.unitPrice; + }}); +%ENDCODE% + +## Insert Statements + +%BEGINCODE% +// single record insertion +db.insert(singleProduct); + +// single record insertion with primary key retrieval +Long key = db.insertAndGetKey(singleProduct); + +// batch record insertion +db.insertAll(myProducts); + +// batch insertion with primary key retrieval +List myKeys = db.insertAllAndGetKeys(list); +%ENDCODE% + +## Update Statements + +%BEGINCODE% +// single record update +db.update(singleProduct); + +// batch record updates +db.updateAll(myProducts); + +// update query +db.from(p).set(p.productName).to("updated") + .increment(p.unitPrice).by(3.14) + .increment(p.unitsInStock).by(2) + .where(p.productId).is(1).update(); + +// reusable, parameterized update query +String q = db.from(p).set(p.productName).toParameter().where(p.productId).is(1).toSQL(); +db.executeUpdate(q, "Lettuce"); + +%ENDCODE% + +## Merge Statements +Merge statements currently generate the [H2 merge syntax](http://h2database.com/html/grammar.html#merge). + +%BEGINCODE% +Product pChang = db.from(p).where(p.productName).is("Chang").selectFirst(); +pChang.unitPrice = 19.5; +pChang.unitsInStock = 16; +db.merge(pChang); +%ENDCODE% + +## Delete Statements + +%BEGINCODE% +// single record deletion +db.delete(singleProduct); + +// batch record deletion +db.deleteAll(myProducts); + +// delete query +db.from(p).where(p.productId).atLeast(10).delete(); + +%ENDCODE% + +## Inner Join Statements + +%BEGINCODE% +final Customer c = new Customer(); +final Order o = new Order(); + +List customersWithLargeOrders = + db.from(c). + innerJoin(o).on(c.customerId).is(o.customerId). + where(o.total).greaterThan(new BigDecimal("500.00")). + groupBy(c.customerId).select(); + + +List orders = + db.from(c). + innerJoin(o).on(c.customerId).is(o.customerId). + where(o.total).lessThan(new BigDecimal("500.00")). + orderBy(1). + select(new CustOrder() {{ + customerId = c.customerId; + orderId = o.orderId; + total = o.total; + }}); +%ENDCODE% + +## View Statements + +%BEGINCODE% +// the view named "ProductView" is created from the "Products" table +@IQView(viewTableName = "Products") +public class ProductView { + + @IQColumn + @IQConstraint("this >= 200 AND this < 300") + Long id; + + @IQColumn + String name; +} + +final ProductView v = new ProductView(); +List allProducts = db.from(v).select(); + +// this version of the view model "ProductView" inherits table metadata +// from the Products class which is annotated with IQTable +@IQView(inheritColumns = true) +public class ProductView extends Products { + + // inherited BUT replaced to define the constraint + @IQColumn + @IQConstraint("this >= 200 AND this < 300") + Long id; + + // inherited from Products + //@IQColumn + //String name; +} + +final ProductView v = new ProductView(); +List allProducts = db.from(v).select(); + +// in this example we are creating a view based on a fluent query +// and using 2 levels of inheritance. IQConstraints are ignored +// when using this approach because we are fluently defining them. +@IQView(inheritColumns = true) +public class ProductViewInherited extends ProductView { + +} + +final Products p = new Products(); +db.from(p).where(p.id).atLeast(200L).and(p.id).lessThan(300L).createView(ProductViewInherited.class); + +// now replace the view with a variation +db.from(p).where(p.id).atLeast(250L).and(p.id).lessThan(350L).replaceView(ProductViewInherited.class); + +// now drop the view from the database +db.dropView(ProductViewInherited.class); + +%ENDCODE% + +## Dynamic Queries + +Dynamic queries skip all field type checking and, depending on which approach you use, may skip model class/table name checking too. + +%BEGINCODE% +// where fragment with object parameters +List restock = db.from(p).where("unitsInStock=? and productName like ? order by productId", 0, "Chef%").select(); + +// parameterized query which can be cached and re-used later +String q = db.from(p).where(p.unitsInStock).isParameter().and(p.productName).likeParameter().orderBy(p.productId).toSQL(); +List allProducts = db.executeQuery(Product.class, q, 0, "Chef%"); + +// statement with binding to your model class +List allProducts = db.executeQuery(Product.class, "select * from products"); + +// statement with object parameters and binding to your model class +List restock = db.executeQuery(Product.class, "select * from products where unitsInStock=?", 0); + +/** + * If you want to process the intermediate ResultSet + * yourself make sure to use the closeSilently() method + * to ensure the parent statement is closed too. + */ +ResultSet rs = db.executeQuery("select * from products"); +List allProducts = db.buildObjects(Product.class, rs); +JdbcUtils.closeSilently(rs, true); + +%ENDCODE% \ No newline at end of file diff --git a/src/site/04_tools.mkd b/src/site/04_tools.mkd new file mode 100644 index 0000000..6d8c348 --- /dev/null +++ b/src/site/04_tools.mkd @@ -0,0 +1,95 @@ +## Model Generation +If you do not have or do not want to annotate your existing model classes, you can use the included model generation tool to create iciql model classes. + + java -jar iciql.jar + +### Parameters + + + + + + + + + + +
-urlJDBC url for the databaseREQUIRED
-usernameusername for JDBC connectionoptional
-passwordpassword for JDBC connectionoptional
-schemathe target schema for model generationdefault: all schemas
-tablethe target table for model generationdefault: all tables
-packagethe destination package name for generated modelsdefault: default package
-folderthe output folder for .java filesdefault: current folder
-annotateSchemainclude the schema name in the class annotationsdefault: true
-trimStringsannotate trimStrings=true for any VARCHAR string mappings   default: false
+ +## Model Validation +Iciql can validate your model classes against your database to ensure that your models are optimally defined and are consistent with the current table and index definitions. + +Each `com.iciql.ValidationRemark` returned by the validation has an associated level from the following enum: +%BEGINCODE% +public static enum Level { + CONSIDER, WARN, ERROR; +} +%ENDCODE% + +A typical validation may output recommendations for adjusting a model field annotation such as setting the *maxLength* of a string to match the length of its linked VARCHAR column. + +### Sample Model Validation using JUnit 4 +%BEGINCODE% +import static org.junit.Assert.assertTrue; + +import java.sql.SQLException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; + +import com.iciql.Db; +import com.iciql.DbInspector; +import com.iciql.ValidationRemark; +import com.iciql.test.models.Product; +import com.iciql.test.models.ProductAnnotationOnly; +import com.iciql.test.models.ProductMixedAnnotation; + +public class ValidateModels { + + /* + * The ErrorCollector Rule allows execution of a test to continue after the + * first problem is found and report them all at once + */ + @Rule + public ErrorCollector errorCollector = new ErrorCollector(); + + private Db db; + + @Before + public void setUp() { + db = Db.open("jdbc:h2:mem:", "sa", "sa"); + } + + @After + public void tearDown() { + db.close(); + } + + @Test + public void testValidateModels() { + DbInspector inspector = new DbInspector(db); + validateModel(inspector, new Product()); + validateModel(inspector, new ProductAnnotationOnly()); + validateModel(inspector, new ProductMixedAnnotation()); + } + + private void validateModel(DbInspector inspector, Object o) { + List remarks = inspector.validateModel(o, false); + assertTrue("Validation remarks are null for " + o.getClass().getName(), remarks != null); + log("Validation remarks for " + o.getClass().getName()); + for (ValidationRemark remark : remarks) { + log(remark.toString()); + if (remark.isError()) { + errorCollector.addError(new SQLException(remark.toString())); + } + } + } + + private void log(String message) { + System.out.println(message); + } +} +%ENDCODE% \ No newline at end of file diff --git a/src/site/05_building.mkd b/src/site/05_building.mkd new file mode 100644 index 0000000..b5548bc --- /dev/null +++ b/src/site/05_building.mkd @@ -0,0 +1,35 @@ +## Building from Source + +[Eclipse](http://eclipse.org) is recommended for development as the project settings are preconfigured. + +Additionally, [eclipse-cs](http://eclipse-cs.sourceforge.net), [FindBugs](http://findbugs.sourceforge.net), and [EclEmma](http://www.eclemma.org) are recommended development tools. + +### Build Dependencies (bundled in repository) +- [ant-googlecode](http://code.google.com/p/ant-googlecode) (New BSD) + +### Build Dependencies (downloaded during build) +- [Moxie Build Toolkit](http://moxie.gitblit.com) (Apache 2.0) +- [H2 Database](http://h2database.com) (Eclipse Public License 1.0) +- [HSQL Database Engine](http://hsqldb.org) (BSD) +- [Apache Derby Database](http://db.apache.org/derby) (Apache 2.0) +- [MySQL Connector/J](http://dev.mysql.com/downloads/connector/j) (GPL) +- [PostgreSQL JDBC Connector](http://jdbc.postgresql.org) (BSD) +- [JUnit](http://junit.org) (Common Public License) +- [SLF4J](http://www.slf4j.org) (MIT/X11) +- [Apache Commons Pool](http://commons.apache.org/pool) (Apache 2.0) +- [Apache Commons DBCP](http://commons.apache.org/dbcp) (Apache 2.0) + +### Instructions +1. Clone the git repository from [Github](${project.scmUrl}). +2. Import the iciql project into your Eclipse workspace.
+*There will be some build errors.* +3. Using Ant, execute the `build.xml` script in the project root.
+*This will download all necessary build dependencies.* +4. Select your iciql project root and **Refresh** the project, this should correct all build problems. + +## Contributing +Patches welcome in any form. + +Contributions must be your own original work and must licensed under the [Apache License, Version 2.0][apachelicense], the same license used by iciql. + +[apachelicense]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License, Version 2.0" \ No newline at end of file diff --git a/src/site/05_javadoc.mkd b/src/site/05_javadoc.mkd new file mode 100644 index 0000000..0d0f161 --- /dev/null +++ b/src/site/05_javadoc.mkd @@ -0,0 +1,7 @@ +
+packages + | tree + | deprecated + | index +
+ diff --git a/src/site/05_releases.mkd b/src/site/05_releases.mkd new file mode 100644 index 0000000..3e66494 --- /dev/null +++ b/src/site/05_releases.mkd @@ -0,0 +1,223 @@ +## Release History + +### Current Release + +**${project.version}** + +- Fixed case-sensitivity bug on setting a compound primary key from an annotation (issue 12) +- Implemented readonly view support. (issue 8)
+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. +- Support inheriting columns from super.super class, if super.super is annotated.
This allows for an inheritance hierarchy like:
+@IQTable class MyTable -> @IQView abstract class MyBaseView -> @IQView class MyConstrainedView +- Fixed order of DEFAULT value in create table statement (issue 11) +- Support inheritance of IQVersion for DbUpgrader implementations (issue 10) +- Fixed password bug in model generator (issue 7) + +### Older Releases + +**1.1.0**   *released 2012-08-20* + +- All bulk operations (insert all, update all, delete all) now use JDBC savepoints to ensure atomicity of the transaction + +**1.0.0**   *released 2012-07-14* + +- Issue CREATE TABLE and CREATE INDEX statements once per-db instance/table-mapping +- Fixed bug in using 0L primitive values in where clauses. These were confused with the COUNT(*) function. (Github/kc5nra,issue 5) +- Added support for single column subquery *select name, address from user_table where user_id in (select user_id from invoice table where paid = false)* +- Added support for left outer join (Github/backpaper0) + +**0.7.10**   *released 2012-01-27* + +- Fixed default String value bug where a default empty string threw an IndexOutOfBounds exception + +**0.7.9**   *released 2012-01-24* + +- Added toParameter() option for SET commands and allow generating parameterized UPDATE statements
+String q = db.from(t).set(t.timestamp).toParameter().where(t.id).is(5).toSQL();
+db.executeUpdate(q, new Date()); + +**0.7.8**   *released 2012-01-11* + +- Replaced non-threadsafe counter used for assigning AS identifiers in JOIN statements with an AtomicInteger +- Prevent negative rollover of the AS counter +- Added optional alias parameter to *Query.toSQL* and *QueryWhere.toSQL* to force generated statement to prefix an AS identifier or, alternatively, the tablename. + - Query.toSQL(boolean distinct, K alias) + - QueryWhere.toSQL(boolean distinct, K alias) +- Fixed bug in Query.select(Z z) which assumed that Z must always be an anonymous inner class which may not always be true. This allows for specifying an existing alias to force table or identifier usage in the generated select list. This is very useful for DISTINCT JOIN statements where only the columns of the primary table are of interest. + +**0.7.7**   *released 2012-01-05* + +- added *Query.toSQL()* and *QueryWhere.toSQL()* methods which, when combined with the following new methods, allows for generation of a parameterized, static sql string to be reused with a dynamic query or a PreparedStatement. + - QueryCondition.isParameter() + - QueryCondition.atLeastParameter() + - QueryCondition.atMostParameter() + - QueryCondition.exceedsParameter() + - QueryCondition.lessThanParameter() + - QueryCondition.likeParameter() + - QueryCondition.isNotParameter() +- Disallow **declaring and explicitly referencing** multiple instances of an enum type within a single model.
A runtime exception will be thrown if an attempt to use where/set/on/and/or/groupBy/orderBy(enum) and your model has multiple fields of a single enum type. + +**0.7.6**   *released 2011-12-21* + +- Iciql now tries to instantiate a default value from an annotated default value IFF the field object is null, it is specified *nullable = false*, and a defaultValue exists. This only applies to *db.insert* or *db.update*. + +**0.7.5**   *released 2011-12-12* + +- Iciql now identifies wildcard queries and builds a dynamic column lookup. Otherwise, the original field-position-based approach is used. This corrects the performance regression released in 0.7.4 while still fixing the wildcard statement column mapping problem. + +**0.7.4**   *released 2011-12-06* + +- Disallow **declaring and explicitly referencing** multiple primitive booleans in a single model.
A runtime exception will be thrown if an attempt to use where/set/on/and/or/groupBy/orderBy(boolean) and your model has multiple mapped primitive boolean fields. +- Added list alternatives to the varargs methods because it was too easy to forget list.toArray()
+*Db.executeQuery(Class<? extends T> modelClass, String sql, List<?> args)*
+*Db.executeQuery(String sql, List<?> args)*
+*Query.where(String fragment, List<?> args)*
+- Fixed inherited JaQu bug related to model classes and wildcard queries (select *).

+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.

+Iciql now maps all fields by their column name, not by their position. + +**0.7.3**   *released 2011-12-06* + +- api change release (API v8) +- Fixed JOIN ON primitives +- Fixed GROUP BY primitives +- Fixed primitive references when selecting into a custom type with primitives +- Improved fluent/type-safety of joins + +**0.7.2**   *released 2011-11-30* + +- generated models are now serializable with a default serial version id of 1 +- Updated to H2 1.3.162 +- Updated to HSQL 2.2.6 (100% unit test pass now that [this bug](https://sourceforge.net/tracker/?func=detail&aid=3390047&group_id=23316&atid=378131) was fixed) + +**0.7.1**   *released 2011-08-31* + +- api change release (API v7) +- Undeprecated interface configuration +- Interface configuration now maps ALL fields, not just public fields +- Added @IQIgnore annotation to explicitly skip fields for interface configuration +- Created additional Define static methods to bring interface configuration to near-parity with annotation configuration +- Documented POJO configuration option (limited subset of interface configuration) +- Fix to PostgreSQL dialect when creating autoincrement columns +- Fix to default dialect when creating autoincrement columns +- Added Db.open(url) method + +**0.7.0**   *released 2011-08-17* + +- api change release (API v6) +- Finished MySQL dialect implementation. MySQL 5.0.51b passes 100% of tests. +- Added PostgreSQL dialect. PostgreSQL 9.0 passes all but the boolean-as-int tests. +- Added Db.dropTable(T) method +- Overhauled test suite and included more database configurations. +- Renamed StatementLogger to IciqlLogger +- Added IciqlLogger.warn method +- Added IciqlLogger.drop method + +**0.6.6**   *released 2011-08-15* + +- api change release (API v5) +- Disabled two concurrency unit tests since I believe they are flawed and do not yield reproducible results +- Added Derby database dialect. Derby 10.7.1.1 and 10.8.1.2 pass 100% of tests. +- Implemented HSQL MERGE syntax. HSQL 2.2.4 fails 1 test which is a known [bug in HSQL](https://sourceforge.net/tracker/?func=detail&aid=3390047&group_id=23316&atid=378131) +- Updated to H2 1.3.159 + +**0.6.5**   *released 2011-08-12* + +- fixed failure of db.delete(PrimitiveModel) and db.update(PrimitiveModel) + +**0.6.4**   *released 2011-08-12* + +- api change release (API v4) +- @IQTable.createIfRequired -> @IQTable.create +- don't INSERT primitive autoIncrement fields, let database assign value +- full support for primitives in all clauses +- DECIMAL(length, scale) support +- 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. +- java.lang.Boolean now maps to BOOLEAN instead of BIT +- expressions on unmapped fields will throw an IciqlException +- expressions on unsupported types will throw an IciqlException +- improved exception reporting by including generated statement, if available +- moved dialects back to main package +- improved automatic dialect determination on pooled connections +- moved create table and create index statement generation into dialects +- added HSQL dialect. HSQL fails 4 out of 50 unit tests. + - 2 failures are unimplemented merge + - 1 has been filed and accepted as a [bug in HSQL](https://sourceforge.net/tracker/?func=detail&aid=3390047&group_id=23316&atid=378131) + - 1 is a concurrency issue, but the test may be flawed +- added untested MySQL dialect +- renamed _ iq_versions table to *iq_versions* since leading _ character is troublesome for some databases. +- @IQColumn(allowNull=true) -> @IQColumn(nullable=true) +- All **Object** columns are assumed NULLABLE unless explicitly set *@IQColumn(nullable = false)* +- All **Primitive** columns are assumed NOT NULLABLE unless explicitly set *@IQColumn(nullable = true)* +- allow using objects to assign default values
+%BEGINCODE% +// CREATE TABLE ... myDate DATETIME DEFAULT '2000-02-01 00:00:00' +@IQColumn +Date myDate = new Date(100, 1, 1); +%ENDCODE% +- changed @IQTable.primaryKey definition to use array of column names
+%BEGINCODE% +@IQTable( primaryKey = {"name", "nickname"}) +%ENDCODE% + +**0.6.3**   *released 2011-08-08* + +- api change release (API v3) +- finished enum support (issue 4) +- added UUID type support (H2 databases only) +- added partial primitives support *(primitives may not be used for compile-time condition clauses)* +- added *between(A y).and(A z)* condition syntax +- moved dialects into separate package + +**0.6.2**   *released 2011-08-05* + +- api change release (API v2) +- fix to versioning to support H2 1.3.158+ +- added BLOB support (issue 1) +- added java.lang.Enum support (issue 2) +- allow runtime flexible mapping of BOOL columns to Integer fields +- allow runtime flexible mapping of INT columns to Boolean fields +- annotations overhaul to reduce verbosity + - @IQSchema(name="public") -> @IQSchema("public") + - @IQDatabase(version=2) -> @IQVersion(2) + - @IQTable(version=2) -> @IQVersion(2) + - @IQIndex annotation simplified to be used for one index definition and expanded to specify index name + - added @IQIndexes annotation to specify multiple IQIndex annotations
+%BEGINCODE% +@IQIndexes({ @IQIndex("name"), @IQIndex(name="myindexname" value={"name", "nickname"}) }) +%ENDCODE% + - @IQColumn(maxLength=20) -> @IQColumn(length=20) + - @IQColumn(trimString=true) -> @IQColumn(trim=true) + +**0.5.0**   *released 2011-08-03* + +- initial release (API v1) + +*API changes compared to JaQu from H2 1.3.157 sources* + +- deprecated model class interface configuration +- added *Db.open(Connection conn)* method, changed constructor to default scope +- added *Db.registerDialect* static methods to register custom dialects +- added *Query.where(String fragment, Object... args)* method to build a runtime query fragment when compile-time queries are too strict +- added *Db.executeQuery(String query, Object... args)* to execute a complete sql query with optional arguments +- added *Db.executeQuery(Class modelClass, String query, Object... args)* to execute a complete sql query, with optional arguments, and build objects from the result +- added *Db.buildObjects(Class modelClass, ResultSet rs)* method to build objects from the ResultSet of a plain sql query +- added *ThreadLocal<T> com.iciql.Utils.newThreadLocal(final Class<? extends T> clazz)* method +- added optional console statement logger and SLF4J statement logger +- refactored dialect support +- throw *IciqlException* (which is a RuntimeException) instead of RuntimeException +- synchronized *Db.classMap* for concurrent sharing of a Db instance +- Database/table versioning uses the _iq_versions table, the _ jq_versions table, if present, is ignored +- Changed the following class names: + - org.h2.jaqu.Table => com.iciql.Iciql + - org.h2.jaqu.JQSchema => com.iciql.IQSchema + - org.h2.jaqu.JQDatabase => com.iciql.IQDatabase + - org.h2.jaqu.JQIndex => com.iciql.IQIndex + - org.h2.jaqu.JQTable => com.iciql.IQTable + - org.h2.jaqu.JQColumn => com.iciql.IQColumn +- Changed the following method names: + - org.h2.jaqu.Table.define() => com.iciql.Iciql.defineIQ() + - QueryConditon.bigger => QueryCondition.exceeds + - QueryConditon.biggerEqual => QueryCondition.atLeast + - QueryConditon.smaller => QueryCondition.lessThan + - QueryConditon.smallEqual => QueryCondition.atMost diff --git a/src/site/06_jaqu_comparison.mkd b/src/site/06_jaqu_comparison.mkd new file mode 100644 index 0000000..20df5d5 --- /dev/null +++ b/src/site/06_jaqu_comparison.mkd @@ -0,0 +1,31 @@ + +## Comparison to JaQu + +This is an overview of the fundamental differences between the original JaQu project and the current featureset of iciql. + + + + + + + + + + + + + + + + + + + + + + + + + + +
IciqlJaQu
core
deploymentsmall, discrete librarydepends on H2 database jar file
databasesH2, HSQL, Derby, MySQL, and PostreSQLH2 only
loggingconsole, SLF4J, or custom loggingconsole logging
exceptionsalways includes generated statement in exception, when available--
column mappingswildcard queries index result sets by column nameall result sets built by field index
this can fail for wildcard queries
savepointsbulk operations (insert, update, delete) use savepoints with rollback in the event of failure--
syntax and api
VIEWscreate readonly views either from a class definition or from a fluent statement--
dynamic queriesmethods and where clauses for dynamic queries that build iciql objects--
DROPsyntax to drop a table or view
BETWEENsyntax for specifying a BETWEEN x AND y clause--
types
primitivesfully supported--
enumsfully supported--
DECIMAL(length,scale)can specify length/precision and scale--
BOOLEANflexible mapping of boolean as bool, varchar, or int--
BLOBpartially supported (can not be used in a WHERE clause)--
UUIDfully supported (H2 only) --
configuration
DEFAULT valuesset from annotation, default object values, or Define.defaultValue()set from annotations
Interface Configuration
Mapped Fields
all fields are mapped regardless of scope
fields are ignored by annotating with @IQIgnore
all public fields are mapped
fields are ignored by reducing their scope
Index namescan be set--
\ No newline at end of file diff --git a/src/site/custom.less b/src/site/custom.less new file mode 100644 index 0000000..31098f5 --- /dev/null +++ b/src/site/custom.less @@ -0,0 +1,40 @@ + +// GLOBAL VALUES +// -------------------------------------------------- +@standardGray: #ccc; +@cornflower: #abd4ff; +@white: #fff; + +// Dropdown +// ------------------------- +@dropdownLinkBackgroundHover: @cornflower; + +// Navbar +// ------------------------- +@navbarHeight: 55px; +@navbarBackground: @cornflower; +@navbarBackgroundHighlight: @cornflower; +@navbarText: @white; +@navbarLinkColor: @white; +@navbarLinkColorHover: @white; +@navbarLinkColorActive: @white; +@navbarLinkBackgroundHover: transparent; +@navbarLinkBackgroundActive: transparent; + +.navbar { + .brand { + @elementHeight: 48px; + padding: 7px; + } +} + +.navbar .nav > li > a { + font-size: @baseFontSize + 2; + text-shadow: 0 1px 0 #6b94df; +} + +body { padding-top: @navbarHeight + 15 } /* 60px to make the container go all the way to the bottom of the topbar */ +footer { margin-top: 25px; padding: 15px 0 16px; border-top: 1px solid #E5E5E5; } + +a:hover { text-decoration: underline !important; } +em { color: #50a000; } diff --git a/src/site/resources/.gitignore b/src/site/resources/.gitignore new file mode 100644 index 0000000..1277f62 --- /dev/null +++ b/src/site/resources/.gitignore @@ -0,0 +1,2 @@ +/site_analytics.html +/site_ads.html diff --git a/src/site/resources/iciql-favicon.png b/src/site/resources/iciql-favicon.png new file mode 100644 index 0000000..dda916b Binary files /dev/null and b/src/site/resources/iciql-favicon.png differ diff --git a/src/site/resources/iciql.png b/src/site/resources/iciql.png new file mode 100644 index 0000000..aa1da69 Binary files /dev/null and b/src/site/resources/iciql.png differ diff --git a/src/site/resources/iciql.xcf b/src/site/resources/iciql.xcf new file mode 100644 index 0000000..1f9f501 Binary files /dev/null and b/src/site/resources/iciql.xcf differ diff --git a/src/site/resources/iciql2.png b/src/site/resources/iciql2.png new file mode 100644 index 0000000..9e96ae5 Binary files /dev/null and b/src/site/resources/iciql2.png differ diff --git a/src/site/resources/iciql2.xcf b/src/site/resources/iciql2.xcf new file mode 100644 index 0000000..c07a46b Binary files /dev/null and b/src/site/resources/iciql2.xcf differ diff --git a/src/site/resources/iciql_white.png b/src/site/resources/iciql_white.png new file mode 100644 index 0000000..4a97147 Binary files /dev/null and b/src/site/resources/iciql_white.png differ diff --git a/src/site/resources/javadoc.css b/src/site/resources/javadoc.css new file mode 100644 index 0000000..8a8b972 --- /dev/null +++ b/src/site/resources/javadoc.css @@ -0,0 +1,52 @@ +/* Javadoc style sheet */ + +/* Define colors, fonts and other style attributes here to override the defaults */ + +/* Page background color */ +body { font-size:13px; background-color: #FFFFFF; color:#000000 } + +hr { + color: #ffffff; + background-color: #ffffff; + height: 1px; !important +} + +/* Headings */ +h1 { font-size: 1.5em } +h2 { font-size: 1.5em } + +table { + border: 1px solid #ccc; !important +} + +table th { + border: 0px solid #ccc; !important + font-size: 1.0em; !important +} + +table th font { + font-size: 1.3em; !important +} + +table td { + border: 0px solid; !important +} + +/* Table colors */ +.TableHeadingColor { background-color: #f0f0f0; } /* light gray */ +.TableSubHeadingColor { background-color: #f0f0f0; } /* light gray */ +.TableRowColor { } + +/* Font used in left-hand frame lists */ +.FrameTitleFont { } +.FrameHeadingFont { } +.FrameItemFont { } + +/* Navigation bar fonts and colors */ +.NavBarCell1 { background-color:#f0f0f0; } /* light gray */ +.NavBarCell1Rev { background-color:#00008B; color:#ffffff} /* Dark Blue */ +.NavBarFont1 { } +.NavBarFont1Rev { color:#ffffff;} + +.NavBarCell2 { } +.NavBarCell3 { } \ No newline at end of file diff --git a/src/test/java/com/iciql/test/AliasMapTest.java b/src/test/java/com/iciql/test/AliasMapTest.java new file mode 100644 index 0000000..092f38b --- /dev/null +++ b/src/test/java/com/iciql/test/AliasMapTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 org.junit.Test; + +import com.iciql.Db; +import com.iciql.IciqlException; +import com.iciql.test.models.PrimitivesModel; +import com.iciql.test.models.Product; +import com.iciql.util.Utils; + +/** + * Tests object and primitive alias referencing. + */ +public class AliasMapTest { + + /** + * Tests that columns (p.unitsInStock) are not compared by value with the + * value (9), but by reference (using an identity hash map). See + * http://code.google.com/p/h2database/issues/detail?id=119 + * + * @author d moebius at scoop dash gmbh dot de + */ + @Test + public void testObjectAliasMapping() throws Exception { + Db db = IciqlSuite.openNewDb(); + db.insertAll(Product.getList()); + + // baseline count is the next id value + long bc = Utils.COUNTER.get(); + // number of fields in primitives model class + // each from() call will increment Utils.COUNTER by this amount + int fc = Product.class.getFields().length; + + Product p = new Product(); + // This test confirms standard object referencing querying. + long count = db.from(p).where(p.productId).is(9).selectCount(); + assertEquals(1, count); + // Confirms that productId counter value is baseline counter value + assertEquals(bc, p.productId.intValue()); + try { + // This test compares "bc + fc" which is the counter value of + // unitsInStock assigned by Utils.newObject() after the 2nd pass + // through from(). + // + // Object fields map by REFERENCE, not value. + db.from(p).where(Long.valueOf(bc + fc).intValue()).is(9).orderBy(p.productId).select(); + assertTrue("Fail: object field is mapping by value.", false); + } catch (IciqlException e) { + assertEquals(IciqlException.CODE_UNMAPPED_FIELD, e.getIciqlCode()); + assertEquals(bc + 5, p.productId.intValue()); + } + + try { + // This test compares Integer(bc) which is the counter value of + // unitsInStock assigned by Utils.newObject() after the 3rd pass + // through from(). + // + // Object fields map by REFERENCE, not value. + db.from(p).where(Long.valueOf(bc).intValue()).is(9).orderBy(p.productId).select(); + assertTrue("Fail: object field is mapping by value.", false); + } catch (IciqlException e) { + assertEquals(IciqlException.CODE_UNMAPPED_FIELD, e.getIciqlCode()); + assertEquals(bc + (2 * fc), p.productId.intValue()); + } + + db.close(); + } + + /** + * Confirms that primitive aliases ARE mapped by value. + */ + @Test + public void testPrimitiveAliasMapping() throws Exception { + Db db = IciqlSuite.openNewDb(); + PrimitivesModel model = new PrimitivesModel(); + model.myLong = 100L; + db.insert(model); + model.myLong = 200L; + db.insert(model); + + // baseline count is the next id value + long bc = Utils.COUNTER.get(); + // number of fields in primitives model class + // each from() call will increment Utils.COUNTER by this amount + int fc = PrimitivesModel.class.getFields().length; + + PrimitivesModel p = new PrimitivesModel(); + // This test confirms standard primitive referencing querying. + long count = db.from(p).where(p.myLong).is(100L).selectCount(); + assertEquals(1, count); + // Confirms that myLong counter value is bc + assertEquals(bc, p.myLong); + try { + // This test compares "bc + fc" which is the counter value + // of myLong assigned by Utils.newObject() after the 2nd pass + // through from(). + // + // Primitive fields map by VALUE. + count = db.from(p).where(bc + fc).is(100L).selectCount(); + assertEquals(1, count); + assertEquals(bc + fc, p.myLong); + } catch (IciqlException e) { + assertTrue(e.getMessage(), false); + } + try { + // This test compares "bc" which was the counter value of + // myLong assigned by Utils.newObject() after the 1st pass + // through from(). "bc" is unmapped now and will throw an + // exception. + // + // Primitive fields map by VALUE. + db.from(p).where(bc).is(100L).select(); + } catch (IciqlException e) { + assertEquals(IciqlException.CODE_UNMAPPED_FIELD, e.getIciqlCode()); + assertEquals(bc + (2 * fc), p.myLong); + } + db.close(); + } +} \ No newline at end of file diff --git a/src/test/java/com/iciql/test/AnnotationsTest.java b/src/test/java/com/iciql/test/AnnotationsTest.java new file mode 100644 index 0000000..6aa75ad --- /dev/null +++ b/src/test/java/com/iciql/test/AnnotationsTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.IciqlException; +import com.iciql.test.models.Product; +import com.iciql.test.models.ProductAnnotationOnly; +import com.iciql.test.models.ProductInheritedAnnotation; +import com.iciql.test.models.ProductMixedAnnotation; +import com.iciql.test.models.ProductNoCreateTable; +import com.iciql.util.Utils; + +/** + * Test annotation processing. + */ +public class AnnotationsTest { + + /** + * This object represents a database (actually a connection to the + * database). + */ + + private Db db; + + @Before + public void setUp() { + db = IciqlSuite.openNewDb(); + db.insertAll(Product.getList()); + db.insertAll(ProductAnnotationOnly.getList()); + db.insertAll(ProductMixedAnnotation.getList()); + } + + @After + public void tearDown() { + db.close(); + } + + @Test + public void testIndexCreation() throws SQLException { + // test indexes are created, and columns are in the right order + DatabaseMetaData meta = db.getConnection().getMetaData(); + String schema = IciqlSuite.getDefaultSchema(db); + boolean toUpper = meta.storesUpperCaseIdentifiers(); + boolean toLower = meta.storesLowerCaseIdentifiers(); + ResultSet rs = meta.getIndexInfo(null, prepName(schema, toUpper, toLower), + prepName("ANNOTATEDPRODUCT", toUpper, toLower), false, true); + + List list = Utils.newArrayList(); + while (rs.next()) { + String col = rs.getString("COLUMN_NAME"); + String index = rs.getString("INDEX_NAME"); + list.add((col + ":" + index).toLowerCase()); + } + assertTrue(list.contains("name:annotatedproduct_idx_0")); + assertTrue(list.contains("cat:annotatedproduct_idx_0")); + assertTrue(list.contains("name:nameidx")); + } + + private String prepName(String name, boolean upper, boolean lower) { + if (name == null) { + return null; + } + if (upper) { + return name.toUpperCase(); + } else if (lower) { + return name.toLowerCase(); + } + return name; + } + + @Test + public void testProductAnnotationOnly() { + ProductAnnotationOnly p = new ProductAnnotationOnly(); + assertEquals(10, db.from(p).selectCount()); + + // test IQColumn.name="cat" + assertEquals(2, db.from(p).where(p.category).is("Beverages").selectCount()); + + // test IQTable.annotationsOnly=true + // public String unmappedField is ignored by iciql + try { + db.from(p).where(p.unmappedField).is("unmapped").selectCount(); + assertTrue("this should never execute", false); + } catch (IciqlException e) { + assertEquals(IciqlException.CODE_UNMAPPED_FIELD, e.getIciqlCode()); + } + + // 10 objects, 10 autoIncremented unique values + assertEquals(10, db.from(p).selectDistinct(p.productName).size()); + + // test IQTable.primaryKey=id + try { + db.insertAll(ProductAnnotationOnly.getList()); + } catch (IciqlException e) { + assertEquals(IciqlException.CODE_DUPLICATE_KEY, e.getIciqlCode()); + } + } + + @Test + public void testProductMixedAnnotation() { + ProductMixedAnnotation p = new ProductMixedAnnotation(); + + // test IQColumn.name="cat" + assertEquals(2, db.from(p).where(p.category).is("Beverages").selectCount()); + + // test IQTable.annotationsOnly=false + // public String mappedField is reflectively mapped by iciql + assertEquals(10, db.from(p).where(p.mappedField).is("mapped").selectCount()); + + // test IQIgnore annotation + assertEquals(null, db.from(p).selectFirst().productDescription); + + // test IQColumn.primaryKey=true + try { + db.insertAll(ProductMixedAnnotation.getList()); + } catch (IciqlException e) { + assertEquals(IciqlException.CODE_DUPLICATE_KEY, e.getIciqlCode()); + } + } + + @Test + public void testTrimStringAnnotation() { + ProductAnnotationOnly p = new ProductAnnotationOnly(); + ProductAnnotationOnly prod = db.from(p).selectFirst(); + String oldValue = prod.category; + String newValue = "01234567890123456789"; + // 2 chars exceeds field max + prod.category = newValue; + db.update(prod); + + ProductAnnotationOnly newProd = db.from(p).where(p.productId).is(prod.productId).selectFirst(); + assertEquals(newValue.substring(0, 15), newProd.category); + + newProd.category = oldValue; + db.update(newProd); + } + + @Test + public void testColumnInheritanceAnnotation() { + ProductInheritedAnnotation table = new ProductInheritedAnnotation(); + List inserted = ProductInheritedAnnotation.getData(); + db.insertAll(inserted); + + List retrieved = db.from(table).select(); + + for (int j = 0; j < retrieved.size(); j++) { + ProductInheritedAnnotation i = inserted.get(j); + ProductInheritedAnnotation r = retrieved.get(j); + assertEquals(i.category, r.category); + assertEquals(i.mappedField, r.mappedField); + assertEquals(i.unitsInStock, r.unitsInStock); + assertEquals(i.unitPrice, r.unitPrice); + assertEquals(i.name(), r.name()); + assertEquals(i.id(), r.id()); + } + } + + @Test + public void testCreateTableIfRequiredAnnotation() { + // tests IQTable.createTableIfRequired=false + try { + db.insertAll(ProductNoCreateTable.getList()); + } catch (IciqlException e) { + assertEquals(IciqlException.CODE_OBJECT_NOT_FOUND, e.getIciqlCode()); + } + } + +} diff --git a/src/test/java/com/iciql/test/BooleanModelTest.java b/src/test/java/com/iciql/test/BooleanModelTest.java new file mode 100644 index 0000000..1e1630a --- /dev/null +++ b/src/test/java/com/iciql/test/BooleanModelTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2011 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.Test; + +import com.iciql.Db; +import com.iciql.test.models.BooleanModel; +import com.iciql.test.models.BooleanModel.BooleanAsIntModel; + +/** + * Tests interchangeable mapping of INT columns with Booleans and BOOL columns + * with Integers. + *

    + *
  • mapping a BIT/BOOLEAN column as an Integer + *
  • mapping a INT column as a Boolean. + *
+ */ +public class BooleanModelTest { + + @Test + public void testBooleanColumn() { + Db db = IciqlSuite.openNewDb(); + db.insertAll(BooleanModel.getList()); + BooleanAsIntModel b = new BooleanAsIntModel(); + List models = db.from(b).select(); + int count = 0; + for (BooleanAsIntModel model : models) { + if ((model.id % 2) == 1) { + // assert that odd ids are true + assertTrue(model.mybool > 0); + } else { + // assert that even ids are false + assertTrue(model.mybool == 0); + } + + // count true values + if (model.mybool > 0) { + count++; + } + } + assertEquals(2, count); + + // invert boolean values and update + for (BooleanAsIntModel model : models) { + model.mybool = model.mybool > 0 ? 0 : 1; + } + db.updateAll(models); + + // check even ids are true + models = db.from(b).select(); + for (BooleanAsIntModel model : models) { + if ((model.id % 2) == 1) { + // assert that odd ids are false + assertTrue(model.mybool == 0); + } else { + // assert that even ids are true + assertTrue(model.mybool > 0); + } + } + db.close(); + } + + @Test + public void testIntColumn() { + Db db = IciqlSuite.openNewDb(); + // insert INT column + db.insertAll(BooleanAsIntModel.getList()); + + // select all rows with INT column and map to Boolean + BooleanModel b = new BooleanModel(); + List models = db.from(b).select(); + int count = 0; + for (BooleanModel model : models) { + if ((model.id % 2) == 1) { + // assert that odd ids are true + assertTrue(model.mybool); + } else { + // assert that even ids are false + assertTrue(!model.mybool); + } + + // count true values + if (model.mybool) { + count++; + } + } + assertEquals(2, count); + + // invert boolean values and update + for (BooleanModel model : models) { + model.mybool = !model.mybool; + } + db.updateAll(models); + + // check even ids are true + models = db.from(b).select(); + for (BooleanModel model : models) { + if ((model.id % 2) == 1) { + // assert that odd ids are false + assertTrue(!model.mybool); + } else { + // assert that even ids are true + assertTrue(model.mybool); + } + } + db.close(); + } +} diff --git a/src/test/java/com/iciql/test/ClobTest.java b/src/test/java/com/iciql/test/ClobTest.java new file mode 100644 index 0000000..49cee72 --- /dev/null +++ b/src/test/java/com/iciql/test/ClobTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 com.iciql.Define.primaryKey; +import static com.iciql.Define.tableName; +import static org.junit.Assert.assertEquals; + +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.Iciql; + +/** + * Tests if converting a CLOB to a String works. + */ +public class ClobTest { + + @Test + public void testClob() throws Exception { + String create = "CREATE TABLE CLOB_TEST(ID INT PRIMARY KEY, WORDS {0})"; + Db db = IciqlSuite.openNewDb(); + db.executeUpdate(MessageFormat.format(create, "VARCHAR(255)")); + db.insertAll(StringRecord.getList()); + testSimpleUpdate(db, "VARCHAR fail"); + db.executeUpdate("DROP TABLE CLOB_TEST"); + db.close(); + + db = IciqlSuite.openNewDb(); + db.executeUpdate(MessageFormat.format(create, db.getDialect().convertSqlType("CLOB"))); + db.insertAll(StringRecord.getList()); + testSimpleUpdate(db, "CLOB fail because of single quote artifacts"); + db.executeUpdate("DROP TABLE CLOB_TEST"); + db.close(); + } + + private void testSimpleUpdate(Db db, String failureMsg) { + String newWords = "I changed the words"; + StringRecord r = new StringRecord(); + StringRecord originalRecord = db.from(r).where(r.id).is(2).selectFirst(); + String oldWords = originalRecord.words; + originalRecord.words = newWords; + db.update(originalRecord); + + StringRecord r2 = new StringRecord(); + StringRecord revisedRecord = db.from(r2).where(r2.id).is(2).selectFirst(); + assertEquals(failureMsg, newWords, revisedRecord.words); + + // undo update + originalRecord.words = oldWords; + db.update(originalRecord); + } + + /** + * A simple class used in this test. + */ + public static class StringRecord implements Iciql { + + public Integer id; + public String words; + + public StringRecord() { + // public constructor + } + + private StringRecord(int id, String words) { + this.id = id; + this.words = words; + } + + public void defineIQ() { + tableName("CLOB_TEST"); + primaryKey(id); + } + + private static StringRecord create(int id, String words) { + return new StringRecord(id, words); + } + + public static List getList() { + StringRecord[] list = { + create(1, "Once upon a midnight dreary, while I pondered weak and weary,"), + create(2, "Over many a quaint and curious volume of forgotten lore,"), + create(3, "While I nodded, nearly napping, suddenly there came a tapping,"), + create(4, "As of some one gently rapping, rapping at my chamber door."), + create(5, "`'Tis some visitor,' I muttered, `tapping at my chamber door -"), + create(6, "Only this, and nothing more.'") }; + + return Arrays.asList(list); + } + + public String toString() { + return id + ": " + words; + } + } +} diff --git a/src/test/java/com/iciql/test/ConcurrencyTest.java b/src/test/java/com/iciql/test/ConcurrencyTest.java new file mode 100644 index 0000000..e248265 --- /dev/null +++ b/src/test/java/com/iciql/test/ConcurrencyTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2011 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 java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.IciqlException; +import com.iciql.Query; +import com.iciql.test.models.Product; +import com.iciql.util.Utils; + +/** + * Tests concurrency and alias instance sharing. + */ +public class ConcurrencyTest { + + private int numberOfTests = 800; + + @Before + public void setUp() { + Db db = IciqlSuite.openNewDb(); + db.insertAll(Product.getList()); + } + + @Test + public void testAliasSharing() throws Exception { + Db db = IciqlSuite.openCurrentDb(); + try { + // Single-threaded example of why aliases can NOT be shared. + Product p = new Product(); + Query query1 = db.from(p); + Query query2 = db.from(p); + + // if you could share alias instances both counts should be equal + long count1 = 0; + try { + count1 = query1.where(p.category).is("Beverages").selectCount(); + } catch (IciqlException e) { + assertEquals(IciqlException.CODE_UNMAPPED_FIELD, e.getIciqlCode()); + } + long count2 = query2.where(p.category).is("Beverages").selectCount(); + + // but they aren't + assertEquals(0, count1); + assertEquals(2, count2); + assertTrue(count1 != count2); + } finally { + db.close(); + } + } + + @Test + @Ignore + public void testConcurrencyFinal() throws Exception { + // Multi-threaded example of why aliases can NOT be shared. + // + // This test looks like it _could_ work and you may find that it _can_ + // work, but you should also find that it _will_ fail. + + List threads = Utils.newArrayList(); + final AtomicInteger failures = new AtomicInteger(0); + final Product p = new Product(); + for (int i = 0; i < numberOfTests; i++) { + final int testNumber = i; + Thread t = new Thread(new Runnable() { + public void run() { + try { + int testCase = testNumber % 10; + test(testCase, p); + } catch (AssertionError e) { + failures.incrementAndGet(); + } catch (IciqlException e) { + failures.incrementAndGet(); + if (e.getIciqlCode() != IciqlException.CODE_UNMAPPED_FIELD) { + System.err.println("UNEXPECTED ERROR in testConcurrencyFinal()"); + e.printStackTrace(); + } + } + } + }, "ICIQL-" + i); + t.start(); + threads.add(t); + } + + // wait till all threads complete + for (Thread t : threads) { + t.join(); + } + + assertTrue("This should fail. Try running a few more times.", failures.get() > 0); + } + + @Test + @Ignore + public void testConcurrencyThreadLocal() throws Exception { + List threads = Utils.newArrayList(); + final AtomicInteger failures = new AtomicInteger(0); + final ThreadLocal tl = Utils.newThreadLocal(Product.class); + for (int i = 0; i < numberOfTests; i++) { + final int testNumber = i; + Thread t = new Thread(new Runnable() { + public void run() { + try { + int testCase = testNumber % 10; + test(testCase, tl.get()); + } catch (AssertionError e) { + failures.incrementAndGet(); + } catch (IciqlException e) { + failures.incrementAndGet(); + if (e.getIciqlCode() != IciqlException.CODE_UNMAPPED_FIELD) { + System.err.println("UNEXPECTED ERROR in testConcurrencyThreadLocal()"); + e.printStackTrace(); + } + } + } + }, "ICIQL-" + i); + t.start(); + threads.add(t); + } + + // wait till all threads complete + for (Thread t : threads) { + t.join(); + } + + assertEquals("ThreadLocal should never fail!", 0, failures.get()); + } + + private void test(int testCase, Product p) throws AssertionError { + Db db = IciqlSuite.openCurrentDb(); + try { + List list; + switch (testCase) { + case 0: + list = db.from(p).where(p.productName).is("Chai").select(); + assertEquals(1, list.size()); + assertEquals("Chai", list.get(0).productName); + break; + case 1: + list = db.from(p).where(p.category).is("Condiments").select(); + assertEquals(5, list.size()); + break; + case 3: + list = db.from(p).where(p.productName).is("Aniseed Syrup").select(); + assertEquals(1, list.size()); + assertEquals("Aniseed Syrup", list.get(0).productName); + break; + case 4: + list = db.from(p).where(p.productName).like("Chef%").select(); + assertEquals(2, list.size()); + assertTrue(list.get(0).productName.startsWith("Chef")); + assertTrue(list.get(1).productName.startsWith("Chef")); + break; + case 6: + list = db.from(p).where(p.unitsInStock).exceeds(0).select(); + assertEquals(9, list.size()); + break; + case 7: + list = db.from(p).where(p.unitsInStock).is(0).select(); + assertEquals(1, list.size()); + assertEquals("Chef Anton's Gumbo Mix", list.get(0).productName); + break; + case 9: + list = db.from(p).where(p.productId).is(7).select(); + assertEquals(1, list.size()); + assertTrue(7 == list.get(0).productId); + break; + default: + list = db.from(p).select(); + assertEquals(10, list.size()); + } + } finally { + db.close(); + } + } +} diff --git a/src/test/java/com/iciql/test/DefaultValuesTest.java b/src/test/java/com/iciql/test/DefaultValuesTest.java new file mode 100644 index 0000000..1374379 --- /dev/null +++ b/src/test/java/com/iciql/test/DefaultValuesTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2011 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.assertTrue; + +import java.util.List; + +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.DbInspector; +import com.iciql.ValidationRemark; +import com.iciql.test.models.DefaultValuesModel; + +/** + * Tests default object values. + */ +public class DefaultValuesTest { + + @Test + public void testDefaultObjectValues() { + Db db = IciqlSuite.openNewDb(); + + // insert random model + DefaultValuesModel model = new DefaultValuesModel(); + db.insert(model); + + DefaultValuesModel v = new DefaultValuesModel(); + + // retrieve model and compare + DefaultValuesModel retrievedModel = db.from(v).selectFirst(); + assertTrue(model.myInteger.equals(retrievedModel.myInteger)); + assertTrue(model.myDate.equals(retrievedModel.myDate)); + assertTrue(model.myEnumIdTree.equals(retrievedModel.myEnumIdTree)); + assertTrue(model.myNameTree.equals(retrievedModel.myNameTree)); + assertTrue(model.myOrdinalTree.equals(retrievedModel.myOrdinalTree)); + assertTrue(retrievedModel.myNullTree == null); + + DbInspector inspector = new DbInspector(db); + List remarks = inspector.validateModel(model, false); + db.close(); + for (ValidationRemark remark : remarks) { + System.out.println(remark.toString()); + } + } +} diff --git a/src/test/java/com/iciql/test/EnumsTest.java b/src/test/java/com/iciql/test/EnumsTest.java new file mode 100644 index 0000000..d8a0589 --- /dev/null +++ b/src/test/java/com/iciql/test/EnumsTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2011 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.IciqlException; +import com.iciql.test.models.EnumModels; +import com.iciql.test.models.EnumModels.EnumIdModel; +import com.iciql.test.models.EnumModels.EnumOrdinalModel; +import com.iciql.test.models.EnumModels.EnumStringModel; +import com.iciql.test.models.EnumModels.Tree; + +/** + * Tests enum support. + */ +public class EnumsTest { + + private Db db; + + @Before + public void setUp() { + db = IciqlSuite.openNewDb(); + db.insertAll(EnumIdModel.createList()); + db.insertAll(EnumOrdinalModel.createList()); + db.insertAll(EnumStringModel.createList()); + } + + @After + public void tearDown() { + db.close(); + } + + @Test + public void testEnumQueries() { + testIntEnums(new EnumIdModel()); + testIntEnums(new EnumOrdinalModel()); + testStringEnums(new EnumStringModel()); + } + + private void testIntEnums(EnumModels e) { + // ensure all records inserted + long count = db.from(e).selectCount(); + assertEquals(5, count); + + // special case: + // value is first enum constant which is also the alias object. + // the first enum constant is used as the alias because we can not + // instantiate an enum reflectively. + EnumModels firstEnumValue = db.from(e).where(e.tree()).is(Tree.PINE).selectFirst(); + assertEquals(Tree.PINE, firstEnumValue.tree()); + + EnumModels model = db.from(e).where(e.tree()).is(Tree.WALNUT).selectFirst(); + + assertEquals(400, model.id.intValue()); + assertEquals(Tree.WALNUT, model.tree()); + + List list = db.from(e).where(e.tree()).atLeast(Tree.BIRCH).select(); + assertEquals(3, list.size()); + + // between is an int compare + list = db.from(e).where(e.tree()).between(Tree.BIRCH).and(Tree.WALNUT).select(); + assertEquals(2, list.size()); + + } + + private void testStringEnums(EnumModels e) { + // ensure all records inserted + long count = db.from(e).selectCount(); + assertEquals(5, count); + + // special case: + // value is first enum constant which is also the alias object. + // the first enum constant is used as the alias because we can not + // instantiate an enum reflectively. + EnumModels firstEnumValue = db.from(e).where(e.tree()).is(Tree.PINE).selectFirst(); + assertEquals(Tree.PINE, firstEnumValue.tree()); + + EnumModels model = db.from(e).where(e.tree()).is(Tree.WALNUT).selectFirst(); + + assertEquals(400, model.id.intValue()); + assertEquals(Tree.WALNUT, model.tree()); + + List list = db.from(e).where(e.tree()).isNot(Tree.BIRCH).select(); + assertEquals(count - 1, list.size()); + + // between is a string compare + list = db.from(e).where(e.tree()).between(Tree.MAPLE).and(Tree.PINE).select(); + assertEquals(3, list.size()); + } + + @Test + public void testMultipleEnumInstances() { + BadEnums b = new BadEnums(); + try { + db.from(b).where(b.tree1).is(Tree.BIRCH).and (b.tree2).is(Tree.MAPLE).getSQL(); + assertTrue("Failed to detect multiple Tree fields?!", false); + } catch (IciqlException e) { + assertTrue(e.getMessage(), e.getMessage().startsWith("Can not explicitly reference Tree")); + } + } + + public static class BadEnums { + Tree tree1 = Tree.BIRCH; + Tree tree2 = Tree.MAPLE; + } +} diff --git a/src/test/java/com/iciql/test/ForeignKeyTest.java b/src/test/java/com/iciql/test/ForeignKeyTest.java new file mode 100644 index 0000000..6b4a3e7 --- /dev/null +++ b/src/test/java/com/iciql/test/ForeignKeyTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012 Frédéric Gaillard. + * 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 org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.IciqlException; +import com.iciql.test.models.CategoryAnnotationOnly; +import com.iciql.test.models.ProductAnnotationOnlyWithForeignKey; + +/** + * Tests of Foreign Keys. + */ +public class ForeignKeyTest { + + /** + * This object represents a database (actually a connection to the + * database). + */ + + private Db db; + + @Before + public void setUp() { + db = IciqlSuite.openNewDb(); + db.insertAll(CategoryAnnotationOnly.getList()); + db.insertAll(ProductAnnotationOnlyWithForeignKey.getList()); + } + + @After + public void tearDown() { + db.dropTable(ProductAnnotationOnlyWithForeignKey.class); + db.dropTable(CategoryAnnotationOnly.class); + db.close(); + } + + @Test + public void testForeignKeyWithOnDeleteCascade() { + ProductAnnotationOnlyWithForeignKey p = new ProductAnnotationOnlyWithForeignKey(); + long count1 = db.from(p).selectCount(); + + // should remove 2 associated products + CategoryAnnotationOnly c = new CategoryAnnotationOnly(); + db.from(c).where(c.categoryId).is(1L).delete(); + + long count2 = db.from(p).selectCount(); + + assertEquals(count1, count2 + 2L); + } + + @Test + public void testForeignKeyDropReferenceTable() { + try { + db.dropTable(CategoryAnnotationOnly.class); + assertTrue("Should not be able to drop reference table!", false); + } catch (IciqlException e) { + assertEquals(e.getMessage(), IciqlException.CODE_CONSTRAINT_VIOLATION, e.getIciqlCode()); + } + } + +} diff --git a/src/test/java/com/iciql/test/IciqlSuite.java b/src/test/java/com/iciql/test/IciqlSuite.java new file mode 100644 index 0000000..a474404 --- /dev/null +++ b/src/test/java/com/iciql/test/IciqlSuite.java @@ -0,0 +1,614 @@ +/* + * Copyright 2011 James Moger. + * Copyright 2012 Frédéric Gaillard. + * + * 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 java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; +import java.sql.SQLException; +import java.text.DecimalFormat; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.commons.dbcp.ConnectionFactory; +import org.apache.commons.dbcp.DriverManagerConnectionFactory; +import org.apache.commons.dbcp.PoolableConnectionFactory; +import org.apache.commons.dbcp.PoolingDataSource; +import org.apache.commons.pool.impl.GenericObjectPool; +import org.hsqldb.persist.HsqlProperties; +import org.junit.Assert; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.RunWith; +import org.junit.runner.notification.Failure; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParameterException; +import com.beust.jcommander.Parameters; +import com.iciql.Constants; +import com.iciql.Db; +import com.iciql.test.models.BooleanModel; +import com.iciql.test.models.CategoryAnnotationOnly; +import com.iciql.test.models.ComplexObject; +import com.iciql.test.models.Customer; +import com.iciql.test.models.DefaultValuesModel; +import com.iciql.test.models.EnumModels.EnumIdModel; +import com.iciql.test.models.EnumModels.EnumOrdinalModel; +import com.iciql.test.models.EnumModels.EnumStringModel; +import com.iciql.test.models.MultipleBoolsModel; +import com.iciql.test.models.Order; +import com.iciql.test.models.PrimitivesModel; +import com.iciql.test.models.Product; +import com.iciql.test.models.ProductAnnotationOnly; +import com.iciql.test.models.ProductAnnotationOnlyWithForeignKey; +import com.iciql.test.models.ProductInheritedAnnotation; +import com.iciql.test.models.ProductMixedAnnotation; +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.iciql.test.models.SupportedTypes; +import com.iciql.util.IciqlLogger; +import com.iciql.util.IciqlLogger.IciqlListener; +import com.iciql.util.IciqlLogger.StatementType; +import com.iciql.util.StringUtils; +import com.iciql.util.Utils; + +/** + * JUnit 4 iciql test suite. + * + * By default this test suite will run against the H2 database. You can change + * this by switching the DEFAULT_TEST_DB value. + *

+ * Alternatively, you can run this class an application which will run all tests + * for all tested database configurations. + *

+ * NOTE: If you want to test against MySQL or PostgreSQL you must create an + * "iciql" database and allow user "sa" password "sa" complete control of that + * database. + * + */ +@RunWith(Suite.class) +@SuiteClasses({ AliasMapTest.class, AnnotationsTest.class, BooleanModelTest.class, ClobTest.class, + ConcurrencyTest.class, EnumsTest.class, ModelsTest.class, PrimitivesTest.class, + RuntimeQueryTest.class, SamplesTest.class, UpdateTest.class, UpgradesTest.class, JoinTest.class, + UUIDTest.class, ViewsTest.class, ForeignKeyTest.class, TransactionTest.class }) +public class IciqlSuite { + + private static final TestDb[] TEST_DBS = { + new TestDb("H2", true, true, "jdbc:h2:mem:iciql"), + new TestDb("H2", true, false, "jdbc:h2:file:testdbs/h2/iciql"), + new TestDb("H2", false, false, "jdbc:h2:tcp://localhost/" + + new File(System.getProperty("user.dir")).getAbsolutePath() + "/testdbs/h2tcp/iciql"), + new TestDb("HSQL", true, true, "jdbc:hsqldb:mem:iciql"), + new TestDb("HSQL", true, false, "jdbc:hsqldb:file:testdbs/hsql/iciql"), + new TestDb("HSQL", false, false, "jdbc:hsqldb:hsql://localhost/iciql"), + new TestDb("Derby", true, true, "jdbc:derby:memory:iciql;create=true"), + new TestDb("Derby", true, false, "jdbc:derby:directory:testdbs/derby/iciql;create=true"), + new TestDb("MySQL", false, false, "jdbc:mysql://localhost:7000/iciql", "sa", "sa"), + new TestDb("PostgreSQL", false, false, "jdbc:postgresql://localhost:5432/iciql", "sa", "sa") }; + + private static final TestDb DEFAULT_TEST_DB = TEST_DBS[0]; + + private static final PrintStream ERR = System.err; + + private static PrintStream out = System.out; + + private static Map connectionFactories = Utils + .newSynchronizedHashMap(); + + private static Map dataSources = Utils.newSynchronizedHashMap(); + + public static void assertStartsWith(String value, String startsWith) { + Assert.assertTrue(MessageFormat.format("Expected \"{0}\", got: \"{1}\"", startsWith, value), + value.startsWith(startsWith)); + } + + public static void assertEqualsIgnoreCase(String expected, String actual) { + Assert.assertTrue(MessageFormat.format("Expected \"{0}\", got: \"{1}\"", expected, actual), + expected.equalsIgnoreCase(actual)); + } + + public static boolean equivalentTo(double expected, double actual) { + if (Double.compare(expected, actual) == 0) { + return true; + } + return Math.abs(expected - actual) <= 0.000001d; + } + + /** + * Open a new Db object. All connections are cached and re-used to eliminate + * embedded database startup costs. + * + * @return a fresh Db object + */ + public static Db openNewDb() { + String testUrl = System.getProperty("iciql.url", DEFAULT_TEST_DB.url); + String testUser = System.getProperty("iciql.user", DEFAULT_TEST_DB.username); + String testPassword = System.getProperty("iciql.password", DEFAULT_TEST_DB.password); + + Db db = null; + PoolingDataSource dataSource = dataSources.get(testUrl); + if (dataSource == null) { + ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(testUrl, testUser, + testPassword); + GenericObjectPool pool = new GenericObjectPool(); + pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_GROW); + PoolableConnectionFactory factory = new PoolableConnectionFactory(connectionFactory, pool, null, + null, false, true); + dataSource = new PoolingDataSource(pool); + dataSources.put(testUrl, dataSource); + connectionFactories.put(testUrl, factory); + } + db = Db.open(dataSource); + + // drop views + db.dropView(ProductView.class); + db.dropView(ProductViewInherited.class); + db.dropView(ProductViewFromQuery.class); + db.dropView(ProductViewInheritedComplex.class); + + // drop tables + db.dropTable(BooleanModel.class); + db.dropTable(ComplexObject.class); + db.dropTable(Customer.class); + db.dropTable(DefaultValuesModel.class); + db.dropTable(EnumIdModel.class); + db.dropTable(EnumOrdinalModel.class); + db.dropTable(EnumStringModel.class); + db.dropTable(Order.class); + db.dropTable(PrimitivesModel.class); + db.dropTable(Product.class); + db.dropTable(ProductAnnotationOnly.class); + db.dropTable(ProductInheritedAnnotation.class); + db.dropTable(ProductMixedAnnotation.class); + db.dropTable(SupportedTypes.class); + db.dropTable(JoinTest.UserId.class); + db.dropTable(JoinTest.UserNote.class); + db.dropTable(EnumsTest.BadEnums.class); + db.dropTable(MultipleBoolsModel.class); + db.dropTable(ProductAnnotationOnlyWithForeignKey.class); + db.dropTable(CategoryAnnotationOnly.class); + + return db; + } + + /** + * Open the current database. + * + * @return the current database + */ + public static Db openCurrentDb() { + String testUrl = System.getProperty("iciql.url", DEFAULT_TEST_DB.url); + String testUser = System.getProperty("iciql.user", DEFAULT_TEST_DB.username); + String testPassword = System.getProperty("iciql.password", DEFAULT_TEST_DB.password); + return Db.open(testUrl, testUser, testPassword); + } + + /** + * Returns the name of the underlying database engine for the Db object. + * + * @param db + * @return the database engine name + */ + public static String getDatabaseEngineName(Db db) { + String database = ""; + try { + database = db.getConnection().getMetaData().getDatabaseProductName(); + } catch (SQLException s) { + } + return database; + } + + /** + * Returns true if the underlying database engine is Derby. + * + * @param db + * @return true if underlying database engine is Derby + */ + public static boolean isDerby(Db db) { + return IciqlSuite.getDatabaseEngineName(db).equals("Apache Derby"); + } + + /** + * Returns true if the underlying database engine is H2. + * + * @param db + * @return true if underlying database engine is H2 + */ + public static boolean isH2(Db db) { + return IciqlSuite.getDatabaseEngineName(db).equals("H2"); + } + + /** + * Returns true if the underlying database engine is MySQL. + * + * @param db + * @return true if underlying database engine is MySQL + */ + public static boolean isMySQL(Db db) { + return IciqlSuite.getDatabaseEngineName(db).equals("MySQL"); + } + + /** + * Gets the default schema of the underlying database engine. + * + * @param db + * @return the default schema + */ + public static String getDefaultSchema(Db db) { + if (isDerby(db)) { + // Derby sets default schema name to username + return "SA"; + } else if (isMySQL(db)) { + // MySQL does not have schemas + return null; + } + + return "PUBLIC"; + } + + /** + * Main entry point for the test suite. Executing this method will run the + * test suite on all registered databases. + * + * @param args + * @throws Exception + */ + public static void main(String... args) throws Exception { + Params params = new Params(); + JCommander jc = new JCommander(params); + try { + jc.parse(args); + } catch (ParameterException t) { + usage(jc, t); + } + + // Replace System.out with a file + if (!StringUtils.isNullOrEmpty(params.dbPerformanceFile)) { + out = new PrintStream(params.dbPerformanceFile); + System.setErr(out); + } + + deleteRecursively(new File("testdbs")); + + // Start the HSQL and H2 servers in-process + org.hsqldb.Server hsql = startHSQL(); + org.h2.tools.Server h2 = startH2(); + + // Statement logging + final FileWriter statementWriter; + if (StringUtils.isNullOrEmpty(params.sqlStatementsFile)) { + statementWriter = null; + } else { + statementWriter = new FileWriter(params.sqlStatementsFile); + } + IciqlListener statementListener = new IciqlListener() { + @Override + public void logIciql(StatementType type, String statement) { + if (statementWriter == null) { + return; + } + try { + statementWriter.append(statement); + statementWriter.append('\n'); + } catch (IOException e) { + e.printStackTrace(); + } + } + }; + IciqlLogger.registerListener(statementListener); + + SuiteClasses suiteClasses = IciqlSuite.class.getAnnotation(SuiteClasses.class); + long quickestDatabase = Long.MAX_VALUE; + String dividerMajor = buildDivider('*', 79); + String dividerMinor = buildDivider('-', 79); + + // Header + out.println(dividerMajor); + out.println(MessageFormat.format("{0} {1} ({2}) testing {3} database configurations", Constants.NAME, + Constants.VERSION, Constants.VERSION_DATE, TEST_DBS.length)); + out.println(dividerMajor); + out.println(); + + showProperty("java.vendor"); + showProperty("java.runtime.version"); + showProperty("java.vm.name"); + showProperty("os.name"); + showProperty("os.version"); + showProperty("os.arch"); + showProperty("available processors", "" + Runtime.getRuntime().availableProcessors()); + showProperty( + "available memory", + MessageFormat.format("{0,number,0.0} GB", ((double) Runtime.getRuntime().maxMemory()) + / (1024 * 1024))); + out.println(); + + // Test a database + long lastCount = 0; + for (TestDb testDb : TEST_DBS) { + out.println(dividerMinor); + out.println("Testing " + testDb.describeDatabase()); + out.println(" " + testDb.url); + out.println(dividerMinor); + + // inject a database section delimiter in the statement log + if (statementWriter != null) { + statementWriter.append("\n\n"); + statementWriter.append("# ").append(dividerMinor).append('\n'); + statementWriter.append("# ").append("Testing " + testDb.describeDatabase()).append('\n'); + statementWriter.append("# ").append(dividerMinor).append('\n'); + statementWriter.append("\n\n"); + } + + if (testDb.getVersion().equals("OFFLINE")) { + // Database not available + out.println("Skipping. Could not find " + testDb.url); + out.println(); + } else { + // Setup system properties + System.setProperty("iciql.url", testDb.url); + System.setProperty("iciql.user", testDb.username); + System.setProperty("iciql.password", testDb.password); + + // Test database + Result result = JUnitCore.runClasses(suiteClasses.value()); + + // Report results + testDb.runtime = result.getRunTime(); + if (testDb.runtime < quickestDatabase) { + quickestDatabase = testDb.runtime; + } + testDb.statements = IciqlLogger.getTotalCount() - lastCount; + // reset total count for next database + lastCount = IciqlLogger.getTotalCount(); + + out.println(MessageFormat.format( + "{0} tests ({1} failures, {2} ignores) {3} statements in {4,number,0.000} secs", + result.getRunCount(), result.getFailureCount(), result.getIgnoreCount(), + testDb.statements, result.getRunTime() / 1000f)); + + if (result.getFailureCount() == 0) { + out.println(); + out.println(" 100% successful test suite run."); + out.println(); + } else { + for (Failure failure : result.getFailures()) { + out.println(MessageFormat.format("\n + {0}\n {1}", failure.getTestHeader(), + failure.getMessage())); + } + out.println(); + } + } + } + + // Display runtime results sorted by performance leader + out.println(); + out.println(dividerMajor); + out.println(MessageFormat.format("{0} {1} ({2}) test suite performance results", Constants.NAME, + Constants.VERSION, Constants.VERSION_DATE)); + out.println(dividerMajor); + List dbs = Arrays.asList(TEST_DBS); + Collections.sort(dbs); + + out.println(MessageFormat.format("{0} {1} {2} {3} {4}", StringUtils.pad("Name", 11, " ", true), + StringUtils.pad("Type", 5, " ", true), StringUtils.pad("Version", 23, " ", true), + StringUtils.pad("Stats/Sec", 10, " ", true), "Runtime")); + out.println(dividerMinor); + for (TestDb testDb : dbs) { + DecimalFormat df = new DecimalFormat("0.0"); + out.println(MessageFormat.format("{0} {1} {2} {3} {4} {5}s ({6,number,0.0}x)", + StringUtils.pad(testDb.name, 11, " ", true), testDb.isEmbedded ? "E" : "T", + testDb.isMemory ? "M" : "F", StringUtils.pad(testDb.getVersion(), 21, " ", true), + StringUtils.pad("" + testDb.getStatementRate(), 10, " ", false), + StringUtils.pad(df.format(testDb.getRuntime()), 8, " ", false), ((double) testDb.runtime) + / quickestDatabase)); + } + out.println(dividerMinor); + out.println(" E = embedded connection"); + out.println(" T = tcp/ip connection"); + out.println(" M = memory database"); + out.println(" F = file/persistent database"); + + // cleanup + for (PoolableConnectionFactory factory : connectionFactories.values()) { + factory.getPool().close(); + } + IciqlLogger.unregisterListener(statementListener); + out.close(); + System.setErr(ERR); + if (statementWriter != null) { + statementWriter.close(); + } + hsql.stop(); + h2.stop(); + System.exit(0); + } + + private static void showProperty(String name) { + showProperty(name, System.getProperty(name)); + } + + private static void showProperty(String name, String value) { + out.print(' '); + out.print(StringUtils.pad(name, 25, " ", true)); + out.println(value); + } + + private static void usage(JCommander jc, ParameterException t) { + System.out.println(Constants.NAME + " test suite v" + Constants.VERSION); + System.out.println(); + if (t != null) { + System.out.println(t.getMessage()); + System.out.println(); + } + if (jc != null) { + jc.usage(); + } + System.exit(0); + } + + private static String buildDivider(char c, int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(c); + } + return sb.toString(); + } + + private static void deleteRecursively(File f) { + if (f.isDirectory()) { + for (File file : f.listFiles()) { + if (file.isDirectory()) { + deleteRecursively(file); + } + file.delete(); + } + } + f.delete(); + } + + /** + * Start an HSQL tcp server. + * + * @return an HSQL server instance + * @throws Exception + */ + private static org.hsqldb.Server startHSQL() throws Exception { + HsqlProperties p = new HsqlProperties(); + String db = new File(System.getProperty("user.dir")).getAbsolutePath() + "/testdbs/hsqltcp/iciql"; + p.setProperty("server.database.0", "file:" + db); + p.setProperty("server.dbname.0", "iciql"); + // set up the rest of properties + + // alternative to the above is + org.hsqldb.Server server = new org.hsqldb.Server(); + server.setProperties(p); + server.setLogWriter(null); + server.setErrWriter(null); + server.start(); + return server; + } + + /** + * Start the H2 tcp server. + * + * @return an H2 server instance + * @throws Exception + */ + private static org.h2.tools.Server startH2() throws Exception { + org.h2.tools.Server server = org.h2.tools.Server.createTcpServer(); + server.start(); + return server; + } + + /** + * Represents a test database url. + */ + private static class TestDb implements Comparable { + final String name; + boolean isEmbedded; + boolean isMemory; + final String url; + final String username; + final String password; + String version; + long runtime; + long statements; + + TestDb(String name, boolean isEmbedded, boolean isMemory, String url) { + this(name, isEmbedded, isMemory, url, "sa", ""); + } + + TestDb(String name, boolean isEmbedded, boolean isMemory, String url, String username, String password) { + this.name = name; + this.isEmbedded = isEmbedded; + this.isMemory = isMemory; + this.url = url; + this.username = username; + this.password = password; + } + + double getRuntime() { + return runtime / 1000d; + } + + int getStatementRate() { + return Double.valueOf(((double) statements) / (runtime / 1000d)).intValue(); + } + + String describeDatabase() { + StringBuilder sb = new StringBuilder(name); + sb.append(" "); + sb.append(getVersion()); + return sb.toString(); + } + + String getVersion() { + if (version == null) { + try { + Db db = Db.open(url, username, password); + version = db.getConnection().getMetaData().getDatabaseProductVersion(); + db.close(); + return version; + } catch (Throwable t) { + version = "OFFLINE"; + } + } + return version; + } + + @Override + public int compareTo(TestDb o) { + if (runtime == 0) { + return 1; + } + if (o.runtime == 0) { + return -1; + } + int r1 = getStatementRate(); + int r2 = o.getStatementRate(); + if (r1 == r2) { + return 0; + } + if (r1 < r2) { + return 1; + } + return -1; + } + } + + /** + * Command-line parameters for TestSuite. + */ + @Parameters(separators = " ") + private static class Params { + + @Parameter(names = { "--dbFile" }, description = "Database performance results text file", required = false) + public String dbPerformanceFile; + + @Parameter(names = { "--sqlFile" }, description = "SQL statements log file", required = false) + public String sqlStatementsFile; + } +} \ No newline at end of file diff --git a/src/test/java/com/iciql/test/JoinTest.java b/src/test/java/com/iciql/test/JoinTest.java new file mode 100644 index 0000000..0e5e39d --- /dev/null +++ b/src/test/java/com/iciql/test/JoinTest.java @@ -0,0 +1,173 @@ +/* + * Copyright 2011 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 java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQTable; +import com.iciql.QueryWhere; + +/** + * Tests of Joins. + */ +public class JoinTest { + + Db db; + + @Before + public void setup() { + db = IciqlSuite.openNewDb(); + + db.insertAll(UserId.getList()); + db.insertAll(UserNote.getList()); + } + + @After + public void tearDown() { + db.close(); + } + + @Test + public void testPrimitiveJoin() throws Exception { + final UserId u = new UserId(); + final UserNote n = new UserNote(); + + List notes = db.from(u).innerJoin(n).on(u.id).is(n.userId).where(u.id).is(2) + .select(new UserNote() { + { + userId = n.userId; + noteId = n.noteId; + text = n.text; + } + }); + assertEquals(3, notes.size()); + } + + @Test + public void testJoin() throws Exception { + final UserId u = new UserId(); + final UserNote n = new UserNote(); + + // this query returns 1 UserId if the user has a note + // it's purpose is to confirm fluency/type-safety on a very simple + // join case where the main table is filtered/reduced by hits in a + // related table + + List users = db.from(u).innerJoin(n).on(u.id).is(n.userId).where(u.id).is(2).selectDistinct(); + + assertEquals(1, users.size()); + assertEquals(2, users.get(0).id); + } + + @Test + public void testLeftJoin() throws Exception { + final UserId u = new UserId(); + final UserNote n = new UserNote(); + + List notes = db.from(u).leftJoin(n).on(u.id).is(n.userId).where(u.id).is(4).select(); + assertEquals(1, notes.size()); + assertEquals(4, notes.get(0).id); + } + + @Test + public void testSubQuery() throws Exception { + final UserId u = new UserId(); + final UserNote n = new UserNote(); + + QueryWhere q = db.from(u).where(u.id).in(db.from(n).where(n.userId).exceeds(0).subQuery(n.userId)); + List notes = q.select(); + assertEquals(3, notes.size()); + + // do not test MySQL on this statement because the databases + if (IciqlSuite.isMySQL(db)) { + assertEquals("SELECT * FROM UserId WHERE `id` in (SELECT `userId` FROM UserNote WHERE `userId` > 0 )", q.toSQL()); + } else { + assertEquals("SELECT * FROM UserId WHERE id in (SELECT userId FROM UserNote WHERE userId > 0 )", q.toSQL()); + } + } + + @IQTable + public static class UserId { + + @IQColumn(primaryKey = true) + public int id; + + @IQColumn(length = 10) + public String name; + + public UserId() { + // public constructor + } + + public UserId(int id, String name) { + this.id = id; + this.name = name; + } + + public String toString() { + return name + " (" + id + ")"; + } + + public static List getList() { + UserId[] list = { new UserId(1, "Tom"), new UserId(2, "Dick"), new UserId(3, "Harry"), new UserId(4, "Jack") }; + return Arrays.asList(list); + } + } + + @IQTable + public static class UserNote { + + @IQColumn(autoIncrement = true, primaryKey = true) + public int noteId; + + @IQColumn + public int userId; + + @IQColumn(length = 10) + public String text; + + public UserNote() { + // public constructor + } + + public UserNote(int userId, String text) { + this.userId = userId; + this.text = text; + } + + public String toString() { + return text; + } + + public static List getList() { + UserNote[] list = { new UserNote(1, "A"), new UserNote(2, "B"), new UserNote(3, "C"), + new UserNote(1, "D"), new UserNote(2, "E"), new UserNote(3, "F"), new UserNote(1, "G"), + new UserNote(2, "H"), new UserNote(3, "I"), }; + return Arrays.asList(list); + } + } +} diff --git a/src/test/java/com/iciql/test/ModelsTest.java b/src/test/java/com/iciql/test/ModelsTest.java new file mode 100644 index 0000000..c5ba27a --- /dev/null +++ b/src/test/java/com/iciql/test/ModelsTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 static com.iciql.test.IciqlSuite.assertEqualsIgnoreCase; + +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; + +import com.iciql.Db; +import com.iciql.DbInspector; +import com.iciql.ValidationRemark; +import com.iciql.test.models.Product; +import com.iciql.test.models.ProductAnnotationOnly; +import com.iciql.test.models.ProductMixedAnnotation; +import com.iciql.test.models.SupportedTypes; +import com.iciql.util.StringUtils; + +/** + * Test that the mapping between classes and tables is done correctly. + */ +public class ModelsTest { + + /* + * The ErrorCollector Rule allows execution of a test to continue after the + * first problem is found and report them all at once + */ + @Rule + public ErrorCollector errorCollector = new ErrorCollector(); + + private Db db; + + @Before + public void setUp() { + db = IciqlSuite.openNewDb(); + db.insertAll(Product.getList()); + db.insertAll(ProductAnnotationOnly.getList()); + db.insertAll(ProductMixedAnnotation.getList()); + } + + @After + public void tearDown() { + db.close(); + } + + @Test + public void testValidateModels() { + String schemaName = IciqlSuite.getDefaultSchema(db); + DbInspector inspector = new DbInspector(db); + validateModel(inspector, schemaName, new ProductAnnotationOnly(), 2); + validateModel(inspector, schemaName, new ProductMixedAnnotation(), 4); + } + + private void validateModel(DbInspector inspector, String schemaName, Object o, int expected) { + List remarks = inspector.validateModel(o, false); + assertTrue("validation remarks are null for " + o.getClass().getName(), remarks != null); + StringBuilder sb = new StringBuilder(); + sb.append("validation remarks for " + o.getClass().getName()); + sb.append('\n'); + for (ValidationRemark remark : remarks) { + sb.append(remark.toString()); + sb.append('\n'); + if (remark.isError()) { + errorCollector.addError(new SQLException(remark.toString())); + } + } + + if (StringUtils.isNullOrEmpty(schemaName)) { + // no schema expected + assertEquals(sb.toString(), expected - 1, remarks.size()); + } else { + assertEquals(sb.toString(), expected, remarks.size()); + assertEqualsIgnoreCase(MessageFormat.format("@IQSchema(\"{0}\")", schemaName), + remarks.get(0).message); + } + } + + @Test + public void testSupportedTypes() { + List original = SupportedTypes.createList(); + db.insertAll(original); + List retrieved = db.from(SupportedTypes.SAMPLE).select(); + assertEquals(original.size(), retrieved.size()); + for (int i = 0; i < original.size(); i++) { + SupportedTypes o = original.get(i); + SupportedTypes r = retrieved.get(i); + assertTrue(o.equivalentTo(r)); + } + } + + @Test + public void testModelGeneration() { + List original = SupportedTypes.createList(); + db.insertAll(original); + DbInspector inspector = new DbInspector(db); + List models = inspector.generateModel(null, "SupportedTypes", "com.iciql.test.models", true, + true); + assertEquals(1, models.size()); + // a poor test, but a start + String dbName = IciqlSuite.getDatabaseEngineName(db); + if (dbName.equals("H2")) { + assertEquals(1587, models.get(0).length()); + } else if (dbName.startsWith("HSQL")) { + // HSQL uses Double instead of Float + assertEquals(1591, models.get(0).length()); + } else if (dbName.equals("Apache Derby")) { + // Derby uses java.sql.Timestamp not java.util.Date + // Derby uses username as schema name + assertEquals(1601, models.get(0).length()); + } else if (dbName.equals("PostgreSQL")) { + assertEquals(1643, models.get(0).length()); + } else if (dbName.equals("MySQL")) { + // MySQL uses timestamp default values like + // 0000-00-00 00:00:00 and CURRENT_TIMESTAMP + assertEquals(1673, models.get(0).length()); + } else { + // unknown database + assertEquals(0, models.get(0).length()); + } + } +} diff --git a/src/test/java/com/iciql/test/PrimitivesTest.java b/src/test/java/com/iciql/test/PrimitivesTest.java new file mode 100644 index 0000000..3d3811e --- /dev/null +++ b/src/test/java/com/iciql/test/PrimitivesTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2011 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.Collections; +import java.util.List; + +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.IciqlException; +import com.iciql.test.models.MultipleBoolsModel; +import com.iciql.test.models.PrimitivesModel; + +/** + * Tests primitives with autoboxing within the framework. + */ +public class PrimitivesTest { + + @Test + public void testPrimitives() { + Db db = IciqlSuite.openNewDb(); + + // insert random models in reverse order + List models = PrimitivesModel.getList(); + PrimitivesModel model = models.get(0); + Collections.reverse(models); + // insert them in reverse order + db.insertAll(models); + + PrimitivesModel p = new PrimitivesModel(); + + // retrieve model and compare + PrimitivesModel retrievedModel = db.from(p).orderBy(p.myLong).selectFirst(); + assertTrue(model.equivalentTo(retrievedModel)); + + retrievedModel = db.from(p).where("mylong = ? and myinteger = ?", model.myLong, model.myInteger) + .selectFirst(); + assertTrue(model.equivalentTo(retrievedModel)); + + // retrieve with conditions and compare + retrievedModel = db.from(p).where(p.myLong).is(model.myLong).and(p.myInteger).is(model.myInteger) + .selectFirst(); + assertTrue(model.equivalentTo(retrievedModel)); + + // set myInteger & myDouble + db.from(p).set(p.myInteger).to(10).set(p.myDouble).to(3.0d).where(p.myLong).is(model.myLong).update(); + retrievedModel = db.from(p).orderBy(p.myLong).selectFirst(); + + assertEquals(10, retrievedModel.myInteger); + assertEquals(3d, retrievedModel.myDouble, 0.001d); + + // increment my double by pi + db.from(p).increment(p.myDouble).by(3.14d).update(); + retrievedModel = db.from(p).orderBy(p.myLong).selectFirst(); + assertEquals(6.14d, retrievedModel.myDouble, 0.001d); + + // test order by + List list = db.from(p).orderBy(p.myLong).select(); + assertEquals(models.size(), list.size()); + assertEquals("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", list.toString()); + + // test model update + retrievedModel.myInteger = 1337; + assertEquals(1, db.update(retrievedModel)); + assertEquals(1, db.delete(retrievedModel)); + + db.close(); + } + + @Test + public void testMultipleBooleans() { + Db db = IciqlSuite.openNewDb(); + db.insertAll(MultipleBoolsModel.getList()); + + MultipleBoolsModel m = new MultipleBoolsModel(); + try { + db.from(m).where(m.a).is(true).select(); + assertTrue(false); + } catch (IciqlException e) { + assertTrue(true); + } + db.close(); + } +} diff --git a/src/test/java/com/iciql/test/RuntimeQueryTest.java b/src/test/java/com/iciql/test/RuntimeQueryTest.java new file mode 100644 index 0000000..c23527f --- /dev/null +++ b/src/test/java/com/iciql/test/RuntimeQueryTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2011 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 java.sql.ResultSet; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.List; + +import org.junit.Assume; +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.QueryWhere; +import com.iciql.test.models.EnumModels.Tree; +import com.iciql.test.models.Product; +import com.iciql.test.models.StaticQueries; +import com.iciql.util.JdbcUtils; +import com.iciql.util.Utils; + +/** + * Tests the runtime dynamic query function. + */ +public class RuntimeQueryTest { + + @Test + public void testParameters() { + Db db = IciqlSuite.openNewDb(); + + // do not test non-H2 databases because dialects will get in the way + // e.g. column quoting, etc + Assume.assumeTrue(IciqlSuite.isH2(db)); + + Product p = new Product(); + String q1 = db.from(p).where(p.unitsInStock).isParameter().and(p.productName).likeParameter().orderBy(p.productId).toSQL(); + String q2 = db.from(p).where(p.unitsInStock).lessThan(100).and(p.productName).like("test").or(p.productName).likeParameter().orderBy(p.productId).toSQL(); + + StaticQueries.StaticModel1 m1 = new StaticQueries.StaticModel1(); + String q3 = db.from(m1).where(m1.myTree).is(Tree.MAPLE).and(m1.myTree).isParameter().toSQL(); + + StaticQueries.StaticModel2 m2 = new StaticQueries.StaticModel2(); + String q4 = db.from(m2).where(m2.myTree).is(Tree.MAPLE).and(m2.myTree).isParameter().toSQL(); + + StaticQueries.StaticModel3 m3 = new StaticQueries.StaticModel3(); + String q5 = db.from(m3).where(m3.myTree).is(Tree.MAPLE).and(m3.myTree).isParameter().toSQL(); + + long now = System.currentTimeMillis(); + java.sql.Date aDate = new java.sql.Date(now); + java.sql.Time aTime = new java.sql.Time(now); + java.sql.Timestamp aTimestamp = new java.sql.Timestamp(now); + + String q6 = db.from(m1).where(m1.myDate).is(aDate).and(m1.myDate).isParameter().toSQL(); + String q7 = db.from(m1).where(m1.myTime).is(aTime).and(m1.myTime).isParameter().toSQL(); + String q8 = db.from(m1).where(m1.myTimestamp).is(aTimestamp).and(m1.myTimestamp).isParameter().toSQL(); + + db.close(); + assertEquals("SELECT * FROM Product WHERE unitsInStock = ? AND productName LIKE ? ORDER BY productId", q1); + assertEquals("SELECT * FROM Product WHERE unitsInStock < 100 AND productName LIKE 'test' OR productName LIKE ? ORDER BY productId", q2); + + assertEquals("SELECT * FROM StaticQueryTest1 WHERE myTree = 'MAPLE' AND myTree = ?", q3); + assertEquals("SELECT * FROM StaticQueryTest2 WHERE myTree = 50 AND myTree = ?", q4); + assertEquals("SELECT * FROM StaticQueryTest3 WHERE myTree = 4 AND myTree = ?", q5); + + java.util.Date refDate = new java.util.Date(now); + assertEquals("SELECT * FROM StaticQueryTest1 WHERE myDate = '" + new SimpleDateFormat("yyyy-MM-dd").format(refDate) + "' AND myDate = ?", q6); + assertEquals("SELECT * FROM StaticQueryTest1 WHERE myTime = '" + new SimpleDateFormat("HH:mm:ss").format(refDate) + "' AND myTime = ?", q7); + assertEquals("SELECT * FROM StaticQueryTest1 WHERE myTimestamp = '" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(refDate) + "' AND myTimestamp = ?", q8); + } + + @Test + public void testRuntimeSet() { + Db db = IciqlSuite.openNewDb(); + + // do not test non-H2 databases because dialects will get in the way + // e.g. column quoting, etc + Assume.assumeTrue(IciqlSuite.isH2(db)); + + StaticQueries.StaticModel1 m = new StaticQueries.StaticModel1(); + String q = db.from(m).set(m.myTimestamp).toParameter().where(m.id).isParameter().toSQL(); + db.close(); + + assertEquals("UPDATE StaticQueryTest1 SET myTimestamp = ? WHERE id = ?", q); + } + + @Test + public void testRuntimeSelectWildcards() { + Db db = IciqlSuite.openNewDb(); + + // do not test non-H2 databases because dialects will get in the way + // e.g. column quoting, etc + Assume.assumeTrue(IciqlSuite.isH2(db)); + + StaticQueries.StaticModel1 m1 = new StaticQueries.StaticModel1(); + StaticQueries.StaticModel2 m2 = new StaticQueries.StaticModel2(); + StaticQueries.StaticModel2 m3 = new StaticQueries.StaticModel2(); + + int t0 = Utils.AS_COUNTER.get() + 1; + int t1 = t0 + 1; + + QueryWhere where = db.from(m1).innerJoin(m2).on(m1.id).is(m2.id).where(m2.myTree).is(Tree.MAPLE); + String q1 = where.toSQL(false); + String q2 = where.toSQL(true); + String q3 = where.toSQL(false, m1); + String q4 = where.toSQL(true, m1); + String q5 = where.toSQL(false, m2); + String q6 = where.toSQL(true, m2); + + // test unused alias + String q7 = where.toSQL(true, m3); + + db.close(); + + assertEquals(MessageFormat.format("SELECT * FROM StaticQueryTest1 AS T{0,number,0} INNER JOIN StaticQueryTest2 AS T{1,number,0} ON T{0,number,0}.id = T{1,number,0}.id WHERE T{1,number,0}.myTree = 50", t0, t1), q1); + assertEquals(MessageFormat.format("SELECT DISTINCT * FROM StaticQueryTest1 AS T{0,number,0} INNER JOIN StaticQueryTest2 AS T{1,number,0} ON T{0,number,0}.id = T{1,number,0}.id WHERE T{1,number,0}.myTree = 50", t0, t1), q2); + + assertEquals(MessageFormat.format("SELECT T{0,number,0}.* FROM StaticQueryTest1 AS T{0,number,0} INNER JOIN StaticQueryTest2 AS T{1,number,0} ON T{0,number,0}.id = T{1,number,0}.id WHERE T{1,number,0}.myTree = 50", t0, t1), q3); + assertEquals(MessageFormat.format("SELECT DISTINCT T{0,number,0}.* FROM StaticQueryTest1 AS T{0,number,0} INNER JOIN StaticQueryTest2 AS T{1,number,0} ON T{0,number,0}.id = T{1,number,0}.id WHERE T{1,number,0}.myTree = 50", t0, t1), q4); + + assertEquals(MessageFormat.format("SELECT T{1,number,0}.* FROM StaticQueryTest1 AS T{0,number,0} INNER JOIN StaticQueryTest2 AS T{1,number,0} ON T{0,number,0}.id = T{1,number,0}.id WHERE T{1,number,0}.myTree = 50", t0, t1), q5); + assertEquals(MessageFormat.format("SELECT DISTINCT T{1,number,0}.* FROM StaticQueryTest1 AS T{0,number,0} INNER JOIN StaticQueryTest2 AS T{1,number,0} ON T{0,number,0}.id = T{1,number,0}.id WHERE T{1,number,0}.myTree = 50", t0, t1), q6); + + assertEquals(MessageFormat.format("SELECT DISTINCT * FROM StaticQueryTest1 AS T{0,number,0} INNER JOIN StaticQueryTest2 AS T{1,number,0} ON T{0,number,0}.id = T{1,number,0}.id WHERE T{1,number,0}.myTree = 50", t0, t1), q7); + } + + @Test + public void testRuntimeQuery() { + Db db = IciqlSuite.openNewDb(); + db.insertAll(Product.getList()); + + Product p = new Product(); + List products = db.from(p).where("unitsInStock=?", 120).orderBy(p.productId).select(); + assertEquals(1, products.size()); + + products = db.from(p).where("unitsInStock=? and productName like ? order by productId", 0, "Chef%") + .select(); + assertEquals(1, products.size()); + + db.close(); + } + + @Test + public void testExecuteQuery() throws SQLException { + Db db = IciqlSuite.openNewDb(); + db.insertAll(Product.getList()); + + // test plain statement + List products = db.executeQuery(Product.class, + "select * from product where unitsInStock=120"); + assertEquals(1, products.size()); + assertEquals("Condiments", products.get(0).category); + + // test prepared statement + products = db.executeQuery(Product.class, "select * from product where unitsInStock=?", 120); + assertEquals(1, products.size()); + assertEquals("Condiments", products.get(0).category); + + db.close(); + } + + @Test + public void testBuildObjects() throws SQLException { + Db db = IciqlSuite.openNewDb(); + db.insertAll(Product.getList()); + + // test plain statement + ResultSet rs = db.executeQuery("select * from product where unitsInStock=120"); + List products = db.buildObjects(Product.class, rs); + JdbcUtils.closeSilently(rs, true); + + assertEquals(1, products.size()); + assertEquals("Condiments", products.get(0).category); + + // test prepared statement + rs = db.executeQuery("select * from product where unitsInStock=?", 120); + products = db.buildObjects(Product.class, rs); + JdbcUtils.closeSilently(rs, true); + + assertEquals(1, products.size()); + assertEquals("Condiments", products.get(0).category); + + db.close(); + } +} diff --git a/src/test/java/com/iciql/test/SamplesTest.java b/src/test/java/com/iciql/test/SamplesTest.java new file mode 100644 index 0000000..49a64f5 --- /dev/null +++ b/src/test/java/com/iciql/test/SamplesTest.java @@ -0,0 +1,442 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 com.iciql.Function.count; +import static com.iciql.Function.isNull; +import static com.iciql.Function.length; +import static com.iciql.Function.max; +import static com.iciql.Function.min; +import static com.iciql.Function.not; +import static com.iciql.Function.sum; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.Filter; +import com.iciql.Iciql.IQColumn; +import com.iciql.test.models.ComplexObject; +import com.iciql.test.models.Customer; +import com.iciql.test.models.Order; +import com.iciql.test.models.Product; +import com.iciql.test.models.SupportedTypes; + +/** + * This is the implementation of the 101 LINQ Samples as described in + * http://msdn2.microsoft.com/en-us/vcsharp/aa336760.aspx + */ +public class SamplesTest { + + /** + * This object represents a database (actually a connection to the + * database). + */ + + Db db; + + @Before + public void setUp() { + db = IciqlSuite.openNewDb(); + db.insertAll(Product.getList()); + db.insertAll(Customer.getList()); + db.insertAll(Order.getList()); + db.insertAll(ComplexObject.getList()); + } + + @After + public void tearDown() { + db.close(); + } + + /** + * A simple test table. The columns are in a different order than in the + * database. + */ + public static class TestReverse { + public String name; + public Integer id; + } + + @Test + public void testReverseColumns() { + db.executeUpdate("create table TestReverse(id int, name varchar(10), additional varchar(10))"); + TestReverse t = new TestReverse(); + t.id = 10; + t.name = "Hello"; + db.insert(t); + TestReverse check = db.from(new TestReverse()).selectFirst(); + assertEquals(t.name, check.name); + assertEquals(t.id, check.id); + db.executeUpdate("DROP TABLE testreverse"); + } + + @Test + public void testWhereSimple2() { + + // var soldOutProducts = + // from p in products + // where p.UnitsInStock == 0 + // select p; + + Product p = new Product(); + List soldOutProducts = db.from(p).where(p.unitsInStock).is(0).orderBy(p.productId).select(); + List soldOutProducts2 = db.from(p).where(p.unitsInStock).is(0).orderBy(p.productId).select(p); + + assertEquals("[Chef Anton's Gumbo Mix: 0]", soldOutProducts.toString()); + assertEquals(soldOutProducts.toString(), soldOutProducts2.toString()); + } + + @Test + public void testWhereSimple3() { + + // var expensiveInStockProducts = + // from p in products + // where p.UnitsInStock > 0 + // && p.UnitPrice > 3.00M + // select p; + + Product p = new Product(); + List expensiveInStockProducts = db.from(p).where(p.unitsInStock).exceeds(0).and(p.unitPrice) + .exceeds(30.0).orderBy(p.productId).select(); + + assertEquals("[Northwoods Cranberry Sauce: 6, Mishi Kobe Niku: 29, Ikura: 31]", + expensiveInStockProducts.toString()); + } + + @Test + public void testWhereSimple4() { + + // var waCustomers = + // from c in customers + // where c.Region == "WA" + // select c; + + Customer c = new Customer(); + List waCustomers = db.from(c).where(c.region).is("WA").select(); + + assertEquals("[ALFKI, ANATR]", waCustomers.toString()); + } + + @Test + public void testSelectSimple2() { + + // var productNames = + // from p in products + // select p.ProductName; + + Product p = new Product(); + List productNames = db.from(p).orderBy(p.productId).select(p.productName); + + List products = Product.getList(); + for (int i = 0; i < products.size(); i++) { + assertEquals(products.get(i).productName, productNames.get(i)); + } + } + + /** + * A result set class containing the product name and price. + */ + public static class ProductPrice { + public String productName; + public String category; + @IQColumn(name = "unitPrice") + public Double price; + } + + @Test + public void testAnonymousTypes3() { + + // var productInfos = + // from p in products + // select new { + // p.ProductName, + // p.Category, + // Price = p.UnitPrice + // }; + + final Product p = new Product(); + List productInfos = db.from(p).orderBy(p.productId).select(new ProductPrice() { + { + productName = p.productName; + category = p.category; + price = p.unitPrice; + } + }); + + List products = Product.getList(); + assertEquals(products.size(), productInfos.size()); + for (int i = 0; i < products.size(); i++) { + ProductPrice pr = productInfos.get(i); + Product p2 = products.get(i); + assertEquals(p2.productName, pr.productName); + assertEquals(p2.category, pr.category); + assertEquals(p2.unitPrice, pr.price); + } + } + + /** + * A result set class containing customer data and the order total. + */ + public static class CustOrder { + public String customerId; + public Integer orderId; + public BigDecimal total; + + public String toString() { + return customerId + ":" + orderId + ":" + total; + } + } + + @Test + public void testSelectManyCompoundFrom2() { + + // var orders = + // from c in customers, + // o in c.Orders + // where o.Total < 500.00M + // select new { + // c.CustomerID, + // o.OrderID, + // o.Total + // }; + + final Customer c = new Customer(); + final Order o = new Order(); + List orders = db.from(c).innerJoin(o).on(c.customerId).is(o.customerId).where(o.total) + .lessThan(new BigDecimal("100.00")).orderBy(c.customerId).select(new CustOrder() { + { + customerId = c.customerId; + orderId = o.orderId; + total = o.total; + } + }); + + assertEquals("[ANATR:10308:88.80]", orders.toString()); + } + + @Test + public void testIsNull() { + Product p = new Product(); + String sql = db.from(p).whereTrue(isNull(p.productName)).getSQL(); + assertEquals("SELECT * FROM Product WHERE (" + db.getDialect().prepareColumnName("productName") + + " IS NULL)", sql); + } + + @Test + public void testDelete() { + Product p = new Product(); + int deleted = db.from(p).where(p.productName).like("A%").delete(); + assertEquals(1, deleted); + deleted = db.from(p).delete(); + assertEquals(9, deleted); + db.insertAll(Product.getList()); + db.deleteAll(Product.getList()); + assertEquals(0, db.from(p).selectCount()); + db.insertAll(Product.getList()); + } + + @Test + public void testOrAndNot() { + Product p = new Product(); + String sql = db.from(p).whereTrue(not(isNull(p.productName))).getSQL(); + String productName = db.getDialect().prepareColumnName("productName"); + assertEquals("SELECT * FROM Product WHERE (NOT " + productName + " IS NULL)", sql); + sql = db.from(p).whereTrue(not(isNull(p.productName))).getSQL(); + assertEquals("SELECT * FROM Product WHERE (NOT " + productName + " IS NULL)", sql); + sql = db.from(p).whereTrue(db.test(p.productId).is(1)).getSQL(); + String productId = db.getDialect().prepareColumnName("productId"); + assertEquals("SELECT * FROM Product WHERE ((" + productId + " = ?))", sql); + } + + @Test + public void testLength() { + Product p = new Product(); + List lengths = db.from(p).where(length(p.productName)).lessThan(10) + .selectDistinct(length(p.productName)); + // Formerly used orderBy(1) here, but that is not portable across DBs + Collections.sort(lengths); + assertEquals("[4, 5]", lengths.toString()); + } + + @Test + public void testSum() { + Product p = new Product(); + Number sum = db.from(p).selectFirst(sum(p.unitsInStock)); + assertEquals(323, sum.intValue()); + Double sumPrice = db.from(p).selectFirst(sum(p.unitPrice)); + assertEquals(313.35, sumPrice.doubleValue(), 0.001); + } + + @Test + public void testMinMax() { + Product p = new Product(); + Integer min = db.from(p).selectFirst(min(p.unitsInStock)); + assertEquals(0, min.intValue()); + String minName = db.from(p).selectFirst(min(p.productName)); + assertEquals("Aniseed Syrup", minName); + Double max = db.from(p).selectFirst(max(p.unitPrice)); + assertEquals(97.0, max.doubleValue(), 0.001); + } + + @Test + public void testLike() { + Product p = new Product(); + List aList = db.from(p).where(p.productName).like("Cha%").orderBy(p.productName).select(); + assertEquals("[Chai: 39, Chang: 17]", aList.toString()); + } + + @Test + public void testCount() { + long count = db.from(new Product()).selectCount(); + assertEquals(10, count); + } + + @Test + public void testComplexObject() { + ComplexObject co = new ComplexObject(); + String sql = db.from(co).where(co.id).is(1).and(co.amount).is(1L).and(co.birthday) + .lessThan(new java.util.Date()).and(co.created) + .lessThan(java.sql.Timestamp.valueOf("2005-05-05 05:05:05")).and(co.name).is("hello") + .and(co.time).lessThan(java.sql.Time.valueOf("23:23:23")).and(co.value) + .is(new BigDecimal("1")).getSQL(); + + StringBuilder sb = new StringBuilder(); + sb.append("SELECT * FROM ComplexObject WHERE "); + sb.append(db.getDialect().prepareColumnName("id")); + sb.append(" = ? AND "); + sb.append(db.getDialect().prepareColumnName("amount")); + sb.append(" = ? AND "); + sb.append(db.getDialect().prepareColumnName("birthday")); + sb.append(" < ? AND "); + sb.append(db.getDialect().prepareColumnName("created")); + sb.append(" < ? AND "); + sb.append(db.getDialect().prepareColumnName("name")); + sb.append(" = ? AND "); + sb.append(db.getDialect().prepareColumnName("time")); + sb.append(" < ? AND "); + sb.append(db.getDialect().prepareColumnName("value")); + sb.append(" = ?"); + assertEquals(sb.toString(), sql); + + long count = db.from(co).where(co.id).is(1).and(co.amount).is(1L).and(co.birthday) + .lessThan(new java.util.Date()).and(co.created) + .lessThan(java.sql.Timestamp.valueOf("2005-05-05 05:05:05")).and(co.name).is("hello") + .and(co.time).lessThan(java.sql.Time.valueOf("23:23:23")).and(co.value) + .is(new BigDecimal("1")).selectCount(); + assertEquals(1, count); + } + + @Test + public void testComplexObject2() { + testComplexObject2(1, "hello"); + } + + private void testComplexObject2(final int x, final String name) { + final ComplexObject co = new ComplexObject(); + + String sql = db.from(co).where(new Filter() { + public boolean where() { + return co.id == x && co.name.equals(name) && co.name.equals("hello"); + } + }).getSQL(); + StringBuilder sb = new StringBuilder(); + sb.append("SELECT * FROM ComplexObject WHERE "); + sb.append(db.getDialect().prepareColumnName("id")); + sb.append("=? AND ?="); + sb.append(db.getDialect().prepareColumnName("name")); + sb.append(" AND 'hello'="); + sb.append(db.getDialect().prepareColumnName("name")); + assertEquals(sb.toString(), sql); + + long count = db.from(co).where(new Filter() { + public boolean where() { + return co.id == x && co.name.equals(name) && co.name.equals("hello"); + } + }).selectCount(); + + assertEquals(1, count); + } + + @Test + public void testLimitOffset() { + Set ids = new HashSet(); + Product p = new Product(); + for (int i = 0; i < 5; i++) { + List products = db.from(p).limit(2).offset(2 * i).select(); + assertTrue(products.size() == 2); + for (Product prod : products) { + assertTrue("Failed to add product id. Duplicate?", ids.add(prod.productId)); + } + } + } + + @Test + public void testKeyRetrieval() { + List list = SupportedTypes.createList(); + List keys = db.insertAllAndGetKeys(list); + Set uniqueKeys = new HashSet(); + for (Long l : keys) { + assertTrue("Failed to add key. Duplicate?", uniqueKeys.add(l)); + } + } + + /** + * A result set class containing product groups. + */ + public static class ProductGroup { + public String category; + public Long productCount; + + public String toString() { + return category + ":" + productCount; + } + } + + @Test + public void testGroup() { + + // var orderGroups = + // from p in products + // group p by p.Category into g + // select new { + // Category = g.Key, + // Products = g + // }; + + final Product p = new Product(); + List list = db.from(p).groupBy(p.category).orderBy(p.category) + .select(new ProductGroup() { + { + category = p.category; + productCount = count(); + } + }); + assertEquals("[Beverages:2, Condiments:5, Meat/Poultry:1, Produce:1, Seafood:1]", list.toString()); + } + +} diff --git a/src/test/java/com/iciql/test/TransactionTest.java b/src/test/java/com/iciql/test/TransactionTest.java new file mode 100644 index 0000000..026366e --- /dev/null +++ b/src/test/java/com/iciql/test/TransactionTest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012 Frédéric Gaillard. + * + * 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 java.sql.SQLException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.IciqlException; +import com.iciql.test.models.CategoryAnnotationOnly; +import com.iciql.test.models.ProductAnnotationOnlyWithForeignKey; + +/** + * Tests of transactions. + */ +public class TransactionTest { + + /** + * This object represents a database (actually a connection to the + * database). + */ + + private Db db; + + @Before + public void setUp() { + db = IciqlSuite.openNewDb(); + + // tables creation + db.from(new CategoryAnnotationOnly()); + db.from(new ProductAnnotationOnlyWithForeignKey()); + + startTransactionMode(); + } + + @After + public void tearDown() { + + endTransactionMode(); + + db.dropTable(ProductAnnotationOnlyWithForeignKey.class); + db.dropTable(CategoryAnnotationOnly.class); + db.close(); + } + + @Test + public void testTransaction() { + + // insert in 2 tables inside a transaction + + // insertAll don't use save point in this transaction + db.insertAll(CategoryAnnotationOnly.getList()); + db.insertAll(ProductAnnotationOnlyWithForeignKey.getList()); + + // don't commit changes + try { + db.getConnection().rollback(); + } catch (SQLException e) { + throw new IciqlException(e, "Can't rollback"); + } + + ProductAnnotationOnlyWithForeignKey p = new ProductAnnotationOnlyWithForeignKey(); + long count1 = db.from(p).selectCount(); + + CategoryAnnotationOnly c = new CategoryAnnotationOnly(); + long count2 = db.from(c).selectCount(); + + // verify changes aren't committed + assertEquals(count1, 0L); + assertEquals(count2, 0L); + } + + /** + * Helper to set transaction mode + */ + private void startTransactionMode() { + db.setSkipCreate(true); + db.setAutoSavePoint(false); + + try { + db.getConnection().setAutoCommit(false); + } catch (SQLException e) { + throw new IciqlException(e, "Could not change auto-commit mode"); + } + } + + /** + * Helper to return to initial mode + */ + private void endTransactionMode() { + try { + db.getConnection().setAutoCommit(true); + } catch (SQLException e) { + throw new IciqlException(e, "Could not change auto-commit mode"); + } + // returns to initial states + db.setSkipCreate(false); + db.setAutoSavePoint(true); + } + +} diff --git a/src/test/java/com/iciql/test/UUIDTest.java b/src/test/java/com/iciql/test/UUIDTest.java new file mode 100644 index 0000000..bb09c9f --- /dev/null +++ b/src/test/java/com/iciql/test/UUIDTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2011 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.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQTable; + +/** + * Tests of UUID type. + *

+ * H2 only. + */ +public class UUIDTest { + + Db db; + + @Before + public void setup() { + db = IciqlSuite.openNewDb(); + } + + @After + public void tearDown() { + db.close(); + } + + @Test + public void testUUIDs() throws Exception { + // do not test non-H2 databases + Assume.assumeTrue(IciqlSuite.isH2(db)); + + List originals = UUIDRecord.getList(); + db.insertAll(originals); + UUIDRecord u = new UUIDRecord(); + List retrieved = db.from(u).orderBy(u.id).select(); + assertEquals(originals.size(), retrieved.size()); + for (int i = 0; i < originals.size(); i++) { + UUIDRecord a = originals.get(i); + UUIDRecord b = retrieved.get(i); + assertTrue(a.equivalentTo(b)); + } + + UUIDRecord second = db.from(u).where(u.uuid).is(originals.get(1).uuid).selectFirst(); + assertTrue(originals.get(1).equivalentTo(second)); + db.dropTable(UUIDRecord.class); + } + + /** + * A simple class used in this test. + */ + @IQTable(name = "UUID_TEST") + public static class UUIDRecord { + + @IQColumn(primaryKey = true) + public Integer id; + + @IQColumn() + public UUID uuid; + + public UUIDRecord() { + // public constructor + } + + private UUIDRecord(int id) { + this.id = id; + this.uuid = UUID.randomUUID(); + } + + public boolean equivalentTo(UUIDRecord b) { + boolean same = true; + same &= id == b.id; + same &= uuid.equals(b.uuid); + return same; + } + + public String toString() { + return id + ": " + uuid; + } + + public static List getList() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + list.add(new UUIDRecord(i + 1)); + } + return list; + } + } +} diff --git a/src/test/java/com/iciql/test/UpdateTest.java b/src/test/java/com/iciql/test/UpdateTest.java new file mode 100644 index 0000000..717c23a --- /dev/null +++ b/src/test/java/com/iciql/test/UpdateTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 java.sql.Date.valueOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.test.models.Customer; +import com.iciql.test.models.Order; +import com.iciql.test.models.Product; + +/** + * Tests the Db.update() function. + * + * @author dmoebius at scoop dash gmbh dot de + */ +public class UpdateTest { + + private Db db; + + @Before + public void setUp() throws Exception { + db = IciqlSuite.openNewDb(); + db.insertAll(Product.getList()); + db.insertAll(Customer.getList()); + db.insertAll(Order.getList()); + } + + @After + public void tearDown() { + db.close(); + } + + @Test + public void testSimpleUpdate() { + Product p = new Product(); + Product pChang = db.from(p).where(p.productName).is("Chang").selectFirst(); + // update unitPrice from 19.0 to 19.5 + pChang.unitPrice = 19.5; + // update unitsInStock from 17 to 16 + pChang.unitsInStock = 16; + db.update(pChang); + + Product p2 = new Product(); + Product pChang2 = db.from(p2).where(p2.productName).is("Chang").selectFirst(); + assertEquals(19.5, pChang2.unitPrice.doubleValue(), 0.001); + assertEquals(16, pChang2.unitsInStock.intValue()); + + // undo update + pChang.unitPrice = 19.0; + pChang.unitsInStock = 17; + db.update(pChang); + } + + @Test + public void testSimpleUpdateWithCombinedPrimaryKey() { + Order o = new Order(); + Order ourOrder = db.from(o).where(o.orderDate).is(valueOf("2007-01-02")).selectFirst(); + ourOrder.orderDate = valueOf("2007-01-03"); + db.update(ourOrder); + + Order ourUpdatedOrder = db.from(o).where(o.orderDate).is(valueOf("2007-01-03")).selectFirst(); + assertTrue("updated order not found", ourUpdatedOrder != null); + + // undo update + ourOrder.orderDate = valueOf("2007-01-02"); + db.update(ourOrder); + } + + @Test + public void testSimpleMerge() { + Product p = new Product(); + Product pChang = db.from(p).where(p.productName).is("Chang").selectFirst(); + // update unitPrice from 19.0 to 19.5 + pChang.unitPrice = 19.5; + // update unitsInStock from 17 to 16 + pChang.unitsInStock = 16; + db.merge(pChang); + + Product p2 = new Product(); + Product pChang2 = db.from(p2).where(p2.productName).is("Chang").selectFirst(); + assertEquals(19.5, pChang2.unitPrice, 0.001); + assertEquals(16, pChang2.unitsInStock.intValue()); + + // undo update + pChang.unitPrice = 19.0; + pChang.unitsInStock = 17; + db.merge(pChang); + } + + @Test + public void testSimpleMergeWithCombinedPrimaryKey() { + Order o = new Order(); + Order ourOrder = db.from(o).where(o.orderDate).is(valueOf("2007-01-02")).selectFirst(); + ourOrder.orderDate = valueOf("2007-01-03"); + db.merge(ourOrder); + + Order ourUpdatedOrder = db.from(o).where(o.orderDate).is(valueOf("2007-01-03")).selectFirst(); + assertTrue("updated order not found", ourUpdatedOrder != null); + + // undo update + ourOrder.orderDate = valueOf("2007-01-02"); + db.merge(ourOrder); + } + + @Test + public void testSetColumns() { + Product p = new Product(); + Product original = db.from(p).where(p.productId).is(1).selectFirst(); + + // update string and double columns + db.from(p).set(p.productName).to("updated").increment(p.unitPrice).by(3.14).increment(p.unitsInStock) + .by(2).where(p.productId).is(1).update(); + + // confirm the data was properly updated + Product revised = db.from(p).where(p.productId).is(1).selectFirst(); + assertEquals("updated", revised.productName); + assertEquals(original.unitPrice + 3.14, revised.unitPrice, 0.001); + assertEquals(original.unitsInStock + 2, revised.unitsInStock.intValue()); + + // restore the data + db.from(p).set(p.productName).to(original.productName).set(p.unitPrice).to(original.unitPrice) + .increment(p.unitsInStock).by(-2).where(p.productId).is(1).update(); + + // confirm the data was properly restored + Product restored = db.from(p).where(p.productId).is(1).selectFirst(); + assertEquals(original.productName, restored.productName); + assertEquals(original.unitPrice, restored.unitPrice); + assertEquals(original.unitsInStock, restored.unitsInStock); + + double unitPriceOld = db.from(p).where(p.productId).is(1).selectFirst().unitPrice; + // double the unit price + db.from(p).increment(p.unitPrice).by(p.unitPrice).where(p.productId).is(1).update(); + double unitPriceNew = db.from(p).where(p.productId).is(1).selectFirst().unitPrice; + assertEquals(unitPriceOld * 2, unitPriceNew, 0.001); + + } + +} diff --git a/src/test/java/com/iciql/test/UpgradesTest.java b/src/test/java/com/iciql/test/UpgradesTest.java new file mode 100644 index 0000000..7de691f --- /dev/null +++ b/src/test/java/com/iciql/test/UpgradesTest.java @@ -0,0 +1,180 @@ +/* + * Copyright 2011 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 java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import com.iciql.Db; +import com.iciql.DbUpgrader; +import com.iciql.Iciql.IQVersion; +import com.iciql.test.models.Product; +import com.iciql.test.models.SupportedTypes; +import com.iciql.test.models.SupportedTypes.SupportedTypes2; + +/** + * Tests the database and table upgrade functions. + * + */ +public class UpgradesTest { + + @Test + public void testDatabaseUpgrade() { + Db db = IciqlSuite.openNewDb(); + + List products = Product.getList(); + + // set the v1 upgrader and insert a record. + // this will trigger the upgrade. + V1DbUpgrader v1 = new V1DbUpgrader(); + db.setDbUpgrader(v1); + db.insert(products.get(0)); + + // confirm that upgrade occurred + assertEquals(0, v1.oldVersion.get()); + assertEquals(1, v1.newVersion.get()); + + // open a second connection to the database + // and then apply the v2 upgrade. + // For H2 its important to keep the first connection + // alive so that the database is not destroyed. + Db db2 = IciqlSuite.openCurrentDb(); + + // set the v2 upgrader and insert a record. + // this will trigger the upgrade. + V2DbUpgrader v2 = new V2DbUpgrader(); + db2.setDbUpgrader(v2); + db2.insert(products.get(1)); + + // confirm that upgrade occurred + assertEquals(1, v2.oldVersion.get()); + assertEquals(2, v2.newVersion.get()); + + db.executeUpdate("DROP TABLE iq_versions"); + db.close(); + db2.close(); + } + + @Test + public void testDatabaseInheritedUpgrade() { + Db db = IciqlSuite.openNewDb(); + + List products = Product.getList(); + + // set the v1 upgrader and insert a record. + // this will trigger the upgrade. + V1DbUpgrader v1 = new V1DbUpgrader(); + db.setDbUpgrader(v1); + db.insert(products.get(0)); + + // confirm that upgrade occurred + assertEquals(0, v1.oldVersion.get()); + assertEquals(1, v1.newVersion.get()); + + // open a second connection to the database + // and then apply the v2 upgrade. + // For H2 its important to keep the first connection + // alive so that the database is not destroyed. + Db db2 = IciqlSuite.openCurrentDb(); + + // set the v2 upgrader and insert a record. + // this will trigger the upgrade. + V2DbUpgraderImpl v2 = new V2DbUpgraderImpl(); + db2.setDbUpgrader(v2); + db2.insert(products.get(1)); + + // confirm that upgrade occurred + assertEquals(1, v2.oldVersion.get()); + assertEquals(2, v2.newVersion.get()); + + db.executeUpdate("DROP TABLE iq_versions"); + db.close(); + db2.close(); + } + + @Test + public void testTableUpgrade() { + Db db = IciqlSuite.openNewDb(); + + // insert first, this will create version record automatically + List original = SupportedTypes.createList(); + db.insertAll(original); + + // reset the dbUpgrader (clears the update check cache) + V2DbUpgrader dbUpgrader = new V2DbUpgrader(); + db.setDbUpgrader(dbUpgrader); + + SupportedTypes2 s2 = new SupportedTypes2(); + + List types = db.from(s2).select(); + assertEquals(10, types.size()); + assertEquals(1, dbUpgrader.oldVersion.get()); + assertEquals(2, dbUpgrader.newVersion.get()); + db.executeUpdate("DROP TABLE iq_versions"); + db.close(); + } + + /** + * A sample database upgrader class. + */ + class BaseDbUpgrader implements DbUpgrader { + final AtomicInteger oldVersion = new AtomicInteger(0); + final AtomicInteger newVersion = new AtomicInteger(0); + + public boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion) { + // just claims success on upgrade request + oldVersion.set(fromVersion); + newVersion.set(toVersion); + return true; + } + + public boolean upgradeDatabase(Db db, int fromVersion, int toVersion) { + // just claims success on upgrade request + oldVersion.set(fromVersion); + newVersion.set(toVersion); + return true; + } + } + + /** + * A sample V1 database upgrader class. + */ + @IQVersion(1) + class V1DbUpgrader extends BaseDbUpgrader { + } + + /** + * A sample V2 database upgrader class. + */ + @IQVersion(2) + class V2DbUpgrader extends BaseDbUpgrader { + } + + + /** + * A sample V2 database upgrader class which inherits its + * version from the parent class. + */ + @IQVersion() + class V2DbUpgraderImpl extends V2DbUpgrader { + } + +} diff --git a/src/test/java/com/iciql/test/ViewsTest.java b/src/test/java/com/iciql/test/ViewsTest.java new file mode 100644 index 0000000..be2e085 --- /dev/null +++ b/src/test/java/com/iciql/test/ViewsTest.java @@ -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 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 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 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 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/src/test/java/com/iciql/test/models/BooleanModel.java b/src/test/java/com/iciql/test/models/BooleanModel.java new file mode 100644 index 0000000..d22e3c1 --- /dev/null +++ b/src/test/java/com/iciql/test/models/BooleanModel.java @@ -0,0 +1,73 @@ +/* + * Copyright 2011 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 java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQTable; + +/** + * Boolean types model. + */ +@IQTable(name = "BooleanTest") +public class BooleanModel { + + @IQColumn(primaryKey = true) + public Integer id; + + @IQColumn + public Boolean mybool; + + public BooleanModel() { + } + + BooleanModel(int id, boolean val) { + this.id = id; + this.mybool = val; + } + + public static List getList() { + return Arrays.asList(new BooleanModel(1, true), new BooleanModel(2, false), + new BooleanModel(3, true), new BooleanModel(4, false)); + } + + /** + * Test boolean as int + */ + @IQTable(name = "BooleanTest") + public static class BooleanAsIntModel { + @IQColumn(primaryKey = true) + public Integer id; + + @IQColumn + public Integer mybool; + + public BooleanAsIntModel() { + } + + BooleanAsIntModel(int id, boolean val) { + this.id = id; + this.mybool = val ? 1 : 0; + } + + public static List getList() { + return Arrays.asList(new BooleanAsIntModel(1, true), new BooleanAsIntModel(2, false), + new BooleanAsIntModel(3, true), new BooleanAsIntModel(4, false)); + } + } +} diff --git a/src/test/java/com/iciql/test/models/CategoryAnnotationOnly.java b/src/test/java/com/iciql/test/models/CategoryAnnotationOnly.java new file mode 100644 index 0000000..b96ab04 --- /dev/null +++ b/src/test/java/com/iciql/test/models/CategoryAnnotationOnly.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012 Frédéric Gaillard. + * 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 java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQContraintUnique; +import com.iciql.Iciql.IQTable; + +/** + * A table containing category data. + */ + +@IQTable(name = "AnnotatedCategory", primaryKey = "id") +// @IQIndex(value = "categ", type=IndexType.UNIQUE) +@IQContraintUnique(uniqueColumns = { "categ" }) +public class CategoryAnnotationOnly { + + @IQColumn(name = "id", autoIncrement = true) + public Long categoryId; + + @IQColumn(name = "categ", length = 15, trim = true) + public String category; + + public CategoryAnnotationOnly() { + // public constructor + } + + private CategoryAnnotationOnly(long categoryId, String category) { + this.categoryId = categoryId; + this.category = category; + } + + private static CategoryAnnotationOnly create(int categoryId, String category) { + return new CategoryAnnotationOnly(categoryId, category); + } + + public static List getList() { + CategoryAnnotationOnly[] list = { + create(1, "Beverages"), + create(2, "Condiments"), + create(3, "Produce"), + create(4, "Meat/Poultry"), + create(5,"Seafood") + }; + return Arrays.asList(list); + } + + public String toString() { + return category; + } + +} diff --git a/src/test/java/com/iciql/test/models/ComplexObject.java b/src/test/java/com/iciql/test/models/ComplexObject.java new file mode 100644 index 0000000..ce9b9ec --- /dev/null +++ b/src/test/java/com/iciql/test/models/ComplexObject.java @@ -0,0 +1,66 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 static com.iciql.Define.length; +import static com.iciql.Define.primaryKey; + +import java.math.BigDecimal; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import com.iciql.Iciql; + +/** + * A table containing all possible data types. + */ + +public class ComplexObject implements Iciql { + public Integer id; + public Long amount; + public String name; + public BigDecimal value; + public Date birthday; + public Time time; + public Timestamp created; + + static ComplexObject build(Integer id, boolean isNull) { + ComplexObject obj = new ComplexObject(); + obj.id = id; + obj.amount = isNull ? null : Long.valueOf(1); + obj.name = isNull ? null : "hello"; + obj.value = isNull ? null : new BigDecimal("1"); + obj.birthday = isNull ? null : java.sql.Date.valueOf("2001-01-01"); + obj.time = isNull ? null : Time.valueOf("10:20:30"); + obj.created = isNull ? null : Timestamp.valueOf("2002-02-02 02:02:02"); + return obj; + } + + public void defineIQ() { + primaryKey(id); + length(name, 25); + } + + public static List getList() { + return Arrays.asList(new ComplexObject[] { build(0, true), build(1, false) }); + } + +} diff --git a/src/test/java/com/iciql/test/models/Customer.java b/src/test/java/com/iciql/test/models/Customer.java new file mode 100644 index 0000000..ae8e40d --- /dev/null +++ b/src/test/java/com/iciql/test/models/Customer.java @@ -0,0 +1,57 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQTable; + +/** + * A table containing customer data. + */ +@IQTable +public class Customer { + + @IQColumn(length = 25) + public String customerId; + + @IQColumn(length = 2) + public String region; + + public Customer() { + // public constructor + } + + public Customer(String customerId, String region) { + this.customerId = customerId; + this.region = region; + } + + public String toString() { + return customerId; + } + + public static List getList() { + Customer[] list = { new Customer("ALFKI", "WA"), new Customer("ANATR", "WA"), + new Customer("ANTON", "CA") }; + return Arrays.asList(list); + } + +} diff --git a/src/test/java/com/iciql/test/models/DefaultValuesModel.java b/src/test/java/com/iciql/test/models/DefaultValuesModel.java new file mode 100644 index 0000000..cb3b421 --- /dev/null +++ b/src/test/java/com/iciql/test/models/DefaultValuesModel.java @@ -0,0 +1,58 @@ +/* + * Copyright 2011 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 java.util.Date; + +import com.iciql.Iciql.EnumType; +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQEnum; +import com.iciql.Iciql.IQTable; +import com.iciql.test.models.EnumModels.Tree; + +/** + * Default values model. + */ +@IQTable(name = "DefaultValuesTest") +public class DefaultValuesModel { + + @IQColumn(primaryKey = true, autoIncrement = true) + public Long myLong; + + @SuppressWarnings("deprecation") + @IQColumn + public Date myDate = new Date(100, 7, 1); + + @IQColumn + public Integer myInteger = 12345; + + @IQColumn + public Tree myEnumIdTree = Tree.WALNUT; + + @IQColumn + @IQEnum(EnumType.NAME) + public Tree myNameTree = Tree.MAPLE; + + @IQColumn + @IQEnum(EnumType.ORDINAL) + public Tree myOrdinalTree = Tree.PINE; + + @IQColumn(nullable = true) + public Tree myNullTree; + + public DefaultValuesModel() { + } +} diff --git a/src/test/java/com/iciql/test/models/EnumModels.java b/src/test/java/com/iciql/test/models/EnumModels.java new file mode 100644 index 0000000..a865f6c --- /dev/null +++ b/src/test/java/com/iciql/test/models/EnumModels.java @@ -0,0 +1,157 @@ +/* + * Copyright 2011 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 java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql.EnumId; +import com.iciql.Iciql.EnumType; +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQEnum; +import com.iciql.Iciql.IQTable; + +/** + * Container for reusable enum model classes which exercise the 3 supported + * types. + */ +public abstract class EnumModels { + + /** + * Test of @IQEnum annotated enumeration. This strategy is the default + * strategy for all fields of the Tree enum. + * + * Individual Tree field declarations can override this strategy by + * specifying a different @IQEnum annotation. + * + * Here ORDINAL specifies that this enum will be mapped to an INT column. + */ + @IQEnum(EnumType.ENUMID) + public enum Tree implements EnumId { + PINE(10), OAK(20), BIRCH(30), WALNUT(40), MAPLE(50); + + private int enumid; + + Tree(int id) { + this.enumid = id; + } + + @Override + public int enumId() { + return enumid; + } + } + + @IQColumn(primaryKey = true) + public Integer id; + + public abstract Tree tree(); + + /** + * Test model for enum-as-enumid. + */ + @IQTable(inheritColumns = true) + public static class EnumIdModel extends EnumModels { + + // no need to specify ENUMID type as the enumeration definition + // specifies it. + @IQColumn + private Tree tree; + + public EnumIdModel() { + } + + public EnumIdModel(int id, Tree tree) { + this.id = id; + this.tree = tree; + } + + @Override + public Tree tree() { + return tree; + } + + public static List createList() { + return Arrays.asList(new EnumIdModel(400, Tree.WALNUT), new EnumIdModel(200, Tree.OAK), + new EnumIdModel(500, Tree.MAPLE), new EnumIdModel(300, Tree.BIRCH), new EnumIdModel(100, + Tree.PINE)); + } + } + + /** + * Test model for enum-as-ordinal. + */ + @IQTable(inheritColumns = true) + public static class EnumOrdinalModel extends EnumModels { + + // override the enumtype to ordinal + @IQEnum(EnumType.ORDINAL) + @IQColumn + private Tree tree; + + public EnumOrdinalModel() { + } + + public EnumOrdinalModel(int id, Tree tree) { + this.id = id; + this.tree = tree; + } + + @Override + public Tree tree() { + return tree; + } + + public static List createList() { + return Arrays.asList(new EnumOrdinalModel(400, Tree.WALNUT), new EnumOrdinalModel(200, Tree.OAK), + new EnumOrdinalModel(500, Tree.MAPLE), new EnumOrdinalModel(300, Tree.BIRCH), + new EnumOrdinalModel(100, Tree.PINE)); + } + } + + /** + * Test model for enum-as-string. + */ + @IQTable(inheritColumns = true) + public static class EnumStringModel extends EnumModels { + + // override the enumtype to string + // ensure that we specify a length so that the column is VARCHAR + @IQEnum(EnumType.NAME) + @IQColumn(length = 25) + private Tree tree; + + public EnumStringModel() { + } + + public EnumStringModel(int id, Tree tree) { + this.id = id; + this.tree = tree; + } + + @Override + public Tree tree() { + return tree; + } + + public static List createList() { + return Arrays.asList(new EnumStringModel(400, Tree.WALNUT), new EnumStringModel(200, Tree.OAK), + new EnumStringModel(500, Tree.MAPLE), new EnumStringModel(300, Tree.BIRCH), + new EnumStringModel(100, Tree.PINE)); + } + } +} diff --git a/src/test/java/com/iciql/test/models/MultipleBoolsModel.java b/src/test/java/com/iciql/test/models/MultipleBoolsModel.java new file mode 100644 index 0000000..7bc429c --- /dev/null +++ b/src/test/java/com/iciql/test/models/MultipleBoolsModel.java @@ -0,0 +1,40 @@ +package com.iciql.test.models; + +import java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQTable; + +/** + * Model class to test the runtime exception of too many primitive boolean + * fields in the model. + * + * @author James Moger + * + */ +@IQTable +public class MultipleBoolsModel { + + @IQColumn(autoIncrement = true, primaryKey = true) + public int id; + + @IQColumn + public boolean a; + + @IQColumn + public boolean b; + + public MultipleBoolsModel() { + } + + public MultipleBoolsModel(boolean a, boolean b) { + this.a = a; + this.b = b; + } + + public static List getList() { + return Arrays.asList(new MultipleBoolsModel(true, true), new MultipleBoolsModel(true, false), + new MultipleBoolsModel(true, false), new MultipleBoolsModel(false, false)); + } +} \ No newline at end of file diff --git a/src/test/java/com/iciql/test/models/Order.java b/src/test/java/com/iciql/test/models/Order.java new file mode 100644 index 0000000..1fa9097 --- /dev/null +++ b/src/test/java/com/iciql/test/models/Order.java @@ -0,0 +1,73 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 static com.iciql.Define.length; +import static com.iciql.Define.primaryKey; +import static com.iciql.Define.scale; +import static com.iciql.Define.tableName; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import com.iciql.Iciql; + +/** + * A table containing order data. + */ + +public class Order implements Iciql { + public String customerId; + public Integer orderId; + public Date orderDate; + public BigDecimal total; + + public Order(String customerId, Integer orderId, String total, String orderDate) { + this.customerId = customerId; + this.orderId = orderId; + this.total = new BigDecimal(total); + this.orderDate = java.sql.Date.valueOf(orderDate); + } + + public Order() { + // public constructor + } + + public void defineIQ() { + tableName("Orders"); + length(customerId, 25); + length(total, 10); + scale(total, 2); + primaryKey(customerId, orderId); + } + + public static List getList() { + Order[] list = { new Order("ALFKI", 10702, "330.00", "2007-01-02"), + new Order("ALFKI", 10952, "471.20", "2007-02-03"), + new Order("ANATR", 10308, "88.80", "2007-01-03"), + new Order("ANATR", 10625, "479.75", "2007-03-03"), + new Order("ANATR", 10759, "320.00", "2007-04-01"), + new Order("ANTON", 10365, "403.20", "2007-02-13"), + new Order("ANTON", 10682, "375.50", "2007-03-13"), + new Order("ANTON", 10355, "480.00", "2007-04-11") }; + return Arrays.asList(list); + } + +} diff --git a/src/test/java/com/iciql/test/models/PrimitivesModel.java b/src/test/java/com/iciql/test/models/PrimitivesModel.java new file mode 100644 index 0000000..44e8b9b --- /dev/null +++ b/src/test/java/com/iciql/test/models/PrimitivesModel.java @@ -0,0 +1,90 @@ +/* + * Copyright 2011 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 java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQTable; +import com.iciql.test.IciqlSuite; + +/** + * Primitive types model. + */ +@IQTable(name = "PrimitivesTest") +public class PrimitivesModel { + + @IQColumn(primaryKey = true) + public long myLong; + + @IQColumn + public int myInteger; + + @IQColumn + public short myShort; + + @IQColumn + public byte myByte; + + @IQColumn + public boolean myBoolean; + + @IQColumn + public double myDouble; + + @IQColumn + public float myFloat; + + public PrimitivesModel() { + Random rand = new Random(); + myLong = rand.nextLong(); + myInteger = rand.nextInt(); + myShort = (short) rand.nextInt(Short.MAX_VALUE); + myByte = (byte) rand.nextInt(Byte.MAX_VALUE); + myBoolean = rand.nextInt(1) == 1; + myDouble = rand.nextDouble(); + myFloat = rand.nextFloat(); + } + + public boolean equivalentTo(PrimitivesModel p) { + boolean same = true; + same &= myLong == p.myLong; + same &= myInteger == p.myInteger; + same &= myShort == p.myShort; + same &= myByte == p.myByte; + same &= myBoolean == p.myBoolean; + same &= IciqlSuite.equivalentTo(myDouble, p.myDouble); + same &= IciqlSuite.equivalentTo(myFloat, p.myFloat); + return same; + } + + public static List getList() { + List list = new ArrayList(); + for (int i = 1; i <= 10; i++) { + PrimitivesModel p = new PrimitivesModel(); + p.myLong = i; + list.add(p); + } + return list; + } + + @Override + public String toString() { + return String.valueOf(myLong); + } +} diff --git a/src/test/java/com/iciql/test/models/Product.java b/src/test/java/com/iciql/test/models/Product.java new file mode 100644 index 0000000..241a3d3 --- /dev/null +++ b/src/test/java/com/iciql/test/models/Product.java @@ -0,0 +1,85 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 static com.iciql.Define.index; +import static com.iciql.Define.length; +import static com.iciql.Define.primaryKey; +import static com.iciql.Define.tableName; + +import java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql; + +/** + * A table containing product data. + */ + +public class Product implements Iciql { + + public Integer productId; + public String productName; + public String category; + public Double unitPrice; + public Integer unitsInStock; + + public Product() { + // public constructor + } + + private Product(int productId, String productName, String category, double unitPrice, int unitsInStock) { + this.productId = productId; + this.productName = productName; + this.category = category; + this.unitPrice = unitPrice; + this.unitsInStock = unitsInStock; + } + + public void defineIQ() { + tableName("Product"); + primaryKey(productId); + length(productName, 255); + length(category, 255); + index("MyIndex", IndexType.STANDARD, productName, category); + } + + private static Product create(int productId, String productName, String category, double unitPrice, + int unitsInStock) { + return new Product(productId, productName, category, unitPrice, unitsInStock); + } + + public static List getList() { + Product[] list = { create(1, "Chai", "Beverages", 18, 39), create(2, "Chang", "Beverages", 19.0, 17), + create(3, "Aniseed Syrup", "Condiments", 10.0, 13), + create(4, "Chef Anton's Cajun Seasoning", "Condiments", 22.0, 53), + create(5, "Chef Anton's Gumbo Mix", "Condiments", 21.3500, 0), + create(6, "Grandma's Boysenberry Spread", "Condiments", 25.0, 120), + create(7, "Uncle Bob's Organic Dried Pears", "Produce", 30.0, 15), + create(8, "Northwoods Cranberry Sauce", "Condiments", 40.0, 6), + create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29), + create(10, "Ikura", "Seafood", 31.0, 31), }; + + return Arrays.asList(list); + } + + public String toString() { + return productName + ": " + unitsInStock; + } + +} diff --git a/src/test/java/com/iciql/test/models/ProductAnnotationOnly.java b/src/test/java/com/iciql/test/models/ProductAnnotationOnly.java new file mode 100644 index 0000000..1f6b4e2 --- /dev/null +++ b/src/test/java/com/iciql/test/models/ProductAnnotationOnly.java @@ -0,0 +1,94 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQIndex; +import com.iciql.Iciql.IQIndexes; +import com.iciql.Iciql.IQTable; +import com.iciql.Iciql.IndexType; + +/** + * A table containing product data. + */ + +@IQTable(name = "AnnotatedProduct", primaryKey = "id") +@IQIndexes({ @IQIndex({ "name", "cat" }), @IQIndex(name = "nameidx", type = IndexType.HASH, value = "name") }) +public class ProductAnnotationOnly { + + public String unmappedField; + + @IQColumn(name = "id", autoIncrement = true) + public Long productId; + + @IQColumn(name = "cat", length = 15, trim = true) + public String category; + + @IQColumn(name = "name", length = 50) + public String productName; + + @SuppressWarnings("unused") + @IQColumn + private Double unitPrice; + + @IQColumn + private Integer unitsInStock; + + public ProductAnnotationOnly() { + // public constructor + } + + private ProductAnnotationOnly(long productId, String productName, String category, double unitPrice, + int unitsInStock, String unmappedField) { + this.productId = productId; + this.productName = productName; + this.category = category; + this.unitPrice = unitPrice; + this.unitsInStock = unitsInStock; + this.unmappedField = unmappedField; + } + + private static ProductAnnotationOnly create(int productId, String productName, String category, + double unitPrice, int unitsInStock, String unmappedField) { + return new ProductAnnotationOnly(productId, productName, category, unitPrice, unitsInStock, + unmappedField); + } + + public static List getList() { + String unmappedField = "unmapped"; + ProductAnnotationOnly[] list = { create(1, "Chai", "Beverages", 18, 39, unmappedField), + create(2, "Chang", "Beverages", 19.0, 17, unmappedField), + create(3, "Aniseed Syrup", "Condiments", 10.0, 13, unmappedField), + create(4, "Chef Anton's Cajun Seasoning", "Condiments", 22.0, 53, unmappedField), + create(5, "Chef Anton's Gumbo Mix", "Condiments", 21.3500, 0, unmappedField), + create(6, "Grandma's Boysenberry Spread", "Condiments", 25.0, 120, unmappedField), + create(7, "Uncle Bob's Organic Dried Pears", "Produce", 30.0, 15, unmappedField), + create(8, "Northwoods Cranberry Sauce", "Condiments", 40.0, 6, unmappedField), + create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29, unmappedField), + create(10, "Ikura", "Seafood", 31.0, 31, unmappedField), }; + return Arrays.asList(list); + } + + public String toString() { + return productName + ": " + unitsInStock; + } + +} diff --git a/src/test/java/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java b/src/test/java/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java new file mode 100644 index 0000000..4268a89 --- /dev/null +++ b/src/test/java/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012 Frédéric Gaillard. + * 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 java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql.ConstraintDeleteType; +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQContraintForeignKey; +import com.iciql.Iciql.IQIndex; +import com.iciql.Iciql.IQIndexes; +import com.iciql.Iciql.IQTable; +import com.iciql.Iciql.IndexType; + +/** + * A table containing product data. + */ + +@IQTable(name = "AnnotatedProduct", primaryKey = "id") +@IQIndexes({ @IQIndex({ "name", "cat" }), @IQIndex(name = "nameidx", type = IndexType.HASH, value = "name") }) +@IQContraintForeignKey( + foreignColumns= { "cat" }, + referenceName = "AnnotatedCategory", + referenceColumns = { "categ" }, + deleteType = ConstraintDeleteType.CASCADE +) +public class ProductAnnotationOnlyWithForeignKey { + + public String unmappedField; + + @IQColumn(name = "id", autoIncrement = true) + public Long productId; + + @IQColumn(name = "cat", length = 15, trim = true) + public String category; + + @IQColumn(name = "name", length = 50) + public String productName; + + @SuppressWarnings("unused") + @IQColumn + private Double unitPrice; + + @IQColumn + private Integer unitsInStock; + + public ProductAnnotationOnlyWithForeignKey() { + // public constructor + } + + private ProductAnnotationOnlyWithForeignKey(long productId, String productName, String category, double unitPrice, + int unitsInStock, String unmappedField) { + this.productId = productId; + this.productName = productName; + this.category = category; + this.unitPrice = unitPrice; + this.unitsInStock = unitsInStock; + this.unmappedField = unmappedField; + } + + private static ProductAnnotationOnlyWithForeignKey create(int productId, String productName, String category, + double unitPrice, int unitsInStock, String unmappedField) { + return new ProductAnnotationOnlyWithForeignKey(productId, productName, category, unitPrice, unitsInStock, + unmappedField); + } + + public static List getList() { + String unmappedField = "unmapped"; + ProductAnnotationOnlyWithForeignKey[] list = { create(1, "Chai", "Beverages", 18, 39, unmappedField), + create(2, "Chang", "Beverages", 19.0, 17, unmappedField), + create(3, "Aniseed Syrup", "Condiments", 10.0, 13, unmappedField), + create(4, "Chef Anton's Cajun Seasoning", "Condiments", 22.0, 53, unmappedField), + create(5, "Chef Anton's Gumbo Mix", "Condiments", 21.3500, 0, unmappedField), + create(6, "Grandma's Boysenberry Spread", "Condiments", 25.0, 120, unmappedField), + create(7, "Uncle Bob's Organic Dried Pears", "Produce", 30.0, 15, unmappedField), + create(8, "Northwoods Cranberry Sauce", "Condiments", 40.0, 6, unmappedField), + create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29, unmappedField), + create(10, "Ikura", "Seafood", 31.0, 31, unmappedField), }; + return Arrays.asList(list); + } + + public String toString() { + return productName + ": " + unitsInStock; + } + +} diff --git a/src/test/java/com/iciql/test/models/ProductInheritedAnnotation.java b/src/test/java/com/iciql/test/models/ProductInheritedAnnotation.java new file mode 100644 index 0000000..112f4ef --- /dev/null +++ b/src/test/java/com/iciql/test/models/ProductInheritedAnnotation.java @@ -0,0 +1,64 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql.IQTable; + +/** + * This class inherits all its fields from a parent class which has annotated + * columns. The IQTable annotation of the parent class is ignored and only the + * IQTable annotation of this class matters. However, this table inherits + * IQColumns from its super class. + */ +@IQTable(inheritColumns = true, annotationsOnly = false) +public class ProductInheritedAnnotation extends ProductMixedAnnotation { + + public ProductInheritedAnnotation() { + // public constructor + } + + private ProductInheritedAnnotation(int productId, String productName, String category, double unitPrice, + int unitsInStock, String mappedField) { + super(productId, productName, category, unitPrice, unitsInStock, mappedField); + } + + private static ProductInheritedAnnotation create(int productId, String productName, String category, + double unitPrice, int unitsInStock, String mappedField) { + return new ProductInheritedAnnotation(productId, productName, category, unitPrice, unitsInStock, + mappedField); + } + + public static List getData() { + String mappedField = "mapped"; + ProductInheritedAnnotation[] list = { create(1, "Chai", "Beverages", 18, 39, mappedField), + create(2, "Chang", "Beverages", 19.0, 17, mappedField), + create(3, "Aniseed Syrup", "Condiments", 10.0, 13, mappedField), + create(4, "Chef Anton's Cajun Seasoning", "Condiments", 22.0, 53, mappedField), + create(5, "Chef Anton's Gumbo Mix", "Condiments", 21.3500, 0, mappedField), + create(6, "Grandma's Boysenberry Spread", "Condiments", 25.0, 120, mappedField), + create(7, "Uncle Bob's Organic Dried Pears", "Produce", 30.0, 15, mappedField), + create(8, "Northwoods Cranberry Sauce", "Condiments", 40.0, 6, mappedField), + create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29, mappedField), + create(10, "Ikura", "Seafood", 31.0, 31, mappedField), }; + return Arrays.asList(list); + } + +} diff --git a/src/test/java/com/iciql/test/models/ProductMixedAnnotation.java b/src/test/java/com/iciql/test/models/ProductMixedAnnotation.java new file mode 100644 index 0000000..a893a69 --- /dev/null +++ b/src/test/java/com/iciql/test/models/ProductMixedAnnotation.java @@ -0,0 +1,104 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 java.util.Arrays; +import java.util.List; + +import com.iciql.Define; +import com.iciql.Iciql; +import com.iciql.Iciql.IQIndex; +import com.iciql.Iciql.IQTable; + +/** + * A table containing product data. + */ + +@IQTable(annotationsOnly = false) +@IQIndex({ "name", "cat" }) +public class ProductMixedAnnotation implements Iciql { + + public Double unitPrice; + public Integer unitsInStock; + public String mappedField; + + @IQIgnore + public String productDescription; + + @IQColumn(name = "cat", length = 255) + public String category; + + @IQColumn(name = "id", primaryKey = true) + private Integer productId; + + @IQColumn(name = "name", length = 255) + private String productName; + + public ProductMixedAnnotation() { + // public constructor + } + + protected ProductMixedAnnotation(int productId, String productName, String category, double unitPrice, + int unitsInStock, String mappedField) { + this.productId = productId; + this.productName = productName; + this.category = category; + this.unitPrice = unitPrice; + this.unitsInStock = unitsInStock; + this.mappedField = mappedField; + this.productDescription = category + ": " + productName; + } + + private static ProductMixedAnnotation create(int productId, String productName, String category, + double unitPrice, int unitsInStock, String mappedField) { + return new ProductMixedAnnotation(productId, productName, category, unitPrice, unitsInStock, + mappedField); + } + + public static List getList() { + String mappedField = "mapped"; + ProductMixedAnnotation[] list = { create(1, "Chai", "Beverages", 18, 39, mappedField), + create(2, "Chang", "Beverages", 19.0, 17, mappedField), + create(3, "Aniseed Syrup", "Condiments", 10.0, 13, mappedField), + create(4, "Chef Anton's Cajun Seasoning", "Condiments", 22.0, 53, mappedField), + create(5, "Chef Anton's Gumbo Mix", "Condiments", 21.3500, 0, mappedField), + create(6, "Grandma's Boysenberry Spread", "Condiments", 25.0, 120, mappedField), + create(7, "Uncle Bob's Organic Dried Pears", "Produce", 30.0, 15, mappedField), + create(8, "Northwoods Cranberry Sauce", "Condiments", 40.0, 6, mappedField), + create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29, mappedField), + create(10, "Ikura", "Seafood", 31.0, 31, mappedField), }; + return Arrays.asList(list); + } + + public String toString() { + return productName + ": " + unitsInStock; + } + + public int id() { + return productId; + } + + public String name() { + return productName; + } + + @Override + public void defineIQ() { + Define.length(mappedField, 25); + } +} diff --git a/src/test/java/com/iciql/test/models/ProductNoCreateTable.java b/src/test/java/com/iciql/test/models/ProductNoCreateTable.java new file mode 100644 index 0000000..cbf96e9 --- /dev/null +++ b/src/test/java/com/iciql/test/models/ProductNoCreateTable.java @@ -0,0 +1,59 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 java.util.Arrays; +import java.util.List; + +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQTable; + +/** + * A table containing product data. + */ + +@IQTable(create = false) +public class ProductNoCreateTable { + + @SuppressWarnings("unused") + @IQColumn(name = "id") + private Integer productId; + + @SuppressWarnings("unused") + @IQColumn(name = "name") + private String productName; + + public ProductNoCreateTable() { + // public constructor + } + + private ProductNoCreateTable(int productId, String productName) { + this.productId = productId; + this.productName = productName; + } + + private static ProductNoCreateTable create(int productId, String productName) { + return new ProductNoCreateTable(productId, productName); + } + + public static List getList() { + ProductNoCreateTable[] list = { create(1, "Chai"), create(2, "Chang") }; + return Arrays.asList(list); + } + +} diff --git a/src/test/java/com/iciql/test/models/ProductView.java b/src/test/java/com/iciql/test/models/ProductView.java new file mode 100644 index 0000000..2efe9eb --- /dev/null +++ b/src/test/java/com/iciql/test/models/ProductView.java @@ -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/src/test/java/com/iciql/test/models/ProductViewFromQuery.java b/src/test/java/com/iciql/test/models/ProductViewFromQuery.java new file mode 100644 index 0000000..2f2f194 --- /dev/null +++ b/src/test/java/com/iciql/test/models/ProductViewFromQuery.java @@ -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/src/test/java/com/iciql/test/models/ProductViewInherited.java b/src/test/java/com/iciql/test/models/ProductViewInherited.java new file mode 100644 index 0000000..e9c274b --- /dev/null +++ b/src/test/java/com/iciql/test/models/ProductViewInherited.java @@ -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/src/test/java/com/iciql/test/models/ProductViewInheritedComplex.java b/src/test/java/com/iciql/test/models/ProductViewInheritedComplex.java new file mode 100644 index 0000000..55e7ba8 --- /dev/null +++ b/src/test/java/com/iciql/test/models/ProductViewInheritedComplex.java @@ -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 { + +} diff --git a/src/test/java/com/iciql/test/models/StaticQueries.java b/src/test/java/com/iciql/test/models/StaticQueries.java new file mode 100644 index 0000000..09f84e6 --- /dev/null +++ b/src/test/java/com/iciql/test/models/StaticQueries.java @@ -0,0 +1,87 @@ +/* + * Copyright 2011 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 java.sql.Timestamp; + +import com.iciql.Iciql.EnumType; +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQEnum; +import com.iciql.Iciql.IQTable; +import com.iciql.test.models.EnumModels.Tree; + +/** + * Static query models. + */ +public class StaticQueries { + + @IQTable(name = "StaticQueryTest1") + public static class StaticModel1 { + + @IQColumn(primaryKey = true, autoIncrement = true) + public Integer id; + + @IQColumn + @IQEnum(EnumType.NAME) + public Tree myTree; + + @IQColumn + public String myString; + + @IQColumn + public Boolean myBool; + + @IQColumn + public Timestamp myTimestamp; + + @IQColumn + public java.sql.Date myDate; + + @IQColumn + public java.sql.Time myTime; + + public StaticModel1() { + } + } + + @IQTable(name = "StaticQueryTest2") + public static class StaticModel2 { + + @IQColumn(primaryKey = true, autoIncrement = true) + public Integer id; + + @IQColumn + @IQEnum(EnumType.ENUMID) + public Tree myTree; + + public StaticModel2() { + } + } + + @IQTable(name = "StaticQueryTest3") + public static class StaticModel3 { + + @IQColumn(primaryKey = true, autoIncrement = true) + public Integer id; + + @IQColumn + @IQEnum(EnumType.ORDINAL) + public Tree myTree; + + public StaticModel3() { + } + } +} diff --git a/src/test/java/com/iciql/test/models/SupportedTypes.java b/src/test/java/com/iciql/test/models/SupportedTypes.java new file mode 100644 index 0000000..1aaa833 --- /dev/null +++ b/src/test/java/com/iciql/test/models/SupportedTypes.java @@ -0,0 +1,199 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 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 java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import com.iciql.Iciql.EnumType; +import com.iciql.Iciql.IQColumn; +import com.iciql.Iciql.IQEnum; +import com.iciql.Iciql.IQIndex; +import com.iciql.Iciql.IQIndexes; +import com.iciql.Iciql.IQTable; +import com.iciql.Iciql.IQVersion; +import com.iciql.Iciql.IndexType; +import com.iciql.test.IciqlSuite; +import com.iciql.test.models.EnumModels.Tree; +import com.iciql.util.Utils; + +/** + * A data class that contains a column for each data type. + */ +@IQTable +@IQIndexes({ @IQIndex({ "myLong", "myInteger" }), @IQIndex(type = IndexType.HASH, value = "myString") }) +@IQVersion(1) +public class SupportedTypes { + + public static final SupportedTypes SAMPLE = new SupportedTypes(); + + /** + * Test of plain enumeration. + * + * Each field declaraton of this enum must specify a mapping strategy. + */ + public enum Flower { + ROSE, TULIP, MUM, PETUNIA, MARIGOLD, DAFFODIL; + } + + @IQColumn(primaryKey = true, autoIncrement = true) + public Integer id; + + @IQColumn + private Boolean myBool; + + @IQColumn + private Byte myByte; + + @IQColumn + private Short myShort; + + @IQColumn + private Integer myInteger; + + @IQColumn + private Long myLong; + + @IQColumn + private Float myFloat; + + @IQColumn + private Double myDouble; + + // scale change must match the test value scale + @IQColumn(length = 10, scale = 5) + private BigDecimal myBigDecimal; + + @IQColumn(length = 40) + private String myString; + + @IQColumn + private java.util.Date myUtilDate; + + @IQColumn + private java.sql.Date mySqlDate; + + @IQColumn + private java.sql.Time mySqlTime; + + @IQColumn + private java.sql.Timestamp mySqlTimestamp; + + @IQColumn + private byte[] myBlob; + + // test default enum type NAME + @IQColumn(trim = true, length = 25) + private Flower myDefaultFlower; + + @IQEnum(EnumType.NAME) + @IQColumn(trim = true, length = 25) + private Flower myFavoriteFlower; + + @IQEnum(EnumType.ORDINAL) + @IQColumn + private Flower myOtherFavoriteFlower; + + @IQEnum(EnumType.ORDINAL) + @IQColumn + // override the default enum strategy and use the ordinal value + private Tree myFavoriteTree; + + // @IQEnum is set on the enumeration definition and is shared + // by all uses of Tree as an @IQColumn + @IQColumn + private Tree myOtherFavoriteTree; + + public static List createList() { + List list = Utils.newArrayList(); + long now = System.currentTimeMillis(); + long oneday = 24 * 60 * 60 * 1000L; + for (int i = 0; i < 10; i++) { + list.add(randomValue(now - (i * oneday))); + } + return list; + } + + static SupportedTypes randomValue(long time) { + Random rand = new Random(); + SupportedTypes s = new SupportedTypes(); + s.myBool = new Boolean(rand.nextBoolean()); + s.myByte = new Byte((byte) rand.nextInt(Byte.MAX_VALUE)); + s.myShort = new Short((short) rand.nextInt(Short.MAX_VALUE)); + s.myInteger = new Integer(rand.nextInt()); + s.myLong = new Long(rand.nextLong()); + s.myFloat = new Float(rand.nextFloat()); + s.myDouble = new Double(rand.nextDouble()); + s.myBigDecimal = new BigDecimal(rand.nextDouble()); + // scale must match annotation + s.myBigDecimal = s.myBigDecimal.setScale(5, RoundingMode.UP); + s.myString = Long.toHexString(rand.nextLong()); + s.myUtilDate = new java.util.Date(time); + s.mySqlDate = new java.sql.Date(time); + s.mySqlTime = new java.sql.Time(time); + s.mySqlTimestamp = new java.sql.Timestamp(time); + s.myBlob = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + s.myDefaultFlower = Flower.DAFFODIL; + s.myFavoriteFlower = Flower.MUM; + s.myOtherFavoriteFlower = Flower.MARIGOLD; + s.myFavoriteTree = Tree.BIRCH; + s.myOtherFavoriteTree = Tree.WALNUT; + return s; + } + + public boolean equivalentTo(SupportedTypes s) { + boolean same = true; + same &= myBool.equals(s.myBool); + same &= myByte.equals(s.myByte); + same &= myShort.equals(s.myShort); + same &= myInteger.equals(s.myInteger); + same &= myLong.equals(s.myLong); + same &= IciqlSuite.equivalentTo(myFloat, s.myFloat); + same &= IciqlSuite.equivalentTo(myDouble, s.myDouble); + same &= myBigDecimal.compareTo(s.myBigDecimal) == 0; + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + same &= df.format(myUtilDate).equals(df.format(s.myUtilDate)); + same &= df.format(mySqlTimestamp).equals(df.format(s.mySqlTimestamp)); + same &= mySqlDate.toString().equals(s.mySqlDate.toString()); + same &= mySqlTime.toString().equals(s.mySqlTime.toString()); + same &= myString.equals(s.myString); + same &= Arrays.equals(myBlob, s.myBlob); + same &= myDefaultFlower.equals(s.myDefaultFlower); + same &= myFavoriteFlower.equals(s.myFavoriteFlower); + same &= myOtherFavoriteFlower.equals(s.myOtherFavoriteFlower); + same &= myFavoriteTree.equals(s.myFavoriteTree); + same &= myOtherFavoriteTree.equals(s.myOtherFavoriteTree); + return same; + } + + /** + * This class demonstrates the table upgrade. + */ + @IQTable(name = "SupportedTypes", inheritColumns = true) + @IQVersion(2) + public static class SupportedTypes2 extends SupportedTypes { + + public SupportedTypes2() { + // nothing to do + } + } +} -- cgit v1.2.3