@@ -1,4 +1,5 @@ | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
@@ -22,24 +22,24 @@ package com.iciql; | |||
*/ | |||
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), IN("IN", true), NOT_IN("NOT IN", true); | |||
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), IN("IN", true), NOT_IN("NOT IN", true); | |||
private String text; | |||
private boolean hasRightExpression; | |||
private String text; | |||
private boolean hasRightExpression; | |||
CompareType(String text, boolean hasRightExpression) { | |||
this.text = text; | |||
this.hasRightExpression = hasRightExpression; | |||
} | |||
CompareType(String text, boolean hasRightExpression) { | |||
this.text = text; | |||
this.hasRightExpression = hasRightExpression; | |||
} | |||
String getString() { | |||
return text; | |||
} | |||
String getString() { | |||
return text; | |||
} | |||
boolean hasRightExpression() { | |||
return hasRightExpression; | |||
} | |||
boolean hasRightExpression() { | |||
return hasRightExpression; | |||
} | |||
} |
@@ -19,56 +19,55 @@ package com.iciql; | |||
/** | |||
* A condition contains one or two operands and a compare operation. | |||
* | |||
* @param <A> | |||
* the operand type | |||
* | |||
* @param <A> the operand type | |||
*/ | |||
class Condition<A> implements Token { | |||
CompareType compareType; | |||
A x, y, z; | |||
Iterable<A> i; | |||
CompareType compareType; | |||
A x, y, z; | |||
Iterable<A> i; | |||
Condition(A x, CompareType compareType) { | |||
this(x, null, null, null, compareType); | |||
} | |||
Condition(A x, CompareType compareType) { | |||
this(x, null, null, null, compareType); | |||
} | |||
Condition(A x, A y, CompareType compareType) { | |||
this(x, y, null, null, compareType); | |||
} | |||
Condition(A x, A y, CompareType compareType) { | |||
this(x, y, null, null, compareType); | |||
} | |||
Condition(A x, A y, A z, CompareType compareType) { | |||
this(x, y, z, null, compareType); | |||
} | |||
Condition(A x, A y, A z, CompareType compareType) { | |||
this(x, y, z, null, compareType); | |||
} | |||
Condition(A x, Iterable<A> i, CompareType compareType) { | |||
this(x, null, null, i, compareType); | |||
} | |||
Condition(A x, Iterable<A> i, CompareType compareType) { | |||
this(x, null, null, i, compareType); | |||
} | |||
Condition(A x, A y, A z, Iterable<A> i, CompareType compareType) { | |||
this.compareType = compareType; | |||
this.x = x; | |||
this.y = y; | |||
this.z = z; | |||
this.i = i; | |||
} | |||
Condition(A x, A y, A z, Iterable<A> i, CompareType compareType) { | |||
this.compareType = compareType; | |||
this.x = x; | |||
this.y = y; | |||
this.z = z; | |||
this.i = i; | |||
} | |||
@SuppressWarnings("unchecked") | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
query.appendSQL(stat, null, x); | |||
stat.appendSQL(" "); | |||
stat.appendSQL(compareType.getString()); | |||
if (compareType.hasRightExpression()) { | |||
if (i == null) { | |||
stat.appendSQL(" "); | |||
if (z == null) { | |||
query.appendSQL(stat, x, y); | |||
} else { | |||
query.appendSQL(stat, x, y, z, compareType); | |||
} | |||
} else { | |||
query.appendSQL(stat, x, (Iterable<Object>)i, compareType); | |||
} | |||
} | |||
} | |||
@SuppressWarnings("unchecked") | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
query.appendSQL(stat, null, x); | |||
stat.appendSQL(" "); | |||
stat.appendSQL(compareType.getString()); | |||
if (compareType.hasRightExpression()) { | |||
if (i == null) { | |||
stat.appendSQL(" "); | |||
if (z == null) { | |||
query.appendSQL(stat, x, y); | |||
} else { | |||
query.appendSQL(stat, x, y, z, compareType); | |||
} | |||
} else { | |||
query.appendSQL(stat, x, (Iterable<Object>) i, compareType); | |||
} | |||
} | |||
} | |||
} |
@@ -22,16 +22,16 @@ package com.iciql; | |||
*/ | |||
enum ConditionAndOr implements Token { | |||
AND("AND"), OR("OR"); | |||
AND("AND"), OR("OR"); | |||
private String text; | |||
private String text; | |||
ConditionAndOr(String text) { | |||
this.text = text; | |||
} | |||
ConditionAndOr(String text) { | |||
this.text = text; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL(text); | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL(text); | |||
} | |||
} |
@@ -18,16 +18,16 @@ | |||
package com.iciql; | |||
enum ConditionOpenClose implements Token { | |||
OPEN("("), CLOSE(")"); | |||
OPEN("("), CLOSE(")"); | |||
private String text; | |||
private String text; | |||
ConditionOpenClose(String text) { | |||
this.text = text; | |||
} | |||
ConditionOpenClose(String text) { | |||
this.text = text; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL(text); | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL(text); | |||
} | |||
} |
@@ -25,38 +25,38 @@ import java.util.jar.Manifest; | |||
*/ | |||
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 API_CURRENT = "15"; | |||
public static String getVersion() { | |||
return getManifestValue("implementation-version", "0.0.0-SNAPSHOT"); | |||
} | |||
public static String getBuildDate() { | |||
return getManifestValue("build-date", "PENDING"); | |||
} | |||
private static String getManifestValue(String attrib, String defaultValue) { | |||
Class<?> clazz = Constants.class; | |||
String className = clazz.getSimpleName() + ".class"; | |||
String classPath = clazz.getResource(className).toString(); | |||
try { | |||
String manifestPath; | |||
if (classPath.indexOf('!') > -1) { | |||
manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"; | |||
} else { | |||
String pkgPath = "/" + clazz.getPackage().getName().replace('.', '/'); | |||
manifestPath = classPath.substring(0, classPath.indexOf(pkgPath)) + "/META-INF/MANIFEST.MF"; | |||
} | |||
Manifest manifest = new Manifest(new URL(manifestPath).openStream()); | |||
Attributes attr = manifest.getMainAttributes(); | |||
String value = attr.getValue(attrib); | |||
return value; | |||
} catch (Exception e) { | |||
} | |||
return defaultValue; | |||
} | |||
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 API_CURRENT = "15"; | |||
public static String getVersion() { | |||
return getManifestValue("implementation-version", "0.0.0-SNAPSHOT"); | |||
} | |||
public static String getBuildDate() { | |||
return getManifestValue("build-date", "PENDING"); | |||
} | |||
private static String getManifestValue(String attrib, String defaultValue) { | |||
Class<?> clazz = Constants.class; | |||
String className = clazz.getSimpleName() + ".class"; | |||
String classPath = clazz.getResource(className).toString(); | |||
try { | |||
String manifestPath; | |||
if (classPath.indexOf('!') > -1) { | |||
manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"; | |||
} else { | |||
String pkgPath = "/" + clazz.getPackage().getName().replace('.', '/'); | |||
manifestPath = classPath.substring(0, classPath.indexOf(pkgPath)) + "/META-INF/MANIFEST.MF"; | |||
} | |||
Manifest manifest = new Manifest(new URL(manifestPath).openStream()); | |||
Attributes attr = manifest.getMainAttributes(); | |||
String value = attr.getValue(attrib); | |||
return value; | |||
} catch (Exception e) { | |||
} | |||
return defaultValue; | |||
} | |||
} |
@@ -25,138 +25,137 @@ import java.util.List; | |||
* The Dao interface defines all CRUD methods for handling SQL object operations. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public interface Dao extends AutoCloseable { | |||
/** | |||
* Insert an object into the database. | |||
* | |||
* @param t | |||
* @return true if successful | |||
*/ | |||
<T> boolean insert(T t); | |||
/** | |||
* Insert an object into the database and return it's primary key. | |||
* | |||
* @param t | |||
* @return | |||
*/ | |||
<T> long insertAndGetKey(T t); | |||
/** | |||
* Insert all objects into the database. | |||
* | |||
* @param list | |||
*/ | |||
<T> void insertAll(List<T> list); | |||
/** | |||
* Insert all objects into the database and return the list of primary keys. | |||
* | |||
* @param t | |||
* @return a list of primary keys | |||
*/ | |||
<T> List<Long> insertAllAndGetKeys(List<T> t); | |||
/** | |||
* Updates an object in the database. | |||
* | |||
* @param t | |||
* @return true if successful | |||
*/ | |||
<T> boolean update(T t); | |||
/** | |||
* Updates all objects in the database. | |||
* | |||
* @param list | |||
*/ | |||
<T> void updateAll(List<T> list); | |||
/** | |||
* Inserts or updates an object in the database. | |||
* | |||
* @param t | |||
*/ | |||
<T> void merge(T t); | |||
/** | |||
* Deletes an object from the database. | |||
* | |||
* @param t | |||
* @return true if successful | |||
*/ | |||
<T> boolean delete(T t); | |||
/** | |||
* Deletes all objects from the database. | |||
* | |||
* @param list | |||
*/ | |||
<T> void deleteAll(List<T> list); | |||
/** | |||
* Returns the underlying Db instance for lower-level access to database methods | |||
* or direct JDBC access. | |||
* | |||
* @return the db instance | |||
*/ | |||
Db db(); | |||
/** | |||
* Close the underlying Db instance. | |||
*/ | |||
@Override | |||
void close(); | |||
/** | |||
* Used to specify custom names for method parameters to be used | |||
* for the SqlQuery or SqlUpdate annotations. | |||
* | |||
* You don't need to explicitly bind the parameters as each parameter | |||
* is accessible by the standard "argN" syntax (0-indexed). | |||
* | |||
* Additionally, if you are compiling with Java 8 AND specifying the | |||
* -parameters flag for javac, then you may use the parameter's name. | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ ElementType.PARAMETER }) | |||
public @interface Bind { | |||
String value(); | |||
} | |||
/** | |||
* | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ ElementType.PARAMETER }) | |||
public @interface BindBean { | |||
String value() default ""; | |||
} | |||
/** | |||
* Used to indicate that a method should execute a query. | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ ElementType.METHOD }) | |||
public @interface SqlQuery { | |||
String value(); | |||
} | |||
/** | |||
* Used to indicate that a method should execute a statement. | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ ElementType.METHOD }) | |||
public @interface SqlStatement { | |||
String value(); | |||
} | |||
public class BeanBinder { | |||
public void bind(BindBean bind, Object obj) { | |||
} | |||
} | |||
/** | |||
* Insert an object into the database. | |||
* | |||
* @param t | |||
* @return true if successful | |||
*/ | |||
<T> boolean insert(T t); | |||
/** | |||
* Insert an object into the database and return it's primary key. | |||
* | |||
* @param t | |||
* @return | |||
*/ | |||
<T> long insertAndGetKey(T t); | |||
/** | |||
* Insert all objects into the database. | |||
* | |||
* @param list | |||
*/ | |||
<T> void insertAll(List<T> list); | |||
/** | |||
* Insert all objects into the database and return the list of primary keys. | |||
* | |||
* @param t | |||
* @return a list of primary keys | |||
*/ | |||
<T> List<Long> insertAllAndGetKeys(List<T> t); | |||
/** | |||
* Updates an object in the database. | |||
* | |||
* @param t | |||
* @return true if successful | |||
*/ | |||
<T> boolean update(T t); | |||
/** | |||
* Updates all objects in the database. | |||
* | |||
* @param list | |||
*/ | |||
<T> void updateAll(List<T> list); | |||
/** | |||
* Inserts or updates an object in the database. | |||
* | |||
* @param t | |||
*/ | |||
<T> void merge(T t); | |||
/** | |||
* Deletes an object from the database. | |||
* | |||
* @param t | |||
* @return true if successful | |||
*/ | |||
<T> boolean delete(T t); | |||
/** | |||
* Deletes all objects from the database. | |||
* | |||
* @param list | |||
*/ | |||
<T> void deleteAll(List<T> list); | |||
/** | |||
* Returns the underlying Db instance for lower-level access to database methods | |||
* or direct JDBC access. | |||
* | |||
* @return the db instance | |||
*/ | |||
Db db(); | |||
/** | |||
* Close the underlying Db instance. | |||
*/ | |||
@Override | |||
void close(); | |||
/** | |||
* Used to specify custom names for method parameters to be used | |||
* for the SqlQuery or SqlUpdate annotations. | |||
* <p> | |||
* You don't need to explicitly bind the parameters as each parameter | |||
* is accessible by the standard "argN" syntax (0-indexed). | |||
* <p> | |||
* Additionally, if you are compiling with Java 8 AND specifying the | |||
* -parameters flag for javac, then you may use the parameter's name. | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ElementType.PARAMETER}) | |||
public @interface Bind { | |||
String value(); | |||
} | |||
/** | |||
* | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ElementType.PARAMETER}) | |||
public @interface BindBean { | |||
String value() default ""; | |||
} | |||
/** | |||
* Used to indicate that a method should execute a query. | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ElementType.METHOD}) | |||
public @interface SqlQuery { | |||
String value(); | |||
} | |||
/** | |||
* Used to indicate that a method should execute a statement. | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ElementType.METHOD}) | |||
public @interface SqlStatement { | |||
String value(); | |||
} | |||
public class BeanBinder { | |||
public void bind(BindBean bind, Object obj) { | |||
} | |||
} | |||
} |
@@ -15,81 +15,80 @@ | |||
*/ | |||
package com.iciql; | |||
import com.iciql.Iciql.Mode; | |||
import java.io.InputStream; | |||
import java.util.Properties; | |||
import com.iciql.Iciql.Mode; | |||
/** | |||
* Loads DAO statements from Properties resource files the classpath. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public class DaoClasspathStatementProvider implements DaoStatementProvider { | |||
private final Properties externalStatements; | |||
public DaoClasspathStatementProvider() { | |||
externalStatements = load(); | |||
} | |||
/** | |||
* Returns the list of statement resources to try locating. | |||
* | |||
* @return | |||
*/ | |||
protected String[] getStatementResources() { | |||
return new String[] { "/iciql.properties", "/iciql.xml", "/conf/iciql.properties", "/conf/iciql.xml" }; | |||
} | |||
/** | |||
* Loads the first statement resource found on the classpath. | |||
* | |||
* @return the loaded statements | |||
*/ | |||
private Properties load() { | |||
Properties props = new Properties(); | |||
for (String resource : getStatementResources()) { | |||
InputStream is = null; | |||
try { | |||
is = DaoProxy.class.getResourceAsStream(resource); | |||
if (is != null) { | |||
if (resource.toLowerCase().endsWith(".xml")) { | |||
// load an .XML statements file | |||
props.loadFromXML(is); | |||
} else { | |||
// load a .Properties statements file | |||
props.load(is); | |||
} | |||
break; | |||
} | |||
} catch (Exception e) { | |||
throw new IciqlException(e, "Failed to parse {0}", resource); | |||
} finally { | |||
try { | |||
is.close(); | |||
} catch (Exception e) { | |||
} | |||
} | |||
} | |||
return props; | |||
} | |||
@Override | |||
public String getStatement(String idOrStatement, Mode mode) { | |||
final String modePrefix = "%" + mode.name().toLowerCase() + "."; | |||
String value = externalStatements.getProperty(idOrStatement, idOrStatement); | |||
value = externalStatements.getProperty(modePrefix + idOrStatement, value); | |||
return value; | |||
} | |||
private final Properties externalStatements; | |||
public DaoClasspathStatementProvider() { | |||
externalStatements = load(); | |||
} | |||
/** | |||
* Returns the list of statement resources to try locating. | |||
* | |||
* @return | |||
*/ | |||
protected String[] getStatementResources() { | |||
return new String[]{"/iciql.properties", "/iciql.xml", "/conf/iciql.properties", "/conf/iciql.xml"}; | |||
} | |||
/** | |||
* Loads the first statement resource found on the classpath. | |||
* | |||
* @return the loaded statements | |||
*/ | |||
private Properties load() { | |||
Properties props = new Properties(); | |||
for (String resource : getStatementResources()) { | |||
InputStream is = null; | |||
try { | |||
is = DaoProxy.class.getResourceAsStream(resource); | |||
if (is != null) { | |||
if (resource.toLowerCase().endsWith(".xml")) { | |||
// load an .XML statements file | |||
props.loadFromXML(is); | |||
} else { | |||
// load a .Properties statements file | |||
props.load(is); | |||
} | |||
break; | |||
} | |||
} catch (Exception e) { | |||
throw new IciqlException(e, "Failed to parse {0}", resource); | |||
} finally { | |||
try { | |||
is.close(); | |||
} catch (Exception e) { | |||
} | |||
} | |||
} | |||
return props; | |||
} | |||
@Override | |||
public String getStatement(String idOrStatement, Mode mode) { | |||
final String modePrefix = "%" + mode.name().toLowerCase() + "."; | |||
String value = externalStatements.getProperty(idOrStatement, idOrStatement); | |||
value = externalStatements.getProperty(modePrefix + idOrStatement, value); | |||
return value; | |||
} | |||
} |
@@ -21,16 +21,15 @@ import com.iciql.Iciql.Mode; | |||
* Defines the interface for retrieving externalized DAO statements. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public interface DaoStatementProvider { | |||
/** | |||
* Returns the statement associated with the id. | |||
* | |||
* @param idOrStatement | |||
* @param mode | |||
* @return the statement | |||
*/ | |||
String getStatement(String idOrStatement, Mode mode); | |||
/** | |||
* Returns the statement associated with the id. | |||
* | |||
* @param idOrStatement | |||
* @param mode | |||
* @return the statement | |||
*/ | |||
String getStatement(String idOrStatement, Mode mode); | |||
} |
@@ -17,6 +17,11 @@ | |||
package com.iciql; | |||
import com.iciql.Iciql.IQTable; | |||
import com.iciql.util.JdbcUtils; | |||
import com.iciql.util.StringUtils; | |||
import com.iciql.util.Utils; | |||
import java.sql.DatabaseMetaData; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
@@ -24,11 +29,6 @@ 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 | |||
@@ -36,169 +36,158 @@ import com.iciql.util.Utils; | |||
*/ | |||
public class DbInspector { | |||
private Db db; | |||
private DatabaseMetaData metaData; | |||
private Class<? extends java.util.Date> 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<? extends java.util.Date> 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<String> generateModel(String schema, String table, String packageName, | |||
boolean annotateSchema, boolean trimStrings) { | |||
try { | |||
List<String> models = Utils.newArrayList(); | |||
List<TableInspector> 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 <T> List<ValidationRemark> validateModel(T model, boolean throwOnError) { | |||
try { | |||
TableInspector inspector = getTable(model); | |||
inspector.read(metaData); | |||
@SuppressWarnings("unchecked") | |||
Class<T> clazz = (Class<T>) model.getClass(); | |||
TableDefinition<T> 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 <T> TableInspector getTable(T model) throws SQLException { | |||
@SuppressWarnings("unchecked") | |||
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 table = forceUpperCase ? def.tableName.toUpperCase() : def.tableName; | |||
List<TableInspector> 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<TableInspector> getTables(String schema, String table) throws SQLException { | |||
ResultSet rs = null; | |||
try { | |||
rs = getMetaData().getSchemas(); | |||
ArrayList<String> schemaList = Utils.newArrayList(); | |||
while (rs.next()) { | |||
schemaList.add(rs.getString("TABLE_SCHEM")); | |||
} | |||
JdbcUtils.closeSilently(rs); | |||
String iciqlTables = DbVersion.class.getAnnotation(IQTable.class).name(); | |||
List<TableInspector> 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<TableInspector> 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); | |||
} | |||
} | |||
private Db db; | |||
private DatabaseMetaData metaData; | |||
private Class<? extends java.util.Date> 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<? extends java.util.Date> 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<String> generateModel(String schema, String table, String packageName, | |||
boolean annotateSchema, boolean trimStrings) { | |||
try { | |||
List<String> models = Utils.newArrayList(); | |||
List<TableInspector> 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 <T> List<ValidationRemark> validateModel(T model, boolean throwOnError) { | |||
try { | |||
TableInspector inspector = getTable(model); | |||
inspector.read(metaData); | |||
@SuppressWarnings("unchecked") | |||
Class<T> clazz = (Class<T>) model.getClass(); | |||
TableDefinition<T> 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 <T> TableInspector getTable(T model) throws SQLException { | |||
@SuppressWarnings("unchecked") | |||
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 table = forceUpperCase ? def.tableName.toUpperCase() : def.tableName; | |||
List<TableInspector> 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<TableInspector> getTables(String schema, String table) throws SQLException { | |||
ResultSet rs = null; | |||
try { | |||
rs = getMetaData().getSchemas(); | |||
ArrayList<String> schemaList = Utils.newArrayList(); | |||
while (rs.next()) { | |||
schemaList.add(rs.getString("TABLE_SCHEM")); | |||
} | |||
JdbcUtils.closeSilently(rs); | |||
String iciqlTables = DbVersion.class.getAnnotation(IQTable.class).name(); | |||
List<TableInspector> 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<TableInspector> 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); | |||
} | |||
} | |||
} |
@@ -27,55 +27,47 @@ import com.iciql.Iciql.IQVersion; | |||
*/ | |||
public interface DbUpgrader { | |||
/** | |||
* Defines method interface to handle database upgrades. This method is only | |||
* called if your <i>DbUpgrader</i> 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 database upgrades. This method is only | |||
* called if your <i>DbUpgrader</i> 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); | |||
/** | |||
* 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 { | |||
/** | |||
* 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 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."); | |||
} | |||
public boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion) { | |||
throw new IciqlException("Please provide your own DbUpgrader implementation."); | |||
} | |||
} | |||
} | |||
} |
@@ -23,33 +23,32 @@ import com.iciql.Iciql.IQTable; | |||
/** | |||
* A system table to track database and table versions. | |||
*/ | |||
@IQTable(name = "iq_versions", primaryKey = { "schemaName", "tableName" }, memoryTable = true) | |||
@IQTable(name = "iq_versions", primaryKey = {"schemaName", "tableName"}, memoryTable = true) | |||
public class DbVersion { | |||
@IQColumn(length = 255) | |||
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; | |||
} | |||
@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; | |||
} | |||
} |
@@ -28,31 +28,31 @@ import com.iciql.Iciql.IndexType; | |||
public class Define { | |||
private static TableDefinition<?> currentTableDefinition; | |||
private static Iciql currentTable; | |||
private static TableDefinition<?> currentTableDefinition; | |||
private static Iciql currentTable; | |||
public static void skipCreate() { | |||
checkInDefine(); | |||
currentTableDefinition.defineSkipCreate(); | |||
} | |||
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(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 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); | |||
} | |||
public static void constraintUnique(String name, Object... columns) { | |||
checkInDefine(); | |||
currentTableDefinition.defineConstraintUnique(name, columns); | |||
} | |||
/* | |||
* The variable argument type Object can't be used twice :-) | |||
* The variable argument type Object can't be used twice :-) | |||
*/ | |||
// public static void constraintForeignKey(String name, String refTableName, | |||
// ConstraintDeleteType deleteType, ConstraintUpdateType updateType, | |||
@@ -61,91 +61,91 @@ public class Define { | |||
// 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); | |||
} | |||
public static void typeAdapter(Object column, Class<? extends DataTypeAdapter<?>> typeAdapter) { | |||
checkInDefine(); | |||
currentTableDefinition.defineTypeAdapter(column, typeAdapter); | |||
} | |||
static synchronized <T> void define(TableDefinition<T> 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."); | |||
} | |||
} | |||
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); | |||
} | |||
public static void typeAdapter(Object column, Class<? extends DataTypeAdapter<?>> typeAdapter) { | |||
checkInDefine(); | |||
currentTableDefinition.defineTypeAdapter(column, typeAdapter); | |||
} | |||
static synchronized <T> void define(TableDefinition<T> 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."); | |||
} | |||
} | |||
} |
@@ -21,5 +21,5 @@ package com.iciql; | |||
* Represents the WHERE clause of a query. | |||
*/ | |||
public interface Filter { | |||
boolean where(); | |||
boolean where(); | |||
} |
@@ -24,126 +24,126 @@ import com.iciql.util.Utils; | |||
*/ | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> 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 extends Number> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
int i = 0; | |||
for (Object o : x) { | |||
if (i++ > 0) { | |||
stat.appendSQL(" AND "); | |||
} | |||
query.appendSQL(stat, null, o); | |||
} | |||
} | |||
}); | |||
} | |||
@SuppressWarnings("unchecked") | |||
public static <X> X min(X x) { | |||
Class<X> clazz = (Class<X>) x.getClass(); | |||
X o = Utils.newObject(clazz); | |||
return Db.registerToken(o, new Function("MIN", x)); | |||
} | |||
@SuppressWarnings("unchecked") | |||
public static <X> X max(X x) { | |||
Class<X> clazz = (Class<X>) 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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL("("); | |||
query.appendSQL(stat, null, x[0]); | |||
stat.appendSQL(" LIKE "); | |||
query.appendSQL(stat, x[0], x[1]); | |||
stat.appendSQL(")"); | |||
} | |||
}); | |||
} | |||
// 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 extends Number> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
int i = 0; | |||
for (Object o : x) { | |||
if (i++ > 0) { | |||
stat.appendSQL(" AND "); | |||
} | |||
query.appendSQL(stat, null, o); | |||
} | |||
} | |||
}); | |||
} | |||
@SuppressWarnings("unchecked") | |||
public static <X> X min(X x) { | |||
Class<X> clazz = (Class<X>) x.getClass(); | |||
X o = Utils.newObject(clazz); | |||
return Db.registerToken(o, new Function("MIN", x)); | |||
} | |||
@SuppressWarnings("unchecked") | |||
public static <X> X max(X x) { | |||
Class<X> clazz = (Class<X>) 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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL("("); | |||
query.appendSQL(stat, null, x[0]); | |||
stat.appendSQL(" LIKE "); | |||
query.appendSQL(stat, x[0], x[1]); | |||
stat.appendSQL(")"); | |||
} | |||
}); | |||
} | |||
} |
@@ -26,170 +26,170 @@ import java.util.regex.Pattern; | |||
*/ | |||
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 if (s.getMessage().startsWith("[SQLITE")) { | |||
// SQLite error codes | |||
final String msg = s.getMessage(); | |||
switch (s.getErrorCode()) { | |||
case 1: | |||
iciqlCode = CODE_OBJECT_NOT_FOUND; | |||
break; | |||
case 19: | |||
if (msg.contains("UNIQUE")) { | |||
iciqlCode = CODE_DUPLICATE_KEY; | |||
} else { | |||
iciqlCode = CODE_CONSTRAINT_VIOLATION; | |||
} | |||
break; | |||
default: | |||
iciqlCode = s.getErrorCode(); | |||
break; | |||
} | |||
} 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(); | |||
} | |||
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 if (s.getMessage().startsWith("[SQLITE")) { | |||
// SQLite error codes | |||
final String msg = s.getMessage(); | |||
switch (s.getErrorCode()) { | |||
case 1: | |||
iciqlCode = CODE_OBJECT_NOT_FOUND; | |||
break; | |||
case 19: | |||
if (msg.contains("UNIQUE")) { | |||
iciqlCode = CODE_DUPLICATE_KEY; | |||
} else { | |||
iciqlCode = CODE_CONSTRAINT_VIOLATION; | |||
} | |||
break; | |||
default: | |||
iciqlCode = s.getErrorCode(); | |||
break; | |||
} | |||
} 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(); | |||
} | |||
} |
@@ -17,7 +17,8 @@ | |||
package com.iciql; | |||
import static com.iciql.util.StringUtils.isNullOrEmpty; | |||
import com.iciql.TableDefinition.FieldDefinition; | |||
import com.iciql.util.StringUtils; | |||
import java.lang.reflect.Method; | |||
import java.math.BigDecimal; | |||
@@ -32,8 +33,7 @@ import java.util.Map; | |||
import java.util.UUID; | |||
import java.util.regex.Pattern; | |||
import com.iciql.TableDefinition.FieldDefinition; | |||
import com.iciql.util.StringUtils; | |||
import static com.iciql.util.StringUtils.isNullOrEmpty; | |||
/** | |||
* Utility methods for models related to type mapping, default value validation, | |||
@@ -41,469 +41,460 @@ import com.iciql.util.StringUtils; | |||
*/ | |||
class ModelUtils { | |||
/** | |||
* The list of supported data types. It is used by the runtime mapping for | |||
* CREATE statements. | |||
*/ | |||
private static final Map<Class<?>, String> SUPPORTED_TYPES = new HashMap<Class<?>, String>(); | |||
static { | |||
Map<Class<?>, 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<String, String> SQL_TYPES = new HashMap<String, String>(); | |||
static { | |||
Map<String, String> 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<String> 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: | |||
return "INT"; | |||
case ENUMID: | |||
String sqlType = SUPPORTED_TYPES.get(fieldDef.enumTypeClass); | |||
if (sqlType == null) { | |||
throw new IciqlException("Unsupported enum mapping type {0} for {1}", | |||
fieldDef.enumTypeClass, fieldDef.columnName); | |||
} | |||
return sqlType; | |||
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<? extends java.util.Date> dateTimeClass) { | |||
sqlType = sqlType.toUpperCase(); | |||
// XXX dropping "UNSIGNED" or parts like that could be trouble | |||
sqlType = sqlType.split(" ")[0].trim(); | |||
if (sqlType.indexOf('(') > -1) { | |||
// strip out length or precision | |||
sqlType = sqlType.substring(0, sqlType.indexOf('(')); | |||
} | |||
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<? extends Date> 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; | |||
} | |||
/** | |||
* The list of supported data types. It is used by the runtime mapping for | |||
* CREATE statements. | |||
*/ | |||
private static final Map<Class<?>, String> SUPPORTED_TYPES = new HashMap<Class<?>, String>(); | |||
static { | |||
Map<Class<?>, 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<String, String> SQL_TYPES = new HashMap<String, String>(); | |||
static { | |||
Map<String, String> 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<String> 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: | |||
return "INT"; | |||
case ENUMID: | |||
String sqlType = SUPPORTED_TYPES.get(fieldDef.enumTypeClass); | |||
if (sqlType == null) { | |||
throw new IciqlException("Unsupported enum mapping type {0} for {1}", | |||
fieldDef.enumTypeClass, fieldDef.columnName); | |||
} | |||
return sqlType; | |||
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<? extends java.util.Date> dateTimeClass) { | |||
sqlType = sqlType.toUpperCase(); | |||
// XXX dropping "UNSIGNED" or parts like that could be trouble | |||
sqlType = sqlType.split(" ")[0].trim(); | |||
if (sqlType.indexOf('(') > -1) { | |||
// strip out length or precision | |||
sqlType = sqlType.substring(0, sqlType.indexOf('(')); | |||
} | |||
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<? extends Date> 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; | |||
} | |||
} |
@@ -19,114 +19,114 @@ package com.iciql; | |||
public abstract class NestedConditions<T> { | |||
public static class And<T> extends NestedConditions<T> { | |||
public static class And<T> extends NestedConditions<T> { | |||
public And(Db db, T alias) { | |||
super(db, alias); | |||
} | |||
public And(Db db, T alias) { | |||
super(db, alias); | |||
} | |||
protected QueryCondition<T, Boolean> and(boolean x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Boolean> and(boolean x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Byte> and(byte x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Byte> and(byte x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Short> and(short x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Short> and(short x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Integer> and(int x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Integer> and(int x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Long> and(long x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Long> and(long x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Float> and(float x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Float> and(float x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Double> and(double x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Double> and(double x) { | |||
return where.and(x); | |||
} | |||
protected <A> QueryCondition<T, A> and(A x) { | |||
return where.and(x); | |||
} | |||
protected <A> QueryCondition<T, A> and(A x) { | |||
return where.and(x); | |||
} | |||
protected QueryWhere<T> and(And<T> conditions) { | |||
where.andOpen(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
protected QueryWhere<T> and(And<T> conditions) { | |||
where.andOpen(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
protected QueryWhere<T> and(Or<T> conditions) { | |||
where.andOpen(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
protected QueryWhere<T> and(Or<T> conditions) { | |||
where.andOpen(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
} | |||
} | |||
public static class Or<T> extends NestedConditions<T> { | |||
public static class Or<T> extends NestedConditions<T> { | |||
public Or(Db db, T alias) { | |||
super(db, alias); | |||
} | |||
public Or(Db db, T alias) { | |||
super(db, alias); | |||
} | |||
protected QueryCondition<T, Boolean> or(boolean x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Boolean> or(boolean x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Byte> or(byte x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Byte> or(byte x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Short> or(short x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Short> or(short x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Integer> or(int x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Integer> or(int x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Long> or(long x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Long> or(long x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Float> or(float x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Float> or(float x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Double> or(double x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Double> or(double x) { | |||
return where.or(x); | |||
} | |||
protected <A> QueryCondition<T, A> or(A x) { | |||
return where.or(x); | |||
} | |||
protected <A> QueryCondition<T, A> or(A x) { | |||
return where.or(x); | |||
} | |||
protected QueryWhere<T> or(And<T> conditions) { | |||
where.orOpen(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
protected QueryWhere<T> or(And<T> conditions) { | |||
where.orOpen(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
protected QueryWhere<T> or(Or<T> conditions) { | |||
where.orOpen(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
protected QueryWhere<T> or(Or<T> conditions) { | |||
where.orOpen(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
} | |||
} | |||
QueryWhere<T> where; | |||
QueryWhere<T> where; | |||
private NestedConditions(Db db, T alias) { | |||
where = new QueryWhere<T>(Query.rebuild(db, alias)); | |||
} | |||
private NestedConditions(Db db, T alias) { | |||
where = new QueryWhere<T>(Query.rebuild(db, alias)); | |||
} | |||
} |
@@ -19,37 +19,36 @@ package com.iciql; | |||
/** | |||
* An expression to order by in a query. | |||
* | |||
* @param <T> | |||
* the query data type | |||
* | |||
* @param <T> the query data type | |||
*/ | |||
class OrderExpression<T> { | |||
private Query<T> query; | |||
private Object expression; | |||
private boolean desc; | |||
private boolean nullsFirst; | |||
private boolean nullsLast; | |||
private Query<T> query; | |||
private Object expression; | |||
private boolean desc; | |||
private boolean nullsFirst; | |||
private boolean nullsLast; | |||
OrderExpression(Query<T> query, Object expression, boolean desc, boolean nullsFirst, boolean nullsLast) { | |||
this.query = query; | |||
this.expression = expression; | |||
this.desc = desc; | |||
this.nullsFirst = nullsFirst; | |||
this.nullsLast = nullsLast; | |||
} | |||
OrderExpression(Query<T> 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"); | |||
} | |||
} | |||
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"); | |||
} | |||
} | |||
} |
@@ -18,43 +18,37 @@ package com.iciql; | |||
/** | |||
* This class represents a "between y and z" condition. | |||
* | |||
* @param <T> | |||
* the return type of the query | |||
* @param <A> | |||
* the incomplete condition data type | |||
* | |||
* @param <T> the return type of the query | |||
* @param <A> the incomplete condition data type | |||
*/ | |||
public class QueryBetween<T, A> { | |||
private Query<T> query; | |||
private A x; | |||
private A y; | |||
private Query<T> 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<T> query, A x, A y) { | |||
this.query = query; | |||
this.x = x; | |||
this.y = 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<T> 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<T> and(A z) { | |||
query.addConditionToken(new Condition<A>(x, y, z, CompareType.BETWEEN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
/** | |||
* Set the upper bound of the between condition. | |||
* | |||
* @param z the upper bound of the between condition | |||
* @return the query | |||
*/ | |||
public QueryWhere<T> and(A z) { | |||
query.addConditionToken(new Condition<A>(x, y, z, CompareType.BETWEEN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
} |
@@ -21,128 +21,126 @@ import com.iciql.util.Utils; | |||
/** | |||
* This class represents a query with an incomplete condition. | |||
* | |||
* @param <T> | |||
* the return type of the query | |||
* @param <A> | |||
* the incomplete condition data type | |||
* | |||
* @param <T> the return type of the query | |||
* @param <A> the incomplete condition data type | |||
*/ | |||
public class QueryCondition<T, A> { | |||
private Query<T> query; | |||
private A x; | |||
QueryCondition(Query<T> query, A x) { | |||
this.query = query; | |||
this.x = x; | |||
} | |||
public <Q, Z> QueryWhere<T> in(SubQuery<Q, Z> q) { | |||
query.addConditionToken(new SubQueryCondition<A, Q, Z>(x, q)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> oneOf(A... a) { | |||
return oneOf(Utils.newArrayIterable(a)); | |||
} | |||
public QueryWhere<T> oneOf(Iterable<A> i) { | |||
query.addConditionToken(new Condition<A>(x, i, CompareType.IN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> noneOf(A... a) { | |||
return noneOf(Utils.newArrayIterable(a)); | |||
} | |||
public QueryWhere<T> noneOf(Iterable<A> i) { | |||
query.addConditionToken(new Condition<A>(x, i, CompareType.NOT_IN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> is(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.EQUAL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> isNot(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.NOT_EQUAL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> isNull() { | |||
query.addConditionToken(new Condition<A>(x, CompareType.IS_NULL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> isNotNull() { | |||
query.addConditionToken(new Condition<A>(x, CompareType.IS_NOT_NULL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> exceeds(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.EXCEEDS)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> atLeast(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.AT_LEAST)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> lessThan(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.LESS_THAN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> atMost(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.AT_MOST)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryBetween<T, A> between(A y) { | |||
return new QueryBetween<T, A>(query, x, y); | |||
} | |||
public QueryWhere<T> like(A pattern) { | |||
query.addConditionToken(new Condition<A>(x, pattern, CompareType.LIKE)); | |||
return new QueryWhere<T>(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<T> isParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.EQUAL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> isNotParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.NOT_EQUAL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> exceedsParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.EXCEEDS)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> lessThanParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.LESS_THAN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> atMostParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.AT_MOST)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> likeParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.LIKE)); | |||
return new QueryWhere<T>(query); | |||
} | |||
private Query<T> query; | |||
private A x; | |||
QueryCondition(Query<T> query, A x) { | |||
this.query = query; | |||
this.x = x; | |||
} | |||
public <Q, Z> QueryWhere<T> in(SubQuery<Q, Z> q) { | |||
query.addConditionToken(new SubQueryCondition<A, Q, Z>(x, q)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> oneOf(A... a) { | |||
return oneOf(Utils.newArrayIterable(a)); | |||
} | |||
public QueryWhere<T> oneOf(Iterable<A> i) { | |||
query.addConditionToken(new Condition<A>(x, i, CompareType.IN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> noneOf(A... a) { | |||
return noneOf(Utils.newArrayIterable(a)); | |||
} | |||
public QueryWhere<T> noneOf(Iterable<A> i) { | |||
query.addConditionToken(new Condition<A>(x, i, CompareType.NOT_IN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> is(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.EQUAL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> isNot(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.NOT_EQUAL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> isNull() { | |||
query.addConditionToken(new Condition<A>(x, CompareType.IS_NULL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> isNotNull() { | |||
query.addConditionToken(new Condition<A>(x, CompareType.IS_NOT_NULL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> exceeds(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.EXCEEDS)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> atLeast(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.AT_LEAST)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> lessThan(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.LESS_THAN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> atMost(A y) { | |||
query.addConditionToken(new Condition<A>(x, y, CompareType.AT_MOST)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryBetween<T, A> between(A y) { | |||
return new QueryBetween<T, A>(query, x, y); | |||
} | |||
public QueryWhere<T> like(A pattern) { | |||
query.addConditionToken(new Condition<A>(x, pattern, CompareType.LIKE)); | |||
return new QueryWhere<T>(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<T> isParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.EQUAL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> isNotParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.NOT_EQUAL)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> exceedsParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.EXCEEDS)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> lessThanParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.LESS_THAN)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> atMostParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.AT_MOST)); | |||
return new QueryWhere<T>(query); | |||
} | |||
public QueryWhere<T> likeParameter() { | |||
query.addConditionToken(new RuntimeParameter<A>(x, CompareType.LIKE)); | |||
return new QueryWhere<T>(query); | |||
} | |||
} |
@@ -23,53 +23,53 @@ package com.iciql; | |||
public class QueryJoin<T> { | |||
private Query<T> query; | |||
private SelectTable<T> join; | |||
private Query<T> query; | |||
private SelectTable<T> join; | |||
QueryJoin(Query<T> query, SelectTable<T> join) { | |||
this.query = query; | |||
this.join = join; | |||
} | |||
QueryJoin(Query<T> query, SelectTable<T> join) { | |||
this.query = query; | |||
this.join = join; | |||
} | |||
public QueryJoinCondition<T, Boolean> on(boolean x) { | |||
query.getFrom().getAliasDefinition().checkMultipleBooleans(); | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Boolean> on(boolean x) { | |||
query.getFrom().getAliasDefinition().checkMultipleBooleans(); | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Byte> on(byte x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Byte> on(byte x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Short> on(short x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Short> on(short x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Integer> on(int x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Integer> on(int x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Long> on(long x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Long> on(long x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Float> on(float x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Float> on(float x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Double> on(double x) { | |||
return addPrimitive(x); | |||
} | |||
public QueryJoinCondition<T, Double> on(double x) { | |||
return addPrimitive(x); | |||
} | |||
private <A> QueryJoinCondition<T, A> addPrimitive(A x) { | |||
A alias = query.getPrimitiveAliasByValue(x); | |||
if (alias == null) { | |||
// this will result in an unmapped field exception | |||
return new QueryJoinCondition<T, A>(query, join, x); | |||
} | |||
return new QueryJoinCondition<T, A>(query, join, alias); | |||
} | |||
private <A> QueryJoinCondition<T, A> addPrimitive(A x) { | |||
A alias = query.getPrimitiveAliasByValue(x); | |||
if (alias == null) { | |||
// this will result in an unmapped field exception | |||
return new QueryJoinCondition<T, A>(query, join, x); | |||
} | |||
return new QueryJoinCondition<T, A>(query, join, alias); | |||
} | |||
public <A> QueryJoinCondition<T, A> on(A x) { | |||
return new QueryJoinCondition<T, A>(query, join, x); | |||
} | |||
public <A> QueryJoinCondition<T, A> on(A x) { | |||
return new QueryJoinCondition<T, A>(query, join, x); | |||
} | |||
} |
@@ -20,64 +20,63 @@ package com.iciql; | |||
/** | |||
* This class represents a query with join and an incomplete condition. | |||
* | |||
* @param <A> | |||
* the incomplete condition data type | |||
* | |||
* @param <A> the incomplete condition data type | |||
*/ | |||
public class QueryJoinCondition<T, A> { | |||
private Query<T> query; | |||
private SelectTable<T> join; | |||
private A x; | |||
private Query<T> query; | |||
private SelectTable<T> join; | |||
private A x; | |||
QueryJoinCondition(Query<T> query, SelectTable<T> join, A x) { | |||
this.query = query; | |||
this.join = join; | |||
this.x = x; | |||
} | |||
QueryJoinCondition(Query<T> query, SelectTable<T> join, A x) { | |||
this.query = query; | |||
this.join = join; | |||
this.x = x; | |||
} | |||
public Query<T> is(boolean y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(boolean y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(byte y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(byte y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(short y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(short y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(int y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(int y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(long y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(long y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(float y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(float y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(double y) { | |||
return addPrimitive(y); | |||
} | |||
public Query<T> is(double y) { | |||
return addPrimitive(y); | |||
} | |||
@SuppressWarnings("unchecked") | |||
private Query<T> addPrimitive(Object o) { | |||
A alias = query.getPrimitiveAliasByValue((A) o); | |||
if (alias == null) { | |||
join.addConditionToken(new Condition<A>(x, (A) o, CompareType.EQUAL)); | |||
} else { | |||
join.addConditionToken(new Condition<A>(x, alias, CompareType.EQUAL)); | |||
} | |||
return query; | |||
} | |||
@SuppressWarnings("unchecked") | |||
private Query<T> addPrimitive(Object o) { | |||
A alias = query.getPrimitiveAliasByValue((A) o); | |||
if (alias == null) { | |||
join.addConditionToken(new Condition<A>(x, (A) o, CompareType.EQUAL)); | |||
} else { | |||
join.addConditionToken(new Condition<A>(x, alias, CompareType.EQUAL)); | |||
} | |||
return query; | |||
} | |||
public Query<T> is(A y) { | |||
join.addConditionToken(new Condition<A>(x, y, CompareType.EQUAL)); | |||
return query; | |||
} | |||
public Query<T> is(A y) { | |||
join.addConditionToken(new Condition<A>(x, y, CompareType.EQUAL)); | |||
return query; | |||
} | |||
} |
@@ -20,30 +20,29 @@ 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 <A> | |||
* the operand type | |||
* | |||
* @param <A> the operand type | |||
*/ | |||
class RuntimeParameter<A> implements Token { | |||
public final static String PARAMETER = ""; | |||
A x; | |||
CompareType compareType; | |||
RuntimeParameter(A x, CompareType type) { | |||
this.x = x; | |||
this.compareType = type; | |||
} | |||
public final static String PARAMETER = ""; | |||
A x; | |||
CompareType compareType; | |||
RuntimeParameter(A x, CompareType type) { | |||
this.x = x; | |||
this.compareType = type; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
query.appendSQL(stat, null, x); | |||
stat.appendSQL(" "); | |||
stat.appendSQL(compareType.getString()); | |||
if (compareType.hasRightExpression()) { | |||
stat.appendSQL(" "); | |||
query.appendSQL(stat, x, PARAMETER); | |||
} | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
query.appendSQL(stat, null, x); | |||
stat.appendSQL(" "); | |||
stat.appendSQL(compareType.getString()); | |||
if (compareType.hasRightExpression()) { | |||
stat.appendSQL(" "); | |||
query.appendSQL(stat, x, PARAMETER); | |||
} | |||
} | |||
} |
@@ -15,43 +15,40 @@ | |||
*/ | |||
package com.iciql; | |||
import java.text.MessageFormat; | |||
import com.iciql.util.StringUtils; | |||
import java.text.MessageFormat; | |||
/** | |||
* Represents a traditional PreparedStatment fragment like "id=?, name=?". | |||
* | |||
*/ | |||
public class RuntimeToken implements Token { | |||
final String fragment; | |||
final Object[] args; | |||
final String fragment; | |||
final Object[] args; | |||
public RuntimeToken(String fragment, Object... args) { | |||
this.fragment = fragment; | |||
this.args = args == null ? new Object[0] : 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 <T> void appendSQL(SQLStatement stat, Query<T> 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); | |||
} | |||
} | |||
/** | |||
* 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 <T> void appendSQL(SQLStatement stat, Query<T> 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); | |||
} | |||
} | |||
} |
@@ -18,210 +18,188 @@ | |||
package com.iciql; | |||
import java.sql.ResultSet; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.TableDefinition.IndexDefinition; | |||
import java.sql.ResultSet; | |||
/** | |||
* This interface defines points where iciql can build different statements | |||
* depending on the database used. | |||
*/ | |||
public interface SQLDialect { | |||
/** | |||
* Registers the type adapter instance. | |||
* | |||
* @param typeAdapter | |||
*/ | |||
void registerAdapter(DataTypeAdapter<?> typeAdapter); | |||
/** | |||
* Returns the registered instance of the type adapter. | |||
* | |||
* @param typeAdapter | |||
* @return the type adapter instance | |||
*/ | |||
DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter); | |||
/** | |||
* Serialize the Java object into a type or format that the database will accept. | |||
* | |||
* @param value | |||
* @param typeAdapter | |||
* @return the serialized object | |||
*/ | |||
<T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter); | |||
/** | |||
* Deserialize the object received from the database into a Java type. | |||
* | |||
* @param rs | |||
* @param columnIndex | |||
* @param targetType | |||
* @param typeAdapter | |||
* @return the deserialized object | |||
*/ | |||
Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter); | |||
/** | |||
* Configure the dialect. | |||
* | |||
* @param db | |||
*/ | |||
void configureDialect(Db db); | |||
/** | |||
* Returns true if savepoints are supported. | |||
* | |||
* @return true if savepoints may be used. | |||
*/ | |||
boolean supportsSavePoints(); | |||
/** | |||
* 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 | |||
*/ | |||
<T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def); | |||
/** | |||
* Get the DROP TABLE statement. | |||
* | |||
* @param stat | |||
* @param def | |||
*/ | |||
<T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def); | |||
/** | |||
* Get the CREATE VIEW statement. | |||
* | |||
* @param stat | |||
* return the SQL statement | |||
* @param def | |||
* table definition | |||
*/ | |||
<T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def); | |||
/** | |||
* Get the CREATE VIEW statement. | |||
* | |||
* @param stat | |||
* return the SQL statement | |||
* @param def | |||
* table definition | |||
* @param fromWhere | |||
*/ | |||
<T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def, String fromWhere); | |||
/** | |||
* Get the DROP VIEW statement. | |||
* | |||
* @param stat | |||
* return the SQL statement | |||
* @param def | |||
* table definition | |||
*/ | |||
<T> void prepareDropView(SQLStatement stat, TableDefinition<T> 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 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 | |||
*/ | |||
<T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition<T> 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. | |||
* <p> | |||
* Either java.util.Date or java.sql.Timestamp | |||
* | |||
* @return preferred DATETIME class | |||
*/ | |||
Class<? extends java.util.Date> 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 prepareStringParameter(Object o); | |||
/** | |||
* Returns the name of a formatted column identifier for the dialect. | |||
* | |||
* @param name | |||
* the column name | |||
* @return the column name without formatting syntax | |||
*/ | |||
String extractColumnName(String name); | |||
/** | |||
* Registers the type adapter instance. | |||
* | |||
* @param typeAdapter | |||
*/ | |||
void registerAdapter(DataTypeAdapter<?> typeAdapter); | |||
/** | |||
* Returns the registered instance of the type adapter. | |||
* | |||
* @param typeAdapter | |||
* @return the type adapter instance | |||
*/ | |||
DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter); | |||
/** | |||
* Serialize the Java object into a type or format that the database will accept. | |||
* | |||
* @param value | |||
* @param typeAdapter | |||
* @return the serialized object | |||
*/ | |||
<T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter); | |||
/** | |||
* Deserialize the object received from the database into a Java type. | |||
* | |||
* @param rs | |||
* @param columnIndex | |||
* @param targetType | |||
* @param typeAdapter | |||
* @return the deserialized object | |||
*/ | |||
Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter); | |||
/** | |||
* Configure the dialect. | |||
* | |||
* @param db | |||
*/ | |||
void configureDialect(Db db); | |||
/** | |||
* Returns true if savepoints are supported. | |||
* | |||
* @return true if savepoints may be used. | |||
*/ | |||
boolean supportsSavePoints(); | |||
/** | |||
* 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 | |||
*/ | |||
<T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def); | |||
/** | |||
* Get the DROP TABLE statement. | |||
* | |||
* @param stat | |||
* @param def | |||
*/ | |||
<T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def); | |||
/** | |||
* Get the CREATE VIEW statement. | |||
* | |||
* @param stat return the SQL statement | |||
* @param def table definition | |||
*/ | |||
<T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def); | |||
/** | |||
* Get the CREATE VIEW statement. | |||
* | |||
* @param stat return the SQL statement | |||
* @param def table definition | |||
* @param fromWhere | |||
*/ | |||
<T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def, String fromWhere); | |||
/** | |||
* Get the DROP VIEW statement. | |||
* | |||
* @param stat return the SQL statement | |||
* @param def table definition | |||
*/ | |||
<T> void prepareDropView(SQLStatement stat, TableDefinition<T> 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 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 | |||
*/ | |||
<T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition<T> 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. | |||
* <p> | |||
* Either java.util.Date or java.sql.Timestamp | |||
* | |||
* @return preferred DATETIME class | |||
*/ | |||
Class<? extends java.util.Date> 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 prepareStringParameter(Object o); | |||
/** | |||
* Returns the name of a formatted column identifier for the dialect. | |||
* | |||
* @param name the column name | |||
* @return the column name without formatting syntax | |||
*/ | |||
String extractColumnName(String name); | |||
} |
@@ -23,49 +23,49 @@ import com.iciql.util.StatementBuilder; | |||
*/ | |||
public class SQLDialectDerby extends SQLDialectDefault { | |||
@Override | |||
public Class<? extends java.util.Date> getDateTimeClass() { | |||
return java.sql.Timestamp.class; | |||
} | |||
@Override | |||
public Class<? extends java.util.Date> 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 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 (databaseMajorVersion >= 10 && databaseMinorVersion >= 5) { | |||
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 void appendLimitOffset(SQLStatement stat, long limit, long offset) { | |||
// FETCH/OFFSET added in 10.5 | |||
if (databaseMajorVersion >= 10 && databaseMinorVersion >= 5) { | |||
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 | |||
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 <T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def) { | |||
StatementBuilder buff = new StatementBuilder("DROP TABLE " | |||
+ prepareTableName(def.schemaName, def.tableName)); | |||
stat.setSQL(buff.toString()); | |||
return; | |||
} | |||
@Override | |||
public <T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def) { | |||
StatementBuilder buff = new StatementBuilder("DROP TABLE " | |||
+ prepareTableName(def.schemaName, def.tableName)); | |||
stat.setSQL(buff.toString()); | |||
return; | |||
} | |||
} |
@@ -25,112 +25,112 @@ import com.iciql.util.StatementBuilder; | |||
*/ | |||
public class SQLDialectH2 extends SQLDialectDefault { | |||
/** | |||
* CACHED tables are created by default. MEMORY tables are created upon | |||
* request. | |||
*/ | |||
@Override | |||
protected <T> String prepareCreateTable(TableDefinition<T> def) { | |||
if (def.memoryTable) { | |||
return "CREATE MEMORY TABLE IF NOT EXISTS"; | |||
} else { | |||
return "CREATE CACHED TABLE IF NOT EXISTS"; | |||
} | |||
} | |||
/** | |||
* CACHED tables are created by default. MEMORY tables are created upon | |||
* request. | |||
*/ | |||
@Override | |||
protected <T> String prepareCreateTable(TableDefinition<T> def) { | |||
if (def.memoryTable) { | |||
return "CREATE MEMORY TABLE IF NOT EXISTS"; | |||
} else { | |||
return "CREATE CACHED TABLE IF NOT EXISTS"; | |||
} | |||
} | |||
@Override | |||
protected <T> String prepareCreateView(TableDefinition<T> def) { | |||
return "CREATE VIEW IF NOT EXISTS"; | |||
} | |||
@Override | |||
protected <T> String prepareCreateView(TableDefinition<T> def) { | |||
return "CREATE VIEW IF NOT EXISTS"; | |||
} | |||
@Override | |||
public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) { | |||
StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " | |||
+ prepareTableName(def.schemaName, def.tableName)); | |||
stat.setSQL(buff.toString()); | |||
return; | |||
} | |||
@Override | |||
public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> 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 | |||
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 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 <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(); | |||
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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
buff.append(')'); | |||
stat.setSQL(buff.toString()); | |||
} | |||
@Override | |||
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(); | |||
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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
buff.append(')'); | |||
stat.setSQL(buff.toString()); | |||
} | |||
} |
@@ -16,135 +16,135 @@ | |||
package com.iciql; | |||
import java.text.MessageFormat; | |||
import com.iciql.TableDefinition.FieldDefinition; | |||
import com.iciql.util.StatementBuilder; | |||
import java.text.MessageFormat; | |||
/** | |||
* HyperSQL database dialect. | |||
*/ | |||
public class SQLDialectHSQL extends SQLDialectDefault { | |||
/** | |||
* CACHED tables are created by default. MEMORY tables are created upon | |||
* request. | |||
*/ | |||
@Override | |||
protected <T> String prepareCreateTable(TableDefinition<T> def) { | |||
if (def.memoryTable) { | |||
return "CREATE MEMORY TABLE IF NOT EXISTS"; | |||
} else { | |||
return "CREATE CACHED TABLE IF NOT EXISTS"; | |||
} | |||
} | |||
/** | |||
* CACHED tables are created by default. MEMORY tables are created upon | |||
* request. | |||
*/ | |||
@Override | |||
protected <T> String prepareCreateTable(TableDefinition<T> def) { | |||
if (def.memoryTable) { | |||
return "CREATE MEMORY TABLE IF NOT EXISTS"; | |||
} else { | |||
return "CREATE CACHED TABLE IF NOT EXISTS"; | |||
} | |||
} | |||
@Override | |||
public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) { | |||
StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " | |||
+ prepareTableName(def.schemaName, def.tableName)); | |||
stat.setSQL(buff.toString()); | |||
return; | |||
} | |||
@Override | |||
public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> 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 | |||
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 <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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
@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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
// map to temporary table | |||
buff.resetCount(); | |||
buff.append(")) AS vals ("); | |||
for (FieldDefinition field : def.fields) { | |||
buff.appendExceptFirst(", "); | |||
buff.append(prepareColumnName(valuePrefix + field.columnName)); | |||
} | |||
// 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 "); | |||
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('='); | |||
} | |||
} | |||
// 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)); | |||
} | |||
// 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()); | |||
} | |||
// 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()); | |||
} | |||
} |
@@ -22,37 +22,37 @@ package com.iciql; | |||
*/ | |||
public class SQLDialectMSSQL extends SQLDialectDefault { | |||
@Override | |||
public String extractColumnName(String name) { | |||
return super.extractColumnName(name).replace('[', ' ').replace(']', ' ').trim(); | |||
} | |||
/** | |||
* 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()); | |||
} | |||
@Override | |||
public String extractColumnName(String name) { | |||
return super.extractColumnName(name).replace('[', ' ').replace(']', ' ').trim(); | |||
} | |||
/** | |||
* 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()); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -24,71 +24,71 @@ import com.iciql.util.StatementBuilder; | |||
*/ | |||
public class SQLDialectMySQL extends SQLDialectDefault { | |||
@Override | |||
public String convertSqlType(String sqlType) { | |||
if (sqlType.equals("CLOB")) { | |||
return "TEXT"; | |||
} | |||
return sqlType; | |||
} | |||
@Override | |||
public String convertSqlType(String sqlType) { | |||
if (sqlType.equals("CLOB")) { | |||
return "TEXT"; | |||
} | |||
return sqlType; | |||
} | |||
@Override | |||
protected <T> String prepareCreateTable(TableDefinition<T> def) { | |||
return "CREATE TABLE IF NOT EXISTS"; | |||
} | |||
@Override | |||
protected <T> String prepareCreateTable(TableDefinition<T> def) { | |||
return "CREATE TABLE IF NOT EXISTS"; | |||
} | |||
@Override | |||
public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) { | |||
StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " | |||
+ prepareTableName(def.schemaName, def.tableName)); | |||
stat.setSQL(buff.toString()); | |||
return; | |||
} | |||
@Override | |||
public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> 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 | |||
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 | |||
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 <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)).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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
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()); | |||
} | |||
@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)).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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
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()); | |||
} | |||
} |
@@ -25,137 +25,137 @@ import com.iciql.util.StatementBuilder; | |||
*/ | |||
public class SQLDialectPostgreSQL extends SQLDialectDefault { | |||
@Override | |||
public Class<? extends java.util.Date> 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()); | |||
} | |||
@Override | |||
public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, | |||
TableDefinition<T> def, Object obj) { | |||
FieldDefinition primaryKey = null; | |||
for (FieldDefinition field : def.fields) { | |||
if (field.isPrimaryKey) { | |||
primaryKey = field; | |||
} | |||
} | |||
if (primaryKey == null || databaseVersion < 9.5f) { | |||
// simulated UPSERT for <= 9.4 release | |||
super.prepareMerge(stat, schemaName, tableName, def, obj); | |||
return; | |||
} | |||
// official UPSERT added in 9.5 release | |||
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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
buff.append(") ON CONFLICT ("); | |||
buff.resetCount(); | |||
for (FieldDefinition field : def.fields) { | |||
if (field.isPrimaryKey) { | |||
buff.appendExceptFirst(", "); | |||
buff.append(field.columnName); | |||
} | |||
} | |||
buff.append(") DO UPDATE SET "); | |||
buff.resetCount(); | |||
for (FieldDefinition field : def.fields) { | |||
buff.appendExceptFirst(", "); | |||
buff.append(field.columnName); | |||
buff.append("=?"); | |||
Object value = def.getValue(obj, field); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
stat.setSQL(buff.toString()); | |||
} | |||
@Override | |||
public Class<? extends java.util.Date> 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()); | |||
} | |||
@Override | |||
public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, | |||
TableDefinition<T> def, Object obj) { | |||
FieldDefinition primaryKey = null; | |||
for (FieldDefinition field : def.fields) { | |||
if (field.isPrimaryKey) { | |||
primaryKey = field; | |||
} | |||
} | |||
if (primaryKey == null || databaseVersion < 9.5f) { | |||
// simulated UPSERT for <= 9.4 release | |||
super.prepareMerge(stat, schemaName, tableName, def, obj); | |||
return; | |||
} | |||
// official UPSERT added in 9.5 release | |||
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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
buff.append(") ON CONFLICT ("); | |||
buff.resetCount(); | |||
for (FieldDefinition field : def.fields) { | |||
if (field.isPrimaryKey) { | |||
buff.appendExceptFirst(", "); | |||
buff.append(field.columnName); | |||
} | |||
} | |||
buff.append(") DO UPDATE SET "); | |||
buff.resetCount(); | |||
for (FieldDefinition field : def.fields) { | |||
buff.appendExceptFirst(", "); | |||
buff.append(field.columnName); | |||
buff.append("=?"); | |||
Object value = def.getValue(obj, field); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
stat.setSQL(buff.toString()); | |||
} | |||
} |
@@ -16,165 +16,165 @@ | |||
package com.iciql; | |||
import java.sql.Date; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.sql.Time; | |||
import java.sql.Timestamp; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.TableDefinition.FieldDefinition; | |||
import com.iciql.TableDefinition.IndexDefinition; | |||
import com.iciql.util.IciqlLogger; | |||
import com.iciql.util.StatementBuilder; | |||
import java.sql.Date; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.sql.Time; | |||
import java.sql.Timestamp; | |||
/** | |||
* SQLite database dialect. | |||
*/ | |||
public class SQLDialectSQLite extends SQLDialectDefault { | |||
@Override | |||
public boolean supportsSavePoints() { | |||
// SAVEPOINT support was added after the 3.8.7 release | |||
String [] chunks = productVersion.split("\\."); | |||
if (Integer.parseInt(chunks[0]) > 3) { | |||
return true; | |||
} | |||
float f = Float.parseFloat(chunks[1] + "." + chunks[2]); | |||
return (f > 8.7); | |||
} | |||
@Override | |||
protected <T> String prepareCreateTable(TableDefinition<T> def) { | |||
return "CREATE TABLE IF NOT EXISTS"; | |||
} | |||
@Override | |||
protected <T> String prepareCreateView(TableDefinition<T> def) { | |||
return "CREATE VIEW IF NOT EXISTS"; | |||
} | |||
@Override | |||
public String convertSqlType(String sqlType) { | |||
if (isIntegerType(sqlType)) { | |||
return "INTEGER"; | |||
} | |||
return sqlType; | |||
} | |||
@Override | |||
protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, | |||
boolean isAutoIncrement, boolean isPrimaryKey) { | |||
String convertedType = convertSqlType(dataType); | |||
buff.append(convertedType); | |||
if (isPrimaryKey) { | |||
buff.append(" PRIMARY KEY"); | |||
if (isAutoIncrement) { | |||
buff.append(" AUTOINCREMENT"); | |||
} | |||
return true; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) { | |||
StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " | |||
+ prepareTableName(def.schemaName, def.tableName)); | |||
stat.setSQL(buff.toString()); | |||
return; | |||
} | |||
@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 IF NOT EXISTS "); | |||
buff.append(index.indexName); | |||
buff.append(" ON "); | |||
buff.append(tableName); | |||
buff.append("("); | |||
for (String col : index.columnNames) { | |||
buff.appendExceptFirst(", "); | |||
buff.append(prepareColumnName(col)); | |||
} | |||
buff.append(") "); | |||
stat.setSQL(buff.toString().trim()); | |||
} | |||
@Override | |||
public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, | |||
TableDefinition<T> def, Object obj) { | |||
StatementBuilder buff = new StatementBuilder("INSERT OR REPLACE INTO "); | |||
buff.append(prepareTableName(schemaName, tableName)).append(" ("); | |||
buff.resetCount(); | |||
for (FieldDefinition field : def.fields) { | |||
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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
buff.append(')'); | |||
stat.setSQL(buff.toString()); | |||
} | |||
@Override | |||
public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter) { | |||
try { | |||
return super.deserialize(rs, columnIndex, targetType, typeAdapter); | |||
} catch (IciqlException e) { | |||
if (typeAdapter == null && e.getMessage().startsWith("Can not convert")) { | |||
try { | |||
// give the SQLite JDBC driver an opportunity to deserialize DateTime objects | |||
if (Timestamp.class.equals(targetType)) { | |||
return rs.getTimestamp(columnIndex); | |||
} else if (Time.class.equals(targetType)) { | |||
return rs.getTime(columnIndex); | |||
} else if (Date.class.equals(targetType)) { | |||
return rs.getDate(columnIndex); | |||
} else if (java.util.Date.class.equals(targetType)) { | |||
Timestamp timestamp = rs.getTimestamp(columnIndex); | |||
return new java.util.Date(timestamp.getTime()); | |||
} | |||
} catch (SQLException x) { | |||
throw new IciqlException(x, "Can not convert the value at column {0} to {1}", | |||
columnIndex, targetType.getName()); | |||
} | |||
} | |||
// rethrow e | |||
throw e; | |||
} | |||
} | |||
@Override | |||
public String prepareStringParameter(Object o) { | |||
if (o instanceof Boolean) { | |||
// SQLite does not have an explicit BOOLEAN type | |||
Boolean bool = (Boolean) o; | |||
return bool ? "1" : "0"; | |||
} | |||
return super.prepareStringParameter(o); | |||
} | |||
} | |||
@Override | |||
public boolean supportsSavePoints() { | |||
// SAVEPOINT support was added after the 3.8.7 release | |||
String[] chunks = productVersion.split("\\."); | |||
if (Integer.parseInt(chunks[0]) > 3) { | |||
return true; | |||
} | |||
float f = Float.parseFloat(chunks[1] + "." + chunks[2]); | |||
return (f > 8.7); | |||
} | |||
@Override | |||
protected <T> String prepareCreateTable(TableDefinition<T> def) { | |||
return "CREATE TABLE IF NOT EXISTS"; | |||
} | |||
@Override | |||
protected <T> String prepareCreateView(TableDefinition<T> def) { | |||
return "CREATE VIEW IF NOT EXISTS"; | |||
} | |||
@Override | |||
public String convertSqlType(String sqlType) { | |||
if (isIntegerType(sqlType)) { | |||
return "INTEGER"; | |||
} | |||
return sqlType; | |||
} | |||
@Override | |||
protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, | |||
boolean isAutoIncrement, boolean isPrimaryKey) { | |||
String convertedType = convertSqlType(dataType); | |||
buff.append(convertedType); | |||
if (isPrimaryKey) { | |||
buff.append(" PRIMARY KEY"); | |||
if (isAutoIncrement) { | |||
buff.append(" AUTOINCREMENT"); | |||
} | |||
return true; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) { | |||
StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS " | |||
+ prepareTableName(def.schemaName, def.tableName)); | |||
stat.setSQL(buff.toString()); | |||
return; | |||
} | |||
@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 IF NOT EXISTS "); | |||
buff.append(index.indexName); | |||
buff.append(" ON "); | |||
buff.append(tableName); | |||
buff.append("("); | |||
for (String col : index.columnNames) { | |||
buff.appendExceptFirst(", "); | |||
buff.append(prepareColumnName(col)); | |||
} | |||
buff.append(") "); | |||
stat.setSQL(buff.toString().trim()); | |||
} | |||
@Override | |||
public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, | |||
TableDefinition<T> def, Object obj) { | |||
StatementBuilder buff = new StatementBuilder("INSERT OR REPLACE INTO "); | |||
buff.append(prepareTableName(schemaName, tableName)).append(" ("); | |||
buff.resetCount(); | |||
for (FieldDefinition field : def.fields) { | |||
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); | |||
Object parameter = serialize(value, field.typeAdapter); | |||
stat.addParameter(parameter); | |||
} | |||
buff.append(')'); | |||
stat.setSQL(buff.toString()); | |||
} | |||
@Override | |||
public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter) { | |||
try { | |||
return super.deserialize(rs, columnIndex, targetType, typeAdapter); | |||
} catch (IciqlException e) { | |||
if (typeAdapter == null && e.getMessage().startsWith("Can not convert")) { | |||
try { | |||
// give the SQLite JDBC driver an opportunity to deserialize DateTime objects | |||
if (Timestamp.class.equals(targetType)) { | |||
return rs.getTimestamp(columnIndex); | |||
} else if (Time.class.equals(targetType)) { | |||
return rs.getTime(columnIndex); | |||
} else if (Date.class.equals(targetType)) { | |||
return rs.getDate(columnIndex); | |||
} else if (java.util.Date.class.equals(targetType)) { | |||
Timestamp timestamp = rs.getTimestamp(columnIndex); | |||
return new java.util.Date(timestamp.getTime()); | |||
} | |||
} catch (SQLException x) { | |||
throw new IciqlException(x, "Can not convert the value at column {0} to {1}", | |||
columnIndex, targetType.getName()); | |||
} | |||
} | |||
// rethrow e | |||
throw e; | |||
} | |||
} | |||
@Override | |||
public String prepareStringParameter(Object o) { | |||
if (o instanceof Boolean) { | |||
// SQLite does not have an explicit BOOLEAN type | |||
Boolean bool = (Boolean) o; | |||
return bool ? "1" : "0"; | |||
} | |||
return super.prepareStringParameter(o); | |||
} | |||
} |
@@ -17,174 +17,174 @@ | |||
package com.iciql; | |||
import com.iciql.util.JdbcUtils; | |||
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<Object> params = new ArrayList<Object>(); | |||
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().prepareStringParameter(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; | |||
} | |||
private Db db; | |||
private StringBuilder buff = new StringBuilder(); | |||
private String sql; | |||
private ArrayList<Object> params = new ArrayList<Object>(); | |||
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().prepareStringParameter(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; | |||
} | |||
} |
@@ -21,37 +21,36 @@ import com.iciql.TableDefinition.FieldDefinition; | |||
/** | |||
* This class represents a column of a table in a query. | |||
* | |||
* @param <T> | |||
* the table data type | |||
* | |||
* @param <T> the table data type | |||
*/ | |||
class SelectColumn<T> { | |||
private SelectTable<T> selectTable; | |||
private FieldDefinition fieldDef; | |||
SelectColumn(SelectTable<T> 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<T> getSelectTable() { | |||
return selectTable; | |||
} | |||
Object getCurrentValue() { | |||
return fieldDef.getValue(selectTable.getCurrent()); | |||
} | |||
private SelectTable<T> selectTable; | |||
private FieldDefinition fieldDef; | |||
SelectColumn(SelectTable<T> 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<T> getSelectTable() { | |||
return selectTable; | |||
} | |||
Object getCurrentValue() { | |||
return fieldDef.getValue(selectTable.getCurrent()); | |||
} | |||
} |
@@ -17,96 +17,95 @@ | |||
package com.iciql; | |||
import java.util.ArrayList; | |||
import com.iciql.util.Utils; | |||
import java.util.ArrayList; | |||
/** | |||
* This class represents a table in a query. | |||
* | |||
* @param <T> | |||
* the table class | |||
* | |||
* @param <T> the table class | |||
*/ | |||
class SelectTable<T> { | |||
private Query<T> query; | |||
private Class<T> clazz; | |||
private T current; | |||
private String as; | |||
private TableDefinition<T> aliasDef; | |||
private boolean outerJoin; | |||
private ArrayList<Token> joinConditions = Utils.newArrayList(); | |||
private T alias; | |||
@SuppressWarnings("unchecked") | |||
SelectTable(Db db, Query<T> query, T alias, boolean outerJoin) { | |||
this.alias = alias; | |||
this.query = query; | |||
this.outerJoin = outerJoin; | |||
aliasDef = (TableDefinition<T>) db.getTableDefinition(alias.getClass()); | |||
clazz = Utils.getClass(alias); | |||
as = "T" + Utils.nextAsCount(); | |||
} | |||
T getAlias() { | |||
return alias; | |||
} | |||
T newObject() { | |||
return Utils.newObject(clazz); | |||
} | |||
TableDefinition<T> 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<T> 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<T> 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; | |||
} | |||
private Query<T> query; | |||
private Class<T> clazz; | |||
private T current; | |||
private String as; | |||
private TableDefinition<T> aliasDef; | |||
private boolean outerJoin; | |||
private ArrayList<Token> joinConditions = Utils.newArrayList(); | |||
private T alias; | |||
@SuppressWarnings("unchecked") | |||
SelectTable(Db db, Query<T> query, T alias, boolean outerJoin) { | |||
this.alias = alias; | |||
this.query = query; | |||
this.outerJoin = outerJoin; | |||
aliasDef = (TableDefinition<T>) db.getTableDefinition(alias.getClass()); | |||
clazz = Utils.getClass(alias); | |||
as = "T" + Utils.nextAsCount(); | |||
} | |||
T getAlias() { | |||
return alias; | |||
} | |||
T newObject() { | |||
return Utils.newObject(clazz); | |||
} | |||
TableDefinition<T> 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<T> 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<T> 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; | |||
} | |||
} |
@@ -17,16 +17,16 @@ | |||
package com.iciql; | |||
public class SubQuery<T, Z> { | |||
final Query<T> query; | |||
final Z z; | |||
public SubQuery(Query<T> query, Z x) { | |||
this.query = query; | |||
this.z = x; | |||
} | |||
public void appendSQL(SQLStatement stat) { | |||
stat.appendSQL(query.toSubQuery(z)); | |||
} | |||
final Query<T> query; | |||
final Z z; | |||
public SubQuery(Query<T> query, Z x) { | |||
this.query = query; | |||
this.z = x; | |||
} | |||
public void appendSQL(SQLStatement stat) { | |||
stat.appendSQL(query.toSubQuery(z)); | |||
} | |||
} |
@@ -18,24 +18,23 @@ package com.iciql; | |||
/** | |||
* A condition that contains a subquery. | |||
* | |||
* @param <A> | |||
* the operand type | |||
* | |||
* @param <A> the operand type | |||
*/ | |||
class SubQueryCondition<A, Y, Z> implements Token { | |||
A x; | |||
SubQuery<Y, Z> subquery; | |||
A x; | |||
SubQuery<Y, Z> subquery; | |||
SubQueryCondition(A x, SubQuery<Y, Z> subquery) { | |||
this.x = x; | |||
this.subquery = subquery; | |||
} | |||
SubQueryCondition(A x, SubQuery<Y, Z> subquery) { | |||
this.x = x; | |||
this.subquery = subquery; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
query.appendSQL(stat, null, x); | |||
stat.appendSQL(" in ("); | |||
subquery.appendSQL(stat); | |||
stat.appendSQL(")"); | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
query.appendSQL(stat, null, x); | |||
stat.appendSQL(" in ("); | |||
subquery.appendSQL(stat); | |||
stat.appendSQL(")"); | |||
} | |||
} |
@@ -21,95 +21,94 @@ import com.iciql.util.Utils; | |||
/** | |||
* This class represents an incomplete condition. | |||
* | |||
* @param <A> | |||
* the incomplete condition data type | |||
* | |||
* @param <A> the incomplete condition data type | |||
*/ | |||
public class TestCondition<A> { | |||
private A x; | |||
private A x; | |||
public TestCondition(A x) { | |||
this.x = 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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL("("); | |||
query.appendSQL(stat, null, x[0]); | |||
stat.appendSQL(" = "); | |||
query.appendSQL(stat, x[0], x[1]); | |||
stat.appendSQL(")"); | |||
} | |||
}); | |||
} | |||
public Boolean is(A y) { | |||
Boolean o = Utils.newObject(Boolean.class); | |||
return Db.registerToken(o, new Function("=", x, y) { | |||
public <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> 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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL("("); | |||
query.appendSQL(stat, null, x[0]); | |||
stat.appendSQL(" LIKE "); | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL("("); | |||
query.appendSQL(stat, null, x[0]); | |||
stat.appendSQL(" LIKE "); | |||
query.appendSQL(stat, x[0], x[1]); | |||
stat.appendSQL(")"); | |||
} | |||
}); | |||
} | |||
} |
@@ -21,15 +21,13 @@ 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 | |||
*/ | |||
/** | |||
* 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 | |||
*/ | |||
<T> void appendSQL(SQLStatement stat, Query<T> query); | |||
<T> void appendSQL(SQLStatement stat, Query<T> query); | |||
} |
@@ -23,13 +23,12 @@ package com.iciql; | |||
*/ | |||
public interface UpdateColumn { | |||
/** | |||
* Append the SQL to the given statement using the given query. | |||
* | |||
* @param stat | |||
* the statement to append the SQL to | |||
*/ | |||
/** | |||
* Append the SQL to the given statement using the given query. | |||
* | |||
* @param stat the statement to append the SQL to | |||
*/ | |||
void appendSQL(SQLStatement stat); | |||
void appendSQL(SQLStatement stat); | |||
} |
@@ -19,37 +19,35 @@ package com.iciql; | |||
/** | |||
* This class represents "SET column = (column + x)" in an UPDATE statement. | |||
* | |||
* @param <T> | |||
* the query type | |||
* @param <A> | |||
* the new value data type | |||
* | |||
* @param <T> the query type | |||
* @param <A> the new value data type | |||
*/ | |||
public class UpdateColumnIncrement<T, A> implements UpdateColumn { | |||
private Query<T> query; | |||
private A x; | |||
private A y; | |||
UpdateColumnIncrement(Query<T> query, A x) { | |||
this.query = query; | |||
this.x = x; | |||
} | |||
public Query<T> 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(")"); | |||
} | |||
private Query<T> query; | |||
private A x; | |||
private A y; | |||
UpdateColumnIncrement(Query<T> query, A x) { | |||
this.query = query; | |||
this.x = x; | |||
} | |||
public Query<T> 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(")"); | |||
} | |||
} |
@@ -19,45 +19,43 @@ package com.iciql; | |||
/** | |||
* This class represents "SET column = value" in an UPDATE statement. | |||
* | |||
* @param <T> | |||
* the query type | |||
* @param <A> | |||
* the new value data type | |||
* | |||
* @param <T> the query type | |||
* @param <A> the new value data type | |||
*/ | |||
public class UpdateColumnSet<T, A> implements UpdateColumn { | |||
private Query<T> query; | |||
private A x; | |||
private A y; | |||
private boolean isParameter; | |||
UpdateColumnSet(Query<T> query, A x) { | |||
this.query = query; | |||
this.x = x; | |||
} | |||
public Query<T> to(A y) { | |||
query.addUpdateColumnDeclaration(this); | |||
this.y = y; | |||
return query; | |||
} | |||
public Query<T> 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); | |||
} | |||
} | |||
private Query<T> query; | |||
private A x; | |||
private A y; | |||
private boolean isParameter; | |||
UpdateColumnSet(Query<T> query, A x) { | |||
this.query = query; | |||
this.x = x; | |||
} | |||
public Query<T> to(A y) { | |||
query.addUpdateColumnDeclaration(this); | |||
this.y = y; | |||
return query; | |||
} | |||
public Query<T> 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); | |||
} | |||
} | |||
} |
@@ -28,100 +28,100 @@ import com.iciql.util.StringUtils; | |||
*/ | |||
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(); | |||
} | |||
/** | |||
* 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(); | |||
} | |||
} |
@@ -26,13 +26,13 @@ import com.iciql.Iciql.Mode; | |||
* <p> | |||
* You use this by creating a subclass which defines your object class. | |||
* </p> | |||
* | |||
* <p> | |||
* <pre> | |||
* public class CustomObjectAdapter extends GsonTypeAdapter<CustomObject> { | |||
* | |||
* public Class<CustomObject> getJavaType() { | |||
* return CustomObject.class; | |||
* } | |||
* } | |||
* } | |||
* </pre> | |||
* | |||
@@ -40,32 +40,32 @@ import com.iciql.Iciql.Mode; | |||
*/ | |||
public abstract class GsonTypeAdapter<T> implements DataTypeAdapter<T> { | |||
protected Mode mode; | |||
protected Mode mode; | |||
protected Gson gson() { | |||
return new GsonBuilder().create(); | |||
} | |||
protected Gson gson() { | |||
return new GsonBuilder().create(); | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "TEXT"; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "TEXT"; | |||
} | |||
@Override | |||
public Object serialize(T value) { | |||
return gson().toJson(value); | |||
} | |||
@Override | |||
public Object serialize(T value) { | |||
return gson().toJson(value); | |||
} | |||
@Override | |||
public T deserialize(Object value) { | |||
String json = value.toString(); | |||
Gson gson = gson(); | |||
T t = gson.fromJson(json, getJavaType()); | |||
return t; | |||
} | |||
@Override | |||
public T deserialize(Object value) { | |||
String json = value.toString(); | |||
Gson gson = gson(); | |||
T t = gson.fromJson(json, getJavaType()); | |||
return t; | |||
} | |||
} |
@@ -16,6 +16,10 @@ | |||
package com.iciql.adapter; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
import com.iciql.IciqlException; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
@@ -25,10 +29,6 @@ import java.io.ObjectOutputStream; | |||
import java.sql.Blob; | |||
import java.sql.SQLException; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
import com.iciql.IciqlException; | |||
/** | |||
* Base class for inserting/retrieving a Java Object as a BLOB field using Java Serialization. | |||
* <p>You use this by creating a subclass which defines your object class.</p> | |||
@@ -40,68 +40,69 @@ import com.iciql.IciqlException; | |||
* } | |||
* } | |||
* </pre> | |||
* | |||
* @param <T> | |||
*/ | |||
public abstract class JavaSerializationTypeAdapter<T> implements DataTypeAdapter<T> { | |||
protected Mode mode; | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public final String getDataType() { | |||
return "BLOB"; | |||
} | |||
@Override | |||
public final String getDataType() { | |||
return "BLOB"; | |||
} | |||
@Override | |||
public final Object serialize(T value) { | |||
ByteArrayOutputStream os = new ByteArrayOutputStream(); | |||
try { | |||
new ObjectOutputStream(os).writeObject(value); | |||
return os.toByteArray(); | |||
} catch (IOException e) { | |||
throw new IciqlException(e); | |||
} finally { | |||
try { | |||
os.close(); | |||
} catch (IOException e) { | |||
throw new IciqlException (e); | |||
} | |||
} | |||
} | |||
@Override | |||
public final Object serialize(T value) { | |||
ByteArrayOutputStream os = new ByteArrayOutputStream(); | |||
try { | |||
new ObjectOutputStream(os).writeObject(value); | |||
return os.toByteArray(); | |||
} catch (IOException e) { | |||
throw new IciqlException(e); | |||
} finally { | |||
try { | |||
os.close(); | |||
} catch (IOException e) { | |||
throw new IciqlException(e); | |||
} | |||
} | |||
} | |||
@SuppressWarnings("unchecked") | |||
@Override | |||
public final T deserialize(Object value) { | |||
InputStream is = null; | |||
if (value instanceof Blob) { | |||
Blob blob = (Blob) value; | |||
try { | |||
is = blob.getBinaryStream(); | |||
} catch (SQLException e) { | |||
throw new IciqlException(e); | |||
} | |||
} else if (value instanceof byte[]) { | |||
byte [] bytes = (byte []) value; | |||
is = new ByteArrayInputStream(bytes); | |||
} | |||
@SuppressWarnings("unchecked") | |||
@Override | |||
public final T deserialize(Object value) { | |||
InputStream is = null; | |||
if (value instanceof Blob) { | |||
Blob blob = (Blob) value; | |||
try { | |||
is = blob.getBinaryStream(); | |||
} catch (SQLException e) { | |||
throw new IciqlException(e); | |||
} | |||
} else if (value instanceof byte[]) { | |||
byte[] bytes = (byte[]) value; | |||
is = new ByteArrayInputStream(bytes); | |||
} | |||
try { | |||
T object = (T) new ObjectInputStream(is).readObject(); | |||
return object; | |||
} catch (Exception e) { | |||
throw new IciqlException(e); | |||
} finally { | |||
if (is != null) { | |||
try { | |||
is.close(); | |||
} catch (IOException e) { | |||
throw new IciqlException (e); | |||
} | |||
} | |||
} | |||
} | |||
try { | |||
T object = (T) new ObjectInputStream(is).readObject(); | |||
return object; | |||
} catch (Exception e) { | |||
throw new IciqlException(e); | |||
} finally { | |||
if (is != null) { | |||
try { | |||
is.close(); | |||
} catch (IOException e) { | |||
throw new IciqlException(e); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -16,48 +16,47 @@ | |||
package com.iciql.adapter; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
import org.yaml.snakeyaml.DumperOptions.FlowStyle; | |||
import org.yaml.snakeyaml.Yaml; | |||
import org.yaml.snakeyaml.nodes.Tag; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
/** | |||
* Base class for inserting/retrieving a Java Object (de)serialized as YAML using SnakeYaml. | |||
*/ | |||
public abstract class SnakeYamlTypeAdapter<T> implements DataTypeAdapter<T> { | |||
protected Mode mode; | |||
protected Yaml yaml() { | |||
return new Yaml(); | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "TEXT"; | |||
} | |||
@Override | |||
public abstract Class<T> getJavaType(); | |||
@Override | |||
public Object serialize(Object value) { | |||
return yaml().dumpAs(value, Tag.MAP, FlowStyle.BLOCK); | |||
} | |||
@Override | |||
public T deserialize(Object value) { | |||
String yaml = value.toString(); | |||
Yaml processor = yaml(); | |||
T t = processor.loadAs(yaml, getJavaType()); | |||
return t; | |||
} | |||
protected Mode mode; | |||
protected Yaml yaml() { | |||
return new Yaml(); | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "TEXT"; | |||
} | |||
@Override | |||
public abstract Class<T> getJavaType(); | |||
@Override | |||
public Object serialize(Object value) { | |||
return yaml().dumpAs(value, Tag.MAP, FlowStyle.BLOCK); | |||
} | |||
@Override | |||
public T deserialize(Object value) { | |||
String yaml = value.toString(); | |||
Yaml processor = yaml(); | |||
T t = processor.loadAs(yaml, getJavaType()); | |||
return t; | |||
} | |||
} |
@@ -25,38 +25,38 @@ import com.thoughtworks.xstream.XStream; | |||
*/ | |||
public class XStreamTypeAdapter implements DataTypeAdapter<Object> { | |||
protected Mode mode; | |||
protected XStream xstream() { | |||
return new XStream(); | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "TEXT"; | |||
} | |||
@Override | |||
public Class<Object> getJavaType() { | |||
return Object.class; | |||
} | |||
@Override | |||
public Object serialize(Object value) { | |||
return xstream().toXML(value); | |||
} | |||
@Override | |||
public Object deserialize(Object value) { | |||
String xml = value.toString(); | |||
XStream xstream = xstream(); | |||
Object t = xstream.fromXML(xml); | |||
return t; | |||
} | |||
protected Mode mode; | |||
protected XStream xstream() { | |||
return new XStream(); | |||
} | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "TEXT"; | |||
} | |||
@Override | |||
public Class<Object> getJavaType() { | |||
return Object.class; | |||
} | |||
@Override | |||
public Object serialize(Object value) { | |||
return xstream().toXML(value); | |||
} | |||
@Override | |||
public Object deserialize(Object value) { | |||
String xml = value.toString(); | |||
XStream xstream = xstream(); | |||
Object t = xstream.fromXML(xml); | |||
return t; | |||
} | |||
} |
@@ -15,38 +15,36 @@ | |||
*/ | |||
package com.iciql.adapter.postgresql; | |||
import java.sql.SQLException; | |||
import com.iciql.adapter.GsonTypeAdapter; | |||
import org.postgresql.util.PGobject; | |||
import com.iciql.adapter.GsonTypeAdapter; | |||
import java.sql.SQLException; | |||
/** | |||
* Postgres JSON data type adapter maps a JSON column to a domain object using | |||
* Google GSON. | |||
* | |||
* @author James Moger | |||
* | |||
* @param <T> | |||
* @author James Moger | |||
*/ | |||
public abstract class JsonObjectAdapter<T> extends GsonTypeAdapter<T> { | |||
@Override | |||
public String getDataType() { | |||
return "json"; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "json"; | |||
} | |||
@Override | |||
public Object serialize(T value) { | |||
@Override | |||
public Object serialize(T value) { | |||
String json = gson().toJson(value); | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(json); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
String json = gson().toJson(value); | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(json); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
} |
@@ -15,49 +15,48 @@ | |||
*/ | |||
package com.iciql.adapter.postgresql; | |||
import java.sql.SQLException; | |||
import org.postgresql.util.PGobject; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
import org.postgresql.util.PGobject; | |||
import java.sql.SQLException; | |||
/** | |||
* Handles transforming raw strings to/from the Postgres JSON data type. | |||
*/ | |||
public class JsonStringAdapter implements DataTypeAdapter<String> { | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "json"; | |||
} | |||
@Override | |||
public Class<String> getJavaType() { | |||
return String.class; | |||
} | |||
@Override | |||
public Object serialize(String value) { | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(value); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
@Override | |||
public String deserialize(Object value) { | |||
return value.toString(); | |||
} | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "json"; | |||
} | |||
@Override | |||
public Class<String> getJavaType() { | |||
return String.class; | |||
} | |||
@Override | |||
public Object serialize(String value) { | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(value); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
@Override | |||
public String deserialize(Object value) { | |||
return value.toString(); | |||
} | |||
} |
@@ -15,38 +15,36 @@ | |||
*/ | |||
package com.iciql.adapter.postgresql; | |||
import java.sql.SQLException; | |||
import com.iciql.adapter.GsonTypeAdapter; | |||
import org.postgresql.util.PGobject; | |||
import com.iciql.adapter.GsonTypeAdapter; | |||
import java.sql.SQLException; | |||
/** | |||
* Postgres JSONB data type adapter maps a JSONB column to a domain object using | |||
* Google GSON. | |||
* | |||
* @author James Moger | |||
* | |||
* @param <T> | |||
* @author James Moger | |||
*/ | |||
public abstract class JsonbObjectAdapter<T> extends GsonTypeAdapter<T> { | |||
@Override | |||
public String getDataType() { | |||
return "jsonb"; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "jsonb"; | |||
} | |||
@Override | |||
public Object serialize(T value) { | |||
@Override | |||
public Object serialize(T value) { | |||
String json = gson().toJson(value); | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(json); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
String json = gson().toJson(value); | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(json); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
} |
@@ -15,49 +15,48 @@ | |||
*/ | |||
package com.iciql.adapter.postgresql; | |||
import java.sql.SQLException; | |||
import org.postgresql.util.PGobject; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
import org.postgresql.util.PGobject; | |||
import java.sql.SQLException; | |||
/** | |||
* Handles transforming raw strings to/from the Postgres JSONB data type. | |||
*/ | |||
public class JsonbStringAdapter implements DataTypeAdapter<String> { | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "jsonb"; | |||
} | |||
@Override | |||
public Class<String> getJavaType() { | |||
return String.class; | |||
} | |||
@Override | |||
public Object serialize(String value) { | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(value); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
@Override | |||
public String deserialize(Object value) { | |||
return value.toString(); | |||
} | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "jsonb"; | |||
} | |||
@Override | |||
public Class<String> getJavaType() { | |||
return String.class; | |||
} | |||
@Override | |||
public Object serialize(String value) { | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(value); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
@Override | |||
public String deserialize(Object value) { | |||
return value.toString(); | |||
} | |||
} |
@@ -23,13 +23,12 @@ import com.iciql.adapter.XStreamTypeAdapter; | |||
* XStream. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public abstract class XmlObjectAdapter extends XStreamTypeAdapter { | |||
@Override | |||
public String getDataType() { | |||
return "xml"; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "xml"; | |||
} | |||
} |
@@ -15,49 +15,48 @@ | |||
*/ | |||
package com.iciql.adapter.postgresql; | |||
import java.sql.SQLException; | |||
import org.postgresql.util.PGobject; | |||
import com.iciql.Iciql.DataTypeAdapter; | |||
import com.iciql.Iciql.Mode; | |||
import org.postgresql.util.PGobject; | |||
import java.sql.SQLException; | |||
/** | |||
* Handles transforming raw strings to/from the Postgres XML data type. | |||
*/ | |||
public class XmlStringAdapter implements DataTypeAdapter<String> { | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "xml"; | |||
} | |||
@Override | |||
public Class<String> getJavaType() { | |||
return String.class; | |||
} | |||
@Override | |||
public Object serialize(String value) { | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(value); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
@Override | |||
public String deserialize(Object value) { | |||
return value.toString(); | |||
} | |||
protected Mode mode; | |||
@Override | |||
public void setMode(Mode mode) { | |||
this.mode = mode; | |||
} | |||
@Override | |||
public String getDataType() { | |||
return "xml"; | |||
} | |||
@Override | |||
public Class<String> getJavaType() { | |||
return String.class; | |||
} | |||
@Override | |||
public Object serialize(String value) { | |||
PGobject pg = new PGobject(); | |||
pg.setType(getDataType()); | |||
try { | |||
pg.setValue(value); | |||
} catch (SQLException e) { | |||
// not thrown on base PGobject | |||
} | |||
return pg; | |||
} | |||
@Override | |||
public String deserialize(Object value) { | |||
return value.toString(); | |||
} | |||
} |
@@ -26,21 +26,21 @@ import com.iciql.Token; | |||
*/ | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
left.appendSQL(stat, query); | |||
stat.appendSQL(" AND "); | |||
right.appendSQL(stat, query); | |||
} | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
left.appendSQL(stat, query); | |||
stat.appendSQL(" AND "); | |||
right.appendSQL(stat, query); | |||
} | |||
} |
@@ -26,24 +26,24 @@ import com.iciql.Token; | |||
*/ | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
variable.appendSQL(stat, query); | |||
stat.appendSQL("["); | |||
index.appendSQL(stat, query); | |||
stat.appendSQL("]"); | |||
} | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
variable.appendSQL(stat, query); | |||
stat.appendSQL("["); | |||
index.appendSQL(stat, query); | |||
stat.appendSQL("]"); | |||
} | |||
} |
@@ -26,37 +26,37 @@ import com.iciql.Token; | |||
*/ | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> 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"); | |||
} | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> 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"); | |||
} | |||
} |
@@ -24,15 +24,15 @@ import com.iciql.Token; | |||
*/ | |||
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 | |||
} | |||
/** | |||
* 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(); | |||
Constant.Type getType(); | |||
int intValue(); | |||
int intValue(); | |||
} |
@@ -25,46 +25,46 @@ import com.iciql.SQLStatement; | |||
*/ | |||
public class ConstantNumber implements Constant { | |||
private final String value; | |||
private final Type type; | |||
private final long longValue; | |||
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; | |||
} | |||
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(String v) { | |||
return new ConstantNumber(v, 0, Type.STRING); | |||
} | |||
static ConstantNumber get(int v) { | |||
return new ConstantNumber("" + v, v, Type.INT); | |||
} | |||
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(long v) { | |||
return new ConstantNumber("" + v, v, Type.LONG); | |||
} | |||
static ConstantNumber get(String s, long x, Type type) { | |||
return new ConstantNumber(s, x, type); | |||
} | |||
static ConstantNumber get(String s, long x, Type type) { | |||
return new ConstantNumber(s, x, type); | |||
} | |||
public int intValue() { | |||
return (int) longValue; | |||
} | |||
public int intValue() { | |||
return (int) longValue; | |||
} | |||
public String toString() { | |||
return value; | |||
} | |||
public String toString() { | |||
return value; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL(toString()); | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL(toString()); | |||
} | |||
public Constant.Type getType() { | |||
return type; | |||
} | |||
public Constant.Type getType() { | |||
return type; | |||
} | |||
} |
@@ -26,30 +26,30 @@ import com.iciql.util.StringUtils; | |||
*/ | |||
public class ConstantString implements Constant { | |||
private final String value; | |||
private final String value; | |||
private ConstantString(String value) { | |||
this.value = value; | |||
} | |||
private ConstantString(String value) { | |||
this.value = value; | |||
} | |||
static ConstantString get(String v) { | |||
return new ConstantString(v); | |||
} | |||
static ConstantString get(String v) { | |||
return new ConstantString(v); | |||
} | |||
public String toString() { | |||
return value; | |||
} | |||
public String toString() { | |||
return value; | |||
} | |||
public int intValue() { | |||
return 0; | |||
} | |||
public int intValue() { | |||
return 0; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL(StringUtils.quoteStringSQL(value)); | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL(StringUtils.quoteStringSQL(value)); | |||
} | |||
public Constant.Type getType() { | |||
return Constant.Type.STRING; | |||
} | |||
public Constant.Type getType() { | |||
return Constant.Type.STRING; | |||
} | |||
} |
@@ -26,22 +26,22 @@ import com.iciql.Token; | |||
*/ | |||
class Function implements Token { | |||
private final String name; | |||
private final Token expr; | |||
private final String name; | |||
private final Token expr; | |||
Function(String name, Token expr) { | |||
this.name = name; | |||
this.expr = expr; | |||
} | |||
Function(String name, Token expr) { | |||
this.name = name; | |||
this.expr = expr; | |||
} | |||
public String toString() { | |||
return name + "(" + expr + ")"; | |||
} | |||
public String toString() { | |||
return name + "(" + expr + ")"; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
stat.appendSQL(name + "("); | |||
expr.appendSQL(stat, query); | |||
stat.appendSQL(")"); | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
stat.appendSQL(name + "("); | |||
expr.appendSQL(stat, query); | |||
stat.appendSQL(")"); | |||
} | |||
} |
@@ -26,30 +26,30 @@ import com.iciql.Token; | |||
*/ | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
stat.appendSQL("NOT("); | |||
expr.appendSQL(stat, query); | |||
stat.appendSQL(")"); | |||
} | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
stat.appendSQL("NOT("); | |||
expr.appendSQL(stat, query); | |||
stat.appendSQL(")"); | |||
} | |||
} |
@@ -26,19 +26,19 @@ import com.iciql.Token; | |||
*/ | |||
public class Null implements Token { | |||
static final Null INSTANCE = new Null(); | |||
static final Null INSTANCE = new Null(); | |||
private Null() { | |||
// don't allow to create new instances | |||
} | |||
private Null() { | |||
// don't allow to create new instances | |||
} | |||
public String toString() { | |||
return "null"; | |||
} | |||
public String toString() { | |||
return "null"; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
stat.appendSQL("NULL"); | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
stat.appendSQL("NULL"); | |||
} | |||
} |
@@ -26,86 +26,86 @@ import com.iciql.Token; | |||
*/ | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
left.appendSQL(stat, query); | |||
stat.appendSQL(op.toString()); | |||
right.appendSQL(stat, query); | |||
} | |||
/** | |||
* 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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
left.appendSQL(stat, query); | |||
stat.appendSQL(op.toString()); | |||
right.appendSQL(stat, query); | |||
} | |||
} |
@@ -26,22 +26,22 @@ import com.iciql.Token; | |||
*/ | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
left.appendSQL(stat, query); | |||
stat.appendSQL(" OR "); | |||
right.appendSQL(stat, query); | |||
} | |||
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 <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
// untested | |||
left.appendSQL(stat, query); | |||
stat.appendSQL(" OR "); | |||
right.appendSQL(stat, query); | |||
} | |||
} |
@@ -26,26 +26,26 @@ import com.iciql.Token; | |||
*/ | |||
public class Variable implements Token { | |||
static final Variable THIS = new Variable("this", null); | |||
static final Variable THIS = new Variable("this", null); | |||
private final String name; | |||
private final Object obj; | |||
private final String name; | |||
private final Object obj; | |||
private Variable(String name, Object obj) { | |||
this.name = name; | |||
this.obj = 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); | |||
} | |||
static Variable get(String name, Object obj) { | |||
return new Variable(name, obj); | |||
} | |||
public String toString() { | |||
return name; | |||
} | |||
public String toString() { | |||
return name; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
query.appendSQL(stat, null, obj); | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
query.appendSQL(stat, null, obj); | |||
} | |||
} |
@@ -16,8 +16,9 @@ | |||
limitations under the License. | |||
--> | |||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> | |||
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> | |||
<title>Javadoc package documentation</title> | |||
<head> | |||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> | |||
<title>Javadoc package documentation</title> | |||
</head> | |||
<body> | |||
The class decompiler for natural syntax iciql clauses. |
@@ -16,8 +16,9 @@ | |||
limitations under the License. | |||
--> | |||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> | |||
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> | |||
<title>Javadoc package documentation</title> | |||
<head> | |||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> | |||
<title>Javadoc package documentation</title> | |||
</head> | |||
<body> | |||
<i>iciql</i> (pronounced "icicle") is a Java JDBC SQL statement generator and simple object mapper |
@@ -17,6 +17,9 @@ | |||
package com.iciql.util; | |||
import com.iciql.Db; | |||
import com.iciql.DbInspector; | |||
import java.io.BufferedWriter; | |||
import java.io.File; | |||
import java.io.FileWriter; | |||
@@ -29,165 +32,152 @@ 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; | |||
/** | |||
* 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 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); | |||
} | |||
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<String> 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); | |||
} | |||
} | |||
/** | |||
* 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<String> 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); | |||
} | |||
/** | |||
* 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 <string>"); | |||
out.println(" -password <string>"); | |||
out.println(" -schema <string>"); | |||
out.println(" -table <string>"); | |||
out.println(" -package <string>"); | |||
out.println(" -folder <string>"); | |||
out.println(" -annotateSchema <boolean>"); | |||
out.println(" -trimStrings <boolean>"); | |||
} | |||
protected void showUsage() { | |||
out.println("GenerateModels"); | |||
out.println("Usage:"); | |||
out.println(); | |||
out.println("(*) -url jdbc:h2:~test"); | |||
out.println(" -user <string>"); | |||
out.println(" -password <string>"); | |||
out.println(" -schema <string>"); | |||
out.println(" -table <string>"); | |||
out.println(" -package <string>"); | |||
out.println(" -folder <string>"); | |||
out.println(" -annotateSchema <boolean>"); | |||
out.println(" -trimStrings <boolean>"); | |||
} | |||
} |
@@ -16,6 +16,8 @@ | |||
package com.iciql.util; | |||
import com.iciql.IciqlException; | |||
import java.text.DecimalFormat; | |||
import java.text.MessageFormat; | |||
import java.util.Set; | |||
@@ -23,192 +25,189 @@ 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.<br> | |||
* Statement logging is disabled by default. | |||
* <p> | |||
* 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<IciqlListener> 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)); | |||
} | |||
} | |||
/** | |||
* 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<IciqlListener> 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)); | |||
} | |||
} | |||
} |
@@ -17,6 +17,9 @@ | |||
package com.iciql.util; | |||
import javax.naming.Context; | |||
import javax.sql.DataSource; | |||
import javax.sql.XAConnection; | |||
import java.sql.Connection; | |||
import java.sql.DriverManager; | |||
import java.sql.ResultSet; | |||
@@ -24,231 +27,212 @@ 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); | |||
} | |||
} | |||
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); | |||
} | |||
} | |||
} |
@@ -16,77 +16,76 @@ | |||
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; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
/** | |||
* Slf4jIciqlListener interfaces the IciqlLogger to the SLF4J logging framework. | |||
*/ | |||
public class Slf4jIciqlListener implements IciqlListener { | |||
private Logger logger = LoggerFactory.getLogger(Iciql.class); | |||
private Logger logger = LoggerFactory.getLogger(Iciql.class); | |||
/** | |||
* Enumeration representing the SLF4J log levels. | |||
*/ | |||
public enum Level { | |||
ERROR, WARN, INFO, DEBUG, TRACE, OFF; | |||
} | |||
/** | |||
* Enumeration representing the SLF4J log levels. | |||
*/ | |||
public enum Level { | |||
ERROR, WARN, INFO, DEBUG, TRACE, OFF; | |||
} | |||
private final Level defaultLevel; | |||
private final Level defaultLevel; | |||
private final Map<StatementType, Level> levels; | |||
private final Map<StatementType, Level> levels; | |||
public Slf4jIciqlListener() { | |||
this(Level.TRACE); | |||
} | |||
public Slf4jIciqlListener() { | |||
this(Level.TRACE); | |||
} | |||
public Slf4jIciqlListener(Level defaultLevel) { | |||
this.defaultLevel = defaultLevel; | |||
levels = new HashMap<StatementType, Level>(); | |||
for (StatementType type : StatementType.values()) { | |||
levels.put(type, defaultLevel); | |||
} | |||
} | |||
public Slf4jIciqlListener(Level defaultLevel) { | |||
this.defaultLevel = defaultLevel; | |||
levels = new HashMap<StatementType, Level>(); | |||
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); | |||
} | |||
/** | |||
* 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; | |||
} | |||
} | |||
@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; | |||
} | |||
} | |||
} |
@@ -21,19 +21,19 @@ 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: | |||
* | |||
* <p> | |||
* <pre> | |||
* StringBuilder buff = new StringBuilder(); | |||
* for (int i = 0; i < args.length; i++) { | |||
* if (i > 0) { | |||
* buff.append(", "); | |||
* } | |||
* } | |||
* buff.append(args[i]); | |||
* } | |||
* </pre> | |||
* | |||
* <p> | |||
* to | |||
* | |||
* <p> | |||
* <pre> | |||
* StatementBuilder buff = new StatementBuilder(); | |||
* for (String s : args) { | |||
@@ -44,123 +44,117 @@ package com.iciql.util; | |||
*/ | |||
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(); | |||
} | |||
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(); | |||
} | |||
} |
@@ -29,354 +29,338 @@ 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(); | |||
} | |||
/** | |||
* 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; | |||
} | |||
/** | |||
* 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(); | |||
} | |||
/** | |||
* 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(); | |||
} | |||
/** | |||
* 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(); | |||
} | |||
/** | |||
* 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<String> 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; | |||
} | |||
/** | |||
* 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<String> 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 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); | |||
} | |||
} | |||
/** | |||
* 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; | |||
} | |||
/** | |||
* 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(); | |||
} | |||
/** | |||
* 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/>"); | |||
} | |||
/** | |||
* 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(); | |||
} | |||
/** | |||
* 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(); | |||
} | |||
} |
@@ -17,227 +17,225 @@ | |||
package com.iciql.util; | |||
import com.iciql.IciqlException; | |||
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 <K> | |||
* the keys | |||
* @param <V> | |||
* the value | |||
* | |||
* @param <K> the keys | |||
* @param <V> the value | |||
*/ | |||
public class WeakIdentityHashMap<K, V> implements Map<K, V> { | |||
private static final int MAX_LOAD = 90; | |||
private static final WeakReference<Object> DELETED_KEY = new WeakReference<Object>(null); | |||
private int mask, len, size, deletedCount, level; | |||
private int maxSize, minSize, maxDeleted; | |||
private WeakReference<K>[] 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> k = keys[index]; | |||
if (k == null) { | |||
// found an empty record | |||
if (deleted >= 0) { | |||
index = deleted; | |||
deletedCount--; | |||
} | |||
size++; | |||
keys[index] = new WeakReference<K>(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> 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<K>) DELETED_KEY; | |||
values[index] = null; | |||
deletedCount++; | |||
size--; | |||
} | |||
private void rehash(int newLevel) { | |||
WeakReference<K>[] oldKeys = keys; | |||
V[] oldValues = values; | |||
reset(newLevel); | |||
for (int i = 0; i < oldKeys.length; i++) { | |||
WeakReference<K> 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> 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<java.util.Map.Entry<K, V>> entrySet() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
public boolean isEmpty() { | |||
return size == 0; | |||
} | |||
public Set<K> keySet() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
public void putAll(Map<? extends K, ? extends V> m) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
public Collection<V> values() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
private static final int MAX_LOAD = 90; | |||
private static final WeakReference<Object> DELETED_KEY = new WeakReference<Object>(null); | |||
private int mask, len, size, deletedCount, level; | |||
private int maxSize, minSize, maxDeleted; | |||
private WeakReference<K>[] 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> k = keys[index]; | |||
if (k == null) { | |||
// found an empty record | |||
if (deleted >= 0) { | |||
index = deleted; | |||
deletedCount--; | |||
} | |||
size++; | |||
keys[index] = new WeakReference<K>(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> 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<K>) DELETED_KEY; | |||
values[index] = null; | |||
deletedCount++; | |||
size--; | |||
} | |||
private void rehash(int newLevel) { | |||
WeakReference<K>[] oldKeys = keys; | |||
V[] oldValues = values; | |||
reset(newLevel); | |||
for (int i = 0; i < oldKeys.length; i++) { | |||
WeakReference<K> 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> 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<java.util.Map.Entry<K, V>> entrySet() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
public boolean isEmpty() { | |||
return size == 0; | |||
} | |||
public Set<K> keySet() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
public void putAll(Map<? extends K, ? extends V> m) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
public Collection<V> values() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
} |
@@ -16,8 +16,9 @@ | |||
limitations under the License. | |||
--> | |||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> | |||
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> | |||
<title>Javadoc package documentation</title> | |||
<head> | |||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> | |||
<title>Javadoc package documentation</title> | |||
</head> | |||
<body> | |||
Utility classes for iciql. |
@@ -36,18 +36,18 @@ public interface AutoCloseable { | |||
* Closes this resource, relinquishing any underlying resources. | |||
* This method is invoked automatically by the {@code | |||
* try}-with-resources statement. | |||
* | |||
* <p> | |||
* <p>Classes implementing this method are strongly encouraged to | |||
* be declared to throw more specific exceptions (or no exception | |||
* at all, if the close cannot fail). | |||
* | |||
* <p> | |||
* <p>Note that unlike the {@link java.io.Closeable#close close} | |||
* method of {@link java.io.Closeable}, this {@code close} method | |||
* is <em>not</em> required to be idempotent. In other words, | |||
* calling this {@code close} method more than once may have some | |||
* visible side effect, unlike {@code Closeable.close} which is | |||
* required to have no effect if called more than once. | |||
* | |||
* <p> | |||
* However, while not required to be idempotent, implementers of | |||
* this interface are strongly encouraged to make their {@code | |||
* close} methods idempotent. |
@@ -17,123 +17,122 @@ | |||
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; | |||
import org.junit.Test; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
/** | |||
* 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()); | |||
/** | |||
* 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; | |||
// 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()); | |||
} | |||
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()); | |||
} | |||
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(); | |||
} | |||
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); | |||
/** | |||
* 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; | |||
// 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(); | |||
} | |||
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(); | |||
} | |||
} |
@@ -17,18 +17,6 @@ | |||
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; | |||
@@ -37,160 +25,171 @@ import com.iciql.test.models.ProductInheritedAnnotation; | |||
import com.iciql.test.models.ProductMixedAnnotation; | |||
import com.iciql.test.models.ProductNoCreateTable; | |||
import com.iciql.util.Utils; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import java.sql.DatabaseMetaData; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.util.List; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
/** | |||
* 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<String> 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<ProductInheritedAnnotation> inserted = ProductInheritedAnnotation.getData(); | |||
db.insertAll(inserted); | |||
List<ProductInheritedAnnotation> 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()); | |||
} | |||
} | |||
/** | |||
* 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<String> 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<ProductInheritedAnnotation> inserted = ProductInheritedAnnotation.getData(); | |||
db.insertAll(inserted); | |||
List<ProductInheritedAnnotation> 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()); | |||
} | |||
} | |||
} |
@@ -16,17 +16,16 @@ | |||
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; | |||
import com.iciql.test.models.BooleanModel.BooleanAsPrimitiveShortModel; | |||
import org.junit.Test; | |||
import java.util.List; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
/** | |||
* Tests interchangeable mapping of INT columns with Booleans and BOOL columns | |||
@@ -39,135 +38,135 @@ import com.iciql.test.models.BooleanModel.BooleanAsPrimitiveShortModel; | |||
*/ | |||
public class BooleanModelTest { | |||
@Test | |||
public void testBooleanColumn() { | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(BooleanModel.getList()); | |||
BooleanAsIntModel b = new BooleanAsIntModel(); | |||
List<BooleanAsIntModel> 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<BooleanModel> 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(); | |||
} | |||
@Test | |||
public void testPrimitiveShortBooleanColumn() { | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(BooleanModel.getList()); | |||
BooleanAsPrimitiveShortModel b = new BooleanAsPrimitiveShortModel(); | |||
List<BooleanAsPrimitiveShortModel> models = db.from(b).select(); | |||
int count = 0; | |||
for (BooleanAsPrimitiveShortModel 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 (BooleanAsPrimitiveShortModel model : models) { | |||
model.mybool = (short) (model.mybool > 0 ? 0 : 1); | |||
} | |||
db.updateAll(models); | |||
// check even ids are true | |||
models = db.from(b).select(); | |||
for (BooleanAsPrimitiveShortModel 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 testBooleanColumn() { | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(BooleanModel.getList()); | |||
BooleanAsIntModel b = new BooleanAsIntModel(); | |||
List<BooleanAsIntModel> 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<BooleanModel> 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(); | |||
} | |||
@Test | |||
public void testPrimitiveShortBooleanColumn() { | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(BooleanModel.getList()); | |||
BooleanAsPrimitiveShortModel b = new BooleanAsPrimitiveShortModel(); | |||
List<BooleanAsPrimitiveShortModel> models = db.from(b).select(); | |||
int count = 0; | |||
for (BooleanAsPrimitiveShortModel 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 (BooleanAsPrimitiveShortModel model : models) { | |||
model.mybool = (short) (model.mybool > 0 ? 0 : 1); | |||
} | |||
db.updateAll(models); | |||
// check even ids are true | |||
models = db.from(b).select(); | |||
for (BooleanAsPrimitiveShortModel 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(); | |||
} | |||
} |
@@ -17,99 +17,98 @@ | |||
package com.iciql.test; | |||
import static com.iciql.Define.primaryKey; | |||
import static com.iciql.Define.tableName; | |||
import static org.junit.Assert.assertEquals; | |||
import com.iciql.Db; | |||
import com.iciql.Iciql; | |||
import org.junit.Test; | |||
import java.text.MessageFormat; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.junit.Test; | |||
import com.iciql.Db; | |||
import com.iciql.Iciql; | |||
import static com.iciql.Define.primaryKey; | |||
import static com.iciql.Define.tableName; | |||
import static org.junit.Assert.assertEquals; | |||
/** | |||
* 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<StringRecord> 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; | |||
} | |||
} | |||
@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<StringRecord> 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; | |||
} | |||
} | |||
} |
@@ -16,184 +16,183 @@ | |||
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; | |||
import org.junit.Before; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import java.util.List; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
/** | |||
* 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<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(); | |||
} | |||
} | |||
@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<Thread> 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<Thread> threads = Utils.newArrayList(); | |||
final AtomicInteger failures = new AtomicInteger(0); | |||
final ThreadLocal<Product> 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<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(); | |||
} | |||
} | |||
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<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(); | |||
} | |||
} | |||
@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<Thread> 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<Thread> threads = Utils.newArrayList(); | |||
final AtomicInteger failures = new AtomicInteger(0); | |||
final ThreadLocal<Product> 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<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(); | |||
} | |||
} | |||
} |
@@ -16,23 +16,22 @@ | |||
package com.iciql.test; | |||
import java.lang.annotation.ElementType; | |||
import java.lang.annotation.Retention; | |||
import java.lang.annotation.RetentionPolicy; | |||
import java.lang.annotation.Target; | |||
import java.util.Date; | |||
import org.junit.After; | |||
import org.junit.Assert; | |||
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.Iciql.TypeAdapter; | |||
import com.iciql.adapter.JavaSerializationTypeAdapter; | |||
import com.iciql.test.models.SupportedTypes; | |||
import org.junit.After; | |||
import org.junit.Assert; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import java.lang.annotation.ElementType; | |||
import java.lang.annotation.Retention; | |||
import java.lang.annotation.RetentionPolicy; | |||
import java.lang.annotation.Target; | |||
import java.util.Date; | |||
/** | |||
* Tests insertion and retrieval of a custom data type that is automatically transformed | |||
@@ -40,66 +39,66 @@ import com.iciql.test.models.SupportedTypes; | |||
*/ | |||
public class DataTypeAdapterTest extends Assert { | |||
private Db db; | |||
private Db db; | |||
@Before | |||
public void setUp() { | |||
db = IciqlSuite.openNewDb(); | |||
} | |||
@Before | |||
public void setUp() { | |||
db = IciqlSuite.openNewDb(); | |||
} | |||
@After | |||
public void tearDown() { | |||
db.close(); | |||
} | |||
@After | |||
public void tearDown() { | |||
db.close(); | |||
} | |||
@Test | |||
public void testSerializedObjectDataType() { | |||
@Test | |||
public void testSerializedObjectDataType() { | |||
SerializedObjectTypeAdapterTest row = new SerializedObjectTypeAdapterTest(); | |||
row.received = new Date(); | |||
row.obj = SupportedTypes.createList().get(1); | |||
db.insert(row); | |||
SerializedObjectTypeAdapterTest row = new SerializedObjectTypeAdapterTest(); | |||
row.received = new Date(); | |||
row.obj = SupportedTypes.createList().get(1); | |||
db.insert(row); | |||
SerializedObjectTypeAdapterTest table = new SerializedObjectTypeAdapterTest(); | |||
SerializedObjectTypeAdapterTest q1 = db.from(table).selectFirst(); | |||
SerializedObjectTypeAdapterTest table = new SerializedObjectTypeAdapterTest(); | |||
SerializedObjectTypeAdapterTest q1 = db.from(table).selectFirst(); | |||
assertNotNull(q1); | |||
assertTrue(row.obj.equivalentTo(q1.obj)); | |||
assertNotNull(q1); | |||
assertTrue(row.obj.equivalentTo(q1.obj)); | |||
} | |||
} | |||
@IQTable(name="dataTypeAdapters") | |||
public static class SerializedObjectTypeAdapterTest { | |||
@IQTable(name = "dataTypeAdapters") | |||
public static class SerializedObjectTypeAdapterTest { | |||
@IQColumn(autoIncrement = true, primaryKey = true) | |||
public long id; | |||
@IQColumn(autoIncrement = true, primaryKey = true) | |||
public long id; | |||
@IQColumn | |||
public java.util.Date received; | |||
@IQColumn | |||
public java.util.Date received; | |||
@IQColumn | |||
@SupportedTypesAdapter | |||
public SupportedTypes obj; | |||
@IQColumn | |||
@SupportedTypesAdapter | |||
public SupportedTypes obj; | |||
} | |||
} | |||
/** | |||
* Maps a SupportedType instance to a BLOB using Java Object serialization. | |||
* | |||
*/ | |||
public static class SupportedTypesAdapterImpl extends JavaSerializationTypeAdapter<SupportedTypes> { | |||
/** | |||
* Maps a SupportedType instance to a BLOB using Java Object serialization. | |||
*/ | |||
public static class SupportedTypesAdapterImpl extends JavaSerializationTypeAdapter<SupportedTypes> { | |||
@Override | |||
public Class<SupportedTypes> getJavaType() { | |||
return SupportedTypes.class; | |||
} | |||
@Override | |||
public Class<SupportedTypes> getJavaType() { | |||
return SupportedTypes.class; | |||
} | |||
} | |||
} | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) | |||
@TypeAdapter(SupportedTypesAdapterImpl.class) | |||
public @interface SupportedTypesAdapter { } | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) | |||
@TypeAdapter(SupportedTypesAdapterImpl.class) | |||
public @interface SupportedTypesAdapter { | |||
} | |||
} |
@@ -16,46 +16,45 @@ | |||
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; | |||
import org.junit.Test; | |||
import java.util.List; | |||
import static org.junit.Assert.assertTrue; | |||
/** | |||
* 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<ValidationRemark> remarks = inspector.validateModel(model, false); | |||
db.close(); | |||
for (ValidationRemark remark : remarks) { | |||
System.out.println(remark.toString()); | |||
} | |||
} | |||
@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<ValidationRemark> remarks = inspector.validateModel(model, false); | |||
db.close(); | |||
for (ValidationRemark remark : remarks) { | |||
System.out.println(remark.toString()); | |||
} | |||
} | |||
} |
@@ -16,15 +16,6 @@ | |||
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; | |||
@@ -33,123 +24,131 @@ import com.iciql.test.models.EnumModels.EnumOrdinalModel; | |||
import com.iciql.test.models.EnumModels.EnumStringModel; | |||
import com.iciql.test.models.EnumModels.Genus; | |||
import com.iciql.test.models.EnumModels.Tree; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import java.util.List; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
/** | |||
* 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()); | |||
testStringEnumIds(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<EnumModels> 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<EnumModels> 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()); | |||
} | |||
private void testStringEnumIds(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.genus()).is(Genus.PINUS).selectFirst(); | |||
assertEquals(Tree.PINE, firstEnumValue.tree()); | |||
assertEquals(Genus.PINUS, firstEnumValue.genus()); | |||
EnumModels model = db.from(e).where(e.genus()).is(Genus.JUGLANS).selectFirst(); | |||
assertEquals(400, model.id.intValue()); | |||
assertEquals(Tree.WALNUT, model.tree()); | |||
assertEquals(Genus.JUGLANS, model.genus()); | |||
List<EnumModels> list = db.from(e).where(e.genus()).isNot(Genus.BETULA).select(); | |||
assertEquals(count - 1, 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; | |||
} | |||
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()); | |||
testStringEnumIds(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<EnumModels> 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<EnumModels> 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()); | |||
} | |||
private void testStringEnumIds(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.genus()).is(Genus.PINUS).selectFirst(); | |||
assertEquals(Tree.PINE, firstEnumValue.tree()); | |||
assertEquals(Genus.PINUS, firstEnumValue.genus()); | |||
EnumModels model = db.from(e).where(e.genus()).is(Genus.JUGLANS).selectFirst(); | |||
assertEquals(400, model.id.intValue()); | |||
assertEquals(Tree.WALNUT, model.tree()); | |||
assertEquals(Genus.JUGLANS, model.genus()); | |||
List<EnumModels> list = db.from(e).where(e.genus()).isNot(Genus.BETULA).select(); | |||
assertEquals(count - 1, 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; | |||
} | |||
} |
@@ -16,68 +16,67 @@ | |||
*/ | |||
package com.iciql.test; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import com.iciql.Db; | |||
import com.iciql.IciqlException; | |||
import com.iciql.test.models.CategoryAnnotationOnly; | |||
import com.iciql.test.models.ProductAnnotationOnlyWithForeignKey; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import com.iciql.Db; | |||
import com.iciql.IciqlException; | |||
import com.iciql.test.models.CategoryAnnotationOnly; | |||
import com.iciql.test.models.ProductAnnotationOnlyWithForeignKey; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
/** | |||
* Tests of Foreign Keys. | |||
*/ | |||
public class ForeignKeyTest { | |||
/** | |||
* This object represents a database (actually a connection to the | |||
* database). | |||
*/ | |||
/** | |||
* 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(); | |||
private Db db; | |||
// should remove 2 associated products | |||
CategoryAnnotationOnly c = new CategoryAnnotationOnly(); | |||
db.from(c).where(c.categoryId).is(1L).delete(); | |||
@Before | |||
public void setUp() { | |||
db = IciqlSuite.openNewDb(); | |||
db.insertAll(CategoryAnnotationOnly.getList()); | |||
db.insertAll(ProductAnnotationOnlyWithForeignKey.getList()); | |||
} | |||
long count2 = db.from(p).selectCount(); | |||
@After | |||
public void tearDown() { | |||
db.dropTable(ProductAnnotationOnlyWithForeignKey.class); | |||
db.dropTable(CategoryAnnotationOnly.class); | |||
db.close(); | |||
} | |||
assertEquals(count1, count2 + 2L); | |||
} | |||
@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 | |||
@Ignore | |||
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()); | |||
} | |||
} | |||
@Test | |||
@Ignore | |||
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()); | |||
} | |||
} | |||
} |
@@ -16,72 +16,70 @@ | |||
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; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import static org.junit.Assert.assertEquals; | |||
/** | |||
* 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<UserNote> 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<UserId> 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); | |||
} | |||
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<UserNote> 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<UserId> 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 { | |||
@@ -103,71 +101,71 @@ public class JoinTest { | |||
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()); | |||
} | |||
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<UserId> 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<UserNote> 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); | |||
} | |||
} | |||
@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<UserId> 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<UserNote> 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); | |||
} | |||
} | |||
} |
@@ -17,20 +17,6 @@ | |||
package com.iciql.test; | |||
import static com.iciql.test.IciqlSuite.assertEqualsIgnoreCase; | |||
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 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; | |||
@@ -39,140 +25,153 @@ import com.iciql.test.models.ProductAnnotationOnly; | |||
import com.iciql.test.models.ProductMixedAnnotation; | |||
import com.iciql.test.models.SupportedTypes; | |||
import com.iciql.util.StringUtils; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ErrorCollector; | |||
import java.sql.SQLException; | |||
import java.text.MessageFormat; | |||
import java.util.List; | |||
import static com.iciql.test.IciqlSuite.assertEqualsIgnoreCase; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
/** | |||
* 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() { | |||
// SQLite metadata mapping in the JDBC driver needs improvement | |||
String schemaName = IciqlSuite.getDefaultSchema(db); | |||
DbInspector inspector = new DbInspector(db); | |||
validateModel(inspector, schemaName, new ProductAnnotationOnly(), IciqlSuite.isSQLite(db) ? 5 : 2); | |||
validateModel(inspector, schemaName, new ProductMixedAnnotation(), IciqlSuite.isSQLite(db) ? 6 : 4); | |||
} | |||
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(); | |||
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 (IciqlSuite.isSQLite(db)) { | |||
assertEquals(sb.toString(), expected, remarks.size()); | |||
} else 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<SupportedTypes> original = SupportedTypes.createList(); | |||
db.insertAll(original); | |||
List<SupportedTypes> 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<SupportedTypes> original = SupportedTypes.createList(); | |||
db.insertAll(original); | |||
DbInspector inspector = new DbInspector(db); | |||
List<String> 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 if (dbName.equals("SQLite")) { | |||
assertEquals(1566, models.get(0).length()); | |||
} else { | |||
// unknown database | |||
assertEquals(0, models.get(0).length()); | |||
} | |||
} | |||
@Test | |||
public void testDiscreteUpdateStringTrimming() { | |||
List<SupportedTypes> original = SupportedTypes.createList(); | |||
db.insertAll(original); | |||
SupportedTypes s1 = db.from(SupportedTypes.SAMPLE).where(SupportedTypes.SAMPLE.id).is(1).selectFirst(); | |||
db.from(SupportedTypes.SAMPLE) | |||
.set(SupportedTypes.SAMPLE.myString) | |||
.to(s1.myString + s1.myString + s1.myString + s1.myString) | |||
.update(); | |||
SupportedTypes s2 = db.from(SupportedTypes.SAMPLE).where(SupportedTypes.SAMPLE.id).is(1).selectFirst(); | |||
assertEquals(40, s2.myString.length()); | |||
} | |||
@Test | |||
public void testColumnSelection() { | |||
List<SupportedTypes> original = SupportedTypes.createList(); | |||
db.insertAll(original); | |||
List<String> myStrings = db.from(SupportedTypes.SAMPLE) | |||
.select(SupportedTypes.SAMPLE.myString); | |||
assertEquals(10, myStrings.size()); | |||
List<Integer> ids = db.from(SupportedTypes.SAMPLE) | |||
.orderByDesc(SupportedTypes.SAMPLE.myInteger) | |||
.selectDistinct(SupportedTypes.SAMPLE.myInteger); | |||
assertEquals(10, ids.size()); | |||
assertEquals("[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]", ids.toString()); | |||
} | |||
/* | |||
* 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() { | |||
// SQLite metadata mapping in the JDBC driver needs improvement | |||
String schemaName = IciqlSuite.getDefaultSchema(db); | |||
DbInspector inspector = new DbInspector(db); | |||
validateModel(inspector, schemaName, new ProductAnnotationOnly(), IciqlSuite.isSQLite(db) ? 5 : 2); | |||
validateModel(inspector, schemaName, new ProductMixedAnnotation(), IciqlSuite.isSQLite(db) ? 6 : 4); | |||
} | |||
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(); | |||
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 (IciqlSuite.isSQLite(db)) { | |||
assertEquals(sb.toString(), expected, remarks.size()); | |||
} else 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<SupportedTypes> original = SupportedTypes.createList(); | |||
db.insertAll(original); | |||
List<SupportedTypes> 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<SupportedTypes> original = SupportedTypes.createList(); | |||
db.insertAll(original); | |||
DbInspector inspector = new DbInspector(db); | |||
List<String> 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 if (dbName.equals("SQLite")) { | |||
assertEquals(1566, models.get(0).length()); | |||
} else { | |||
// unknown database | |||
assertEquals(0, models.get(0).length()); | |||
} | |||
} | |||
@Test | |||
public void testDiscreteUpdateStringTrimming() { | |||
List<SupportedTypes> original = SupportedTypes.createList(); | |||
db.insertAll(original); | |||
SupportedTypes s1 = db.from(SupportedTypes.SAMPLE).where(SupportedTypes.SAMPLE.id).is(1).selectFirst(); | |||
db.from(SupportedTypes.SAMPLE) | |||
.set(SupportedTypes.SAMPLE.myString) | |||
.to(s1.myString + s1.myString + s1.myString + s1.myString) | |||
.update(); | |||
SupportedTypes s2 = db.from(SupportedTypes.SAMPLE).where(SupportedTypes.SAMPLE.id).is(1).selectFirst(); | |||
assertEquals(40, s2.myString.length()); | |||
} | |||
@Test | |||
public void testColumnSelection() { | |||
List<SupportedTypes> original = SupportedTypes.createList(); | |||
db.insertAll(original); | |||
List<String> myStrings = db.from(SupportedTypes.SAMPLE) | |||
.select(SupportedTypes.SAMPLE.myString); | |||
assertEquals(10, myStrings.size()); | |||
List<Integer> ids = db.from(SupportedTypes.SAMPLE) | |||
.orderByDesc(SupportedTypes.SAMPLE.myInteger) | |||
.selectDistinct(SupportedTypes.SAMPLE.myInteger); | |||
assertEquals(10, ids.size()); | |||
assertEquals("[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]", ids.toString()); | |||
} | |||
} |