EMPIREDB-266 Add special handling for DBQueryColumns in DBReader getFieldIndex()
Project: http://git-wip-us.apache.org/repos/asf/empire-db/repo Commit: http://git-wip-us.apache.org/repos/asf/empire-db/commit/0a8ae70f Tree: http://git-wip-us.apache.org/repos/asf/empire-db/tree/0a8ae70f Diff: http://git-wip-us.apache.org/repos/asf/empire-db/diff/0a8ae70f Branch: refs/heads/master Commit: 0a8ae70fcb0fa399c72e067abfe6530a08fbadcf Parents: 65f5aa0 Author: Rainer Döbele <[email protected]> Authored: Sun Jan 7 12:28:42 2018 +0100 Committer: Rainer Döbele <[email protected]> Committed: Sun Jan 7 12:28:42 2018 +0100 ---------------------------------------------------------------------- .../main/java/org/apache/empire/db/DBQuery.java | 1269 +++++----- .../org/apache/empire/db/DBQueryColumn.java | 128 + .../java/org/apache/empire/db/DBReader.java | 2194 +++++++++--------- 3 files changed, 1811 insertions(+), 1780 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/empire-db/blob/0a8ae70f/empire-db/src/main/java/org/apache/empire/db/DBQuery.java ---------------------------------------------------------------------- diff --git a/empire-db/src/main/java/org/apache/empire/db/DBQuery.java b/empire-db/src/main/java/org/apache/empire/db/DBQuery.java index ff3fa52..9e25b28 100644 --- a/empire-db/src/main/java/org/apache/empire/db/DBQuery.java +++ b/empire-db/src/main/java/org/apache/empire/db/DBQuery.java @@ -1,687 +1,584 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.empire.db; - -import java.sql.Connection; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.atomic.AtomicInteger; - -import org.apache.empire.commons.ObjectUtils; -import org.apache.empire.commons.Options; -import org.apache.empire.commons.StringUtils; -import org.apache.empire.data.DataType; -import org.apache.empire.db.exceptions.InvalidKeyException; -import org.apache.empire.db.exceptions.NoPrimaryKeyException; -import org.apache.empire.db.exceptions.QueryNoResultException; -import org.apache.empire.db.exceptions.RecordNotFoundException; -import org.apache.empire.db.exceptions.RecordUpdateFailedException; -import org.apache.empire.db.exceptions.RecordUpdateInvalidException; -import org.apache.empire.db.expr.compare.DBCompareColExpr; -import org.apache.empire.db.expr.compare.DBCompareExpr; -import org.apache.empire.db.expr.join.DBColumnJoinExpr; -import org.apache.empire.db.expr.join.DBJoinExpr; -import org.apache.empire.exceptions.InvalidArgumentException; -import org.apache.empire.exceptions.ItemNotFoundException; -import org.apache.empire.exceptions.NotImplementedException; -import org.apache.empire.exceptions.NotSupportedException; -import org.w3c.dom.Element; - - -/** - * This class can be used to wrap a query from a DBCommand and use it like a DBRowSet.<BR> - * You may use this class for two purposes: - * <UL> - * <LI>In oder to define subqueries simply define a command object with the subquery and wrap it inside a DBQuery. - * Then in a second command object you can reference this Query to join with your other tables and views. - * In order to join other columns with your query use findQueryColumn(DBColumnExpr expr) to get the - * query column object for a given column expression in the original select clause.</LI> - * <LI>With a key supplied you can have an updateable query that will update several records at once.</LI> - * </UL> - * - */ -public class DBQuery extends DBRowSet -{ - private final static long serialVersionUID = 1L; - - public static class DBQueryColumn extends DBColumn - { - private final static long serialVersionUID = 1L; - protected DBColumnExpr expr; - - /** - * Constructs a DBQueryColumn object set the specified parameters to this object. - * <P> - * @param query the DBQuery object - * @param expr the concrete DBColumnExpr object - */ - public DBQueryColumn(DBQuery query, DBColumnExpr expr) - { // call base - super(query, expr.getName()); - // set Expression - this.expr = expr; - } - - @Override - public DataType getDataType() - { - return expr.getDataType(); - } - - @Override - public double getSize() - { - DBColumn column = expr.getUpdateColumn(); - if (column==null) - return 0.0; - return column.getSize(); - } - - @Override - public boolean isReadOnly() - { - DBColumn column = expr.getUpdateColumn(); - if (column==null) - return true; - return column.isReadOnly(); - } - - @Override - public boolean isAutoGenerated() - { - DBColumn column = expr.getUpdateColumn(); - if (column==null) - return false; - return column.isAutoGenerated(); - } - - @Override - public boolean isRequired() - { - DBColumn column = expr.getUpdateColumn(); - if (column==null) - return false; - return column.isRequired(); - } - - @Override - public Object validate(Object value) - { - DBColumn column = expr.getUpdateColumn(); - if (column==null) - return value; - return column.validate(value); - } - - @Override - public Object getAttribute(String name) - { - if (attributes != null && attributes.contains(name)) - return attributes.get(name); - // Otherwise ask expression - DBColumn column = expr.getUpdateColumn(); - if (column==null) - return null; - return column.getAttribute(name); - } - - @Override - public Options getOptions() - { - if (options != null) - return options; - // Otherwise ask expression - DBColumn column = expr.getUpdateColumn(); - if (column==null) - return null; - return column.getOptions(); - } - - @Override - public Element addXml(Element parent, long flags) - { - return expr.addXml(parent, flags); - } - } - - private static AtomicInteger queryCount = new AtomicInteger(0); - - protected DBCommandExpr cmdExpr; - protected DBColumn[] keyColumns = null; - protected DBQueryColumn[] queryColumns = null; - protected String alias; - - /** - * Constructor initializes the query object. - * Saves the columns and the primary keys of this query. - * - * @param cmd the SQL-Command - * @param keyColumns an array of the primary key columns - * @param the query alias - */ - public DBQuery(DBCommandExpr cmd, DBColumn[] keyColumns, String alias) - { // Set the column expressions - super(cmd.getDatabase()); - this.cmdExpr = cmd; - // Set Query Columns - DBColumnExpr[] exprList = cmd.getSelectExprList(); - queryColumns = new DBQueryColumn[exprList.length]; - for (int i = 0; i < exprList.length; i++) - { // Init Columns - columns.add(exprList[i].getUpdateColumn()); - queryColumns[i] = new DBQueryColumn(this, exprList[i]); - } - // Set the key Column - this.keyColumns = keyColumns; - // set alias - this.alias = alias; - } - - /** - * Constructor initializes the query object. - * Saves the columns and the primary keys of this query. - * - * @param cmd the SQL-Command - * @param keyColumns an array of the primary key columns - */ - public DBQuery(DBCommandExpr cmd, DBColumn[] keyColumns) - { // Set the column expressions - this(cmd, keyColumns, "q" + String.valueOf(queryCount.incrementAndGet())); - } - - /** - * Constructs a new DBQuery object initialize the query object. - * Save the columns and the primary key of this query. - * - * @param cmd the SQL-Command - * @param keyColumn the primary key column - * @param the query alias - */ - public DBQuery(DBCommandExpr cmd, DBColumn keyColumn, String alias) - { // Set the column expressions - this(cmd, new DBColumn[] { keyColumn }, alias); - } - - /** - * Constructs a new DBQuery object initialize the query object. - * Save the columns and the primary key of this query. - * - * @param cmd the SQL-Command - * @param keyColumn the primary key column - */ - public DBQuery(DBCommandExpr cmd, DBColumn keyColumn) - { // Set the column expressions - this(cmd, new DBColumn[] { keyColumn }); - } - - /** - * Creaes a DBQuery object from a given command object. - * - * @param cmd the command object representing an SQL-Command. - * @param the query alias - */ - public DBQuery(DBCommandExpr cmd, String alias) - { // Set the column expressions - this(cmd, (DBColumn[]) null, alias); - } - - /** - * Creaes a DBQuery object from a given command object. - * - * @param cmd the command object representing an SQL-Command. - */ - public DBQuery(DBCommandExpr cmd) - { // Set the column expressions - this(cmd, (DBColumn[]) null); - } - - /** - * returns the command from the underlying command expression or throws an exception - * @return the command used for this query - */ - private DBCommand getCommandFromExpression() - { - if (cmdExpr instanceof DBCommand) - return ((DBCommand)cmdExpr); - // not supported - throw new NotSupportedException(this, "getCommand"); - } - - /** - * returns the underlying command expression - * @return the command used for this query - */ - public DBCommandExpr getCommandExpr() - { - return cmdExpr; - } - - /** - * not applicable - returns null - */ - @Override - public String getName() - { - return null; - } - - /** - * not applicable - returns null - */ - @Override - public String getAlias() - { - return alias; - } - - /** - * Returns whether or not the table supports record updates. - * @return true if the table allows record updates - */ - @Override - public boolean isUpdateable() - { - return (getKeyColumns()!=null); - } - - /** - * Gets all columns of this rowset (e.g. for cmd.select()). - * - * @return all columns of this rowset - */ - public DBQueryColumn[] getQueryColumns() - { - return queryColumns; - } - - /** - * This function searchs for equal columns given by the - * specified DBColumnExpr object. - * - * @param expr the DBColumnExpr object - * @return the located column - */ - public DBQueryColumn findQueryColumn(DBColumnExpr expr) - { - for (int i = 0; i < queryColumns.length; i++) - { - if (queryColumns[i].expr.equals(expr)) - return queryColumns[i]; - } - // not found - return null; - } - - /** - * This function searchs for a query column by name - * - * @param the column name - * @return the located column - */ - public DBQueryColumn findQueryColumn(String name) - { - for (int i = 0; i < queryColumns.length; i++) - { - if (StringUtils.compareEqual(queryColumns[i].getName(), name, true)) - return queryColumns[i]; - } - // not found - return null; - } - - /** - * return query key columns - */ - @Override - public DBColumn[] getKeyColumns() - { - return keyColumns; - } - - /** - * Returns a array of primary key columns by a specified DBRecord object. - * - * @param record the DBRecord object, contains all fields and the field properties - * @return a array of primary key columns - */ - @Override - public Object[] getRecordKey(DBRecord record) - { - if (record == null || record.getRowSet() != this) - throw new InvalidArgumentException("record", record); - // get Key - return (Object[]) record.getRowSetData(); - } - - /** - * Adds the select SQL Command of this object to the specified StringBuilder object. - * - * @param buf the SQL-Command - * @param context the current SQL-Command context - */ - @Override - public void addSQL(StringBuilder buf, long context) - { - buf.append("("); - buf.append(cmdExpr.getSelect()); - buf.append(")"); - // Add Alias - if ((context & CTX_ALIAS) != 0 && alias != null) - { // append alias - buf.append(" "); - buf.append(alias); - } - } - - /** - * Initialize specified DBRecord object with primary key - * columns (the Object[] keyValues). - * - * @param rec the Record object - * @param keyValues an array of the primary key columns - */ - @Override - public void initRecord(DBRecord rec, Object[] keyValues, boolean insert) - { - // Prepare - prepareInitRecord(rec, keyValues, insert); - // Initialize all Fields - Object[] fields = rec.getFields(); - for (int i = 0; i < fields.length; i++) - fields[i] = ObjectUtils.NO_VALUE; - // Set primary key values - if (keyValues != null) - { // search for primary key fields - DBColumn[] keyColumns = getKeyColumns(); - for (int i = 0; i < keyColumns.length; i++) - if (columns.contains(keyColumns[i])) - fields[columns.indexOf(keyColumns[i])] = keyValues[i]; - } - // Init - completeInitRecord(rec); - } - - /** - * Returns an error, because it is not possible to add a record to a query. - * - * @param rec the DBRecord object, contains all fields and the field properties - * @param conn a valid database connection - * @throws NotImplementedException because this is not implemented - */ - @Override - public void createRecord(DBRecord rec, Connection conn) - { - throw new NotImplementedException(this, "createRecord"); - } - - /** - * Creates a select SQL-Command of the query call the InitRecord method to execute the SQL-Command. - * - * @param rec the DBRecord object, contains all fields and the field properties - * @param key an array of the primary key columns - * @param conn a valid connection to the database. - */ - @Override - public void readRecord(DBRecord rec, Object[] key, Connection conn) - { - if (conn == null || rec == null) - throw new InvalidArgumentException("conn|rec", null); - DBColumn[] keyColumns = getKeyColumns(); - if (key == null || keyColumns.length != key.length) - throw new InvalidKeyException(this, key); - // Select - DBCommand cmd = getCommandFromExpression(); - for (int i = 0; i < keyColumns.length; i++) - { // Set key column constraint - Object value = key[i]; - if (db.isPreparedStatementsEnabled()) - value = cmd.addParam(keyColumns[i], value); - cmd.where(keyColumns[i].is(value)); - } - // Read Record - try { - // Read Record - readRecord(rec, cmd, conn); - // Set RowSetData - rec.updateComplete(key.clone()); - } catch (QueryNoResultException e) { - // Record not found - throw new RecordNotFoundException(this, key); - } - } - - /** - * Updates a query record by creating individual update commands for each table. - * - * @param rec the DBRecord object. contains all fields and the field properties - * @param conn a valid connection to the database. - */ - @Override - public void updateRecord(DBRecord rec, Connection conn) - { - // check updateable - if (isUpdateable()==false) - throw new NotSupportedException(this, "updateRecord"); - // check params - if (rec == null) - throw new InvalidArgumentException("record", null); - if (conn == null) - throw new InvalidArgumentException("conn", null); - // Has record been modified? - if (rec.isModified() == false) - return; // Nothing to update - // Must have key Columns - DBColumn[] keyColumns = getKeyColumns(); - if (keyColumns==null) - throw new NoPrimaryKeyException(this); - // Get the fields and the flags - Object[] fields = rec.getFields(); - // Get all Update Commands - Map<DBRowSet, DBCommand> updCmds = new HashMap<DBRowSet, DBCommand>(3); - for (int i = 0; i < columns.size(); i++) - { // get the table - DBColumn col = columns.get(i); - if (col == null) - continue; - DBRowSet table = col.getRowSet(); - DBCommand updCmd = updCmds.get(table); - if (updCmd == null) - { // Add a new Command - updCmd = db.createCommand(); - updCmds.put(table, updCmd); - } - /* - * if (updateTimestampColumns.contains( col ) ) { // Check the update timestamp cmd.set( col.to( DBDatabase.SYSDATE ) ); } - */ - // Set the field Value - boolean modified = rec.wasModified(i); - if (modified == true) - { // Update a field - if (col.isReadOnly() && log.isDebugEnabled()) - log.debug("updateRecord: Read-only column '" + col.getName() + " has been modified!"); - // Check the value - col.validate(fields[i]); - // Set - updCmd.set(col.to(fields[i])); - } - } - // the commands - DBCommand cmd = getCommandFromExpression(); - Object[] keys = (Object[]) rec.getRowSetData(); - DBRowSet table= null; - DBCommand upd = null; - for(Entry<DBRowSet,DBCommand> entry:updCmds.entrySet()) - { - int i = 0; - // Iterate through options - table = entry.getKey(); - upd = entry.getValue(); - // Is there something to update - if (upd.set == null) - continue; // nothing to do for this table! - // Evaluate Joins - for (i = 0; cmd.joins != null && i < cmd.joins.size(); i++) - { - DBJoinExpr jex = cmd.joins.get(i); - if (!(jex instanceof DBColumnJoinExpr)) - continue; - DBColumnJoinExpr join = (DBColumnJoinExpr)jex; - DBColumn left = join.getLeft() .getUpdateColumn(); - DBColumn right = join.getRight().getUpdateColumn(); - if (left.getRowSet()==table && table.isKeyColumn(left)) - if (!addJoinRestriction(upd, left, right, keyColumns, rec)) - throw new ItemNotFoundException(left.getFullName()); - if (right.getRowSet()==table && table.isKeyColumn(right)) - if (!addJoinRestriction(upd, right, left, keyColumns, rec)) - throw new ItemNotFoundException(right.getFullName()); - } - // Evaluate Existing restrictions - for (i = 0; cmd.where != null && i < cmd.where.size(); i++) - { - DBCompareExpr cmp = cmd.where.get(i); - if (cmp instanceof DBCompareColExpr) - { // Check whether constraint belongs to update table - DBCompareColExpr cmpExpr = (DBCompareColExpr) cmp; - DBColumn col = cmpExpr.getColumnExpr().getUpdateColumn(); - if (col!=null && col.getRowSet() == table) - { // add the constraint - if (cmpExpr.getValue() instanceof DBCmdParam) - { // Create a new command param - DBColumnExpr colExpr = cmpExpr.getColumnExpr(); - DBCmdParam param =(DBCmdParam)cmpExpr.getValue(); - DBCmdParam value = upd.addParam(colExpr, param.getValue()); - cmp = new DBCompareColExpr(colExpr, cmpExpr.getCmpop(), value); - } - upd.where(cmp); - } - } - else - { // other constraints are not supported - throw new NotSupportedException(this, "updateRecord with "+cmp.getClass().getName()); - } - } - // Add Restrictions - for (i = 0; i < keyColumns.length; i++) - { - if (keyColumns[i].getRowSet() == table) - { // Set key column constraint - Object value = keys[i]; - if (db.isPreparedStatementsEnabled()) - value = upd.addParam(keyColumns[i], value); - upd.where(keyColumns[i].is(value)); - } - } - - // Set Update Timestamp - int timestampIndex = -1; - Object timestampValue = null; - if (table.getTimestampColumn() != null) - { - DBColumn tsColumn = table.getTimestampColumn(); - timestampIndex = this.getColumnIndex(tsColumn); - if (timestampIndex>=0) - { // The timestamp is availabe in the record - timestampValue = db.getUpdateTimestamp(conn); - Object lastTS = fields[timestampIndex]; - if (ObjectUtils.isEmpty(lastTS)==false) - { // set timestamp constraint - if (db.isPreparedStatementsEnabled()) - lastTS = upd.addParam(tsColumn, lastTS); - upd.where(tsColumn.is(lastTS)); - } - // Set new Timestamp - upd.set(tsColumn.to(timestampValue)); - } - else - { // Timestamp columns has not been provided with the record - upd.set(tsColumn.to(DBDatabase.SYSDATE)); - } - } - - // Execute SQL - int affected = db.executeSQL(upd.getUpdate(), upd.getParamValues(), conn); - if (affected <= 0) - { // Error - if (affected == 0) - { // Record not found - throw new RecordUpdateFailedException(this, keys); - } - // Rollback - db.rollback(conn); - return; - } - else if (affected > 1) - { // More than one record - throw new RecordUpdateInvalidException(this, keys); - } - else - { // success - log.info("Record for table '" + table.getName() + " sucessfully updated!"); - } - // Correct Timestamp - if (timestampIndex >= 0) - { // Set the correct Timestamp - fields[timestampIndex] = timestampValue; - } - } - // success - rec.updateComplete(keys); - } - - /** - * Adds join restrictions to the supplied command object. - */ - protected boolean addJoinRestriction(DBCommand upd, DBColumn updCol, DBColumn keyCol, DBColumn[] keyColumns, DBRecord rec) - { // Find key for foreign field - Object rowsetData = rec.getRowSetData(); - for (int i = 0; i < keyColumns.length; i++) - if (keyColumns[i]==keyCol && rowsetData!=null) - { // Set Field from Key - upd.where(updCol.is(((Object[]) rowsetData)[i])); - return true; - } - // Not found, what about the record - int index = this.getColumnIndex(updCol); - if (index<0) - index = this.getColumnIndex(keyCol); - if (index>=0) - { // Field Found - if (rec.wasModified(index)) - return false; // Ooops, Key field has changed - // Set Constraint - upd.where(updCol.is(rec.getValue(index))); - return true; - } - return false; - } - - /** - * Deletes a record identified by its primary key from the database. - * - * @param keys array of primary key values - * @param conn a valid database connection - */ - @Override - public void deleteRecord(Object[] keys, Connection conn) - { - throw new NotImplementedException(this, "deleteRecord()"); - } - +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.empire.db; + +import java.sql.Connection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.empire.commons.ObjectUtils; +import org.apache.empire.commons.StringUtils; +import org.apache.empire.db.exceptions.InvalidKeyException; +import org.apache.empire.db.exceptions.NoPrimaryKeyException; +import org.apache.empire.db.exceptions.QueryNoResultException; +import org.apache.empire.db.exceptions.RecordNotFoundException; +import org.apache.empire.db.exceptions.RecordUpdateFailedException; +import org.apache.empire.db.exceptions.RecordUpdateInvalidException; +import org.apache.empire.db.expr.compare.DBCompareColExpr; +import org.apache.empire.db.expr.compare.DBCompareExpr; +import org.apache.empire.db.expr.join.DBColumnJoinExpr; +import org.apache.empire.db.expr.join.DBJoinExpr; +import org.apache.empire.exceptions.InvalidArgumentException; +import org.apache.empire.exceptions.ItemNotFoundException; +import org.apache.empire.exceptions.NotImplementedException; +import org.apache.empire.exceptions.NotSupportedException; + + +/** + * This class can be used to wrap a query from a DBCommand and use it like a DBRowSet.<BR> + * You may use this class for two purposes: + * <UL> + * <LI>In oder to define subqueries simply define a command object with the subquery and wrap it inside a DBQuery. + * Then in a second command object you can reference this Query to join with your other tables and views. + * In order to join other columns with your query use findQueryColumn(DBColumnExpr expr) to get the + * query column object for a given column expression in the original select clause.</LI> + * <LI>With a key supplied you can have an updateable query that will update several records at once.</LI> + * </UL> + * + */ +public class DBQuery extends DBRowSet +{ + private final static long serialVersionUID = 1L; + + private static AtomicInteger queryCount = new AtomicInteger(0); + + protected DBCommandExpr cmdExpr; + protected DBColumn[] keyColumns = null; + protected DBQueryColumn[] queryColumns = null; + protected String alias; + + /** + * Constructor initializes the query object. + * Saves the columns and the primary keys of this query. + * + * @param cmd the SQL-Command + * @param keyColumns an array of the primary key columns + * @param the query alias + */ + public DBQuery(DBCommandExpr cmd, DBColumn[] keyColumns, String alias) + { // Set the column expressions + super(cmd.getDatabase()); + this.cmdExpr = cmd; + // Set Query Columns + DBColumnExpr[] exprList = cmd.getSelectExprList(); + queryColumns = new DBQueryColumn[exprList.length]; + for (int i = 0; i < exprList.length; i++) + { // Init Columns + columns.add(exprList[i].getUpdateColumn()); + queryColumns[i] = new DBQueryColumn(this, exprList[i]); + } + // Set the key Column + this.keyColumns = keyColumns; + // set alias + this.alias = alias; + } + + /** + * Constructor initializes the query object. + * Saves the columns and the primary keys of this query. + * + * @param cmd the SQL-Command + * @param keyColumns an array of the primary key columns + */ + public DBQuery(DBCommandExpr cmd, DBColumn[] keyColumns) + { // Set the column expressions + this(cmd, keyColumns, "q" + String.valueOf(queryCount.incrementAndGet())); + } + + /** + * Constructs a new DBQuery object initialize the query object. + * Save the columns and the primary key of this query. + * + * @param cmd the SQL-Command + * @param keyColumn the primary key column + * @param the query alias + */ + public DBQuery(DBCommandExpr cmd, DBColumn keyColumn, String alias) + { // Set the column expressions + this(cmd, new DBColumn[] { keyColumn }, alias); + } + + /** + * Constructs a new DBQuery object initialize the query object. + * Save the columns and the primary key of this query. + * + * @param cmd the SQL-Command + * @param keyColumn the primary key column + */ + public DBQuery(DBCommandExpr cmd, DBColumn keyColumn) + { // Set the column expressions + this(cmd, new DBColumn[] { keyColumn }); + } + + /** + * Creaes a DBQuery object from a given command object. + * + * @param cmd the command object representing an SQL-Command. + * @param the query alias + */ + public DBQuery(DBCommandExpr cmd, String alias) + { // Set the column expressions + this(cmd, (DBColumn[]) null, alias); + } + + /** + * Creaes a DBQuery object from a given command object. + * + * @param cmd the command object representing an SQL-Command. + */ + public DBQuery(DBCommandExpr cmd) + { // Set the column expressions + this(cmd, (DBColumn[]) null); + } + + /** + * returns the command from the underlying command expression or throws an exception + * @return the command used for this query + */ + private DBCommand getCommandFromExpression() + { + if (cmdExpr instanceof DBCommand) + return ((DBCommand)cmdExpr); + // not supported + throw new NotSupportedException(this, "getCommand"); + } + + /** + * returns the underlying command expression + * @return the command used for this query + */ + public DBCommandExpr getCommandExpr() + { + return cmdExpr; + } + + /** + * not applicable - returns null + */ + @Override + public String getName() + { + return null; + } + + /** + * not applicable - returns null + */ + @Override + public String getAlias() + { + return alias; + } + + /** + * Returns whether or not the table supports record updates. + * @return true if the table allows record updates + */ + @Override + public boolean isUpdateable() + { + return (getKeyColumns()!=null); + } + + /** + * Gets all columns of this rowset (e.g. for cmd.select()). + * + * @return all columns of this rowset + */ + public DBQueryColumn[] getQueryColumns() + { + return queryColumns; + } + + /** + * This function searchs for equal columns given by the + * specified DBColumnExpr object. + * + * @param expr the DBColumnExpr object + * @return the located column + */ + public DBQueryColumn findQueryColumn(DBColumnExpr expr) + { + for (int i = 0; i < queryColumns.length; i++) + { + if (queryColumns[i].expr.equals(expr)) + return queryColumns[i]; + } + // not found + return null; + } + + /** + * This function searchs for a query column by name + * + * @param the column name + * @return the located column + */ + public DBQueryColumn findQueryColumn(String name) + { + for (int i = 0; i < queryColumns.length; i++) + { + if (StringUtils.compareEqual(queryColumns[i].getName(), name, true)) + return queryColumns[i]; + } + // not found + return null; + } + + /** + * return query key columns + */ + @Override + public DBColumn[] getKeyColumns() + { + return keyColumns; + } + + /** + * Returns a array of primary key columns by a specified DBRecord object. + * + * @param record the DBRecord object, contains all fields and the field properties + * @return a array of primary key columns + */ + @Override + public Object[] getRecordKey(DBRecord record) + { + if (record == null || record.getRowSet() != this) + throw new InvalidArgumentException("record", record); + // get Key + return (Object[]) record.getRowSetData(); + } + + /** + * Adds the select SQL Command of this object to the specified StringBuilder object. + * + * @param buf the SQL-Command + * @param context the current SQL-Command context + */ + @Override + public void addSQL(StringBuilder buf, long context) + { + buf.append("("); + buf.append(cmdExpr.getSelect()); + buf.append(")"); + // Add Alias + if ((context & CTX_ALIAS) != 0 && alias != null) + { // append alias + buf.append(" "); + buf.append(alias); + } + } + + /** + * Initialize specified DBRecord object with primary key + * columns (the Object[] keyValues). + * + * @param rec the Record object + * @param keyValues an array of the primary key columns + */ + @Override + public void initRecord(DBRecord rec, Object[] keyValues, boolean insert) + { + // Prepare + prepareInitRecord(rec, keyValues, insert); + // Initialize all Fields + Object[] fields = rec.getFields(); + for (int i = 0; i < fields.length; i++) + fields[i] = ObjectUtils.NO_VALUE; + // Set primary key values + if (keyValues != null) + { // search for primary key fields + DBColumn[] keyColumns = getKeyColumns(); + for (int i = 0; i < keyColumns.length; i++) + if (columns.contains(keyColumns[i])) + fields[columns.indexOf(keyColumns[i])] = keyValues[i]; + } + // Init + completeInitRecord(rec); + } + + /** + * Returns an error, because it is not possible to add a record to a query. + * + * @param rec the DBRecord object, contains all fields and the field properties + * @param conn a valid database connection + * @throws NotImplementedException because this is not implemented + */ + @Override + public void createRecord(DBRecord rec, Connection conn) + { + throw new NotImplementedException(this, "createRecord"); + } + + /** + * Creates a select SQL-Command of the query call the InitRecord method to execute the SQL-Command. + * + * @param rec the DBRecord object, contains all fields and the field properties + * @param key an array of the primary key columns + * @param conn a valid connection to the database. + */ + @Override + public void readRecord(DBRecord rec, Object[] key, Connection conn) + { + if (conn == null || rec == null) + throw new InvalidArgumentException("conn|rec", null); + DBColumn[] keyColumns = getKeyColumns(); + if (key == null || keyColumns.length != key.length) + throw new InvalidKeyException(this, key); + // Select + DBCommand cmd = getCommandFromExpression(); + for (int i = 0; i < keyColumns.length; i++) + { // Set key column constraint + Object value = key[i]; + if (db.isPreparedStatementsEnabled()) + value = cmd.addParam(keyColumns[i], value); + cmd.where(keyColumns[i].is(value)); + } + // Read Record + try { + // Read Record + readRecord(rec, cmd, conn); + // Set RowSetData + rec.updateComplete(key.clone()); + } catch (QueryNoResultException e) { + // Record not found + throw new RecordNotFoundException(this, key); + } + } + + /** + * Updates a query record by creating individual update commands for each table. + * + * @param rec the DBRecord object. contains all fields and the field properties + * @param conn a valid connection to the database. + */ + @Override + public void updateRecord(DBRecord rec, Connection conn) + { + // check updateable + if (isUpdateable()==false) + throw new NotSupportedException(this, "updateRecord"); + // check params + if (rec == null) + throw new InvalidArgumentException("record", null); + if (conn == null) + throw new InvalidArgumentException("conn", null); + // Has record been modified? + if (rec.isModified() == false) + return; // Nothing to update + // Must have key Columns + DBColumn[] keyColumns = getKeyColumns(); + if (keyColumns==null) + throw new NoPrimaryKeyException(this); + // Get the fields and the flags + Object[] fields = rec.getFields(); + // Get all Update Commands + Map<DBRowSet, DBCommand> updCmds = new HashMap<DBRowSet, DBCommand>(3); + for (int i = 0; i < columns.size(); i++) + { // get the table + DBColumn col = columns.get(i); + if (col == null) + continue; + DBRowSet table = col.getRowSet(); + DBCommand updCmd = updCmds.get(table); + if (updCmd == null) + { // Add a new Command + updCmd = db.createCommand(); + updCmds.put(table, updCmd); + } + /* + * if (updateTimestampColumns.contains( col ) ) { // Check the update timestamp cmd.set( col.to( DBDatabase.SYSDATE ) ); } + */ + // Set the field Value + boolean modified = rec.wasModified(i); + if (modified == true) + { // Update a field + if (col.isReadOnly() && log.isDebugEnabled()) + log.debug("updateRecord: Read-only column '" + col.getName() + " has been modified!"); + // Check the value + col.validate(fields[i]); + // Set + updCmd.set(col.to(fields[i])); + } + } + // the commands + DBCommand cmd = getCommandFromExpression(); + Object[] keys = (Object[]) rec.getRowSetData(); + DBRowSet table= null; + DBCommand upd = null; + for(Entry<DBRowSet,DBCommand> entry:updCmds.entrySet()) + { + int i = 0; + // Iterate through options + table = entry.getKey(); + upd = entry.getValue(); + // Is there something to update + if (upd.set == null) + continue; // nothing to do for this table! + // Evaluate Joins + for (i = 0; cmd.joins != null && i < cmd.joins.size(); i++) + { + DBJoinExpr jex = cmd.joins.get(i); + if (!(jex instanceof DBColumnJoinExpr)) + continue; + DBColumnJoinExpr join = (DBColumnJoinExpr)jex; + DBColumn left = join.getLeft() .getUpdateColumn(); + DBColumn right = join.getRight().getUpdateColumn(); + if (left.getRowSet()==table && table.isKeyColumn(left)) + if (!addJoinRestriction(upd, left, right, keyColumns, rec)) + throw new ItemNotFoundException(left.getFullName()); + if (right.getRowSet()==table && table.isKeyColumn(right)) + if (!addJoinRestriction(upd, right, left, keyColumns, rec)) + throw new ItemNotFoundException(right.getFullName()); + } + // Evaluate Existing restrictions + for (i = 0; cmd.where != null && i < cmd.where.size(); i++) + { + DBCompareExpr cmp = cmd.where.get(i); + if (cmp instanceof DBCompareColExpr) + { // Check whether constraint belongs to update table + DBCompareColExpr cmpExpr = (DBCompareColExpr) cmp; + DBColumn col = cmpExpr.getColumnExpr().getUpdateColumn(); + if (col!=null && col.getRowSet() == table) + { // add the constraint + if (cmpExpr.getValue() instanceof DBCmdParam) + { // Create a new command param + DBColumnExpr colExpr = cmpExpr.getColumnExpr(); + DBCmdParam param =(DBCmdParam)cmpExpr.getValue(); + DBCmdParam value = upd.addParam(colExpr, param.getValue()); + cmp = new DBCompareColExpr(colExpr, cmpExpr.getCmpop(), value); + } + upd.where(cmp); + } + } + else + { // other constraints are not supported + throw new NotSupportedException(this, "updateRecord with "+cmp.getClass().getName()); + } + } + // Add Restrictions + for (i = 0; i < keyColumns.length; i++) + { + if (keyColumns[i].getRowSet() == table) + { // Set key column constraint + Object value = keys[i]; + if (db.isPreparedStatementsEnabled()) + value = upd.addParam(keyColumns[i], value); + upd.where(keyColumns[i].is(value)); + } + } + + // Set Update Timestamp + int timestampIndex = -1; + Object timestampValue = null; + if (table.getTimestampColumn() != null) + { + DBColumn tsColumn = table.getTimestampColumn(); + timestampIndex = this.getColumnIndex(tsColumn); + if (timestampIndex>=0) + { // The timestamp is availabe in the record + timestampValue = db.getUpdateTimestamp(conn); + Object lastTS = fields[timestampIndex]; + if (ObjectUtils.isEmpty(lastTS)==false) + { // set timestamp constraint + if (db.isPreparedStatementsEnabled()) + lastTS = upd.addParam(tsColumn, lastTS); + upd.where(tsColumn.is(lastTS)); + } + // Set new Timestamp + upd.set(tsColumn.to(timestampValue)); + } + else + { // Timestamp columns has not been provided with the record + upd.set(tsColumn.to(DBDatabase.SYSDATE)); + } + } + + // Execute SQL + int affected = db.executeSQL(upd.getUpdate(), upd.getParamValues(), conn); + if (affected <= 0) + { // Error + if (affected == 0) + { // Record not found + throw new RecordUpdateFailedException(this, keys); + } + // Rollback + db.rollback(conn); + return; + } + else if (affected > 1) + { // More than one record + throw new RecordUpdateInvalidException(this, keys); + } + else + { // success + log.info("Record for table '" + table.getName() + " sucessfully updated!"); + } + // Correct Timestamp + if (timestampIndex >= 0) + { // Set the correct Timestamp + fields[timestampIndex] = timestampValue; + } + } + // success + rec.updateComplete(keys); + } + + /** + * Adds join restrictions to the supplied command object. + */ + protected boolean addJoinRestriction(DBCommand upd, DBColumn updCol, DBColumn keyCol, DBColumn[] keyColumns, DBRecord rec) + { // Find key for foreign field + Object rowsetData = rec.getRowSetData(); + for (int i = 0; i < keyColumns.length; i++) + if (keyColumns[i]==keyCol && rowsetData!=null) + { // Set Field from Key + upd.where(updCol.is(((Object[]) rowsetData)[i])); + return true; + } + // Not found, what about the record + int index = this.getColumnIndex(updCol); + if (index<0) + index = this.getColumnIndex(keyCol); + if (index>=0) + { // Field Found + if (rec.wasModified(index)) + return false; // Ooops, Key field has changed + // Set Constraint + upd.where(updCol.is(rec.getValue(index))); + return true; + } + return false; + } + + /** + * Deletes a record identified by its primary key from the database. + * + * @param keys array of primary key values + * @param conn a valid database connection + */ + @Override + public void deleteRecord(Object[] keys, Connection conn) + { + throw new NotImplementedException(this, "deleteRecord()"); + } + } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/empire-db/blob/0a8ae70f/empire-db/src/main/java/org/apache/empire/db/DBQueryColumn.java ---------------------------------------------------------------------- diff --git a/empire-db/src/main/java/org/apache/empire/db/DBQueryColumn.java b/empire-db/src/main/java/org/apache/empire/db/DBQueryColumn.java new file mode 100644 index 0000000..d1d28cc --- /dev/null +++ b/empire-db/src/main/java/org/apache/empire/db/DBQueryColumn.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.empire.db; + +import org.apache.empire.commons.Options; +import org.apache.empire.data.DataType; +import org.w3c.dom.Element; + +public class DBQueryColumn extends DBColumn +{ + private final static long serialVersionUID = 1L; + protected final DBColumnExpr expr; + + /** + * Constructs a DBQueryColumn object set the specified parameters to this object. + * <P> + * @param query the DBQuery object + * @param expr the concrete DBColumnExpr object + */ + public DBQueryColumn(DBQuery query, DBColumnExpr expr) + { // call base + super(query, expr.getName()); + // set Expression + this.expr = expr; + } + + public DBColumnExpr getQueryExpression() + { + return expr; + } + + @Override + public DataType getDataType() + { + return expr.getDataType(); + } + + @Override + public double getSize() + { + DBColumn column = expr.getUpdateColumn(); + if (column==null) + return 0.0; + return column.getSize(); + } + + @Override + public boolean isReadOnly() + { + DBColumn column = expr.getUpdateColumn(); + if (column==null) + return true; + return column.isReadOnly(); + } + + @Override + public boolean isAutoGenerated() + { + DBColumn column = expr.getUpdateColumn(); + if (column==null) + return false; + return column.isAutoGenerated(); + } + + @Override + public boolean isRequired() + { + DBColumn column = expr.getUpdateColumn(); + if (column==null) + return false; + return column.isRequired(); + } + + @Override + public Object validate(Object value) + { + DBColumn column = expr.getUpdateColumn(); + if (column==null) + return value; + return column.validate(value); + } + + @Override + public Object getAttribute(String name) + { + if (attributes != null && attributes.contains(name)) + return attributes.get(name); + // Otherwise ask expression + DBColumn column = expr.getUpdateColumn(); + if (column==null) + return null; + return column.getAttribute(name); + } + + @Override + public Options getOptions() + { + if (options != null) + return options; + // Otherwise ask expression + DBColumn column = expr.getUpdateColumn(); + if (column==null) + return null; + return column.getOptions(); + } + + @Override + public Element addXml(Element parent, long flags) + { + return expr.addXml(parent, flags); + } +}
