diff --git a/src/java/com/samskivert/depot/annotation/Index.java b/src/java/com/samskivert/depot/annotation/Index.java
index 23f89bd..17ec791 100644
--- a/src/java/com/samskivert/depot/annotation/Index.java
+++ b/src/java/com/samskivert/depot/annotation/Index.java
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
-//
+//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -39,4 +39,11 @@ public @interface Index
/** Defines the fields on which the index operates. */
String[] fields () default {};
+
+ /** Whether or not this is a complex index. If true, a static method
+ * must be defined on the record that declares this index of the signature:
+ *
public static List> indexNameExpression ()
+ * which should return the index's defining expressions and whether each one is ascending
+ * or descending. */
+ boolean complex () default false;
}
diff --git a/src/java/com/samskivert/depot/impl/BindVisitor.java b/src/java/com/samskivert/depot/impl/BindVisitor.java
index 1bcfe64..ac6eb22 100644
--- a/src/java/com/samskivert/depot/impl/BindVisitor.java
+++ b/src/java/com/samskivert/depot/impl/BindVisitor.java
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
-//
+//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -32,7 +32,9 @@ import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.Key;
import com.samskivert.depot.MultiKey;
import com.samskivert.depot.PersistentRecord;
+import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.DeleteClause;
+import com.samskivert.depot.clause.DropIndexClause;
import com.samskivert.depot.clause.FieldDefinition;
import com.samskivert.depot.clause.ForUpdate;
import com.samskivert.depot.clause.FromOverride;
@@ -41,6 +43,7 @@ import com.samskivert.depot.clause.InsertClause;
import com.samskivert.depot.clause.Join;
import com.samskivert.depot.clause.Limit;
import com.samskivert.depot.clause.OrderBy;
+import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.clause.UpdateClause;
import com.samskivert.depot.clause.WhereClause;
@@ -59,6 +62,7 @@ import com.samskivert.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.depot.operator.SQLOperator.MultiOperator;
import com.samskivert.util.StringUtil;
+import com.samskivert.util.Tuple;
/**
* Implements the base functionality of the argument-binding pass of {@link SQLBuilder}. Dialectal
@@ -267,6 +271,18 @@ public class BindVisitor implements ExpressionVisitor
deleteClause.getWhereClause().accept(this);
}
+ public void visit (CreateIndexClause extends PersistentRecord> createIndexClause)
+ {
+ for (Tuple field : createIndexClause.getFields()) {
+ field.left.accept(this);
+ }
+ }
+
+ public void visit (DropIndexClause extends PersistentRecord> createIndexClause)
+ {
+ // do nothing
+ }
+
protected BindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt)
{
_types = types;
diff --git a/src/java/com/samskivert/depot/impl/BuildVisitor.java b/src/java/com/samskivert/depot/impl/BuildVisitor.java
index 9826565..54b4bd3 100644
--- a/src/java/com/samskivert/depot/impl/BuildVisitor.java
+++ b/src/java/com/samskivert/depot/impl/BuildVisitor.java
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
-//
+//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -30,7 +30,9 @@ import com.samskivert.depot.Key;
import com.samskivert.depot.MultiKey;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.Computed;
+import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.DeleteClause;
+import com.samskivert.depot.clause.DropIndexClause;
import com.samskivert.depot.clause.FieldDefinition;
import com.samskivert.depot.clause.FieldOverride;
import com.samskivert.depot.clause.ForUpdate;
@@ -43,6 +45,7 @@ import com.samskivert.depot.clause.OrderBy;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.clause.UpdateClause;
import com.samskivert.depot.clause.WhereClause;
+import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.EpochSeconds;
import com.samskivert.depot.expression.FunctionExp;
@@ -56,6 +59,7 @@ import com.samskivert.depot.operator.Conditionals.IsNull;
import com.samskivert.depot.operator.Logic.Not;
import com.samskivert.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.depot.operator.SQLOperator.MultiOperator;
+import com.samskivert.util.Tuple;
/**
* Implements the base functionality of the SQL-building pass of {@link SQLBuilder}. Dialectal
@@ -476,6 +480,45 @@ public abstract class BuildVisitor implements ExpressionVisitor
_builder.append(")");
}
+ public void visit (CreateIndexClause extends PersistentRecord> createIndexClause)
+ {
+ _builder.append("create ");
+ if (createIndexClause.isUnique()) {
+ _builder.append("unique ");
+ }
+ _builder.append("index ");
+ appendIdentifier(createIndexClause.getName());
+ _builder.append(" on ");
+ appendTableName(createIndexClause.getPersistentClass());
+ _builder.append(" (");
+
+ // turn off table abbreviations here
+ _defaultType = createIndexClause.getPersistentClass();
+ boolean comma = false;
+ for (Tuple field : createIndexClause.getFields()) {
+ if (comma) {
+ _builder.append(", ");
+ }
+ comma = true;
+
+ field.left.accept(this);
+ if (field.right == Order.DESC) {
+ // ascending is default, print nothing unless explicitly descending
+ _builder.append(" desc");
+ }
+ }
+ // turn them back on
+ _defaultType = null;
+
+ _builder.append(")");
+ }
+
+ public void visit (DropIndexClause extends PersistentRecord> dropIndexClause)
+ {
+ _builder.append("drop index ");
+ appendIdentifier(dropIndexClause.getName());
+ }
+
protected abstract void appendIdentifier (String field);
protected void appendTableName (Class extends PersistentRecord> type)
@@ -588,8 +631,10 @@ public abstract class BuildVisitor implements ExpressionVisitor
// if we get this far we hopefully have a table to select from
if (tableClass != null) {
- appendTableAbbreviation(tableClass);
- _builder.append(".");
+ if (_defaultType != tableClass) {
+ appendTableAbbreviation(tableClass);
+ _builder.append(".");
+ }
appendIdentifier(fm.getColumnName());
return;
}
@@ -613,6 +658,9 @@ public abstract class BuildVisitor implements ExpressionVisitor
protected Map, Map> _definitions=
Maps.newHashMap();
+ /** Set this to non-null to suppress table abbreviations from being prepended for a class. */
+ protected Class extends PersistentRecord> _defaultType;
+
/** A flag that's set to true for inner SELECT's */
protected boolean _innerClause = false;
diff --git a/src/java/com/samskivert/depot/impl/DepotMarshaller.java b/src/java/com/samskivert/depot/impl/DepotMarshaller.java
index 9eb78c7..eb664b0 100644
--- a/src/java/com/samskivert/depot/impl/DepotMarshaller.java
+++ b/src/java/com/samskivert/depot/impl/DepotMarshaller.java
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
-//
+//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -21,6 +21,7 @@
package com.samskivert.depot.impl;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
@@ -30,6 +31,7 @@ import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -53,10 +55,17 @@ import com.samskivert.depot.annotation.Index;
import com.samskivert.depot.annotation.TableGenerator;
import com.samskivert.depot.annotation.Transient;
import com.samskivert.depot.annotation.UniqueConstraint;
+import com.samskivert.depot.clause.CreateIndexClause;
+import com.samskivert.depot.clause.OrderBy.Order;
+import com.samskivert.depot.clause.QueryClause;
+import com.samskivert.depot.expression.ColumnExp;
+import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.DatabaseLiaison;
+import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.util.ArrayUtil;
+import com.samskivert.util.Tuple;
import static com.samskivert.depot.Log.log;
@@ -716,10 +725,11 @@ public class DepotMarshaller
declarations, uniqueConCols, primaryKeyColumns);
// add its indexen
- for (Index idx : indexen) {
- liaison.addIndexToTable(
- conn, getTableName(), fieldsToColumns(idx.fields()),
- getTableName() + "_" + idx.name(), idx.unique());
+ for (Index index : indexen) {
+ String ixName = getTableName() + "_" + index.name();
+ execute(conn, builder, index.complex() ?
+ buildComplexIndex(ixName, index.unique(), index.name()) :
+ buildSimpleIndex(ixName, index.unique(), index.fields()));
}
// create our value generators
@@ -737,7 +747,22 @@ public class DepotMarshaller
});
}
- protected TableMetaData runMigrations (PersistenceContext ctx, TableMetaData metaData,
+ protected int execute (Connection conn, SQLBuilder builder, QueryClause clause)
+ throws SQLException
+ {
+ if (builder.newQuery(clause)) {
+ PreparedStatement stmt = builder.prepare(conn);
+
+ try {
+ return stmt.executeUpdate();
+ } finally {
+ JDBCUtil.close(stmt);
+ }
+ }
+ return 0;
+ }
+
+ protected TableMetaData runMigrations (final PersistenceContext ctx, TableMetaData metaData,
final SQLBuilder builder, int currentVersion)
throws DatabaseException
{
@@ -840,9 +865,9 @@ public class DepotMarshaller
ctx.invoke(new Modifier() {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
- liaison.addIndexToTable(
- conn, getTableName(), fieldsToColumns(index.fields()),
- ixName, index.unique());
+ execute(conn, builder, index.complex() ?
+ buildComplexIndex(ixName, index.unique(), index.name()) :
+ buildSimpleIndex(ixName, index.unique(), index.fields()));
return 0;
}
});
@@ -876,7 +901,9 @@ public class DepotMarshaller
ctx.invoke(new Modifier() {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
- liaison.addIndexToTable(conn, getTableName(), colArr, fName, true);
+ if (!liaison.tableContainsIndex(conn, getTableName(), fName)) {
+ execute(conn, builder, buildSimpleIndex(fName, true, colArr));
+ }
return 0;
}
});
@@ -953,6 +980,59 @@ public class DepotMarshaller
return metaData;
}
+ private CreateIndexClause buildComplexIndex (String ixName, boolean unique, String localIxName)
+ throws SQLException
+ {
+ List> definition;
+ String methName = localIxName + "Definition";
+
+ Method method;
+ try {
+ method = _pClass.getMethod(methName);
+ } catch (NoSuchMethodException nsme) {
+ throw new IllegalArgumentException(
+ "Index flagged as complex, but no defining method '" + methName
+ + "' found.", nsme);
+ }
+
+ Object result;
+ try {
+ result = method.invoke(null);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "Error calling index definition method '" + methName + "'", e);
+ }
+
+ if (result instanceof SQLExpression) {
+ definition = new ArrayList>();
+ definition.add(new Tuple((SQLExpression)result, Order.ASC));
+
+ } else if (result instanceof List) {
+ @SuppressWarnings("unchecked")
+ List> def = (List>)result;
+ definition = def;
+
+ } else {
+ throw new IllegalArgumentException("Method '" + methName
+ + "' must return SQLExpression or " + "List>");
+ }
+
+ return new CreateIndexClause(_pClass, ixName, unique, definition);
+ }
+
+ private CreateIndexClause buildSimpleIndex (String ixName, boolean unique, String[] fields)
+ throws SQLException
+ {
+ List> definition = new ArrayList>();
+
+ for (String field : fields) {
+ ColumnExp column = new ColumnExp(_pClass, field);
+ definition.add(new Tuple(column, Order.ASC));
+ }
+
+ return new CreateIndexClause(_pClass, ixName, unique, definition);
+ }
+
// translate an array of field names to an array of column names
protected String[] fieldsToColumns (String[] fields)
{
diff --git a/src/java/com/samskivert/depot/impl/ExpressionVisitor.java b/src/java/com/samskivert/depot/impl/ExpressionVisitor.java
index 559e36a..9d329bf 100644
--- a/src/java/com/samskivert/depot/impl/ExpressionVisitor.java
+++ b/src/java/com/samskivert/depot/impl/ExpressionVisitor.java
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
-//
+//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -24,7 +24,9 @@ import com.samskivert.depot.Key;
import com.samskivert.depot.MultiKey;
import com.samskivert.depot.PersistentRecord;
+import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.DeleteClause;
+import com.samskivert.depot.clause.DropIndexClause;
import com.samskivert.depot.clause.FieldDefinition;
import com.samskivert.depot.clause.ForUpdate;
import com.samskivert.depot.clause.FromOverride;
@@ -81,4 +83,6 @@ public interface ExpressionVisitor
public void visit (UpdateClause extends PersistentRecord> updateClause);
public void visit (DeleteClause extends PersistentRecord> deleteClause);
public void visit (InsertClause extends PersistentRecord> insertClause);
+ public void visit (CreateIndexClause extends PersistentRecord> createIndexClause);
+ public void visit (DropIndexClause extends PersistentRecord> dropIndexClause);
}
diff --git a/src/java/com/samskivert/depot/impl/HSQLBuilder.java b/src/java/com/samskivert/depot/impl/HSQLBuilder.java
index b661a8b..03a227e 100644
--- a/src/java/com/samskivert/depot/impl/HSQLBuilder.java
+++ b/src/java/com/samskivert/depot/impl/HSQLBuilder.java
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
-//
+//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -20,6 +20,8 @@
package com.samskivert.depot.impl;
+import static com.samskivert.Log.log;
+
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
@@ -36,10 +38,13 @@ import com.google.common.collect.Lists;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.util.ArrayUtil;
+import com.samskivert.util.Tuple;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.annotation.GeneratedValue;
+import com.samskivert.depot.clause.CreateIndexClause;
+import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.EpochSeconds;
import com.samskivert.depot.expression.FunctionExp;
@@ -111,6 +116,19 @@ public class HSQLBuilder
_builder.append(", '1970-01-01')");
}
+ @Override
+ public void visit (CreateIndexClause extends PersistentRecord> createIndexClause)
+ {
+ for (Tuple field : createIndexClause.getFields()) {
+ if (!(field.left instanceof ColumnExp)) {
+ log.warning("This database can't handle complex indexes. Aborting creation.",
+ "ixName", createIndexClause.getName());
+ return;
+ }
+ }
+ super.visit(createIndexClause);
+ }
+
protected HBuildVisitor (DepotTypes types)
{
super(types);
@@ -184,7 +202,7 @@ public class HSQLBuilder
protected void maybeMutateForGeneratedValue (GeneratedValue genValue, ColumnDefinition column)
{
// HSQL's IDENTITY() implementation does not take the form of a type, as MySQL's
- // and PostgreSQL's conveniently shared SERIAL alias, nor as MySQL's original
+ // and PostgreSQL's conveniently shared SERIAL alias, nor as MySQL's original
// AUTO_INCREMENT modifier -- but as a default value, which admittedly makes sense
switch (genValue.strategy()) {
case AUTO:
diff --git a/src/java/com/samskivert/depot/impl/MySQLBuilder.java b/src/java/com/samskivert/depot/impl/MySQLBuilder.java
index eb4b05e..649de98 100644
--- a/src/java/com/samskivert/depot/impl/MySQLBuilder.java
+++ b/src/java/com/samskivert/depot/impl/MySQLBuilder.java
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
-//
+//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -32,13 +32,18 @@ import java.sql.Timestamp;
import java.util.Set;
import com.samskivert.jdbc.JDBCUtil;
+import com.samskivert.util.Tuple;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex;
+import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.DeleteClause;
+import com.samskivert.depot.clause.DropIndexClause;
+import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.EpochSeconds;
+import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FieldMarshaller.BooleanMarshaller;
import com.samskivert.depot.impl.FieldMarshaller.ByteArrayMarshaller;
import com.samskivert.depot.impl.FieldMarshaller.ByteEnumMarshaller;
@@ -97,6 +102,30 @@ public class MySQLBuilder
_builder.append(")");
}
+ @Override
+ public void visit (CreateIndexClause extends PersistentRecord> createIndexClause)
+ {
+ for (Tuple field : createIndexClause.getFields()) {
+ if (!(field.left instanceof ColumnExp)) {
+ log.warning("This database can't handle complex indexes. Aborting creation.",
+ "ixName", createIndexClause.getName());
+ return;
+ }
+ }
+ super.visit(createIndexClause);
+ }
+
+ @Override
+ public void visit (DropIndexClause extends PersistentRecord> dropIndexClause)
+ {
+ // MySQL's indexes are scoped on the table, not on the database, and the
+ // SQL syntax reflects it: DROP INDEX fooIx on fooTable
+ _builder.append("drop index ");
+ appendIdentifier(dropIndexClause.getName());
+ _builder.append(" on ");
+ appendTableName(dropIndexClause.getPersistentClass());
+ }
+
protected MSBuildVisitor (DepotTypes types)
{
super(types);
diff --git a/src/java/com/samskivert/depot/impl/SQLBuilder.java b/src/java/com/samskivert/depot/impl/SQLBuilder.java
index b55134d..9beeda3 100644
--- a/src/java/com/samskivert/depot/impl/SQLBuilder.java
+++ b/src/java/com/samskivert/depot/impl/SQLBuilder.java
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
-//
+//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
@@ -53,15 +53,16 @@ public abstract class SQLBuilder
/**
* Construct an entirely new SQL query relative to our configured {@link DepotTypes} data.
- * This method may be called multiple times, each time beginning a new query, and should be
- * followed up by a call to {@link #prepare(Connection)} which creates, configures and
- * returns the actual {@link PreparedStatement} to execute.
+ * This method may be called multiple times, each time beginning a new query, and will return
+ * true if SQL was generated. If so, a call to {@link #prepare(Connection)} should follow,
+ * which creates, configures and returns the actual {@link PreparedStatement} to execute.
*/
- public void newQuery (QueryClause clause)
+ public boolean newQuery (QueryClause clause)
{
_clause = clause;
_buildVisitor = getBuildVisitor();
_clause.accept(_buildVisitor);
+ return _buildVisitor.getQuery().trim().length() > 0;
}
/**