stmts = Lists.newArrayListWithCapacity(1);
@@ -667,7 +665,7 @@ public class PersistenceContext
// the MySQL JDBC driver has the annoying habit of including the embedded
// exception stack trace in the message of their outer exception; if I want a
// fucking stack trace, I'll call printStackTrace() thanksverymuch
- String msg = StringUtil.split(String.valueOf(sqe), "\n")[0];
+ String msg = String.valueOf(sqe).split("\n")[0];
log.info("Transient failure executing op, retrying [error=" + msg + "].");
} else {
@@ -695,7 +693,7 @@ public class PersistenceContext
}
interface ConnOp {
- Connection get () throws PersistenceException;
+ Connection get ();
void done (Connection conn) throws SQLException;
boolean fail (Connection conn, SQLException sqe);
void release (Connection conn);
@@ -706,7 +704,7 @@ public class PersistenceContext
public NonTxOp (boolean readOnly) {
this.readOnly = readOnly;
}
- public Connection get () throws PersistenceException {
+ public Connection get () {
return _conprov.getConnection(_ident, readOnly);
}
public void done (Connection conn) throws SQLException {
@@ -731,7 +729,7 @@ public class PersistenceContext
public TxOp (Transaction tx) {
this.tx = tx;
}
- public Connection get () throws PersistenceException {
+ public Connection get () {
return tx.getConnection();
}
public void done (Connection conn) throws SQLException {
diff --git a/src/main/java/com/samskivert/depot/SchemaMigration.java b/src/main/java/com/samskivert/depot/SchemaMigration.java
index 44d5a23..34174af 100644
--- a/src/main/java/com/samskivert/depot/SchemaMigration.java
+++ b/src/main/java/com/samskivert/depot/SchemaMigration.java
@@ -8,13 +8,12 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
-import com.samskivert.jdbc.ColumnDefinition;
-import com.samskivert.jdbc.DatabaseLiaison;
-
import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.impl.FieldMarshaller;
import com.samskivert.depot.impl.Modifier;
+import com.samskivert.depot.impl.jdbc.ColumnDefinition;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
import static com.google.common.base.Preconditions.checkArgument;
import static com.samskivert.depot.Log.log;
diff --git a/src/main/java/com/samskivert/depot/StaticConnectionProvider.java b/src/main/java/com/samskivert/depot/StaticConnectionProvider.java
new file mode 100644
index 0000000..a193c13
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/StaticConnectionProvider.java
@@ -0,0 +1,354 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.*;
+import java.util.*;
+
+import com.google.common.base.Strings;
+
+import static com.samskivert.depot.Log.log;
+
+/**
+ * The static connection provider generates JDBC connections based on configuration information
+ * provided via a properties file. It does no connection pooling and always returns the same
+ * connection for a particular identifier (unless that connection need be closed because of a
+ * connection failure, in which case it opens a new one the next time the connection is requested).
+ *
+ * The configuration properties file should contain the following information:
+ *
+ *
+ * IDENT.driver=[jdbc driver class]
+ * IDENT.url=[jdbc driver url]
+ * IDENT.username=[jdbc username]
+ * IDENT.password=[jdbc password]
+ *
+ * [...]
+ *
+ *
+ * Where IDENT is the database identifier for a particular database connection. When a
+ * particular database identifier is requested, the configuration information will be fetched from
+ * the properties.
+ *
+ * Additionally, a default set of properties can be provided using the identifier
+ * default. Values not provided for a specific identifier will be sought from the
+ * defaults. For example:
+ *
+ *
+ * default.driver=[jdbc driver class]
+ * default.url=[jdbc driver class]
+ *
+ * IDENT1.username=[jdbc username]
+ * IDENT1.password=[jdbc password]
+ *
+ * IDENT2.username=[jdbc username]
+ * IDENT2.password=[jdbc password]
+ *
+ * [...]
+ *
+ */
+public class StaticConnectionProvider implements ConnectionProvider
+{
+ /** Creates a provider for testing, using HSQLDB. */
+ public static ConnectionProvider forTest (String dbname) {
+ Properties props = new Properties();
+ props.setProperty("default.driver", "org.hsqldb.jdbcDriver");
+ props.setProperty("default.username", "sa");
+ props.setProperty("default.password", "none");
+ props.setProperty("default.url", "jdbc:hsqldb:mem:" + dbname);
+ return new StaticConnectionProvider(props);
+ }
+
+ /**
+ * Constructs a static connection provider which will load its configuration from a properties
+ * file accessible via the classpath of the running application and identified by the specified
+ * path.
+ *
+ * @param propPath the path (relative to the classpath) to the properties file that will be
+ * used for configuration information.
+ *
+ * @exception IOException thrown if an error occurs locating or loading the specified
+ * properties file.
+ */
+ public StaticConnectionProvider (String propPath) throws IOException
+ {
+ this(loadProperties(propPath, ConnectionProvider.class.getClassLoader()));
+ }
+
+ /**
+ * Constructs a static connection provider which will fetch its configuration information from
+ * the specified properties object.
+ *
+ * @param props the configuration for this connection provider.
+ */
+ public StaticConnectionProvider (Properties props)
+ {
+ _props = props;
+ }
+
+ // from ConnectionProvider
+ public String getURL (String ident)
+ {
+ Properties props = getSubProperties(_props, ident, DEFAULTS_KEY);
+ return props.getProperty("url");
+ }
+
+ // from ConnectionProvider
+ public Connection getConnection (String ident, boolean readOnly)
+ {
+ return getMapping(ident, readOnly).getConnection(ident);
+ }
+
+ // from ConnectionProvider
+ public void releaseConnection (String ident, boolean readOnly, Connection conn)
+ {
+ // nothing to do here, all is well
+ }
+
+ // from ConnectionProvider
+ public void connectionFailed (
+ String ident, boolean readOnly, Connection conn, SQLException error)
+ {
+ String mapkey = ident + ":" + readOnly;
+ Mapping conmap = _idents.get(mapkey);
+ if (conmap == null) {
+ log.warning("Unknown connection failed!?", "key", mapkey);
+ } else {
+ conmap.closeConnection(ident);
+ }
+ }
+
+ // from ConnectionProvider
+ public Connection getTxConnection (String ident)
+ {
+ return getMapping(ident, false).openConnection(ident, false);
+ }
+
+ // from ConnectionProvider
+ public void releaseTxConnection (String ident, Connection conn)
+ {
+ close(conn, ident);
+ }
+
+ // from ConnectionProvider
+ public void txConnectionFailed (String ident, Connection conn, SQLException error)
+ {
+ close(conn, ident);
+ }
+
+ // from ConnectionProvider
+ public void shutdown ()
+ {
+ // close all of the connections
+ for (Map.Entry entry : _keys.entrySet()) {
+ entry.getValue().closeConnection(entry.getKey());
+ }
+
+ // clear out our mapping tables
+ _keys.clear();
+ _idents.clear();
+ }
+
+ protected Mapping getMapping (String ident, boolean readOnly) {
+ String mapkey = ident + ":" + readOnly;
+ Mapping conmap = _idents.get(mapkey);
+ if (conmap != null) return conmap;
+
+ Properties props = getSubProperties(_props, ident, DEFAULTS_KEY);
+ Info info = new Info(ident, props);
+
+ // if this is a read-only connection, we cache connections by username+url+readOnly to
+ // avoid making more that one connection to a particular database server
+ String key = info.username + "@" + info.url + ":" + readOnly;
+ conmap = _keys.get(key);
+ if (conmap == null) {
+ log.debug("Creating " + key + " for " + ident + ".");
+ _keys.put(key, conmap = new Mapping(key, info, readOnly));
+
+ } else {
+ log.debug("Reusing " + key + " for " + ident + ".");
+ }
+
+ // cache the connection
+ _idents.put(mapkey, conmap);
+
+ return conmap;
+ }
+
+ protected static void close (Connection conn, String ident) {
+ try {
+ conn.close();
+ } catch (SQLException sqe) {
+ log.warning("Error closing failed connection", "ident", ident, "error", sqe);
+ }
+ }
+
+ protected static Properties loadProperties (String propPath, ClassLoader loader)
+ throws IOException {
+ InputStream in = loader.getResourceAsStream(propPath);
+ if (in == null) throw new FileNotFoundException(propPath);
+ Properties props = new Properties();
+ props.load(in);
+ in.close();
+ return props;
+ }
+
+ protected static Properties getSubProperties (Properties source, String pref, String defPref)
+ {
+ Properties defs = new Properties();
+ extractProperties(source, defs, defPref);
+ Properties dest = new Properties(defs);
+ extractProperties(source, dest, pref);
+ return dest;
+ }
+
+ protected static void extractProperties (Properties source, Properties dest, String prefix)
+ {
+ // extend the prefix to contain a dot
+ prefix = prefix + ".";
+ int preflen = prefix.length();
+
+ // scan the source properties
+ Enumeration> names = source.propertyNames();
+ while (names.hasMoreElements()) {
+ String name = (String)names.nextElement();
+ // skip unrelated properties
+ if (!name.startsWith(prefix)) {
+ continue;
+ }
+ // insert the value into the new properties minus the prefix
+ dest.put(name.substring(preflen), source.getProperty(name));
+ }
+ }
+
+ protected static class Info {
+ public final String ident, driver, url, username, password;
+ public final Boolean autoCommit;
+
+ public Info (String ident, Properties props) {
+ this.ident = ident;
+ this.driver = requireProp(props, "driver", "No driver class specified");
+ this.url = requireProp(props, "url", "No driver URL specified");
+ this.username = requireProp(props, "username", "No driver username specified");
+ this.password = props.getProperty("password", "");
+ String ac = props.getProperty("autocommit");
+ this.autoCommit = (ac == null) ? null : Boolean.valueOf(ac);
+ }
+
+ protected String requireProp (Properties props, String name, String errmsg) {
+ String value = Strings.nullToEmpty(props.getProperty(name)).trim();
+ if (value.isEmpty()) {
+ errmsg = "Unable to get connection. " + errmsg + " [ident=" + ident + "]";
+ throw new DatabaseException(errmsg);
+ }
+ return value;
+ }
+ }
+
+ /** Contains information on a particular connection to which any number of database identifiers
+ * can be mapped. */
+ protected static class Mapping
+ {
+ /** The combination of username and JDBC url that uniquely identifies our database
+ * connection. */
+ public final String key;
+
+ public Mapping (String key, Info info, boolean readOnly) {
+ this.key = key;
+ _info = info;
+ _readOnly = readOnly;
+ }
+
+ /** Returns the main connection for this mapping, (re)opening it if necessary. */
+ public Connection getConnection (String ident) {
+ if (_conn == null) _conn = openConnection(ident, _info.autoCommit);
+ return _conn;
+ }
+
+ /** Opens and returns a new connection to this mapping's database. */
+ public Connection openConnection (String ident, Boolean autoCommit) {
+ // create an instance of the driver
+ Driver jdriver;
+ try {
+ jdriver = (Driver)Class.forName(_info.driver).newInstance();
+ } catch (Exception e) {
+ throw new DatabaseException(
+ "Error loading driver [class=" + _info.driver + "].", e);
+ }
+
+ // create the connection
+ Connection conn;
+ try {
+ Properties props = new Properties();
+ props.put("user", _info.username);
+ props.put("password", _info.password);
+ conn = jdriver.connect(_info.url, props);
+
+ } catch (SQLException sqe) {
+ throw new DatabaseException(
+ "Error creating database connection [driver=" + _info.driver +
+ ", url=" + _info.url + ", username=" + _info.username + "].", sqe);
+ }
+
+ // if we were requested to configure auto-commit, then do so
+ if (autoCommit != null) {
+ try {
+ conn.setAutoCommit(autoCommit);
+ } catch (SQLException sqe) {
+ close(conn, ident);
+ throw new DatabaseException(
+ "Failed to configure auto-commit [key=" + key + ", ident=" + ident +
+ ", autoCommit=" + autoCommit + "].", sqe);
+ }
+ }
+
+ // make the connection read-only to let the JDBC driver know that it can and should
+ // use the read-only mirror(s)
+ if (_readOnly) {
+ try {
+ conn.setReadOnly(true);
+ } catch (SQLException sqe) {
+ close(conn, ident);
+ throw new DatabaseException(
+ "Failed to make connection read-only [key=" + key + ", ident=" + ident +
+ "].", sqe);
+ }
+ }
+ return conn;
+ }
+
+ /**
+ * Closes the main connection for this mapping, causing it to be reopened on the next call
+ * to {@link #getConnection}.
+ * @param ident the ident on behalf of which we are operating.
+ */
+ public void closeConnection (String ident) {
+ if (_conn != null) {
+ close(_conn, ident);
+ _conn = null;
+ }
+ }
+
+ protected final Info _info;
+ protected final boolean _readOnly;
+ protected Connection _conn;
+ }
+
+ /** Our configuration in the form of a properties object. */
+ protected Properties _props;
+
+ /** A mapping from database identifier to connection records. */
+ protected HashMap _idents = new HashMap();
+
+ /** A mapping from connection key to connection records. */
+ protected HashMap _keys = new HashMap();
+
+ /** The key used as defaults for the database definitions. */
+ protected static final String DEFAULTS_KEY = "default";
+}
diff --git a/src/main/java/com/samskivert/depot/Stats.java b/src/main/java/com/samskivert/depot/Stats.java
index 0e6c48b..252b414 100644
--- a/src/main/java/com/samskivert/depot/Stats.java
+++ b/src/main/java/com/samskivert/depot/Stats.java
@@ -4,7 +4,7 @@
package com.samskivert.depot;
-import com.samskivert.util.Histogram;
+import java.util.Arrays;
import static com.samskivert.depot.Log.log;
@@ -13,6 +13,77 @@ import static com.samskivert.depot.Log.log;
*/
public class Stats
{
+ /** Used for tracking a set of values that fall into a discrete range of values. */
+ public static class Histogram implements Cloneable
+ {
+ public Histogram (int minValue, int bucketWidth, int bucketCount) {
+ _minValue = minValue;
+ _maxValue = minValue + bucketWidth*bucketCount;
+ _bucketWidth = bucketWidth;
+ _buckets = new int[bucketCount];
+ }
+
+ /**
+ * Returns the array containing the bucket values. The zeroth element contains the count of
+ * all values less than minValue, the subsequent bucketCount
+ * elements contain the count of values falling into those buckets and the last element
+ * contains values greater than or equal to maxValue.
+ */
+ public int[] getBuckets () {
+ return _buckets;
+ }
+
+ /**
+ * Generates a terse summary of the count and contents of the values in this histogram.
+ */
+ public String summarize () {
+ StringBuilder buf = new StringBuilder();
+ buf.append(_count).append(":");
+ for (int ii = 0; ii < _buckets.length; ii++) {
+ if (ii > 0) {
+ buf.append(",");
+ }
+ buf.append(_buckets[ii]);
+ }
+ return buf.toString();
+ }
+
+ @Override public Histogram clone () {
+ try {
+ Histogram chisto = (Histogram)super.clone();
+ chisto._buckets = _buckets.clone();
+ return chisto;
+ } catch (CloneNotSupportedException cnse) {
+ throw new AssertionError(cnse);
+ }
+ }
+
+ @Override public String toString () {
+ return "[min=" + _minValue + ", max=" + _maxValue + ", bwidth=" + _bucketWidth +
+ ", buckets=" + Arrays.toString(_buckets) + "]";
+ }
+
+ void addValue (int value) {
+ if (value < _minValue) {
+ _buckets[0]++;
+ } else if (value >= _maxValue) {
+ _buckets[_buckets.length-1]++;
+ } else {
+ _buckets[(value-_minValue)/_bucketWidth]++;
+ }
+ _count++;
+ }
+ int size () {
+ return _count;
+ }
+ void clear () {
+ Arrays.fill(_buckets, 0);
+ }
+
+ protected int _minValue, _maxValue, _bucketWidth, _count;
+ protected int[] _buckets;
+ }
+
/**
* An immutable class used to report statistics on repository activity. Statistics are tracked
* from the start of the VM and are never reset.
diff --git a/src/main/java/com/samskivert/depot/Transaction.java b/src/main/java/com/samskivert/depot/Transaction.java
index ac0af23..b1a5207 100644
--- a/src/main/java/com/samskivert/depot/Transaction.java
+++ b/src/main/java/com/samskivert/depot/Transaction.java
@@ -4,8 +4,6 @@
package com.samskivert.depot;
-import com.samskivert.io.PersistenceException;
-import com.samskivert.jdbc.ConnectionProvider;
import java.sql.Connection;
import java.sql.SQLException;
@@ -122,7 +120,7 @@ public class Transaction {
}
}
- Connection getConnection () throws PersistenceException
+ Connection getConnection ()
{
if (_conn == null) _conn = ctx._conprov.getTxConnection(ctx._ident);
return _conn;
diff --git a/src/main/java/com/samskivert/depot/Transformers.java b/src/main/java/com/samskivert/depot/Transformers.java
index 2c1924f..2ca5502 100644
--- a/src/main/java/com/samskivert/depot/Transformers.java
+++ b/src/main/java/com/samskivert/depot/Transformers.java
@@ -25,11 +25,9 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
-import com.samskivert.util.ByteEnum;
-import com.samskivert.util.ByteEnumUtil;
-
import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.annotation.Transform;
+import com.samskivert.depot.util.ByteEnum;
import static com.samskivert.depot.Log.log;
@@ -61,13 +59,13 @@ public class Transformers
@Override
public Integer toPersistent (Set value)
{
- return (value == null) ? null : ByteEnumUtil.setToInt(value);
+ return (value == null) ? null : ByteEnum.Util.setToInt(value);
}
@Override
public Set fromPersistent (Integer encoded)
{
- return (encoded == null) ? null : ByteEnumUtil.intToSet(_eclass, encoded);
+ return (encoded == null) ? null : ByteEnum.Util.intToSet(_eclass, encoded);
}
/** The enum class token. */
diff --git a/src/main/java/com/samskivert/depot/clause/OrderBy.java b/src/main/java/com/samskivert/depot/clause/OrderBy.java
index 8bc1ccb..59f863e 100644
--- a/src/main/java/com/samskivert/depot/clause/OrderBy.java
+++ b/src/main/java/com/samskivert/depot/clause/OrderBy.java
@@ -6,8 +6,9 @@ package com.samskivert.depot.clause;
import java.util.Collection;
+import com.google.common.collect.ObjectArrays;
+
import com.samskivert.depot.PersistentRecord;
-import com.samskivert.util.ArrayUtil;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.LiteralExp;
@@ -89,8 +90,8 @@ public class OrderBy implements QueryClause
*/
public OrderBy thenAscending (SQLExpression> value)
{
- return new OrderBy(ArrayUtil.append(_values, value),
- ArrayUtil.append(_orders, Order.ASC));
+ return new OrderBy(ObjectArrays.concat(_values, value),
+ ObjectArrays.concat(_orders, Order.ASC));
}
/**
@@ -98,8 +99,8 @@ public class OrderBy implements QueryClause
*/
public OrderBy thenDescending (SQLExpression> value)
{
- return new OrderBy(ArrayUtil.append(_values, value),
- ArrayUtil.append(_orders, Order.DESC));
+ return new OrderBy(ObjectArrays.concat(_values, value),
+ ObjectArrays.concat(_orders, Order.DESC));
}
// from SQLExpression
diff --git a/src/main/java/com/samskivert/depot/impl/BuildVisitor.java b/src/main/java/com/samskivert/depot/impl/BuildVisitor.java
index fb18309..f540d88 100644
--- a/src/main/java/com/samskivert/depot/impl/BuildVisitor.java
+++ b/src/main/java/com/samskivert/depot/impl/BuildVisitor.java
@@ -16,10 +16,6 @@ import com.google.common.collect.Maps;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import com.samskivert.depot.clause.Distinct;
-import com.samskivert.util.ByteEnum;
-import com.samskivert.util.Tuple;
-
import com.samskivert.depot.Exps;
import com.samskivert.depot.Key;
import com.samskivert.depot.PersistentRecord;
@@ -39,29 +35,30 @@ import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.*;
import com.samskivert.depot.operator.Case;
import com.samskivert.depot.operator.FullText;
+import com.samskivert.depot.util.ByteEnum;
+import com.samskivert.depot.util.Tuple2;
import static com.samskivert.depot.Log.log;
+import com.samskivert.depot.clause.Distinct;
import com.samskivert.depot.impl.clause.CreateIndexClause;
import com.samskivert.depot.impl.clause.DeleteClause;
import com.samskivert.depot.impl.clause.DropIndexClause;
import com.samskivert.depot.impl.clause.UpdateClause;
-import com.samskivert.depot.impl.expression.AggregateFun;
-import com.samskivert.depot.impl.expression.IntervalExp;
-import com.samskivert.depot.impl.expression.LiteralExp;
-import com.samskivert.depot.impl.expression.RandomExp;
-import com.samskivert.depot.impl.expression.ValueExp;
import com.samskivert.depot.impl.expression.AggregateFun.Average;
import com.samskivert.depot.impl.expression.AggregateFun.Count;
import com.samskivert.depot.impl.expression.AggregateFun.Every;
import com.samskivert.depot.impl.expression.AggregateFun.Max;
import com.samskivert.depot.impl.expression.AggregateFun.Min;
import com.samskivert.depot.impl.expression.AggregateFun.Sum;
+import com.samskivert.depot.impl.expression.AggregateFun;
import com.samskivert.depot.impl.expression.ConditionalFun.Coalesce;
import com.samskivert.depot.impl.expression.ConditionalFun.Greatest;
import com.samskivert.depot.impl.expression.ConditionalFun.Least;
import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DateTruncate;
import com.samskivert.depot.impl.expression.DateFun.Now;
+import com.samskivert.depot.impl.expression.IntervalExp;
+import com.samskivert.depot.impl.expression.LiteralExp;
import com.samskivert.depot.impl.expression.NumericalFun.Abs;
import com.samskivert.depot.impl.expression.NumericalFun.Ceil;
import com.samskivert.depot.impl.expression.NumericalFun.Exp;
@@ -75,12 +72,14 @@ import com.samskivert.depot.impl.expression.NumericalFun.Round;
import com.samskivert.depot.impl.expression.NumericalFun.Sign;
import com.samskivert.depot.impl.expression.NumericalFun.Sqrt;
import com.samskivert.depot.impl.expression.NumericalFun.Trunc;
+import com.samskivert.depot.impl.expression.RandomExp;
import com.samskivert.depot.impl.expression.StringFun.Length;
import com.samskivert.depot.impl.expression.StringFun.Lower;
import com.samskivert.depot.impl.expression.StringFun.Position;
import com.samskivert.depot.impl.expression.StringFun.Substring;
import com.samskivert.depot.impl.expression.StringFun.Trim;
import com.samskivert.depot.impl.expression.StringFun.Upper;
+import com.samskivert.depot.impl.expression.ValueExp;
import com.samskivert.depot.impl.operator.BinaryOperator;
import com.samskivert.depot.impl.operator.Exists;
import com.samskivert.depot.impl.operator.In;
@@ -214,11 +213,11 @@ public abstract class BuildVisitor implements FragmentVisitor
public Void visit (Case> caseExp)
{
_builder.append("(case ");
- for (Tuple, SQLExpression>> tuple : caseExp.getWhenExps()) {
+ for (Tuple2, SQLExpression>> tuple : caseExp.getWhenExps()) {
_builder.append(" when ");
- tuple.left.accept(this);
+ tuple.a.accept(this);
_builder.append(" then ");
- tuple.right.accept(this);
+ tuple.b.accept(this);
}
SQLExpression> elseExp = caseExp.getElseExp();
if (elseExp != null) {
@@ -511,8 +510,8 @@ public abstract class BuildVisitor implements FragmentVisitor
public Void visit (CreateIndexClause createIndexClause)
{
if (!_allowComplexIndices) {
- for (Tuple, Order> field : createIndexClause.getFields()) {
- if (!(field.left instanceof ColumnExp>)) {
+ for (Tuple2, Order> field : createIndexClause.getFields()) {
+ if (!(field.a instanceof ColumnExp>)) {
log.warning("This database can't handle complex indexes. Not creating.",
"ixName", createIndexClause.getName());
return null;
@@ -532,7 +531,7 @@ public abstract class BuildVisitor implements FragmentVisitor
// turn off table abbreviations here
_defaultType = createIndexClause.getPersistentClass();
boolean comma = false;
- for (Tuple, Order> field : createIndexClause.getFields()) {
+ for (Tuple2, Order> field : createIndexClause.getFields()) {
if (comma) {
_builder.append(", ");
}
@@ -544,11 +543,11 @@ public abstract class BuildVisitor implements FragmentVisitor
if (_allowComplexIndices) {
_builder.append("(");
}
- field.left.accept(this);
+ field.a.accept(this);
if (_allowComplexIndices) {
_builder.append(")");
}
- if (field.right == Order.DESC) {
+ if (field.b == Order.DESC) {
// ascending is default, print nothing unless explicitly descending
_builder.append(" desc");
}
diff --git a/src/main/java/com/samskivert/depot/impl/DepotMarshaller.java b/src/main/java/com/samskivert/depot/impl/DepotMarshaller.java
index 83e545d..d1a5915 100644
--- a/src/main/java/com/samskivert/depot/impl/DepotMarshaller.java
+++ b/src/main/java/com/samskivert/depot/impl/DepotMarshaller.java
@@ -18,6 +18,7 @@ import java.util.Map;
import java.util.Set;
import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
@@ -25,11 +26,6 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import static com.google.common.base.Preconditions.checkArgument;
-import com.samskivert.jdbc.ColumnDefinition;
-import com.samskivert.jdbc.DatabaseLiaison;
-import com.samskivert.util.StringUtil;
-import com.samskivert.util.Tuple;
-
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.Key;
import com.samskivert.depot.PersistenceContext;
@@ -50,6 +46,9 @@ import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.clause.CreateIndexClause;
+import com.samskivert.depot.impl.jdbc.ColumnDefinition;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
+import com.samskivert.depot.util.Tuple2;
import static com.samskivert.depot.Log.log;
/**
@@ -100,9 +99,9 @@ public class DepotMarshaller implements QueryMarshal
// introspect on the class and create marshallers and indices for persistent fields
List> fields = Lists.newArrayList();
- ListMultimap, Order>> namedFieldIndices =
+ ListMultimap, Order>> namedFieldIndices =
ArrayListMultimap.create();
- ListMultimap, Order>> uniqueNamedFieldIndices =
+ ListMultimap, Order>> uniqueNamedFieldIndices =
ArrayListMultimap.create();
for (Field field : _pClass.getFields()) {
int mods = field.getModifiers();
@@ -183,8 +182,8 @@ public class DepotMarshaller implements QueryMarshal
"Unique columns are implicitly indexed and should not be @Index'd.");
String name = index.name().equals("") ? field.getName() + "Index" : index.name();
- Tuple, Order> entry =
- new Tuple, Order>(fieldColumn, Order.ASC);
+ Tuple2, Order> entry =
+ new Tuple2, Order>(fieldColumn, Order.ASC);
if (index.unique()) {
checkArgument(!namedFieldIndices.containsKey(index.name()),
"All @Index for a particular name must be unique or non-unique");
@@ -931,27 +930,27 @@ public class DepotMarshaller implements QueryMarshal
protected CreateIndexClause buildIndex (String name, boolean unique, Object config)
{
- List, Order>> definition = Lists.newArrayList();
+ List, Order>> definition = Lists.newArrayList();
if (config instanceof ColumnExp>) {
- definition.add(new Tuple, Order>((ColumnExp>)config, Order.ASC));
+ definition.add(new Tuple2, Order>((ColumnExp>)config, Order.ASC));
} else if (config instanceof ColumnExp>[]) {
for (ColumnExp> column : (ColumnExp>[])config) {
- definition.add(new Tuple, Order>(column, Order.ASC));
+ definition.add(new Tuple2, Order>(column, Order.ASC));
}
} else if (config instanceof SQLExpression) {
- definition.add(new Tuple, Order>((SQLExpression>)config, Order.ASC));
- } else if (config instanceof Tuple,?>) {
- @SuppressWarnings("unchecked") Tuple, Order> tuple =
- (Tuple, Order>)config;
+ definition.add(new Tuple2, Order>((SQLExpression>)config, Order.ASC));
+ } else if (config instanceof Tuple2,?>) {
+ @SuppressWarnings("unchecked") Tuple2, Order> tuple =
+ (Tuple2, Order>)config;
definition.add(tuple);
} else if (config instanceof List>) {
- @SuppressWarnings("unchecked") List, Order>> defs =
- (List, Order>>)config;
+ @SuppressWarnings("unchecked") List, Order>> defs =
+ (List, Order>>)config;
definition.addAll(defs);
} else {
throw new IllegalArgumentException(
"Method '" + name + "' must return ColumnExp[], SQLExpression or " +
- "List>");
+ "List>");
}
return new CreateIndexClause(_pClass, getTableName() + "_" + name, unique, definition);
}
@@ -1087,10 +1086,15 @@ public class DepotMarshaller implements QueryMarshal
return true;
}
- @Override
- public String toString ()
+ @Override public String toString ()
{
- return StringUtil.fieldsToString(this);
+ return MoreObjects.toStringHelper(this).
+ add("tableExists", tableExists).
+ add("tableColumns", tableColumns).
+ add("indexColumns", indexColumns).
+ add("pkName", pkName).
+ add("pkColumns", pkColumns).
+ toString();
}
}
diff --git a/src/main/java/com/samskivert/depot/impl/DepotMetaData.java b/src/main/java/com/samskivert/depot/impl/DepotMetaData.java
index 9383f4c..a3dce06 100644
--- a/src/main/java/com/samskivert/depot/impl/DepotMetaData.java
+++ b/src/main/java/com/samskivert/depot/impl/DepotMetaData.java
@@ -13,13 +13,12 @@ import java.util.Map;
import com.google.common.collect.Maps;
-import com.samskivert.jdbc.ColumnDefinition;
-import com.samskivert.jdbc.DatabaseLiaison;
-import com.samskivert.jdbc.HsqldbLiaison;
-import com.samskivert.jdbc.MySQLLiaison;
-import com.samskivert.jdbc.PostgreSQLLiaison;
-
import com.samskivert.depot.PersistenceContext;
+import com.samskivert.depot.impl.jdbc.ColumnDefinition;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
+import com.samskivert.depot.impl.jdbc.HsqldbLiaison;
+import com.samskivert.depot.impl.jdbc.MySQLLiaison;
+import com.samskivert.depot.impl.jdbc.PostgreSQLLiaison;
/**
* Does something extraordinary.
@@ -140,6 +139,19 @@ public class DepotMetaData
}) > 0;
}
+ /**
+ * Removes the version for the specified persistent class. This is useful if the class's table
+ * is being dropped.
+ */
+ public void clearVersion (final String pClass) {
+ _ctx.invoke(new Modifier.Simple() {
+ @Override protected String createQuery (DatabaseLiaison liaison) {
+ return "delete from " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
+ " where " + liaison.columnSQL(P_COLUMN) + " = '" + pClass + "'";
+ }
+ });
+ }
+
/**
* Creates and return a new {@link SQLBuilder} for the appropriate dialect.
*
diff --git a/src/main/java/com/samskivert/depot/impl/ExpressionEvaluator.java b/src/main/java/com/samskivert/depot/impl/ExpressionEvaluator.java
index 42c11e8..5395ee7 100644
--- a/src/main/java/com/samskivert/depot/impl/ExpressionEvaluator.java
+++ b/src/main/java/com/samskivert/depot/impl/ExpressionEvaluator.java
@@ -5,9 +5,11 @@
package com.samskivert.depot.impl;
import java.lang.reflect.Field;
+import java.util.Arrays;
import com.samskivert.depot.Key;
import com.samskivert.depot.PersistentRecord;
+import com.samskivert.depot.util.Tuple2;
import com.samskivert.depot.clause.Distinct;
import com.samskivert.depot.clause.FieldDefinition;
@@ -72,8 +74,6 @@ import com.samskivert.depot.impl.operator.In;
import com.samskivert.depot.impl.operator.IsNull;
import com.samskivert.depot.impl.operator.MultiOperator;
import com.samskivert.depot.impl.operator.Not;
-import com.samskivert.util.ArrayUtil;
-import com.samskivert.util.Tuple;
import static com.samskivert.depot.Log.log;
@@ -130,7 +130,7 @@ public class ExpressionEvaluator
{
Object operand = in.getExpression().accept(this);
return (operand instanceof NoValue) ? operand :
- -1 != ArrayUtil.indexOf(in.getValues(), operand);
+ Arrays.asList(in.getValues()).contains(operand);
}
public Object visit (FullText.Match match)
@@ -145,13 +145,13 @@ public class ExpressionEvaluator
public Object visit (Case> caseExp)
{
- for (Tuple, SQLExpression>> exp : caseExp.getWhenExps()) {
- Object result = exp.left.accept(this);
+ for (Tuple2, SQLExpression>> exp : caseExp.getWhenExps()) {
+ Object result = exp.a.accept(this);
if (result instanceof NoValue || !(result instanceof Boolean)) {
- return new NoValue("Failed to evaluate case: " + exp.left + " -> " + result);
+ return new NoValue("Failed to evaluate case: " + exp.a + " -> " + result);
}
if (((Boolean) result).booleanValue()) {
- return exp.right.accept(this);
+ return exp.b.accept(this);
}
}
SQLExpression> elseExp = caseExp.getElseExp();
diff --git a/src/main/java/com/samskivert/depot/impl/FieldMarshaller.java b/src/main/java/com/samskivert/depot/impl/FieldMarshaller.java
index 965c803..03b090f 100644
--- a/src/main/java/com/samskivert/depot/impl/FieldMarshaller.java
+++ b/src/main/java/com/samskivert/depot/impl/FieldMarshaller.java
@@ -22,18 +22,15 @@ import com.google.common.collect.ImmutableMap;
import static com.google.common.base.Preconditions.checkArgument;
import com.samskivert.depot.DatabaseException;
+import com.samskivert.depot.Log;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Transformer;
import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.annotation.Computed;
import com.samskivert.depot.annotation.GeneratedValue;
import com.samskivert.depot.annotation.Transform;
-import com.samskivert.jdbc.ColumnDefinition;
-
-import com.samskivert.util.ByteEnum;
-import com.samskivert.util.ByteEnumUtil;
-import com.samskivert.util.Logger;
-import com.samskivert.util.StringUtil;
+import com.samskivert.depot.impl.jdbc.ColumnDefinition;
+import com.samskivert.depot.util.ByteEnum;
/**
* Handles the marshalling and unmarshalling of a particular field of a persistent object.
@@ -88,12 +85,12 @@ public abstract class FieldMarshaller
return createTransformingMarshaller(xformer, field, xform);
} catch (InstantiationException e) {
throw new IllegalArgumentException(
- Logger.format("Unable to create Transformer", "xclass", xform.value(),
- "field", field), e);
+ Log.format("Unable to create Transformer", "xclass", xform.value(),
+ "field", field), e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(
- Logger.format("Unable to create Transformer", "xclass", xform.value(),
- "field", field), e);
+ Log.format("Unable to create Transformer", "xclass", xform.value(),
+ "field", field), e);
}
}
@@ -277,10 +274,8 @@ public abstract class FieldMarshaller
}
}
}
- if (column != null) {
- if (!StringUtil.isBlank(column.name())) {
- _columnName = column.name();
- }
+ if (column != null && !column.name().isEmpty()) {
+ _columnName = column.name();
}
if (_computed != null) {
@@ -673,11 +668,11 @@ public abstract class FieldMarshaller
}
@Override public ByteEnum getFromSet (ResultSet rs) throws SQLException {
Number value = (Number)rs.getObject(getColumnName());
- return (value == null) ? null : ByteEnumUtil.fromByte(_eclass, value.byteValue());
+ return (value == null) ? null : ByteEnum.Util.fromByte(_eclass, value.byteValue());
}
@Override public ByteEnum getFromSet (ResultSet rs, int index) throws SQLException {
Number value = (Number)rs.getObject(index);
- return (value == null) ? null : ByteEnumUtil.fromByte(_eclass, value.byteValue());
+ return (value == null) ? null : ByteEnum.Util.fromByte(_eclass, value.byteValue());
}
@Override public void writeToObject (Object po, ByteEnum value)
throws IllegalArgumentException, IllegalAccessException {
diff --git a/src/main/java/com/samskivert/depot/impl/FindAllKeysQuery.java b/src/main/java/com/samskivert/depot/impl/FindAllKeysQuery.java
index 2187065..438f706 100644
--- a/src/main/java/com/samskivert/depot/impl/FindAllKeysQuery.java
+++ b/src/main/java/com/samskivert/depot/impl/FindAllKeysQuery.java
@@ -13,7 +13,6 @@ import java.sql.SQLException;
import com.google.common.collect.Lists;
-import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.Key;
import com.samskivert.depot.PersistenceContext;
@@ -21,6 +20,7 @@ import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Stats;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.SelectClause;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
import static com.samskivert.depot.Log.log;
diff --git a/src/main/java/com/samskivert/depot/impl/FindAllQuery.java b/src/main/java/com/samskivert/depot/impl/FindAllQuery.java
index 7088138..3fdeafb 100644
--- a/src/main/java/com/samskivert/depot/impl/FindAllQuery.java
+++ b/src/main/java/com/samskivert/depot/impl/FindAllQuery.java
@@ -33,8 +33,8 @@ import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
import com.samskivert.depot.impl.operator.In;
-import com.samskivert.jdbc.DatabaseLiaison;
import static com.google.common.base.Preconditions.checkArgument;
import static com.samskivert.depot.Log.log;
diff --git a/src/main/java/com/samskivert/depot/impl/FindOneQuery.java b/src/main/java/com/samskivert/depot/impl/FindOneQuery.java
index 9290940..1c4fe86 100644
--- a/src/main/java/com/samskivert/depot/impl/FindOneQuery.java
+++ b/src/main/java/com/samskivert/depot/impl/FindOneQuery.java
@@ -8,17 +8,17 @@ import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
-import com.samskivert.jdbc.DatabaseLiaison;
+import com.samskivert.depot.CacheAdapter.CacheCategory;
import com.samskivert.depot.CacheKey;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.DepotRepository;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Stats;
-import com.samskivert.depot.CacheAdapter.CacheCategory;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.clause.WhereClause;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
import static com.samskivert.depot.Log.log;
diff --git a/src/main/java/com/samskivert/depot/impl/HSQLBuilder.java b/src/main/java/com/samskivert/depot/impl/HSQLBuilder.java
index 7f41273..f6708a2 100644
--- a/src/main/java/com/samskivert/depot/impl/HSQLBuilder.java
+++ b/src/main/java/com/samskivert/depot/impl/HSQLBuilder.java
@@ -7,6 +7,7 @@ package com.samskivert.depot.impl;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -23,21 +24,19 @@ import com.samskivert.depot.clause.OrderBy;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.expression.AggregateFun;
-import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DatePart.Part;
+import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DateTruncate;
import com.samskivert.depot.impl.expression.LiteralExp;
import com.samskivert.depot.impl.expression.NumericalFun;
import com.samskivert.depot.impl.expression.StringFun.Lower;
import com.samskivert.depot.impl.expression.ValueExp;
+import com.samskivert.depot.impl.jdbc.ColumnDefinition;
import com.samskivert.depot.impl.operator.BitAnd;
import com.samskivert.depot.impl.operator.BitOr;
import com.samskivert.depot.impl.operator.Like;
import com.samskivert.depot.impl.operator.MultiOperator;
import com.samskivert.depot.operator.FullText;
-import com.samskivert.util.ArrayUtil;
-
-import com.samskivert.jdbc.ColumnDefinition;
public class HSQLBuilder
extends SQLBuilder
@@ -92,10 +91,11 @@ public class HSQLBuilder
getFullTextIndex(match.getDefinition().getName()).fields();
// explode the query into words
- String[] ftsWords = match.getDefinition().getQuery().toLowerCase().split("\\W+");
- if (ftsWords.length > 0 && ftsWords[0].length() == 0) {
+ List ftsWords = Arrays.asList(
+ match.getDefinition().getQuery().toLowerCase().split("\\W+"));
+ if (ftsWords.size() > 0 && ftsWords.get(0).length() == 0) {
// if the query led with whitespace, the first 'word' will be empty; strip it
- ftsWords = ArrayUtil.splice(ftsWords, 0, 1);
+ ftsWords = ftsWords.subList(1, ftsWords.size());
}
// now iterate over the cartesian product of the query words & the fields
diff --git a/src/main/java/com/samskivert/depot/impl/IdentityValueGenerator.java b/src/main/java/com/samskivert/depot/impl/IdentityValueGenerator.java
index c912094..2cd564f 100644
--- a/src/main/java/com/samskivert/depot/impl/IdentityValueGenerator.java
+++ b/src/main/java/com/samskivert/depot/impl/IdentityValueGenerator.java
@@ -8,8 +8,8 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
-import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.annotation.GeneratedValue;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
/**
* Generates primary keys using an identity column.
diff --git a/src/main/java/com/samskivert/depot/impl/Modifier.java b/src/main/java/com/samskivert/depot/impl/Modifier.java
index a1bdec8..fb78697 100644
--- a/src/main/java/com/samskivert/depot/impl/Modifier.java
+++ b/src/main/java/com/samskivert/depot/impl/Modifier.java
@@ -15,7 +15,7 @@ import com.samskivert.depot.Key;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Stats;
-import com.samskivert.jdbc.DatabaseLiaison;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
/**
* Encapsulates a modification of persistent objects.
diff --git a/src/main/java/com/samskivert/depot/impl/MySQLBuilder.java b/src/main/java/com/samskivert/depot/impl/MySQLBuilder.java
index 9a0f1e6..07d35c0 100644
--- a/src/main/java/com/samskivert/depot/impl/MySQLBuilder.java
+++ b/src/main/java/com/samskivert/depot/impl/MySQLBuilder.java
@@ -6,15 +6,14 @@ package com.samskivert.depot.impl;
import java.sql.Connection;
import java.sql.SQLException;
-
import java.util.Map;
import java.util.Set;
-import com.samskivert.depot.clause.Distinct;
-import com.samskivert.util.StringUtil;
+import com.google.common.base.Joiner;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex;
+import com.samskivert.depot.clause.Distinct;
import com.samskivert.depot.clause.OrderBy;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.operator.FullText;
@@ -188,10 +187,10 @@ public class MySQLBuilder
String[] searchTerms = fullText.getQuery().toLowerCase().trim().split("\\W+");
if (fullText.isMatchAll()) {
// Prepend every search term with a plus for ANDing
- return '+' + StringUtil.join(searchTerms, " +");
+ return "+" + Joiner.on(" +").join(searchTerms);
} else {
// Use the default OR matching
- return StringUtil.join(searchTerms, " ");
+ return Joiner.on(" ").join(searchTerms);
}
}
diff --git a/src/main/java/com/samskivert/depot/impl/Operation.java b/src/main/java/com/samskivert/depot/impl/Operation.java
index aec0c43..0853c51 100644
--- a/src/main/java/com/samskivert/depot/impl/Operation.java
+++ b/src/main/java/com/samskivert/depot/impl/Operation.java
@@ -10,7 +10,7 @@ import java.sql.Statement;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.Stats;
-import com.samskivert.jdbc.DatabaseLiaison;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
/**
* An abstraction that encompasses both {@link Fetcher} and {@link Modifier} operations.
diff --git a/src/main/java/com/samskivert/depot/impl/PostgreSQL4Builder.java b/src/main/java/com/samskivert/depot/impl/PostgreSQL4Builder.java
index e2501e0..fb1613c 100644
--- a/src/main/java/com/samskivert/depot/impl/PostgreSQL4Builder.java
+++ b/src/main/java/com/samskivert/depot/impl/PostgreSQL4Builder.java
@@ -14,7 +14,7 @@ import java.sql.Timestamp;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.Exps;
import com.samskivert.depot.impl.operator.In;
-import com.samskivert.util.ByteEnum;
+import com.samskivert.depot.util.ByteEnum;
/**
* Specializes our PostgreSQL builder for JDBC4.
diff --git a/src/main/java/com/samskivert/depot/impl/PostgreSQLBuilder.java b/src/main/java/com/samskivert/depot/impl/PostgreSQLBuilder.java
index 5c11132..fae7e39 100644
--- a/src/main/java/com/samskivert/depot/impl/PostgreSQLBuilder.java
+++ b/src/main/java/com/samskivert/depot/impl/PostgreSQLBuilder.java
@@ -5,17 +5,13 @@
package com.samskivert.depot.impl;
import java.lang.reflect.Field;
-
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
-
import java.util.Map;
import java.util.Set;
-import com.samskivert.jdbc.DatabaseLiaison;
-import com.samskivert.jdbc.LiaisonRegistry;
-import com.samskivert.util.StringUtil;
+import com.google.common.base.Joiner;
import com.samskivert.depot.Exps;
import com.samskivert.depot.PersistentRecord;
@@ -30,6 +26,8 @@ import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DateTruncate;
import com.samskivert.depot.impl.expression.IntervalExp;
import com.samskivert.depot.impl.expression.RandomExp;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
+import com.samskivert.depot.impl.jdbc.LiaisonRegistry;
public class PostgreSQLBuilder
extends SQLBuilder
@@ -284,7 +282,7 @@ public class PostgreSQLBuilder
//
String[] searchTerms = fullText.getQuery().toLowerCase().trim().split("\\W+");
String operator = fullText.isMatchAll() ? "&" : "|";
- return StringUtil.join(searchTerms, operator);
+ return Joiner.on(operator).join(searchTerms);
}
// Translate the mildly abstracted full-text parser/dictionary configuration support
diff --git a/src/main/java/com/samskivert/depot/impl/SQLBuilder.java b/src/main/java/com/samskivert/depot/impl/SQLBuilder.java
index 799633e..095d9f0 100644
--- a/src/main/java/com/samskivert/depot/impl/SQLBuilder.java
+++ b/src/main/java/com/samskivert/depot/impl/SQLBuilder.java
@@ -19,8 +19,8 @@ import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.annotation.GeneratedValue;
import com.samskivert.depot.clause.QueryClause;
-import com.samskivert.jdbc.ColumnDefinition;
-import com.samskivert.util.ByteEnum;
+import com.samskivert.depot.impl.jdbc.ColumnDefinition;
+import com.samskivert.depot.util.ByteEnum;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
diff --git a/src/main/java/com/samskivert/depot/impl/TableValueGenerator.java b/src/main/java/com/samskivert/depot/impl/TableValueGenerator.java
index 778f9cf..9c988ff 100644
--- a/src/main/java/com/samskivert/depot/impl/TableValueGenerator.java
+++ b/src/main/java/com/samskivert/depot/impl/TableValueGenerator.java
@@ -13,9 +13,8 @@ import java.util.Arrays;
import com.samskivert.depot.annotation.GeneratedValue;
import com.samskivert.depot.annotation.TableGenerator;
-
-import com.samskivert.jdbc.ColumnDefinition;
-import com.samskivert.jdbc.DatabaseLiaison;
+import com.samskivert.depot.impl.jdbc.ColumnDefinition;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
/**
* Generates primary keys using an external table .
diff --git a/src/main/java/com/samskivert/depot/impl/ValueGenerator.java b/src/main/java/com/samskivert/depot/impl/ValueGenerator.java
index 309fe35..493fadf 100644
--- a/src/main/java/com/samskivert/depot/impl/ValueGenerator.java
+++ b/src/main/java/com/samskivert/depot/impl/ValueGenerator.java
@@ -9,8 +9,8 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.annotation.GeneratedValue;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
import static com.samskivert.depot.Log.log;
diff --git a/src/main/java/com/samskivert/depot/impl/clause/CreateIndexClause.java b/src/main/java/com/samskivert/depot/impl/clause/CreateIndexClause.java
index 8236cd2..eddcaf1 100644
--- a/src/main/java/com/samskivert/depot/impl/clause/CreateIndexClause.java
+++ b/src/main/java/com/samskivert/depot/impl/clause/CreateIndexClause.java
@@ -7,12 +7,11 @@ package com.samskivert.depot.impl.clause;
import java.util.Collection;
import java.util.List;
-import com.samskivert.util.Tuple;
-
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.expression.SQLExpression;
+import com.samskivert.depot.util.Tuple2;
import com.samskivert.depot.impl.FragmentVisitor;
@@ -27,7 +26,7 @@ public class CreateIndexClause
* database.
*/
public CreateIndexClause (Class extends PersistentRecord> pClass, String name, boolean unique,
- List, Order>> fields)
+ List, Order>> fields)
{
_pClass = pClass;
_name = name;
@@ -50,7 +49,7 @@ public class CreateIndexClause
return _unique;
}
- public List,Order>> getFields ()
+ public List,Order>> getFields ()
{
return _fields;
}
@@ -72,5 +71,5 @@ public class CreateIndexClause
protected boolean _unique;
/** The components of the index, e.g. columns or functions of columns. */
- protected List,Order>> _fields;
+ protected List,Order>> _fields;
}
diff --git a/src/main/java/com/samskivert/depot/impl/expression/Function.java b/src/main/java/com/samskivert/depot/impl/expression/Function.java
index 3e731cd..4e95e98 100644
--- a/src/main/java/com/samskivert/depot/impl/expression/Function.java
+++ b/src/main/java/com/samskivert/depot/impl/expression/Function.java
@@ -6,10 +6,11 @@ package com.samskivert.depot.impl.expression;
import java.util.Collection;
+import com.google.common.base.Joiner;
+
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.FluentExp;
import com.samskivert.depot.expression.SQLExpression;
-import com.samskivert.util.StringUtil;
public interface Function
{
@@ -91,7 +92,7 @@ public interface Function
@Override
public String toString ()
{
- return getCanonicalFunctionName() + "(" + StringUtil.join(_args, ", ") + ")";
+ return getCanonicalFunctionName() + "(" + Joiner.on(", ").join(_args) + ")";
}
}
}
diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/BaseLiaison.java b/src/main/java/com/samskivert/depot/impl/jdbc/BaseLiaison.java
new file mode 100644
index 0000000..b4ac6f3
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/impl/jdbc/BaseLiaison.java
@@ -0,0 +1,361 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.impl.jdbc;
+
+import com.google.common.base.Joiner;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+
+import static com.samskivert.depot.Log.log;
+
+/**
+ * A superclass to help with the shrinking subset of SQL our supported dialects can agree on,
+ * or when there is disagreement, implement the most standard-compliant version and let the
+ * dialectal sub-classes override.
+ */
+public abstract class BaseLiaison implements DatabaseLiaison
+{
+ public BaseLiaison () {
+ super();
+ }
+
+ // we override all the interface methods here so that our subclasses can use @Override without
+ // incurring the wrath of the Eclipse Java 1.5 compiler; Jesus Fuck, why do we support 1.5?
+
+ // from DatabaseLiaison
+ public abstract boolean matchesURL (String url);
+
+ // from DatabaseLiaison
+ public abstract boolean isDuplicateRowException (SQLException sqe);
+
+ // from DatabaseLiaison
+ public abstract boolean isTransientException (SQLException sqe);
+
+ @Deprecated
+ public int lastInsertedId (Connection conn, String table, String column) throws SQLException {
+ return lastInsertedId(conn, null, table, column);
+ }
+
+ // from DatabaseLiaison
+ public int lastInsertedId (Connection conn, Statement istmt, String table, String column)
+ throws SQLException
+ {
+ // if this JDBC driver supports getGeneratedKeys, use it!
+ if (istmt != null && conn.getMetaData().supportsGetGeneratedKeys()) {
+ ResultSet rs = istmt.getGeneratedKeys();
+ if (rs.next()) {
+ return rs.getInt(column);
+ }
+ }
+ return fetchLastInsertedId(conn, table, column);
+ }
+
+ /**
+ * Requests the last inserted id for the specified table and column. This is used if a JDBC
+ * driver does not support {@code getGeneratedKeys} or an attempt to use that failed.
+ */
+ protected int fetchLastInsertedId (Connection conn, String table, String column)
+ throws SQLException
+ {
+ throw new SQLException(
+ "Unable to obtain last inserted id [table=" + table + ", column=" + column + "]");
+ }
+
+ // from DatabaseLiaison
+ public boolean tableExists (Connection conn, String name) throws SQLException
+ {
+ ResultSet rs = conn.getMetaData().getTables(null, null, name, null);
+ while (rs.next()) {
+ String tname = rs.getString("TABLE_NAME");
+ if (name.equals(tname)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // from DatabaseLiaison
+ public boolean tableContainsColumn (Connection conn, String table, String column)
+ throws SQLException
+ {
+ ResultSet rs = conn.getMetaData().getColumns(null, null, table, column);
+ while (rs.next()) {
+ String tname = rs.getString("TABLE_NAME");
+ String cname = rs.getString("COLUMN_NAME");
+ if (tname.equals(table) && cname.equals(column)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // from DatabaseLiaison
+ public boolean tableContainsIndex (Connection conn, String table, String index)
+ throws SQLException
+ {
+ ResultSet rs = conn.getMetaData().getIndexInfo(null, null, table, false, true);
+ while (rs.next()) {
+ String tname = rs.getString("TABLE_NAME");
+ String iname = rs.getString("INDEX_NAME");
+ if (tname.equals(table) && index.equals(iname)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // from DatabaseLiaison
+ public boolean addIndexToTable (Connection conn, String table, List columns,
+ String ixName, boolean unique) throws SQLException
+ {
+ if (tableContainsIndex(conn, table, ixName)) {
+ return false;
+ }
+ ixName = (ixName != null ? ixName :
+ Joiner.on("_").join(columns.toArray(new String[columns.size()])));
+
+ StringBuilder update = new StringBuilder("CREATE ");
+ if (unique) {
+ update.append("UNIQUE ");
+ }
+ update.append("INDEX ").append(indexSQL(ixName)).append(" ON ").
+ append(tableSQL(table)).append(" (");
+ appendColumns(columns, update);
+ update.append(")");
+
+ executeQuery(conn, update.toString());
+ log("Database index '" + ixName + "' added to table '" + table + "'");
+ return true;
+ }
+
+ // from DatabaseLiaison
+ public void addPrimaryKey (Connection conn, String table, List columns)
+ throws SQLException
+ {
+ StringBuilder fields = new StringBuilder("(");
+ appendColumns(columns, fields);
+ fields.append(")");
+ String update = "ALTER TABLE " + tableSQL(table) + " ADD PRIMARY KEY " + fields.toString();
+
+ executeQuery(conn, update);
+ log("Primary key " + fields + " added to table '" + table + "'");
+ }
+
+ // from DatabaseLiaison
+ public void dropIndex (Connection conn, String table, String index) throws SQLException
+ {
+ executeQuery(conn, "DROP INDEX " + columnSQL(index));
+ }
+
+ // from DatabaseLiaison
+ public void dropPrimaryKey (Connection conn, String table, String pkName) throws SQLException
+ {
+ executeQuery(conn, "ALTER TABLE " + tableSQL(table) +
+ " DROP CONSTRAINT " + columnSQL(pkName));
+ }
+
+ // from DatabaseLiaison
+ public boolean addColumn (Connection conn, String table, String column, String definition,
+ boolean check) throws SQLException
+ {
+ if (check && tableContainsColumn(conn, table, column)) {
+ return false;
+ }
+
+ executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " ADD COLUMN " +
+ columnSQL(column) + " " + definition);
+ log("Database column '" + column + "' added to table '" + table + "'.");
+ return true;
+ }
+
+ // from DatabaseLiaison
+ public boolean addColumn (Connection conn, String table, String column,
+ ColumnDefinition newColumnDef, boolean check)
+ throws SQLException
+ {
+ if (check && tableContainsColumn(conn, table, column)) {
+ return false;
+ }
+
+ executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " ADD COLUMN " +
+ columnSQL(column) + " " + expandDefinition(newColumnDef));
+ log("Database column '" + column + "' added to table '" + table + "'.");
+ return true;
+ }
+
+ // from DatabaseLiaison
+ public boolean changeColumn (Connection conn, String table, String column, String type,
+ Boolean nullable, Boolean unique, String defaultValue)
+ throws SQLException
+ {
+ String defStr = expandDefinition(type, nullable != null ? nullable : false,
+ unique != null ? unique : false, defaultValue);
+
+ executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " CHANGE " +
+ columnSQL(column) + " " + columnSQL(column) + " " + defStr);
+ log("Database column '" + column + "' of table '" + table + "' modified to have " +
+ "definition '" + defStr + "'.");
+ return true;
+ }
+
+ // from DatabaseLiaison
+ public boolean renameColumn (Connection conn, String table, String from, String to,
+ ColumnDefinition newColumnDef) throws SQLException
+ {
+ executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " RENAME COLUMN " +
+ columnSQL(from) + " TO " + columnSQL(to));
+ log("Renamed column '" + from + "' on table '" + table + "' to '" + to + "'");
+ return true;
+ }
+
+ // from DatabaseLiaison
+ public abstract void createGenerator (Connection conn, String tableName, String columnName,
+ int initialValue)
+ throws SQLException;
+
+ // from DatabaseLiaison
+ public abstract void deleteGenerator (Connection conn, String tableName, String columnName)
+ throws SQLException;
+
+ // from DatabaseLiaison
+ public boolean dropColumn (Connection conn, String table, String column) throws SQLException
+ {
+ if (!tableContainsColumn(conn, table, column)) {
+ return false;
+ }
+ executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " DROP COLUMN " + columnSQL(column));
+ log("Database column '" + column + "' removed from table '" + table + "'.");
+ return true;
+ }
+
+ // from DatabaseLiaison
+ public boolean createTableIfMissing (Connection conn, String table, List columns,
+ List declarations,
+ List primaryKeyColumns) throws SQLException {
+ return createTableIfMissing(conn, table, columns, declarations, null, primaryKeyColumns);
+ }
+
+ // from DatabaseLiaison
+ public boolean createTableIfMissing (Connection conn, String table, List columns,
+ List definitions,
+ List> uniqueConstraintColumns,
+ List primaryKeyColumns)
+ throws SQLException
+ {
+ if (tableExists(conn, table)) {
+ return false;
+ }
+ if (columns.size() != definitions.size()) {
+ throw new IllegalArgumentException("Column name and definition number mismatch");
+ }
+
+ StringBuilder builder = new StringBuilder("CREATE TABLE ").
+ append(tableSQL(table)).append(" (");
+ for (int ii = 0; ii < columns.size(); ii ++) {
+ if (ii > 0) {
+ builder.append(", ");
+ }
+ builder.append(columnSQL(columns.get(ii))).append(" ");
+ builder.append(expandDefinition(definitions.get(ii)));
+ }
+
+ if (uniqueConstraintColumns != null) {
+ for (List uCols : uniqueConstraintColumns) {
+ builder.append(", UNIQUE (");
+ appendColumns(uCols, builder);
+ builder.append(")");
+ }
+ }
+
+ if (primaryKeyColumns != null && !primaryKeyColumns.isEmpty()) {
+ builder.append(", PRIMARY KEY (");
+ appendColumns(primaryKeyColumns, builder);
+ builder.append(")");
+ }
+
+ builder.append(")");
+
+ executeQuery(conn, builder.toString());
+ log("Database table '" + table + "' created.");
+ return true;
+ }
+
+ // from DatabaseLiaison
+ public boolean dropTable (Connection conn, String name) throws SQLException
+ {
+ if (!tableExists(conn, name)) {
+ return false;
+ }
+ executeQuery(conn, "DROP TABLE " + tableSQL(name));
+ log("Table '" + name + "' dropped.");
+ return true;
+ }
+
+ // from DatabaseLiaison
+ public abstract String tableSQL (String table);
+
+ // from DatabaseLiaison
+ public abstract String columnSQL (String column);
+
+ // from DatabaseLiaison
+ public abstract String indexSQL (String index);
+
+ /**
+ * Create an SQL string that summarizes a column definition in that format generally accepted
+ * in table creation and column addition statements, e.g. {@code INTEGER UNIQUE NOT NULL
+ * DEFAULT 100}.
+ */
+ public String expandDefinition (ColumnDefinition def)
+ {
+ return expandDefinition(def.type, def.nullable, def.unique, def.defaultValue);
+ }
+
+ protected int executeQuery (Connection conn, String query) throws SQLException
+ {
+ Statement stmt = conn.createStatement();
+ try {
+ return stmt.executeUpdate(query);
+ } finally {
+ JDBCUtil.close(stmt);
+ }
+ }
+
+ protected String expandDefinition (String type, boolean nullable, boolean unique,
+ String defaultValue)
+ {
+ StringBuilder builder = new StringBuilder(type);
+ if (!nullable) {
+ builder.append(" NOT NULL");
+ }
+ if (unique) {
+ builder.append(" UNIQUE");
+ }
+ // append the default value if one was specified
+ if (defaultValue != null) {
+ builder.append(" DEFAULT ").append(defaultValue);
+ }
+ return builder.toString();
+ }
+
+ /** Escapes {@code columns} with {@link #columnSQL}, appends (comma-sepped) to {@code buf}. */
+ protected void appendColumns (Iterable columns, StringBuilder buf) {
+ int ii = 0;
+ for (String column : columns) {
+ if (ii++ > 0) {
+ buf.append(", ");
+ }
+ buf.append(columnSQL(column));
+ }
+ }
+
+ protected void log (String message) {
+ log.info(message);
+ }
+}
diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/ColumnDefinition.java b/src/main/java/com/samskivert/depot/impl/jdbc/ColumnDefinition.java
new file mode 100644
index 0000000..fb55f5b
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/impl/jdbc/ColumnDefinition.java
@@ -0,0 +1,36 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.impl.jdbc;
+
+/**
+ * An object representing the configuration of a typical SQL column.
+ */
+public class ColumnDefinition
+{
+ public String type;
+ public boolean nullable;
+ public boolean unique;
+ public String defaultValue;
+
+ public ColumnDefinition ()
+ {
+ this(null);
+ }
+
+ public ColumnDefinition (String type)
+ {
+ this(type, false, false, null);
+ }
+
+ public ColumnDefinition (
+ String type, boolean nullable, boolean unique, String defaultValue)
+ {
+ this.type = type;
+ this.nullable = nullable;
+ this.unique = unique;
+ this.defaultValue = defaultValue;
+ }
+}
diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/DatabaseLiaison.java b/src/main/java/com/samskivert/depot/impl/jdbc/DatabaseLiaison.java
new file mode 100644
index 0000000..84f58b6
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/impl/jdbc/DatabaseLiaison.java
@@ -0,0 +1,222 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.impl.jdbc;
+
+import java.sql.Statement;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * Despite good intentions, JDBC and SQL do not provide a unified interface to all databases. There
+ * remain idiosyncrasies that must be worked around when making code interact with different
+ * database servers. The database liaison encapsulates the code needed to straighten out the curves
+ * and curve out the straights.
+ */
+public interface DatabaseLiaison
+{
+ /**
+ * Indicates whether or not this database liaison is the proper liaison for the specified
+ * database URL.
+ *
+ * @return true if we should use this liaison for connections created with the supplied URL,
+ * false if we should not.
+ */
+ public boolean matchesURL (String url);
+
+ /**
+ * Determines whether or not the supplied SQL exception was caused by a duplicate row being
+ * inserted into a table with a unique key.
+ *
+ * @return true if the exception was caused by the insertion of a duplicate row, false if not.
+ */
+ public boolean isDuplicateRowException (SQLException sqe);
+
+ /**
+ * Determines whether or not the supplied SQL exception is a transient failure, meaning one
+ * that is not related to the SQL being executed, but instead to the environment at the time of
+ * execution, like the connection to the database having been lost.
+ *
+ * @return true if the exception was thrown due to a transient failure, false if not.
+ */
+ public boolean isTransientException (SQLException sqe);
+
+ /** @deprecated Use version that takes the insert statement. */
+ @Deprecated
+ public int lastInsertedId (Connection conn, String table, String column) throws SQLException;
+
+ /**
+ * Attempts as dialect-agnostic an interface as possible to the ability of certain databases to
+ * auto-generated numerical values for i.e. key columns; there is MySQL's AUTO_INCREMENT and
+ * PostgreSQL's DEFAULT nextval(sequence), for example.
+ *
+ * @param istmt the insert statement that generated the keys. May be null if the ORM doesn't
+ * have the statement handy.
+ * @return the requested inserted id.
+ * @throws SQLException if we are unable to obtain the last inserted id.
+ */
+ public int lastInsertedId (Connection conn, Statement istmt, String table, String column)
+ throws SQLException;
+
+ /**
+ * Initializes the column value auto-generator described in {@link #lastInsertedId}. This
+ * should be idempotent (meaning the generator may already exist in which case this method
+ * should have no negative effect like resetting it).
+ */
+ public void createGenerator (Connection conn, String tableName, String columnName,
+ int initialValue)
+ throws SQLException;
+
+ /**
+ * Deletes the column value auto-generator described in {@link #lastInsertedId}.
+ */
+ public void deleteGenerator (Connection conn, String tableName, String columnName)
+ throws SQLException;
+
+ /**
+ * Drops the given column from the given table. Returns true or false if the database did or
+ * did not report a schema modification.
+ */
+ public boolean dropColumn (Connection conn, String table, String column)
+ throws SQLException;
+
+ /**
+ * Adds a named index to a table on the given columns. Returns true or false if the database
+ * did or did not report a schema modification.
+ */
+ public boolean addIndexToTable (Connection conn, String table, List columns,
+ String index, boolean unique)
+ throws SQLException;
+
+ /**
+ * Drops the named index from the given table.
+ */
+ public void dropIndex (Connection conn, String table, String index)
+ throws SQLException;
+
+ /**
+ * Adds a primary key to a table of the given name and on the given columns. Returns true or
+ * false if the database did nor did not report a schema modification.
+ */
+ public void addPrimaryKey (Connection conn, String table, List columns)
+ throws SQLException;
+
+ /**
+ * Deletes the primary key from a table, if it exists.
+ */
+ public void dropPrimaryKey (Connection conn, String table, String pkName)
+ throws SQLException;
+
+ /**
+ * Adds a column to a table with the given definition. Tests for the previous existence of
+ * the column iff 'check' is true.
+ */
+ public boolean addColumn (Connection conn, String table, String column, String definition,
+ boolean check)
+ throws SQLException;
+
+ /**
+ * Adds a column to a table with the given definition. Tests for the previous existence of
+ * the column iff 'check' is true.
+ */
+ public boolean addColumn (Connection conn, String table, String column,
+ ColumnDefinition columnDef, boolean check)
+ throws SQLException;
+
+ /**
+ * Alter the definition, but not the name, of a given column on a given table. Returns true or
+ * false if the database did or did not report a schema modification. Any of the nullable,
+ * unique and defaultValue arguments may be null, in which case the implementation will attempt
+ * to leave that aspect of the column unchanged.
+ */
+ public boolean changeColumn (Connection conn, String table, String column, String type,
+ Boolean nullable, Boolean unique, String defaultValue)
+ throws SQLException;
+
+ /**
+ * Alter the name, but not the definition, of a given column on a given table. Returns true or
+ * false if the database did or did not report a schema modification.
+ *
+ * @param columnDef the full definition of the new column, including its new name (MySQL
+ * requires this for a column rename).
+ */
+ public boolean renameColumn (Connection conn, String table, String oldColumn, String newColumn,
+ ColumnDefinition columnDef)
+ throws SQLException;
+
+ /**
+ * Created a new table of the given name with the given column names and column definitions;
+ * the given set of unique constraints (or null) and the given primary key columns (or null).
+ * Returns true if the table was successfully created, false if it already existed.
+ */
+ public boolean createTableIfMissing (Connection conn, String table, List columns,
+ List declarations,
+ List primaryKeyColumns)
+ throws SQLException;
+
+ /**
+ * Created a new table of the given name with the given column names and column definitions;
+ * the given set of unique constraints (or null) and the given primary key columns (or null).
+ * Returns true if the table was successfully created, false if it already existed.
+ */
+ public boolean createTableIfMissing (Connection conn, String table, List columns,
+ List declarations,
+ List> uniqueConstraintColumns,
+ List primaryKeyColumns)
+ throws SQLException;
+
+ /**
+ * Create an SQL string that summarizes a column definition in that format generally accepted
+ * in table creation and column addition statements, e.g. {@code INTEGER UNIQUE NOT NULL
+ * DEFAULT 100}
+ */
+ public String expandDefinition (ColumnDefinition coldef);
+
+ /**
+ * Returns true if the specified table exists and contains an index of the specified name;
+ * false if either conditions does not hold true. Note: the names are case sensitive.
+ */
+ public boolean tableContainsIndex (Connection conn, String table, String index)
+ throws SQLException;
+
+ /**
+ * Returns true if the specified table exists and contains a column with the specified name;
+ * false if either condition does not hold true. Note: the names are case sensitive.
+ */
+ public boolean tableContainsColumn (Connection conn, String table, String column)
+ throws SQLException;
+
+ /**
+ * Returns true if the table with the specified name exists, false if it does not.
+ * Note: the table name is case sensitive.
+ */
+ public boolean tableExists (Connection conn, String name)
+ throws SQLException;
+
+ /**
+ * Drops the given table and returns true if the table exists, else returns false.
+ * Note: the table name is case sensitive.
+ */
+ public boolean dropTable (Connection conn, String name)
+ throws SQLException;
+
+ /**
+ * Returns the proper SQL to identify a table. Some databases require table names to be quoted.
+ */
+ public String tableSQL (String table);
+
+ /**
+ * Returns the proper SQL to identify a column. Some databases require columns names to be
+ * quoted.
+ */
+ public String columnSQL (String column);
+
+ /**
+ * Returns the proper SQL to identify an index. Some databases require index names to be
+ * quoted.
+ */
+ public String indexSQL (String index);
+}
diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/DefaultLiaison.java b/src/main/java/com/samskivert/depot/impl/jdbc/DefaultLiaison.java
new file mode 100644
index 0000000..fe7305f
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/impl/jdbc/DefaultLiaison.java
@@ -0,0 +1,66 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.impl.jdbc;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * The default liaison is used if no other liaison could be matched for a particular database
+ * connection. It isn't very smart or useful but we need something.
+ */
+public class DefaultLiaison extends BaseLiaison
+{
+ @Override // from DatabaseLiaison
+ public boolean matchesURL (String url)
+ {
+ return true;
+ }
+
+ @Override // from DatabaseLiaison
+ public boolean isDuplicateRowException (SQLException sqe)
+ {
+ return false;
+ }
+
+ @Override // from DatabaseLiaison
+ public boolean isTransientException (SQLException sqe)
+ {
+ return false;
+ }
+
+ @Override // from DatabaseLiaison
+ public void createGenerator (Connection conn, String tableName, String columnName, int initValue)
+ throws SQLException
+ {
+ // nothing doing
+ }
+
+ @Override // from DatabaseLiaison
+ public void deleteGenerator (Connection conn, String tableName, String columnName)
+ throws SQLException
+ {
+ // nothing doing
+ }
+
+ @Override // from DatabaseLiaison
+ public String columnSQL (String column)
+ {
+ return column;
+ }
+
+ @Override // from DatabaseLiaison
+ public String tableSQL (String table)
+ {
+ return table;
+ }
+
+ @Override // from DatabaseLiaison
+ public String indexSQL (String index)
+ {
+ return index;
+ }
+}
diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/HsqldbLiaison.java b/src/main/java/com/samskivert/depot/impl/jdbc/HsqldbLiaison.java
new file mode 100644
index 0000000..a35f27c
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/impl/jdbc/HsqldbLiaison.java
@@ -0,0 +1,204 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.impl.jdbc;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.sql.SQLException;
+
+import static com.samskivert.depot.Log.log;
+
+/**
+ * Handles liaison for HSQLDB.
+ */
+public class HsqldbLiaison extends BaseLiaison
+{
+ @Override // from DatabaseLiaison
+ public boolean matchesURL (String url)
+ {
+ return url.startsWith("jdbc:hsqldb");
+ }
+
+ @Override // from DatabaseLiaison
+ public String columnSQL (String column)
+ {
+ return "\"" + column + "\"";
+ }
+
+ @Override // from DatabaseLiaison
+ public String tableSQL (String table)
+ {
+ return "\"" + table + "\"";
+ }
+
+ @Override // from DatabaseLiaison
+ public String indexSQL (String index)
+ {
+ return "\"" + index + "\"";
+ }
+
+ @Override // from DatabaseLiaison
+ public void createGenerator (Connection conn, String tableName,
+ String columnName, int initValue)
+ throws SQLException
+ {
+ if (initValue == 1) {
+ return; // that's the default! yay, do nothing
+ }
+
+ Statement stmt = conn.createStatement();
+ try {
+ stmt.execute("alter table " + tableSQL(tableName) +
+ " alter column " + columnSQL(columnName) +
+ " restart with " + initValue);
+ } finally {
+ JDBCUtil.close(stmt);
+ }
+ log("Initial value of " + tableName + ":" + columnName + " set to " + initValue + ".");
+ }
+
+ @Override // from DatabaseLiaison
+ public void deleteGenerator (Connection conn, String tableName, String columnName)
+ throws SQLException
+ {
+ // HSQL's IDENTITY() does not create any database entities that we need to delete
+ }
+
+ @Override
+ protected int fetchLastInsertedId (Connection conn, String table, String column)
+ throws SQLException
+ {
+ // HSQL does not keep track of per-table-and-column insertion data, so we are pretty much
+ // going on blind faith here that we're fetching the right ID. In the overwhelming number
+ // of cases that will be so, but it's still not pretty.
+ Statement stmt = null;
+ try {
+ stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("call IDENTITY()");
+ return rs.next() ? rs.getInt(1) : super.fetchLastInsertedId(conn, table, column);
+ } finally {
+ JDBCUtil.close(stmt);
+ }
+ }
+
+ @Override // from DatabaseLiaison
+ public boolean isTransientException (SQLException sqe)
+ {
+ return false; // no known transient exceptions for HSQLDB
+ }
+
+ @Override // from DatabaseLiaison
+ public boolean isDuplicateRowException (SQLException sqe)
+ {
+ // Violation of unique constraint SYS_PK_51: duplicate value(s) for column(s) FOO
+ // integrity constraint violation: unique constraint or index violation; SYS_CT_10057
+ String msg = sqe.getMessage();
+ return (msg != null && (msg.contains("duplicate value(s)") ||
+ msg.contains("unique constraint or index violation")));
+ }
+
+ // BaseLiaison's implementation of table creation accepts unique constraints both as
+ // part of the column definition and as a separate argument, and merrily passes this
+ // duality onto the database. Postgres and MySQL both handle this fine but HSQL seems
+ // to simply not allow uniqueness in the column definitions. So, for HSQL, we transfer
+ // uniqueness from the ColumnDefinitions to the uniqueConstraintColumns before we pass
+ // it in to the super implementation.
+ //
+ // TODO: Consider making this the general MO instead of a subclass override. In fact
+ // it may be that uniqueness should be removed from ColumnDefinition.
+ @Override // from DatabaseLiaison
+ public boolean createTableIfMissing (Connection conn, String table,
+ List columns, List defns,
+ List> uniqueColumns, List pkColumns)
+ throws SQLException
+ {
+ if (columns.size() != defns.size()) {
+ throw new IllegalArgumentException("Column name and definition number mismatch");
+ }
+
+ // note the set of single column unique constraints already provided (see method comment)
+ Set seenUniques = new HashSet();
+ if (uniqueColumns != null) {
+ for (List udef : uniqueColumns) {
+ if (udef.size() == 1) {
+ seenUniques.addAll(udef);
+ }
+ }
+ }
+ // primary key columns are also considered implicitly unique as of HSQL 2.2.4, and it will
+ // freak out if we also try to include them in the UNIQUE clause, so add those too
+ seenUniques.addAll(pkColumns);
+
+ // lazily create a copy of uniqueColumns, if needed
+ List> newUniques = uniqueColumns;
+
+ // go through the columns and find any that are unique; these we replace with a non-unique
+ // variant, and instead add a new entry to the table unique constraint
+ List newDefns = new ArrayList(defns.size());
+ for (int ii = 0; ii < defns.size(); ii ++) {
+ ColumnDefinition def = defns.get(ii);
+ if (!def.unique) {
+ newDefns.add(def);
+ continue;
+ }
+
+ // let's be nice and not mutate the caller's object
+ newDefns.add(
+ new ColumnDefinition(def.type, def.nullable, false, def.defaultValue));
+ // if a uniqueness constraint for this column was not in the primaryKeys or
+ // uniqueColumns parameters, add the column to uniqueCsts
+ if (!seenUniques.contains(columns.get(ii))) {
+ if (newUniques == uniqueColumns) {
+ newUniques = new ArrayList>(uniqueColumns);
+ }
+ newUniques.add(Collections.singletonList(columns.get(ii)));
+ }
+ }
+
+ // now call the real implementation with our modified data
+ return super.createTableIfMissing(conn, table, columns, newDefns, newUniques, pkColumns);
+ }
+
+ @Override // from DatabaseLiaison
+ protected String expandDefinition (String type, boolean nullable, boolean unique,
+ String defaultValue)
+ {
+ StringBuilder builder = new StringBuilder(type);
+
+ // append the default value if one was specified
+ if (defaultValue != null) {
+ if ("IDENTITY".equals(defaultValue)) {
+ // this is a blatant hack, we need this method to join Depot's SQLBuilder
+ builder.append(" GENERATED BY DEFAULT AS IDENTITY (START WITH 1)");
+ } else {
+ builder.append(" DEFAULT ").append(defaultValue);
+ }
+ }
+
+ if (!nullable) {
+ builder.append(" NOT NULL");
+ }
+ if (unique) {
+ throw new IllegalArgumentException("HSQL can't deal with column uniqueness here");
+ }
+
+ return builder.toString();
+ }
+
+ @Override
+ protected void log (String message) {
+ // HSQL is generally used as a test database, so we don't generally want to hear a bunch of
+ // spam about table creation, etc. every time we start up/run tests
+ log.debug(message);
+ }
+}
diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/JDBCUtil.java b/src/main/java/com/samskivert/depot/impl/jdbc/JDBCUtil.java
new file mode 100644
index 0000000..9c1edb1
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/impl/jdbc/JDBCUtil.java
@@ -0,0 +1,647 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.impl.jdbc;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import com.google.common.base.Joiner;
+
+import static com.samskivert.depot.Log.log;
+
+/**
+ * A repository for JDBC related utility functions.
+ */
+public class JDBCUtil
+{
+ /** Used for {@link #batchQuery}. */
+ public interface BatchProcessor
+ {
+ /**
+ * Called for each row returned during our batch query. Do not advance the result set,
+ * simply query its values with the get methods.
+ */
+ public void process (ResultSet row)
+ throws SQLException;
+ }
+
+ /**
+ * Closes the supplied JDBC statement and gracefully handles being passed null (by doing
+ * nothing).
+ */
+ public static void close (Statement stmt)
+ throws SQLException
+ {
+ if (stmt != null) {
+ stmt.close();
+ }
+ }
+
+ /**
+ * Closes the supplied JDBC connection and gracefully handles being passed null (by doing
+ * nothing).
+ */
+ public static void close (Connection conn)
+ throws SQLException
+ {
+ if (conn != null) {
+ conn.close();
+ }
+ }
+
+ /**
+ * Wraps the given connection in a proxied instance that will add all statements returned by
+ * methods called on the proxy (such as {@link Connection#createStatement}) to the supplied
+ * list. Thus you can create the proxy, pass the proxy to code that creates and uses statements
+ * and then close any statements created by the code that operated on that Connection before
+ * returning it to a pool, for example.
+ */
+ public static Connection makeCollector (final Connection conn, final List stmts)
+ {
+ return (Connection)Proxy.newProxyInstance(
+ Connection.class.getClassLoader(), PROXY_IFACES, new InvocationHandler() {
+ public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
+ Object result = method.invoke(conn, args);
+ if (result instanceof Statement) {
+ stmts.add((Statement)result);
+ }
+ return result;
+ }
+ });
+ }
+
+ /**
+ * Calls stmt.executeUpdate() on the supplied statement, checking to see that it
+ * returns the expected update count and throwing an exception if it does not.
+ */
+ public static void checkedUpdate (PreparedStatement stmt, int expectedCount)
+ throws SQLException
+ {
+ int modified = stmt.executeUpdate();
+ if (modified != expectedCount) {
+ String err = "Statement did not modify expected number of rows [stmt=" + stmt +
+ ", expected=" + expectedCount + ", modified=" + modified + "]";
+ throw new SQLException(err);
+ }
+ }
+
+ /**
+ * Calls stmt.executeUpdate() on the supplied statement with the supplied query,
+ * checking to see that it returns the expected update count and throwing an exception if it
+ * does not.
+ */
+ public static void checkedUpdate (Statement stmt, String query, int expectedCount)
+ throws SQLException
+ {
+ int modified = stmt.executeUpdate(query);
+ if (modified != expectedCount) {
+ String err = "Statement did not modify expected number of rows [stmt=" + stmt +
+ ", expected=" + expectedCount + ", modified=" + modified + "]";
+ throw new SQLException(err);
+ }
+ }
+
+ /**
+ * Calls stmt.executeUpdate() on the supplied statement, checking to see that it
+ * returns the expected update count and logging a warning if it does not.
+ */
+ public static void warnedUpdate (PreparedStatement stmt, int expectedCount)
+ throws SQLException
+ {
+ int modified = stmt.executeUpdate();
+ if (modified != expectedCount) {
+ log.warning("Statement did not modify expected number of rows", "stmt", stmt,
+ "expected", expectedCount, "modified", modified);
+ }
+ }
+
+ /**
+ * Issues a query with a potentially large number of keys in batches. For example, you might
+ * have 10,000 ids that you wish to use in an "in" clause, but don't trust the database to be
+ * smart about optimizing that many keys, so instead you use batchQuery like so:
+ * {@code
+ * Collection keys = ...;
+ * String query = "select NAME from USERS where USER_ID in (#KEYS)";
+ * JDBCUtil.BatchProcessor proc = new JDBCUtil.BatchProcessor() {
+ * public void process (ResultSet row) {
+ * String name = rs.getString(1);
+ * // do whatever with name
+ * }
+ * };
+ * JDBCUtil.batchQuery(conn, query, keys, false, 500, proc);
+ * }
+ *
+ * @param query the SQL query to run for each batch with the string #KEYS# in the
+ * place where the batch of keys should be substituted.
+ * @param escapeKeys if true, {@link #escape} will be called on each key to escape any
+ * dangerous characters and wrap the key in quotes.
+ * @param batchSize the number of keys at a time to substitute in for #KEYS#.
+ */
+ public static void batchQuery (Connection conn, String query, Collection> keys,
+ boolean escapeKeys, int batchSize, BatchProcessor processor)
+ throws SQLException
+ {
+ Statement stmt = conn.createStatement();
+ try {
+ Iterator> itr = keys.iterator();
+ while (itr.hasNext()) {
+ // group one batch of keys together
+ StringBuilder buf = new StringBuilder();
+ for (int ii = 0; ii < batchSize && itr.hasNext(); ii++) {
+ if (ii > 0) {
+ buf.append(",");
+ }
+ String key = String.valueOf(itr.next());
+ buf.append(escapeKeys ? escape(key) : key);
+ }
+
+ // issue the query with that batch
+ String squery = query.replace("#KEYS#", buf.toString());
+ ResultSet rs = stmt.executeQuery(squery);
+ while (rs.next()) {
+ processor.process(rs);
+ }
+ }
+
+ } finally {
+ close(stmt);
+ }
+ }
+
+ /**
+ * Calls stmt.executeUpdate() on the supplied statement with the supplied query,
+ * checking to see that it returns the expected update count and logging a warning if it does
+ * not.
+ */
+ public static void warnedUpdate (
+ Statement stmt, String query, int expectedCount)
+ throws SQLException
+ {
+ int modified = stmt.executeUpdate(query);
+ if (modified != expectedCount) {
+ log.warning("Statement did not modify expected number of rows", "stmt", stmt,
+ "expected", expectedCount, "modified", modified);
+ }
+ }
+
+ /**
+ * Converts the date to a string and surrounds it in single-quotes via the escape method. If the
+ * date is null, returns null.
+ */
+ public String quote (Date date)
+ {
+ return (date == null) ? null : escape(String.valueOf(date));
+ }
+
+ /**
+ * Escapes any single quotes in the supplied text and wraps it in single quotes to make it safe
+ * for embedding into a database query.
+ */
+ public static String escape (String text)
+ {
+ text = text.replace("\\", "\\\\");
+ return "'" + text.replace("'", "\\'") + "'";
+ }
+
+ /**
+ * Escapes a list of values, separating the escaped values by commas. See {@link
+ * #escape(String)}.
+ */
+ public static String escape (Object[] values)
+ {
+ StringBuilder buf = new StringBuilder();
+ for (int ii = 0; ii < values.length; ii++) {
+ if (ii > 0) {
+ buf.append(", ");
+ }
+ buf.append(escape(String.valueOf(values[ii])));
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Many databases simply fail to handle Unicode text properly and this routine provides a
+ * common workaround which is to represent a UTF-8 string as an ISO-8859-1 string. If you don't
+ * need to use the database's collation routines, this allows you to do pretty much exactly
+ * what you want at the expense of having to jigger and dejigger every goddamned string that
+ * might contain multibyte characters every time you access the database. Three cheers for
+ * progress!
+ */
+ public static String jigger (String text)
+ {
+ if (text == null) {
+ return null;
+ }
+ try {
+ return new String(text.getBytes("UTF8"), "8859_1");
+ } catch (UnsupportedEncodingException uee) {
+ log.warning("Jigger failed", uee);
+ return text;
+ }
+ }
+
+ /**
+ * Reverses {@link #jigger}.
+ */
+ public static String unjigger (String text)
+ {
+ if (text == null) {
+ return null;
+ }
+ try {
+ return new String(text.getBytes("8859_1"), "UTF8");
+ } catch (UnsupportedEncodingException uee) {
+ log.warning("Unjigger failed", uee);
+ return text;
+ }
+ }
+
+ /**
+ * Utility method to jigger the specified string so that it's safe to use in a regular
+ * Statement.
+ */
+ public static String safeJigger (String text)
+ {
+ return jigger(text).replace("'", "\\'");
+ }
+
+ /**
+ * Used to programatically create a database table. Does nothing if the table already exists.
+ *
+ * @return true if the table was created, false if it already existed.
+ */
+ public static boolean createTableIfMissing (
+ Connection conn, String table, String[] definition, String postamble)
+ throws SQLException
+ {
+ if (tableExists(conn, table)) {
+ return false;
+ }
+
+ Statement stmt = conn.createStatement();
+ try {
+ stmt.executeUpdate("create table " + table +
+ "(" + Joiner.on(", ").join(definition) + ") " + postamble);
+ } finally {
+ close(stmt);
+ }
+
+ log.info("Database table '" + table + "' created.");
+ return true;
+ }
+
+ /**
+ * Returns true if the table with the specified name exists, false if it does
+ * not. Note: the table name is case sensitive.
+ */
+ public static boolean tableExists (Connection conn, String name)
+ throws SQLException
+ {
+ boolean matched = false;
+ ResultSet rs = conn.getMetaData().getTables("", "", name, null);
+ while (rs.next()) {
+ String tname = rs.getString("TABLE_NAME");
+ if (name.equals(tname)) {
+ matched = true;
+ }
+ }
+ return matched;
+ }
+
+ /**
+ * Returns true if the table with the specified name exists and contains a column with the
+ * specified name, false if either condition does not hold true. Note: the names are
+ * case sensitive.
+ */
+ public static boolean tableContainsColumn (Connection conn, String table, String column)
+ throws SQLException
+ {
+ boolean matched = false;
+ ResultSet rs = conn.getMetaData().getColumns("", "", table, column);
+ while (rs.next()) {
+ String tname = rs.getString("TABLE_NAME");
+ String cname = rs.getString("COLUMN_NAME");
+ if (tname.equals(table) && cname.equals(column)) {
+ matched = true;
+ }
+ }
+ return matched;
+ }
+
+ /**
+ * Returns true if the index on the specified column exists for the specified table, false if
+ * it does not. Optionally you can specifiy a non null index name, and the table will be
+ * checked to see if it contains that specifically named index. Note: the names are
+ * case sensitive.
+ */
+ public static boolean tableContainsIndex (
+ Connection conn, String table, String column, String index)
+ throws SQLException
+ {
+ boolean matched = false;
+ ResultSet rs = conn.getMetaData().getIndexInfo("", "", table, false, true);
+ while (rs.next()) {
+ String tname = rs.getString("TABLE_NAME");
+ String cname = rs.getString("COLUMN_NAME");
+ String iname = rs.getString("INDEX_NAME");
+ if (index == null) {
+ if (tname.equals(table) && cname.equals(column)) {
+ matched = true;
+ }
+ } else if (index.equals(iname)) {
+ matched = true;
+ }
+ }
+ return matched;
+ }
+
+ /**
+ * Returns true if the specified table contains a primary key on the specified column.
+ */
+ public static boolean tableContainsPrimaryKey (Connection conn, String table, String column)
+ throws SQLException
+ {
+ boolean matched = false;
+ ResultSet rs = conn.getMetaData().getPrimaryKeys("", "", table);
+ while (rs.next()) {
+ String tname = rs.getString("TABLE_NAME");
+ String cname = rs.getString("COLUMN_NAME");
+ if (tname.equals(table) && cname.equals(column)) {
+ matched = true;
+ }
+ }
+ return matched;
+ }
+
+ /**
+ * Returns the name of the index for the specified column in the specified table.
+ */
+ public static String getIndexName (Connection conn, String table,
+ String column)
+ throws SQLException
+ {
+ ResultSet rs = conn.getMetaData().getIndexInfo("", "", table, false, true);
+ while (rs.next()) {
+ String tname = rs.getString("TABLE_NAME");
+ String cname = rs.getString("COLUMN_NAME");
+ String iname = rs.getString("INDEX_NAME");
+ if (tname.equals(table) && cname.equals(column)) {
+ return iname;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the type (as specified in {@link java.sql.Types} for the specified column in the
+ * specified table.
+ */
+ public static int getColumnType (Connection conn, String table,
+ String column)
+ throws SQLException
+ {
+ ResultSet rs = getColumnMetaData(conn, table, column);
+ try {
+ return rs.getInt("DATA_TYPE");
+ } finally {
+ rs.close();
+ }
+ }
+
+ /**
+ * Determines whether or not the specified column accepts null values.
+ *
+ * @return true if the column accepts null values, false if it does not (or its nullability is
+ * unknown)
+ */
+ public static boolean isColumnNullable (Connection conn, String table,
+ String column)
+ throws SQLException
+ {
+ ResultSet rs = getColumnMetaData(conn, table, column);
+ try {
+ return rs.getString("IS_NULLABLE").equals("YES");
+ } finally {
+ rs.close();
+ }
+ }
+
+ /**
+ * Returns the size for the specified column in the specified table. For char or date types
+ * this is the maximum number of characters, for numeric or decimal types this is the
+ * precision.
+ */
+ public static int getColumnSize (Connection conn, String table, String column)
+ throws SQLException
+ {
+ ResultSet rs = getColumnMetaData(conn, table, column);
+ try {
+ return rs.getInt("COLUMN_SIZE");
+ } finally {
+ rs.close();
+ }
+ }
+
+ /**
+ * Returns a string representation of the default value for the specified column in the
+ * specified table. This may be null.
+ */
+ public static String getColumnDefaultValue (Connection conn, String table, String column)
+ throws SQLException
+ {
+ ResultSet rs = getColumnMetaData(conn, table, column);
+ try {
+ return rs.getString("COLUMN_DEF");
+ } finally {
+ rs.close();
+ }
+ }
+
+ /**
+ * Adds a column (with name 'cname' and definition 'cdef') to the specified table.
+ *
+ * @param afterCname (optional) the name of the column after which to add the new column.
+ *
+ * @return true if the column was added, false if it already existed.
+ */
+ public static boolean addColumn (
+ Connection conn, String table, String cname, String cdef, String afterCname)
+ throws SQLException
+ {
+ if (tableContainsColumn(conn, table, cname)) {
+// Log.info("Database table '" + table + "' already has column '" + cname + "'.");
+ return false;
+ }
+
+ String update = "ALTER TABLE " + table + " ADD COLUMN " + cname + " " + cdef;
+ if (afterCname != null) {
+ update += " AFTER " + afterCname;
+ }
+ PreparedStatement stmt = null;
+ try {
+ stmt = conn.prepareStatement(update);
+ stmt.executeUpdate();
+ } finally {
+ close(stmt);
+ }
+ log.info("Database column '" + cname + "' added to table '" + table + "'.");
+ return true;
+ }
+
+ /**
+ * Changes a column's definition. Takes a full column definition 'cdef' (including the name of
+ * the column) with which to replace the specified column 'cname'.
+ *
+ * NOTE: A handy thing you can do with this is to rename a column by providing a column
+ * definition that has a different name, but the same column type.
+ */
+ public static void changeColumn (Connection conn, String table, String cname, String cdef)
+ throws SQLException
+ {
+ String update = "ALTER TABLE " + table + " CHANGE " + cname + " " + cdef;
+ PreparedStatement stmt = null;
+ try {
+ stmt = conn.prepareStatement(update);
+ stmt.executeUpdate();
+ } finally {
+ close(stmt);
+ }
+ log.info("Database column '" + cname + "' of table '" + table +
+ "' modified to have this def '" + cdef + "'.");
+ }
+
+ /**
+ * Removes a column from the specified table.
+ *
+ * @return true if the column was dropped, false if it did not exist in the first place.
+ */
+ public static boolean dropColumn (Connection conn, String table, String cname)
+ throws SQLException
+ {
+ if (!tableContainsColumn(conn, table, cname)) {
+ return false;
+ }
+
+ String update = "ALTER TABLE " + table + " DROP COLUMN " + cname;
+ PreparedStatement stmt = null;
+ try {
+ stmt = conn.prepareStatement(update);
+ if (stmt.executeUpdate() == 1) {
+ log.info("Database index '" + cname + "' removed from table '" + table + "'.");
+ }
+ } finally {
+ close(stmt);
+ }
+ return true;
+ }
+
+ /**
+ * Removes a named index from the specified table.
+ *
+ * @return true if the index was dropped, false if it did not exist in the first place.
+ */
+ public static boolean dropIndex (Connection conn, String table, String cname, String iname)
+ throws SQLException
+ {
+ if (!tableContainsIndex(conn, table, cname, iname)) {
+ return false;
+ }
+
+ String update = "ALTER TABLE " + table + " DROP INDEX " + iname;
+ PreparedStatement stmt = null;
+ try {
+ stmt = conn.prepareStatement(update);
+ if (stmt.executeUpdate() == 1) {
+ log.info("Database index '" + iname + "' removed from table '" + table + "'.");
+ }
+ } finally {
+ close(stmt);
+ }
+ return true;
+ }
+
+ /**
+ * Removes the primary key from the specified table.
+ */
+ public static void dropPrimaryKey (Connection conn, String table)
+ throws SQLException
+ {
+ String update = "ALTER TABLE " + table + " DROP PRIMARY KEY";
+ PreparedStatement stmt = null;
+ try {
+ stmt = conn.prepareStatement(update);
+ if (stmt.executeUpdate() == 1) {
+ log.info("Database primary key removed from '" + table + "'.");
+ }
+ } finally {
+ close(stmt);
+ }
+ }
+
+ /**
+ * Adds an index on the specified column (cname) to the specified table. Optionally supply an
+ * index name, otherwise the index is named after the column.
+ *
+ * @return true if the index was added, false if it already existed.
+ */
+ public static boolean addIndexToTable (
+ Connection conn, String table, String cname, String iname)
+ throws SQLException
+ {
+ if (tableContainsIndex(conn, table, cname, iname)) {
+// Log.info("Database table '" + table + "' already has an index " +
+// "on column '" + cname + "'" +
+// (iname != null ? " named '" + iname + "'." : "."));
+ return false;
+ }
+
+ String idx_name = (iname != null ? iname : cname);
+ String update = "CREATE INDEX " + idx_name + " on " + table + "(" + cname + ")";
+ PreparedStatement stmt = null;
+ try {
+ stmt = conn.prepareStatement(update);
+ stmt.executeUpdate();
+ } finally {
+ close(stmt);
+ }
+ log.info("Database index '" + idx_name + "' added to table '" + table + "'");
+ return true;
+ }
+
+ /**
+ * Helper function for {@link #getColumnType}, etc.
+ */
+ protected static ResultSet getColumnMetaData (Connection conn, String table, String column)
+ throws SQLException
+ {
+ ResultSet rs = conn.getMetaData().getColumns("", "", table, column);
+ while (rs.next()) {
+ String tname = rs.getString("TABLE_NAME");
+ String cname = rs.getString("COLUMN_NAME");
+ if (tname.equals(table) && cname.equals(column)) {
+ return rs;
+ }
+ }
+ throw new SQLException("Table or Column not defined. [table=" + table +
+ ", col=" + column + "].");
+ }
+
+ /** Used by {@link #makeCollector}. */
+ protected static final Class>[] PROXY_IFACES = { Connection.class };
+}
diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/LiaisonRegistry.java b/src/main/java/com/samskivert/depot/impl/jdbc/LiaisonRegistry.java
new file mode 100644
index 0000000..bd13ecc
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/impl/jdbc/LiaisonRegistry.java
@@ -0,0 +1,81 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.impl.jdbc;
+
+import java.sql.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.samskivert.depot.Log.log;
+
+/**
+ * The liaison registry provides access to the appropriate database liaison implementation for a
+ * particular database connection.
+ */
+public class LiaisonRegistry
+{
+ /**
+ * Fetch the appropriate database liaison for the supplied URL, which should be the same string
+ * that would be used to configure a connection to the database.
+ */
+ public static DatabaseLiaison getLiaison (String url)
+ {
+ if (url == null) throw new NullPointerException("URL must not be null");
+ // see if we already have a liaison mapped for this connection
+ DatabaseLiaison liaison = _mappings.get(url);
+ if (liaison == null) {
+ // scan the list looking for a matching liaison
+ for (DatabaseLiaison candidate : _liaisons) {
+ if (candidate.matchesURL(url)) {
+ liaison = candidate;
+ break;
+ }
+ }
+
+ // if we didn't find a matching liaison, use the default
+ if (liaison == null) {
+ log.warning("Unable to match liaison for database. Using default.", "url", url);
+ liaison = new DefaultLiaison();
+ }
+
+ // map this URL to this liaison
+ _mappings.put(url, liaison);
+ }
+
+ return liaison;
+ }
+
+ /**
+ * Fetch the appropriate database liaison for the supplied database connection.
+ */
+ public static DatabaseLiaison getLiaison (Connection conn)
+ throws SQLException
+ {
+ return getLiaison(conn.getMetaData().getURL());
+ }
+
+ protected static void registerLiaisonClass (Class extends DatabaseLiaison> lclass)
+ {
+ // create a new instance and stick it on our list
+ try {
+ _liaisons.add(lclass.newInstance());
+ } catch (Exception e) {
+ log.warning("Unable to instantiate liaison", "class", lclass.getName(), "error", e);
+ }
+ }
+
+ protected static ArrayList _liaisons = new ArrayList();
+ protected static Map _mappings = new HashMap();
+
+ // register our liaison classes
+ static {
+ registerLiaisonClass(MySQLLiaison.class);
+ registerLiaisonClass(PostgreSQLLiaison.class);
+ registerLiaisonClass(HsqldbLiaison.class);
+ }
+}
diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/MySQLLiaison.java b/src/main/java/com/samskivert/depot/impl/jdbc/MySQLLiaison.java
new file mode 100644
index 0000000..1950c49
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/impl/jdbc/MySQLLiaison.java
@@ -0,0 +1,145 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.impl.jdbc;
+
+import java.sql.*;
+import java.util.List;
+
+/**
+ * A database liaison for the MySQL database.
+ */
+public class MySQLLiaison extends BaseLiaison
+{
+ @Override // from DatabaseLiaison
+ public boolean matchesURL (String url)
+ {
+ return url.startsWith("jdbc:mysql");
+ }
+
+ @Override // from DatabaseLiaison
+ public boolean isDuplicateRowException (SQLException sqe)
+ {
+ String msg = sqe.getMessage();
+ return (msg != null && msg.indexOf("Duplicate entry") != -1);
+ }
+
+ @Override // from DatabaseLiaison
+ public boolean isTransientException (SQLException sqe)
+ {
+ String msg = sqe.getMessage();
+ return (msg != null && (msg.indexOf("Lost connection") != -1 ||
+ msg.indexOf("link failure") != -1 ||
+ msg.indexOf("Broken pipe") != -1));
+ }
+
+ @Override // from DatabaseLiaison
+ public void createGenerator (Connection conn, String tableName, String columnName, int initValue)
+ throws SQLException
+ {
+ // TODO: is there any way we can set the initial AUTO_INCREMENT value?
+ }
+
+ @Override // from DatabaseLiaison
+ public void deleteGenerator (Connection conn, String tableName, String columnName)
+ throws SQLException
+ {
+ // AUTO_INCREMENT does not create any database entities that we need to delete
+ }
+
+ @Override // from BaseLiaison
+ public int lastInsertedId (Connection conn, Statement istmt, String table, String column)
+ throws SQLException
+ {
+ // MySQL uses "GENERATED_KEY" as the column name for the last inserted key, so we have to
+ // hackily pass that to our super method to get things to work
+ return super.lastInsertedId(conn, istmt, table, "GENERATED_KEY");
+ }
+
+ @Override
+ protected int fetchLastInsertedId (Connection conn, String table, String column)
+ throws SQLException
+ {
+ // MySQL does not keep track of per-table-and-column insertion data, so we are pretty much
+ // going on blind faith here that we're fetching the right ID. In the overwhelming number
+ // of cases that will be so, but it's still not pretty.
+ Statement stmt = null;
+ try {
+ stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()");
+ return rs.next() ? rs.getInt(1) : super.fetchLastInsertedId(conn, table, column);
+ } finally {
+ JDBCUtil.close(stmt);
+ }
+ }
+
+ @Override // from BaseLiaison
+ public boolean addIndexToTable (Connection conn, String table, List columns,
+ String ixName, boolean unique) throws SQLException
+ {
+ if (tableContainsIndex(conn, table, ixName)) {
+ return false;
+ }
+
+ // MySQL's "CREATE INDEX" is buggy, it actually changes the case of the table names you're
+ // working with. Luckily (?) ALTER TABLE ADD INDEX works, so we do that here.
+ StringBuilder update = new StringBuilder("ALTER TABLE ").append(table).append(" ADD ");
+ if (unique) {
+ update.append("UNIQUE ");
+ }
+ update.append("INDEX ").append(indexSQL(ixName)).append(" (");
+ appendColumns(columns, update);
+ update.append(")");
+
+ executeQuery(conn, update.toString());
+ log("Database index '" + ixName + "' added to table '" + table + "'");
+ return true;
+ }
+
+ @Override // from BaseLiaison
+ public void dropIndex (Connection conn, String table, String index) throws SQLException
+ {
+ executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " DROP INDEX " + columnSQL(index));
+ }
+
+ @Override // from BaseLiaison
+ public void dropPrimaryKey (Connection conn, String table, String pkName) throws SQLException
+ {
+ executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " DROP PRIMARY KEY");
+ }
+
+ @Override // from BaseLiaison
+ public boolean renameColumn (Connection conn, String table, String oldColumnName,
+ String newColumnName, ColumnDefinition newColumnDef)
+ throws SQLException
+ {
+ if (!tableContainsColumn(conn, table, oldColumnName)) {
+ return false;
+ }
+ executeQuery(conn, "ALTER TABLE " + table + " CHANGE " + oldColumnName + " " +
+ newColumnName + " " + expandDefinition(newColumnDef));
+ log("Renamed column '" + oldColumnName + "' on table '" + table + "' to '" +
+ newColumnName + "'");
+ return true;
+ }
+
+ @Override // from DatabaseLiaison
+ public String columnSQL (String column)
+ {
+ return "`" + column + "`";
+ }
+
+ @Override // from DatabaseLiaison
+ public String tableSQL (String table)
+ {
+ return "`" + table + "`";
+ }
+
+ @Override // from DatabaseLiaison
+ public String indexSQL (String index)
+ {
+ return "`" + index + "`";
+ }
+}
diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/PostgreSQLLiaison.java b/src/main/java/com/samskivert/depot/impl/jdbc/PostgreSQLLiaison.java
new file mode 100644
index 0000000..a1eda57
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/impl/jdbc/PostgreSQLLiaison.java
@@ -0,0 +1,139 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.impl.jdbc;
+
+import java.sql.*;
+
+/**
+ * A database liaison for the MySQL database.
+ */
+public class PostgreSQLLiaison extends BaseLiaison
+{
+ // from DatabaseLiaison
+ public boolean matchesURL (String url)
+ {
+ return url.startsWith("jdbc:postgresql");
+ }
+
+ // from DatabaseLiaison
+ public boolean isDuplicateRowException (SQLException sqe)
+ {
+ String msg = sqe.getMessage();
+ return (msg != null && msg.indexOf("duplicate key") != -1);
+ }
+
+ // from DatabaseLiaison
+ public boolean isTransientException (SQLException sqe)
+ {
+ // TODO: Add more error messages here as we encounter them.
+ String msg = sqe.getMessage();
+ return (msg != null &&
+ msg.indexOf("An I/O error occured while sending to the backend") != -1);
+ }
+
+ @Override
+ protected int fetchLastInsertedId (Connection conn, String table, String column)
+ throws SQLException
+ {
+ // PostgreSQL's support for auto-generated ID's comes in the form of appropriately named
+ // sequences and DEFAULT nextval(sequence) modifiers in the ID columns. To get the next ID,
+ // we use the currval() method which is set in a database sessions when any given sequence
+ // is incremented.
+ Statement stmt = conn.createStatement();
+ try {
+ ResultSet rs = stmt.executeQuery(
+ "select currval('\"" + table + "_" + column + "_seq\"')");
+ return rs.next() ? rs.getInt(1) : super.fetchLastInsertedId(conn, table, column);
+ } finally {
+ JDBCUtil.close(stmt);
+ }
+ }
+
+ // from DatabaseLiaison
+ public void createGenerator (Connection conn, String tableName, String columnName, int initValue)
+ throws SQLException
+ {
+ if (initValue == 1) {
+ return; // that's the default! yay, do nothing
+ }
+
+ String seqname = "\"" + tableName + "_" + columnName + "_seq\"";
+ Statement stmt = conn.createStatement();
+ try {
+ stmt.executeQuery("select setval('" + seqname + "', " + initValue + ", false)");
+ } finally {
+ JDBCUtil.close(stmt);
+ }
+ log("Initial value of " + seqname + " set to " + initValue + ".");
+ }
+
+ // from DatabaseLiaison
+ public void deleteGenerator (Connection conn, String table, String column)
+ throws SQLException
+ {
+ executeQuery(conn, "drop sequence if exists \"" + table + "_" + column + "_seq\"");
+ }
+
+ @Override // from DatabaseLiaison
+ public boolean changeColumn (Connection conn, String table, String column, String type,
+ Boolean nullable, Boolean unique, String defaultValue)
+ throws SQLException
+ {
+ StringBuilder lbuf = new StringBuilder();
+ if (type != null) {
+ executeQuery(
+ conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + columnSQL(column) +
+ " TYPE " + type);
+ lbuf.append("type=").append(type);
+ }
+ if (nullable != null) {
+ executeQuery(
+ conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + columnSQL(column) +
+ " " + (nullable ? "DROP NOT NULL" : "SET NOT NULL"));
+ if (lbuf.length() > 0) {
+ lbuf.append(", ");
+ }
+ lbuf.append("nullable=").append(nullable);
+ }
+ if (unique != null) {
+ // TODO: I think this requires ALTER TABLE DROP CONSTRAINT and so on
+ if (lbuf.length() > 0) {
+ lbuf.append(", ");
+ }
+ lbuf.append("unique=").append(unique).append(" (not implemented yet)");
+ }
+ if (defaultValue != null) {
+ executeQuery(
+ conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + columnSQL(column) +
+ " " + (defaultValue.length() > 0 ? "SET DEFAULT " + defaultValue : "DROP DEFAULT"));
+ if (lbuf.length() > 0) {
+ lbuf.append(", ");
+ }
+ lbuf.append("defaultValue=").append(defaultValue);
+ }
+ log("Database column '" + column + "' of table '" + table + "' modified to have " +
+ "definition [" + lbuf + "].");
+ return true;
+ }
+
+ // from DatabaseLiaison
+ public String columnSQL (String column)
+ {
+ return "\"" + column + "\"";
+ }
+
+ // from DatabaseLiaison
+ public String tableSQL (String table)
+ {
+ return "\"" + table + "\"";
+ }
+
+ // from DatabaseLiaison
+ public String indexSQL (String index)
+ {
+ return "\"" + index + "\"";
+ }
+}
diff --git a/src/main/java/com/samskivert/depot/impl/operator/Div.java b/src/main/java/com/samskivert/depot/impl/operator/Div.java
index 31def86..dced3b6 100644
--- a/src/main/java/com/samskivert/depot/impl/operator/Div.java
+++ b/src/main/java/com/samskivert/depot/impl/operator/Div.java
@@ -4,7 +4,7 @@
package com.samskivert.depot.impl.operator;
-import com.samskivert.util.StringUtil;
+import java.util.Arrays;
import com.samskivert.depot.expression.SQLExpression;
@@ -34,7 +34,7 @@ public class Div extends Arithmetic
{
for (int ii = 1; ii < operands.length; ii ++) {
if (Double.valueOf(0).equals(NUMERICAL.apply(operands[ii]))) {
- return new NoValue("Division by zero in: " + StringUtil.toString(operands));
+ return new NoValue("Division by zero in: " + Arrays.toString(operands));
}
}
return evaluate(operands, "/", new Accumulator() {
diff --git a/src/main/java/com/samskivert/depot/operator/Case.java b/src/main/java/com/samskivert/depot/operator/Case.java
index 1e96943..27ad26e 100644
--- a/src/main/java/com/samskivert/depot/operator/Case.java
+++ b/src/main/java/com/samskivert/depot/operator/Case.java
@@ -12,7 +12,7 @@ import com.google.common.collect.Lists;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
-import com.samskivert.util.Tuple;
+import com.samskivert.depot.util.Tuple2;
/**
* The SQL 'case' operator.
@@ -24,12 +24,12 @@ public class Case
{
int i = 0;
for (; i + 1 < exps.length; i += 2) {
- _whenExps.add(Tuple., SQLExpression>>newTuple(exps[i], exps[i + 1]));
+ _whenExps.add(Tuple2., SQLExpression>>create(exps[i], exps[i + 1]));
}
_elseExp = (i < exps.length) ? exps[i] : null;
}
- public List, SQLExpression>>> getWhenExps ()
+ public List, SQLExpression>>> getWhenExps ()
{
return _whenExps;
}
@@ -48,9 +48,9 @@ public class Case
// from SQLExpression
public void addClasses (Collection> classSet)
{
- for (Tuple, SQLExpression>> tuple : _whenExps) {
- tuple.left.addClasses(classSet);
- tuple.right.addClasses(classSet);
+ for (Tuple2, SQLExpression>> tuple : _whenExps) {
+ tuple.a.addClasses(classSet);
+ tuple.b.addClasses(classSet);
}
if (_elseExp != null) {
_elseExp.addClasses(classSet);
@@ -62,9 +62,9 @@ public class Case
{
StringBuilder builder = new StringBuilder();
builder.append("Case(");
- for (Tuple, SQLExpression>> tuple : _whenExps) {
- builder.append(tuple.left.toString()).append("->");
- builder.append(tuple.right.toString()).append(",");
+ for (Tuple2, SQLExpression>> tuple : _whenExps) {
+ builder.append(tuple.a.toString()).append("->");
+ builder.append(tuple.b.toString()).append(",");
}
if (_elseExp != null) {
builder.append(_elseExp.toString()).append(")");
@@ -72,6 +72,6 @@ public class Case
return builder.toString();
}
- protected List, SQLExpression>>> _whenExps = Lists.newArrayList();
+ protected List, SQLExpression>>> _whenExps = Lists.newArrayList();
protected SQLExpression> _elseExp;
}
diff --git a/src/main/java/com/samskivert/depot/tools/GenRecord.java b/src/main/java/com/samskivert/depot/tools/GenRecord.java
index aaa52cb..df1e225 100644
--- a/src/main/java/com/samskivert/depot/tools/GenRecord.java
+++ b/src/main/java/com/samskivert/depot/tools/GenRecord.java
@@ -4,31 +4,25 @@
package com.samskivert.depot.tools;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
+import java.io.*;
import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
-import java.lang.reflect.GenericArrayType;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
import java.nio.charset.Charset;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.lang.reflect.*;
+
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import com.google.common.io.CharStreams;
import com.google.common.io.Files;
import com.samskivert.depot.PersistentRecord;
@@ -36,10 +30,6 @@ import com.samskivert.depot.annotation.GeneratedValue;
import com.samskivert.depot.annotation.Id;
import com.samskivert.depot.annotation.Transient;
import com.samskivert.depot.impl.DepotUtil;
-import com.samskivert.io.StreamUtil;
-import com.samskivert.util.ClassUtil;
-import com.samskivert.util.GenUtil;
-import com.samskivert.util.StringUtil;
public abstract class GenRecord
{
@@ -95,9 +85,9 @@ public abstract class GenRecord
List kflist = Lists.newArrayList();
if (!Modifier.isAbstract(rclass.getModifiers())) {
// determine which fields make up our primary key; we'd just use Class.getFields() but
- // that returns things in a random order whereas ClassUtil returns fields in
- // declaration order starting from the top-most class and going down the line
- for (Field field : ClassUtil.getFields(rclass)) {
+ // that returns things in a random order whereas ours returns fields in declaration
+ // order starting from the top-most class and going down the line
+ for (Field field : getFields(rclass)) {
if (hasAnnotation(field, Id.class)) kflist.add(field);
else if (hasAnnotation(field, GeneratedValue.class)) {
logWarn("Skipping " + rclass.getName() + ". Field '" + field.getName() +
@@ -221,7 +211,7 @@ public abstract class GenRecord
Map fsubs = Maps.newHashMap(subs);
fsubs.put("type", getTypeName(f.getGenericType()));
fsubs.put("field", fname);
- fsubs.put("capfield", StringUtil.unStudlyName(fname).toUpperCase());
+ fsubs.put("capfield", unStudlyName(fname).toUpperCase());
// now generate our bits
fsection.append(mergeTemplate(COL_TMPL, fsubs));
@@ -242,9 +232,9 @@ public abstract class GenRecord
fieldNameList.append(", ");
}
String name = keyField.getName();
- argList.append(GenUtil.simpleName(keyField)).append(" ").append(name);
+ argList.append(simpleName(keyField.getGenericType())).append(" ").append(name);
argNameList.append(name);
- fieldNameList.append(StringUtil.unStudlyName(name));
+ fieldNameList.append(unStudlyName(name));
}
subs.put("argList", argList.toString());
@@ -264,31 +254,23 @@ public abstract class GenRecord
if (fsection.length() > 0) {
String prev = get(lines, nstart-1);
- if (!StringUtil.isBlank(prev) && !prev.equals("{")) {
- pout.println();
- }
+ if (!isBlank(prev) && !prev.equals("{")) pout.println();
pout.println(" " + FIELDS_START);
pout.write(fsection.toString());
pout.println(" " + FIELDS_END);
- if (!StringUtil.isBlank(get(lines, nend))) {
- pout.println();
- }
+ if (!isBlank(get(lines, nend))) pout.println();
}
for (int ii = nend; ii < mstart; ii++) {
pout.println(lines[ii]);
}
if (msection.length() > 0) {
- if (!StringUtil.isBlank(get(lines, mstart-1))) {
- pout.println();
- }
+ if (!isBlank(get(lines, mstart-1))) pout.println();
pout.println(" " + METHODS_START);
pout.write(msection.toString());
pout.println(" " + METHODS_END);
String next = get(lines, mend);
- if (!StringUtil.isBlank(next) && !next.equals("}")) {
- pout.println();
- }
+ if (!isBlank(next) && !next.equals("}")) pout.println();
}
for (int ii = mend; ii < lines.length; ii++) {
pout.println(lines[ii]);
@@ -340,8 +322,8 @@ public abstract class GenRecord
protected String mergeTemplate (String tmpl, Map subs)
{
try {
- String text = StreamUtil.toString(
- getClass().getClassLoader().getResourceAsStream(tmpl), "UTF-8");
+ InputStream in = getClass().getClassLoader().getResourceAsStream(tmpl);
+ String text = CharStreams.toString(new InputStreamReader(in, "UTF-8"));
text = text.replace("\n", System.getProperty("line.separator"));
for (Map.Entry entry : subs.entrySet()) {
text = text.replaceAll("@"+entry.getKey()+"@", entry.getValue());
@@ -434,6 +416,121 @@ public abstract class GenRecord
}
}
+ protected static Field[] getFields (Class> clazz)
+ {
+ List list = Lists.newArrayList();
+ getFields(clazz, list);
+ return list.toArray(new Field[list.size()]);
+ }
+
+ protected static void getFields (Class> clazz, List addTo)
+ {
+ // first get the fields of the superclass
+ Class> pclazz = clazz.getSuperclass();
+ if (pclazz != null && !pclazz.equals(Object.class)) getFields(pclazz, addTo);
+
+ // then reflect on this class's declared fields
+ Field[] fields = clazz.getDeclaredFields();
+
+ // override the default accessibility check for the fields
+ try {
+ AccessibleObject.setAccessible(fields, true);
+ } catch (SecurityException se) {
+ // ah well, only publics for us
+ }
+
+ for (Field field : fields) {
+ int mods = field.getModifiers();
+ // skip static and transient fields
+ if (Modifier.isStatic(mods) || Modifier.isTransient(mods)) continue;
+ addTo.add(field);
+ }
+ }
+
+ protected static String simpleName (Type type)
+ {
+ if (type instanceof GenericArrayType) {
+ return simpleName(((GenericArrayType)type).getGenericComponentType()) + "[]";
+ } else if (type instanceof Class>) {
+ Class> clazz = (Class>)type;
+ if (clazz.isArray()) {
+ return simpleName(clazz.getComponentType()) + "[]";
+ } else {
+ Package pkg = clazz.getPackage();
+ int offset = (pkg == null) ? 0 : pkg.getName().length()+1;
+ return clazz.getName().substring(offset).replace('$', '.');
+ }
+
+ } else if (type instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType)type;
+ StringBuilder buf = new StringBuilder();
+ for (Type arg : pt.getActualTypeArguments()) {
+ if (buf.length() > 0) {
+ buf.append(", ");
+ }
+ buf.append(simpleName(arg));
+ }
+ return simpleName(pt.getRawType()) + "<" + buf + ">";
+
+ } else if (type instanceof WildcardType) {
+ WildcardType wt = (WildcardType)type;
+ if (wt.getLowerBounds().length > 0) {
+ String errmsg = "Generation of simple name for wildcard type with lower bounds " +
+ "not implemented [type=" + type +
+ ", lbounds=" + Arrays.toString(wt.getLowerBounds()) + "]";
+ throw new IllegalArgumentException(errmsg);
+ }
+ if (wt.getUpperBounds().length > 1) {
+ String errmsg = "Generation of simple name for wildcard type with multiple upper " +
+ "bounds not implemented [type=" + type +
+ ", ubounds=" + Arrays.toString(wt.getUpperBounds()) + "]";
+ throw new IllegalArgumentException(errmsg);
+ }
+ StringBuilder buf = new StringBuilder("?");
+ if (!Object.class.equals(wt.getUpperBounds()[0])) {
+ buf.append(" extends ").append(simpleName(wt.getUpperBounds()[0]));
+ }
+ return buf.toString();
+
+ } else if (type instanceof TypeVariable) {
+ return ((TypeVariable>)type).getName();
+
+ } else {
+ throw new IllegalArgumentException("Can't generate simple name [type=" + type + "]");
+ }
+ }
+
+ protected static boolean isBlank (String value)
+ {
+ for (int ii = 0, ll = (value == null) ? 0 : value.length(); ii < ll; ii++) {
+ if (!Character.isWhitespace(value.charAt(ii))) return false;
+ }
+ return true;
+ }
+
+ protected static String unStudlyName (String name)
+ {
+ boolean seenLower = false;
+ StringBuilder nname = new StringBuilder();
+ int nlen = name.length();
+ for (int i = 0; i < nlen; i++) {
+ char c = name.charAt(i);
+ // if we see an upper case character and we've seen a lower case character since the
+ // last time we did so, slip in an _
+ if (Character.isUpperCase(c)) {
+ if (seenLower) {
+ nname.append("_");
+ }
+ seenLower = false;
+ nname.append(c);
+ } else {
+ seenLower = true;
+ nname.append(Character.toUpperCase(c));
+ }
+ }
+ return nname.toString();
+ }
+
/** Used to do our own classpath business. */
protected final ClassLoader _cloader;
diff --git a/src/main/java/com/samskivert/depot/util/ByteEnum.java b/src/main/java/com/samskivert/depot/util/ByteEnum.java
new file mode 100644
index 0000000..e1fbfff
--- /dev/null
+++ b/src/main/java/com/samskivert/depot/util/ByteEnum.java
@@ -0,0 +1,82 @@
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001-2012 Michael Bayne, et al.
+// http://github.com/samskivert/samskivert/blob/master/COPYING
+
+package com.samskivert.depot.util;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * An interface implemented by enums that can map themselves to a byte. See {@link ByteEnum.Util}
+ * for mapping back from a byte to an enum.
+ */
+public interface ByteEnum
+{
+ /** {@link ByteEnum} utility methods. */
+ public static class Util
+ {
+ /**
+ * Returns the enum value with the specified code in the supplied enum class.
+ *
+ * @exception IllegalArgumentException thrown if the enum lacks a value that maps to the
+ * supplied code.
+ */
+ public static & ByteEnum> E fromByte (Class eclass, byte code) {
+ for (E value : eclass.getEnumConstants()) {
+ if (value.toByte() == code) {
+ return value;
+ }
+ }
+ throw new IllegalArgumentException(eclass + " has no value with code " + code);
+ }
+
+ /**
+ * Convert a Set of ByteEnums into an integer compactly representing the elements that are
+ * included.
+ */
+ public static & ByteEnum> int setToInt (Set set) {
+ int flags = 0;
+ for (E value : set) {
+ flags |= toIntFlag(value);
+ }
+ return flags;
+ }
+
+ /**
+ * Convert an int representation of ByteEnum flags into an EnumSet.
+ */
+ public static & ByteEnum> EnumSet intToSet (
+ Class eclass, int flags) {
+ EnumSet set = EnumSet.noneOf(eclass);
+ for (E value : eclass.getEnumConstants()) {
+ if ((flags & toIntFlag(value)) != 0) {
+ set.add(value);
+ }
+ }
+ return set;
+ }
+
+ /**
+ * A helper function for setToInt() and intToSet() that validates that the specified
+ * ByteEnum value is not null and has a code between 0 and 31, inclusive.
+ */
+ protected static & ByteEnum> int toIntFlag (E value) {
+ byte code = value.toByte(); // allow this to throw NPE
+ if (code < 0 || code > 31) {
+ throw new IllegalArgumentException(
+ "ByteEnum code is outside the range that can be turned into an int " +
+ "[value=" + value + ", code=" + code + "]");
+ }
+ return (1 << code);
+ }
+
+ // TODO: setToByteArray() and byteArrayToSet(), for larger ByteEnums?
+ }
+
+ /**
+ * Returns the byte value to which to map this enum value.
+ */
+ public byte toByte ();
+}
diff --git a/src/main/java/com/samskivert/depot/util/RuntimeUtil.java b/src/main/java/com/samskivert/depot/util/RuntimeUtil.java
index 6f50325..31bebbf 100644
--- a/src/main/java/com/samskivert/depot/util/RuntimeUtil.java
+++ b/src/main/java/com/samskivert/depot/util/RuntimeUtil.java
@@ -17,7 +17,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.samskivert.depot.PersistentRecord;
-import com.samskivert.util.Tuple;
+import com.samskivert.depot.util.Tuple2;
import static com.google.common.base.Preconditions.checkArgument;
@@ -263,14 +263,14 @@ public class RuntimeUtil
protected static Function getconv (Class> fc, Class> tc)
{
- return _converters.get(Tuple.newTuple(fc, tc));
+ return _converters.get(Tuple2.create(fc, tc));
}
protected static void regconv (Class fc, Class tc, Function conv)
{
@SuppressWarnings("unchecked") Function value =
(Function)conv;
- _converters.put(Tuple.newTuple(fc, tc), value);
+ _converters.put(Tuple2.create(fc, tc), value);
}
protected static Map> _converters =
diff --git a/src/test/java/com/samskivert/depot/ByteEnumTest.java b/src/test/java/com/samskivert/depot/ByteEnumTest.java
index 9bad277..288d7ac 100644
--- a/src/test/java/com/samskivert/depot/ByteEnumTest.java
+++ b/src/test/java/com/samskivert/depot/ByteEnumTest.java
@@ -9,9 +9,8 @@ import java.lang.reflect.Field;
import org.junit.Test;
import static org.junit.Assert.*;
-import com.samskivert.util.ByteEnum;
-
import com.samskivert.depot.impl.FieldMarshaller;
+import com.samskivert.depot.util.ByteEnum;
/**
* Tests ByteEnum related bits.
diff --git a/src/test/java/com/samskivert/depot/MigrationTest.java b/src/test/java/com/samskivert/depot/MigrationTest.java
index 5f445fd..5354348 100644
--- a/src/test/java/com/samskivert/depot/MigrationTest.java
+++ b/src/test/java/com/samskivert/depot/MigrationTest.java
@@ -11,7 +11,7 @@ import org.junit.Test;
import com.samskivert.depot.annotation.Id;
import com.samskivert.depot.impl.Modifier;
-import com.samskivert.jdbc.DatabaseLiaison;
+import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
/**
* Tests various migrations.
diff --git a/src/test/java/com/samskivert/depot/TestBase.java b/src/test/java/com/samskivert/depot/TestBase.java
index aeed37c..9fd3189 100644
--- a/src/test/java/com/samskivert/depot/TestBase.java
+++ b/src/test/java/com/samskivert/depot/TestBase.java
@@ -12,8 +12,8 @@ import java.util.Properties;
import com.google.common.collect.Lists;
-import com.samskivert.jdbc.ConnectionProvider;
-import com.samskivert.jdbc.StaticConnectionProvider;
+import com.samskivert.depot.ConnectionProvider;
+import com.samskivert.depot.StaticConnectionProvider;
import com.samskivert.util.Calendars;
import static org.junit.Assert.*;
diff --git a/src/test/java/com/samskivert/depot/TransformTest.java b/src/test/java/com/samskivert/depot/TransformTest.java
index 9f7b699..d0ddc42 100644
--- a/src/test/java/com/samskivert/depot/TransformTest.java
+++ b/src/test/java/com/samskivert/depot/TransformTest.java
@@ -16,15 +16,13 @@ import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-import com.samskivert.util.ByteEnum;
-
import com.samskivert.depot.Key;
-import com.samskivert.depot.expression.ColumnExp;
-
import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.annotation.Id;
import com.samskivert.depot.annotation.Transform;
+import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.impl.FieldMarshaller;
+import com.samskivert.depot.util.ByteEnum;
import static org.junit.Assert.*;
diff --git a/src/test/java/com/samskivert/depot/TransformersTest.java b/src/test/java/com/samskivert/depot/TransformersTest.java
index e38e010..c691143 100644
--- a/src/test/java/com/samskivert/depot/TransformersTest.java
+++ b/src/test/java/com/samskivert/depot/TransformersTest.java
@@ -12,7 +12,7 @@ import static org.junit.Assert.*;
import com.google.common.collect.Sets;
-import com.samskivert.util.ByteEnum;
+import com.samskivert.depot.util.ByteEnum;
/**
* Tests the stock transformers.