* Improved DEFAULT value specifications. * Fixed bug in buildObjects where the ResultSet could be closed by the automatic create table attempt. * DbInspector now uses the dialect's reported DATETIME class preference. * Improved IciqlException SQLState code checks. * Integrated LIMIT and OFFSET expression appending in dialects. * Updated to H2 1.3.159 * Allow reopening of a memory database in the test suite.tags/v0.6.6
@@ -14,11 +14,6 @@ | |||
<attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/markdownpapers-core-1.1.0-javadoc.jar!/"/> | |||
</attributes> | |||
</classpathentry> | |||
<classpathentry kind="lib" path="ext/h2-1.3.158.jar" sourcepath="ext/h2-1.3.158-sources.jar"> | |||
<attributes> | |||
<attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/h2-1.3.158-javadoc.jar!/"/> | |||
</attributes> | |||
</classpathentry> | |||
<classpathentry kind="lib" path="ext/doclava-1.0.3.jar" sourcepath="ext/doclava-1.0.3-sources.jar"> | |||
<attributes> | |||
<attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/doclava-1.0.3-javadoc.jar!/"/> | |||
@@ -30,5 +25,11 @@ | |||
</attributes> | |||
</classpathentry> | |||
<classpathentry kind="lib" path="ext/hsqldb-2.2.4.jar"/> | |||
<classpathentry kind="lib" path="ext/derby-10.8.1.2.jar"/> | |||
<classpathentry kind="lib" path="ext/h2-1.3.159.jar" sourcepath="ext/h2-1.3.159-sources.jar"> | |||
<attributes> | |||
<attribute name="javadoc_location" value="jar:platform:/resource/iciql/ext/h2-1.3.159-javadoc.jar!/"/> | |||
</attributes> | |||
</classpathentry> | |||
<classpathentry kind="output" path="bin"/> | |||
</classpath> |
@@ -6,3 +6,6 @@ | |||
/*.zip | |||
/*.jar | |||
/build.properties | |||
/derby.log | |||
/performance.txt | |||
/performance_db.txt |
@@ -45,6 +45,14 @@ HSQL Database | |||
http://hsqldb.org | |||
--------------------------------------------------------------------------- | |||
Derby Database | |||
--------------------------------------------------------------------------- | |||
Derby, released under the | |||
Apache Software License, Version 2.0. | |||
http://db.apache.org/derby | |||
--------------------------------------------------------------------------- | |||
MarkdownPapers | |||
--------------------------------------------------------------------------- |
@@ -5,7 +5,7 @@ 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 (100KB) with no runtime dependencies | |||
- small (125KB) with no runtime dependencies | |||
- pronounced *icicle* (although it could be French: *ici ql* - here query language) | |||
- a friendly fork of the H2 [JaQu](http://h2database.com/html/jaqu.html) project | |||
@@ -15,11 +15,17 @@ iciql **is not**... | |||
- designed to compete with more powerful database query tools like [jOOQ](http://jooq.sourceforge.net) or [Querydsl](http://source.mysema.com/display/querydsl/Querydsl) | |||
- designed to compete with enterprise ORM tools like [Hibernate](http://www.hibernate.org) or [mybatis](http://www.mybatis.org) | |||
Supported Databases | |||
Supported Databases (Unit-Tested) | |||
------- | |||
- [H2 1.3](http://h2database.com) | |||
- [HSQLDB 2.2](http://hsqldb.org) | |||
- Support for others is planned and may only require creating a simple "dialect" class. | |||
- [H2](http://h2database.com) 1.3.159 | |||
- [HSQLDB](http://hsqldb.org) 2.2.4 | |||
- [Derby](http://db.apache.org/derby) 10.7.1.1 & 10.8.1.2 | |||
Partially Supported Databases (not Unit-Tested) | |||
------- | |||
- [MySQL](http://mysql.com) | |||
Support for others is planned and may only require creating a simple "dialect" class. | |||
License | |||
------- |
@@ -99,12 +99,21 @@ | |||
</path> | |||
<javac destdir="${project.build.dir}" failonerror="false"> | |||
<src path="${basedir}/src" /> | |||
<src path="${basedir}/tests" /> | |||
<classpath refid="master-classpath" /> | |||
</javac> | |||
<copy todir="${project.build.dir}"> | |||
<fileset dir="${basedir}/src" excludes="**/*.java,**/thumbs.db" /> | |||
</copy> | |||
<!-- Execute the test suite --> | |||
<echo>Executing iciql ${iq.version} test suite</echo> | |||
<java classpath="${project.build.dir}" classname="com.iciql.test.IciqlSuite"> | |||
<classpath refid="master-classpath" /> | |||
<arg value="--outputFile" /> | |||
<arg value="${basedir}/performance_db.txt" /> | |||
</java> | |||
<!-- Build Standard Javadoc --> | |||
<delete dir="${basedir}/javadoc" /> | |||
<javadoc destdir="${basedir}/javadoc" nonavbar="true" stylesheetfile="${basedir}/docs/resources/javadoc.css"> | |||
@@ -170,6 +179,7 @@ | |||
<fileset dir="${project.build.dir}"> | |||
<include name="**/*" /> | |||
<exclude name="com/iciql/build/" /> | |||
<exclude name="com/iciql/tests/" /> | |||
<exclude name="**/*.html" /> | |||
</fileset> | |||
</jar> | |||
@@ -186,6 +196,7 @@ | |||
<fileset dir="${basedir}/src"> | |||
<include name="**/*" /> | |||
<exclude name="com/iciql/build/" /> | |||
<exclude name="com/iciql/tests/" /> | |||
<exclude name="**/*.html" /> | |||
</fileset> | |||
</jar> | |||
@@ -229,7 +240,7 @@ | |||
<include name="**/*" /> | |||
</fileset> | |||
</copy> | |||
<!-- Build site pages --> | |||
<java classpath="${project.build.dir}" classname="com.iciql.build.BuildSite"> | |||
<classpath refid="master-classpath" /> | |||
@@ -281,6 +292,9 @@ | |||
<arg value="--regex" /> | |||
<arg value=""\b(issue)(\s*[#]?|-){0,1}(\d+)\b!!!<a href='http://code.google.com/p/iciql/issues/detail?id=$3'>issue $3</a>"" /> | |||
<arg value="--load" /> | |||
<arg value="%DBPERFORMANCE%=${basedir}/performance_db.txt" /> | |||
</java> | |||
</target> | |||
@@ -5,7 +5,7 @@ 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 (120KB) with no runtime dependencies | |||
- small (125KB) with no runtime dependencies | |||
- pronounced *icicle* (although it could be French: *ici ql* - here query language) | |||
- a friendly fork of the H2 [JaQu][jaqu] project | |||
@@ -35,9 +35,13 @@ select * from products | |||
</tr> | |||
</table> | |||
### Supported Databases | |||
- [H2 1.3](http://h2database.com) | |||
- [HSQLDB 2.2](http://hsqldb.org) | |||
### Supported Databases (Unit-Tested) | |||
- [H2](http://h2database.com) 1.3.159 | |||
- [HSQLDB](http://hsqldb.org) 2.2.4 | |||
- [Derby](http://db.apache.org/derby) 10.7.1.1 & 10.8.1.2 | |||
### Partially Supported Databases (not Unit-Tested) | |||
- [MySQL](http://mysql.com) | |||
Support for others is planned and may only require creating a simple "dialect" class. | |||
@@ -65,6 +65,30 @@ List<Product> allProducts = db.buildObjects(Product.class, rs); | |||
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. |
@@ -1,23 +0,0 @@ | |||
## 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% |
@@ -0,0 +1,16 @@ | |||
## 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 but that is planned. | |||
### iciql+database performance comparison | |||
The following data was generated by running the iciql test suite. The suite is almost completely single-threaded. All databases are run in embedded *memory-only* mode through a JDBC connection. Since the suite is running in memory-only mode, disk IO bottlenecks should be removed from the equation and the results should be measuring raw statement processing. | |||
<pre> | |||
%DBPERFORMANCE% | |||
</pre> |
@@ -10,6 +10,7 @@ Additionally, [eclipse-cs](http://eclipse-cs.sourceforge.net), [FindBugs](http:/ | |||
### Build Dependencies (downloaded during build) | |||
- [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) | |||
- [JUnit](http://junit.org) (Common Public License) | |||
- [commons-net](http://commons.apache.org/net) (Apache 2.0) | |||
- [ant-googlecode](http://code.google.com/p/ant-googlecode) (New BSD) |
@@ -6,10 +6,17 @@ | |||
**%VERSION%** ([zip](http://code.google.com/p/iciql/downloads/detail?name=%ZIP%)|[jar](http://code.google.com/p/iciql/downloads/detail?name=%JAR%)) *released %BUILDDATE%* | |||
- fixed failure of db.delete(PrimitiveModel) and db.update(PrimitiveModel) | |||
- Disabled 2 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 | |||
### Older Releases | |||
**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) | |||
@@ -28,7 +35,7 @@ | |||
- 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 not be flawed | |||
- 1 is a concurrency issue, but the test may be flawed | |||
- added untested MySQL dialect | |||
- renamed <b>_ iq_versions</b> table to *iq_versions* since leading _ character is troublesome for some databases. | |||
- @IQColumn(allowNull=true) -> @IQColumn(nullable=true) |
@@ -25,11 +25,11 @@ public class Constants { | |||
// 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 = "0.6.5"; | |||
public static final String VERSION = "0.6.6-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 = "2011-08-12"; | |||
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. |
@@ -74,6 +74,7 @@ public class Db { | |||
DIALECTS.put("H2", SQLDialectH2.class); | |||
DIALECTS.put("MySQL", SQLDialectMySQL.class); | |||
DIALECTS.put("HSQL Database Engine", SQLDialectHSQL.class); | |||
DIALECTS.put("Apache Derby", SQLDialectDerby.class); | |||
} | |||
private Db(Connection conn) { | |||
@@ -192,13 +193,30 @@ public class Db { | |||
* 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: | |||
* <p> | |||
* INSERT INTO foo... (SELECT ?,... FROM foo WHERE pk=? HAVING count(*)=0) | |||
* <p> | |||
* 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. | |||
* <p> | |||
* See the Derby dialect for an implementation of this technique. | |||
* <p> | |||
* If the dialect does not support merge an IciqlException will be thrown. | |||
* | |||
* @param t | |||
*/ | |||
public <T> void merge(T t) { | |||
Class<?> clazz = t.getClass(); | |||
define(clazz).createTableIfRequired(this).merge(this, t); | |||
TableDefinition<?> def = define(clazz).createTableIfRequired(this); | |||
int rc = def.merge(this, t); | |||
if (rc == 0) { | |||
rc = def.update(this, t); | |||
} | |||
if (rc == 0) { | |||
throw new IciqlException("merge failed"); | |||
} | |||
} | |||
public <T> int update(T t) { | |||
@@ -220,7 +238,7 @@ public class Db { | |||
@SuppressWarnings("unchecked") | |||
public <T> List<T> buildObjects(Class<? extends T> modelClass, ResultSet rs) { | |||
List<T> result = new ArrayList<T>(); | |||
TableDefinition<T> def = (TableDefinition<T>) define(modelClass).createTableIfRequired(this); | |||
TableDefinition<T> def = (TableDefinition<T>) define(modelClass); | |||
try { | |||
while (rs.next()) { | |||
T item = Utils.newObject(modelClass); |
@@ -42,6 +42,7 @@ public class DbInspector { | |||
public DbInspector(Db db) { | |||
this.db = db; | |||
setPreferredDateTimeClass(db.getDialect().getDateTimeClass()); | |||
} | |||
/** | |||
@@ -73,8 +74,8 @@ public class DbInspector { | |||
* (trims strings to maxLength of column) | |||
* @return a list of complete model classes as strings, each element a class | |||
*/ | |||
public List<String> generateModel(String schema, String table, String packageName, boolean annotateSchema, | |||
boolean trimStrings) { | |||
public List<String> generateModel(String schema, String table, String packageName, | |||
boolean annotateSchema, boolean trimStrings) { | |||
try { | |||
List<String> models = Utils.newArrayList(); | |||
List<TableInspector> tables = getTables(schema, table); | |||
@@ -130,7 +131,8 @@ public class DbInspector { | |||
Class<T> clazz = (Class<T>) model.getClass(); | |||
TableDefinition<T> def = db.define(clazz); | |||
boolean forceUpperCase = getMetaData().storesUpperCaseIdentifiers(); | |||
String schema = (forceUpperCase && def.schemaName != null) ? def.schemaName.toUpperCase() : def.schemaName; | |||
String schema = (forceUpperCase && def.schemaName != null) ? def.schemaName.toUpperCase() | |||
: def.schemaName; | |||
String table = forceUpperCase ? def.tableName.toUpperCase() : def.tableName; | |||
List<TableInspector> tables = getTables(schema, table); | |||
return tables.get(0); | |||
@@ -167,7 +169,8 @@ public class DbInspector { | |||
while (rs.next()) { | |||
String t = rs.getString("TABLE_NAME"); | |||
if (!t.equalsIgnoreCase(iciqlTables)) { | |||
tables.add(new TableInspector(s, t, getMetaData().storesUpperCaseIdentifiers(), dateTimeClass)); | |||
tables.add(new TableInspector(s, t, getMetaData().storesUpperCaseIdentifiers(), | |||
dateTimeClass)); | |||
} | |||
} | |||
} |
@@ -72,7 +72,8 @@ import java.lang.annotation.Target; | |||
* </tr> | |||
* <tr> | |||
* <td>java.math.BigDecimal</td> | |||
* <td>DECIMAL (length == 0)<br/>DECIMAL(length, scale) (length > 0)</td> | |||
* <td>DECIMAL (length == 0)<br/> | |||
* DECIMAL(length, scale) (length > 0)</td> | |||
* </tr> | |||
* <tr> | |||
* <td>java.sql.Date</td> | |||
@@ -400,8 +401,8 @@ public interface Iciql { | |||
* Scale is used during the CREATE TABLE phase to define the scale of a | |||
* DECIMAL(precision, scale) expression. | |||
* <p> | |||
* Any scale set in define() may override this annotation setting if | |||
* the model class is not annotated with IQTable. Default: 0. | |||
* 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; | |||
@@ -28,9 +28,10 @@ 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_TABLE_NOT_FOUND = 3; | |||
public static final int CODE_INDEX_ALREADY_EXISTS = 4; | |||
public static final int CODE_TABLE_ALREADY_EXISTS = 4; | |||
public static final int CODE_INDEX_ALREADY_EXISTS = 5; | |||
private static final String TOKEN_UNMAPPED_FIELD = "\\? (=|\\>|\\<|\\<\\>|!=|\\>=|\\<=|LIKE|BETWEEN) \\?"; | |||
private static final String TOKEN_UNMAPPED_FIELD = "\\? (=|\\>|\\<|\\<\\>|!=|\\>=|\\<=|LIKE|BETWEEN) \\?"; | |||
private static final long serialVersionUID = 1L; | |||
@@ -51,7 +52,7 @@ public class IciqlException extends RuntimeException { | |||
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!"); | |||
@@ -60,7 +61,7 @@ public class IciqlException extends RuntimeException { | |||
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!"); | |||
@@ -73,7 +74,7 @@ public class IciqlException extends RuntimeException { | |||
return e; | |||
} | |||
} | |||
public void setSQL(String sql) { | |||
this.sql = sql; | |||
} | |||
@@ -85,7 +86,7 @@ public class IciqlException extends RuntimeException { | |||
public int getIciqlCode() { | |||
return iciqlCode; | |||
} | |||
private void configureCode(Throwable t) { | |||
if (t == null) { | |||
return; | |||
@@ -96,29 +97,39 @@ public class IciqlException extends RuntimeException { | |||
String state = s.getSQLState(); | |||
if ("23505".equals(state)) { | |||
iciqlCode = CODE_DUPLICATE_KEY; | |||
} else if ("42501".equals(state)) { | |||
} else if ("42X05".equals(state)) { | |||
// Derby | |||
iciqlCode = CODE_TABLE_NOT_FOUND; | |||
} else if ("42S02".equals(state)) { | |||
// H2 | |||
iciqlCode = CODE_TABLE_NOT_FOUND; | |||
} else if ("42504".equals(state)) { | |||
iciqlCode = CODE_INDEX_ALREADY_EXISTS; | |||
} else if ("42501".equals(state)) { | |||
// HSQL | |||
iciqlCode = CODE_TABLE_NOT_FOUND; | |||
} else if ("X0Y32".equals(state)) { | |||
// Derby | |||
iciqlCode = CODE_TABLE_ALREADY_EXISTS; | |||
} else if ("42S11".equals(state)) { | |||
// H2 | |||
iciqlCode = CODE_INDEX_ALREADY_EXISTS; | |||
} else if ("42504".equals(state)) { | |||
// HSQL | |||
iciqlCode = CODE_INDEX_ALREADY_EXISTS; | |||
} | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
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(); | |||
} | |||
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(); | |||
} | |||
} |
@@ -253,7 +253,10 @@ class ModelUtils { | |||
String value = null; | |||
if (Number.class.isAssignableFrom(objectClass)) { | |||
// NUMBER | |||
value = ((Number) o).toString(); | |||
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); | |||
@@ -266,9 +269,6 @@ class ModelUtils { | |||
} else if (String.class.isAssignableFrom(objectClass)) { | |||
// STRING | |||
value = o.toString(); | |||
} else if (Boolean.class.isAssignableFrom(objectClass)) { | |||
// BOOLEAN | |||
value = o.toString(); | |||
} | |||
if (value == null) { | |||
return "''"; | |||
@@ -313,10 +313,6 @@ class ModelUtils { | |||
return true; | |||
} | |||
// TODO H2 single-quotes literal values, which is useful. | |||
// MySQL does not single-quote literal values so its hard to | |||
// differentiate a FUNCTION/VARIABLE from a literal value. | |||
// function / variable | |||
Pattern functionDefault = Pattern.compile("[^'].*[^']"); | |||
if (functionDefault.matcher(defaultValue).matches()) { |
@@ -193,7 +193,7 @@ public class Query<T> { | |||
public UpdateColumnIncrement<T, Byte> increment(byte field) { | |||
return incrementPrimitive(field); | |||
} | |||
public UpdateColumnIncrement<T, Short> increment(short field) { | |||
return incrementPrimitive(field); | |||
} | |||
@@ -511,13 +511,13 @@ public class Query<T> { | |||
} | |||
return orderBy(alias); | |||
} | |||
public Query<T> orderBy(Object expr) { | |||
OrderExpression<T> e = new OrderExpression<T>(this, expr, false, false, false); | |||
addOrderBy(e); | |||
return this; | |||
} | |||
/** | |||
* Order by a number of columns. | |||
* | |||
@@ -575,12 +575,12 @@ public class Query<T> { | |||
} | |||
return groupBy(alias); | |||
} | |||
public Query<T> groupBy(Object expr) { | |||
groupByExpressions.add(expr); | |||
return this; | |||
} | |||
public Query<T> groupBy(Object... groupBy) { | |||
this.groupByExpressions.addAll(Arrays.asList(groupBy)); | |||
return this; | |||
@@ -712,12 +712,7 @@ public class Query<T> { | |||
stat.appendSQL(" "); | |||
} | |||
} | |||
if (limit > 0) { | |||
db.getDialect().appendLimit(stat, limit); | |||
} | |||
if (offset > 0) { | |||
db.getDialect().appendOffset(stat, offset); | |||
} | |||
db.getDialect().appendLimitOffset(stat, limit, offset); | |||
StatementLogger.select(stat.getSQL()); | |||
} | |||
@@ -18,6 +18,7 @@ package com.iciql; | |||
/** | |||
* This class represents a "between y and z" condition. | |||
* | |||
* @param <T> | |||
* the return type of the query | |||
* @param <A> |
@@ -344,6 +344,7 @@ public class QueryWhere<T> { | |||
query.orderBy(field); | |||
return this; | |||
} | |||
/** | |||
* Order by a number of Object columns. | |||
* |
@@ -62,7 +62,7 @@ public interface SQLDialect { | |||
* @param def | |||
*/ | |||
<T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def); | |||
/** | |||
* Get the CREATE INDEX statement. | |||
* | |||
@@ -90,24 +90,16 @@ public interface SQLDialect { | |||
Object obj); | |||
/** | |||
* Append "LIMIT limit" to the SQL statement. | |||
* Append "LIMIT limit OFFSET offset" to the SQL statement. | |||
* | |||
* @param stat | |||
* the statement | |||
* @param limit | |||
* the limit | |||
*/ | |||
void appendLimit(SQLStatement stat, long limit); | |||
/** | |||
* Append "OFFSET offset" to the SQL statement. | |||
* | |||
* @param stat | |||
* the statement | |||
* @param offset | |||
* the offset | |||
*/ | |||
void appendOffset(SQLStatement stat, long offset); | |||
void appendLimitOffset(SQLStatement stat, long limit, long offset); | |||
/** | |||
* Whether memory tables are supported. | |||
@@ -115,7 +107,14 @@ public interface SQLDialect { | |||
* @return true if they are | |||
*/ | |||
boolean supportsMemoryTables(); | |||
/** | |||
* Whether IF NOT EXISTS notation is supported. | |||
* | |||
* @return true if they are | |||
*/ | |||
boolean supportsIfNotExists(); | |||
/** | |||
* Whether LIMIT/OFFSET notation is supported. | |||
* | |||
@@ -123,4 +122,12 @@ public interface SQLDialect { | |||
*/ | |||
boolean supportsLimitOffset(); | |||
/** | |||
* Returns the preferred DATETIME class for the database. | |||
* <p> | |||
* Either java.util.Date or java.sql.Timestamp | |||
* | |||
* @return preferred DATETIME class | |||
*/ | |||
Class<? extends java.util.Date> getDateTimeClass(); | |||
} |
@@ -61,11 +61,21 @@ public class SQLDialectDefault implements SQLDialect { | |||
return sqlType; | |||
} | |||
@Override | |||
public Class<? extends java.util.Date> getDateTimeClass() { | |||
return java.util.Date.class; | |||
} | |||
@Override | |||
public boolean supportsMemoryTables() { | |||
return false; | |||
} | |||
@Override | |||
public boolean supportsIfNotExists() { | |||
return true; | |||
} | |||
@Override | |||
public boolean supportsLimitOffset() { | |||
return true; | |||
@@ -88,9 +98,13 @@ public class SQLDialectDefault implements SQLDialect { | |||
public <T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def) { | |||
StatementBuilder buff; | |||
if (def.memoryTable && supportsMemoryTables()) { | |||
buff = new StatementBuilder("CREATE MEMORY TABLE IF NOT EXISTS "); | |||
buff = new StatementBuilder("CREATE MEMORY TABLE "); | |||
} else { | |||
buff = new StatementBuilder("CREATE TABLE IF NOT EXISTS "); | |||
buff = new StatementBuilder("CREATE TABLE "); | |||
} | |||
if (supportsIfNotExists()) { | |||
buff.append("IF NOT EXISTS "); | |||
} | |||
buff.append(prepareTableName(def.schemaName, def.tableName)).append('('); | |||
@@ -183,12 +197,12 @@ public class SQLDialectDefault implements SQLDialect { | |||
} | |||
@Override | |||
public void appendLimit(SQLStatement stat, long limit) { | |||
stat.appendSQL(" LIMIT " + limit); | |||
} | |||
@Override | |||
public void appendOffset(SQLStatement stat, long offset) { | |||
stat.appendSQL(" OFFSET " + offset); | |||
public void appendLimitOffset(SQLStatement stat, long limit, long offset) { | |||
if (limit > 0) { | |||
stat.appendSQL(" LIMIT " + limit); | |||
} | |||
if (offset > 0) { | |||
stat.appendSQL(" OFFSET " + offset); | |||
} | |||
} | |||
} |
@@ -0,0 +1,151 @@ | |||
/* | |||
* 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.TableDefinition.IndexDefinition; | |||
import com.iciql.util.StatementBuilder; | |||
/** | |||
* Derby database dialect. | |||
*/ | |||
public class SQLDialectDerby extends SQLDialectDefault { | |||
@Override | |||
public Class<? extends java.util.Date> getDateTimeClass() { | |||
return java.sql.Timestamp.class; | |||
} | |||
@Override | |||
protected String convertSqlType(String sqlType) { | |||
if ("TINYINT".equals(sqlType)) { | |||
// Derby does not have a TINYINT/BYTE type | |||
return "SMALLINT"; | |||
} | |||
return sqlType; | |||
} | |||
@Override | |||
public boolean supportsMemoryTables() { | |||
return false; | |||
} | |||
@Override | |||
public boolean supportsIfNotExists() { | |||
return false; | |||
} | |||
@Override | |||
public boolean supportsLimitOffset() { | |||
// FETCH/OFFSET added in 10.5 | |||
return databaseVersion >= 10.5f; | |||
} | |||
@Override | |||
public void appendLimitOffset(SQLStatement stat, long limit, long offset) { | |||
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 | |||
public String prepareColumnName(String name) { | |||
return name; | |||
} | |||
@Override | |||
protected boolean prepareColumnDefinition(StatementBuilder buff, boolean isAutoIncrement, | |||
boolean isPrimaryKey) { | |||
if (isAutoIncrement) { | |||
buff.append(" GENERATED BY DEFAULT AS IDENTITY"); | |||
} | |||
return false; | |||
} | |||
@Override | |||
public void prepareCreateIndex(SQLStatement stat, String schema, String table, 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(table); | |||
buff.append("("); | |||
for (String col : index.columnNames) { | |||
buff.appendExceptFirst(", "); | |||
buff.append(prepareColumnName(col)); | |||
} | |||
buff.append(") "); | |||
stat.setSQL(buff.toString().trim()); | |||
} | |||
/** | |||
* Derby does 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; | |||
* | |||
* http://stackoverflow.com/questions/407688 | |||
*/ | |||
@Override | |||
public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, | |||
TableDefinition<T> 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()); | |||
} | |||
} |
@@ -57,11 +57,12 @@ public class SQLDialectH2 extends SQLDialectDefault { | |||
buff.append(col); | |||
} | |||
buff.append(")"); | |||
stat.setSQL(buff.toString()); | |||
stat.setSQL(buff.toString()); | |||
} | |||
@Override | |||
public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition<T> def, Object obj) { | |||
public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, | |||
TableDefinition<T> def, Object obj) { | |||
StatementBuilder buff = new StatementBuilder("MERGE INTO "); | |||
buff.append(prepareTableName(schemaName, tableName)).append(" ("); | |||
buff.resetCount(); |
@@ -16,6 +16,9 @@ | |||
package com.iciql; | |||
import java.text.MessageFormat; | |||
import com.iciql.TableDefinition.FieldDefinition; | |||
import com.iciql.TableDefinition.IndexDefinition; | |||
import com.iciql.util.StatementBuilder; | |||
@@ -23,14 +26,15 @@ import com.iciql.util.StatementBuilder; | |||
* HyperSQL database dialect. | |||
*/ | |||
public class SQLDialectHSQL extends SQLDialectDefault { | |||
@Override | |||
public boolean supportsMemoryTables() { | |||
return true; | |||
} | |||
@Override | |||
protected boolean prepareColumnDefinition(StatementBuilder buff, boolean isAutoIncrement, boolean isPrimaryKey) { | |||
protected boolean prepareColumnDefinition(StatementBuilder buff, boolean isAutoIncrement, | |||
boolean isPrimaryKey) { | |||
boolean isIdentity = false; | |||
if (isAutoIncrement && isPrimaryKey) { | |||
buff.append(" IDENTITY"); | |||
@@ -44,8 +48,6 @@ public class SQLDialectHSQL extends SQLDialectDefault { | |||
StatementBuilder buff = new StatementBuilder(); | |||
buff.append("CREATE "); | |||
switch (index.type) { | |||
case STANDARD: | |||
break; | |||
case UNIQUE: | |||
buff.append("UNIQUE "); | |||
break; | |||
@@ -65,4 +67,91 @@ public class SQLDialectHSQL extends SQLDialectDefault { | |||
buff.append(")"); | |||
stat.setSQL(buff.toString()); | |||
} | |||
@Override | |||
public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, | |||
TableDefinition<T> 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()); | |||
} | |||
} |
@@ -31,7 +31,7 @@ public class SQLDialectMySQL extends SQLDialectDefault { | |||
} | |||
return sqlType; | |||
} | |||
@Override | |||
public boolean supportsMemoryTables() { | |||
return false; | |||
@@ -41,15 +41,16 @@ public class SQLDialectMySQL extends SQLDialectDefault { | |||
public String prepareColumnName(String name) { | |||
return "`" + name + "`"; | |||
} | |||
@Override | |||
protected boolean prepareColumnDefinition(StatementBuilder buff, boolean isAutoIncrement, boolean isPrimaryKey) { | |||
protected boolean prepareColumnDefinition(StatementBuilder buff, boolean isAutoIncrement, | |||
boolean isPrimaryKey) { | |||
if (isAutoIncrement) { | |||
buff.append(" AUTO_INCREMENT"); | |||
} | |||
return false; | |||
} | |||
@Override | |||
public void prepareCreateIndex(SQLStatement stat, String schema, String table, IndexDefinition index) { | |||
StatementBuilder buff = new StatementBuilder(); | |||
@@ -74,7 +75,7 @@ public class SQLDialectMySQL extends SQLDialectDefault { | |||
buff.append(prepareColumnName(col)); | |||
} | |||
buff.append(") "); | |||
// USING | |||
switch (index.type) { | |||
case HASH: |
@@ -65,6 +65,12 @@ public class SQLStatement { | |||
} | |||
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; | |||
} | |||
@@ -112,7 +118,8 @@ public class SQLStatement { | |||
try { | |||
prep.setObject(parameterIndex, x); | |||
} catch (SQLException e) { | |||
IciqlException ix = new IciqlException(e, "error setting parameter {0} as {1}", parameterIndex, x.getClass().getSimpleName()); | |||
IciqlException ix = new IciqlException(e, "error setting parameter {0} as {1}", parameterIndex, x | |||
.getClass().getSimpleName()); | |||
ix.setSQL(getSQL()); | |||
throw ix; | |||
} |
@@ -447,30 +447,35 @@ public class TableDefinition<T> { | |||
} | |||
private boolean skipInsertField(FieldDefinition field, Object obj) { | |||
// skip uninitialized primitive autoincrement values | |||
if (field.isAutoIncrement && field.isPrimitive) { | |||
if (field.isAutoIncrement) { | |||
Object value = getValue(obj, field); | |||
if (value.toString().equals("0")) { | |||
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; | |||
} | |||
} | |||
return false; | |||
} | |||
void merge(Db db, Object obj) { | |||
int merge(Db db, Object obj) { | |||
if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) { | |||
throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass() | |||
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); | |||
StatementLogger.merge(stat.getSQL()); | |||
stat.executeUpdate(); | |||
return stat.executeUpdate(); | |||
} | |||
int update(Db db, Object obj) { | |||
if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) { | |||
throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass() | |||
throw new IllegalStateException("No primary key columns defined for table " + obj.getClass() | |||
+ " - no update possible"); | |||
} | |||
SQLStatement stat = new SQLStatement(db); | |||
@@ -552,7 +557,13 @@ public class TableDefinition<T> { | |||
SQLStatement stat = new SQLStatement(db); | |||
db.getDialect().prepareCreateTable(stat, this); | |||
StatementLogger.create(stat.getSQL()); | |||
stat.executeUpdate(); | |||
try { | |||
stat.executeUpdate(); | |||
} catch (IciqlException e) { | |||
if (e.getIciqlCode() != IciqlException.CODE_TABLE_ALREADY_EXISTS) { | |||
throw e; | |||
} | |||
} | |||
// create indexes | |||
for (IndexDefinition index : indexes) { |
@@ -120,7 +120,8 @@ public class TableInspector { | |||
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")) { | |||
if (name.startsWith("primary") || name.startsWith("sys_idx_sys_pk") | |||
|| name.startsWith("sql")) { | |||
// skip primary key indexes | |||
continue; | |||
} |
@@ -17,7 +17,6 @@ | |||
package com.iciql; | |||
/** | |||
* This class represents "SET column = value" in an UPDATE statement. | |||
* |
@@ -57,6 +57,7 @@ public class Build { | |||
downloadFromApache(MavenObject.H2, BuildType.RUNTIME); | |||
downloadFromApache(MavenObject.H2, BuildType.COMPILETIME); | |||
downloadFromApache(MavenObject.HSQLDB, BuildType.RUNTIME); | |||
downloadFromApache(MavenObject.DERBY, BuildType.RUNTIME); | |||
downloadFromApache(MavenObject.JCOMMANDER, BuildType.RUNTIME); | |||
downloadFromApache(MavenObject.JCOMMANDER, BuildType.COMPILETIME); | |||
downloadFromApache(MavenObject.MARKDOWNPAPERS, BuildType.RUNTIME); | |||
@@ -169,13 +170,15 @@ public class Build { | |||
"219a3540f3b27d7cc3b1d91d6ea046cd8723290e", "0bb50eec177acf0e94d58e0cf07262fe5164331d", | |||
"c7adc475ca40c288c93054e0f4fe58f3a98c0cb5"); | |||
public static final MavenObject H2 = new MavenObject("com/h2database", "h2", "1.3.158", | |||
"4bac13427caeb32ef6e93b70101e61f370c7b5e2", "6bb165156a0831879fa7797df6e18bdcd4421f2d", | |||
"446d3f58c44992534cb54f67134532d95961904a"); | |||
public static final MavenObject H2 = new MavenObject("com/h2database", "h2", "1.3.159", | |||
"dd89f939661eb5593909584e1c243db0c25de130", "4d953bf765e8a13c7e06ca51165438338966c698", | |||
"4c79ed03f994820a1a873150c8a9f13c667784d3"); | |||
public static final MavenObject HSQLDB = new MavenObject("org/hsqldb", "hsqldb", "2.2.4", | |||
"6a6e040b07f5ee409fc825f1c5e5b574b1fa1428", "", | |||
""); | |||
"6a6e040b07f5ee409fc825f1c5e5b574b1fa1428", "", ""); | |||
public static final MavenObject DERBY = new MavenObject("org/apache/derby", "derby", "10.8.1.2", | |||
"2f8717d96eafe3eef3de445ba653f142d54ddab1", "", ""); | |||
public static final MavenObject JUNIT = new MavenObject("junit", "junit", "4.8.2", | |||
"c94f54227b08100974c36170dcb53329435fe5ad", "", ""); |
@@ -87,8 +87,8 @@ public class BuildSite { | |||
aliasMap.put(values[0], values[1]); | |||
} | |||
System.out.println(MessageFormat.format("Generating site from {0} Markdown Docs in {1} ", markdownFiles.length, | |||
sourceFolder.getAbsolutePath())); | |||
System.out.println(MessageFormat.format("Generating site from {0} Markdown Docs in {1} ", | |||
markdownFiles.length, sourceFolder.getAbsolutePath())); | |||
String linkPattern = "<a href=''{0}''>{1}</a>"; | |||
StringBuilder sb = new StringBuilder(); | |||
for (File file : markdownFiles) { | |||
@@ -168,7 +168,8 @@ public class BuildSite { | |||
// get remainder of text | |||
if (endCode < markdownContent.length()) { | |||
strippedContent.append(markdownContent.substring(endCode, markdownContent.length())); | |||
strippedContent.append(markdownContent.substring(endCode, | |||
markdownContent.length())); | |||
} | |||
markdownContent = strippedContent.toString(); | |||
nmd++; | |||
@@ -193,9 +194,16 @@ public class BuildSite { | |||
String[] kv = token.split("!!!", 2); | |||
content = content.replaceAll(kv[0], kv[1]); | |||
} | |||
for (String alias : params.loads) { | |||
String[] kv = alias.split("=", 2); | |||
String loadedContent = StringUtils.readContent(new File(kv[1]), "\n"); | |||
loadedContent = StringUtils.escapeForHtml(loadedContent, false); | |||
loadedContent = StringUtils.breakLinesForHtml(loadedContent); | |||
content = content.replace(kv[0], loadedContent); | |||
} | |||
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File(destinationFolder, | |||
fileName)), Charset.forName("UTF-8")); | |||
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File( | |||
destinationFolder, fileName)), Charset.forName("UTF-8")); | |||
writer.write(header); | |||
if (!StringUtils.isNullOrEmpty(htmlAdSnippet)) { | |||
writer.write(htmlAdSnippet); | |||
@@ -317,9 +325,12 @@ public class BuildSite { | |||
@Parameter(names = { "--substitute" }, description = "%TOKEN%=value", required = false) | |||
public List<String> substitutions = new ArrayList<String>(); | |||
@Parameter(names = { "--load" }, description = "%TOKEN%=filename", required = false) | |||
public List<String> loads = new ArrayList<String>(); | |||
@Parameter(names = { "--nomarkdown" }, description = "%STARTTOKEN%:%ENDTOKEN%", required = false) | |||
public List<String> nomarkdown = new ArrayList<String>(); | |||
@Parameter(names = { "--regex" }, description = "searchPattern!!!replacePattern", required = false) | |||
public List<String> regex = new ArrayList<String>(); | |||
@@ -65,7 +65,8 @@ public class ClassReader { | |||
debug("class name " + className); | |||
ByteArrayOutputStream buff = new ByteArrayOutputStream(); | |||
try { | |||
InputStream in = clazz.getClassLoader().getResource(className.replace('.', '/') + ".class").openStream(); | |||
InputStream in = clazz.getClassLoader().getResource(className.replace('.', '/') + ".class") | |||
.openStream(); | |||
while (true) { | |||
int x = in.read(); | |||
if (x < 0) { | |||
@@ -108,7 +109,8 @@ public class ClassReader { | |||
} | |||
case 6: { | |||
long x = readLong(); | |||
constantPool[i] = ConstantNumber.get("" + Double.longBitsToDouble(x), x, Constant.Type.DOUBLE); | |||
constantPool[i] = ConstantNumber | |||
.get("" + Double.longBitsToDouble(x), x, Constant.Type.DOUBLE); | |||
i++; | |||
break; | |||
} | |||
@@ -1380,8 +1382,8 @@ public class ClassReader { | |||
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]; | |||
String className = constantPool[constantPool[classIndex].intValue()] + "." | |||
+ constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff]; | |||
return className; | |||
} | |||
@@ -1389,8 +1391,8 @@ public class ClassReader { | |||
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]; | |||
String className = constantPool[constantPool[classIndex].intValue()] + "." | |||
+ constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff]; | |||
return className; | |||
} | |||
@@ -1444,7 +1446,8 @@ public class ClassReader { | |||
private int readInt() { | |||
byte[] buff = data; | |||
return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8) + (buff[pos++] & 0xff); | |||
return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8) | |||
+ (buff[pos++] & 0xff); | |||
} | |||
private long readLong() { |
@@ -128,13 +128,15 @@ public class GenerateModels { | |||
* 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 { | |||
String packageName, String folder, boolean annotateSchema, boolean trimStrings) | |||
throws SQLException { | |||
Connection conn = null; | |||
try { | |||
conn = DriverManager.getConnection(url, user, password); | |||
Db db = Db.open(url, user, password.toCharArray()); | |||
DbInspector inspector = new DbInspector(db); | |||
List<String> models = inspector.generateModel(schema, table, packageName, annotateSchema, trimStrings); | |||
List<String> models = inspector.generateModel(schema, table, packageName, annotateSchema, | |||
trimStrings); | |||
File parentFile; | |||
if (StringUtils.isNullOrEmpty(folder)) { | |||
parentFile = new File(System.getProperty("user.dir")); |
@@ -206,7 +206,7 @@ public class JdbcUtils { | |||
return ds.getConnection(user, password); | |||
} catch (SQLException e) { | |||
throw e; | |||
} catch (Exception e) { | |||
} catch (Exception e) { | |||
throw new SQLException("Failed to get connection for " + url, e); | |||
} | |||
} else { |
@@ -60,7 +60,7 @@ public class Slf4jStatementListener implements StatementListener { | |||
/** | |||
* Sets the logging level for a particular statement type. | |||
* | |||
* | |||
* @param type | |||
* @param level | |||
*/ | |||
@@ -84,10 +84,10 @@ public class Slf4jStatementListener implements StatementListener { | |||
case DEBUG: | |||
logger.debug(statement); | |||
break; | |||
case TRACE: | |||
case TRACE: | |||
logger.trace(statement); | |||
break; | |||
case OFF: | |||
case OFF: | |||
break; | |||
} | |||
} |
@@ -99,9 +99,10 @@ public class StatementBuilder { | |||
builder.append(x); | |||
return this; | |||
} | |||
/** | |||
* Returns the current value of the loop counter. | |||
* | |||
* @return the loop counter | |||
*/ | |||
public int getCount() { |
@@ -17,7 +17,12 @@ | |||
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; | |||
@@ -305,4 +310,73 @@ public class StringUtils { | |||
} | |||
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", "<br/>").replace("\r", "<br/>").replace("\n", "<br/>"); | |||
} | |||
/** | |||
* 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(); | |||
} | |||
} |
@@ -116,7 +116,9 @@ public class Utils { | |||
} | |||
} | |||
} | |||
throw new IciqlException(e, "Missing default constructor? Exception trying to instantiate {0}: {1}", clazz.getName(), e.getMessage()); | |||
throw new IciqlException(e, | |||
"Missing default constructor? Exception trying to instantiate {0}: {1}", | |||
clazz.getName(), e.getMessage()); | |||
} | |||
} | |||
}; | |||
@@ -194,7 +196,9 @@ public class Utils { | |||
} | |||
} | |||
} | |||
throw new IciqlException(e, "Missing default constructor?! Exception trying to instantiate {0}: {1}", clazz.getName(), e.getMessage()); | |||
throw new IciqlException(e, | |||
"Missing default constructor?! Exception trying to instantiate {0}: {1}", | |||
clazz.getName(), e.getMessage()); | |||
} | |||
} | |||
@@ -242,7 +246,7 @@ public class Utils { | |||
float f = 0f; | |||
try { | |||
f = Float.parseFloat(s); | |||
} catch (Exception e) { | |||
} catch (Exception e) { | |||
} | |||
return f > 0 || s.equals("true") || s.equals("yes"); | |||
} |
@@ -42,7 +42,7 @@ public class AliasMapTest { | |||
*/ | |||
@Test | |||
public void testObjectAliasMapping() throws Exception { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(Product.getList()); | |||
// baseline count is the next id value | |||
@@ -91,7 +91,7 @@ public class AliasMapTest { | |||
*/ | |||
@Test | |||
public void testPrimitiveAliasMapping() throws Exception { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
PrimitivesModel model = new PrimitivesModel(); | |||
model.myLong = 100L; | |||
db.insert(model); |
@@ -53,7 +53,7 @@ public class AnnotationsTest { | |||
@Before | |||
public void setUp() { | |||
db = IciqlSuite.openDb(); | |||
db = IciqlSuite.openNewDb(); | |||
db.insertAll(Product.getList()); | |||
db.insertAll(ProductAnnotationOnly.getList()); | |||
db.insertAll(ProductMixedAnnotation.getList()); | |||
@@ -69,7 +69,15 @@ public class AnnotationsTest { | |||
// test indexes are created, and columns are in the right order | |||
DatabaseMetaData meta = db.getConnection().getMetaData(); | |||
boolean isH2 = meta.getDatabaseProductName().equals("H2"); | |||
ResultSet rs = meta.getIndexInfo(null, "PUBLIC", "ANNOTATEDPRODUCT", false, true); | |||
boolean isDerby = meta.getDatabaseProductName().equals("Apache Derby"); | |||
ResultSet rs; | |||
if (isDerby) { | |||
// Derby defaults to USERNAME schema | |||
rs = meta.getIndexInfo(null, "SA", "ANNOTATEDPRODUCT", false, true); | |||
} else { | |||
// H2, HSQL default to PUBLIC schema | |||
rs = meta.getIndexInfo(null, "PUBLIC", "ANNOTATEDPRODUCT", false, true); | |||
} | |||
// first index is primary key index | |||
// H2 gives this a testable name. | |||
assertTrue(rs.next()); | |||
@@ -104,7 +112,7 @@ public class AnnotationsTest { | |||
} 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()); | |||
@@ -39,7 +39,7 @@ public class BooleanModelTest { | |||
@Test | |||
public void testBooleanColumn() { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(BooleanModel.getList()); | |||
BooleanAsIntModel b = new BooleanAsIntModel(); | |||
List<BooleanAsIntModel> models = db.from(b).select(); | |||
@@ -82,7 +82,7 @@ public class BooleanModelTest { | |||
@Test | |||
public void testIntColumn() { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
// insert INT column | |||
db.insertAll(BooleanAsIntModel.getList()); | |||
@@ -38,13 +38,13 @@ public class ClobTest { | |||
@Test | |||
public void testClob() throws Exception { | |||
String create = "CREATE TABLE CLOB_TEST(ID INT PRIMARY KEY, WORDS {0})"; | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
db.executeUpdate(MessageFormat.format(create, "VARCHAR(255)")); | |||
db.insertAll(StringRecord.getList()); | |||
testSimpleUpdate(db, "VARCHAR fail"); | |||
db.close(); | |||
db = IciqlSuite.openDb(); | |||
db = IciqlSuite.openNewDb(); | |||
db.executeUpdate(MessageFormat.format(create, "CLOB")); | |||
db.insertAll(StringRecord.getList()); | |||
testSimpleUpdate(db, "CLOB fail because of single quote artifacts"); | |||
@@ -95,7 +95,8 @@ public class ClobTest { | |||
} | |||
public static List<StringRecord> getList() { | |||
StringRecord[] list = { create(1, "Once upon a midnight dreary, while I pondered weak and weary,"), | |||
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."), |
@@ -22,8 +22,8 @@ import static org.junit.Assert.assertTrue; | |||
import java.util.List; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import com.iciql.Db; | |||
@@ -37,43 +37,43 @@ import com.iciql.util.Utils; | |||
*/ | |||
public class ConcurrencyTest { | |||
private Db db; | |||
private int numberOfTests = 200; | |||
private int numberOfTests = 800; | |||
@Before | |||
public void setUp() { | |||
db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(Product.getList()); | |||
} | |||
@After | |||
public void tearDown() { | |||
db.close(); | |||
} | |||
@Test | |||
public void testAliasSharing() throws Exception { | |||
// Single-threaded example of why aliases can NOT be shared. | |||
Product p = new Product(); | |||
Query<Product> query1 = db.from(p); | |||
Query<Product> query2 = db.from(p); | |||
// if you could share alias instances both counts should be equal | |||
long count1 = 0; | |||
Db db = IciqlSuite.openCurrentDb(); | |||
try { | |||
count1 = query1.where(p.category).is("Beverages").selectCount(); | |||
} catch (IciqlException e) { | |||
assertEquals(IciqlException.CODE_UNMAPPED_FIELD, e.getIciqlCode()); | |||
// Single-threaded example of why aliases can NOT be shared. | |||
Product p = new Product(); | |||
Query<Product> query1 = db.from(p); | |||
Query<Product> 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(); | |||
} | |||
long count2 = query2.where(p.category).is("Beverages").selectCount(); | |||
// but they aren't | |||
assertEquals(0, count1); | |||
assertEquals(2, count2); | |||
assertTrue(count1 != count2); | |||
} | |||
@Test | |||
@Ignore | |||
public void testConcurrencyFinal() throws Exception { | |||
// Multi-threaded example of why aliases can NOT be shared. | |||
// | |||
@@ -114,6 +114,7 @@ public class ConcurrencyTest { | |||
} | |||
@Test | |||
@Ignore | |||
public void testConcurrencyThreadLocal() throws Exception { | |||
List<Thread> threads = Utils.newArrayList(); | |||
final AtomicInteger failures = new AtomicInteger(0); | |||
@@ -149,45 +150,50 @@ public class ConcurrencyTest { | |||
} | |||
private void test(int testCase, Product p) throws AssertionError { | |||
List<Product> 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()); | |||
Db db = IciqlSuite.openCurrentDb(); | |||
try { | |||
List<Product> 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(); | |||
} | |||
} | |||
} |
@@ -34,7 +34,7 @@ public class DefaultValuesTest { | |||
@Test | |||
public void testDefaultObjectValues() { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
// insert random model | |||
DefaultValuesModel model = new DefaultValuesModel(); |
@@ -40,7 +40,7 @@ public class EnumsTest { | |||
@Before | |||
public void setUp() { | |||
db = IciqlSuite.openDb(); | |||
db = IciqlSuite.openNewDb(); | |||
db.insertAll(EnumIdModel.createList()); | |||
db.insertAll(EnumOrdinalModel.createList()); | |||
db.insertAll(EnumStringModel.createList()); |
@@ -15,8 +15,13 @@ | |||
*/ | |||
package com.iciql.test; | |||
import java.io.PrintStream; | |||
import java.sql.SQLException; | |||
import java.text.MessageFormat; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.Comparator; | |||
import java.util.List; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
import org.junit.Assert; | |||
@@ -27,7 +32,13 @@ 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.util.StringUtils; | |||
/** | |||
* JUnit 4 iciql test suite. | |||
@@ -45,29 +56,62 @@ import com.iciql.Db; | |||
RuntimeQueryTest.class, SamplesTest.class, UpdateTest.class, UUIDTest.class }) | |||
public class IciqlSuite { | |||
private static final TestDb[] TEST_DBS = { | |||
new TestDb("H2", "jdbc:h2:mem:"), | |||
new TestDb("HSQL", "jdbc:hsqldb:mem:db{0,number,000}") }; | |||
private static final TestDb[] TEST_DBS = { new TestDb("H2", "jdbc:h2:mem:db{0,number,000}"), | |||
new TestDb("HSQL", "jdbc:hsqldb:mem:db{0,number,000}"), | |||
new TestDb("Derby", "jdbc:derby:memory:db{0,number,000};create=true") }; | |||
private static final TestDb DEFAULT_TEST_DB = TEST_DBS[0]; | |||
private static final PrintStream ERR = System.err; | |||
private static AtomicInteger openCount = new AtomicInteger(0); | |||
private static String username = "sa"; | |||
private static String password = "sa"; | |||
private static PrintStream out = System.out; | |||
public static void assertStartsWith(String value, String startsWith) { | |||
Assert.assertTrue(MessageFormat.format("Expected \"{0}\", got: \"{1}\"", startsWith, value), | |||
value.startsWith(startsWith)); | |||
} | |||
public static Db openDb() { | |||
/** | |||
* Increment the database counter, open and create a new database. | |||
* | |||
* @return a fresh database | |||
*/ | |||
public static Db openNewDb() { | |||
String testUrl = System.getProperty("iciql.url"); | |||
if (testUrl == null) { | |||
testUrl = DEFAULT_TEST_DB.url; | |||
} | |||
testUrl = MessageFormat.format(testUrl, openCount.incrementAndGet()); | |||
return Db.open(testUrl, "sa", "sa"); | |||
return Db.open(testUrl, username, password); | |||
} | |||
public static String getDatabaseName(Db db) { | |||
/** | |||
* Open the current database. | |||
* | |||
* @return the current database | |||
*/ | |||
public static Db openCurrentDb() { | |||
String testUrl = System.getProperty("iciql.url"); | |||
if (testUrl == null) { | |||
testUrl = DEFAULT_TEST_DB.url; | |||
} | |||
testUrl = MessageFormat.format(testUrl, openCount.get()); | |||
return Db.open(testUrl, username, password); | |||
} | |||
/** | |||
* 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(); | |||
@@ -76,23 +120,161 @@ public class IciqlSuite { | |||
return database; | |||
} | |||
public static void main(String... args) { | |||
/** | |||
* 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"); | |||
} | |||
/** | |||
* 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 username.toUpperCase(); | |||
} | |||
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.outputFile)) { | |||
out = new PrintStream(params.outputFile); | |||
System.setErr(out); | |||
} | |||
SuiteClasses suiteClasses = IciqlSuite.class.getAnnotation(SuiteClasses.class); | |||
long quickestDatabase = Long.MAX_VALUE; | |||
String dividerMajor = buildDivider('*', 70); | |||
String dividerMinor = buildDivider('-', 70); | |||
// Header | |||
out.println(dividerMajor); | |||
out.println(MessageFormat.format("{0} {1} ({2}) testing {3} databases", 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"); | |||
out.println(); | |||
// Test a database | |||
for (TestDb testDb : TEST_DBS) { | |||
System.out.println("*********************************************"); | |||
System.out.println("Testing " + testDb.name + " " + testDb.getVersion()); | |||
System.out.println("*********************************************"); | |||
out.println(dividerMinor); | |||
out.println("Testing " + testDb.name + " " + testDb.getVersion()); | |||
out.println(dividerMinor); | |||
System.setProperty("iciql.url", testDb.url); | |||
Result result = JUnitCore.runClasses(suiteClasses.value()); | |||
System.out.println(MessageFormat.format("{0} runs, {1} failures, {2} ignores in {3} msecs", | |||
testDb.runtime = result.getRunTime(); | |||
if (testDb.runtime < quickestDatabase) { | |||
quickestDatabase = testDb.runtime; | |||
} | |||
out.println(MessageFormat.format("{0} tests: {1} failures, {2} ignores in {3,number,0.000} secs", | |||
result.getRunCount(), result.getFailureCount(), result.getIgnoreCount(), | |||
result.getRunTime())); | |||
for (Failure failure : result.getFailures()) { | |||
System.out.println(MessageFormat.format("{0}: {1}", failure.getTestHeader(), | |||
failure.getMessage())); | |||
result.getRunTime() / 1000f)); | |||
if (result.getFailureCount() == 0) { | |||
out.println(); | |||
} else { | |||
for (Failure failure : result.getFailures()) { | |||
out.println(MessageFormat.format("\n + {0}\n {1}\n", failure.getTestHeader(), | |||
failure.getMessage())); | |||
} | |||
} | |||
} | |||
// Display runtime results sorted by performance leader | |||
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<TestDb> dbs = Arrays.asList(TEST_DBS); | |||
Collections.sort(dbs, new Comparator<TestDb>() { | |||
@Override | |||
public int compare(TestDb o1, TestDb o2) { | |||
if (o1.runtime == o2.runtime) { | |||
return 0; | |||
} | |||
if (o1.runtime > o2.runtime) { | |||
return 1; | |||
} | |||
return -1; | |||
} | |||
}); | |||
for (TestDb testDb : dbs) { | |||
out.println(MessageFormat.format("{0} {1} {2,number,0.000} secs ({3,number,#.0}x)", | |||
StringUtils.pad(testDb.name, 6, " ", true), | |||
StringUtils.pad(testDb.getVersion(), 22, " ", true), testDb.runtime / 1000f, | |||
((double) testDb.runtime) / quickestDatabase)); | |||
} | |||
// close PrintStream and restore System.err | |||
out.close(); | |||
System.setErr(ERR); | |||
} | |||
private static void showProperty(String name) { | |||
out.print(StringUtils.pad(name, 25, " ", true)); | |||
out.println(System.getProperty(name)); | |||
} | |||
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(); | |||
} | |||
/** | |||
@@ -101,6 +283,8 @@ public class IciqlSuite { | |||
private static class TestDb { | |||
final String name; | |||
final String url; | |||
String version; | |||
long runtime; | |||
TestDb(String name, String url) { | |||
this.name = name; | |||
@@ -108,14 +292,27 @@ public class IciqlSuite { | |||
} | |||
String getVersion() { | |||
try { | |||
Db db = Db.open(url, "sa", "sa"); | |||
String version = db.getConnection().getMetaData().getDatabaseProductVersion(); | |||
db.close(); | |||
return version; | |||
} catch (SQLException s) { | |||
if (version == null) { | |||
try { | |||
Db db = Db.open(url, username, password); | |||
version = db.getConnection().getMetaData().getDatabaseProductVersion(); | |||
db.close(); | |||
return version; | |||
} catch (SQLException s) { | |||
version = ""; | |||
} | |||
} | |||
return ""; | |||
return version; | |||
} | |||
} | |||
} | |||
/** | |||
* Command-line parameters for TestSuite. | |||
*/ | |||
@Parameters(separators = " ") | |||
private static class Params { | |||
@Parameter(names = { "--outputFile" }, description = "Results text file", required = false) | |||
public String outputFile; | |||
} | |||
} |
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import java.sql.SQLException; | |||
import java.text.MessageFormat; | |||
import java.util.List; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
@@ -58,7 +59,7 @@ public class ModelsTest { | |||
@Before | |||
public void setUp() { | |||
db = IciqlSuite.openDb(); | |||
db = IciqlSuite.openNewDb(); | |||
db.insertAll(Product.getList()); | |||
db.insertAll(ProductAnnotationOnly.getList()); | |||
db.insertAll(ProductMixedAnnotation.getList()); | |||
@@ -71,14 +72,17 @@ public class ModelsTest { | |||
@Test | |||
public void testValidateModels() { | |||
boolean isH2 = IciqlSuite.getDatabaseName(db).equals("H2"); | |||
boolean isH2 = IciqlSuite.isH2(db); | |||
boolean isDerby = IciqlSuite.isDerby(db); | |||
String schemaName = IciqlSuite.getDefaultSchema(db); | |||
DbInspector inspector = new DbInspector(db); | |||
validateModel(inspector, new Product(), 3); | |||
validateModel(inspector, new ProductAnnotationOnly(), isH2 ? 2 : 3); | |||
validateModel(inspector, new ProductMixedAnnotation(), isH2 ? 3 : 4); | |||
validateModel(inspector, schemaName, new Product(), 3); | |||
validateModel(inspector, schemaName, new ProductAnnotationOnly(), (isH2 || isDerby) ? 2 : 3); | |||
validateModel(inspector, schemaName, new ProductMixedAnnotation(), 4); | |||
} | |||
private void validateModel(DbInspector inspector, Object o, int expected) { | |||
private void validateModel(DbInspector inspector, String schemaName, Object o, int expected) { | |||
List<ValidationRemark> remarks = inspector.validateModel(o, false); | |||
assertTrue("validation remarks are null for " + o.getClass().getName(), remarks != null); | |||
StringBuilder sb = new StringBuilder(); | |||
@@ -91,7 +95,7 @@ public class ModelsTest { | |||
errorCollector.addError(new SQLException(remark.toString())); | |||
} | |||
} | |||
assertTrue(remarks.get(0).message.equals("@IQSchema(name=PUBLIC)")); | |||
assertTrue(remarks.get(0).message.equals(MessageFormat.format("@IQSchema(name={0})", schemaName))); | |||
assertEquals(sb.toString(), expected, remarks.size()); | |||
} | |||
@@ -117,12 +121,16 @@ public class ModelsTest { | |||
true); | |||
assertEquals(1, models.size()); | |||
// a poor test, but a start | |||
String dbName = IciqlSuite.getDatabaseName(db); | |||
String dbName = IciqlSuite.getDatabaseEngineName(db); | |||
if (dbName.equals("H2")) { | |||
assertEquals(1478, models.get(0).length()); | |||
} else if (dbName.startsWith("HSQL")) { | |||
// HSQL uses Double instead of Float | |||
assertEquals(1479, 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(1489, models.get(0).length()); | |||
} | |||
} | |||
@@ -143,7 +151,7 @@ public class ModelsTest { | |||
@Test | |||
public void testTableUpgrade() { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
// insert first, this will create version record automatically | |||
List<SupportedTypes> original = SupportedTypes.createList(); |
@@ -34,7 +34,7 @@ public class PrimitivesTest { | |||
@Test | |||
public void testPrimitives() { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
// insert random models in reverse order | |||
List<PrimitivesModel> models = PrimitivesModel.getList(); | |||
@@ -74,11 +74,11 @@ public class PrimitivesTest { | |||
List<PrimitivesModel> 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)); | |||
assertEquals(1, db.delete(retrievedModel)); | |||
db.close(); | |||
} |
@@ -34,7 +34,7 @@ public class RuntimeQueryTest { | |||
@Test | |||
public void testRuntimeQuery() { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(Product.getList()); | |||
Product p = new Product(); | |||
@@ -50,14 +50,15 @@ public class RuntimeQueryTest { | |||
@Test | |||
public void testExecuteQuery() throws SQLException { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(Product.getList()); | |||
// test plain statement | |||
List<Product> products = db.executeQuery(Product.class, "select * from product where unitsInStock=120"); | |||
List<Product> 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()); | |||
@@ -65,10 +66,10 @@ public class RuntimeQueryTest { | |||
db.close(); | |||
} | |||
@Test | |||
public void testBuildObjects() throws SQLException { | |||
Db db = IciqlSuite.openDb(); | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(Product.getList()); | |||
// test plain statement | |||
@@ -78,7 +79,7 @@ public class RuntimeQueryTest { | |||
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); |
@@ -60,7 +60,7 @@ public class SamplesTest { | |||
@Before | |||
public void setUp() { | |||
db = IciqlSuite.openDb(); | |||
db = IciqlSuite.openNewDb(); | |||
db.insertAll(Product.getList()); | |||
db.insertAll(Customer.getList()); | |||
db.insertAll(Order.getList()); | |||
@@ -278,7 +278,7 @@ public class SamplesTest { | |||
@Test | |||
public void testSum() { | |||
Product p = new Product(); | |||
Long sum = db.from(p).selectFirst(sum(p.unitsInStock)); | |||
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); |
@@ -41,7 +41,7 @@ public class UpdateTest { | |||
@Before | |||
public void setUp() throws Exception { | |||
db = IciqlSuite.openDb(); | |||
db = IciqlSuite.openNewDb(); | |||
db.insertAll(Product.getList()); | |||
db.insertAll(Customer.getList()); | |||
db.insertAll(Order.getList()); |
@@ -17,6 +17,7 @@ | |||
package com.iciql.test.models; | |||
import static com.iciql.Define.length; | |||
import static com.iciql.Define.primaryKey; | |||
import java.math.BigDecimal; | |||
@@ -55,6 +56,7 @@ public class ComplexObject implements Iciql { | |||
public void defineIQ() { | |||
primaryKey(id); | |||
length(name, 25); | |||
} | |||
public static List<ComplexObject> getList() { |
@@ -31,7 +31,7 @@ public class Customer { | |||
@IQColumn(length = 25) | |||
public String customerId; | |||
@IQColumn(length = 2) | |||
public String region; | |||
@@ -31,14 +31,14 @@ 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; | |||
@@ -36,7 +36,7 @@ import com.iciql.Iciql; | |||
public class Order implements Iciql { | |||
public String customerId; | |||
public Integer orderId; | |||
public Date orderDate; | |||
public Date orderDate; | |||
public BigDecimal total; | |||
public Order(String customerId, Integer orderId, String total, String orderDate) { | |||
@@ -60,9 +60,12 @@ public class Order implements Iciql { | |||
public static List<Order> 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("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); | |||
} |
@@ -81,7 +81,7 @@ public class PrimitivesModel { | |||
} | |||
return list; | |||
} | |||
@Override | |||
public String toString() { | |||
return String.valueOf(myLong); |
@@ -59,7 +59,8 @@ public class Product implements Iciql { | |||
index(productName, category); | |||
} | |||
private static Product create(int productId, String productName, String category, double unitPrice, int unitsInStock) { | |||
private static Product create(int productId, String productName, String category, double unitPrice, | |||
int unitsInStock) { | |||
return new Product(productId, productName, category, unitPrice, unitsInStock); | |||
} | |||
@@ -71,7 +72,8 @@ public class Product implements Iciql { | |||
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), }; | |||
create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29), | |||
create(10, "Ikura", "Seafood", 31.0, 31), }; | |||
return Arrays.asList(list); | |||
} |
@@ -42,7 +42,8 @@ public class ProductInheritedAnnotation extends ProductMixedAnnotation { | |||
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); | |||
return new ProductInheritedAnnotation(productId, productName, category, unitPrice, unitsInStock, | |||
mappedField); | |||
} | |||
public static List<ProductInheritedAnnotation> getData() { |
@@ -20,7 +20,8 @@ package com.iciql.test.models; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import com.iciql.Iciql.IQColumn; | |||
import com.iciql.Define; | |||
import com.iciql.Iciql; | |||
import com.iciql.Iciql.IQIndex; | |||
import com.iciql.Iciql.IQTable; | |||
@@ -29,8 +30,8 @@ import com.iciql.Iciql.IQTable; | |||
*/ | |||
@IQTable(annotationsOnly = false) | |||
@IQIndex({"name", "cat" }) | |||
public class ProductMixedAnnotation { | |||
@IQIndex({ "name", "cat" }) | |||
public class ProductMixedAnnotation implements Iciql { | |||
public Double unitPrice; | |||
public Integer unitsInStock; | |||
@@ -59,9 +60,10 @@ public class ProductMixedAnnotation { | |||
this.mappedField = mappedField; | |||
} | |||
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); | |||
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<ProductMixedAnnotation> getList() { | |||
@@ -91,4 +93,8 @@ public class ProductMixedAnnotation { | |||
return productName; | |||
} | |||
@Override | |||
public void defineIQ() { | |||
Define.length(mappedField, 25); | |||
} | |||
} |
@@ -124,13 +124,15 @@ public class SupportedTypes { | |||
public static List<SupportedTypes> createList() { | |||
List<SupportedTypes> list = Utils.newArrayList(); | |||
long now = System.currentTimeMillis(); | |||
long oneday = 24 * 60 * 60 * 1000L; | |||
for (int i = 0; i < 10; i++) { | |||
list.add(randomValue()); | |||
list.add(randomValue(now - (i * oneday))); | |||
} | |||
return list; | |||
} | |||
static SupportedTypes randomValue() { | |||
static SupportedTypes randomValue(long time) { | |||
Random rand = new Random(); | |||
SupportedTypes s = new SupportedTypes(); | |||
s.myBool = new Boolean(rand.nextBoolean()); | |||
@@ -144,10 +146,10 @@ public class SupportedTypes { | |||
// scale must match annotation | |||
s.myBigDecimal = s.myBigDecimal.setScale(5, RoundingMode.UP); | |||
s.myString = Long.toHexString(rand.nextLong()); | |||
s.myUtilDate = new java.util.Date(rand.nextLong()); | |||
s.mySqlDate = new java.sql.Date(rand.nextLong()); | |||
s.mySqlTime = new java.sql.Time(rand.nextLong()); | |||
s.mySqlTimestamp = new java.sql.Timestamp(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; | |||
@@ -181,7 +183,7 @@ public class SupportedTypes { | |||
same &= myOtherFavoriteTree.equals(s.myOtherFavoriteTree); | |||
return same; | |||
} | |||
/** | |||
* This class demonstrates the table upgrade. | |||
*/ |