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);
+    }
+}