El Giganto Depot database dialects support from Zell. PostgresSQL here we

come. Automatic schema migration is currently disabled and remains to be
dialectified.
This commit is contained in:
Michael Bayne
2007-07-25 20:53:40 +00:00
parent f0bae6ff8d
commit 9f544c9174
51 changed files with 3143 additions and 1796 deletions
@@ -0,0 +1,269 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot;
import java.sql.PreparedStatement;
import java.util.Map;
import com.samskivert.jdbc.depot.Key.WhereCondition;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.ForUpdate;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.GroupBy;
import com.samskivert.jdbc.depot.clause.InsertClause;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.Limit;
import com.samskivert.jdbc.depot.clause.OrderBy;
import com.samskivert.jdbc.depot.clause.SelectClause;
import com.samskivert.jdbc.depot.clause.UpdateClause;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.FunctionExp;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
import com.samskivert.jdbc.depot.operator.Conditionals.In;
import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
import com.samskivert.jdbc.depot.operator.Conditionals.Match;
import com.samskivert.jdbc.depot.operator.Logic.Not;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator;
/**
* Implements the base functionality of the argument-binding pass of {@link SQLBuilder}. Dialectal
* subclasses of this should be created and returned from {@link SQLBuilder#getBindVisitor()}.
*
* This class is intimately paired with {#link BuildVisitor}.
*/
public class BindVisitor implements ExpressionVisitor
{
public void visit (FromOverride override)
throws Exception
{
}
public void visit (FieldOverride fieldOverride)
throws Exception
{
}
public void visit (WhereCondition<? extends PersistentRecord> whereCondition)
throws Exception
{
Comparable[] values = whereCondition.getValues();
for (int ii = 0; ii < values.length; ii ++) {
if (values[ii] != null) {
_stmt.setObject(_argIdx ++, values[ii]);
}
}
}
public void visit (Key key)
throws Exception
{
key.condition.accept(this);
}
public void visit (MultiKey<? extends PersistentRecord> key)
throws Exception
{
for (Map.Entry entry : key.getSingleFieldsMap().entrySet()) {
if (entry.getValue() != null) {
_stmt.setObject(_argIdx ++, entry.getValue());
}
}
Comparable[] values = key.getMultiValues();
for (int ii = 0; ii < values.length; ii++) {
_stmt.setObject(_argIdx ++, values[ii]);
}
}
public void visit (FunctionExp functionExp)
throws Exception
{
visit(functionExp.getArguments());
}
public void visit (MultiOperator multiOperator)
throws Exception
{
visit(multiOperator.getConditions());
}
public void visit (BinaryOperator binaryOperator) throws Exception
{
binaryOperator.getLeftHandSide().accept(this);
binaryOperator.getRightHandSide().accept(this);
}
public void visit (IsNull isNull) throws Exception
{
}
public void visit (In in) throws Exception
{
Comparable[] values = in.getValues();
for (int ii = 0; ii < values.length; ii++) {
_stmt.setObject(_argIdx ++, values[ii]);
}
}
public void visit (Match match) throws Exception
{
// we never get here
}
public void visit (ColumnExp columnExp) throws Exception
{
// no arguments
}
public void visit (Not not) throws Exception
{
not.getCondition().accept(this);
}
public void visit (GroupBy groupBy) throws Exception
{
visit(groupBy.getValues());
}
public void visit (ForUpdate forUpdate) throws Exception
{
// do nothing
}
public void visit (OrderBy orderBy) throws Exception
{
visit(orderBy.getValues());
}
public void visit (Where where) throws Exception
{
where.getCondition().accept(this);
}
public void visit (Join join) throws Exception
{
join.getJoinCondition().accept(this);
}
public void visit (Limit limit) throws Exception
{
_stmt.setObject(_argIdx++, limit.getCount());
_stmt.setObject(_argIdx++, limit.getOffset());
}
public void visit (LiteralExp literalExp) throws Exception
{
// do nothing
}
public void visit (ValueExp valueExp) throws Exception
{
// workaround for postgres bug, fixed in next release:
// http://archives.postgresql.org/pgsql-jdbc/2007-06/msg00080.php
if (valueExp.getValue() instanceof Byte) {
_stmt.setByte(_argIdx ++, (Byte) valueExp.getValue());
} else {
_stmt.setObject(_argIdx ++, valueExp.getValue());
}
}
public void visit (SelectClause<? extends PersistentRecord> selectClause)
throws Exception
{
for (Join clause : selectClause.getJoinClauses()) {
clause.accept(this);
}
if (selectClause.getWhereClause() != null) {
selectClause.getWhereClause().accept(this);
}
if (selectClause.getGroupBy() != null) {
selectClause.getGroupBy().accept(this);
}
if (selectClause.getOrderBy() != null) {
selectClause.getOrderBy().accept(this);
}
if (selectClause.getLimit() != null) {
selectClause.getLimit().accept(this);
}
if (selectClause.getForUpdate() != null) {
selectClause.getForUpdate().accept(this);
}
}
public void visit (UpdateClause<? extends PersistentRecord> updateClause) throws Exception
{
DepotMarshaller marsh = _types.getMarshaller(updateClause.getPersistentClass());
// bind the update arguments
String[] fields = updateClause.getFields();
Object pojo = updateClause.getPojo();
if (pojo != null) {
for (int ii = 0; ii < fields.length; ii ++) {
marsh.getFieldMarshaller(fields[ii]).readFromObject(pojo, _stmt, _argIdx++);
}
} else {
visit(updateClause.getValues());
}
updateClause.getWhereClause().accept(this);
}
public void visit (DeleteClause<? extends PersistentRecord> deleteClause) throws Exception
{
deleteClause.getWhereClause().accept(this);
}
public void visit (InsertClause<? extends PersistentRecord> insertClause) throws Exception
{
DepotMarshaller marsh = _types.getMarshaller(insertClause.getPersistentClass());
Object pojo = insertClause.getPojo();
String ixField = insertClause.getIndexField();
for (String field : marsh.getColumnFieldNames()) {
if (ixField == null || !ixField.equals(field)) {
marsh.getFieldMarshaller(field).readFromObject(pojo, _stmt, _argIdx ++);
}
}
}
protected BindVisitor (DepotTypes types, PreparedStatement stmt)
{
_types = types;
_stmt = stmt;
_argIdx = 1;
}
protected void visit (SQLExpression[] expressions)
throws Exception
{
for (int ii = 0; ii < expressions.length; ii ++) {
expressions[ii].accept(this);
}
}
protected DepotTypes _types;
protected PreparedStatement _stmt;
protected int _argIdx;
}
@@ -0,0 +1,516 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import com.samskivert.jdbc.depot.Key.WhereCondition;
import com.samskivert.jdbc.depot.annotation.Computed;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.ForUpdate;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.GroupBy;
import com.samskivert.jdbc.depot.clause.InsertClause;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.Limit;
import com.samskivert.jdbc.depot.clause.OrderBy;
import com.samskivert.jdbc.depot.clause.SelectClause;
import com.samskivert.jdbc.depot.clause.UpdateClause;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.FunctionExp;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
import com.samskivert.jdbc.depot.operator.Conditionals.In;
import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
import com.samskivert.jdbc.depot.operator.Conditionals.Match;
import com.samskivert.jdbc.depot.operator.Logic.Not;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator;
/**
* Implements the base functionality of the SQL-building pass of {@link SQLBuilder}. Dialectal
* subclasses of this should be created and returned from {@link SQLBuilder#getBuildVisitor()}.
*
* This class is intimately paired with {#link BindVisitor}.
*/
public abstract class BuildVisitor implements ExpressionVisitor
{
public String getQuery ()
{
return _builder.toString();
}
public void visit (FromOverride override)
throws Exception
{
_builder.append(" from " );
List<Class<? extends PersistentRecord>> from = override.getFromClasses();
for (int ii = 0; ii < from.size(); ii++) {
if (ii > 0) {
_builder.append(", ");
}
appendTableName(from.get(ii));
_builder.append(" as ");
appendTableAbbreviation(from.get(ii));
}
}
public void visit (FieldOverride fieldOverride)
throws Exception
{
fieldOverride.getOverride().accept(this);
_builder.append(" as ");
appendField(fieldOverride.getField());
}
public void visit (WhereCondition<? extends PersistentRecord> whereCondition)
throws Exception
{
String[] keyFields = Key.getKeyFields(whereCondition.getPersistentClass());
Comparable[] values = whereCondition.getValues();
for (int ii = 0; ii < keyFields.length; ii ++) {
if (ii > 0) {
_builder.append(" and ");
}
appendColumn(whereCondition.getPersistentClass(), keyFields[ii]);
_builder.append(values[ii] == null ? " is null " : " = ? ");
}
}
public void visit (Key key)
throws Exception
{
_builder.append(" where ");
key.condition.accept(this);
}
public void visit (MultiKey<? extends PersistentRecord> key)
throws Exception
{
_builder.append(" where ");
boolean first = true;
for (Map.Entry<String, Comparable> entry : key.getSingleFieldsMap().entrySet()) {
if (first) {
first = false;
} else {
_builder.append(" and ");
}
appendColumn(key.getPersistentClass(), entry.getKey());
_builder.append(entry.getValue() == null ? " is null " : " = ? ");
}
if (!first) {
_builder.append(" and ");
}
appendColumn(key.getPersistentClass(), key.getMultiField());
_builder.append(" in (");
Comparable[] values = key.getMultiValues();
for (int ii = 0; ii < values.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
_builder.append("?");
}
_builder.append(")");
}
public void visit (FunctionExp functionExp)
throws Exception
{
_builder.append(functionExp.getFunction());
_builder.append("(");
SQLExpression[] arguments = functionExp.getArguments();
for (int ii = 0; ii < arguments.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
arguments[ii].accept(this);
}
_builder.append(")");
}
public void visit (MultiOperator multiOperator)
throws Exception
{
SQLExpression[] conditions = multiOperator.getConditions();
for (int ii = 0; ii < conditions.length; ii++) {
if (ii > 0) {
_builder.append(" ").append(multiOperator.operator()).append(" ");
}
_builder.append("(");
conditions[ii].accept(this);
_builder.append(")");
}
}
public void visit (BinaryOperator binaryOperator)
throws Exception
{
binaryOperator.getLeftHandSide().accept(this);
_builder.append(binaryOperator.operator());
binaryOperator.getRightHandSide().accept(this);
}
public void visit (IsNull isNull)
throws Exception
{
isNull.getColumn().accept(this);
_builder.append(" is null");
}
public void visit (In in)
throws Exception
{
in.getColumn().accept(this);
_builder.append(" in (");
Comparable[] values = in.getValues();
for (int ii = 0; ii < values.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
_builder.append("?");
}
_builder.append(")");
}
public void visit (Match match)
throws Exception
{
throw new IllegalArgumentException("Match() is MySQL-specific.");
}
public void visit (ColumnExp columnExp)
throws Exception
{
appendTableAbbreviation(columnExp.getPersistentClass());
_builder.append(".");
appendColumn(columnExp.getPersistentClass(), columnExp.getField());
}
public void visit (Not not)
throws Exception
{
_builder.append(" not (");
not.getCondition().accept(this);
_builder.append(")");
}
public void visit (GroupBy groupBy)
throws Exception
{
_builder.append(" group by ");
SQLExpression[] values = groupBy.getValues();
for (int ii = 0; ii < values.length; ii++) {
if (ii > 0) {
_builder.append(", ");
}
values[ii].accept(this);
}
}
public void visit (ForUpdate forUpdate)
throws Exception
{
_builder.append(" for update ");
}
public void visit (OrderBy orderBy)
throws Exception
{
_builder.append(" order by ");
SQLExpression[] values = orderBy.getValues();
OrderBy.Order[] orders = orderBy.getOrders();
for (int ii = 0; ii < values.length; ii++) {
if (ii > 0) {
_builder.append(", ");
}
values[ii].accept(this);
_builder.append(" ").append(orders[ii]);
}
}
public void visit (Where where)
throws Exception
{
_builder.append(" where ");
where.getCondition().accept(this);
}
public void visit (Join join)
throws Exception
{
switch (join.getType()) {
case INNER:
_builder.append(" inner join " );
break;
case LEFT_OUTER:
_builder.append(" left outer join " );
break;
case RIGHT_OUTER:
_builder.append(" right outer join " );
break;
}
appendTableName(join.getJoinClass());
_builder.append(" as ");
appendTableAbbreviation(join.getJoinClass());
_builder.append(" on ");
join.getJoinCondition().accept(this);
}
public void visit (Limit limit)
throws Exception
{
_builder.append(" limit ? offset ? ");
}
public void visit (LiteralExp literalExp)
throws Exception
{
_builder.append(literalExp.getText());
}
public void visit (ValueExp valueExp)
throws Exception
{
_builder.append("?");
}
public void visit (SelectClause<? extends PersistentRecord> selectClause)
throws Exception
{
Class<? extends PersistentRecord> pClass = selectClause.getPersistentClass();
boolean isInner = _innerClause;
_innerClause = true;
if (isInner) {
_builder.append("(");
}
_builder.append("select ");
Computed entityComputed = pClass.getAnnotation(Computed.class);
// iterate over the fields we're filling in and figure out whence each one comes
boolean skip = true;
for (String field : selectClause.getFields()) {
if (!skip) {
_builder.append(", ");
}
skip = false;
// first, see if there's a field override
FieldOverride override = selectClause.lookupOverride(field);
if (override != null) {
override.accept(this);
continue;
}
// figure out the class we're selecting from unless we're otherwise overriden:
// for a concrete record, simply use the corresponding table; for a computed one,
// default to the shadowed concrete record, or null if there isn't one
Class<? extends PersistentRecord> tableClass;
if (entityComputed == null) {
tableClass = pClass;
} else if (!PersistentRecord.class.equals(entityComputed.shadowOf())) {
tableClass = entityComputed.shadowOf();
} else {
tableClass = null;
}
// handle the field-level @Computed annotation, if there is one
FieldMarshaller fm = _types.getMarshaller(pClass).getFieldMarshaller(field);
if (fm == null) {
throw new IllegalArgumentException(
"could not find marshaller for field: " + field);
}
Computed fieldComputed = fm.getComputed();
if (fieldComputed != null) {
// check if the computed field has a literal SQL definition
if (fieldComputed.fieldDefinition().length() > 0) {
_builder.append(fieldComputed.fieldDefinition()).append(" as ");
appendField(field);
continue;
}
// or if we can simply ignore the field
if (!fieldComputed.required()) {
skip = true;
continue;
}
// else see if there's an overriding shadowOf definition
if (fieldComputed.shadowOf() != null) {
tableClass = fieldComputed.shadowOf();
}
}
// if we get this far we hopefully have a table to select from
if (tableClass != null) {
appendTableAbbreviation(tableClass);
_builder.append(".");
appendColumn(tableClass, field);
continue;
}
// else owie
throw new IllegalArgumentException(
"Persistent field has no definition [class=" +
selectClause.getPersistentClass() + ", field=" + field + "]");
}
if (selectClause.getFromOverride() != null) {
selectClause.getFromOverride().accept(this);
} else if (_types.getTableName(pClass) != null) {
_builder.append(" from ");
appendTableName(pClass);
_builder.append(" as ");
appendTableAbbreviation(pClass);
} else {
throw new SQLException("Query on @Computed entity with no FromOverrideClause.");
}
for (Join clause : selectClause.getJoinClauses()) {
clause.accept(this);
}
if (selectClause.getWhereClause() != null) {
selectClause.getWhereClause().accept(this);
}
if (selectClause.getGroupBy() != null) {
selectClause.getGroupBy().accept(this);
}
if (selectClause.getOrderBy() != null) {
selectClause.getOrderBy().accept(this);
}
if (selectClause.getLimit() != null) {
selectClause.getLimit().accept(this);
}
if (selectClause.getForUpdate() != null) {
selectClause.getForUpdate().accept(this);
}
if (isInner) {
_builder.append(")");
}
}
public void visit (UpdateClause<? extends PersistentRecord> updateClause)
throws Exception
{
Class<? extends PersistentRecord> pClass = updateClause.getPersistentClass();
_innerClause = true;
_builder.append("update ");
appendTableName(pClass);
_builder.append(" as ");
appendTableAbbreviation(pClass);
_builder.append(" set ");
String[] fields = updateClause.getFields();
Object pojo = updateClause.getPojo();
SQLExpression[] values = updateClause.getValues();
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
appendColumn(pClass, fields[ii]);
_builder.append(" = ");
if (pojo != null) {
_builder.append("?");
} else {
values[ii].accept(this);
}
}
updateClause.getWhereClause().accept(this);
}
public void visit (DeleteClause<? extends PersistentRecord> deleteClause)
throws Exception
{
_builder.append("delete from ");
appendTableName(deleteClause.getPersistentClass());
_builder.append(" ");
deleteClause.getWhereClause().accept(this);
}
public void visit (InsertClause<? extends PersistentRecord> insertClause)
throws Exception
{
Class<? extends PersistentRecord> pClass = insertClause.getPersistentClass();
DepotMarshaller marsh = _types.getMarshaller(pClass);
_innerClause = true;
String[] fields = marsh.getColumnFieldNames();
_builder.append("insert into ");
appendTableName(insertClause.getPersistentClass());
_builder.append(" (");
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
appendColumn(pClass, fields[ii]);
}
_builder.append(") values(");
String ixField = insertClause.getIndexField();
for (int ii = 0; ii < fields.length; ii++) {
if (ii > 0) {
_builder.append(", ");
}
if (ixField != null && ixField.equals(fields[ii])) {
_builder.append("DEFAULT");
} else {
_builder.append("?");
}
}
_builder.append(")");
}
protected abstract void appendTableName (Class<? extends PersistentRecord> type);
protected abstract void appendTableAbbreviation (Class<? extends PersistentRecord> type);
protected abstract void appendColumn (Class<? extends PersistentRecord> type, String field);
protected abstract void appendField (String field);
protected BuildVisitor (DepotTypes types)
{
_types = types;
_builder = new StringBuilder();
_innerClause = false;
}
protected DepotTypes _types;
/** A StringBuilder to hold the constructed SQL. */
protected StringBuilder _builder;
/** A flag that's set to true for inner SELECT's */
protected boolean _innerClause;
}
@@ -48,11 +48,8 @@ import com.samskivert.jdbc.depot.annotation.TableGenerator;
import com.samskivert.jdbc.depot.annotation.Transient;
import com.samskivert.jdbc.depot.annotation.UniqueConstraint;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
import static com.samskivert.jdbc.depot.Log.log;
@@ -75,7 +72,6 @@ public class DepotMarshaller<T extends PersistentRecord>
_pclass = pclass;
Entity entity = pclass.getAnnotation(Entity.class);
Table table = null;
// see if this is a computed entity
Computed computed = pclass.getAnnotation(Computed.class);
@@ -89,11 +85,7 @@ public class DepotMarshaller<T extends PersistentRecord>
if (entity.name().length() > 0) {
_tableName = entity.name();
}
_postamble = entity.postamble();
}
// check for a Table annotation, for unique constraints
table = pclass.getAnnotation(Table.class);
}
// if the entity defines a new TableGenerator, map that in our static table as those are
@@ -127,8 +119,8 @@ public class DepotMarshaller<T extends PersistentRecord>
}
FieldMarshaller fm = FieldMarshaller.createMarshaller(field);
_fields.put(fm.getColumnName(), fm);
fields.add(fm.getColumnName());
_fields.put(field.getName(), fm);
fields.add(field.getName());
// check to see if this is our primary key
if (field.getAnnotation(Id.class) != null) {
@@ -181,7 +173,8 @@ public class DepotMarshaller<T extends PersistentRecord>
switch(gv.strategy()) {
case AUTO:
case IDENTITY:
_keyGenerator = new IdentityKeyGenerator();
_keyGenerator = new IdentityKeyGenerator(
gv, getTableName(), keyField.getColumnName());
break;
case TABLE:
@@ -191,7 +184,8 @@ public class DepotMarshaller<T extends PersistentRecord>
throw new IllegalArgumentException(
"Unknown generator [generator=" + name + "]");
}
_keyGenerator = new TableKeyGenerator(generator);
_keyGenerator = new TableKeyGenerator(
generator, gv, getTableName(), keyField.getColumnName());
break;
}
}
@@ -199,54 +193,6 @@ public class DepotMarshaller<T extends PersistentRecord>
// generate our full list of fields/columns for use in queries
_allFields = fields.toArray(new String[fields.size()]);
// if we're a computed entity, stop here
if (_tableName == null) {
return;
}
// figure out the list of fields that correspond to actual table columns and generate the
// SQL used to create and migrate our table (unless we're a computed entity)
_columnFields = new String[_allFields.length];
int jj = 0;
for (int ii = 0; ii < _allFields.length; ii++) {
// include all persistent non-computed fields
String colDef = _fields.get(_allFields[ii]).getColumnDefinition();
if (colDef != null) {
_columnFields[jj] = _allFields[ii];
_declarations.add(colDef);
jj ++;
}
}
_columnFields = ArrayUtil.splice(_columnFields, jj);
// determine whether we have any index definitions
if (entity != null) {
for (Index index : entity.indices()) {
// TODO: delegate this to a database specific SQL generator
_declarations.add(index.type() + " index " + index.name() +
" (" + StringUtil.join(index.columns(), ", ") + ")");
}
}
// add any unique constraints given
if (table != null) {
for (UniqueConstraint constraint : table.uniqueConstraints()) {
_declarations.add(
"UNIQUE (" + StringUtil.join(constraint.columnNames(), ", ") + ")");
}
}
// add the primary key, if we have one
if (hasPrimaryKey()) {
_declarations.add("PRIMARY KEY (" + getJoinedKeyColumns() + ")");
}
// if we did not find a schema version field, complain
if (_schemaVersion < 0) {
log.warning("Unable to read " + _pclass.getName() + "." + SCHEMA_VERSION_FIELD +
". Schema migration disabled.");
}
}
/**
@@ -266,6 +212,14 @@ public class DepotMarshaller<T extends PersistentRecord>
return _allFields;
}
/**
* Returns all the persistent fields that correspond to concrete table columns.
*/
public String[] getColumnFieldNames ()
{
return _columnFields;
}
/**
* Returns the {@link FieldMarshaller} for a named field on our persistent class.
*/
@@ -283,14 +237,23 @@ public class DepotMarshaller<T extends PersistentRecord>
}
/**
* Return the names of the columns that constitute the primary key of our associated
* persistent record.
* Returns the {@link KeyGenerator} used to generate primary keys for this persistent object,
* or null if it does not use a key generator.
*/
public String[] getPrimaryKeyColumns ()
public KeyGenerator getKeyGenerator ()
{
return _keyGenerator;
}
/**
* Return the names of the columns that constitute the primary key of our associated persistent
* record.
*/
public String[] getPrimaryKeyFields ()
{
String[] pkcols = new String[_pkColumns.size()];
for (int ii = 0; ii < pkcols.length; ii ++) {
pkcols[ii] = _pkColumns.get(ii).getColumnName();
pkcols[ii] = _pkColumns.get(ii).getField().getName();
}
return pkcols;
}
@@ -359,11 +322,11 @@ public class DepotMarshaller<T extends PersistentRecord>
throw new UnsupportedOperationException(
getClass().getName() + " does not define a primary key");
}
String[] columns = new String[_pkColumns.size()];
String[] fields = new String[_pkColumns.size()];
for (int ii = 0; ii < _pkColumns.size(); ii++) {
columns[ii] = _pkColumns.get(ii).getColumnName();
fields[ii] = _pkColumns.get(ii).getField().getName();
}
return new Key<T>(_pclass, columns, values);
return new Key<T>(_pclass, fields, values);
}
/**
@@ -416,7 +379,7 @@ public class DepotMarshaller<T extends PersistentRecord>
// then create and populate the persistent object
T po = _pclass.newInstance();
for (FieldMarshaller fm : _fields.values()) {
if (!fields.contains(fm.getField().getName())) {
if (!fields.contains(fm.getColumnName())) {
// this field was not in the result set, make sure that's OK
if (fm.getComputed() != null && !fm.getComputed().required()) {
continue;
@@ -438,46 +401,6 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
/**
* Creates a statement that will insert the supplied persistent object into the database.
*/
public PreparedStatement createInsert (Connection conn, Object po)
throws SQLException
{
requireNotComputed("insert rows into");
try {
StringBuilder insert = new StringBuilder();
insert.append("insert into ").append(getTableName());
insert.append(" (").append(StringUtil.join(_columnFields, ","));
insert.append(")").append(" values(");
for (int ii = 0; ii < _columnFields.length; ii++) {
if (ii > 0) {
insert.append(", ");
}
insert.append("?");
}
insert.append(")");
// TODO: handle primary key, nullable fields specially?
PreparedStatement pstmt = conn.prepareStatement(insert.toString());
int idx = 0;
for (String field : _columnFields) {
_fields.get(field).readFromObject(po, pstmt, ++idx);
}
return pstmt;
} catch (SQLException sqe) {
// pass this on through
throw sqe;
} catch (Exception e) {
String errmsg = "Failed to marshall persistent object [pclass=" +
_pclass.getName() + "]";
throw (SQLException)new SQLException(errmsg).initCause(e);
}
}
/**
* Fills in the primary key just assigned to the supplied persistence object by the execution
* of the results of {@link #createInsert}.
@@ -485,7 +408,8 @@ public class DepotMarshaller<T extends PersistentRecord>
* @return the newly assigned primary key or null if the object does not use primary keys or
* this is not the right time to assign the key.
*/
public Key assignPrimaryKey (Connection conn, Object po, boolean postFactum)
public Key assignPrimaryKey (
Connection conn, DatabaseLiaison liaison, Object po, boolean postFactum)
throws SQLException
{
// if we have no primary key or no generator, then we're done
@@ -499,7 +423,7 @@ public class DepotMarshaller<T extends PersistentRecord>
}
try {
int nextValue = _keyGenerator.nextGeneratedValue(conn);
int nextValue = _keyGenerator.nextGeneratedValue(conn, liaison);
_pkColumns.get(0).getField().set(po, nextValue);
return makePrimaryKey(nextValue);
} catch (Exception e) {
@@ -508,131 +432,6 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
/**
* Creates a statement that will update the supplied persistent object using the supplied key.
*/
public PreparedStatement createUpdate (Connection conn, Object po, Where key)
throws SQLException
{
return createUpdate(conn, po, key, _columnFields);
}
/**
* Creates a statement that will update the supplied persistent object
* using the supplied key.
*/
public PreparedStatement createUpdate (
Connection conn, Object po, Where key, String[] modifiedFields)
throws SQLException
{
requireNotComputed("update rows in");
StringBuilder update = new StringBuilder();
update.append("update ").append(getTableName()).append(" set ");
int idx = 0;
for (String field : modifiedFields) {
if (idx++ > 0) {
update.append(", ");
}
update.append(field).append(" = ?");
}
key.appendClause(null, update);
try {
PreparedStatement pstmt = conn.prepareStatement(update.toString());
idx = 0;
// bind the update arguments
for (String field : modifiedFields) {
_fields.get(field).readFromObject(po, pstmt, ++idx);
}
// now bind the key arguments
key.bindClauseArguments(pstmt, ++idx);
return pstmt;
} catch (SQLException sqe) {
// pass this on through
throw sqe;
} catch (Exception e) {
String errmsg = "Failed to marshall persistent object " +
"[pclass=" + _pclass.getName() + "]";
throw (SQLException)new SQLException(errmsg).initCause(e);
}
}
/**
* Creates a statement that will update the specified set of fields for all persistent objects
* that match the supplied key.
*/
public PreparedStatement createPartialUpdate (
Connection conn, Where key, String[] modifiedFields, Object[] modifiedValues)
throws SQLException
{
requireNotComputed("update rows in");
StringBuilder update = new StringBuilder();
update.append("update ").append(getTableName()).append(" set ");
int idx = 0;
for (String field : modifiedFields) {
if (idx++ > 0) {
update.append(", ");
}
update.append(field).append(" = ?");
}
key.appendClause(null, update);
PreparedStatement pstmt = conn.prepareStatement(update.toString());
idx = 0;
// bind the update arguments
for (Object value : modifiedValues) {
// TODO: use the field marshaller?
pstmt.setObject(++idx, value);
}
// now bind the key arguments
key.bindClauseArguments(pstmt, ++idx);
return pstmt;
}
/**
* Creates a statement that will delete all rows matching the supplied key.
*/
public PreparedStatement createDelete (Connection conn, Where key)
throws SQLException
{
requireNotComputed("delete rows from");
StringBuilder query = new StringBuilder("delete from " + getTableName());
key.appendClause(null, query);
PreparedStatement pstmt = conn.prepareStatement(query.toString());
key.bindClauseArguments(pstmt, 1);
return pstmt;
}
/**
* Creates a statement that will update the specified set of fields, using the supplied literal
* SQL values, for all persistent objects that match the supplied key.
*/
public PreparedStatement createLiteralUpdate (
Connection conn, Where key, String[] modifiedFields, Object[] modifiedValues)
throws SQLException
{
requireNotComputed("update rows in");
StringBuilder update = new StringBuilder();
update.append("update ").append(getTableName()).append(" set ");
for (int ii = 0; ii < modifiedFields.length; ii++) {
if (ii > 0) {
update.append(", ");
}
update.append(modifiedFields[ii]).append(" = ");
update.append(modifiedValues[ii]);
}
key.appendClause(null, update);
PreparedStatement pstmt = conn.prepareStatement(update.toString());
key.bindClauseArguments(pstmt, 1);
return pstmt;
}
/**
* This is called by the persistence context to register a migration for the entity managed by
@@ -652,47 +451,122 @@ public class DepotMarshaller<T extends PersistentRecord>
protected void init (PersistenceContext ctx)
throws PersistenceException
{
SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.TRIVIAL);
if (_initialized) { // sanity check
throw new IllegalStateException(
"Cannot re-initialize marshaller [type=" + _pclass + "].");
}
_initialized = true;
// perform the context-sensitive initialization of the field marshallers
for (FieldMarshaller fm : _fields.values()) {
fm.init(builder);
}
// figure out the list of fields that correspond to actual table columns and generate the
// SQL used to create and migrate our table (unless we're a computed entity)
_columnFields = new String[_allFields.length];
String[] declarations = new String[_allFields.length];
int jj = 0;
for (int ii = 0; ii < _allFields.length; ii++) {
@SuppressWarnings("unchecked") FieldMarshaller<T> fm = _fields.get(_allFields[ii]);
// include all persistent non-computed fields
String colDef = fm.getColumnDefinition();
if (colDef != null) {
_columnFields[jj] = _allFields[ii];
declarations[jj] = colDef;
jj ++;
}
}
_columnFields = ArrayUtil.splice(_columnFields, jj);
declarations = ArrayUtil.splice(declarations, jj);
// if we have no table (i.e. we're a computed entity), we have nothing to create
if (getTableName() == null) {
return;
}
// add any additional unique constraints
String[][] uniqueConstraintColumns = null;
Table table = _pclass.getAnnotation(Table.class);
if (table != null) {
UniqueConstraint[] uCons = table.uniqueConstraints();
uniqueConstraintColumns = new String[uCons.length][];
for (int kk = 0; kk < uCons.length; kk ++) {
String[] columns = uCons[kk].columnNames();
for (int ii = 0; ii < columns.length; ii ++) {
FieldMarshaller fm = getFieldMarshaller(columns[ii]);
if (fm == null) {
throw new IllegalArgumentException(
"Unknown field in @UniqueConstraint: " + columns[ii]);
}
columns[ii] = fm.getColumnName();
}
uniqueConstraintColumns[kk] = columns;
}
}
// if we did not find a schema version field, complain
if (_schemaVersion < 0) {
log.warning("Unable to read " + _pclass.getName() + "." + SCHEMA_VERSION_FIELD +
". Schema migration disabled.");
}
// check to see if our schema version table exists, create it if not
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
JDBCUtil.createTableIfMissing(
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.createTableIfMissing(
conn, SCHEMA_VERSION_TABLE,
new String[] { "persistentClass VARCHAR(255) NOT NULL",
"version INTEGER NOT NULL" }, "");
new String[] { "persistentClass", "version" },
new String[] { "VARCHAR(255) NOT NULL", "INTEGER NOT NULL" },
null,
new String[] { "persistentClass" });
return 0;
}
});
// now create the table for our persistent class if it does not exist
final String[] fDeclarations = declarations;
final String[][] fUniqueConstraintColumns = uniqueConstraintColumns;
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
if (!JDBCUtil.tableExists(conn, getTableName())) {
log.info("Creating table " + getTableName() + " (" + _declarations + ") " +
_postamble);
String[] definition = _declarations.toArray(new String[_declarations.size()]);
JDBCUtil.createTableIfMissing(conn, getTableName(), definition, _postamble);
updateVersion(conn, _schemaVersion);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
String[] columns = new String[_columnFields.length];
for (int ii = 0; ii < columns.length; ii ++) {
columns[ii] = _fields.get(_columnFields[ii]).getColumnName();
}
String[] primaryKeyColumns = null;
if (_pkColumns != null) {
primaryKeyColumns = new String[_pkColumns.size()];
for (int ii = 0; ii < primaryKeyColumns.length; ii ++) {
primaryKeyColumns[ii] = _pkColumns.get(ii).getColumnName();
}
}
liaison.createTableIfMissing(conn, getTableName(), columns, fDeclarations,
fUniqueConstraintColumns, primaryKeyColumns);
updateVersion(conn, liaison, _schemaVersion);
return 0;
}
});
// ensure that all indices are created
final Entity entity = _pclass.getAnnotation(Entity.class);
if (entity != null && entity.indices().length > 0) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
for (Index idx : entity.indices()) {
liaison.addIndexToTable(conn, getTableName(), idx.columns(), idx.name());
}
return entity.indices().length;
}
});
}
// if we have a key generator, initialize that too
if (_keyGenerator != null) {
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
_keyGenerator.init(conn);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
_keyGenerator.init(conn, liaison);
return 0;
}
});
@@ -706,9 +580,12 @@ public class DepotMarshaller<T extends PersistentRecord>
// make sure the versions match
int currentVersion = ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
String query = "select version from " + SCHEMA_VERSION_TABLE +
" where persistentClass = '" + getTableName() + "'";
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
String query =
" select " + liaison.columnSQL("version") +
" from " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" where " + liaison.columnSQL("persistentClass") +
" = '" + getTableName() + "'";
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery(query);
@@ -723,6 +600,12 @@ public class DepotMarshaller<T extends PersistentRecord>
return;
}
if (true) {
log.warning("Automatic schema migration is temporarily disabled.");
verifySchemasMatch(ctx);
return;
}
// otherwise try to migrate the schema
log.info("Migrating " + getTableName() + " from " +
currentVersion + " to " + _schemaVersion + "...");
@@ -736,35 +619,29 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
// enumerate all of the columns now that we've run our pre-migrations
Set<String> columns = new HashSet<String>();
Map<String, Set<String>> indexColumns = new HashMap<String, Set<String>>();
loadMetaData(ctx, columns, indexColumns);
// enumerate all columns and indexes now that we've run our pre-migrations
TableMetaData metaData = loadMetaData(ctx);
// this is a little silly, but we need a copy for name disambiguation later
Set<String> indicesCopy = new HashSet<String>(indexColumns.keySet());
Set<String> indicesCopy = new HashSet<String>(metaData.indexColumns.keySet());
// add any missing columns
for (String fname : _columnFields) {
FieldMarshaller fmarsh = _fields.get(fname);
if (columns.remove(fmarsh.getColumnName())) {
@SuppressWarnings("unchecked") final FieldMarshaller<T> fmarsh = _fields.get(fname);
if (metaData.tableColumns.remove(fmarsh.getColumnName())) {
continue;
}
// otherwise add the column
String coldef = fmarsh.getColumnDefinition();
String query = "alter table " + getTableName() + " add column " + coldef;
// try to add it to the appropriate spot
int fidx = ListUtil.indexOf(_allFields, fmarsh.getColumnName());
if (fidx == 0) {
query += " first";
} else {
query += " after " + _allFields[fidx-1];
final String coldef = fmarsh.getColumnDefinition();
log.info("Adding column to " + getTableName() + ": " +
fmarsh.getColumnName() + " " + coldef);
ctx.invoke(new Modifier.Simple() {
protected String createQuery (DatabaseLiaison liaison) {
return "alter table " + liaison.tableSQL(getTableName()) +
" add column " + liaison.columnSQL(fmarsh.getColumnName()) + " " + coldef;
}
log.info("Adding column to " + getTableName() + ": " + coldef);
ctx.invoke(new Modifier.Simple(query));
});
// if the column is a TIMESTAMP or DATETIME column, we need to run a special query to
// update all existing rows to the current time because MySQL annoyingly assigns
@@ -773,75 +650,84 @@ public class DepotMarshaller<T extends PersistentRecord>
// cannot accept CURRENT_TIME or NOW() defaults at all.
if (coldef.toLowerCase().indexOf(" timestamp") != -1 ||
coldef.toLowerCase().indexOf(" datetime") != -1) {
query = "update " + getTableName() + " set " + fmarsh.getColumnName() + " = NOW()";
log.info("Assigning current time to column: " + query);
ctx.invoke(new Modifier.Simple(query));
log.info("Assigning current time to " + fmarsh.getColumnName() + ".");
ctx.invoke(new Modifier.Simple() {
protected String createQuery (DatabaseLiaison liaison) {
// TODO: is NOW() standard SQL?
return "update " + liaison.tableSQL(getTableName()) +
" set " + liaison.columnSQL(fmarsh.getColumnName()) + " = NOW()";
}
});
}
}
// add or remove the primary key as needed
if (hasPrimaryKey() && !indexColumns.containsKey("PRIMARY")) {
String pkdef = "primary key (" + getJoinedKeyColumns() + ")";
log.info("Adding primary key to " + getTableName() + ": " + pkdef);
ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " add " + pkdef));
// TODO: This needs more work to be database-independent, I think. Assuming the index name
// is going to be PRIMARY seems like a MySQL-ism, and we'll do the rest of the index work
// when we do that.
} else if (!hasPrimaryKey() && indexColumns.containsKey("PRIMARY")) {
indexColumns.remove("PRIMARY");
log.info("Dropping primary from " + getTableName());
ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " drop primary key"));
}
// add any missing indices
Entity entity = _pclass.getAnnotation(Entity.class);
for (Index index : (entity == null ? new Index[0] : entity.indices())) {
if (indexColumns.containsKey(index.name())) {
indexColumns.remove(index.name());
continue;
}
String indexdef = "create " + index.type() + " index " + index.name() +
" on " + getTableName() + " (" + StringUtil.join(index.columns(), ", ") + ")";
log.info("Adding index: " + indexdef);
ctx.invoke(new Modifier.Simple(indexdef));
}
// to get the @Table(uniqueIndices...) indices, we use our clever set of column name sets
Set<Set<String>> uniqueIndices = new HashSet<Set<String>>(indexColumns.values());
Table table;
if (getTableName() != null && (table = _pclass.getAnnotation(Table.class)) != null) {
Set<String> colSet = new HashSet<String>();
for (UniqueConstraint constraint : table.uniqueConstraints()) {
// for each given UniqueConstraint, build a new column set
colSet.clear();
for (String column : constraint.columnNames()) {
colSet.add(column);
}
// and check if the table contained this set
if (uniqueIndices.contains(colSet)) {
continue; // good, carry on
}
// else build the index; we'll use mysql's convention of naming it after a column,
// with possible _N disambiguation; luckily we made a copy of the index names!
String indexName = colSet.iterator().next();
if (indicesCopy.contains(indexName)) {
int num = 1;
indexName += "_";
while (indicesCopy.contains(indexName + num)) {
num ++;
}
indexName += num;
}
String[] columnArr = colSet.toArray(new String[colSet.size()]);
String indexdef = "create unique index " + indexName + " on " +
getTableName() + " (" + StringUtil.join(columnArr, ", ") + ")";
log.info("Adding unique index: " + indexdef);
ctx.invoke(new Modifier.Simple(indexdef));
}
}
// // add or remove the primary key as needed
// if (hasPrimaryKey() && !indexColumns.containsKey("PRIMARY")) {
// String pkdef = "primary key (" + getJoinedKeyColumns() + ")";
// log.info("Adding primary key to " + getTableName() + ": " + pkdef);
// ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " add " + pkdef));
//
// } else if (!hasPrimaryKey() && indexColumns.containsKey("PRIMARY")) {
// indexColumns.remove("PRIMARY");
// log.info("Dropping primary from " + getTableName());
// ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " drop primary key"));
// }
//
// // add any missing indices
// for (Index index : (entity == null ? new Index[0] : entity.indices())) {
// if (indexColumns.containsKey(index.name())) {
// indexColumns.remove(index.name());
// continue;
// }
// String indexdef =
// "create " + index.type() + " index " + _liaison.indexSQL(index.name()) +
// " on " + _liaison.tableSQL(getTableName()) + " (" + StringUtil.join(index.columns(), ", ") + ")";
// log.info("Adding index: " + indexdef);
// ctx.invoke(new Modifier.Simple(indexdef));
// }
//
// // to get the @Table(uniqueIndices...) indices, we use our clever set of column name sets
// Set<Set<String>> uniqueIndices = new HashSet<Set<String>>(indexColumns.values());
// Table table;
// if (getTableName() != null && (table = _pclass.getAnnotation(Table.class)) != null) {
// Set<String> colSet = new HashSet<String>();
// for (UniqueConstraint constraint : table.uniqueConstraints()) {
// // for each given UniqueConstraint, build a new column set
// colSet.clear();
// for (String column : constraint.columnNames()) {
// colSet.add(column);
// }
// // and check if the table contained this set
// if (uniqueIndices.contains(colSet)) {
// continue; // good, carry on
// }
//
// // else build the index; we'll use mysql's convention of naming it after a column,
// // with possible _N disambiguation; luckily we made a copy of the index names!
// String indexName = colSet.iterator().next();
// if (indicesCopy.contains(indexName)) {
// int num = 1;
// indexName += "_";
// while (indicesCopy.contains(indexName + num)) {
// num ++;
// }
// indexName += num;
// }
//
// String[] columnArr = colSet.toArray(new String[colSet.size()]);
// String indexdef = "create unique index " + indexName + " on " +
// getTableName() + " (" + StringUtil.join(columnArr, ", ") + ")";
// log.info("Adding unique index: " + indexdef);
// ctx.invoke(new Modifier.Simple(indexdef));
// }
// }
// we do not auto-remove columns but rather require that EntityMigration.Drop records be
// registered by hand to avoid accidentally causin the loss of data
// registered by hand to avoid accidentally causing the loss of data
// we don't auto-remove indices either because we'd have to sort out the potentially
// complex origins of an index (which might be because of a @Unique column or maybe the
@@ -858,29 +744,39 @@ public class DepotMarshaller<T extends PersistentRecord>
// record our new version in the database
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
updateVersion(conn, _schemaVersion);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
updateVersion(conn, liaison, _schemaVersion);
return 0;
}
});
}
/**
* Loads up the metadata for our database table: the names of our columns and indices.
*/
protected void loadMetaData (PersistenceContext ctx, final Set<String> columns,
final Map<String, Set<String>> indexColumns)
protected TableMetaData loadMetaData (PersistenceContext ctx)
throws PersistenceException
{
ctx.invoke(new Modifier() {
public int invoke (Connection conn) throws SQLException {
DatabaseMetaData meta = conn.getMetaData();
ResultSet rs = meta.getColumns(null, null, getTableName(), "%");
while (rs.next()) {
columns.add(rs.getString("COLUMN_NAME"));
return ctx.invoke(new Query.TrivialQuery<TableMetaData>() {
public TableMetaData invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
return new TableMetaData(conn.getMetaData());
}
});
}
protected class TableMetaData
{
public Set<String> tableColumns = new HashSet<String>();
public Map<String, Set<String>> indexColumns = new HashMap<String, Set<String>>();
public String pkName;
public Set<String> pkColumns = new HashSet<String>();
public TableMetaData (DatabaseMetaData meta)
throws SQLException
{
ResultSet rs = meta.getColumns(null, null, getTableName(), "%");
while (rs.next()) {
tableColumns.add(rs.getString("COLUMN_NAME"));
}
if (indexColumns != null) {
rs = meta.getIndexInfo(null, null, getTableName(), false, false);
while (rs.next()) {
String indexName = rs.getString("INDEX_NAME");
@@ -900,11 +796,13 @@ public class DepotMarshaller<T extends PersistentRecord>
set.add(rs.getString("COLUMN_NAME"));
}
}
}
return 0;
rs = meta.getPrimaryKeys(null, null, getTableName());
while (rs.next()) {
pkName = rs.getString("PK_NAME");
pkColumns.add(rs.getString("COLUMN_NAME"));
}
}
});
}
/**
@@ -913,32 +811,29 @@ public class DepotMarshaller<T extends PersistentRecord>
protected void verifySchemasMatch (PersistenceContext ctx)
throws PersistenceException
{
Set<String> columns = new HashSet<String>();
loadMetaData(ctx, columns, null);
TableMetaData meta = loadMetaData(ctx);
for (String fname : _columnFields) {
FieldMarshaller fmarsh = _fields.get(fname);
columns.remove(fmarsh.getColumnName());
meta.tableColumns.remove(fmarsh.getColumnName());
}
for (String column : columns) {
for (String column : meta.tableColumns) {
log.warning(getTableName() + " contains stale column '" + column + "'.");
}
}
protected String getJoinedKeyColumns ()
{
return StringUtil.join(getPrimaryKeyColumns(), ", ");
}
protected void updateVersion (Connection conn, int version)
protected void updateVersion (Connection conn, DatabaseLiaison liaison, int version)
throws SQLException
{
String update = "update " + SCHEMA_VERSION_TABLE +
" set version = " + version + " where persistentClass = '" + getTableName() + "'";
String insert = "insert into " + SCHEMA_VERSION_TABLE +
" values('" + getTableName() + "', " + version + ")";
String update =
"update " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" set " + liaison.columnSQL("version") + " = " + version +
" where " + liaison.columnSQL("persistentClass") + " = '" + getTableName() + "'";
Statement stmt = conn.createStatement();
try {
if (stmt.executeUpdate(update) == 0) {
String insert =
"insert into " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" values('" + getTableName() + "', " + version + ")";
stmt.executeUpdate(insert);
}
} finally {
@@ -946,15 +841,6 @@ public class DepotMarshaller<T extends PersistentRecord>
}
}
protected void requireNotComputed (String action)
throws SQLException
{
if (getTableName() == null) {
throw new IllegalArgumentException(
"Can't " + action + " computed entities [class=" + _pclass + "]");
}
}
/** The persistent object class that we manage. */
protected Class<T> _pclass;
@@ -962,10 +848,10 @@ public class DepotMarshaller<T extends PersistentRecord>
protected String _tableName;
/** A field marshaller for each persistent field in our object. */
protected HashMap<String, FieldMarshaller> _fields = new HashMap<String, FieldMarshaller>();
protected Map<String, FieldMarshaller> _fields = new HashMap<String, FieldMarshaller>();
/** The field marshallers for our persistent object's primary key columns
* or null if it did not define a primary key. */
/** The field marshallers for our persistent object's primary key columns or null if it did not
* define a primary key. */
protected ArrayList<FieldMarshaller> _pkColumns;
/** The generator to use for auto-generating primary key values, or null. */
@@ -977,16 +863,9 @@ public class DepotMarshaller<T extends PersistentRecord>
/** The fields of our object with directly corresponding table columns. */
protected String[] _columnFields;
/** The version of our persistent object schema as specified in the class
* definition. */
/** The version of our persistent object schema as specified in the class definition. */
protected int _schemaVersion = -1;
/** Used when creating and migrating our table schema. */
protected ArrayList<String> _declarations = new ArrayList<String>();
/** Used when creating and migrating our table schema. */
protected String _postamble = "";
/** Indicates that we have been initialized (created or migrated our tables). */
protected boolean _initialized;
@@ -29,15 +29,22 @@ import java.util.List;
import java.util.Map;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.ArrayUtil;
import com.samskivert.jdbc.ConnectionProvider;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.Modifier.*;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.InsertClause;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.util.ArrayUtil;
import com.samskivert.jdbc.depot.clause.UpdateClause;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
/**
* Provides a base for classes that provide access to persistent objects. Also defines the
@@ -163,18 +170,36 @@ public class DepotRepository
protected <T extends PersistentRecord> int insert (T record)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass());
final Key key = marsh.getPrimaryKey(record, false);
@SuppressWarnings("unchecked") final Class<T> pClass = (Class<T>) record.getClass();
final DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key<T> key = marsh.getPrimaryKey(record, false);
DepotTypes types = DepotTypes.getDepotTypes(_ctx);
types.addClass(_ctx, pClass);
final SQLBuilder builder = _ctx.getSQLBuilder(types);
// key will be null if record was supplied without a primary key
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
public int invoke (Connection conn) throws SQLException {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// update our modifier's key so that it can cache our results
updateKey(marsh.assignPrimaryKey(conn, _result, false));
PreparedStatement stmt = marsh.createInsert(conn, _result);
if (_key == null) {
updateKey(marsh.assignPrimaryKey(conn, liaison, _result, false));
}
String ixField = null;
if (_key == null && marsh.hasPrimaryKey() && marsh.getKeyGenerator() != null &&
marsh.getKeyGenerator() instanceof IdentityKeyGenerator) {
ixField = ((IdentityKeyGenerator) marsh.getKeyGenerator()).getColumn();
}
builder.newQuery(new InsertClause<T>(pClass, _result, ixField));
PreparedStatement stmt = builder.prepare(conn);
try {
int mods = stmt.executeUpdate();
// check again in case we have a post-factum key generator
updateKey(marsh.assignPrimaryKey(conn, _result, true));
if (_key == null) {
updateKey(marsh.assignPrimaryKey(conn, liaison, _result, true));
}
return mods;
} finally {
JDBCUtil.close(stmt);
@@ -192,14 +217,22 @@ public class DepotRepository
protected <T extends PersistentRecord> int update (T record)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass());
final Key key = marsh.getPrimaryKey(record);
@SuppressWarnings("unchecked") Class<T> pClass = (Class<T>) record.getClass();
requireNotComputed(pClass, "update");
DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key key = marsh.getPrimaryKey(record);
if (key == null) {
throw new IllegalArgumentException("Can't update record with null primary key.");
}
UpdateClause<T> update = new UpdateClause<T>(pClass, key, marsh._columnFields, record);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createUpdate(conn, _result, key);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
} finally {
@@ -220,14 +253,26 @@ public class DepotRepository
protected <T extends PersistentRecord> int update (T record, final String... modifiedFields)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass());
final Key key = marsh.getPrimaryKey(record);
@SuppressWarnings("unchecked")
Class<T> pClass = (Class<T>) record.getClass();
requireNotComputed(pClass, "updatePartial");
DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key key = marsh.getPrimaryKey(record);
if (key == null) {
throw new IllegalArgumentException("Can't update record with null primary key.");
}
UpdateClause<T> update = new UpdateClause<T>(pClass, key, modifiedFields, record);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createUpdate(conn, _result, key, modifiedFields);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
// clear out _result so that we don't rewrite this partial record to the cache
_result = null;
try {
@@ -336,21 +381,23 @@ public class DepotRepository
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updatePartial (
Class<T> type, final Where key, CacheInvalidator invalidator, Object... fieldsValues)
Class<T> type, final WhereClause key, CacheInvalidator invalidator, Object... fieldsValues)
throws PersistenceException
{
// separate the arguments into keys and values
final String[] fields = new String[fieldsValues.length/2];
final Object[] values = new Object[fields.length];
final SQLExpression[] values = new SQLExpression[fields.length];
for (int ii = 0, idx = 0; ii < fields.length; ii++) {
fields[ii] = (String)fieldsValues[idx++];
values[ii] = fieldsValues[idx++];
values[ii] = new ValueExp(fieldsValues[idx++]);
}
UpdateClause<T> update = new UpdateClause<T>(type, key, fields, values);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
final DepotMarshaller marsh = _ctx.getMarshaller(type);
return _ctx.invoke(new Modifier(invalidator) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createPartialUpdate(conn, key, fields, values);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
} finally {
@@ -378,11 +425,11 @@ public class DepotRepository
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, Comparable primaryKey, String... fieldsValues)
Class<T> type, Comparable primaryKey, Map<String, SQLExpression> fieldsToValues)
throws PersistenceException
{
Key<T> key = _ctx.getMarshaller(type).makePrimaryKey(primaryKey);
return updateLiteral(type, key, key, fieldsValues);
return updateLiteral(type, key, key, fieldsToValues);
}
/**
@@ -404,11 +451,11 @@ public class DepotRepository
*/
protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, String ix1, Comparable val1, String ix2, Comparable val2,
String... fieldsValues)
Map<String, SQLExpression> fieldsToValues)
throws PersistenceException
{
Key<T> key = new Key<T>(type, ix1, val1, ix2, val2);
return updateLiteral(type, key, key, fieldsValues);
return updateLiteral(type, key, key, fieldsToValues);
}
/**
@@ -430,11 +477,11 @@ public class DepotRepository
*/
protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, String ix1, Comparable val1, String ix2, Comparable val2,
String ix3, Comparable val3, String... fieldsValues)
String ix3, Comparable val3, Map<String, SQLExpression> fieldsToValues)
throws PersistenceException
{
Key<T> key = new Key<T>(type, ix1, val1, ix2, val2, ix3, val3);
return updateLiteral(type, key, key, fieldsValues);
return updateLiteral(type, key, key, fieldsToValues);
}
/**
@@ -455,21 +502,29 @@ public class DepotRepository
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, final Where key, CacheInvalidator invalidator, String... fieldsValues)
Class<T> type, final WhereClause key, CacheInvalidator invalidator,
Map<String, SQLExpression> fieldsToValues)
throws PersistenceException
{
requireNotComputed(type, "updateLiteral");
// separate the arguments into keys and values
final String[] fields = new String[fieldsValues.length/2];
final String[] values = new String[fields.length];
for (int ii = 0, idx = 0; ii < fields.length; ii++) {
fields[ii] = fieldsValues[idx++];
values[ii] = fieldsValues[idx++];
final String[] fields = new String[fieldsToValues.size()];
final SQLExpression[] values = new SQLExpression[fields.length];
int ii = 0;
for (Map.Entry<String, SQLExpression> entry : fieldsToValues.entrySet()) {
fields[ii] = entry.getKey();
values[ii] = entry.getValue();
ii ++;
}
final DepotMarshaller marsh = _ctx.getMarshaller(type);
UpdateClause<T> update = new UpdateClause<T>(type, key, fields, values);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
return _ctx.invoke(new Modifier(invalidator) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createLiteralUpdate(conn, key, fields, values);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
} finally {
@@ -489,17 +544,25 @@ public class DepotRepository
protected <T extends PersistentRecord> boolean store (T record)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass());
final Key key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null;
@SuppressWarnings("unchecked") final Class<T> pClass = (Class<T>) record.getClass();
requireNotComputed(pClass, "store");
final DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key<T> key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null;
final UpdateClause<T> update =
new UpdateClause<T>(pClass, key, marsh._columnFields, record);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
final boolean[] created = new boolean[1];
_ctx.invoke(new CachingModifier<T>(record, key, key) {
public int invoke (Connection conn) throws SQLException {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = null;
try {
// if our primary key isn't null, update rather than insert the record
// before been persisted and insert
if (key != null) {
stmt = marsh.createUpdate(conn, _result, key);
// if our primary key isn't null, update rather than insert the record before
// been persisted and insert
if (_key != null) {
stmt = builder.prepare(conn);
int mods = stmt.executeUpdate();
if (mods > 0) {
return mods;
@@ -509,10 +572,22 @@ public class DepotRepository
// if the update modified zero rows or the primary key was obviously unset, do
// an insertion
updateKey(marsh.assignPrimaryKey(conn, _result, false));
stmt = marsh.createInsert(conn, _result);
if (_key == null) {
updateKey(marsh.assignPrimaryKey(conn, liaison, _result, false));
}
String ixField = null;
if (_key == null && marsh.hasPrimaryKey() && marsh.getKeyGenerator() != null &&
marsh.getKeyGenerator() instanceof IdentityKeyGenerator) {
ixField = ((IdentityKeyGenerator) marsh.getKeyGenerator()).getColumn();
}
builder.newQuery(new InsertClause<T>(pClass, _result, ixField));
stmt = builder.prepare(conn);
int mods = stmt.executeUpdate();
updateKey(marsh.assignPrimaryKey(conn, _result, true));
if (_key == null) {
updateKey(marsh.assignPrimaryKey(conn, liaison, _result, true));
}
created[0] = true;
return mods;
@@ -571,13 +646,16 @@ public class DepotRepository
* @return the number of rows deleted by this action.
*/
protected <T extends PersistentRecord> int deleteAll (
Class<T> type, final Where key, CacheInvalidator invalidator)
Class<T> type, final WhereClause key, CacheInvalidator invalidator)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(type);
DeleteClause<T> delete = new DeleteClause<T>(type, key);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, delete));
builder.newQuery(delete);
return _ctx.invoke(new Modifier(invalidator) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = marsh.createDelete(conn, key);
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
} finally {
@@ -587,25 +665,18 @@ public class DepotRepository
});
}
protected static abstract class CollectionQuery<T extends Collection> implements Query<T>
{
public CollectionQuery (CacheKey key)
// make sure the given type corresponds to a concrete class
protected void requireNotComputed (Class<? extends PersistentRecord> type, String action)
throws PersistenceException
{
_key = key;
DepotMarshaller marsh = _ctx.getMarshaller(type);
if (marsh == null) {
throw new PersistenceException("Unknown persistent type [class=" + type + "]");
}
public CacheKey getCacheKey ()
{
return _key;
if (marsh.getTableName() == null) {
throw new PersistenceException(
"Can't " + action + " computed entities [class=" + type + "]");
}
public void updateCache (PersistenceContext ctx, T result)
{
ctx.cacheStore(_key, result);
}
protected CacheKey _key;
}
protected PersistenceContext _ctx;
@@ -20,52 +20,60 @@
package com.samskivert.jdbc.depot;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Primarily an implementation of {@link QueryBuilderContext}, this class holds the persistent
* classes used in the context of a single query and maps them to {@link DepotMarshaller}
* instances as well as table names and table abbreviations.
* Maintains a record of the persistent classes brought into the context of the associated SQL,
* i.e. any class associated with a concrete table that would appear in FROM or JOIN clauses or as
* the target of an UPDATE or an INSERT or any other place where a table abbreviation could be
* constructed.
*
* The main motivation for breaking this functionality out into its own class is to encapsulate
* the operation that throws {@link PersistenceException} as separate from the operations that
* throw {@link SQLException}.
* The main motivation for breaking this functionality out into its own class is to encapsulate the
* operation that throws {@link PersistenceException} as separate from the operations that throw
* {@link SQLException}. Once this class has been constructed, it may be used to create {@link
* SQLBuilder} instances without any {@link PersistenceException} worries.
*/
public class DepotTypes<T>
implements QueryBuilderContext<T>
public class DepotTypes
{
public DepotTypes (PersistenceContext ctx,
Class<? extends PersistentRecord> main,
Set<Class<? extends PersistentRecord>> others)
/** A trivial instance that is accessible in places where we want the dialectal benefits of the
* SQLBuilder without really requiring per-persistent-class context. */
public static DepotTypes TRIVIAL = new DepotTypes();
/**
* Conveniently constructs a {@link DepotTypes} object given {@link QueryClause} objects, which
* are interrogated for their class definition sets through {@link SQLExpression#addClasses}.
*/
public static <T extends PersistentRecord> DepotTypes getDepotTypes (
PersistenceContext ctx, QueryClause... clauses)
throws PersistenceException
{
_mainType = main;
_classMap = new HashMap<Class, DepotMarshaller>();
for (Class<? extends PersistentRecord> c : others) {
_classMap.put(c, ctx.getMarshaller(c));
Set<Class<? extends PersistentRecord>> classSet =
new HashSet<Class<? extends PersistentRecord>>();
for (QueryClause clause : clauses) {
if (clause != null) {
clause.addClasses(classSet);
}
_classList = new ArrayList<Class<? extends PersistentRecord>>(others);
}
return new DepotTypes(ctx, classSet);
}
public Class<? extends PersistentRecord> getMainType ()
{
return _mainType;
}
// from ConstructedQuery
public String getTableName (Class<? extends PersistentRecord> cl)
{
return _classMap.get(cl).getTableName();
}
// from ConstructedQuery
public String getTableAbbreviation (Class<? extends PersistentRecord> cl)
{
int ix = _classList.indexOf(cl);
@@ -75,32 +83,42 @@ public class DepotTypes<T>
return "T" + (ix+1);
}
public String getColumnName (Class<? extends PersistentRecord> cl, String field)
{
return getMarshaller(cl).getFieldMarshaller(field).getColumnName();
}
public DepotMarshaller getMarshaller (Class cl)
{
return _classMap.get(cl);
}
public String getMainTableName ()
public void addClass (PersistenceContext ctx, Class <? extends PersistentRecord> type)
throws PersistenceException
{
return getTableName(_mainType);
if (_classMap.containsKey(type)) {
return;
}
_classList.add(type);
_classMap.put(type, ctx.getMarshaller(type));
}
public DepotMarshaller getMainMarshaller ()
protected DepotTypes (PersistenceContext ctx, Set<Class<? extends PersistentRecord>> others)
throws PersistenceException
{
return getMarshaller(_mainType);
for (Class<? extends PersistentRecord> c : others) {
addClass(ctx, c);
}
}
public String getMainTableAbbreviation ()
protected DepotTypes ()
{
return getTableAbbreviation(_mainType);
}
/** The persistent class to instantiate for the results. */
protected Class<? extends PersistentRecord> _mainType;
/** A list of referenced classes, used to generate table abbreviations. */
protected List<Class<? extends PersistentRecord>> _classList;
protected List<Class<? extends PersistentRecord>> _classList =
new ArrayList<Class<? extends PersistentRecord>>();
/** Classes mapped to marshallers, used for table names and field lists. */
protected Map<Class, DepotMarshaller> _classMap;
protected Map<Class, DepotMarshaller> _classMap = new HashMap<Class, DepotMarshaller>();
}
@@ -22,10 +22,11 @@ package com.samskivert.jdbc.depot;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.LiaisonRegistry;
import static com.samskivert.jdbc.depot.Log.log;
@@ -47,21 +48,15 @@ public abstract class EntityMigration extends Modifier
_columnName = columnName;
}
public int invoke (Connection conn) throws SQLException {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (!JDBCUtil.tableContainsColumn(conn, _tableName, _columnName)) {
// we'll accept this inconsistency
log.warning(_tableName + "." + _columnName + " already dropped.");
return 0;
}
Statement stmt = conn.createStatement();
try {
log.info("Dropping '" + _columnName + "' from " + _tableName);
return stmt.executeUpdate(
"alter table " + _tableName + " drop column " + _columnName);
} finally {
stmt.close();
}
return liaison.dropColumn(conn, _tableName, _columnName) ? 1 : 0;
}
protected String _columnName;
@@ -78,7 +73,7 @@ public abstract class EntityMigration extends Modifier
_newColumnName = newColumnName;
}
public int invoke (Connection conn) throws SQLException {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (!JDBCUtil.tableContainsColumn(conn, _tableName, _oldColumnName)) {
if (JDBCUtil.tableContainsColumn(conn, _tableName, _newColumnName)) {
// we'll accept this inconsistency
@@ -97,60 +92,46 @@ public abstract class EntityMigration extends Modifier
_tableName + " already contains '" + _newColumnName + "'");
}
Statement stmt = conn.createStatement();
try {
log.info("Changing '" + _oldColumnName + "' to '" + _newColumnDef + "' in " +
log.info("Renaming '" + _oldColumnName + "' to '" + _newColumnName + "' in: " +
_tableName);
return stmt.executeUpdate("alter table " + _tableName + " change column " +
_oldColumnName + " " + _newColumnDef);
} finally {
stmt.close();
}
return liaison.renameColumn(conn, _tableName, _oldColumnName, _newColumnName) ? 1 : 0;
}
public boolean runBeforeDefault () {
return false;
}
protected void init (String tableName, HashMap<String,FieldMarshaller> marshallers) {
super.init(tableName, marshallers);
_newColumnDef = marshallers.get(_newColumnName).getColumnDefinition();
}
protected String _oldColumnName, _newColumnName, _newColumnDef;
protected String _oldColumnName, _newColumnName;
}
/**
* A convenient migration for changing the type of an existing column.
* A convenient migration for changing the type of an existing field. NOTE: This object is
* instantiated with the name of a persistent field, not the name of a database column. These
* can be very different things for classes that use @Column annotations.
*/
public static class Retype extends EntityMigration
{
public Retype (int targetVersion, String columnName) {
public Retype (int targetVersion, String fieldName) {
super(targetVersion);
_columnName = columnName;
_fieldName = fieldName;
}
public int invoke (Connection conn) throws SQLException {
Statement stmt = conn.createStatement();
try {
log.info("Updating type of '" + _columnName + "' in " + _tableName);
return stmt.executeUpdate("alter table " + _tableName + " change column " +
_columnName + " " + _newColumnDef);
} finally {
stmt.close();
}
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
log.info("Updating type of '" + _fieldName + "' in " + _tableName);
return liaison.changeColumn(conn, _tableName, _fieldName, _newColumnDef) ? 1 : 0;
}
public boolean runBeforeDefault () {
return false;
}
protected void init (String tableName, HashMap<String,FieldMarshaller> marshallers) {
protected void init (String tableName, Map<String,FieldMarshaller> marshallers) {
super.init(tableName, marshallers);
_newColumnDef = marshallers.get(_columnName).getColumnDefinition();
_columnName = marshallers.get(_fieldName).getColumnName();
_newColumnDef = marshallers.get(_fieldName).getColumnDefinition();
}
protected String _columnName, _newColumnDef;
protected String _fieldName, _columnName, _newColumnDef;
}
/**
@@ -184,7 +165,7 @@ public abstract class EntityMigration extends Modifier
* migration has been determined to be runnable so one cannot rely on this method having been
* called in {@link #shouldRunMigration}.
*/
protected void init (String tableName, HashMap<String,FieldMarshaller> marshallers)
protected void init (String tableName, Map<String,FieldMarshaller> marshallers)
{
_tableName = tableName;
}
@@ -33,6 +33,7 @@ import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.annotation.Column;
import com.samskivert.jdbc.depot.annotation.Computed;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
@@ -106,10 +107,20 @@ public abstract class FieldMarshaller<T>
"Cannot marshall field of type '" + ftype.getName() + "'.");
}
marshaller.init(field);
marshaller.create(field);
return marshaller;
}
/**
* Initializes this field marshaller with a SQL builder which it uses to construct its column
* definition according to the appropriate database dialect.
*/
public void init (SQLBuilder builder)
throws PersistenceException
{
_columnDefinition = builder.buildColumnDefinition(this);
}
/**
* Returns the {@link Field} handled by this marshaller.
*/
@@ -194,98 +205,30 @@ public abstract class FieldMarshaller<T>
writeToObject(po, getFromSet(rset));
}
/**
* Returns the type used in the SQL column definition for this field.
*/
public abstract String getColumnType ();
protected void init (Field field)
protected void create (Field field)
{
_field = field;
_columnName = field.getName();
// read our column metadata from the annotation (if it exists); annoyingly we can't create
// a Column instance to read the defaults so we have to duplicate them here
int length = 255;
boolean nullable = false;
boolean unique = false;
String defval = "";
Column column = _field.getAnnotation(Column.class);
if (column != null) {
nullable = column.nullable();
unique = column.unique();
length = column.length();
defval = column.defaultValue();
if (!StringUtil.isBlank(column.name())) {
_columnName = column.name();
}
}
_computed = field.getAnnotation(Computed.class);
// if this field is @Computed, it has no SQL definition
if (_computed != null) {
_columnDefinition = null;
return;
}
// create our SQL column definition
StringBuilder builder = new StringBuilder();
if (column != null && !StringUtil.isBlank(column.columnDefinition())) {
builder.append(column.columnDefinition());
} else {
builder.append(getColumnName());
String type = getColumnType();
builder.append(" ").append(type);
// if this is a VARCHAR field, add the length
if (type.equals("VARCHAR") || type.equals("VARBINARY")) {
builder.append("(").append(length).append(")");
}
// TODO: handle precision and scale
// handle nullability and uniqueness
if (!nullable) {
builder.append(" NOT NULL");
}
if (unique) {
builder.append(" UNIQUE");
}
// append the default value if one was specified
if (defval.length() > 0) {
builder.append(" DEFAULT ").append(defval);
}
}
// handle primary keyness
if (field.getAnnotation(Id.class) != null) {
// figure out how we're going to generate our primary key values
_generatedValue = field.getAnnotation(GeneratedValue.class);
if (_generatedValue != null) {
switch (_generatedValue.strategy()) {
case AUTO:
case IDENTITY:
builder.append(" AUTO_INCREMENT");
break;
case SEQUENCE: // TODO
throw new IllegalArgumentException(
"SEQUENCE key generation strategy not yet supported.");
case TABLE:
// nothing to do here, it'll be handled later
break;
}
}
}
_columnDefinition = builder.toString();
}
protected static class BooleanMarshaller extends FieldMarshaller<Boolean> {
public String getColumnType () {
return "TINYINT";
}
public Boolean getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getBoolean(po);
@@ -305,9 +248,6 @@ public abstract class FieldMarshaller<T>
}
protected static class ByteMarshaller extends FieldMarshaller<Byte> {
public String getColumnType () {
return "TINYINT";
}
public Byte getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getByte(po);
@@ -327,9 +267,6 @@ public abstract class FieldMarshaller<T>
}
protected static class ShortMarshaller extends FieldMarshaller<Short> {
public String getColumnType () {
return "SMALLINT";
}
public Short getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getShort(po);
@@ -349,9 +286,6 @@ public abstract class FieldMarshaller<T>
}
protected static class IntMarshaller extends FieldMarshaller<Integer> {
public String getColumnType () {
return "INTEGER";
}
public Integer getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getInt(po);
@@ -371,9 +305,6 @@ public abstract class FieldMarshaller<T>
}
protected static class LongMarshaller extends FieldMarshaller<Long> {
public String getColumnType () {
return "BIGINT";
}
public Long getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getLong(po);
@@ -393,9 +324,6 @@ public abstract class FieldMarshaller<T>
}
protected static class FloatMarshaller extends FieldMarshaller<Float> {
public String getColumnType () {
return "FLOAT";
}
public Float getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getFloat(po);
@@ -415,9 +343,6 @@ public abstract class FieldMarshaller<T>
}
protected static class DoubleMarshaller extends FieldMarshaller<Double> {
public String getColumnType () {
return "DOUBLE";
}
public Double getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getDouble(po);
@@ -437,37 +362,6 @@ public abstract class FieldMarshaller<T>
}
protected static class ObjectMarshaller extends FieldMarshaller<Object> {
public String getColumnType () {
Class<?> ftype = _field.getType();
if (ftype.equals(Byte.class)) {
return "TINYINT";
} else if (ftype.equals(Short.class)) {
return "SMALLINT";
} else if (ftype.equals(Integer.class)) {
return "INTEGER";
} else if (ftype.equals(Long.class)) {
return "BIGINT";
} else if (ftype.equals(Float.class)) {
return "FLOAT";
} else if (ftype.equals(Double.class)) {
return "DOUBLE";
} else if (ftype.equals(String.class)) {
return "VARCHAR";
} else if (ftype.equals(Date.class)) {
return "DATE";
} else if (ftype.equals(Time.class)) {
return "DATETIME";
} else if (ftype.equals(Timestamp.class)) {
return "TIMESTAMP";
} else if (ftype.equals(Blob.class)) {
return "BLOB";
} else if (ftype.equals(Clob.class)) {
return "CLOB";
} else {
throw new IllegalArgumentException(
"Don't know how to create SQL for " + ftype + ".");
}
}
public Object getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.get(po);
@@ -503,9 +397,6 @@ public abstract class FieldMarshaller<T>
throws SQLException {
ps.setBytes(column, value);
}
public String getColumnType () {
return "VARBINARY";
}
}
protected static class IntArrayMarshaller extends FieldMarshaller<int[]> {
@@ -525,9 +416,6 @@ public abstract class FieldMarshaller<T>
throws SQLException {
ps.setObject(column, value);
}
public String getColumnType () {
return "BLOB";
}
}
protected static class ByteEnumMarshaller extends FieldMarshaller<ByteEnum> {
@@ -545,10 +433,6 @@ public abstract class FieldMarshaller<T>
}
}
public String getColumnType () {
return "TINYINT";
}
public ByteEnum getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return (ByteEnum) _field.get(po);
@@ -33,14 +33,18 @@ import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.ArrayUtil;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.operator.Logic.*;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.SelectClause;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.util.ArrayUtil;
import com.samskivert.jdbc.depot.operator.Logic.*;
/**
* This class implements the functionality required by {@link DepotRepository#findAll): fetch
@@ -58,18 +62,20 @@ public abstract class FindAllQuery<T extends PersistentRecord>
throws PersistenceException
{
super(ctx, type);
_types = SQLQueryBuilder.getDepotTypes(ctx, type, clauses);
DepotTypes types = DepotTypes.getDepotTypes(ctx, clauses);
types.addClass(ctx, type);
_builder = _ctx.getSQLBuilder(types);
_clauses = clauses;
}
public List<T> invoke (Connection conn) throws SQLException {
SQLQueryBuilder<T> keyFieldQuery = new SQLQueryBuilder<T>(
_ctx, _types, _marsh.getPrimaryKeyColumns(), _clauses);
PreparedStatement stmt = keyFieldQuery.prepare(conn);
public List<T> invoke (Connection conn, DatabaseLiaison liaison) throws SQLException
{
Map<Key<T>, T> entities = new HashMap<Key<T>, T>();
List<Key<T>> allKeys = new ArrayList<Key<T>>();
List<Key<T>> fetchKeys = new ArrayList<Key<T>>();
_builder.newQuery(new SelectClause<T>(_type, _marsh.getPrimaryKeyFields(), _clauses));
PreparedStatement stmt = _builder.prepare(conn);
try {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
@@ -97,26 +103,28 @@ public abstract class FindAllQuery<T extends PersistentRecord>
if (fetchKeys.size() > 0) {
int jj = 0;
QueryClause[] newClauses = new QueryClause[_clauses.length + 1];
// a select subset of query clauses are preserved for the entity query
QueryClause[] newClauses = new QueryClause[_clauses.length + 1];
for (int ii = 0; ii < _clauses.length; ii ++) {
if (_clauses[ii] instanceof Join || _clauses[ii] instanceof FieldOverride) {
newClauses[jj ++] = _clauses[ii];
}
}
SQLExpression[] keyArray = fetchKeys.toArray(new SQLExpression[0]);
SQLExpression[] keyArray = new SQLExpression[fetchKeys.size()];
for (int ii = 0; ii < keyArray.length; ii ++) {
keyArray[ii] = fetchKeys.get(ii).condition;
}
// add our special key-matching where clause
newClauses[jj ++] = new Where(new Or(keyArray));
newClauses = ArrayUtil.splice(newClauses, jj);
// build the new query
SQLQueryBuilder<T> entityQuery = new SQLQueryBuilder<T>(
_ctx, _types, _marsh.getFieldNames(), newClauses);
_builder.newQuery(new SelectClause<T>(_type, _marsh.getFieldNames(), newClauses));
stmt = _builder.prepare(conn);
// and execute it
stmt = entityQuery.prepare(conn);
try {
ResultSet rs = stmt.executeQuery();
for (Key<T> key : fetchKeys) {
@@ -138,7 +146,6 @@ public abstract class FindAllQuery<T extends PersistentRecord>
return result;
}
protected DepotTypes<T> _types;
protected QueryClause[] _clauses;
}
@@ -151,33 +158,32 @@ public abstract class FindAllQuery<T extends PersistentRecord>
throws PersistenceException
{
super(ctx, type);
DepotTypes<List<T>> types = SQLQueryBuilder.getDepotTypes(ctx, type, clauses);
_builder = new SQLQueryBuilder<List<T>>(ctx, types, _marsh.getFieldNames(), clauses);
SelectClause<T> select = new SelectClause<T>(type, _marsh.getFieldNames(), clauses);
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select));
_builder.newQuery(select);
}
public List<T> invoke (Connection conn) throws SQLException {
PreparedStatement stmt = _builder.prepare(conn);
public List<T> invoke (Connection conn, DatabaseLiaison liaison) throws SQLException
{
List<T> result = new ArrayList<T>();
PreparedStatement stmt = _builder.prepare(conn);
try {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
result.add(_marsh.createObject(rs));
}
} finally {
JDBCUtil.close(stmt);
}
return result;
}
protected SQLQueryBuilder<List<T>> _builder;
}
public FindAllQuery (PersistenceContext ctx, Class<T> type)
throws PersistenceException
{
_ctx = ctx;
_type = type;
_marsh = _ctx.getMarshaller(type);
}
@@ -216,5 +222,7 @@ public abstract class FindAllQuery<T extends PersistentRecord>
}
protected PersistenceContext _ctx;
protected SQLBuilder _builder;
protected DepotMarshaller<T> _marsh;
protected Class<T> _type;
}
@@ -26,8 +26,11 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.SelectClause;
/**
* The implementation of {@link DepotRepository#find) functionality.
@@ -39,21 +42,23 @@ public class FindOneQuery<T extends PersistentRecord>
throws PersistenceException
{
_marsh = ctx.getMarshaller(type);
DepotTypes<T> types = SQLQueryBuilder.getDepotTypes(ctx, type, clauses);
_builder = new SQLQueryBuilder<T>(ctx, types, _marsh.getFieldNames(), clauses);
_select = new SelectClause<T>(type, _marsh.getFieldNames(), clauses);
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
_builder.newQuery(_select);
}
// from Query
public CacheKey getCacheKey ()
{
if (_builder.where != null && _builder.where instanceof CacheKey) {
return (CacheKey) _builder.where;
WhereClause where = _select.getWhereClause();
if (where != null && where instanceof CacheKey) {
return (CacheKey) where;
}
return null;
}
// from Query
public T invoke (Connection conn) throws SQLException {
public T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = _builder.prepare(conn);
try {
T result = null;
@@ -96,5 +101,6 @@ public class FindOneQuery<T extends PersistentRecord>
}
protected DepotMarshaller<T> _marsh;
protected SQLQueryBuilder<T> _builder;
protected SelectClause<T> _select;
protected SQLBuilder _builder;
}
@@ -21,17 +21,22 @@
package com.samskivert.jdbc.depot;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.LiaisonRegistry;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
/**
* Generates primary keys using an identity column.
*/
public class IdentityKeyGenerator implements KeyGenerator
public class IdentityKeyGenerator extends KeyGenerator
{
public IdentityKeyGenerator (GeneratedValue gv, String table, String column)
{
super(gv, table, column);
}
// from interface KeyGenerator
public boolean isPostFactum ()
{
@@ -39,27 +44,16 @@ public class IdentityKeyGenerator implements KeyGenerator
}
// from interface KeyGenerator
public void init (Connection conn)
public void init (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
// nothing to do here
liaison.initializeGenerator(conn, _table, _column, _initialValue, _allocationSize);
}
// from interface KeyGenerator
public int nextGeneratedValue (Connection conn)
public int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
// load up the last inserted ID mysql style
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()");
if (rs.next()) {
return rs.getInt(1);
}
throw new SQLException("Failed to generate next ID");
} finally {
JDBCUtil.close(stmt);
}
return liaison.lastInsertedId(conn, _table, _column);
}
}
+84 -63
View File
@@ -22,8 +22,6 @@ package com.samskivert.jdbc.depot;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -31,20 +29,77 @@ import java.util.HashMap;
import java.util.Map;
import com.samskivert.jdbc.depot.annotation.Id;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.util.StringUtil;
/**
* A special form of {@link Where} clause that uniquely specifies a single database row and
* A special form of {@link WhereClause} that uniquely specifies a single database row and
* thus also a single persistent object. Because it implements both {@link CacheKey} and
* {@link CacheInvalidator} it also uniquely indexes into the cache and knows how to invalidate
* itself upon modification. This class is created by many {@link DepotMarshaller} methods as
* a convenience, and may also be instantiated explicitly.
*/
public class Key<T extends PersistentRecord> extends Where
public class Key<T extends PersistentRecord> extends WhereClause
implements SQLExpression, CacheKey, CacheInvalidator, Serializable
{
/** An expression that contains our key columns and values. */
public static class WhereCondition<U extends PersistentRecord> implements SQLExpression
{
public WhereCondition (Class<U> pClass, Comparable[] values)
{
_pClass = pClass;
_values = values;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
throws Exception
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
public Class<U> getPersistentClass ()
{
return _pClass;
}
public Comparable[] getValues ()
{
return _values;
}
@Override
public boolean equals (Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
WhereCondition other = (WhereCondition) obj;
return _pClass == other._pClass && Arrays.equals(_values, other._values);
}
@Override
public int hashCode ()
{
return _pClass.hashCode() ^ Arrays.hashCode(_values);
}
protected Class<U> _pClass;
protected Comparable[] _values;
}
/** The expression that identifies our row. */
public final WhereCondition<T> condition;
/**
* Constructs a new single-column {@code Key} with the given value.
*/
@@ -76,10 +131,6 @@ public class Key<T extends PersistentRecord> extends Where
*/
public Key (Class<T> pClass, String[] fields, Comparable[] values)
{
// TODO: make Where an interface so we don't have to do this ugly super call
super(null);
_pClass = pClass;
if (fields.length != values.length) {
throw new IllegalArgumentException("Field and Value arrays must be of equal length.");
}
@@ -94,69 +145,41 @@ public class Key<T extends PersistentRecord> extends Where
String[] keyFields = getKeyFields(pClass);
// now extract the values in field order and ensure none are extra or missing
_values = new Comparable[fields.length];
Comparable[] newValues = new Comparable[fields.length];
for (int ii = 0; ii < keyFields.length; ii++) {
_values[ii] = map.remove(keyFields[ii]);
newValues[ii] = map.remove(keyFields[ii]);
// make sure we were provided with a value for this primary key field
if (_values[ii] == null) {
if (newValues[ii] == null) {
throw new IllegalArgumentException("Missing value for key field: " + keyFields[ii]);
}
}
// finally make sure we were not given any fields that are not in fact primary key fields
if (map.size() > 0) {
throw new IllegalArgumentException(
"Non-key columns given: " + StringUtil.join(map.keySet().toArray(), ", "));
}
condition = new WhereCondition<T>(pClass, newValues);
}
// from QueryClause
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from QueryClause
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
{
builder.append(" where ");
appendExpression(query, builder);
}
// from QueryClause
public int bindClauseArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
{
for (int ii = 0; ii < _values.length; ii ++) {
if (_values[ii] != null) {
pstmt.setObject(argIdx ++, _values[ii]);
}
}
return argIdx;
classSet.add(condition.getPersistentClass());
}
// from SQLExpression
public void appendExpression (QueryBuilderContext<?> query, StringBuilder builder)
public void accept (ExpressionVisitor builder)
throws Exception
{
String[] keyFields = getKeyFields(_pClass);
for (int ii = 0; ii < keyFields.length; ii ++) {
if (ii > 0) {
builder.append(" and ");
}
builder.append(keyFields[ii]).append(_values[ii] == null ? " is null " : " = ? ");
}
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
{
return bindClauseArguments(pstmt, argIdx);
builder.visit(this);
}
// from CacheKey
public String getCacheId ()
{
return _pClass.getName();
return condition.getPersistentClass().getName();
}
// from CacheKey
@@ -171,12 +194,6 @@ public class Key<T extends PersistentRecord> extends Where
ctx.cacheInvalidate(this);
}
@Override
public int hashCode ()
{
return _pClass.hashCode() ^ Arrays.hashCode(_values);
}
@Override
public boolean equals (Object obj)
{
@@ -186,17 +203,24 @@ public class Key<T extends PersistentRecord> extends Where
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Key other = (Key) obj;
return _pClass == other._pClass && Arrays.equals(_values, other._values);
return condition.equals(((Key) obj).condition);
}
@Override
public int hashCode ()
{
return condition.hashCode();
}
@Override
public String toString ()
{
StringBuilder builder = new StringBuilder("[Key pClass=" + _pClass.getName());
String[] keyFields = getKeyFields(_pClass);
StringBuilder builder =
new StringBuilder("[Key pClass=").append(condition.getPersistentClass().getName());
String[] keyFields = getKeyFields(condition.getPersistentClass());
for (int ii = 0; ii < keyFields.length; ii ++) {
builder.append(", ").append(keyFields[ii]).append("=").append(_values[ii]);
builder.append(", ").append(keyFields[ii]).append("=").
append(condition.getValues()[ii]);
}
builder.append("]");
return builder.toString();
@@ -222,9 +246,6 @@ public class Key<T extends PersistentRecord> extends Where
return fields;
}
protected Class<T> _pClass;
protected Comparable[] _values;
/** A (never expiring) cache of primary key field names for all persistent classes (of which
* there are merely dozens, so we don't need to worry about expiring). */
protected static HashMap<Class<?>,String[]> _keyFields = new HashMap<Class<?>,String[]>();
@@ -23,18 +23,58 @@ package com.samskivert.jdbc.depot;
import java.sql.Connection;
import java.sql.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
/**
* Defines the interface to our primary key generators.
*/
public interface KeyGenerator
public abstract class KeyGenerator
{
/** If true, this key generator will be run after the insert statement, if false, it will be
* run before. */
public boolean isPostFactum ();
public KeyGenerator (GeneratedValue gv, String table, String column)
{
_allocationSize = gv.allocationSize();
_initialValue = gv.initialValue();
_table = table;
_column = column;
}
/** Prepares the generator for operation. */
public void init (Connection conn) throws SQLException;
/**
* If true, this key generator will be run after the insert statement, if false, it will be run
* before.
*/
public abstract boolean isPostFactum ();
/** Fetch/generate the next primary key value. */
public int nextGeneratedValue (Connection conn) throws SQLException;
/**
* Prepares the generator for operation.
*/
public abstract void init (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Fetch/generate the next primary key value.
*/
public abstract int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Returns the name of the table for which we're generating a key value.
*/
public String getTable ()
{
return _table;
}
/**
* Returns the name of the column for which we're generating a key value.
*/
public String getColumn ()
{
return _column;
}
protected int _initialValue;
protected int _allocationSize;
protected String _table;
protected String _column;
}
@@ -24,6 +24,8 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import com.samskivert.jdbc.DatabaseLiaison;
/**
* Encapsulates a modification of persistent objects.
*/
@@ -33,23 +35,22 @@ public abstract class Modifier
* A simple modifier that executes a single SQL statement. No cache flushing is done as a
* result of this operation.
*/
public static class Simple extends Modifier
public abstract static class Simple extends Modifier
{
public Simple (String query) {
public Simple () {
super(null);
_query = query;
}
public int invoke (Connection conn) throws SQLException {
public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
Statement stmt = conn.createStatement();
try {
return stmt.executeUpdate(_query);
return stmt.executeUpdate(createQuery(liaison));
} finally {
stmt.close();
}
}
protected String _query;
protected abstract String createQuery (DatabaseLiaison liaison);
}
/**
@@ -98,10 +99,10 @@ public abstract class Modifier
}
/**
* Overriden to perform the actual database modifications represented by this object;
* should return the number of modified rows.
* Overriden to perform the actual database modifications represented by this object; should
* return the number of modified rows.
*/
public abstract int invoke (Connection conn) throws SQLException;
public abstract int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException;
/**
* Constructs a {@link Modifier} without a cache invalidator.
@@ -20,21 +20,19 @@
package com.samskivert.jdbc.depot;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* A special form of {@link Where} clause that specifies an explicit range of database rows. It
* does not implement {@link CacheKey} but it does implement {@link CacheInvalidator} which means
* it can be sent into e.g. {@link DepotRepository#deleteAll) and have it clean up after itself.
*/
public class MultiKey<T extends PersistentRecord> extends Where
public class MultiKey<T extends PersistentRecord> extends WhereClause
implements CacheInvalidator
{
/**
@@ -69,8 +67,6 @@ public class MultiKey<T extends PersistentRecord> extends Where
public MultiKey (Class<T> pClass, String[] sFields, Comparable[] sValues,
String mField, Comparable[] mValues)
{
// TODO
super(null);
if (sFields.length != sValues.length) {
throw new IllegalArgumentException(
"Key field and values arrays must be of equal length.");
@@ -84,52 +80,16 @@ public class MultiKey<T extends PersistentRecord> extends Where
}
}
@Override // from QueryClause
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
@Override // from Where
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
{
builder.append(" where ");
boolean first = true;
for (Map.Entry entry : _map.entrySet()) {
if (first) {
first = false;
} else {
builder.append(" and ");
}
builder.append(entry.getKey());
builder.append(entry.getValue() == null ? " is null " : " = ? ");
}
if (!first) {
builder.append(" and ");
}
builder.append(_mField).append(" in (");
for (int ii = 0; ii < _mValues.length; ii ++) {
if (ii > 0) {
builder.append(", ");
}
builder.append("?");
}
builder.append(")");
}
@Override // from Where
public int bindClauseArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
{
for (Map.Entry entry : _map.entrySet()) {
if (entry.getValue() != null) {
pstmt.setObject(argIdx ++, entry.getValue());
}
}
for (int ii = 0; ii < _mValues.length; ii++) {
pstmt.setObject(argIdx ++, _mValues[ii]);
}
return argIdx;
// nothing to add
}
// from CacheInvalidator
@@ -142,8 +102,28 @@ public class MultiKey<T extends PersistentRecord> extends Where
}
}
protected Class<T> _pClass;
protected HashMap<String, Comparable> _map;
public Class<T> getPersistentClass ()
{
return _pClass;
}
public Map<String, Comparable> getSingleFieldsMap ()
{
return _map;
}
public String getMultiField ()
{
return _mField;
}
public Comparable[] getMultiValues ()
{
return _mValues;
}
protected String _mField;
protected Comparable[] _mValues;
protected Class<T> _pClass;
protected HashMap<String, Comparable> _map;
}
@@ -0,0 +1,191 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne, 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.Time;
import java.sql.Timestamp;
import com.samskivert.jdbc.depot.FieldMarshaller.BooleanMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteArrayMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteEnumMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.DoubleMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.FloatMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.IntArrayMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.IntMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.LongMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ObjectMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ShortMarshaller;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.operator.Conditionals.Match;
public class MySQLBuilder
extends SQLBuilder
{
public class MSBuildVisitor extends BuildVisitor
{
protected MSBuildVisitor (DepotTypes types)
{
super(types);
}
@Override
public void visit (Match match)
throws Exception
{
_builder.append("match(");
ColumnExp[] columns = match.getColumns();
for (int ii = 0; ii < columns.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
columns[ii].accept(this);
}
_builder.append(") against (?");
switch (match.getMode()) {
case BOOLEAN:
_builder.append(" in boolean mode");
break;
case NATURAL_LANGUAGE:
_builder.append(" in natural language mode");
break;
}
if (match.isQueryExpansion()) {
_builder.append(" with query expansion");
}
_builder.append(")");
}
protected void appendTableName (Class<? extends PersistentRecord> type)
{
_builder.append(_types.getTableName(type));
}
protected void appendTableAbbreviation (Class<? extends PersistentRecord> type)
{
_builder.append(_types.getTableAbbreviation(type));
}
protected void appendColumn (Class<? extends PersistentRecord> type, String field)
{
_builder.append(_types.getColumnName(type, field));
}
protected void appendField (String field)
{
_builder.append(field);
}
}
public class MSBindVisitor extends BindVisitor
{
protected MSBindVisitor (DepotTypes types, PreparedStatement stmt)
{
super(types, stmt);
}
@Override
public void visit (Match match)
throws Exception
{
_stmt.setString(_argIdx ++, match.getQuery());
}
}
public MySQLBuilder (DepotTypes types)
{
super(types);
}
@Override
protected BuildVisitor getBuildVisitor ()
{
return new MSBuildVisitor(_types);
}
@Override
protected BindVisitor getBindVisitor (PreparedStatement stmt)
{
return new MSBindVisitor(_types, stmt);
}
@Override
protected <T> String getColumnType (FieldMarshaller fm)
{
if (fm instanceof ByteMarshaller) {
return "TINYINT";
} else if (fm instanceof ShortMarshaller) {
return "SMALLINT";
} else if (fm instanceof IntMarshaller) {
return "INTEGER";
} else if (fm instanceof LongMarshaller) {
return "BIGINT";
} else if (fm instanceof FloatMarshaller) {
return "FLOAT";
} else if (fm instanceof DoubleMarshaller) {
return "DOUBLE";
} else if (fm instanceof ObjectMarshaller) {
Class<?> ftype = fm.getField().getType();
if (ftype.equals(Byte.class)) {
return "SMALLINT";
} else if (ftype.equals(Short.class)) {
return "SMALLINT";
} else if (ftype.equals(Integer.class)) {
return "INTEGER";
} else if (ftype.equals(Long.class)) {
return "BIGINT";
} else if (ftype.equals(Float.class)) {
return "FLOAT";
} else if (ftype.equals(Double.class)) {
return "DOUBLE";
} else if (ftype.equals(String.class)) {
return "VARCHAR";
} else if (ftype.equals(Date.class)) {
return "DATE";
} else if (ftype.equals(Time.class)) {
return "DATETIME";
} else if (ftype.equals(Timestamp.class)) {
return "DATETIME";
} else if (ftype.equals(Blob.class)) {
return "BYTEA";
} else if (ftype.equals(Clob.class)) {
return "TEXT";
} else {
throw new IllegalArgumentException(
"Don't know how to create SQL for " + ftype + ".");
}
} else if (fm instanceof ByteArrayMarshaller) {
return "VARBINARY";
} else if (fm instanceof IntArrayMarshaller) {
return "BLOB";
} else if (fm instanceof ByteEnumMarshaller) {
return "TINYINT";
} else if (fm instanceof BooleanMarshaller) {
return "TINYINT";
} else {
throw new IllegalArgumentException("Unknown field marshaller type: " + fm.getClass());
}
}
}
@@ -40,8 +40,11 @@ import com.samskivert.io.PersistenceException;
import com.samskivert.util.StringUtil;
import com.samskivert.jdbc.ConnectionProvider;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.DuplicateKeyException;
import com.samskivert.jdbc.LiaisonRegistry;
import com.samskivert.jdbc.MySQLLiaison;
import com.samskivert.jdbc.PostgreSQLLiaison;
/**
* Defines a scope in which global annotations are shared.
@@ -124,6 +127,23 @@ public class PersistenceContext
_ident = ident;
_conprov = conprov;
_cachemgr = CacheManager.getInstance();
_liaison = LiaisonRegistry.getLiaison(conprov.getURL(ident));
}
/**
* Create and return a new {@link SQLBuilder} for the appropriate dialect.
*
* TODO: At some point perhaps use a more elegant way of discerning our dialect.
*/
public SQLBuilder getSQLBuilder (DepotTypes types)
{
if (_liaison instanceof PostgreSQLLiaison) {
return new PostgreSQLBuilder(types);
}
if (_liaison instanceof MySQLLiaison) {
return new MySQLBuilder(types);
}
throw new IllegalArgumentException("Unknown liaison type: " + _liaison.getClass());
}
/**
@@ -399,25 +419,21 @@ public class PersistenceContext
Connection conn = _conprov.getConnection(_ident, isReadOnlyQuery);
try {
if (isReadOnlyQuery) {
// if this becomes more complex than this single statement,
// then this should turn into a method call that contains
// the complexity
return query.invoke(conn);
// if this becomes more complex than this single statement, then this should turn
// into a method call that contains the complexity
return query.invoke(conn, _liaison);
} else {
// if this becomes more complex than this single statement,
// then this should turn into a method call that contains
// the complexity
return modifier.invoke(conn);
// if this becomes more complex than this single statement, then this should turn
// into a method call that contains the complexity
return modifier.invoke(conn, _liaison);
}
} catch (SQLException sqe) {
if (!isReadOnlyQuery) {
// convert this exception to a DuplicateKeyException if
// appropriate
String msg = sqe.getMessage();
if (msg != null && msg.indexOf("Duplicate entry") != -1) {
throw new DuplicateKeyException(msg);
// convert this exception to a DuplicateKeyException if appropriate
if (_liaison.isDuplicateRowException(sqe)) {
throw new DuplicateKeyException(sqe.getMessage());
}
}
@@ -425,7 +441,7 @@ public class PersistenceContext
_conprov.connectionFailed(_ident, isReadOnlyQuery, conn, sqe);
conn = null;
if (retryOnTransientFailure && isTransientException(sqe)) {
if (retryOnTransientFailure && _liaison.isTransientException(sqe)) {
// 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
@@ -448,21 +464,10 @@ public class PersistenceContext
return invoke(query, modifier, false);
}
/**
* Check whether the specified exception is a transient failure that can be retried.
*/
protected boolean isTransientException (SQLException sqe)
{
// TODO: this is MySQL specific. This was snarfed from MySQLLiaison.
String msg = sqe.getMessage();
return (msg != null && (msg.indexOf("Lost connection") != -1 ||
msg.indexOf("link failure") != -1 ||
msg.indexOf("Broken pipe") != -1));
}
protected String _ident;
protected ConnectionProvider _conprov;
protected CacheManager _cachemgr;
protected DatabaseLiaison _liaison;
protected Map<String, Set<CacheListener<?>>> _listenerSets =
new HashMap<String, Set<CacheListener<?>>>();
@@ -0,0 +1,155 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne, 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.Time;
import java.sql.Timestamp;
import com.samskivert.jdbc.depot.FieldMarshaller.BooleanMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteArrayMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteEnumMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.DoubleMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.FloatMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.IntArrayMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.IntMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.LongMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ObjectMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ShortMarshaller;
public class PostgreSQLBuilder
extends SQLBuilder
{
public class PGBuildVisitor extends BuildVisitor
{
protected PGBuildVisitor (DepotTypes types)
{
super(types);
}
protected void appendTableName (Class<? extends PersistentRecord> type)
{
_builder.append("\"").append(_types.getTableName(type)).append("\"");
}
protected void appendTableAbbreviation (Class<? extends PersistentRecord> type)
{
_builder.append("\"").append(_types.getTableAbbreviation(type)).append("\"");
}
protected void appendColumn (Class<? extends PersistentRecord> type, String field)
{
_builder.append("\"").append(_types.getColumnName(type, field)).append("\"");
}
protected void appendField (String field)
{
_builder.append("\"").append(field).append("\"");
}
}
public class PGBindVisitor extends BindVisitor
{
protected PGBindVisitor (DepotTypes types, PreparedStatement stmt)
{
super(types, stmt);
}
}
public PostgreSQLBuilder (DepotTypes types)
{
super(types);
}
@Override
protected BuildVisitor getBuildVisitor ()
{
return new PGBuildVisitor(_types);
}
@Override
protected BindVisitor getBindVisitor (PreparedStatement stmt)
{
return new PGBindVisitor(_types, stmt);
}
@Override
protected <T> String getColumnType (FieldMarshaller fm)
{
if (fm instanceof ByteMarshaller) {
return "SMALLINT";
} else if (fm instanceof ShortMarshaller) {
return "SMALLINT";
} else if (fm instanceof IntMarshaller) {
return "INTEGER";
} else if (fm instanceof LongMarshaller) {
return "BIGINT";
} else if (fm instanceof FloatMarshaller) {
return "FLOAT";
} else if (fm instanceof DoubleMarshaller) {
return "DOUBLE";
} else if (fm instanceof ObjectMarshaller) {
Class<?> ftype = fm.getField().getType();
if (ftype.equals(Byte.class)) {
return "SMALLINT";
} else if (ftype.equals(Short.class)) {
return "SMALLINT";
} else if (ftype.equals(Integer.class)) {
return "INTEGER";
} else if (ftype.equals(Long.class)) {
return "BIGINT";
} else if (ftype.equals(Float.class)) {
return "FLOAT";
} else if (ftype.equals(Double.class)) {
return "DOUBLE";
} else if (ftype.equals(String.class)) {
return "VARCHAR";
} else if (ftype.equals(Date.class)) {
return "DATE";
} else if (ftype.equals(Time.class)) {
return "TIME";
} else if (ftype.equals(Timestamp.class)) {
return "TIMESTAMP";
} else if (ftype.equals(Blob.class)) {
return "BYTEA";
} else if (ftype.equals(Clob.class)) {
return "TEXT";
} else {
throw new IllegalArgumentException(
"Don't know how to create SQL for " + ftype + ".");
}
} else if (fm instanceof ByteArrayMarshaller) {
return "BYTEA";
} else if (fm instanceof IntArrayMarshaller) {
return "BYTEA";
} else if (fm instanceof ByteEnumMarshaller) {
return "SMALLINT";
} else if (fm instanceof BooleanMarshaller) {
return "BOOLEAN";
} else {
throw new IllegalArgumentException("Unknown field marshaller type: " + fm.getClass());
}
}
}
+20 -1
View File
@@ -23,11 +23,30 @@ package com.samskivert.jdbc.depot;
import java.sql.Connection;
import java.sql.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
/**
* The base of all read-only queries.
*/
public interface Query<T>
{
/** A simple base class for non-complex queries. */
public abstract class TrivialQuery<T> implements Query<T>
{
public abstract T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException;
public CacheKey getCacheKey () {
return null;
}
public T transformCacheHit (CacheKey key, T value) {
return value;
}
public void updateCache (PersistenceContext ctx, T result) {
}
}
/**
* Any query may elect to utilize the built-in cache by returning a non-null {@link CacheKey}
* in this method. This is done automatically by the {@link DepotRepository} when looking up
@@ -43,7 +62,7 @@ public interface Query<T>
/**
* Performs the actual JDBC operations associated with this query.
*/
public T invoke (Connection conn)
public T invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
@@ -1,43 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot;
/**
* This type is used during the construction of SQL queries to map persistent classes
* to table names and table abbreviations. Its primary implementation is {@link DepotTypes}.
*/
public interface QueryBuilderContext<T>
{
/**
* Maps a class referenced by this query to its associated table.
*
* This method is called by individual clauses.
*/
public String getTableName (Class<? extends PersistentRecord> cl);
/**
* Maps a class referenced by this query to the abbreviation used for its associated table. The
* abbreviation is meaningful only within the scope of this particular query.
*
* This method is called by individual clauses.
*/
public String getTableAbbreviation (Class<? extends PersistentRecord> cl);
}
@@ -0,0 +1,195 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.annotation.Column;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
import com.samskivert.jdbc.depot.clause.QueryClause;
/**
* At the heart of Depot's SQL generation, this object constructs two {@link ExpressionVisitor}
* objects and executes them, one after another; the first one constructs SQL as it recurses, the
* other binds arguments in the {@link PreparedStatement}. This class must be subclassed by the
* database dialects we wish to support.
*/
public abstract class SQLBuilder
{
public SQLBuilder (DepotTypes types)
{
_types = types;
}
/**
* 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.
*/
public void newQuery (QueryClause clause)
{
_clause = clause;
_buildVisitor = getBuildVisitor();
try {
_clause.accept(_buildVisitor);
} catch (Exception e) {
throw new RuntimeException("Failed to build SQL", e);
}
}
/**
* After {@link #newQuery(QueryClause)} has been executed, this method is run to recurse
* through the {@link QueryClause} structure, setting the {@link PreparedStatement} arguments
* that were defined in the generated SQL.
*
* This method throws {@link SQLException} and is thus meant to be called from within
* {@link Query#invoke(Connection)} and {@link Modifier#invoke(Connection)}.
*/
public PreparedStatement prepare (Connection conn)
throws SQLException
{
if (_buildVisitor == null) {
throw new IllegalArgumentException("Cannot prepare query until it's been built.");
}
PreparedStatement stmt = conn.prepareStatement(_buildVisitor.getQuery());
_bindVisitor = getBindVisitor(stmt);
try {
_clause.accept(_bindVisitor);
} catch (SQLException se) {
throw se;
} catch (Exception e) {
throw new RuntimeException("Failed to find SQL parameters", e);
}
return stmt;
}
/**
* Generates the SQL needed to construct a database column for field represented by the given
* {@link FieldMarshaller}.
*
* TODO: This method should be split into several parts that are more easily overridden on a
* case-by-case basis in the dialectal subclasses.
*/
public <T> String buildColumnDefinition (FieldMarshaller<T> fm)
{
// if this field is @Computed, it has no SQL definition
if (fm.getComputed() != null) {
return null;
}
Field field = fm.getField();
// read our column metadata from the annotation (if it exists); annoyingly we can't create
// a Column instance to read the defaults so we have to duplicate them here
int length = 255;
boolean nullable = false;
boolean unique = false;
String type = "";
String defval = "";
Column column = field.getAnnotation(Column.class);
if (column != null) {
nullable = column.nullable();
unique = column.unique();
length = column.length();
type = column.type();
defval = column.defaultValue();
}
// create our SQL column definition
StringBuilder builder = new StringBuilder();
boolean typeDone = false;
// handle primary keyness
GeneratedValue genValue = fm.getGeneratedValue();
if (genValue != null) {
switch (genValue.strategy()) {
case AUTO:
case IDENTITY:
builder.append(" SERIAL UNIQUE");
typeDone = true;
break;
case SEQUENCE: // TODO
throw new IllegalArgumentException(
"SEQUENCE key generation strategy not yet supported.");
case TABLE:
// nothing to do here, it'll be handled later
break;
}
}
if (!typeDone) {
if (type.length() == 0) {
type = getColumnType(fm);
}
builder.append(" ").append(type);
// if this is a VARCHAR field, add the length
if (type.equals("VARCHAR") || type.equals("VARBINARY")) {
builder.append("(").append(length).append(")");
}
// TODO: handle precision and scale
// handle nullability and uniqueness
if (!nullable) {
builder.append(" NOT NULL");
}
if (unique) {
builder.append(" UNIQUE");
}
// append the default value if one was specified
if (defval.length() > 0) {
builder.append(" DEFAULT ").append(defval);
}
}
return builder.toString();
}
/**
* Overridden by subclasses to create a dialect-specific {@link BuildVisitor}.
*/
protected abstract BuildVisitor getBuildVisitor ();
/**
* Overridden by subclasses to create a dialect-specific {@link BindVisitor}.
*/
protected abstract BindVisitor getBindVisitor (PreparedStatement stmt);
/**
* Overridden by subclasses to figure the dialect-specific SQL type of the given field.
*/
protected abstract <T> String getColumnType (FieldMarshaller fm);
/** The class that maps persistent classes to marshallers. */
protected DepotTypes _types;
protected QueryClause _clause;
protected BuildVisitor _buildVisitor;
protected BindVisitor _bindVisitor;
}
@@ -1,288 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.annotation.Computed;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.ForUpdate;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.GroupBy;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.Limit;
import com.samskivert.jdbc.depot.clause.OrderBy;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.Where;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class SQLQueryBuilder<T>
{
/** The from override clause, if any. */
public FromOverride fromOverride;
/** The where clause. */
public Where where;
/** A list of join clauses, each potentially referencing a new class. */
public List<Join> joinClauses = new ArrayList<Join>();
/** The order by clause, if any. */
public OrderBy orderBy;
/** The group by clause, if any. */
public GroupBy groupBy;
/** The limit clause, if any. */
public Limit limit;
/** The For Update clause, if any. */
public ForUpdate forUpdate;
/**
* Constructs a {@link DepotTypes} object for your convenience given similar arguments used to
* construct a {@link SQLQueryBuilder}. This method mainly offloads the task of interrogating
* the various clauses for what persistent classes they introduce into the query.
*/
public static <T> DepotTypes<T> getDepotTypes (
PersistenceContext ctx, Class<? extends PersistentRecord> type, QueryClause... clauses)
throws PersistenceException
{
Set<Class<? extends PersistentRecord>> classSet =
new HashSet<Class<? extends PersistentRecord>>();
if (type != null) {
classSet.add(type);
}
for (QueryClause clause : clauses) {
if (clause != null) {
clause.addClasses(classSet);
}
}
return new DepotTypes<T>(ctx, type, classSet);
}
/**
* Creates a new Query object to generate one or more instances of the specified persistent
* class, as dictated by the key and query clauses. A persistence context is supplied for
* instantiation of marshallers, which may trigger table creations and schema migrations.
*/
public SQLQueryBuilder (PersistenceContext ctx, DepotTypes<T> types, String[] fields,
QueryClause... clauses)
{
_builder = new StringBuilder("select ");
_types = types;
// iterate over the clauses and sort them into the different types we understand
for (QueryClause clause : clauses) {
if (clause == null) {
continue;
}
if (clause instanceof Where) {
if (where != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Where clauses.");
}
where = (Where) clause;
} else if (clause instanceof FromOverride) {
if (fromOverride != null) {
throw new IllegalArgumentException(
"Query can't contain multiple FromOverride clauses.");
}
fromOverride = (FromOverride) clause;
} else if (clause instanceof Join) {
joinClauses.add((Join) clause);
} else if (clause instanceof FieldOverride) {
_disMap.put(((FieldOverride) clause).getField(),
((FieldOverride) clause));
} else if (clause instanceof OrderBy) {
if (orderBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple OrderBy clauses.");
}
orderBy = (OrderBy) clause;
} else if (clause instanceof GroupBy) {
if (groupBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple GroupBy clauses.");
}
groupBy = (GroupBy) clause;
} else if (clause instanceof Limit) {
if (limit != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Limit clauses.");
}
limit = (Limit) clause;
} else if (clause instanceof ForUpdate) {
if (forUpdate != null) {
throw new IllegalArgumentException(
"Query can't contain multiple For Update clauses.");
}
forUpdate = (ForUpdate) clause;
}
}
Computed entityComputed = _types.getMainType().getAnnotation(Computed.class);
// iterate over the fields we're filling in and figure out whence each one comes
boolean skip = true;
for (String field : fields) {
if (!skip) {
_builder.append(", ");
}
skip = false;
// first, see if there's a field override
FieldOverride clause1 = _disMap.get(field);
if (clause1 != null) {
clause1.appendClause(_types, _builder);
continue;
}
// figure out the class we're selecting from unless we're otherwise overriden:
// for a concrete record, simply use the corresponding table; for a computed one,
// default to the shadowed concrete record, or null if there isn't one
Class<? extends PersistentRecord> tableClass =
entityComputed == null ? _types.getMainType() : entityComputed.shadowOf();
// handle the field-level @Computed annotation, if there is one
Computed fieldComputed =
_types.getMainMarshaller().getFieldMarshaller(field).getComputed();
if (fieldComputed != null) {
// check if the computed field has a literal SQL definition
if (fieldComputed.fieldDefinition().length() > 0) {
_builder.append(fieldComputed.fieldDefinition()).append(" as ").append(field);
continue;
}
// or if we can simply ignore the field
if (!fieldComputed.required()) {
skip = true;
continue;
}
// else see if there's an overriding shadowOf definition
if (fieldComputed.shadowOf() != null) {
tableClass = fieldComputed.shadowOf();
}
}
// if we get this far we hopefully have a table to select from
if (tableClass != null) {
String tableName = _types.getTableAbbreviation(tableClass);
_builder.append(tableName).append(".").append(field);
continue;
}
// else owie
throw new IllegalArgumentException(
"Persistent field has no definition [class=" + _types.getMainType() +
", field=" + field + "]");
}
}
public PreparedStatement prepare (Connection conn)
throws SQLException
{
if (fromOverride != null) {
fromOverride.appendClause(_types, _builder);
} else if (_types.getMainTableName() != null) {
_builder.append(" from ").append(_types.getMainTableName()).
append(" as ").append(_types.getMainTableAbbreviation());
} else {
throw new SQLException("Query on @Computed entity with no FromOverrideClause.");
}
for (Join clause : joinClauses) {
clause.appendClause(_types, _builder);
}
if (where != null) {
where.appendClause(_types, _builder);
}
if (groupBy != null) {
groupBy.appendClause(_types, _builder);
}
if (orderBy != null) {
orderBy.appendClause(_types, _builder);
}
if (limit != null) {
limit.appendClause(_types, _builder);
}
if (forUpdate != null) {
forUpdate.appendClause(_types, _builder);
}
PreparedStatement pstmt = conn.prepareStatement(_builder.toString());
int argIdx = 1;
for (Join clause : joinClauses) {
argIdx = clause.bindClauseArguments(pstmt, argIdx);
}
if (where != null) {
argIdx = where.bindClauseArguments(pstmt, argIdx);
}
if (groupBy != null) {
argIdx = groupBy.bindClauseArguments(pstmt, argIdx);
}
if (orderBy != null) {
argIdx = orderBy.bindClauseArguments(pstmt, argIdx);
}
if (limit != null) {
argIdx = limit.bindClauseArguments(pstmt, argIdx);
}
if (forUpdate != null) {
argIdx = forUpdate.bindClauseArguments(pstmt, argIdx);
}
return pstmt;
}
/** A StringBuilder to hold the constructed SQL. */
protected StringBuilder _builder;
/** The class that maps persistent classes to marshallers. */
protected DepotTypes<T> _types;
/** Persistent class fields mapped to field override clauses. */
protected Map<String, FieldOverride> _disMap = new HashMap<String, FieldOverride>();
}
@@ -25,23 +25,25 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
import com.samskivert.jdbc.depot.annotation.TableGenerator;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.LiaisonRegistry;
/**
* Generates primary keys using an external table
* Generates primary keys using an external table .
*/
public class TableKeyGenerator implements KeyGenerator
public class TableKeyGenerator extends KeyGenerator
{
public TableKeyGenerator (TableGenerator annotation)
public TableKeyGenerator (TableGenerator tg, GeneratedValue gv, String table, String column)
{
_table = defStr(annotation.table(), "IdSequences");
_pkColumnName = defStr(annotation.pkColumnName(), "sequence");
_pkColumnValue = defStr(annotation.pkColumnValue(), "default");
_valueColumnName = defStr(annotation.valueColumnName(), "value");
_allocationSize = annotation.allocationSize();
_initialValue = annotation.initialValue();
super(gv, table, column);
_valueTable = defStr(tg.table(), "IdSequences");
_pkColumnName = defStr(tg.pkColumnName(), "sequence");
_pkColumnValue = defStr(tg.pkColumnValue(), "default");
_valueColumnName = defStr(tg.valueColumnName(), "value");
}
// from interface KeyGenerator
@@ -51,20 +53,23 @@ public class TableKeyGenerator implements KeyGenerator
}
// from interface KeyGenerator
public void init (Connection conn)
public void init (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
// make sure our table exists
JDBCUtil.createTableIfMissing(conn, _table, new String[] {
_pkColumnName + " VARCHAR(255) PRIMARY KEY",
_valueColumnName + " INTEGER NOT NULL"
}, "");
liaison.createTableIfMissing(
conn, _valueTable,
new String[] { _pkColumnName, _valueColumnName },
new String[] { "VARCHAR(255)", "INTEGER NOT NULL" },
null,
new String[] { _pkColumnName });
// and also that there's a row in it for us
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(
"SELECT * FROM " + _table + " WHERE " + _pkColumnName + " = ?");
" SELECT * FROM " + liaison.tableSQL(_valueTable) +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ?");
stmt.setString(1, _pkColumnValue);
if (stmt.executeQuery().next()) {
return;
@@ -72,8 +77,10 @@ public class TableKeyGenerator implements KeyGenerator
JDBCUtil.close(stmt);
stmt = null;
stmt = conn.prepareStatement("INSERT INTO " + _table + " SET " +
_pkColumnName + " = ?, " + _valueColumnName + " = ?");
stmt = conn.prepareStatement(
" INSERT INTO " + liaison.tableSQL(_valueTable) + " (" +
liaison.columnSQL(_pkColumnName) + ", " + liaison.columnSQL(_valueColumnName) +
") VALUES (?, ?)");
stmt.setString(1, _pkColumnValue);
stmt.setInt(2, _initialValue);
stmt.executeUpdate();
@@ -84,27 +91,32 @@ public class TableKeyGenerator implements KeyGenerator
}
// from interface KeyGenerator
public int nextGeneratedValue (Connection conn)
public int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
PreparedStatement stmt = null;
try {
String query = "SELECT " + _valueColumnName + " FROM " + _table +
" WHERE " + _pkColumnName + "= ? FOR UPDATE";
// TODO: Make this lockless!
String query =
" SELECT " + liaison.columnSQL(_valueColumnName) +
" FROM " + liaison.tableSQL(_valueTable) +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ? FOR UPDATE";
stmt = conn.prepareStatement(query);
stmt.setString(1, _pkColumnValue);
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
throw new SQLException("Failed to find next primary key value [table=" + _table +
", column=" + _valueColumnName +
throw new SQLException("Failed to find next primary key value " +
"[table=" + _valueTable + ", column=" + _valueColumnName +
", where=" + _pkColumnName + "=" + _pkColumnValue + "]");
}
int val = rs.getInt(1);
JDBCUtil.close(stmt);
stmt = conn.prepareStatement("UPDATE " + _table + " SET " + _valueColumnName + " = ? " +
"WHERE " + _pkColumnName + " = ?");
stmt = conn.prepareStatement(
" UPDATE " + liaison.tableSQL(_valueTable) +
" SET " + liaison.columnSQL(_valueColumnName) + " = ? " +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ?");
stmt.setInt(1, val + _allocationSize);
stmt.setString(2, _pkColumnValue);
stmt.executeUpdate();
@@ -126,10 +138,8 @@ public class TableKeyGenerator implements KeyGenerator
return value;
}
protected String _table;
protected String _valueTable;
protected String _pkColumnName;
protected String _pkColumnValue;
protected String _valueColumnName;
protected int _initialValue;
protected int _allocationSize;
}
@@ -18,3 +18,13 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot;
import com.samskivert.jdbc.depot.clause.QueryClause;
/**
* Currently only exists as a type without any functionality of its own.
*/
public abstract class WhereClause extends QueryClause
{
}
@@ -53,10 +53,9 @@ public @interface Column
boolean nullable () default false;
/**
* The SQL fragment that is used when generating the DDL for the column. Defaults to the
* generated SQL to create a column of the inferred type.
* The SQL type that is used when generating the DDL for the column.
*/
String columnDefinition () default "";
String type() default "";
/**
* The column length. (Applies to String and byte[] columns.)
@@ -25,13 +25,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* Marks a field as computed, meaning it is ignored for schema purposes and it does not directly
* correspond to a column in a table, thus its value must be overridden in the {@link
* QueryBuilderContext}.
* correspond to a column in a table.
*/
@Retention(value=RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
@@ -37,8 +37,4 @@ public @interface Entity
/** Defines indices to add to this entity's table. */
Index[] indices () default {};
/** Additional SQL to be added when creating this entity's table for the first time. You can
* specify a MySQL table storage type for example. */
String postamble () default "";
}
@@ -40,4 +40,18 @@ public @interface GeneratedValue
/** Identifies the strategy to be used to generate this value. */
GenerationType strategy () default GenerationType.AUTO;
/**
* The initial value to be used when allocating id numbers from the generator. The default
* initial value is 1. <em>Note:</em> this default differs from the value used by the EJB3
* persistence framework.
*/
int initialValue () default 1;
/**
* The amount to increment by when allocating id numbers from the generator. The default
* allocation size is 1. <em>Note:</em> this default differs from the value used by the EJB3
* persistence framework.
*/
int allocationSize () default 1;
}
@@ -63,18 +63,4 @@ public @interface TableGenerator
* the primary key column of the generator table
*/
String pkColumnValue () default "";
/**
* The initial value to be used when allocating id numbers from the generator. The default
* initial value is 1. <em>Note:</em> this default differs from the value used by the EJB3
* persistence framework.
*/
int initialValue () default 1;
/**
* The amount to increment by when allocating id numbers from the generator. The default
* allocation size is 1. <em>Note:</em> this default differs from the value used by the EJB3
* persistence framework.
*/
int allocationSize () default 1;
}
@@ -0,0 +1,67 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne
//
// 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.WhereClause;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class DeleteClause<T extends PersistentRecord> extends QueryClause
{
public DeleteClause (Class<? extends PersistentRecord> pClass, WhereClause where)
{
_pClass = pClass;
_where = where;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public WhereClause getWhereClause ()
{
return _where;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
/** The type of persistent record on which we operate. */
protected Class<? extends PersistentRecord> _pClass;
/** The where clause. */
protected WhereClause _where;
}
@@ -20,14 +20,12 @@
package com.samskivert.jdbc.depot.clause;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
@@ -68,11 +66,22 @@ public class FieldOverride extends QueryClause
return _field;
}
// from QueryClause
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
public SQLExpression getOverride ()
{
_override.appendExpression(query, builder);
builder.append(" as ").append(_field);
return _override;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_override.addClasses(classSet);
}
// from SQLExpression
public void accept (ExpressionVisitor visitor)
throws Exception
{
visitor.visit(this);
}
/** The name of the field on the persistent object to override. */
@@ -80,4 +89,5 @@ public class FieldOverride extends QueryClause
/** The overriding expression. */
protected SQLExpression _override;
}
@@ -20,21 +20,24 @@
package com.samskivert.jdbc.depot.clause;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Represents a FOR UPDATE clause.
*/
public class ForUpdate extends QueryClause
{
// from QueryClause
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
builder.append(" for update ");
}
}
@@ -20,14 +20,13 @@
package com.samskivert.jdbc.depot.clause;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Completely overrides the FROM clause, if it exists.
@@ -54,27 +53,25 @@ public class FromOverride extends QueryClause
_fromClasses.addAll(fromClasses);
}
// from QueryClause
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
public List<Class<? extends PersistentRecord>> getFromClasses ()
{
classSet.addAll(_fromClasses);
return _fromClasses;
}
// from QueryClause
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
builder.append(" from " );
for (int ii = 0; ii < _fromClasses.size(); ii++) {
if (ii > 0) {
builder.append(", ");
}
builder.append(query.getTableName(_fromClasses.get(ii))).
append(" as ").append(query.getTableAbbreviation(_fromClasses.get(ii)));
classSet.addAll(getFromClasses());
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
throws Exception
{
builder.visit(this);
}
/** The classes of the tables we're selecting from. */
protected ArrayList<Class<? extends PersistentRecord>> _fromClasses =
protected List<Class<? extends PersistentRecord>> _fromClasses =
new ArrayList<Class<? extends PersistentRecord>>();
}
@@ -20,12 +20,10 @@
package com.samskivert.jdbc.depot.clause;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
@@ -38,28 +36,24 @@ public class GroupBy extends QueryClause
_values = values;
}
// from QueryClause
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
public SQLExpression[] getValues ()
{
builder.append(" group by ");
for (int ii = 0; ii < _values.length; ii++) {
if (ii > 0) {
builder.append(", ");
}
_values[ii].appendExpression(query, builder);
}
return _values;
}
// from QueryClause
public int bindClauseArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
for (int ii = 0; ii < _values.length; ii++) {
argIdx = _values[ii].bindExpressionArguments(pstmt, argIdx);
builder.visit(this);
}
return argIdx;
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
// I can't imagine a GROUP BY clause bringing in new tables... ?
}
/** The expressions that are generated for the clause. */
protected SQLExpression[] _values;
}
@@ -0,0 +1,74 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne
//
// 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class InsertClause<T extends PersistentRecord> extends QueryClause
{
public InsertClause (Class<? extends PersistentRecord> pClass, Object pojo, String ixField)
{
_pClass = pClass;
_pojo = pojo;
_ixField = ixField;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public Object getPojo ()
{
return _pojo;
}
public String getIndexField ()
{
return _ixField;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
// If we add SQLExpression[] values INSERT, remember to recurse into them here.
}
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
protected Class<? extends PersistentRecord> _pClass;
/** The object from which to fetch values, or null. */
protected Object _pojo;
protected String _ixField;
}
@@ -20,16 +20,14 @@
package com.samskivert.jdbc.depot.clause;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.Conditionals.*;
import com.samskivert.jdbc.depot.operator.Conditionals.Equals;
/**
* Represents a JOIN.
@@ -50,7 +48,7 @@ public class Join extends QueryClause
public Join (ColumnExp primary, ColumnExp join)
throws PersistenceException
{
_joinClass = join.pClass;
_joinClass = join.getPersistentClass();
_joinCondition = new Equals(primary, join);
}
@@ -60,12 +58,6 @@ public class Join extends QueryClause
_joinCondition = joinCondition;
}
// from QueryClause
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_joinClass);
}
/**
* Configures the type of join to be performed.
*/
@@ -75,30 +67,31 @@ public class Join extends QueryClause
return this;
}
// from QueryClause
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
public Type getType ()
{
switch (_type) {
case INNER:
builder.append(" inner join " );
break;
case LEFT_OUTER:
builder.append(" left outer join " );
break;
case RIGHT_OUTER:
builder.append(" right outer join " );
break;
}
builder.append(query.getTableName(_joinClass)).append(" as ");
builder.append(query.getTableAbbreviation(_joinClass)).append(" on ");
_joinCondition.appendExpression(query, builder);
return _type;
}
// from QueryClause
public int bindClauseArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public Class<? extends PersistentRecord> getJoinClass ()
{
return _joinCondition.bindExpressionArguments(pstmt, argIdx);
return _joinClass;
}
public SQLExpression getJoinCondition ()
{
return _joinCondition;
}
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_joinClass);
}
/** Indicates the type of join to be performed. */
@@ -20,12 +20,10 @@
package com.samskivert.jdbc.depot.clause;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Represents a LIMIT/OFFSET clause, for pagination.
@@ -38,19 +36,25 @@ public class Limit extends QueryClause
_count = count;
}
// from QueryClause
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
public int getOffset ()
{
builder.append(" limit ? offset ? ");
return _offset;
}
// from QueryClause
public int bindClauseArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public int getCount ()
{
return _count;
}
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
pstmt.setObject(argIdx++, _count);
pstmt.setObject(argIdx++, _offset);
return argIdx;
}
/** The first row of the result set to return. */
@@ -20,13 +20,10 @@
package com.samskivert.jdbc.depot.clause;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
@@ -46,22 +43,6 @@ public class OrderBy extends QueryClause
return ascending(new LiteralExp("rand()"));
}
/**
* Creates and returns an ascending order by clause on the supplied column.
*/
public static OrderBy ascending (String column)
{
return ascending(new ColumnExp(null, column));
}
/**
* Creates and returns a descending order by clause on the supplied column.
*/
public static OrderBy descending (String column)
{
return descending(new ColumnExp(null, column));
}
/**
* Creates and returns an ascending order by clause on the supplied expression.
*/
@@ -84,27 +65,28 @@ public class OrderBy extends QueryClause
_orders = orders;
}
// from QueryClause
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
public SQLExpression[] getValues ()
{
builder.append(" order by ");
for (int ii = 0; ii < _values.length; ii++) {
if (ii > 0) {
builder.append(", ");
}
_values[ii].appendExpression(query, builder);
builder.append(" ").append(_orders[ii]);
}
return _values;
}
// from QueryClause
public int bindClauseArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public Order[] getOrders ()
{
for (int ii = 0; ii < _values.length; ii++) {
argIdx = _values[ii].bindExpressionArguments(pstmt, argIdx);
return _orders;
}
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (SQLExpression expression : _values) {
expression.addClasses(classSet);
}
return argIdx;
}
/** The expressions that are generated for the clause. */
@@ -112,4 +94,5 @@ public class OrderBy extends QueryClause
/** Whether the ordering is to be ascending or descending. */
protected Order[] _orders;
}
@@ -20,39 +20,11 @@
package com.samskivert.jdbc.depot.clause;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Represents a piece or modifier of an SQL query.
*/
public abstract class QueryClause
public abstract class QueryClause implements SQLExpression
{
/**
* Adds all persistent classes referenced by this clause, if any, to the supplied set.
*/
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
/**
* Constructs the SQL form of this query clause. The implementor is expected to call methods on
* the Query object to e.g. resolve current table abbreviations associated with classes.
*/
public abstract void appendClause (QueryBuilderContext<?> query, StringBuilder builder);
/**
* Binds any objects that were referenced in the generated SQL. For each ? that appears in the
* SQL, precisely one parameter must be claimed and bound in this method, and argIdx
* incremented and returned. The default implementation binds nothing.
*/
public int bindClauseArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
{
return argIdx;
}
}
@@ -0,0 +1,212 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne
//
// 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot.clause;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.WhereClause;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class SelectClause<T extends PersistentRecord> extends QueryClause
{
/**
* Creates a new Query object to generate one or more instances of the specified persistent
* class, as dictated by the key and query clauses. A persistence context is supplied for
* instantiation of marshallers, which may trigger table creations and schema migrations.
*/
public SelectClause (Class<T> pClass, String[] fields, QueryClause... clauses)
{
_pClass = pClass;
_fields = fields;
// iterate over the clauses and sort them into the different types we understand
for (QueryClause clause : clauses) {
if (clause == null) {
continue;
}
if (clause instanceof WhereClause) {
if (_where != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Where clauses.");
}
_where = (WhereClause) clause;
} else if (clause instanceof FromOverride) {
if (_fromOverride != null) {
throw new IllegalArgumentException(
"Query can't contain multiple FromOverride clauses.");
}
_fromOverride = (FromOverride) clause;
} else if (clause instanceof Join) {
_joinClauses.add((Join) clause);
} else if (clause instanceof FieldOverride) {
_disMap.put(((FieldOverride) clause).getField(), ((FieldOverride) clause));
} else if (clause instanceof OrderBy) {
if (_orderBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple OrderBy clauses.");
}
_orderBy = (OrderBy) clause;
} else if (clause instanceof GroupBy) {
if (_groupBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple GroupBy clauses.");
}
_groupBy = (GroupBy) clause;
} else if (clause instanceof Limit) {
if (_limit != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Limit clauses.");
}
_limit = (Limit) clause;
} else if (clause instanceof ForUpdate) {
if (_forUpdate != null) {
throw new IllegalArgumentException(
"Query can't contain multiple For Update clauses.");
}
_forUpdate = (ForUpdate) clause;
}
}
}
public FieldOverride lookupOverride (String field)
{
return _disMap.get(field);
}
public Collection<FieldOverride> getFieldOverrides ()
{
return _disMap.values();
}
public Class<T> getPersistentClass ()
{
return _pClass;
}
public String[] getFields ()
{
return _fields;
}
public FromOverride getFromOverride ()
{
return _fromOverride;
}
public WhereClause getWhereClause ()
{
return _where;
}
public List<Join> getJoinClauses ()
{
return _joinClauses;
}
public OrderBy getOrderBy ()
{
return _orderBy;
}
public GroupBy getGroupBy ()
{
return _groupBy;
}
public Limit getLimit ()
{
return _limit;
}
public ForUpdate getForUpdate ()
{
return _forUpdate;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
if (_fromOverride != null) {
_fromOverride.addClasses(classSet);
}
if (_where != null) {
_where.addClasses(classSet);
}
for (Join join : _joinClauses) {
join.addClasses(classSet);
}
for (FieldOverride override : _disMap.values()) {
override.addClasses(classSet);
}
}
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
/** Persistent class fields mapped to field override clauses. */
protected Map<String, FieldOverride> _disMap = new HashMap<String, FieldOverride>();
/** The persistent class this select defines. */
protected Class<T> _pClass;
/** The persistent fields to select. */
protected String[] _fields;
/** The from override clause, if any. */
protected FromOverride _fromOverride;
/** The where clause. */
protected WhereClause _where;
/** A list of join clauses, each potentially referencing a new class. */
protected List<Join> _joinClauses = new ArrayList<Join>();
/** The order by clause, if any. */
protected OrderBy _orderBy;
/** The group by clause, if any. */
protected GroupBy _groupBy;
/** The limit clause, if any. */
protected Limit _limit;
/** The For Update clause, if any. */
protected ForUpdate _forUpdate;
}
@@ -0,0 +1,114 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne
//
// 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.WhereClause;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class UpdateClause<T extends PersistentRecord> extends QueryClause
{
public UpdateClause (Class<? extends PersistentRecord> pClass, WhereClause where,
String[] fields, T pojo)
{
_pClass = pClass;
_where = where;
_fields = fields;
_values = null;
_pojo = pojo;
}
public UpdateClause (Class<? extends PersistentRecord> pClass, WhereClause where,
String[] fields, SQLExpression[] values)
{
_pClass = pClass;
_fields = fields;
_where = where;
_values = values;
_pojo = null;
}
public WhereClause getWhereClause ()
{
return _where;
}
public String[] getFields ()
{
return _fields;
}
public SQLExpression[] getValues ()
{
return _values;
}
public Object getPojo ()
{
return _pojo;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
if (_where != null) {
_where.addClasses(classSet);
}
if (_values != null) {
for (int ii = 0; ii < _values.length; ii ++) {
_values[ii].addClasses(classSet);
}
}
}
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
/** The class we're updating. */
protected Class<? extends PersistentRecord> _pClass;
/** The where clause. */
protected WhereClause _where;
/** The persistent fields to update. */
protected String[] _fields;
/** The field values, or null. */
protected SQLExpression[] _values;
/** The object from which to fetch values, or null. */
protected Object _pojo;
}
@@ -20,14 +20,12 @@
package com.samskivert.jdbc.depot.clause;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.WhereClause;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
import com.samskivert.jdbc.depot.operator.Conditionals.Equals;
@@ -38,13 +36,8 @@ import com.samskivert.jdbc.depot.operator.Logic.And;
* Represents a where clause: the condition can be any comparison operator or logical combination
* thereof.
*/
public class Where extends QueryClause
public class Where extends WhereClause
{
public Where (String index, Comparable value)
{
this(new ColumnExp(index), value);
}
public Where (ColumnExp column, Comparable value)
{
this(new ColumnExp[] { column }, new Comparable[] { value });
@@ -64,18 +57,6 @@ public class Where extends QueryClause
new Comparable[] { value1, value2, value3 });
}
public Where (String index1, Comparable value1, String index2, Comparable value2)
{
this(new ColumnExp(index1), value1, new ColumnExp(index2), value2);
}
public Where (String index1, Comparable value1, String index2, Comparable value2,
String index3, Comparable value3)
{
this(new ColumnExp(index1), value1, new ColumnExp(index2), value2,
new ColumnExp(index3), value3);
}
public Where (ColumnExp[] columns, Comparable[] values)
{
this(toCondition(columns, values));
@@ -86,18 +67,21 @@ public class Where extends QueryClause
_condition = condition;
}
// from QueryClause
public void appendClause (QueryBuilderContext<?> query, StringBuilder builder)
public SQLExpression getCondition ()
{
builder.append(" where ");
_condition.appendExpression(query, builder);
return _condition;
}
// from QueryClause
public int bindClauseArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
return _condition.bindExpressionArguments(pstmt, argIdx);
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_condition.addClasses(classSet);
}
protected static SQLExpression toCondition (ColumnExp[] columns, Comparable[] values)
@@ -20,53 +20,48 @@
package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* An expression identifying a column of a class, e.g. GameRecord.itemId. If no class is given,
* no disambiguation occurs in the generated SQL.
* An expression that unambiguously identifies a field of a class, e.g. GameRecord.itemId.
*/
public class ColumnExp
implements SQLExpression
{
/** The table that hosts the column we reference, or null. */
final public Class<? extends PersistentRecord> pClass;
/** The name of the column we reference. */
final public String pColumn;
public ColumnExp (String column)
{
this(null, column);
}
public ColumnExp (Class<? extends PersistentRecord> c, String column)
public ColumnExp (Class<? extends PersistentRecord> pClass, String field)
{
super();
pClass = c;
this.pColumn = column;
_pClass = pClass;
_pField = field;
}
// from SQLExpression
public void appendExpression (QueryBuilderContext<?> query, StringBuilder builder)
public void accept (ExpressionVisitor builder) throws Exception
{
if (pClass == null || query == null) {
builder.append(pColumn);
} else {
String tRef = query.getTableAbbreviation(pClass);
builder.append(tRef).append(".").append(pColumn);
}
builder.visit(this);
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
return argIdx;
classSet.add(_pClass);
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public String getField ()
{
return _pField;
}
/** The table that hosts the column we reference, or null. */
protected final Class<? extends PersistentRecord> _pClass;
/** The name of the column we reference. */
protected final String _pField;
}
@@ -0,0 +1,103 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne, 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
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot.expression;
import com.samskivert.jdbc.depot.Key.WhereCondition;
import com.samskivert.jdbc.depot.Key;
import com.samskivert.jdbc.depot.MultiKey;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.ForUpdate;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.GroupBy;
import com.samskivert.jdbc.depot.clause.InsertClause;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.Limit;
import com.samskivert.jdbc.depot.clause.OrderBy;
import com.samskivert.jdbc.depot.clause.SelectClause;
import com.samskivert.jdbc.depot.clause.UpdateClause;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.operator.Conditionals.In;
import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
import com.samskivert.jdbc.depot.operator.Conditionals.Match;
import com.samskivert.jdbc.depot.operator.Logic.Not;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator;
/**
* Enumerates visitation methods for every possible SQL expression type.
*/
public interface ExpressionVisitor
{
public void visit (FieldOverride fieldOverride)
throws Exception;
public void visit (WhereCondition<? extends PersistentRecord> whereCondition)
throws Exception;
public void visit (Key key)
throws Exception;
public void visit (MultiKey<? extends PersistentRecord> key)
throws Exception;
public void visit (FunctionExp functionExp)
throws Exception;
public void visit (FromOverride fromOverride)
throws Exception;
public void visit (MultiOperator multiOperator)
throws Exception;
public void visit (BinaryOperator binaryOperator)
throws Exception;
public void visit (IsNull isNull)
throws Exception;
public void visit (In in)
throws Exception;
public void visit (Match match)
throws Exception;
public void visit (ColumnExp columnExp)
throws Exception;
public void visit (Not not)
throws Exception;
public void visit (GroupBy groupBy)
throws Exception;
public void visit (ForUpdate forUpdate)
throws Exception;
public void visit (OrderBy orderBy)
throws Exception;
public void visit (Where where)
throws Exception;
public void visit (Join join)
throws Exception;
public void visit (Limit limit)
throws Exception;
public void visit (LiteralExp literalExp)
throws Exception;
public void visit (ValueExp valueExp)
throws Exception;
public void visit (SelectClause<? extends PersistentRecord> selectClause)
throws Exception;
public void visit (UpdateClause<? extends PersistentRecord> updateClause)
throws Exception;
public void visit (DeleteClause<? extends PersistentRecord> deleteClause)
throws Exception;
public void visit (InsertClause<? extends PersistentRecord> insertClause)
throws Exception;
}
@@ -20,16 +20,14 @@
package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* An expression for a function, e.g. FLOOR(blah).
*/
public class FunctionExp
implements SQLExpression
public class FunctionExp implements SQLExpression
{
/**
* Create a new FunctionExp with the given function and arguments.
@@ -41,27 +39,28 @@ public class FunctionExp
}
// from SQLExpression
public void appendExpression (QueryBuilderContext query, StringBuilder builder)
public void accept (ExpressionVisitor builder)
throws Exception
{
builder.append(_function);
builder.append("(");
for (int ii = 0; ii < _arguments.length; ii ++) {
if (ii > 0) {
builder.append(", ");
}
_arguments[ii].appendExpression(query, builder);
}
builder.append(")");
builder.visit(this);
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (int ii = 0; ii < _arguments.length; ii ++) {
argIdx = _arguments[ii].bindExpressionArguments(pstmt, argIdx);
_arguments[ii].addClasses(classSet);
}
return argIdx;
}
public String getFunction ()
{
return _function;
}
public SQLExpression[] getArguments ()
{
return _arguments;
}
/** The literal name of this function, e.g. FLOOR */
@@ -20,10 +20,9 @@
package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* An expression for things we don't support natively, e.g. COUNT(*).
@@ -38,16 +37,19 @@ public class LiteralExp
}
// from SQLExpression
public void appendExpression (QueryBuilderContext query, StringBuilder builder)
public void accept (ExpressionVisitor builder) throws Exception
{
builder.append(_text);
builder.visit(this);
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
return argIdx;
}
public String getText ()
{
return _text;
}
/** The literal text of this expression, e.g. COUNT(*) */
@@ -20,10 +20,10 @@
package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.SQLBuilder;
/**
* Represents an SQL expression, e.g. column name, function, or constant.
@@ -31,16 +31,19 @@ import com.samskivert.jdbc.depot.QueryBuilderContext;
public interface SQLExpression
{
/**
* Construct the SQL form of this expression. The implementor is invited to call methods on the
* Query object to e.g. resolve the current table abbreviations associated with classes.
* Most uses of this class have been implemented with a visitor pattern. Create your own
* {@link ExpressionVisitor} and call this method with it.
*
* @see SQLBuilder
*/
public void appendExpression (QueryBuilderContext<?> query, StringBuilder builder);
public void accept (ExpressionVisitor builder)
throws Exception;
/**
* Bind any objects that were referenced in the generated SQL. For each ? that appears in the
* SQL, precisely one parameter must be claimed and bound in this method, and argIdx
* incremented and returned.
* Adds all persistent classes that are brought into the SQL context by this clause: FROM
* clauses, JOINs, UPDATEs, anything that could create a new table abbreviation. This method
* should recurse into any subordinate state that may in turn bring in new classes so that
* sub-queries work correctly.
*/
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException;
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet);
}
@@ -20,10 +20,9 @@
package com.samskivert.jdbc.depot.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* A Java value that is bound as a parameter to the query, e.g. 1 or 'abc'.
@@ -31,25 +30,27 @@ import com.samskivert.jdbc.depot.QueryBuilderContext;
public class ValueExp
implements SQLExpression
{
public ValueExp (Comparable _value)
public ValueExp (Object _value)
{
this._value = _value;
}
// from SQLExpression
public void appendExpression (QueryBuilderContext query, StringBuilder builder)
public void accept (ExpressionVisitor builder) throws Exception
{
builder.append("?");
builder.visit(this);
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
pstmt.setObject(argIdx ++, _value);
return argIdx;
}
public Object getValue ()
{
return _value;
}
/** The value to be bound to the SQL parameters. */
protected Comparable _value;
protected Object _value;
}
@@ -20,7 +20,6 @@
package com.samskivert.jdbc.depot.operator;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
@@ -34,11 +33,6 @@ public abstract class Arithmetic
/** The SQL '+' operator. */
public static class Add extends BinaryOperator
{
public Add (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public Add (SQLExpression column, Comparable value)
{
super(column, value);
@@ -50,7 +44,7 @@ public abstract class Arithmetic
}
@Override
protected String operator()
public String operator()
{
return "+";
}
@@ -59,11 +53,6 @@ public abstract class Arithmetic
/** The SQL '-' operator. */
public static class Sub extends BinaryOperator
{
public Sub (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public Sub (SQLExpression column, Comparable value)
{
super(column, value);
@@ -75,7 +64,7 @@ public abstract class Arithmetic
}
@Override
protected String operator()
public String operator()
{
return "-";
}
@@ -84,11 +73,6 @@ public abstract class Arithmetic
/** The SQL '*' operator. */
public static class Mul extends BinaryOperator
{
public Mul (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public Mul (SQLExpression column, Comparable value)
{
super(column, value);
@@ -100,7 +84,7 @@ public abstract class Arithmetic
}
@Override
protected String operator()
public String operator()
{
return "*";
}
@@ -109,11 +93,6 @@ public abstract class Arithmetic
/** The SQL '/' operator. */
public static class Div extends BinaryOperator
{
public Div (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public Div (SQLExpression column, Comparable value)
{
super(column, value);
@@ -125,7 +104,7 @@ public abstract class Arithmetic
}
@Override
protected String operator()
public String operator()
{
return "/";
}
@@ -134,11 +113,6 @@ public abstract class Arithmetic
/** The SQL '&' operator. */
public static class BitAnd extends BinaryOperator
{
public BitAnd (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public BitAnd (SQLExpression column, Comparable value)
{
super(column, value);
@@ -150,7 +124,7 @@ public abstract class Arithmetic
}
@Override
protected String operator()
public String operator()
{
return "&";
}
@@ -159,11 +133,6 @@ public abstract class Arithmetic
/** The SQL '|' operator. */
public static class BitOr extends BinaryOperator
{
public BitOr (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public BitOr (SQLExpression column, Comparable value)
{
super(column, value);
@@ -175,7 +144,7 @@ public abstract class Arithmetic
}
@Override
protected String operator()
public String operator()
{
return "|";
}
@@ -20,15 +20,12 @@
package com.samskivert.jdbc.depot.operator;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
/**
* A convenient container for implementations of conditional operators. Classes that value brevity
@@ -56,31 +53,28 @@ public abstract class Conditionals
_column = column;
}
// from SQLExpression
public void appendExpression (QueryBuilderContext query, StringBuilder builder)
public ColumnExp getColumn()
{
_column.appendExpression(query, builder);
builder.append(" is null");
return _column;
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
return argIdx;
}
protected ColumnExp _column;
}
/** The SQL '=' operator. */
public static class Equals extends BinaryOperator
public static class Equals extends SQLOperator.BinaryOperator
{
public Equals (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public Equals (SQLExpression column, Comparable value)
{
super(column, value);
@@ -92,20 +86,15 @@ public abstract class Conditionals
}
@Override
protected String operator()
public String operator()
{
return "=";
}
}
/** The SQL '<' operator. */
public static class LessThan extends BinaryOperator
public static class LessThan extends SQLOperator.BinaryOperator
{
public LessThan (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public LessThan (SQLExpression column, Comparable value)
{
super(column, value);
@@ -117,20 +106,15 @@ public abstract class Conditionals
}
@Override
protected String operator()
public String operator()
{
return "<";
}
}
/** The SQL '<=' operator. */
public static class LessThanEquals extends BinaryOperator
public static class LessThanEquals extends SQLOperator.BinaryOperator
{
public LessThanEquals (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public LessThanEquals (SQLExpression column, Comparable value)
{
super(column, value);
@@ -142,20 +126,15 @@ public abstract class Conditionals
}
@Override
protected String operator()
public String operator()
{
return "<=";
}
}
/** The SQL '>' operator. */
public static class GreaterThan extends BinaryOperator
public static class GreaterThan extends SQLOperator.BinaryOperator
{
public GreaterThan (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public GreaterThan (SQLExpression column, Comparable value)
{
super(column, value);
@@ -167,20 +146,15 @@ public abstract class Conditionals
}
@Override
protected String operator()
public String operator()
{
return ">";
}
}
/** The SQL '>=' operator. */
public static class GreaterThanEquals extends BinaryOperator
public static class GreaterThanEquals extends SQLOperator.BinaryOperator
{
public GreaterThanEquals (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public GreaterThanEquals (SQLExpression column, Comparable value)
{
super(column, value);
@@ -192,7 +166,7 @@ public abstract class Conditionals
}
@Override
protected String operator()
public String operator()
{
return ">=";
}
@@ -202,16 +176,6 @@ public abstract class Conditionals
public static class In
implements SQLOperator
{
public In (String pColumn, Comparable... values)
{
this(new ColumnExp(null, pColumn), values);
}
public In (String pColumn, Collection<? extends Comparable> values)
{
this(new ColumnExp(null, pColumn), values.toArray(new Comparable[values.size()]));
}
public In (Class<? extends PersistentRecord> pClass, String pColumn, Comparable... values)
{
this(new ColumnExp(pClass, pColumn), values);
@@ -232,107 +196,40 @@ public abstract class Conditionals
_values = values;
}
// from SQLExpression
public void appendExpression (QueryBuilderContext query, StringBuilder builder)
public In (ColumnExp pColumn, Collection<? extends Comparable> values)
{
_column.appendExpression(query, builder);
builder.append(" in (");
for (int ii = 0; ii < _values.length; ii ++) {
if (ii > 0) {
builder.append(", ");
this(pColumn, values.toArray(new Comparable[values.size()]));
}
builder.append("?");
public ColumnExp getColumn ()
{
return _column;
}
builder.append(")");
public Comparable[] getValues ()
{
return _values;
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public void accept (ExpressionVisitor builder) throws Exception
{
for (int ii = 0; ii < _values.length; ii++) {
pstmt.setObject(argIdx ++, _values[ii]);
builder.visit(this);
}
return argIdx;
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_column.addClasses(classSet);
}
protected ColumnExp _column;
protected Comparable[] _values;
}
/** The MySQL 'match (...) against (...)' operator. */
public static class Match
implements SQLOperator
{
public enum Mode { DEFAULT, BOOLEAN, NATURAL_LANGUAGE };
public Match (String query, Mode mode, boolean queryExpansion, String... pColumns)
{
_query = query;
_mode = mode;
_queryExpansion = queryExpansion;
_columns = new ColumnExp[pColumns.length];
for (int ii = 0; ii < pColumns.length; ii++) {
_columns[ii] = new ColumnExp(null, pColumns[ii]);
}
}
public Match (String query, Mode mode, boolean queryExpansion, ColumnExp... columns)
{
_query = query;
_queryExpansion = queryExpansion;
_mode = mode;
_columns = columns;
}
// from SQLExpression
public void appendExpression (QueryBuilderContext query, StringBuilder builder)
{
builder.append("match(");
int idx = 0;
for (ColumnExp column : _columns) {
if (idx++ > 0) {
builder.append(", ");
}
column.appendExpression(query, builder);
}
builder.append(") against (?");
switch (_mode) {
case BOOLEAN:
builder.append(" in boolean mode");
break;
case NATURAL_LANGUAGE:
builder.append(" in natural language mode");
break;
}
if (_queryExpansion) {
builder.append(" with query expansion");
}
builder.append(")");
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
{
pstmt.setString(argIdx++, _query);
return argIdx;
}
protected String _query;
protected Mode _mode;
protected boolean _queryExpansion;
protected ColumnExp[] _columns;
}
/** The SQL ' like ' operator. */
public static class Like extends BinaryOperator
public static class Like extends SQLOperator.BinaryOperator
{
public Like (String pColumn, Comparable value)
{
super(new ColumnExp(pColumn), value);
}
public Like (SQLExpression column, Comparable value)
{
super(column, value);
@@ -344,9 +241,64 @@ public abstract class Conditionals
}
@Override
protected String operator()
public String operator()
{
return " like ";
}
}
/** The MySQL 'match (...) against (...)' operator. */
@Deprecated
public static class Match
implements SQLOperator
{
public enum Mode { DEFAULT, BOOLEAN, NATURAL_LANGUAGE };
public Match (String query, Mode mode, boolean queryExpansion, ColumnExp... columns)
{
_query = query;
_queryExpansion = queryExpansion;
_mode = mode;
_columns = columns;
}
public String getQuery ()
{
return _query;
}
public Mode getMode ()
{
return _mode;
}
public boolean isQueryExpansion ()
{
return _queryExpansion;
}
public ColumnExp[] getColumns ()
{
return _columns;
}
// from SQLExpression
public void accept (ExpressionVisitor builder) throws Exception
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (ColumnExp column : _columns) {
column.addClasses(classSet);
}
}
protected String _query;
protected Mode _mode;
protected boolean _queryExpansion;
protected ColumnExp[] _columns;
}
}
@@ -20,12 +20,11 @@
package com.samskivert.jdbc.depot.operator;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator;
/**
* A convenient container for implementations of logical operators. Classes that value brevity
@@ -37,7 +36,7 @@ public abstract class Logic
/**
* Represents a condition that is false iff all its subconditions are false.
*/
public static class Or extends MultiOperator
public static class Or extends SQLOperator.MultiOperator
{
public Or (SQLExpression... conditions)
{
@@ -45,7 +44,7 @@ public abstract class Logic
}
@Override
protected String operator()
public String operator()
{
return "or";
}
@@ -54,7 +53,7 @@ public abstract class Logic
/**
* Represents a condition that is true iff all its subconditions are true.
*/
public static class And extends MultiOperator
public static class And extends SQLOperator.MultiOperator
{
public And (SQLExpression... conditions)
{
@@ -62,7 +61,7 @@ public abstract class Logic
}
@Override
protected String operator()
public String operator()
{
return "and";
}
@@ -76,23 +75,24 @@ public abstract class Logic
{
public Not (SQLExpression condition)
{
super();
_condition = condition;
}
// from SQLExpression
public void appendExpression (QueryBuilderContext query, StringBuilder builder)
public SQLExpression getCondition ()
{
builder.append(" not (");
_condition.appendExpression(query, builder);
builder.append(")");
return _condition;
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public void accept (ExpressionVisitor builder) throws Exception
{
return _condition.bindExpressionArguments(pstmt, argIdx);
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_condition.addClasses(classSet);
}
protected SQLExpression _condition;
@@ -20,10 +20,10 @@
package com.samskivert.jdbc.depot.operator;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.jdbc.depot.QueryBuilderContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
@@ -42,37 +42,32 @@ public interface SQLOperator extends SQLExpression
{
public MultiOperator (SQLExpression ... conditions)
{
super();
_conditions = conditions;
}
// from SQLExpression
public void appendExpression (QueryBuilderContext query, StringBuilder builder)
public void accept (ExpressionVisitor builder) throws Exception
{
for (int ii = 0; ii < _conditions.length; ii++) {
if (ii > 0) {
builder.append(" ").append(operator()).append(" ");
}
builder.append("(");
_conditions[ii].appendExpression(query, builder);
builder.append(")");
}
builder.visit(this);
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (int ii = 0; ii < _conditions.length; ii++) {
argIdx = _conditions[ii].bindExpressionArguments(pstmt, argIdx);
for (int ii = 0; ii < _conditions.length; ii ++) {
_conditions[ii].addClasses(classSet);
}
return argIdx;
}
public SQLExpression[] getConditions ()
{
return _conditions;
}
/**
* Returns the text infix to be used to join expressions together.
*/
protected abstract String operator ();
public abstract String operator ();
protected SQLExpression[] _conditions;
}
@@ -94,26 +89,32 @@ public interface SQLOperator extends SQLExpression
}
// from SQLExpression
public void appendExpression (QueryBuilderContext query, StringBuilder builder)
public void accept (ExpressionVisitor builder) throws Exception
{
_lhs.appendExpression(query, builder);
builder.append(operator());
_rhs.appendExpression(query, builder);
builder.visit(this);
}
// from SQLExpression
public int bindExpressionArguments (PreparedStatement pstmt, int argIdx)
throws SQLException
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
argIdx = _lhs.bindExpressionArguments(pstmt, argIdx);
argIdx = _rhs.bindExpressionArguments(pstmt, argIdx);
return argIdx;
_lhs.addClasses(classSet);
_rhs.addClasses(classSet);
}
/**
* Returns the string representation of the operator.
*/
protected abstract String operator();
public abstract String operator();
public SQLExpression getLeftHandSide ()
{
return _lhs;
}
public SQLExpression getRightHandSide ()
{
return _rhs;
}
protected SQLExpression _lhs;
protected SQLExpression _rhs;