Eliminated samskivert dependency.
It was a constant source of annoyance that part of the Depot implementation lived in samskivert.jdbc. Now it all lives in Depot and samskivert can go back to being a long obsolete library of utility methods. Depot clients will have to make three renaming changes: 1. ConnectionProvider &c are now in com.samskivert.depot. 2. ByteEnum is now in com.samskivert.util. 3. Methods that returned index information used to return samskivert Tuple if you wanted an expression and an order. Now they must return Depot Tuple2. This should fail at app startup if you miss a spot, so this should not result in any ticking timebombs. NB: the next commit is probably going to change Tuple2 into a proper named class because using a Tuple2 for this situation turns out to suck. NB2: I moved DropTableMigration out of impl and put the impl details into DepotMetaData. I should have done that separately, but it was already in progress when I undertook to revamp.
This commit is contained in:
@@ -43,15 +43,10 @@
|
||||
</scm>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.samskivert</groupId>
|
||||
<artifactId>samskivert</artifactId>
|
||||
<version>1.8.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>17.0</version>
|
||||
<version>18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.ehcache</groupId>
|
||||
@@ -65,6 +60,7 @@
|
||||
<version>1.7.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
@@ -77,6 +73,12 @@
|
||||
<version>2.2.9</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.samskivert</groupId>
|
||||
<artifactId>samskivert</artifactId>
|
||||
<version>1.8.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -130,7 +132,6 @@
|
||||
<show>public</show>
|
||||
<excludePackageNames>com.samskivert.depot.impl</excludePackageNames>
|
||||
<links>
|
||||
<link>http://samskivert.github.com/samskivert/apidocs/</link>
|
||||
<link>http://docs.guava-libraries.googlecode.com/git/javadoc/</link>
|
||||
<link>http://ehcache.org/apidocs/</link>
|
||||
</links>
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// 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.sql.*;
|
||||
|
||||
/**
|
||||
* As the repository aims to interface with whatever database pooling services a project cares to
|
||||
* use, it obtains all of its database connections through a connection provider. The connection
|
||||
* provider provides connections based on a database identifier (a string) which identifies a
|
||||
* particular connection to a particular database. A user of these services would then coordinate
|
||||
* the database identifier (or identifiers) used to invoke a database operation with the database
|
||||
* connections that are returned by the connection provider that they provided to the repository at
|
||||
* construct time.
|
||||
*/
|
||||
public interface ConnectionProvider
|
||||
{
|
||||
/**
|
||||
* Obtains a database connection based on the supplied database identifier. The repository
|
||||
* expects to have exclusive use of this connection instance until it releases it. This
|
||||
* connection will be released subsequently with a call to {@link #releaseConnection} or {@link
|
||||
* #connectionFailed} depending on the circumstances of the release. <code>close()</code>
|
||||
* <em>will not</em> be called on the connection. It is up to the connection provider to close
|
||||
* the connection when it is released if appropriate.
|
||||
*
|
||||
* @param ident the database connection identifier.
|
||||
* @param readOnly whether or not the connection may be to a read-only mirror of the
|
||||
* repository.
|
||||
*
|
||||
* @return an active JDBC connection (which may have come from a connection pool).
|
||||
*
|
||||
* @exception DatabaseException thrown if a problem occurs trying to open the requested
|
||||
* connection.
|
||||
*/
|
||||
Connection getConnection (String ident, boolean readOnly);
|
||||
|
||||
/**
|
||||
* Releases a database connection when it is no longer needed by the repository.
|
||||
*
|
||||
* @param ident the database identifier used when obtaining this connection.
|
||||
* @param readOnly the same value that was passed to {@link #getConnection} to obtain this
|
||||
* connection.
|
||||
* @param conn the connection to release (back into the pool or to be closed if pooling is not
|
||||
* going on).
|
||||
*/
|
||||
void releaseConnection (String ident, boolean readOnly, Connection conn);
|
||||
|
||||
/**
|
||||
* Called by the repository if a failure occurred on the connection. It is expected that the
|
||||
* connection will be disposed of and subsequent calls to <code>getConnection</code> will
|
||||
* return a freshly established connection to the database.
|
||||
*
|
||||
* @param ident the database identifier used when obtaining this connection.
|
||||
* @param readOnly the same value that was passed to {@link #getConnection} to obtain this
|
||||
* connection.
|
||||
* @param conn the connection that failed.
|
||||
* @param error the error thrown by the connection (which may be used to determine if the
|
||||
* connection should be closed or can be reused).
|
||||
*/
|
||||
void connectionFailed (String ident, boolean readOnly, Connection conn, SQLException error);
|
||||
|
||||
/**
|
||||
* Returns a connection that can be used in a transaction. See {@link #getConnection}. Note:
|
||||
* transaction connections are never read-only. What would be the point?
|
||||
*
|
||||
* @exception DatabaseException thrown if a problem occurs trying to open the requested
|
||||
* connection.
|
||||
*/
|
||||
Connection getTxConnection (String ident);
|
||||
|
||||
/**
|
||||
* Releases a connection obtained by {@link #getTxConnection}. See {@link #releaseConnection}.
|
||||
*/
|
||||
void releaseTxConnection (String ident, Connection conn);
|
||||
|
||||
/**
|
||||
* Reports failure of a connection obtained by {@link #getTxConnection}. See {@link
|
||||
* #connectionFailed}.
|
||||
*/
|
||||
void txConnectionFailed (String ident, Connection conn, SQLException error);
|
||||
|
||||
/**
|
||||
* Returns the URL associated with this database identifier. This should be the same value that
|
||||
* would be used if {@link #getConnection} were called.
|
||||
*/
|
||||
String getURL (String ident);
|
||||
|
||||
/**
|
||||
* Shuts down this connection provider, closing all connections currently in the pool. This
|
||||
* should only be called once all active connections have been released with {@link
|
||||
* #releaseConnection}.
|
||||
*/
|
||||
void shutdown ();
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// 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.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import static com.samskivert.depot.Log.log;
|
||||
|
||||
/**
|
||||
* Provides connections using a pair of {@link DataSource} instances (one for read-only operations
|
||||
* and one for read-write operations). Note: if transactions are going to be used, the data sources
|
||||
* must be pooled data sources, otherwise bad things will happen.
|
||||
*/
|
||||
public class DataSourceConnectionProvider implements ConnectionProvider
|
||||
{
|
||||
/**
|
||||
* Creates a connection provider that will obtain connections from the supplied read-only and
|
||||
* read-write sources. The <code>ident</code> mechanism is not used by this provider.
|
||||
* Responsibility for the lifecycle of the supplied datasources is left to the caller and is
|
||||
* not managed by the connection provider ({@link #shutdown} does nothing).
|
||||
*
|
||||
* @param url a URL prefix that can be used by the {@link DatabaseLiaison} to identify the type
|
||||
* of database being accessed by the supplied data sources, this should be one of "jdbc:mysql"
|
||||
* or "jdbc:postgresql" as those are the only two types of database currently supported.
|
||||
*/
|
||||
public DataSourceConnectionProvider (String url, DataSource readSource, DataSource writeSource)
|
||||
{
|
||||
_url = url;
|
||||
_readSource = readSource;
|
||||
_writeSource = writeSource;
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public Connection getConnection (String ident, boolean readOnly)
|
||||
{
|
||||
return getConnection(ident, readOnly, null);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public void releaseConnection (String ident, boolean readOnly, Connection conn)
|
||||
{
|
||||
try {
|
||||
conn.close();
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure closing connection",
|
||||
"ident", ident, "ro", readOnly, "conn", conn, e);
|
||||
}
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public void connectionFailed (String ident, boolean readOnly, Connection conn,
|
||||
SQLException error)
|
||||
{
|
||||
try {
|
||||
conn.close();
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure closing failed connection",
|
||||
"ident", ident, "ro", readOnly, "conn", conn, e);
|
||||
}
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public Connection getTxConnection (String ident)
|
||||
{
|
||||
// our connections are pooled, so we can just get them normally
|
||||
return getConnection(ident, false, false);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public void releaseTxConnection (String ident, Connection conn)
|
||||
{
|
||||
// our connections are pooled, so we can just release them normally
|
||||
releaseConnection(ident, false, conn);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public void txConnectionFailed (String ident, Connection conn, SQLException error)
|
||||
{
|
||||
// our connections are pooled, so we can just fail them normally
|
||||
connectionFailed(ident, false, conn, error);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public String getURL (String ident)
|
||||
{
|
||||
return _url;
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public void shutdown ()
|
||||
{
|
||||
// nothing doing, the caller has to shutdown the datasources
|
||||
}
|
||||
|
||||
protected Connection getConnection (String ident, boolean readOnly, Boolean autoCommit)
|
||||
{
|
||||
try {
|
||||
Connection conn;
|
||||
if (readOnly) {
|
||||
conn = _readSource.getConnection();
|
||||
conn.setReadOnly(true);
|
||||
} else {
|
||||
conn = _writeSource.getConnection();
|
||||
}
|
||||
if (autoCommit != null) conn.setAutoCommit(autoCommit);
|
||||
return conn;
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
throw new DatabaseException(sqe);
|
||||
}
|
||||
}
|
||||
|
||||
protected String _url;
|
||||
protected DataSource _readSource, _writeSource;
|
||||
}
|
||||
@@ -19,13 +19,10 @@ import java.util.Set;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.ObjectArrays;
|
||||
import com.google.common.collect.Sets;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.samskivert.jdbc.ConnectionProvider;
|
||||
import com.samskivert.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
|
||||
import com.samskivert.depot.clause.InsertClause;
|
||||
import com.samskivert.depot.clause.Limit;
|
||||
import com.samskivert.depot.clause.QueryClause;
|
||||
@@ -48,6 +45,7 @@ import com.samskivert.depot.impl.SQLBuilder;
|
||||
import com.samskivert.depot.impl.clause.DeleteClause;
|
||||
import com.samskivert.depot.impl.clause.UpdateClause;
|
||||
import com.samskivert.depot.impl.expression.ValueExp;
|
||||
import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.depot.impl.util.SeqImpl;
|
||||
|
||||
/**
|
||||
@@ -143,7 +141,7 @@ public abstract class DepotRepository
|
||||
Key<T> key, CacheStrategy strategy, QueryClause... clauses)
|
||||
throws DatabaseException
|
||||
{
|
||||
clauses = ArrayUtil.append(clauses, key);
|
||||
clauses = ObjectArrays.concat(clauses, key);
|
||||
return _ctx.invoke(new FindOneQuery<T>(_ctx, key.getPersistentClass(), strategy, clauses));
|
||||
}
|
||||
|
||||
|
||||
+5
-13
@@ -2,7 +2,7 @@
|
||||
// Depot library - a Java relational persistence library
|
||||
// https://github.com/threerings/depot/blob/master/LICENSE
|
||||
|
||||
package com.samskivert.depot.impl;
|
||||
package com.samskivert.depot;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
@@ -10,8 +10,9 @@ import java.sql.SQLException;
|
||||
import com.samskivert.depot.DataMigration;
|
||||
import com.samskivert.depot.DatabaseException;
|
||||
import com.samskivert.depot.PersistenceContext;
|
||||
|
||||
import com.samskivert.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.depot.impl.DepotMetaData;
|
||||
import com.samskivert.depot.impl.Modifier;
|
||||
import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
|
||||
|
||||
/**
|
||||
* Migration to drop a table.
|
||||
@@ -47,16 +48,7 @@ public class DropTableMigration extends DataMigration
|
||||
}
|
||||
});
|
||||
// delete the schema version
|
||||
_ctx.invoke(new Modifier.Simple() {
|
||||
@Override protected String createQuery (DatabaseLiaison liaison)
|
||||
{
|
||||
return "delete from " +
|
||||
liaison.tableSQL(DepotMetaData.SCHEMA_VERSION_TABLE) +
|
||||
" where " +
|
||||
liaison.columnSQL(DepotMetaData.P_COLUMN) +
|
||||
" = '" + _table + "'";
|
||||
}
|
||||
});
|
||||
_ctx.getMetaData().clearVersion(_table);
|
||||
}
|
||||
|
||||
/** Our persistence context. */
|
||||
@@ -13,7 +13,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.samskivert.util.Histogram;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
@@ -43,10 +42,10 @@ public class EHCacheAdapter
|
||||
{
|
||||
public static class EHCachePerformance
|
||||
{
|
||||
public Histogram lookups;
|
||||
public Histogram stores;
|
||||
public Histogram removes;
|
||||
public Histogram enumerations;
|
||||
public Stats.Histogram lookups;
|
||||
public Stats.Histogram stores;
|
||||
public Stats.Histogram removes;
|
||||
public Stats.Histogram enumerations;
|
||||
}
|
||||
|
||||
public static class EHCacheConfig
|
||||
@@ -412,10 +411,10 @@ public class EHCacheAdapter
|
||||
};
|
||||
|
||||
protected CacheManager _cachemgr;
|
||||
protected Histogram _lookups = new Histogram(0, 50, 20);
|
||||
protected Histogram _stores = new Histogram(0, 50, 20);
|
||||
protected Histogram _removes = new Histogram(0, 50, 20);
|
||||
protected Histogram _enumerations = new Histogram(0, 50, 20);
|
||||
protected Stats.Histogram _lookups = new Stats.Histogram(0, 50, 20);
|
||||
protected Stats.Histogram _stores = new Stats.Histogram(0, 50, 20);
|
||||
protected Stats.Histogram _removes = new Stats.Histogram(0, 50, 20);
|
||||
protected Stats.Histogram _enumerations = new Stats.Histogram(0, 50, 20);
|
||||
|
||||
protected Map<CacheCategory, Ehcache> _categories =
|
||||
Collections.synchronizedMap(Maps.<CacheCategory, Ehcache>newHashMap());
|
||||
|
||||
@@ -10,10 +10,9 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.samskivert.depot.clause.WhereClause;
|
||||
import com.samskivert.depot.expression.ColumnExp;
|
||||
import com.samskivert.depot.expression.SQLExpression;
|
||||
@@ -260,7 +259,7 @@ public class Key<T extends PersistentRecord> extends WhereClause
|
||||
|
||||
// finally make sure we were not given any fields that are not primary key fields
|
||||
checkArgument(map.isEmpty(), "Non-key columns given: " +
|
||||
StringUtil.join(map.keySet().toArray()));
|
||||
Joiner.on(", ").join(map.keySet()));
|
||||
|
||||
return cvalues;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ import java.util.Iterator;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.samskivert.depot.clause.WhereClause;
|
||||
import com.samskivert.depot.expression.ColumnExp;
|
||||
import com.samskivert.depot.expression.SQLExpression;
|
||||
@@ -167,7 +164,7 @@ public abstract class KeySet<T extends PersistentRecord> extends WhereClause
|
||||
}
|
||||
|
||||
@Override public String toString () {
|
||||
return DepotUtil.justClassName(_pClass) + StringUtil.toString(_keys);
|
||||
return DepotUtil.justClassName(_pClass) + Arrays.toString(_keys);
|
||||
}
|
||||
|
||||
protected Comparable<?>[] _keys;
|
||||
@@ -216,7 +213,7 @@ public abstract class KeySet<T extends PersistentRecord> extends WhereClause
|
||||
public void validateFlushType (Class<?> pClass)
|
||||
{
|
||||
if (!pClass.equals(_pClass)) {
|
||||
throw new IllegalArgumentException(Logger.format(
|
||||
throw new IllegalArgumentException(Log.format(
|
||||
"Class mismatch between persistent record and cache invalidator",
|
||||
"record", pClass.getSimpleName(), "invtype", _pClass.getSimpleName()));
|
||||
}
|
||||
|
||||
@@ -4,13 +4,50 @@
|
||||
|
||||
package com.samskivert.depot;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Contains a reference to the log object used by this package.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
public static interface Target {
|
||||
void debug (String message, Object... args);
|
||||
void info (String message, Object... args);
|
||||
void warning (String message, Object... args);
|
||||
}
|
||||
|
||||
/** We dispatch our log messages through this logger. */
|
||||
public static Logger log = Logger.getLogger("com.samskivert.depot");
|
||||
public static Target log = new Target() {
|
||||
public void debug (String message, Object... args) {
|
||||
log(Level.FINE, message, args);
|
||||
}
|
||||
public void info (String message, Object... args) {
|
||||
log(Level.INFO, message, args);
|
||||
}
|
||||
public void warning (String message, Object... args) {
|
||||
log(Level.WARNING, message, args);
|
||||
}
|
||||
private void log (Level level, String message, Object[] args) {
|
||||
Object exn = (args.length == 0) ? null : args[args.length-1];
|
||||
if (args.length % 2 == 1 && (exn instanceof Throwable)) {
|
||||
impl.log(level, format(message, args), (Throwable)exn);
|
||||
} else {
|
||||
impl.log(level, format(message, args));
|
||||
}
|
||||
}
|
||||
private Logger impl = Logger.getLogger("com.samskivert.depot");
|
||||
};
|
||||
|
||||
/** Formats the supplied log message and arguments. */
|
||||
public static String format (String message, Object... args) {
|
||||
if (args.length < 2) return message;
|
||||
StringBuilder sb = new StringBuilder(message);
|
||||
sb.append(" [");
|
||||
for (int ii = 0; ii < args.length; ii += 2) {
|
||||
sb.append(args[ii]).append("=").append(args[ii+1]);
|
||||
}
|
||||
return sb.append("]").toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Depot library - a Java relational persistence library
|
||||
// https://github.com/threerings/depot/blob/master/LICENSE
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package com.samskivert.depot;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -22,8 +19,7 @@ import com.google.common.collect.Sets;
|
||||
import com.samskivert.depot.expression.ColumnExp;
|
||||
import com.samskivert.depot.expression.SQLExpression;
|
||||
import com.samskivert.depot.impl.DepotUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.util.Tuple;
|
||||
import com.samskivert.depot.util.Tuple2;
|
||||
|
||||
/**
|
||||
* This class handles the construction of a Where clause from a set of multi-column keys.
|
||||
@@ -85,7 +81,7 @@ class MultiKeySet<T extends PersistentRecord> extends KeySet<T>
|
||||
}
|
||||
|
||||
@Override public String toString () {
|
||||
return DepotUtil.justClassName(_pClass) + StringUtil.toString(_keys);
|
||||
return DepotUtil.justClassName(_pClass) + Arrays.toString(_keys);
|
||||
}
|
||||
|
||||
// note: this method will destructively modify its arguments
|
||||
@@ -102,11 +98,11 @@ class MultiKeySet<T extends PersistentRecord> extends KeySet<T>
|
||||
Comparable<?> maxValue = null;
|
||||
|
||||
for (int column : columnsLeft) {
|
||||
Tuple<Comparable<?>, Integer> colChunk = findBiggestChunk(keys, column);
|
||||
if (colChunk.right > maxSize) {
|
||||
Tuple2<Comparable<?>, Integer> colChunk = findBiggestChunk(keys, column);
|
||||
if (colChunk.b > maxSize) {
|
||||
maxColumn = column;
|
||||
maxSize = colChunk.right;
|
||||
maxValue = colChunk.left;
|
||||
maxSize = colChunk.b;
|
||||
maxValue = colChunk.a;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +121,7 @@ class MultiKeySet<T extends PersistentRecord> extends KeySet<T>
|
||||
}
|
||||
|
||||
// iterate key rows and find the most common value across those rows, in the given column
|
||||
protected Tuple<Comparable<?>, Integer> findBiggestChunk (List<Comparable<?>[]> rows, int col)
|
||||
protected Tuple2<Comparable<?>, Integer> findBiggestChunk (List<Comparable<?>[]> rows, int col)
|
||||
{
|
||||
int maxCount = 0;
|
||||
Comparable<?> maxValue = null;
|
||||
@@ -146,7 +142,7 @@ class MultiKeySet<T extends PersistentRecord> extends KeySet<T>
|
||||
maxValue = element;
|
||||
}
|
||||
}
|
||||
return new Tuple<Comparable<?>, Integer>(maxValue, maxCount);
|
||||
return new Tuple2<Comparable<?>, Integer>(maxValue, maxCount);
|
||||
}
|
||||
|
||||
// find all the rows that contain the given chunk value in the given column. delete these
|
||||
|
||||
@@ -17,14 +17,6 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.samskivert.jdbc.ConnectionProvider;
|
||||
import com.samskivert.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.jdbc.JDBCUtil;
|
||||
import com.samskivert.jdbc.LiaisonRegistry;
|
||||
|
||||
import com.samskivert.depot.CacheAdapter.CacheCategory;
|
||||
import com.samskivert.depot.CacheAdapter.CachedValue;
|
||||
import com.samskivert.depot.annotation.TableGenerator;
|
||||
@@ -37,6 +29,9 @@ import com.samskivert.depot.impl.KeyCacheKey;
|
||||
import com.samskivert.depot.impl.Modifier;
|
||||
import com.samskivert.depot.impl.Operation;
|
||||
import com.samskivert.depot.impl.SQLBuilder;
|
||||
import com.samskivert.depot.impl.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.depot.impl.jdbc.JDBCUtil;
|
||||
import com.samskivert.depot.impl.jdbc.LiaisonRegistry;
|
||||
|
||||
import static com.samskivert.depot.Log.log;
|
||||
|
||||
@@ -165,6 +160,15 @@ public class PersistenceContext
|
||||
return _cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that handles metadata management for Depot. This is not usually needed for
|
||||
* normal operation.
|
||||
*/
|
||||
public DepotMetaData getMetaData ()
|
||||
{
|
||||
return _meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether schema migrations are allowed in this context.
|
||||
*/
|
||||
@@ -612,13 +616,7 @@ public class PersistenceContext
|
||||
}
|
||||
|
||||
long preConnect = System.nanoTime();
|
||||
Connection conn;
|
||||
try {
|
||||
conn = connop.get();
|
||||
} catch (PersistenceException pe) {
|
||||
throw new DatabaseException("Failed get connection [ident=" + _ident +
|
||||
", isRO=" + isReadOnly + "]", pe);
|
||||
}
|
||||
Connection conn = connop.get();
|
||||
|
||||
// wrap the connection in a proxy that will collect all opened statements
|
||||
List<Statement> 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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).
|
||||
*
|
||||
* <p> The configuration properties file should contain the following information:
|
||||
*
|
||||
* <pre>
|
||||
* IDENT.driver=[jdbc driver class]
|
||||
* IDENT.url=[jdbc driver url]
|
||||
* IDENT.username=[jdbc username]
|
||||
* IDENT.password=[jdbc password]
|
||||
*
|
||||
* [...]
|
||||
* </pre>
|
||||
*
|
||||
* Where <code>IDENT</code> 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.
|
||||
*
|
||||
* <p> Additionally, a default set of properties can be provided using the identifier
|
||||
* <code>default</code>. Values not provided for a specific identifier will be sought from the
|
||||
* defaults. For example:
|
||||
*
|
||||
* <pre>
|
||||
* 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]
|
||||
*
|
||||
* [...]
|
||||
* </pre>
|
||||
*/
|
||||
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<String, Mapping> 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<String,Mapping> _idents = new HashMap<String,Mapping>();
|
||||
|
||||
/** A mapping from connection key to connection records. */
|
||||
protected HashMap<String,Mapping> _keys = new HashMap<String,Mapping>();
|
||||
|
||||
/** The key used as defaults for the database definitions. */
|
||||
protected static final String DEFAULTS_KEY = "default";
|
||||
}
|
||||
@@ -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 <code>minValue</code>, the subsequent <code>bucketCount</code>
|
||||
* elements contain the count of values falling into those buckets and the last element
|
||||
* contains values greater than or equal to <code>maxValue</code>.
|
||||
*/
|
||||
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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<E> value)
|
||||
{
|
||||
return (value == null) ? null : ByteEnumUtil.setToInt(value);
|
||||
return (value == null) ? null : ByteEnum.Util.setToInt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<E> fromPersistent (Integer encoded)
|
||||
{
|
||||
return (encoded == null) ? null : ByteEnumUtil.intToSet(_eclass, encoded);
|
||||
return (encoded == null) ? null : ByteEnum.Util.intToSet(_eclass, encoded);
|
||||
}
|
||||
|
||||
/** The enum class token. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Void>
|
||||
public Void visit (Case<?> caseExp)
|
||||
{
|
||||
_builder.append("(case ");
|
||||
for (Tuple<SQLExpression<?>, SQLExpression<?>> tuple : caseExp.getWhenExps()) {
|
||||
for (Tuple2<SQLExpression<?>, 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<Void>
|
||||
public Void visit (CreateIndexClause createIndexClause)
|
||||
{
|
||||
if (!_allowComplexIndices) {
|
||||
for (Tuple<SQLExpression<?>, Order> field : createIndexClause.getFields()) {
|
||||
if (!(field.left instanceof ColumnExp<?>)) {
|
||||
for (Tuple2<SQLExpression<?>, 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<Void>
|
||||
// turn off table abbreviations here
|
||||
_defaultType = createIndexClause.getPersistentClass();
|
||||
boolean comma = false;
|
||||
for (Tuple<SQLExpression<?>, Order> field : createIndexClause.getFields()) {
|
||||
for (Tuple2<SQLExpression<?>, Order> field : createIndexClause.getFields()) {
|
||||
if (comma) {
|
||||
_builder.append(", ");
|
||||
}
|
||||
@@ -544,11 +543,11 @@ public abstract class BuildVisitor implements FragmentVisitor<Void>
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -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<T extends PersistentRecord> implements QueryMarshal
|
||||
|
||||
// introspect on the class and create marshallers and indices for persistent fields
|
||||
List<ColumnExp<?>> fields = Lists.newArrayList();
|
||||
ListMultimap<String, Tuple<SQLExpression<?>, Order>> namedFieldIndices =
|
||||
ListMultimap<String, Tuple2<SQLExpression<?>, Order>> namedFieldIndices =
|
||||
ArrayListMultimap.create();
|
||||
ListMultimap<String, Tuple<SQLExpression<?>, Order>> uniqueNamedFieldIndices =
|
||||
ListMultimap<String, Tuple2<SQLExpression<?>, Order>> uniqueNamedFieldIndices =
|
||||
ArrayListMultimap.create();
|
||||
for (Field field : _pClass.getFields()) {
|
||||
int mods = field.getModifiers();
|
||||
@@ -183,8 +182,8 @@ public class DepotMarshaller<T extends PersistentRecord> implements QueryMarshal
|
||||
"Unique columns are implicitly indexed and should not be @Index'd.");
|
||||
|
||||
String name = index.name().equals("") ? field.getName() + "Index" : index.name();
|
||||
Tuple<SQLExpression<?>, Order> entry =
|
||||
new Tuple<SQLExpression<?>, Order>(fieldColumn, Order.ASC);
|
||||
Tuple2<SQLExpression<?>, Order> entry =
|
||||
new Tuple2<SQLExpression<?>, 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<T extends PersistentRecord> implements QueryMarshal
|
||||
|
||||
protected CreateIndexClause buildIndex (String name, boolean unique, Object config)
|
||||
{
|
||||
List<Tuple<SQLExpression<?>, Order>> definition = Lists.newArrayList();
|
||||
List<Tuple2<SQLExpression<?>, Order>> definition = Lists.newArrayList();
|
||||
if (config instanceof ColumnExp<?>) {
|
||||
definition.add(new Tuple<SQLExpression<?>, Order>((ColumnExp<?>)config, Order.ASC));
|
||||
definition.add(new Tuple2<SQLExpression<?>, Order>((ColumnExp<?>)config, Order.ASC));
|
||||
} else if (config instanceof ColumnExp<?>[]) {
|
||||
for (ColumnExp<?> column : (ColumnExp<?>[])config) {
|
||||
definition.add(new Tuple<SQLExpression<?>, Order>(column, Order.ASC));
|
||||
definition.add(new Tuple2<SQLExpression<?>, Order>(column, Order.ASC));
|
||||
}
|
||||
} else if (config instanceof SQLExpression) {
|
||||
definition.add(new Tuple<SQLExpression<?>, Order>((SQLExpression<?>)config, Order.ASC));
|
||||
} else if (config instanceof Tuple<?,?>) {
|
||||
@SuppressWarnings("unchecked") Tuple<SQLExpression<?>, Order> tuple =
|
||||
(Tuple<SQLExpression<?>, Order>)config;
|
||||
definition.add(new Tuple2<SQLExpression<?>, Order>((SQLExpression<?>)config, Order.ASC));
|
||||
} else if (config instanceof Tuple2<?,?>) {
|
||||
@SuppressWarnings("unchecked") Tuple2<SQLExpression<?>, Order> tuple =
|
||||
(Tuple2<SQLExpression<?>, Order>)config;
|
||||
definition.add(tuple);
|
||||
} else if (config instanceof List<?>) {
|
||||
@SuppressWarnings("unchecked") List<Tuple<SQLExpression<?>, Order>> defs =
|
||||
(List<Tuple<SQLExpression<?>, Order>>)config;
|
||||
@SuppressWarnings("unchecked") List<Tuple2<SQLExpression<?>, Order>> defs =
|
||||
(List<Tuple2<SQLExpression<?>, Order>>)config;
|
||||
definition.addAll(defs);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Method '" + name + "' must return ColumnExp[], SQLExpression or " +
|
||||
"List<Tuple<SQLExpression, Order>>");
|
||||
"List<Tuple2<SQLExpression, Order>>");
|
||||
}
|
||||
return new CreateIndexClause(_pClass, getTableName() + "_" + name, unique, definition);
|
||||
}
|
||||
@@ -1087,10 +1086,15 @@ public class DepotMarshaller<T extends PersistentRecord> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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<?>, SQLExpression<?>> exp : caseExp.getWhenExps()) {
|
||||
Object result = exp.left.accept(this);
|
||||
for (Tuple2<SQLExpression<?>, 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();
|
||||
|
||||
@@ -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,11 +85,11 @@ public abstract class FieldMarshaller<T>
|
||||
return createTransformingMarshaller(xformer, field, xform);
|
||||
} catch (InstantiationException e) {
|
||||
throw new IllegalArgumentException(
|
||||
Logger.format("Unable to create Transformer", "xclass", xform.value(),
|
||||
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(),
|
||||
Log.format("Unable to create Transformer", "xclass", xform.value(),
|
||||
"field", field), e);
|
||||
}
|
||||
}
|
||||
@@ -277,11 +274,9 @@ public abstract class FieldMarshaller<T>
|
||||
}
|
||||
}
|
||||
}
|
||||
if (column != null) {
|
||||
if (!StringUtil.isBlank(column.name())) {
|
||||
if (column != null && !column.name().isEmpty()) {
|
||||
_columnName = column.name();
|
||||
}
|
||||
}
|
||||
|
||||
if (_computed != null) {
|
||||
return;
|
||||
@@ -673,11 +668,11 @@ public abstract class FieldMarshaller<T>
|
||||
}
|
||||
@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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<String> 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 .
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<Tuple<SQLExpression<?>, Order>> fields)
|
||||
List<Tuple2<SQLExpression<?>, Order>> fields)
|
||||
{
|
||||
_pClass = pClass;
|
||||
_name = name;
|
||||
@@ -50,7 +49,7 @@ public class CreateIndexClause
|
||||
return _unique;
|
||||
}
|
||||
|
||||
public List<Tuple<SQLExpression<?>,Order>> getFields ()
|
||||
public List<Tuple2<SQLExpression<?>,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<Tuple<SQLExpression<?>,Order>> _fields;
|
||||
protected List<Tuple2<SQLExpression<?>,Order>> _fields;
|
||||
}
|
||||
|
||||
@@ -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) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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<String> 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<String> columns,
|
||||
List<ColumnDefinition> declarations,
|
||||
List<String> primaryKeyColumns) throws SQLException {
|
||||
return createTableIfMissing(conn, table, columns, declarations, null, primaryKeyColumns);
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean createTableIfMissing (Connection conn, String table, List<String> columns,
|
||||
List<ColumnDefinition> definitions,
|
||||
List<List<String>> uniqueConstraintColumns,
|
||||
List<String> 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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<String> 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<String> columns,
|
||||
List<ColumnDefinition> declarations,
|
||||
List<String> 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<String> columns,
|
||||
List<ColumnDefinition> declarations,
|
||||
List<List<String>> uniqueConstraintColumns,
|
||||
List<String> 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. <em>Note:</em> 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. <em>Note:</em> 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.
|
||||
* <em>Note:</em> 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.
|
||||
* <em>Note:</em> 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> columns, List<ColumnDefinition> defns,
|
||||
List<List<String>> uniqueColumns, List<String> 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<String> seenUniques = new HashSet<String>();
|
||||
if (uniqueColumns != null) {
|
||||
for (List<String> 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<List<String>> 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<ColumnDefinition> newDefns = new ArrayList<ColumnDefinition>(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<List<String>>(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);
|
||||
}
|
||||
}
|
||||
@@ -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<Statement> 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 <code>stmt.executeUpdate()</code> 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 <code>stmt.executeUpdate()</code> 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 <code>stmt.executeUpdate()</code> 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:
|
||||
* <pre>{@code
|
||||
* Collection<Integer> 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);
|
||||
* }</pre>
|
||||
*
|
||||
* @param query the SQL query to run for each batch with the string <code>#KEYS#</code> 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 <code>#KEYS#</code>.
|
||||
*/
|
||||
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 <code>stmt.executeUpdate()</code> 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. <em>Note:</em> 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. <em>Note:</em> 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. <em>Note:</em> 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 };
|
||||
}
|
||||
@@ -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<DatabaseLiaison> _liaisons = new ArrayList<DatabaseLiaison>();
|
||||
protected static Map<String,DatabaseLiaison> _mappings = new HashMap<String,DatabaseLiaison>();
|
||||
|
||||
// register our liaison classes
|
||||
static {
|
||||
registerLiaisonClass(MySQLLiaison.class);
|
||||
registerLiaisonClass(PostgreSQLLiaison.class);
|
||||
registerLiaisonClass(HsqldbLiaison.class);
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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 + "`";
|
||||
}
|
||||
}
|
||||
@@ -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 + "\"";
|
||||
}
|
||||
}
|
||||
@@ -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<T extends Number> extends Arithmetic<T>
|
||||
{
|
||||
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<Double>() {
|
||||
|
||||
@@ -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<T>
|
||||
{
|
||||
int i = 0;
|
||||
for (; i + 1 < exps.length; i += 2) {
|
||||
_whenExps.add(Tuple.<SQLExpression<?>, SQLExpression<?>>newTuple(exps[i], exps[i + 1]));
|
||||
_whenExps.add(Tuple2.<SQLExpression<?>, SQLExpression<?>>create(exps[i], exps[i + 1]));
|
||||
}
|
||||
_elseExp = (i < exps.length) ? exps[i] : null;
|
||||
}
|
||||
|
||||
public List<Tuple<SQLExpression<?>, SQLExpression<?>>> getWhenExps ()
|
||||
public List<Tuple2<SQLExpression<?>, SQLExpression<?>>> getWhenExps ()
|
||||
{
|
||||
return _whenExps;
|
||||
}
|
||||
@@ -48,9 +48,9 @@ public class Case<T>
|
||||
// from SQLExpression
|
||||
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
|
||||
{
|
||||
for (Tuple<SQLExpression<?>, SQLExpression<?>> tuple : _whenExps) {
|
||||
tuple.left.addClasses(classSet);
|
||||
tuple.right.addClasses(classSet);
|
||||
for (Tuple2<SQLExpression<?>, SQLExpression<?>> tuple : _whenExps) {
|
||||
tuple.a.addClasses(classSet);
|
||||
tuple.b.addClasses(classSet);
|
||||
}
|
||||
if (_elseExp != null) {
|
||||
_elseExp.addClasses(classSet);
|
||||
@@ -62,9 +62,9 @@ public class Case<T>
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("Case(");
|
||||
for (Tuple<SQLExpression<?>, SQLExpression<?>> tuple : _whenExps) {
|
||||
builder.append(tuple.left.toString()).append("->");
|
||||
builder.append(tuple.right.toString()).append(",");
|
||||
for (Tuple2<SQLExpression<?>, 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<T>
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
protected List<Tuple<SQLExpression<?>, SQLExpression<?>>> _whenExps = Lists.newArrayList();
|
||||
protected List<Tuple2<SQLExpression<?>, SQLExpression<?>>> _whenExps = Lists.newArrayList();
|
||||
protected SQLExpression<?> _elseExp;
|
||||
}
|
||||
|
||||
@@ -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<Field> 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<String, String> 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<String, String> 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<String, String> 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<Field> list = Lists.newArrayList();
|
||||
getFields(clazz, list);
|
||||
return list.toArray(new Field[list.size()]);
|
||||
}
|
||||
|
||||
protected static void getFields (Class<?> clazz, List<Field> 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;
|
||||
|
||||
|
||||
@@ -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 <E extends Enum<E> & ByteEnum> E fromByte (Class<E> 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 <E extends Enum<E> & ByteEnum> int setToInt (Set<E> 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 <E extends Enum<E> & ByteEnum> EnumSet<E> intToSet (
|
||||
Class<E> eclass, int flags) {
|
||||
EnumSet<E> 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 <E extends Enum<E> & 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 ();
|
||||
}
|
||||
@@ -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<Object, Object> getconv (Class<?> fc, Class<?> tc)
|
||||
{
|
||||
return _converters.get(Tuple.newTuple(fc, tc));
|
||||
return _converters.get(Tuple2.create(fc, tc));
|
||||
}
|
||||
|
||||
protected static <F, T> void regconv (Class<F> fc, Class<T> tc, Function<F, T> conv)
|
||||
{
|
||||
@SuppressWarnings("unchecked") Function<Object, Object> value =
|
||||
(Function<Object, Object>)conv;
|
||||
_converters.put(Tuple.newTuple(fc, tc), value);
|
||||
_converters.put(Tuple2.create(fc, tc), value);
|
||||
}
|
||||
|
||||
protected static Map<Object, Function<Object, Object>> _converters =
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.*;
|
||||
|
||||
@@ -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.*;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user