You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@empire-db.apache.org by do...@apache.org on 2018/01/07 11:28:00 UTC
[1/2] empire-db git commit: EMPIREDB-266 Add special handling for
DBQueryColumns in DBReader getFieldIndex()
Repository: empire-db
Updated Branches:
refs/heads/master 65f5aa094 -> 0a8ae70fc
http://git-wip-us.apache.org/repos/asf/empire-db/blob/0a8ae70f/empire-db/src/main/java/org/apache/empire/db/DBReader.java
----------------------------------------------------------------------
diff --git a/empire-db/src/main/java/org/apache/empire/db/DBReader.java b/empire-db/src/main/java/org/apache/empire/db/DBReader.java
index 484c316..1d35097 100644
--- a/empire-db/src/main/java/org/apache/empire/db/DBReader.java
+++ b/empire-db/src/main/java/org/apache/empire/db/DBReader.java
@@ -1,1095 +1,1101 @@
-/*
- * 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.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.beanutils.ConstructorUtils;
-import org.apache.empire.commons.ObjectUtils;
-import org.apache.empire.data.ColumnExpr;
-import org.apache.empire.data.DataType;
-import org.apache.empire.db.exceptions.EmpireSQLException;
-import org.apache.empire.db.exceptions.QueryNoResultException;
-import org.apache.empire.db.expr.join.DBJoinExpr;
-import org.apache.empire.exceptions.BeanInstantiationException;
-import org.apache.empire.exceptions.InvalidArgumentException;
-import org.apache.empire.exceptions.MiscellaneousErrorException;
-import org.apache.empire.exceptions.ObjectNotValidException;
-import org.apache.empire.xml.XMLUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-
-/**
- * <P>
- * This class is used to perform database queries from a DBCommand object and access the results.<BR>
- * In oder to perform a query call the open() function or - for single row queries - call getRecordData();<BR>
- * You can iterate through the rows using moveNext() or an iterator.<BR>
- * <P>
- * However take care: A reader must always be explicitly closed using the close() method!<BR>
- * Otherwise you may lock the JDBC connection and run out of resources.<BR>
- * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR>
- * <P>
- * To access and work with the query result you can do one of the following:<BR>
- * <ul>
- * <li>access field values directly by using one of the get... functions (see {@link DBRecordData})</li>
- * <li>get the rows as a list of Java Beans using by using {@link DBReader#getBeanList(Class, int)}</li>
- * <li>get the rows as an XML-Document using {@link DBReader#getXmlDocument()} </li>
- * <li>initialize a DBRecord with the current row data using {@link DBReader#initRecord(DBRowSet, DBRecord)}<br>
- * This will allow you to modify and update the data.
- * </li>
- * </ul>
- *
- *
- */
-public class DBReader extends DBRecordData
-{
- private final static long serialVersionUID = 1L;
-
- public abstract class DBReaderIterator implements Iterator<DBRecordData>
- {
- protected int curCount = 0;
- protected int maxCount = 0;
-
- public DBReaderIterator(int maxCount)
- {
- if (maxCount < 0)
- maxCount = 0x7FFFFFFF; // Highest positive number
- // Set Maxcount
- this.maxCount = maxCount;
- }
-
- /**
- * Implements the Iterator Interface Method remove not implemented and not applicable.
- */
- @Override
- public void remove()
- {
- log.error("DBReader.remove ist not implemented!");
- }
-
- /**
- * Disposes the iterator.
- */
- public void dispose()
- {
- curCount = maxCount = -1;
- }
- }
-
- /**
- * This is an iterator for scrolling resultsets.
- * This iterator has no such limitations as the forward iterator.
- */
- public class DBReaderScrollableIterator extends DBReaderIterator
- {
- public DBReaderScrollableIterator(int maxCount)
- {
- super(maxCount);
- }
-
- /**
- * Implements the Iterator Interface.
- *
- * @return true if there is another record to read
- */
- @Override
- public boolean hasNext()
- {
- try
- { // Check position
- if (curCount >= maxCount)
- return false;
- // Check Recordset
- if (rset == null || rset.isLast() || rset.isAfterLast())
- return false;
- // there are more records
- return true;
- } catch (SQLException e) {
- // Error
- throw new EmpireSQLException(getDatabase(), e);
- }
- }
-
- /**
- * Implements the Iterator Interface.
- *
- * @return the current Record interface
- */
- @Override
- public DBRecordData next()
- {
- if ((curCount < maxCount && moveNext()))
- {
- curCount++;
- return DBReader.this;
- }
- // Past the end!
- return null;
- }
- }
-
- /**
- * This is an iterator for forward only resultsets.
- * There is an important limitation on this iterator: After calling
- * hasNext() the caller may not use any functions on the current item any more. i.e.
- * Example:
- * while (i.hasNext())
- * {
- * DBRecordData r = i.next();
- * Object o = r.getValue(0); // ok
- *
- * bool last = i.hasNext(); // ok
- * Object o = r.getValue(0); // Illegal call!
- * }
- */
- public class DBReaderForwardIterator extends DBReaderIterator
- {
- private boolean getCurrent = true;
- private boolean hasCurrent = false;
-
- public DBReaderForwardIterator(int maxCount)
- {
- super(maxCount);
- }
-
- /**
- * Implements the Iterator Interface.
- *
- * @return true if there is another record to read
- */
- @Override
- public boolean hasNext()
- {
- // Check position
- if (curCount >= maxCount)
- return false;
- if (rset == null)
- throw new ObjectNotValidException(this);
- // Check next Record
- if (getCurrent == true)
- {
- getCurrent = false;
- hasCurrent = moveNext();
- }
- return hasCurrent;
- }
-
- /**
- * Implements the Iterator Interface.
- *
- * @return the current Record interface
- */
- @Override
- public DBRecordData next()
- {
- if (hasCurrent == false)
- return null; // Past the end!
- // next called without call to hasNext ?
- if (getCurrent && !moveNext())
- { // No more records
- hasCurrent = false;
- getCurrent = false;
- return null;
- }
- // Move forward
- curCount++;
- getCurrent = true;
- return DBReader.this;
- }
- }
-
- // Logger
- protected static final Logger log = LoggerFactory.getLogger(DBReader.class);
-
- private static boolean trackOpenResultSets = false;
-
- /**
- * Support for finding code errors where a DBRecordSet is opened but not closed
- */
- private static ThreadLocal<Map<DBReader, Exception>> threadLocalOpenResultSets = new ThreadLocal<Map<DBReader, Exception>>();
-
- // Object references
- private DBDatabase db = null;
- private DBColumnExpr[] colList = null;
- private ResultSet rset = null;
- // the field index map
- private Map<ColumnExpr, Integer> fieldIndexMap = null;
-
- /**
- * Constructs a default DBReader object with the fieldIndexMap enabled.
- */
- public DBReader()
- {
- // Default Constructor
- this(true);
- }
-
- /**
- * Constructs an empty DBRecordSet object.
- * @param useFieldIndexMap
- */
- public DBReader(boolean useFieldIndexMap)
- {
- if (useFieldIndexMap)
- fieldIndexMap = new HashMap<ColumnExpr, Integer>();
- }
-
- /**
- * Returns the current DBDatabase object.
- *
- * @return the current DBDatabase object
- */
- @Override
- public DBDatabase getDatabase()
- {
- return db;
- }
-
- public boolean getScrollable()
- {
- try
- {
- // Check Resultset
- return (rset!=null && rset.getType()!=ResultSet.TYPE_FORWARD_ONLY);
- } catch (SQLException e)
- {
- log.error("Cannot determine Resultset type", e);
- return false;
- }
- }
-
- /**
- * Returns the index value by a specified DBColumnExpr object.
- *
- * @return the index value
- */
- @Override
- public int getFieldIndex(ColumnExpr column)
- {
- if (fieldIndexMap==null)
- return findFieldIndex(column);
- // Use fieldIndexMap
- Integer index = fieldIndexMap.get(column);
- if (index==null)
- { // add to field Index map
- index = findFieldIndex(column);
- fieldIndexMap.put(column, index);
- }
- return index;
- }
-
- /** Get the column Expression at position */
- @Override
- public DBColumnExpr getColumnExpr(int iColumn)
- {
- if (colList == null || iColumn < 0 || iColumn >= colList.length)
- return null; // Index out of range
- // return column Expression
- return colList[iColumn];
- }
-
- /**
- * Returns the index value by a specified column name.
- *
- * @param column the column name
- * @return the index value
- */
- @Override
- public int getFieldIndex(String column)
- {
- if (colList != null)
- {
- for (int i = 0; i < colList.length; i++)
- if (colList[i].getName().equalsIgnoreCase(column))
- return i;
- }
- // not found
- return -1;
- }
-
- /**
- * Checks wehter a column value is null Unlike the base
- * class implementation, this class directly check the value fromt the
- * resultset.
- *
- * @param index index of the column
- * @return true if the value is null or false otherwise
- */
- @Override
- public boolean isNull(int index)
- {
- if (index < 0 || index >= colList.length)
- { // Index out of range
- log.error("Index out of range: " + index);
- return true;
- }
- try
- { // Check Value on Resultset
- rset.getObject(index + 1);
- return rset.wasNull();
- } catch (Exception e)
- {
- log.error("isNullValue exception", e);
- return super.isNull(index);
- }
- }
-
- /**
- * Returns a data value identified by the column index.
- *
- * @param index index of the column
- * @return the value
- */
- @Override
- public Object getValue(int index)
- {
- // Check params
- if (index < 0 || index >= colList.length)
- throw new InvalidArgumentException("index", index);
- try
- { // Get Value from Resultset
- DataType dataType = colList[index].getDataType();
- return db.driver.getResultValue(rset, index + 1, dataType);
-
- } catch (SQLException e)
- { // Operation failed
- throw new EmpireSQLException(this, e);
- }
- }
-
- /**
- * Checks if the rowset is open
- *
- * @return true if the rowset is open
- */
- public boolean isOpen()
- {
- return (rset != null);
- }
-
- /**
- * Opens the reader by executing the given SQL command.<BR>
- * After the reader is open, the reader's position is before the first record.<BR>
- * Use moveNext or iterator() to step through the rows.<BR>
- * Data of the current row can be accessed through the functions on the RecordData interface.<BR>
- * <P>
- * ATTENTION: After using the reader it must be closed using the close() method!<BR>
- * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR>
- * <P>
- * @param cmd the SQL-Command with cmd.getSelect()
- * @param scrollable true if the reader should be scrollable or false if not
- * @param conn a valid JDBC connection.
- */
- public void open(DBCommandExpr cmd, boolean scrollable, Connection conn)
- {
- if (isOpen())
- close();
- // Get the query statement
- String sqlCmd = cmd.getSelect();
- // Collect the query parameters
- Object[] paramValues = cmd.getParamValues();
- List<Object[]> subqueryParamValues = (cmd instanceof DBCommand) ? findSubQueryParams((DBCommand)cmd) : null;
- if (subqueryParamValues!=null && !subqueryParamValues.isEmpty())
- { // Check Count
- if (paramValues!=null || subqueryParamValues.size()>1)
- throw new MiscellaneousErrorException("More than one (sub)query is a parameterized query. Currently one one query is allowed to be parameterized!");
- // Use subquery params
- paramValues = subqueryParamValues.get(0);
- }
- // Execute the query
- DBDatabase queryDb = cmd.getDatabase();
- ResultSet queryRset = queryDb.executeQuery(sqlCmd, paramValues, scrollable, conn);
- if (queryRset==null)
- throw new QueryNoResultException(sqlCmd);
- // init
- init(queryDb, cmd.getSelectExprList(), queryRset);
- }
-
- /**
- * Opens the reader by executing the given SQL command.<BR>
- * <P>
- * see {@link DBReader#open(DBCommandExpr, boolean, Connection)}
- * </P>
- * @param cmd the SQL-Command with cmd.getSelect()
- * @param conn a valid JDBC connection.
- */
- public final void open(DBCommandExpr cmd, Connection conn)
- {
- open(cmd, false, conn);
- }
-
- /**
- * <P>
- * Opens the reader by executing the given SQL command and moves to the first row.<BR>
- * If true is returned data of the row can be accessed through the functions on the RecordData interface.<BR>
- * This function is intended for single row queries and provided for convenience.<BR>
- * However it behaves exacly as calling reader.open() and reader.moveNext()<BR>
- * <P>
- * ATTENTION: After using the reader it must be closed using the close() method!<BR>
- * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR>
- * <P>
- * @param cmd the SQL-Command with cmd.getSelect()
- * @param conn a valid JDBC connection.
- */
- public void getRecordData(DBCommandExpr cmd, Connection conn)
- { // Open the record
- open(cmd, conn);
- // Get First Record
- if (!moveNext())
- { // Close
- throw new QueryNoResultException(cmd.getSelect());
- }
- }
-
- /**
- * Closes the DBRecordSet object, the Statement object and detach the columns.<BR>
- * A reader must always be closed immediately after using it.
- */
- @Override
- public void close()
- {
- try
- { // Dispose iterator
- if (iterator != null)
- {
- iterator.dispose();
- iterator = null;
- }
- // Close Recordset
- if (rset != null)
- {
- getDatabase().closeResultSet(rset);
- // remove from tracking-list
- endTrackingThisResultSet();
- }
- // Detach columns
- colList = null;
- rset = null;
- // clear FieldIndexMap
- if (fieldIndexMap!=null)
- fieldIndexMap.clear();
- // Done
- } catch (Exception e)
- { // What's wrong here?
- log.warn(e.toString());
- }
- }
-
- /**
- * Moves the cursor down the given number of rows.
- *
- * @param count the number of rows to skip
- *
- * @return true if the reader is on a valid record or false otherwise
- */
- public boolean skipRows(int count)
- {
- try
- { // Check Recordset
- if (rset == null)
- throw new ObjectNotValidException(this);
- // Forward only cursor?
- int type = rset.getType();
- if (type == ResultSet.TYPE_FORWARD_ONLY)
- {
- if (count < 0)
- throw new InvalidArgumentException("count", count);
- // Move
- for (; count > 0; count--)
- {
- if (!moveNext())
- return false;
- }
- return true;
- }
- // Scrollable Cursor
- if (count > 0)
- { // Move a single record first
- if (rset.next() == false)
- return false;
- // Move relative
- if (count > 1)
- return rset.relative(count - 1);
- }
- else if (count < 0)
- { // Move a single record first
- if (rset.previous() == false)
- return false;
- // Move relative
- if (count < -1)
- return rset.relative(count + 1);
- }
- return true;
-
- } catch (SQLException e) {
- // an error occurred
- throw new EmpireSQLException(this, e);
- }
- }
-
- /**
- * Moves the cursor down one row from its current position.
- *
- * @return true if the reader is on a valid record or false otherwise
- */
- public boolean moveNext()
- {
- try
- { // Check Recordset
- if (rset == null)
- throw new ObjectNotValidException(this);
- // Move Next
- if (rset.next() == false)
- { // Close recordset automatically after last record
- close();
- return false;
- }
- return true;
-
- } catch (SQLException e) {
- // an error occurred
- throw new EmpireSQLException(this, e);
- }
- }
-
- private DBReaderIterator iterator = null; // there can only be one!
-
- /**
- * Returns an row iterator for this reader.<BR>
- * There can only be one iterator at a time.
- * <P>
- * @param maxCount the maximum number of item that should be returned by this iterator
- * @return the row iterator
- */
- public Iterator<DBRecordData> iterator(int maxCount)
- {
- if (iterator == null && rset != null)
- {
- if (getScrollable())
- iterator = new DBReaderScrollableIterator(maxCount);
- else
- iterator = new DBReaderForwardIterator(maxCount);
- }
- return iterator;
- }
-
- /**
- * <PRE>
- * Returns an row iterator for this reader.
- * There can only be one iterator at a time.
- * </PRE>
- * @return the row iterator
- */
- public final Iterator<DBRecordData> iterator()
- {
- return iterator(-1);
- }
-
- /**
- * <PRE>
- * initializes a DBRecord object with the values of the current row.
- * At least all primary key columns of the target rowset must be provided by this reader.
- * This function is equivalent to calling rowset.initRecord(rec, reader)
- * set also {@link DBRowSet#initRecord(DBRecord, DBRecordData)});
- * </PRE>
- * @param rowset the rowset to which to attach
- * @param rec the record which to initialize
- */
- public void initRecord(DBRowSet rowset, DBRecord rec)
- {
- if (rowset==null)
- throw new InvalidArgumentException("rowset", rowset);
- // init Record
- rowset.initRecord(rec, this);
- }
-
- /**
- * Returns the result of a query as a list of objects restricted
- * to a maximum number of objects (unless maxCount is -1).
- *
- * @param c the collection to add the objects to
- * @param t the class type of the objects in the list
- * @param maxCount the maximum number of objects
- *
- * @return the list of T
- */
- @SuppressWarnings("unchecked")
- public <C extends Collection<T>, T> C getBeanList(C c, Class<T> t, int maxCount)
- {
- // Check Recordset
- if (rset == null)
- { // Resultset not available
- throw new ObjectNotValidException(this);
- }
- // Query List
- try
- {
- // Check whether we can use a constructor
- Class<?>[] paramTypes = new Class[getFieldCount()];
- for (int i = 0; i < colList.length; i++)
- paramTypes[i] = DBExpr.getValueClass(colList[i].getDataType());
- // Find Constructor
- Constructor<?> ctor = findMatchingAccessibleConstructor(t, paramTypes);
- Object[] args = (ctor!=null) ? new Object[getFieldCount()] : null;
-
- // Create a list of beans
- while (moveNext() && maxCount != 0)
- { // Create bean an init
- if (ctor!=null)
- { // Use Constructor
- Class<?>[] ctorParamTypes = ctor.getParameterTypes();
- for (int i = 0; i < getFieldCount(); i++)
- args[i] = ObjectUtils.convert(ctorParamTypes[i], getValue(i));
- T bean = (T)ctor.newInstance(args);
- c.add(bean);
- }
- else
- { // Use Property Setters
- T bean = t.newInstance();
- setBeanProperties(bean);
- c.add(bean);
- }
- // Decrease count
- if (maxCount > 0)
- maxCount--;
- }
- // done
- return c;
- } catch (InvocationTargetException e) {
- throw new BeanInstantiationException(t, e);
- } catch (IllegalAccessException e) {
- throw new BeanInstantiationException(t, e);
- } catch (InstantiationException e) {
- throw new BeanInstantiationException(t, e);
- }
- }
-
- /**
- * Returns the result of a query as a list of objects.
- *
- * @param t the class type of the objects in the list
- * @param maxItems the maximum number of objects
- *
- * @return the list of T
- */
- public final <T> ArrayList<T> getBeanList(Class<T> t, int maxItems) {
- return getBeanList(new ArrayList<T>(), t, maxItems);
- }
-
- /**
- * Returns the result of a query as a list of objects.
- *
- * @param t the class type of the objects in the list
- *
- * @return the list of T
- */
- public final <T> ArrayList<T> getBeanList(Class<T> t) {
- return getBeanList(t, -1);
- }
-
- /**
- * Moves the cursor down one row from its current position.
- *
- * @return the number of column descriptions added to the Element
- */
- @Override
- public int addColumnDesc(Element parent)
- {
- if (colList == null)
- throw new ObjectNotValidException(this);
- // Add Field Description
- for (int i = 0; i < colList.length; i++)
- colList[i].addXml(parent, 0);
- // return count
- return colList.length;
- }
-
- /**
- * Adds all children to a parent.
- *
- * @param parent the parent element below which to search the child
- * @return the number of row values added to the element
- */
- @Override
- public int addRowValues(Element parent)
- {
- if (rset == null)
- throw new ObjectNotValidException(this);
- // Add all children
- for (int i = 0; i < colList.length; i++)
- { // Read all
- String name = colList[i].getName();
- String idColumnAttr = getXmlDictionary().getRowIdColumnAttribute();
- if (name.equalsIgnoreCase("id"))
- { // Add Attribute
- parent.setAttribute(idColumnAttr, getString(i));
- }
- else
- { // Add Element
- String value = getString(i);
- Element elem = XMLUtil.addElement(parent, name, value);
- if (value == null)
- elem.setAttribute("null", "yes"); // Null-Value
- }
- }
- // return count
- return colList.length;
- }
-
- /**
- * Adds all children to a parent.
- *
- * @param parent the parent element below which to search the child
- * @return the number of rows added to the element
- */
- public int addRows(Element parent)
- {
- int count = 0;
- if (rset == null)
- return 0;
- // Add all rows
- String rowElementName = getXmlDictionary().getRowElementName();
- while (moveNext())
- {
- addRowValues(XMLUtil.addElement(parent, rowElementName));
- count++;
- }
- return count;
- }
-
- /**
- * returns the DBXmlDictionary that should used to generate XMLDocuments<BR>
- * @return the DBXmlDictionary
- */
- protected DBXmlDictionary getXmlDictionary()
- {
- return DBXmlDictionary.getInstance();
- }
-
- /**
- * Returns a XML document with the field description an values of this record.
- *
- * @return the new XML Document object
- */
- @Override
- public Document getXmlDocument()
- {
- if (rset == null)
- return null;
- // Create Document
- String rowsetElementName = getXmlDictionary().getRowSetElementName();
- Element root = XMLUtil.createDocument(rowsetElementName);
- // Add Field Description
- addColumnDesc(root);
- // Add row rset
- addRows(root);
- // return Document
- return root.getOwnerDocument();
- }
-
- /** returns the number of the elements of the colList array */
- @Override
- public int getFieldCount()
- {
- return (colList != null) ? colList.length : 0;
- }
-
- /**
- * Initialize the reader from an open JDBC-ResultSet
- * @param db the database
- * @param colList the query column expressions
- * @param rset the JDBC-ResultSet
- */
- protected void init(DBDatabase db, DBColumnExpr[] colList, ResultSet rset)
- {
- this.db = db;
- this.colList = colList;
- this.rset = rset;
- // add to tracking list (if enabled)
- trackThisResultSet();
- }
-
- /**
- * Access the column expression list
- * @return the column expression list
- */
- protected final DBColumnExpr[] getColumnExprList()
- {
- return colList;
- }
-
- /**
- * Access the JDBC-ResultSet
- * @return the JDBC-ResultSet
- */
- protected final ResultSet getResultSet()
- {
- return rset;
- }
-
- /**
- * finds the field Index of a given column expression
- * Internally used as helper for getFieldIndex()
- * @return the index value
- */
- protected int findFieldIndex(ColumnExpr column)
- {
- if (colList == null)
- return -1;
- // First chance: Try to find an exact match
- for (int i = 0; i < colList.length; i++)
- {
- if (colList[i].equals(column))
- return i;
- }
- // Second chance: Try Update Column
- if (column instanceof DBColumn)
- {
- for (int i = 0; i < colList.length; i++)
- {
- DBColumn updColumn = colList[i].getUpdateColumn();
- if (updColumn!=null && updColumn.equals(column))
- return i;
- }
- }
- // not found!
- return -1;
- }
-
- /**
- * internal helper function to find parameterized subqueries
- * @param cmd the command
- * @return a list of parameter arrays, one for each subquery
- */
- protected List<Object[]> findSubQueryParams(DBCommand cmd)
- {
- List<Object[]> subQueryParams = null;
- List<DBJoinExpr> joins = cmd.getJoins();
- if (joins==null)
- return null; // no joins
- // check the joins
- for (DBJoinExpr j : joins)
- {
- DBRowSet rsl = j.getLeftTable();
- DBRowSet rsr = j.getRightTable();
- if (rsl instanceof DBQuery)
- { // the left join is a query
- subQueryParams = addSubQueryParams((DBQuery)rsl, subQueryParams);
- }
- if (rsr instanceof DBQuery)
- { // the right join is a query
- subQueryParams = addSubQueryParams((DBQuery)rsr, subQueryParams);
- }
- }
- return subQueryParams;
- }
-
- /**
- * Adds any subquery params to the supplied list
- * @param query the subquery
- * @param list the current list of parameters
- * @return the new list of parameters
- */
- private List<Object[]> addSubQueryParams(DBQuery query, List<Object[]> list)
- {
- DBCommandExpr sqcmd = query.getCommandExpr();
- Object[] params = query.getCommandExpr().getParamValues();
- if (params!=null && params.length>0)
- { // add params
- if (list== null)
- list = new ArrayList<Object[]>();
- list.add(params);
- }
- // recurse
- if (sqcmd instanceof DBCommand)
- { // check this command too
- List<Object[]> sqlist = findSubQueryParams((DBCommand)sqcmd);
- if (sqlist!=null && !sqlist.isEmpty())
- { // make one list
- if (list!= null)
- list.addAll(sqlist);
- else
- list = sqlist;
- }
- }
- return list;
- }
-
- /**
- * Support for finding code errors where a DBRecordSet is opened but not closed.
- *
- * @author bond
- */
- protected synchronized void trackThisResultSet()
- {
- // check if enabled
- if (trackOpenResultSets==false)
- return;
- // add this to the vector of open resultsets on this thread
- Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get();
- if (openResultSets == null)
- {
- // Lazy initialization of the
- openResultSets = new HashMap<DBReader, Exception>(2);
- threadLocalOpenResultSets.set(openResultSets);
- }
-
- Exception stackException = openResultSets.get(this);
- if (stackException != null)
- {
- log.error("DBRecordSet.addOpenResultSet called for an object which is already in the open list. This is the stack of the method opening the object which was not previously closed.", stackException);
- // the code continues and overwrites the logged object with the new one
- }
- // get the current stack trace
- openResultSets.put(this, new Exception());
- }
-
- /**
- * Support for finding code errors where a DBRecordSet is opened but not closed.
- *
- * @author bond
- */
- protected synchronized void endTrackingThisResultSet()
- {
- // check if enabled
- if (trackOpenResultSets==false)
- return;
- // remove
- Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get();
- if (openResultSets.containsKey(this) == false)
- {
- log.error("DBRecordSet.removeOpenResultSet called for an object which is not in the open list. Here is the current stack.", new Exception());
- }
- else
- {
- openResultSets.remove(this);
- }
- }
-
- /*
- private void writeObject(ObjectOutputStream stream) throws IOException {
- if (rset != null) {
- throw new NotSerializableException(DBReader.class.getName() + " (due to attached ResultSet)");
- }
- }
- */
-
- /**
- * copied from org.apache.commons.beanutils.ConstructorUtils since it's private there
- */
- protected static Constructor<?> findMatchingAccessibleConstructor(Class<?> clazz, Class<?>[] parameterTypes)
- {
- // See if we can find the method directly
- // probably faster if it works
- // (I am not sure whether it's a good idea to run into Exceptions)
- // try {
- // Constructor ctor = clazz.getConstructor(parameterTypes);
- // try {
- // // see comment in org.apache.commons.beanutils.ConstructorUtils
- // ctor.setAccessible(true);
- // } catch (SecurityException se) { /* ignore */ }
- // return ctor;
- // } catch (NoSuchMethodException e) { /* SWALLOW */ }
-
- // search through all constructors
- int paramSize = parameterTypes.length;
- Constructor<?>[] ctors = clazz.getConstructors();
- for (int i = 0, size = ctors.length; i < size; i++)
- { // compare parameters
- Class<?>[] ctorParams = ctors[i].getParameterTypes();
- int ctorParamSize = ctorParams.length;
- if (ctorParamSize == paramSize)
- { // Param Size matches
- boolean match = true;
- for (int n = 0; n < ctorParamSize; n++)
- {
- if (!ObjectUtils.isAssignmentCompatible(ctorParams[n], parameterTypes[n]))
- {
- match = false;
- break;
- }
- }
- if (match) {
- // get accessible version of method
- Constructor<?> ctor = ConstructorUtils.getAccessibleConstructor(ctors[i]);
- if (ctor != null) {
- try {
- ctor.setAccessible(true);
- } catch (SecurityException se) { /* ignore */ }
- return ctor;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Enables or disabled tracking of open ResultSets
- * @param enable true to enable or false otherwise
- * @return the previous state of the trackOpenResultSets
- */
- public static synchronized boolean enableOpenResultSetTracking(boolean enable)
- {
- boolean prev = trackOpenResultSets;
- trackOpenResultSets = enable;
- return prev;
- }
-
- /**
- * <PRE>
- * Call this if you want to check whether there are any unclosed resultsets
- * It logs stack traces to help find piece of code
- * where a DBReader was opened but not closed.
- * </PRE>
- */
- public static synchronized void checkOpenResultSets()
- {
- // check if enabled
- if (trackOpenResultSets==false)
- throw new MiscellaneousErrorException("Open-ResultSet-Tracking has not been enabled. Use DBReader.enableOpenResultSetTracking() to enable or disable.");
- // Check map
- Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get();
- if (openResultSets != null && openResultSets.isEmpty() == false)
- {
- // we have found a(n) open result set(s). Now show the stack trace(s)
- Object keySet[] = openResultSets.keySet().toArray();
- for (int i = 0; i < keySet.length; i++)
- {
- Exception stackException = openResultSets.get(keySet[i]);
- log.error("A DBReader was not closed. Stack of opening code is ", stackException);
- }
- openResultSets.clear();
- }
- }
-
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.beanutils.ConstructorUtils;
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.data.ColumnExpr;
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.exceptions.EmpireSQLException;
+import org.apache.empire.db.exceptions.QueryNoResultException;
+import org.apache.empire.db.expr.join.DBJoinExpr;
+import org.apache.empire.exceptions.BeanInstantiationException;
+import org.apache.empire.exceptions.InvalidArgumentException;
+import org.apache.empire.exceptions.MiscellaneousErrorException;
+import org.apache.empire.exceptions.ObjectNotValidException;
+import org.apache.empire.xml.XMLUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+
+/**
+ * <P>
+ * This class is used to perform database queries from a DBCommand object and access the results.<BR>
+ * In oder to perform a query call the open() function or - for single row queries - call getRecordData();<BR>
+ * You can iterate through the rows using moveNext() or an iterator.<BR>
+ * <P>
+ * However take care: A reader must always be explicitly closed using the close() method!<BR>
+ * Otherwise you may lock the JDBC connection and run out of resources.<BR>
+ * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR>
+ * <P>
+ * To access and work with the query result you can do one of the following:<BR>
+ * <ul>
+ * <li>access field values directly by using one of the get... functions (see {@link DBRecordData})</li>
+ * <li>get the rows as a list of Java Beans using by using {@link DBReader#getBeanList(Class, int)}</li>
+ * <li>get the rows as an XML-Document using {@link DBReader#getXmlDocument()} </li>
+ * <li>initialize a DBRecord with the current row data using {@link DBReader#initRecord(DBRowSet, DBRecord)}<br>
+ * This will allow you to modify and update the data.
+ * </li>
+ * </ul>
+ *
+ *
+ */
+public class DBReader extends DBRecordData
+{
+ private final static long serialVersionUID = 1L;
+
+ public abstract class DBReaderIterator implements Iterator<DBRecordData>
+ {
+ protected int curCount = 0;
+ protected int maxCount = 0;
+
+ public DBReaderIterator(int maxCount)
+ {
+ if (maxCount < 0)
+ maxCount = 0x7FFFFFFF; // Highest positive number
+ // Set Maxcount
+ this.maxCount = maxCount;
+ }
+
+ /**
+ * Implements the Iterator Interface Method remove not implemented and not applicable.
+ */
+ @Override
+ public void remove()
+ {
+ log.error("DBReader.remove ist not implemented!");
+ }
+
+ /**
+ * Disposes the iterator.
+ */
+ public void dispose()
+ {
+ curCount = maxCount = -1;
+ }
+ }
+
+ /**
+ * This is an iterator for scrolling resultsets.
+ * This iterator has no such limitations as the forward iterator.
+ */
+ public class DBReaderScrollableIterator extends DBReaderIterator
+ {
+ public DBReaderScrollableIterator(int maxCount)
+ {
+ super(maxCount);
+ }
+
+ /**
+ * Implements the Iterator Interface.
+ *
+ * @return true if there is another record to read
+ */
+ @Override
+ public boolean hasNext()
+ {
+ try
+ { // Check position
+ if (curCount >= maxCount)
+ return false;
+ // Check Recordset
+ if (rset == null || rset.isLast() || rset.isAfterLast())
+ return false;
+ // there are more records
+ return true;
+ } catch (SQLException e) {
+ // Error
+ throw new EmpireSQLException(getDatabase(), e);
+ }
+ }
+
+ /**
+ * Implements the Iterator Interface.
+ *
+ * @return the current Record interface
+ */
+ @Override
+ public DBRecordData next()
+ {
+ if ((curCount < maxCount && moveNext()))
+ {
+ curCount++;
+ return DBReader.this;
+ }
+ // Past the end!
+ return null;
+ }
+ }
+
+ /**
+ * This is an iterator for forward only resultsets.
+ * There is an important limitation on this iterator: After calling
+ * hasNext() the caller may not use any functions on the current item any more. i.e.
+ * Example:
+ * while (i.hasNext())
+ * {
+ * DBRecordData r = i.next();
+ * Object o = r.getValue(0); // ok
+ *
+ * bool last = i.hasNext(); // ok
+ * Object o = r.getValue(0); // Illegal call!
+ * }
+ */
+ public class DBReaderForwardIterator extends DBReaderIterator
+ {
+ private boolean getCurrent = true;
+ private boolean hasCurrent = false;
+
+ public DBReaderForwardIterator(int maxCount)
+ {
+ super(maxCount);
+ }
+
+ /**
+ * Implements the Iterator Interface.
+ *
+ * @return true if there is another record to read
+ */
+ @Override
+ public boolean hasNext()
+ {
+ // Check position
+ if (curCount >= maxCount)
+ return false;
+ if (rset == null)
+ throw new ObjectNotValidException(this);
+ // Check next Record
+ if (getCurrent == true)
+ {
+ getCurrent = false;
+ hasCurrent = moveNext();
+ }
+ return hasCurrent;
+ }
+
+ /**
+ * Implements the Iterator Interface.
+ *
+ * @return the current Record interface
+ */
+ @Override
+ public DBRecordData next()
+ {
+ if (hasCurrent == false)
+ return null; // Past the end!
+ // next called without call to hasNext ?
+ if (getCurrent && !moveNext())
+ { // No more records
+ hasCurrent = false;
+ getCurrent = false;
+ return null;
+ }
+ // Move forward
+ curCount++;
+ getCurrent = true;
+ return DBReader.this;
+ }
+ }
+
+ // Logger
+ protected static final Logger log = LoggerFactory.getLogger(DBReader.class);
+
+ private static boolean trackOpenResultSets = false;
+
+ /**
+ * Support for finding code errors where a DBRecordSet is opened but not closed
+ */
+ private static ThreadLocal<Map<DBReader, Exception>> threadLocalOpenResultSets = new ThreadLocal<Map<DBReader, Exception>>();
+
+ // Object references
+ private DBDatabase db = null;
+ private DBColumnExpr[] colList = null;
+ private ResultSet rset = null;
+ // the field index map
+ private Map<ColumnExpr, Integer> fieldIndexMap = null;
+
+ /**
+ * Constructs a default DBReader object with the fieldIndexMap enabled.
+ */
+ public DBReader()
+ {
+ // Default Constructor
+ this(true);
+ }
+
+ /**
+ * Constructs an empty DBRecordSet object.
+ * @param useFieldIndexMap
+ */
+ public DBReader(boolean useFieldIndexMap)
+ {
+ if (useFieldIndexMap)
+ fieldIndexMap = new HashMap<ColumnExpr, Integer>();
+ }
+
+ /**
+ * Returns the current DBDatabase object.
+ *
+ * @return the current DBDatabase object
+ */
+ @Override
+ public DBDatabase getDatabase()
+ {
+ return db;
+ }
+
+ public boolean getScrollable()
+ {
+ try
+ {
+ // Check Resultset
+ return (rset!=null && rset.getType()!=ResultSet.TYPE_FORWARD_ONLY);
+ } catch (SQLException e)
+ {
+ log.error("Cannot determine Resultset type", e);
+ return false;
+ }
+ }
+
+ /**
+ * Returns the index value by a specified DBColumnExpr object.
+ *
+ * @return the index value
+ */
+ @Override
+ public int getFieldIndex(ColumnExpr column)
+ {
+ if (fieldIndexMap==null)
+ return findFieldIndex(column);
+ // Use fieldIndexMap
+ Integer index = fieldIndexMap.get(column);
+ if (index==null)
+ { // add to field Index map
+ index = findFieldIndex(column);
+ fieldIndexMap.put(column, index);
+ }
+ return index;
+ }
+
+ /** Get the column Expression at position */
+ @Override
+ public DBColumnExpr getColumnExpr(int iColumn)
+ {
+ if (colList == null || iColumn < 0 || iColumn >= colList.length)
+ return null; // Index out of range
+ // return column Expression
+ return colList[iColumn];
+ }
+
+ /**
+ * Returns the index value by a specified column name.
+ *
+ * @param column the column name
+ * @return the index value
+ */
+ @Override
+ public int getFieldIndex(String column)
+ {
+ if (colList != null)
+ {
+ for (int i = 0; i < colList.length; i++)
+ if (colList[i].getName().equalsIgnoreCase(column))
+ return i;
+ }
+ // not found
+ return -1;
+ }
+
+ /**
+ * Checks wehter a column value is null Unlike the base
+ * class implementation, this class directly check the value fromt the
+ * resultset.
+ *
+ * @param index index of the column
+ * @return true if the value is null or false otherwise
+ */
+ @Override
+ public boolean isNull(int index)
+ {
+ if (index < 0 || index >= colList.length)
+ { // Index out of range
+ log.error("Index out of range: " + index);
+ return true;
+ }
+ try
+ { // Check Value on Resultset
+ rset.getObject(index + 1);
+ return rset.wasNull();
+ } catch (Exception e)
+ {
+ log.error("isNullValue exception", e);
+ return super.isNull(index);
+ }
+ }
+
+ /**
+ * Returns a data value identified by the column index.
+ *
+ * @param index index of the column
+ * @return the value
+ */
+ @Override
+ public Object getValue(int index)
+ {
+ // Check params
+ if (index < 0 || index >= colList.length)
+ throw new InvalidArgumentException("index", index);
+ try
+ { // Get Value from Resultset
+ DataType dataType = colList[index].getDataType();
+ return db.driver.getResultValue(rset, index + 1, dataType);
+
+ } catch (SQLException e)
+ { // Operation failed
+ throw new EmpireSQLException(this, e);
+ }
+ }
+
+ /**
+ * Checks if the rowset is open
+ *
+ * @return true if the rowset is open
+ */
+ public boolean isOpen()
+ {
+ return (rset != null);
+ }
+
+ /**
+ * Opens the reader by executing the given SQL command.<BR>
+ * After the reader is open, the reader's position is before the first record.<BR>
+ * Use moveNext or iterator() to step through the rows.<BR>
+ * Data of the current row can be accessed through the functions on the RecordData interface.<BR>
+ * <P>
+ * ATTENTION: After using the reader it must be closed using the close() method!<BR>
+ * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR>
+ * <P>
+ * @param cmd the SQL-Command with cmd.getSelect()
+ * @param scrollable true if the reader should be scrollable or false if not
+ * @param conn a valid JDBC connection.
+ */
+ public void open(DBCommandExpr cmd, boolean scrollable, Connection conn)
+ {
+ if (isOpen())
+ close();
+ // Get the query statement
+ String sqlCmd = cmd.getSelect();
+ // Collect the query parameters
+ Object[] paramValues = cmd.getParamValues();
+ List<Object[]> subqueryParamValues = (cmd instanceof DBCommand) ? findSubQueryParams((DBCommand)cmd) : null;
+ if (subqueryParamValues!=null && !subqueryParamValues.isEmpty())
+ { // Check Count
+ if (paramValues!=null || subqueryParamValues.size()>1)
+ throw new MiscellaneousErrorException("More than one (sub)query is a parameterized query. Currently one one query is allowed to be parameterized!");
+ // Use subquery params
+ paramValues = subqueryParamValues.get(0);
+ }
+ // Execute the query
+ DBDatabase queryDb = cmd.getDatabase();
+ ResultSet queryRset = queryDb.executeQuery(sqlCmd, paramValues, scrollable, conn);
+ if (queryRset==null)
+ throw new QueryNoResultException(sqlCmd);
+ // init
+ init(queryDb, cmd.getSelectExprList(), queryRset);
+ }
+
+ /**
+ * Opens the reader by executing the given SQL command.<BR>
+ * <P>
+ * see {@link DBReader#open(DBCommandExpr, boolean, Connection)}
+ * </P>
+ * @param cmd the SQL-Command with cmd.getSelect()
+ * @param conn a valid JDBC connection.
+ */
+ public final void open(DBCommandExpr cmd, Connection conn)
+ {
+ open(cmd, false, conn);
+ }
+
+ /**
+ * <P>
+ * Opens the reader by executing the given SQL command and moves to the first row.<BR>
+ * If true is returned data of the row can be accessed through the functions on the RecordData interface.<BR>
+ * This function is intended for single row queries and provided for convenience.<BR>
+ * However it behaves exacly as calling reader.open() and reader.moveNext()<BR>
+ * <P>
+ * ATTENTION: After using the reader it must be closed using the close() method!<BR>
+ * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR>
+ * <P>
+ * @param cmd the SQL-Command with cmd.getSelect()
+ * @param conn a valid JDBC connection.
+ */
+ public void getRecordData(DBCommandExpr cmd, Connection conn)
+ { // Open the record
+ open(cmd, conn);
+ // Get First Record
+ if (!moveNext())
+ { // Close
+ throw new QueryNoResultException(cmd.getSelect());
+ }
+ }
+
+ /**
+ * Closes the DBRecordSet object, the Statement object and detach the columns.<BR>
+ * A reader must always be closed immediately after using it.
+ */
+ @Override
+ public void close()
+ {
+ try
+ { // Dispose iterator
+ if (iterator != null)
+ {
+ iterator.dispose();
+ iterator = null;
+ }
+ // Close Recordset
+ if (rset != null)
+ {
+ getDatabase().closeResultSet(rset);
+ // remove from tracking-list
+ endTrackingThisResultSet();
+ }
+ // Detach columns
+ colList = null;
+ rset = null;
+ // clear FieldIndexMap
+ if (fieldIndexMap!=null)
+ fieldIndexMap.clear();
+ // Done
+ } catch (Exception e)
+ { // What's wrong here?
+ log.warn(e.toString());
+ }
+ }
+
+ /**
+ * Moves the cursor down the given number of rows.
+ *
+ * @param count the number of rows to skip
+ *
+ * @return true if the reader is on a valid record or false otherwise
+ */
+ public boolean skipRows(int count)
+ {
+ try
+ { // Check Recordset
+ if (rset == null)
+ throw new ObjectNotValidException(this);
+ // Forward only cursor?
+ int type = rset.getType();
+ if (type == ResultSet.TYPE_FORWARD_ONLY)
+ {
+ if (count < 0)
+ throw new InvalidArgumentException("count", count);
+ // Move
+ for (; count > 0; count--)
+ {
+ if (!moveNext())
+ return false;
+ }
+ return true;
+ }
+ // Scrollable Cursor
+ if (count > 0)
+ { // Move a single record first
+ if (rset.next() == false)
+ return false;
+ // Move relative
+ if (count > 1)
+ return rset.relative(count - 1);
+ }
+ else if (count < 0)
+ { // Move a single record first
+ if (rset.previous() == false)
+ return false;
+ // Move relative
+ if (count < -1)
+ return rset.relative(count + 1);
+ }
+ return true;
+
+ } catch (SQLException e) {
+ // an error occurred
+ throw new EmpireSQLException(this, e);
+ }
+ }
+
+ /**
+ * Moves the cursor down one row from its current position.
+ *
+ * @return true if the reader is on a valid record or false otherwise
+ */
+ public boolean moveNext()
+ {
+ try
+ { // Check Recordset
+ if (rset == null)
+ throw new ObjectNotValidException(this);
+ // Move Next
+ if (rset.next() == false)
+ { // Close recordset automatically after last record
+ close();
+ return false;
+ }
+ return true;
+
+ } catch (SQLException e) {
+ // an error occurred
+ throw new EmpireSQLException(this, e);
+ }
+ }
+
+ private DBReaderIterator iterator = null; // there can only be one!
+
+ /**
+ * Returns an row iterator for this reader.<BR>
+ * There can only be one iterator at a time.
+ * <P>
+ * @param maxCount the maximum number of item that should be returned by this iterator
+ * @return the row iterator
+ */
+ public Iterator<DBRecordData> iterator(int maxCount)
+ {
+ if (iterator == null && rset != null)
+ {
+ if (getScrollable())
+ iterator = new DBReaderScrollableIterator(maxCount);
+ else
+ iterator = new DBReaderForwardIterator(maxCount);
+ }
+ return iterator;
+ }
+
+ /**
+ * <PRE>
+ * Returns an row iterator for this reader.
+ * There can only be one iterator at a time.
+ * </PRE>
+ * @return the row iterator
+ */
+ public final Iterator<DBRecordData> iterator()
+ {
+ return iterator(-1);
+ }
+
+ /**
+ * <PRE>
+ * initializes a DBRecord object with the values of the current row.
+ * At least all primary key columns of the target rowset must be provided by this reader.
+ * This function is equivalent to calling rowset.initRecord(rec, reader)
+ * set also {@link DBRowSet#initRecord(DBRecord, DBRecordData)});
+ * </PRE>
+ * @param rowset the rowset to which to attach
+ * @param rec the record which to initialize
+ */
+ public void initRecord(DBRowSet rowset, DBRecord rec)
+ {
+ if (rowset==null)
+ throw new InvalidArgumentException("rowset", rowset);
+ // init Record
+ rowset.initRecord(rec, this);
+ }
+
+ /**
+ * Returns the result of a query as a list of objects restricted
+ * to a maximum number of objects (unless maxCount is -1).
+ *
+ * @param c the collection to add the objects to
+ * @param t the class type of the objects in the list
+ * @param maxCount the maximum number of objects
+ *
+ * @return the list of T
+ */
+ @SuppressWarnings("unchecked")
+ public <C extends Collection<T>, T> C getBeanList(C c, Class<T> t, int maxCount)
+ {
+ // Check Recordset
+ if (rset == null)
+ { // Resultset not available
+ throw new ObjectNotValidException(this);
+ }
+ // Query List
+ try
+ {
+ // Check whether we can use a constructor
+ Class<?>[] paramTypes = new Class[getFieldCount()];
+ for (int i = 0; i < colList.length; i++)
+ paramTypes[i] = DBExpr.getValueClass(colList[i].getDataType());
+ // Find Constructor
+ Constructor<?> ctor = findMatchingAccessibleConstructor(t, paramTypes);
+ Object[] args = (ctor!=null) ? new Object[getFieldCount()] : null;
+
+ // Create a list of beans
+ while (moveNext() && maxCount != 0)
+ { // Create bean an init
+ if (ctor!=null)
+ { // Use Constructor
+ Class<?>[] ctorParamTypes = ctor.getParameterTypes();
+ for (int i = 0; i < getFieldCount(); i++)
+ args[i] = ObjectUtils.convert(ctorParamTypes[i], getValue(i));
+ T bean = (T)ctor.newInstance(args);
+ c.add(bean);
+ }
+ else
+ { // Use Property Setters
+ T bean = t.newInstance();
+ setBeanProperties(bean);
+ c.add(bean);
+ }
+ // Decrease count
+ if (maxCount > 0)
+ maxCount--;
+ }
+ // done
+ return c;
+ } catch (InvocationTargetException e) {
+ throw new BeanInstantiationException(t, e);
+ } catch (IllegalAccessException e) {
+ throw new BeanInstantiationException(t, e);
+ } catch (InstantiationException e) {
+ throw new BeanInstantiationException(t, e);
+ }
+ }
+
+ /**
+ * Returns the result of a query as a list of objects.
+ *
+ * @param t the class type of the objects in the list
+ * @param maxItems the maximum number of objects
+ *
+ * @return the list of T
+ */
+ public final <T> ArrayList<T> getBeanList(Class<T> t, int maxItems) {
+ return getBeanList(new ArrayList<T>(), t, maxItems);
+ }
+
+ /**
+ * Returns the result of a query as a list of objects.
+ *
+ * @param t the class type of the objects in the list
+ *
+ * @return the list of T
+ */
+ public final <T> ArrayList<T> getBeanList(Class<T> t) {
+ return getBeanList(t, -1);
+ }
+
+ /**
+ * Moves the cursor down one row from its current position.
+ *
+ * @return the number of column descriptions added to the Element
+ */
+ @Override
+ public int addColumnDesc(Element parent)
+ {
+ if (colList == null)
+ throw new ObjectNotValidException(this);
+ // Add Field Description
+ for (int i = 0; i < colList.length; i++)
+ colList[i].addXml(parent, 0);
+ // return count
+ return colList.length;
+ }
+
+ /**
+ * Adds all children to a parent.
+ *
+ * @param parent the parent element below which to search the child
+ * @return the number of row values added to the element
+ */
+ @Override
+ public int addRowValues(Element parent)
+ {
+ if (rset == null)
+ throw new ObjectNotValidException(this);
+ // Add all children
+ for (int i = 0; i < colList.length; i++)
+ { // Read all
+ String name = colList[i].getName();
+ String idColumnAttr = getXmlDictionary().getRowIdColumnAttribute();
+ if (name.equalsIgnoreCase("id"))
+ { // Add Attribute
+ parent.setAttribute(idColumnAttr, getString(i));
+ }
+ else
+ { // Add Element
+ String value = getString(i);
+ Element elem = XMLUtil.addElement(parent, name, value);
+ if (value == null)
+ elem.setAttribute("null", "yes"); // Null-Value
+ }
+ }
+ // return count
+ return colList.length;
+ }
+
+ /**
+ * Adds all children to a parent.
+ *
+ * @param parent the parent element below which to search the child
+ * @return the number of rows added to the element
+ */
+ public int addRows(Element parent)
+ {
+ int count = 0;
+ if (rset == null)
+ return 0;
+ // Add all rows
+ String rowElementName = getXmlDictionary().getRowElementName();
+ while (moveNext())
+ {
+ addRowValues(XMLUtil.addElement(parent, rowElementName));
+ count++;
+ }
+ return count;
+ }
+
+ /**
+ * returns the DBXmlDictionary that should used to generate XMLDocuments<BR>
+ * @return the DBXmlDictionary
+ */
+ protected DBXmlDictionary getXmlDictionary()
+ {
+ return DBXmlDictionary.getInstance();
+ }
+
+ /**
+ * Returns a XML document with the field description an values of this record.
+ *
+ * @return the new XML Document object
+ */
+ @Override
+ public Document getXmlDocument()
+ {
+ if (rset == null)
+ return null;
+ // Create Document
+ String rowsetElementName = getXmlDictionary().getRowSetElementName();
+ Element root = XMLUtil.createDocument(rowsetElementName);
+ // Add Field Description
+ addColumnDesc(root);
+ // Add row rset
+ addRows(root);
+ // return Document
+ return root.getOwnerDocument();
+ }
+
+ /** returns the number of the elements of the colList array */
+ @Override
+ public int getFieldCount()
+ {
+ return (colList != null) ? colList.length : 0;
+ }
+
+ /**
+ * Initialize the reader from an open JDBC-ResultSet
+ * @param db the database
+ * @param colList the query column expressions
+ * @param rset the JDBC-ResultSet
+ */
+ protected void init(DBDatabase db, DBColumnExpr[] colList, ResultSet rset)
+ {
+ this.db = db;
+ this.colList = colList;
+ this.rset = rset;
+ // add to tracking list (if enabled)
+ trackThisResultSet();
+ }
+
+ /**
+ * Access the column expression list
+ * @return the column expression list
+ */
+ protected final DBColumnExpr[] getColumnExprList()
+ {
+ return colList;
+ }
+
+ /**
+ * Access the JDBC-ResultSet
+ * @return the JDBC-ResultSet
+ */
+ protected final ResultSet getResultSet()
+ {
+ return rset;
+ }
+
+ /**
+ * finds the field Index of a given column expression
+ * Internally used as helper for getFieldIndex()
+ * @return the index value
+ */
+ protected int findFieldIndex(ColumnExpr column)
+ {
+ if (colList == null)
+ return -1;
+ // First chance: Try to find an exact match
+ for (int i = 0; i < colList.length; i++)
+ {
+ if (colList[i].equals(column))
+ return i;
+ }
+ // Second chance: Try Update Column
+ if (column instanceof DBColumn)
+ {
+ for (int i = 0; i < colList.length; i++)
+ {
+ DBColumn updColumn = colList[i].getUpdateColumn();
+ if (updColumn!=null && updColumn.equals(column))
+ return i;
+ // Query Expression?
+ if (updColumn instanceof DBQueryColumn)
+ { updColumn = ((DBQueryColumn)updColumn).getQueryExpression().getUpdateColumn();
+ if (updColumn!=null && updColumn.equals(column))
+ return i;
+ }
+ }
+ }
+ // not found!
+ return -1;
+ }
+
+ /**
+ * internal helper function to find parameterized subqueries
+ * @param cmd the command
+ * @return a list of parameter arrays, one for each subquery
+ */
+ protected List<Object[]> findSubQueryParams(DBCommand cmd)
+ {
+ List<Object[]> subQueryParams = null;
+ List<DBJoinExpr> joins = cmd.getJoins();
+ if (joins==null)
+ return null; // no joins
+ // check the joins
+ for (DBJoinExpr j : joins)
+ {
+ DBRowSet rsl = j.getLeftTable();
+ DBRowSet rsr = j.getRightTable();
+ if (rsl instanceof DBQuery)
+ { // the left join is a query
+ subQueryParams = addSubQueryParams((DBQuery)rsl, subQueryParams);
+ }
+ if (rsr instanceof DBQuery)
+ { // the right join is a query
+ subQueryParams = addSubQueryParams((DBQuery)rsr, subQueryParams);
+ }
+ }
+ return subQueryParams;
+ }
+
+ /**
+ * Adds any subquery params to the supplied list
+ * @param query the subquery
+ * @param list the current list of parameters
+ * @return the new list of parameters
+ */
+ private List<Object[]> addSubQueryParams(DBQuery query, List<Object[]> list)
+ {
+ DBCommandExpr sqcmd = query.getCommandExpr();
+ Object[] params = query.getCommandExpr().getParamValues();
+ if (params!=null && params.length>0)
+ { // add params
+ if (list== null)
+ list = new ArrayList<Object[]>();
+ list.add(params);
+ }
+ // recurse
+ if (sqcmd instanceof DBCommand)
+ { // check this command too
+ List<Object[]> sqlist = findSubQueryParams((DBCommand)sqcmd);
+ if (sqlist!=null && !sqlist.isEmpty())
+ { // make one list
+ if (list!= null)
+ list.addAll(sqlist);
+ else
+ list = sqlist;
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Support for finding code errors where a DBRecordSet is opened but not closed.
+ *
+ * @author bond
+ */
+ protected synchronized void trackThisResultSet()
+ {
+ // check if enabled
+ if (trackOpenResultSets==false)
+ return;
+ // add this to the vector of open resultsets on this thread
+ Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get();
+ if (openResultSets == null)
+ {
+ // Lazy initialization of the
+ openResultSets = new HashMap<DBReader, Exception>(2);
+ threadLocalOpenResultSets.set(openResultSets);
+ }
+
+ Exception stackException = openResultSets.get(this);
+ if (stackException != null)
+ {
+ log.error("DBRecordSet.addOpenResultSet called for an object which is already in the open list. This is the stack of the method opening the object which was not previously closed.", stackException);
+ // the code continues and overwrites the logged object with the new one
+ }
+ // get the current stack trace
+ openResultSets.put(this, new Exception());
+ }
+
+ /**
+ * Support for finding code errors where a DBRecordSet is opened but not closed.
+ *
+ * @author bond
+ */
+ protected synchronized void endTrackingThisResultSet()
+ {
+ // check if enabled
+ if (trackOpenResultSets==false)
+ return;
+ // remove
+ Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get();
+ if (openResultSets.containsKey(this) == false)
+ {
+ log.error("DBRecordSet.removeOpenResultSet called for an object which is not in the open list. Here is the current stack.", new Exception());
+ }
+ else
+ {
+ openResultSets.remove(this);
+ }
+ }
+
+ /*
+ private void writeObject(ObjectOutputStream stream) throws IOException {
+ if (rset != null) {
+ throw new NotSerializableException(DBReader.class.getName() + " (due to attached ResultSet)");
+ }
+ }
+ */
+
+ /**
+ * copied from org.apache.commons.beanutils.ConstructorUtils since it's private there
+ */
+ protected static Constructor<?> findMatchingAccessibleConstructor(Class<?> clazz, Class<?>[] parameterTypes)
+ {
+ // See if we can find the method directly
+ // probably faster if it works
+ // (I am not sure whether it's a good idea to run into Exceptions)
+ // try {
+ // Constructor ctor = clazz.getConstructor(parameterTypes);
+ // try {
+ // // see comment in org.apache.commons.beanutils.ConstructorUtils
+ // ctor.setAccessible(true);
+ // } catch (SecurityException se) { /* ignore */ }
+ // return ctor;
+ // } catch (NoSuchMethodException e) { /* SWALLOW */ }
+
+ // search through all constructors
+ int paramSize = parameterTypes.length;
+ Constructor<?>[] ctors = clazz.getConstructors();
+ for (int i = 0, size = ctors.length; i < size; i++)
+ { // compare parameters
+ Class<?>[] ctorParams = ctors[i].getParameterTypes();
+ int ctorParamSize = ctorParams.length;
+ if (ctorParamSize == paramSize)
+ { // Param Size matches
+ boolean match = true;
+ for (int n = 0; n < ctorParamSize; n++)
+ {
+ if (!ObjectUtils.isAssignmentCompatible(ctorParams[n], parameterTypes[n]))
+ {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ // get accessible version of method
+ Constructor<?> ctor = ConstructorUtils.getAccessibleConstructor(ctors[i]);
+ if (ctor != null) {
+ try {
+ ctor.setAccessible(true);
+ } catch (SecurityException se) { /* ignore */ }
+ return ctor;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Enables or disabled tracking of open ResultSets
+ * @param enable true to enable or false otherwise
+ * @return the previous state of the trackOpenResultSets
+ */
+ public static synchronized boolean enableOpenResultSetTracking(boolean enable)
+ {
+ boolean prev = trackOpenResultSets;
+ trackOpenResultSets = enable;
+ return prev;
+ }
+
+ /**
+ * <PRE>
+ * Call this if you want to check whether there are any unclosed resultsets
+ * It logs stack traces to help find piece of code
+ * where a DBReader was opened but not closed.
+ * </PRE>
+ */
+ public static synchronized void checkOpenResultSets()
+ {
+ // check if enabled
+ if (trackOpenResultSets==false)
+ throw new MiscellaneousErrorException("Open-ResultSet-Tracking has not been enabled. Use DBReader.enableOpenResultSetTracking() to enable or disable.");
+ // Check map
+ Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get();
+ if (openResultSets != null && openResultSets.isEmpty() == false)
+ {
+ // we have found a(n) open result set(s). Now show the stack trace(s)
+ Object keySet[] = openResultSets.keySet().toArray();
+ for (int i = 0; i < keySet.length; i++)
+ {
+ Exception stackException = openResultSets.get(keySet[i]);
+ log.error("A DBReader was not closed. Stack of opening code is ", stackException);
+ }
+ openResultSets.clear();
+ }
+ }
+
}
\ No newline at end of file
[2/2] empire-db git commit: EMPIREDB-266 Add special handling for
DBQueryColumns in DBReader getFieldIndex()
Posted by do...@apache.org.
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 <do...@apache.org>
Authored: Sun Jan 7 12:28:42 2018 +0100
Committer: Rainer Döbele <do...@apache.org>
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);
+ }
+}