You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pc...@apache.org on 2006/07/19 23:35:07 UTC

svn commit: r423615 [4/44] - in /incubator/openjpa/trunk: ./ openjpa-jdbc-5/ openjpa-jdbc-5/src/ openjpa-jdbc-5/src/main/ openjpa-jdbc-5/src/main/java/ openjpa-jdbc-5/src/main/java/org/ openjpa-jdbc-5/src/main/java/org/apache/ openjpa-jdbc-5/src/main/j...

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStore.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStore.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStore.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStore.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed 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.openjpa.jdbc.kernel;
+
+import java.sql.Connection;
+
+import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.ValueMapping;
+import org.apache.openjpa.jdbc.sql.DBDictionary;
+import org.apache.openjpa.jdbc.sql.Joins;
+import org.apache.openjpa.jdbc.sql.SQLFactory;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.kernel.StoreContext;
+import org.apache.openjpa.util.Id;
+
+/**
+ * Represents the JDBC store.
+ *
+ * @author Abe White
+ * @since 4.0
+ */
+public interface JDBCStore {
+
+    /**
+     * Current persistence context.
+     */
+    public StoreContext getContext();
+
+    /**
+     * Return the configuration for this runtime.
+     */
+    public JDBCConfiguration getConfiguration();
+
+    /**
+     * Return the dictionary in use.
+     */
+    public DBDictionary getDBDictionary();
+
+    /**
+     * Return the SQL factory for this runtime.
+     */
+    public SQLFactory getSQLFactory();
+
+    /**
+     * If the lock manager in use is a {@link JDBCLockManager}, return it.
+     */
+    public JDBCLockManager getLockManager();
+
+    /**
+     * Return a SQL connection to the database.
+     * The <code>close</code> method should always be called on the connection
+     * to free any resources it is using. When appropriate, the close
+     * method is implemented as a no-op.
+     */
+    public Connection getConnection();
+
+    /**
+     * Return the current default fetch configuration.
+     */
+    public JDBCFetchConfiguration getFetchConfiguration();
+
+    /**
+     * Create a new datastore identity object from the given id value and
+     * mapping.
+     */
+    public Id newDataStoreId(long id, ClassMapping mapping, boolean subs);
+
+    /**
+     * Find the object with the given oid. Convenience method on top of
+     * the store's persistence context.
+     *
+     * @param vm the mapping holding this oid, or null if not applicable
+     */
+    public Object find(Object oid, ValueMapping vm,
+        JDBCFetchState fetchState);
+
+    /**
+     * Makes sure all subclasses of the given type are loaded in the JVM.
+     * This is usually done automatically.
+     */
+    public void loadSubclasses(ClassMapping mapping);
+
+    /**
+     * Add WHERE conditions to the given select limiting the returned results
+     * to the given mapping type, possibly including subclasses.
+     *
+     * @return true if the mapping was joined down to its base class
+     * in order to add the conditions
+     */
+    public boolean addClassConditions(Select sel, ClassMapping mapping,
+        boolean subs, Joins joins);
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStore.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,1458 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed 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.openjpa.jdbc.kernel;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import javax.sql.DataSource;
+
+import org.apache.openjpa.event.OrphanedKeyAction;
+import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.Discriminator;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.ValueMapping;
+import org.apache.openjpa.jdbc.sql.DBDictionary;
+import org.apache.openjpa.jdbc.sql.JoinSyntaxes;
+import org.apache.openjpa.jdbc.sql.Joins;
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.SQLBuffer;
+import org.apache.openjpa.jdbc.sql.SQLExceptions;
+import org.apache.openjpa.jdbc.sql.SQLFactory;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.jdbc.sql.SelectExecutor;
+import org.apache.openjpa.jdbc.sql.Union;
+import org.apache.openjpa.kernel.FetchConfiguration;
+import org.apache.openjpa.kernel.FetchState;
+import org.apache.openjpa.kernel.LockManager;
+import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.kernel.PCState;
+import org.apache.openjpa.kernel.QueryLanguages;
+import org.apache.openjpa.kernel.Seq;
+import org.apache.openjpa.kernel.StoreContext;
+import org.apache.openjpa.kernel.StoreManager;
+import org.apache.openjpa.kernel.StoreQuery;
+import org.apache.openjpa.kernel.exps.ExpressionParser;
+import org.apache.openjpa.lib.jdbc.DelegatingConnection;
+import org.apache.openjpa.lib.jdbc.DelegatingPreparedStatement;
+import org.apache.openjpa.lib.jdbc.DelegatingStatement;
+import org.apache.openjpa.lib.rop.MergedResultObjectProvider;
+import org.apache.openjpa.lib.rop.ResultObjectProvider;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.ClassMetaData;
+import org.apache.openjpa.meta.FieldMetaData;
+import org.apache.openjpa.meta.JavaTypes;
+import org.apache.openjpa.meta.ValueStrategies;
+import org.apache.openjpa.util.ApplicationIds;
+import org.apache.openjpa.util.Id;
+import org.apache.openjpa.util.ImplHelper;
+import org.apache.openjpa.util.InvalidStateException;
+import org.apache.openjpa.util.OpenJPAId;
+import org.apache.openjpa.util.StoreException;
+import org.apache.openjpa.util.UserException;
+
+/**
+ * StoreManager plugin that uses JDBC to store persistent data in a
+ * relational data store.
+ *
+ * @author Abe White
+ * @nojavadoc
+ */
+public class JDBCStoreManager
+    implements StoreManager, JDBCStore {
+
+    private static final Localizer _loc = Localizer.forPackage
+        (JDBCStoreManager.class);
+
+    private StoreContext _ctx = null;
+    private JDBCConfiguration _conf = null;
+    private DBDictionary _dict = null;
+    private SQLFactory _sql = null;
+    private JDBCLockManager _lm = null;
+    private DataSource _ds = null;
+    private RefCountConnection _conn = null;
+    private boolean _active = false;
+
+    // track the pending statements so we can cancel them
+    private Set _stmnts = Collections.synchronizedSet(new HashSet());
+
+    public StoreContext getContext() {
+        return _ctx;
+    }
+
+    public void setContext(StoreContext ctx) {
+        _ctx = ctx;
+        _conf = (JDBCConfiguration) ctx.getConfiguration();
+        _dict = _conf.getDBDictionaryInstance();
+        _sql = _conf.getSQLFactoryInstance();
+
+        LockManager lm = ctx.getLockManager();
+        if (lm instanceof JDBCLockManager)
+            _lm = (JDBCLockManager) lm;
+
+        if (!ctx.isManaged() && _conf.isConnectionFactoryModeManaged())
+            _ds = _conf.getDataSource2(ctx);
+        else
+            _ds = _conf.getDataSource(ctx);
+
+        if (_conf.getUpdateManagerInstance().orderDirty())
+            ctx.setOrderDirtyObjects(true);
+    }
+
+    public JDBCConfiguration getConfiguration() {
+        return _conf;
+    }
+
+    public DBDictionary getDBDictionary() {
+        return _dict;
+    }
+
+    public SQLFactory getSQLFactory() {
+        return _sql;
+    }
+
+    public JDBCLockManager getLockManager() {
+        return _lm;
+    }
+
+    public JDBCFetchConfiguration getFetchConfiguration() {
+        return (JDBCFetchConfiguration) _ctx.getFetchConfiguration();
+    }
+
+    private JDBCFetchConfiguration getFetchConfiguration
+        (JDBCFetchState fetchState) {
+        return (fetchState == null) ? getFetchConfiguration()
+            : fetchState.getJDBCFetchConfiguration();
+    }
+
+    public void beginOptimistic() {
+    }
+
+    public void rollbackOptimistic() {
+    }
+
+    public void begin() {
+        _active = true;
+        try {
+            if ((!_ctx.isManaged() || !_conf.isConnectionFactoryModeManaged())
+                && _conn.getAutoCommit())
+                _conn.setAutoCommit(false);
+        } catch (SQLException se) {
+            _active = false;
+            throw SQLExceptions.getStore(se, _dict);
+        }
+    }
+
+    public void commit() {
+        try {
+            if (!_ctx.isManaged() || !_conf.isConnectionFactoryModeManaged())
+                _conn.commit();
+        } catch (SQLException se) {
+            try {
+                _conn.rollback();
+            } catch (SQLException se2) {
+            }
+            throw SQLExceptions.getStore(se, _dict);
+        } finally {
+            _active = false;
+        }
+    }
+
+    public void rollback() {
+        // already rolled back ourselves?
+        if (!_active)
+            return;
+
+        try {
+            if (_conn != null && (!_ctx.isManaged()
+                || !_conf.isConnectionFactoryModeManaged()))
+                _conn.rollback();
+        } catch (SQLException se) {
+            throw SQLExceptions.getStore(se, _dict);
+        } finally {
+            _active = false;
+        }
+    }
+
+    public void retainConnection() {
+        connect(false);
+        _conn.setRetain(true);
+    }
+
+    public void releaseConnection() {
+        if (_conn != null)
+            _conn.setRetain(false);
+    }
+
+    public Object getClientConnection() {
+        return new ClientConnection(getConnection());
+    }
+
+    public Connection getConnection() {
+        connect(true);
+        return _conn;
+    }
+
+    public boolean exists(OpenJPAStateManager sm, Object context) {
+        // add where conditions on base class to avoid joins if subclass
+        // doesn't use oid as identifier
+        ClassMapping mapping = (ClassMapping) sm.getMetaData();
+        return exists(mapping, sm.getObjectId(), context);
+    }
+
+    private boolean exists(ClassMapping mapping, Object oid, Object context) {
+        // add where conditions on base class to avoid joins if subclass
+        // doesn't use oid as identifier
+        Select sel = _sql.newSelect();
+        while (mapping.getJoinablePCSuperclassMapping() != null)
+            mapping = mapping.getJoinablePCSuperclassMapping();
+
+        sel.wherePrimaryKey(oid, mapping, this);
+        try {
+            return sel.getCount(this) != 0;
+        } catch (SQLException se) {
+            throw SQLExceptions.getStore(se, _dict);
+        }
+    }
+
+    public boolean syncVersion(OpenJPAStateManager sm, Object context) {
+        ClassMapping mapping = (ClassMapping) sm.getMetaData();
+        try {
+            return mapping.getVersion().checkVersion(sm, this, true);
+        } catch (SQLException se) {
+            throw SQLExceptions.getStore(se, _dict);
+        }
+    }
+
+    public int compareVersion(OpenJPAStateManager state, Object v1, Object v2) {
+        ClassMapping mapping = (ClassMapping) state.getMetaData();
+        return mapping.getVersion().compareVersion(v1, v2);
+    }
+
+    public boolean initialize(OpenJPAStateManager sm, PCState state,
+        FetchState fetchState, Object context) {
+        ConnectionInfo info = (ConnectionInfo) context;
+        try {
+            return initializeState(sm, state, (JDBCFetchState) fetchState,
+                info);
+        } catch (ClassNotFoundException cnfe) {
+            throw new UserException(cnfe);
+        } catch (SQLException se) {
+            throw SQLExceptions.getStore(se, _dict);
+        }
+    }
+
+    /**
+     * Initialize a newly-loaded instance.
+     */
+    private boolean initializeState(OpenJPAStateManager sm, PCState state,
+        JDBCFetchState fetchState, ConnectionInfo info)
+        throws ClassNotFoundException, SQLException {
+        Object oid = sm.getObjectId();
+        ClassMapping mapping = (ClassMapping) sm.getMetaData();
+        JDBCFetchConfiguration jfetch = getFetchConfiguration(fetchState);
+        Result res = null;
+        if (info != null && info.result != null) {
+            res = info.result;
+            info.sm = sm;
+            if (info.mapping == null)
+                info.mapping = mapping;
+            mapping = info.mapping;
+        } else
+        if (oid instanceof OpenJPAId && !((OpenJPAId) oid).hasSubclasses()) {
+            Boolean custom = customLoad(sm, mapping, state, jfetch);
+            if (custom != null)
+                return custom.booleanValue();
+            res = getInitializeStateResult(sm, mapping, fetchState,
+                Select.SUBS_EXACT);
+            if (res == null && !selectPrimaryKey(sm, mapping, jfetch))
+                return false;
+            if (res != null && !res.next())
+                return false;
+        } else {
+            ClassMapping[] mappings = mapping.
+                getIndependentAssignableMappings();
+            if (mappings.length == 1) {
+                mapping = mappings[0];
+                Boolean custom = customLoad(sm, mapping, state, jfetch);
+                if (custom != null)
+                    return custom.booleanValue();
+                res = getInitializeStateResult(sm, mapping, fetchState,
+                    Select.SUBS_ANY_JOINABLE);
+                if (res == null && !selectPrimaryKey(sm, mapping, jfetch))
+                    return false;
+            } else
+                res = getInitializeStateUnionResult(sm, mapping, mappings,
+                    fetchState);
+            if (res != null && !res.next())
+                return false;
+        }
+
+        try {
+            // figure out what type of object this is; the state manager
+            // only guarantees to provide a base class
+            Class type;
+            if (res == null)
+                type = mapping.getDescribedType();
+            else {
+                if (res.getBaseMapping() != null)
+                    mapping = res.getBaseMapping();
+                res.startDataRequest(mapping.getDiscriminator());
+                try {
+                    type = mapping.getDiscriminator().getClass(this, mapping,
+                        res);
+                } finally {
+                    res.endDataRequest();
+                }
+            }
+
+            // initialize the state manager; this may change the mapping
+            // and the object id instance if the type as determined
+            // from the indicator is a subclass of expected type
+            sm.initialize(type, state);
+
+            // load the selected mappings into the given state manager
+            if (res != null) {
+                // re-get the mapping in case the instance was a subclass
+                mapping = (ClassMapping) sm.getMetaData();
+
+                load(mapping, sm, fetchState, res);
+                mapping.getVersion().afterLoad(sm, this);
+            }
+            return true;
+        } finally {
+            if (res != null && (info == null || res != info.result))
+                res.close();
+        }
+    }
+
+    /**
+     * Allow the mapping to custom load data. Return null if the mapping
+     * does not use custom loading.
+     */
+    private Boolean customLoad(OpenJPAStateManager sm, ClassMapping mapping,
+        PCState state, JDBCFetchConfiguration jfetch)
+        throws ClassNotFoundException, SQLException {
+        // check to see if the mapping takes care of initialization
+        if (!mapping.customLoad(sm, this, state, jfetch))
+            return null;
+        if (sm.getManagedInstance() != null) {
+            mapping.getVersion().afterLoad(sm, this);
+            return Boolean.TRUE;
+        }
+        return Boolean.FALSE;
+    }
+
+    /**
+     * Select the data for the given instance and return the result. Return
+     * null if there is no data in the current fetch groups to select.
+     */
+    private Result getInitializeStateResult(OpenJPAStateManager sm,
+        ClassMapping mapping, JDBCFetchState fetchState, int subs)
+        throws SQLException {
+        Select sel = _sql.newSelect();
+        if (!select(sel, mapping, subs, sm, null, fetchState,
+            JDBCFetchConfiguration.EAGER_JOIN, true))
+            return null;
+
+        sel.wherePrimaryKey(sm.getObjectId(), mapping, this);
+        return sel.execute(this, getFetchConfiguration(fetchState));
+    }
+
+    /**
+     * Select a union of the data for the given instance from possible concrete
+     * mappings and return the result.
+     */
+    private Result getInitializeStateUnionResult(final OpenJPAStateManager sm,
+        ClassMapping mapping, final ClassMapping[] mappings,
+        final JDBCFetchState fetchState)
+        throws SQLException {
+        final JDBCStoreManager store = this;
+        JDBCFetchConfiguration jfetch = getFetchConfiguration(fetchState);
+        final int eager = Math.min(jfetch.getEagerFetchMode(),
+            JDBCFetchConfiguration.EAGER_JOIN);
+
+        Union union = _sql.newUnion(mappings.length);
+        union.setSingleResult(true);
+        if (jfetch.getSubclassFetchMode(mapping) != jfetch.EAGER_JOIN)
+            union.abortUnion();
+        union.select(new Union.Selector() {
+            public void select(Select sel, int i) {
+                sel.select(mappings[i], Select.SUBS_ANY_JOINABLE, store,
+                    fetchState, eager);
+                sel.wherePrimaryKey(sm.getObjectId(), mappings[i], store);
+            }
+        });
+        return union.execute(this, jfetch);
+    }
+
+    /**
+     * Select primary key data to make sure the given instance exists, locking
+     * if needed.
+     */
+    private boolean selectPrimaryKey(OpenJPAStateManager sm,
+        ClassMapping mapping,
+        JDBCFetchConfiguration jfetch)
+        throws SQLException {
+        // select pks from base class record to ensure it exists and lock
+        // it if needed
+        ClassMapping base = mapping;
+        while (base.getJoinablePCSuperclassMapping() != null)
+            base = base.getJoinablePCSuperclassMapping();
+
+        Select sel = _sql.newSelect();
+        sel.select(base.getPrimaryKeyColumns());
+        sel.wherePrimaryKey(sm.getObjectId(), base, this);
+        Result exists = sel.execute(this, jfetch);
+        try {
+            if (!exists.next())
+                return false;
+
+            // record locked?
+            if (_active && _lm != null && exists.isLocking())
+                _lm.loadedForUpdate(sm);
+            return true;
+        } finally {
+            exists.close();
+        }
+    }
+
+    public boolean load(OpenJPAStateManager sm, BitSet fields,
+        FetchState fetchState, int lockLevel, Object context) {
+        JDBCFetchState jfetchState = (fetchState == null)
+            ? (JDBCFetchState) getFetchConfiguration().newFetchState()
+            : (JDBCFetchState) fetchState;
+
+        JDBCFetchConfiguration jfetch = getFetchConfiguration(jfetchState);
+
+        // get a connection, or reuse current one
+        ConnectionInfo info = (ConnectionInfo) context;
+        Result res = null;
+        if (info != null) {
+            // if initialize() fails to load required fields, then this method
+            // is called; make sure not to try to use the given result if it's
+            // the same one we just failed to completely initialize() with
+            if (info.sm != sm)
+                res = info.result;
+            info.sm = null;
+        }
+        try {
+            // if there's an existing result, load all we can from it
+            ClassMapping mapping = (ClassMapping) sm.getMetaData();
+            if (res != null) {
+                load(mapping, sm, jfetchState, res);
+                removeLoadedFields(sm, fields);
+            }
+
+            // if the instance is hollow and there's a customized
+            // get by id method, use it
+            if (sm.getLoaded().length() == 0)
+                if (mapping.customLoad(sm, this, null, jfetch))
+                    removeLoadedFields(sm, fields);
+
+            //### select is kind of a big object, and in some cases we don't
+            //### use it... would it be worth it to have a small shell select
+            //### object that only creates a real select when actually used?
+
+            Select sel = _sql.newSelect();
+            if (select(sel, mapping, sel.SUBS_EXACT, sm, fields, jfetchState,
+                jfetch.EAGER_JOIN, true)) {
+                sel.wherePrimaryKey(sm.getObjectId(), mapping, this);
+                res = sel.execute(this, jfetch, lockLevel);
+                try {
+                    if (!res.next())
+                        return false;
+                    load(mapping, sm, jfetchState, res);
+                } finally {
+                    res.close();
+                }
+            }
+
+            // now allow the fields to load themselves individually too
+            for (int i = 0, len = fields.length(); i < len; i++)
+                if (fields.get(i) && !sm.getLoaded().get(i))
+                    mapping.getFieldMapping(i).load(sm, this, jfetchState);
+
+            mapping.getVersion().afterLoad(sm, this);
+            return true;
+        } catch (ClassNotFoundException cnfe) {
+            throw new StoreException(cnfe);
+        } catch (SQLException se) {
+            throw SQLExceptions.getStore(se, _dict);
+        }
+    }
+
+    /**
+     * Return a list formed by removing all loaded fields from the given one.
+     */
+    private void removeLoadedFields(OpenJPAStateManager sm, BitSet fields) {
+        for (int i = 0, len = fields.length(); i < len; i++)
+            if (fields.get(i) && sm.getLoaded().get(i))
+                fields.clear(i);
+    }
+
+    public Collection loadAll(Collection sms, PCState state, int load,
+        FetchState fetchState, Object context) {
+        return ImplHelper.loadAll(sms, this, state, load, fetchState, context);
+    }
+
+    public void beforeStateChange(OpenJPAStateManager sm, PCState fromState,
+        PCState toState) {
+    }
+
+    public Collection flush(Collection sms) {
+        return _conf.getUpdateManagerInstance().flush(sms, this);
+    }
+
+    public boolean cancelAll() {
+        // note that this method does not lock the context, since
+        // we want to allow a different thread to be able to cancel the
+        // outstanding statement on a different context
+
+        Collection stmnts;
+        synchronized (_stmnts) {
+            if (_stmnts.isEmpty())
+                return false;
+            stmnts = new ArrayList(_stmnts);
+        }
+
+        try {
+            for (Iterator itr = stmnts.iterator(); itr.hasNext();)
+                ((Statement) itr.next()).cancel();
+            return true;
+        } catch (SQLException se) {
+            throw SQLExceptions.getStore(se, _dict);
+        }
+    }
+
+    public boolean assignObjectId(OpenJPAStateManager sm, boolean preFlush) {
+        ClassMetaData meta = sm.getMetaData();
+        if (meta.getIdentityType() == ClassMetaData.ID_APPLICATION)
+            return ApplicationIds.assign(sm, this, preFlush);
+
+        // datastore identity
+        Object val = ImplHelper.generateIdentityValue(_ctx, meta,
+            JavaTypes.LONG);
+        if (val == null && meta.getIdentityStrategy() != ValueStrategies.NATIVE)
+            return false;
+        if (val == null)
+            val = getDataStoreIdSequence(meta).next(_ctx, meta);
+        sm.setObjectId(newDataStoreId(val, meta));
+        return true;
+    }
+
+    public boolean assignField(OpenJPAStateManager sm, int field,
+        boolean preFlush) {
+        FieldMetaData fmd = sm.getMetaData().getField(field);
+        Object val = ImplHelper.generateFieldValue(_ctx, fmd);
+        if (val == null)
+            return false;
+        sm.store(field, val);
+        return true;
+    }
+
+    public Class getManagedType(Object oid) {
+        if (oid instanceof Id)
+            return ((Id) oid).getType();
+        return null;
+    }
+
+    public Class getDataStoreIdType(ClassMetaData meta) {
+        return Id.class;
+    }
+
+    public Object copyDataStoreId(Object oid, ClassMetaData meta) {
+        Id id = (Id) oid;
+        return new Id(meta.getDescribedType(), id.getId(),
+            id.hasSubclasses());
+    }
+
+    public Object newDataStoreId(Object val, ClassMetaData meta) {
+        return Id.newInstance(meta.getDescribedType(), val);
+    }
+
+    public Id newDataStoreId(long id, ClassMapping mapping, boolean subs) {
+        return new Id(mapping.getDescribedType(), id, subs);
+    }
+
+    public ResultObjectProvider executeExtent(ClassMetaData meta,
+        final boolean subclasses, FetchConfiguration fetch) {
+        ClassMapping mapping = (ClassMapping) meta;
+        final ClassMapping[] mappings;
+        if (subclasses)
+            mappings = mapping.getIndependentAssignableMappings();
+        else
+            mappings = new ClassMapping[]{ mapping };
+
+        ResultObjectProvider[] rops = null;
+        final JDBCFetchConfiguration jfetch = (JDBCFetchConfiguration) fetch;
+        final JDBCFetchState jfetchState = (JDBCFetchState)
+            jfetch.newFetchState();
+        if (jfetch.getSubclassFetchMode(mapping) != jfetch.EAGER_JOIN)
+            rops = new ResultObjectProvider[mappings.length];
+
+        try {
+            // check for custom loads
+            ResultObjectProvider rop;
+            for (int i = 0; i < mappings.length; i++) {
+                rop = mappings[i].customLoad(this, subclasses, jfetch, 0,
+                    Long.MAX_VALUE);
+                if (rop != null) {
+                    if (rops == null)
+                        rops = new ResultObjectProvider[mappings.length];
+                    rops[i] = rop;
+                }
+            }
+
+            // if we're selecting independent mappings separately or have
+            // custom loads, do individual selects for each class
+            rop = null;
+            if (rops != null) {
+                for (int i = 0; i < mappings.length; i++) {
+                    if (rops[i] != null)
+                        continue;
+
+                    Select sel = _sql.newSelect();
+                    sel.setLRS(true);
+                    BitSet paged = selectExtent(sel, mappings[i], jfetchState,
+                        subclasses);
+                    if (paged == null)
+                        rops[i] = new InstanceResultObjectProvider(sel,
+                            mappings[i], this, jfetchState);
+                    else
+                        rops[i] = new PagingResultObjectProvider(sel,
+                            mappings[i], this, jfetchState, paged,
+                            Long.MAX_VALUE);
+                }
+                if (rops.length == 1)
+                    return rops[0];
+                return new MergedResultObjectProvider(rops);
+            }
+
+            // perform a union on all independent classes
+            Union union = _sql.newUnion(mappings.length);
+            union.setLRS(true);
+            final BitSet[] paged = new BitSet[mappings.length];
+            union.select(new Union.Selector() {
+                public void select(Select sel, int idx) {
+                    paged[idx] = selectExtent(sel, mappings[idx], jfetchState,
+                        subclasses);
+                }
+            });
+
+            // using paging rop if any union element has paged fields
+            for (int i = 0; i < paged.length; i++) {
+                if (paged[i] != null)
+                    return new PagingResultObjectProvider(union, mappings,
+                        JDBCStoreManager.this, jfetchState, paged,
+                        Long.MAX_VALUE);
+            }
+            return new InstanceResultObjectProvider(union, mappings[0],
+                this, jfetchState);
+        } catch (SQLException se) {
+            throw SQLExceptions.getStore(se, _dict);
+        }
+    }
+
+    /**
+     * Select the given mapping for use in an extent, returning paged fields.
+     */
+    private BitSet selectExtent(Select sel, ClassMapping mapping,
+        JDBCFetchState jfetchState, boolean subclasses) {
+        int subs = (subclasses) ? Select.SUBS_JOINABLE : Select.SUBS_NONE;
+        // decide between paging and standard iteration
+        BitSet paged = PagingResultObjectProvider.getPagedFields(sel,
+            mapping, this, jfetchState, JDBCFetchConfiguration.EAGER_PARALLEL,
+            Long.MAX_VALUE);
+        if (paged == null)
+            sel.selectIdentifier(mapping, subs, this, jfetchState,
+                JDBCFetchConfiguration.EAGER_PARALLEL);
+        else
+            sel.selectIdentifier(mapping, subs, this, jfetchState,
+                JDBCFetchConfiguration.EAGER_JOIN);
+        return paged;
+    }
+
+    public StoreQuery newQuery(String language) {
+        ExpressionParser ep = QueryLanguages.parserForLanguage(language);
+        if (ep != null)
+            return new JDBCStoreQuery(this, ep);
+        if (QueryLanguages.LANG_SQL.equals(language))
+            return new SQLStoreQuery(this);
+        return null;
+    }
+
+    public FetchConfiguration newFetchConfiguration() {
+        return new JDBCFetchConfigurationImpl();
+    }
+
+    public Seq getDataStoreIdSequence(ClassMetaData meta) {
+        if (meta.getIdentityStrategy() == ValueStrategies.NATIVE
+            || meta.getIdentityStrategy() == ValueStrategies.NONE)
+            return _conf.getSequenceInstance();
+        return null;
+    }
+
+    public Seq getValueSequence(FieldMetaData fmd) {
+        return null;
+    }
+
+    public void close() {
+        if (_conn != null)
+            _conn.free();
+    }
+
+    /////////////
+    // Utilities
+    /////////////
+
+    /**
+     * Connect to the db.
+     */
+    private void connect(boolean ref) {
+        _ctx.lock();
+        try {
+            // connect if the connection is currently null, or if
+            // the connection has been closed out from under us
+            if (_conn == null)
+                _conn = connectInternal();
+            if (ref)
+                _conn.ref();
+        } catch (SQLException se) {
+            throw SQLExceptions.getStore(se, _dict);
+        } finally {
+            _ctx.unlock();
+        }
+    }
+
+    /**
+     * Connect to the database. This method is separated out so that it
+     * can be profiled.
+     */
+    private RefCountConnection connectInternal()
+        throws SQLException {
+        return new RefCountConnection(_ds.getConnection());
+    }
+
+    /**
+     * Find the object with the given oid.
+     */
+    public Object find(Object oid, ValueMapping vm,
+        JDBCFetchState fetchState) {
+        if (oid == null)
+            return null;
+        Object pc = _ctx.find(oid, fetchState, null, null, 0);
+        if (pc == null && vm != null) {
+            OrphanedKeyAction action = _conf.getOrphanedKeyActionInstance();
+            pc = action.orphan(oid, null, vm);
+        }
+        return pc;
+    }
+
+    /**
+     * Load the object in the current row of the given result.
+     */
+    public Object load(ClassMapping mapping, JDBCFetchState fetchState,
+        BitSet exclude, Result result)
+        throws SQLException {
+        if (!mapping.isMapped())
+            throw new InvalidStateException(_loc.get("virtual-mapping",
+                mapping));
+
+        // get the object id for the row; base class selects pk columns
+        ClassMapping base = mapping;
+        while (base.getJoinablePCSuperclassMapping() != null)
+            base = base.getJoinablePCSuperclassMapping();
+        Object oid = base.getObjectId(this, result, null, true, null);
+        if (oid == null)
+            return null;
+
+        ConnectionInfo info = new ConnectionInfo();
+        info.result = result;
+        info.mapping = mapping;
+        return _ctx.find(oid, fetchState, exclude, info, 0);
+    }
+
+    /**
+     * Load the given state manager with data from the result set. Only
+     * mappings originally selected will be loaded.
+     */
+    private void load(ClassMapping mapping, OpenJPAStateManager sm,
+        JDBCFetchState fetchState, Result res)
+        throws SQLException {
+        FieldMapping eagerToMany = load(mapping, sm, fetchState, res, null);
+        if (eagerToMany != null)
+            eagerToMany.loadEagerJoin(sm, this, fetchState, res);
+        if (_active && _lm != null && res.isLocking())
+            _lm.loadedForUpdate(sm);
+    }
+
+    /**
+     * Load the fields of the given mapping. Return any to-many eager field
+     * without loading it.
+     */
+    private FieldMapping load(ClassMapping mapping, OpenJPAStateManager sm,
+        JDBCFetchState fetchState, Result res, FieldMapping eagerToMany)
+        throws SQLException {
+        JDBCFetchConfiguration fetch = getFetchConfiguration(fetchState);
+        if (mapping.customLoad(sm, this, fetch, res))
+            return eagerToMany;
+
+        // load superclass data; base class loads version
+        ClassMapping parent = mapping.getJoinablePCSuperclassMapping();
+        if (parent != null)
+            eagerToMany = load(parent, sm, fetchState, res, eagerToMany);
+        else if (sm.getVersion() == null)
+            mapping.getVersion().load(sm, this, res);
+
+        // load unloaded fields
+        FieldMapping[] fms = mapping.getDefinedFieldMappings();
+        Object eres, processed;
+        for (int i = 0; i < fms.length; i++) {
+            if (fms[i].isPrimaryKey()
+                || sm.getLoaded().get(fms[i].getIndex()))
+                continue;
+
+            // check for eager result, and if not present do standard load
+            eres = res.getEager(fms[i]);
+            res.startDataRequest(fms[i]);
+            try {
+                if (eres == res) {
+                    if (eagerToMany == null && fms[i].isEagerSelectToMany())
+                        eagerToMany = fms[i];
+                    else
+                        fms[i].loadEagerJoin(sm, this, fetchState, res);
+                } else if (eres != null) {
+                    processed = fms[i].loadEagerParallel(sm, this, fetchState,
+                        eres);
+                    if (processed != eres)
+                        res.putEager(fms[i], processed);
+                } else
+                    fms[i].load(sm, this, fetchState, res);
+            } finally {
+                res.endDataRequest();
+            }
+        }
+        return eagerToMany;
+    }
+
+    /**
+     * For implementation use only.
+     * Return a select for the proper mappings. Return null if no select is
+     * needed. The method is designed to be complementary to the load methods.
+     *
+     * @param sel select to build on
+     * @param mapping the mapping for the base type to select for
+     * @param subs whether the select might include subclasses of the
+     * given mapping
+     * @param sm state manager if an instance is being loaded or
+     * initialized, else null
+     * @param fields if a state manager is being loaded, the set of
+     * fields that must be loaded in order, else null
+     * @param fetchState the fetch configuration; used if no specific fields
+     * must be loaded, and used when selecting relations
+     * @param eager eager fetch mode to use
+     * @param ident whether to select primary key columns as distinct
+     * identifiers
+     * @return true if the select is required, false otherwise
+     */
+    public boolean select(Select sel, ClassMapping mapping, int subs,
+        OpenJPAStateManager sm, BitSet fields, JDBCFetchState fetchState,
+        int eager, boolean ident) {
+        // add class conditions so that they're cloned for any batched selects
+        boolean joinedSupers = false;
+        if ((sm == null || sm.getPCState() == PCState.TRANSIENT)
+            && (subs == sel.SUBS_JOINABLE || subs == sel.SUBS_NONE))
+            joinedSupers = addClassConditions(sel, mapping,
+                subs == sel.SUBS_JOINABLE, null);
+
+        // create all our eager selects so that those fields are reserved
+        // and cannot be reused during the actual eager select process,
+        // preventing infinite recursion
+        JDBCFetchConfiguration fetch = getFetchConfiguration(fetchState);
+        eager = Math.min(eager, fetch.getEagerFetchMode());
+        FieldMapping eagerToMany = createEagerSelects(sel, mapping, sm,
+            fields, fetchState, eager);
+
+        // select all base class mappings; do this after batching so that
+        // the joins needed by these selects don't get in the WHERE clause
+        // of the batched selects
+        int seld = selectBaseMappings(sel, mapping, mapping, sm, fields,
+            fetchState, eager, eagerToMany, ident, joinedSupers);
+
+        // select eager to-many relations last because during load they
+        // advance the result set and could exhaust it, so no other mappings
+        // can load afterwords
+        if (eagerToMany != null)
+            eagerToMany.selectEagerJoin(sel, sm, this, fetchState, eager);
+
+        // optionally select subclass mappings
+        if (subs == sel.SUBS_JOINABLE || subs == sel.SUBS_ANY_JOINABLE)
+            selectSubclassMappings(sel, mapping, sm, fetchState);
+        if (sm != null)
+            sel.setDistinct(false);
+        return seld > 0;
+    }
+
+    /**
+     * Mark the fields of this mapping as reserved so that eager fetches can't
+     * get into infinite recursive situations.
+     */
+    private FieldMapping createEagerSelects(Select sel, ClassMapping mapping,
+        OpenJPAStateManager sm, BitSet fields, JDBCFetchState fetchState,
+        int eager) {
+        if (mapping == null || eager == JDBCFetchConfiguration.EAGER_NONE)
+            return null;
+
+        JDBCFetchConfiguration fetch = getFetchConfiguration(fetchState);
+        FieldMapping eagerToMany = createEagerSelects(sel, mapping.
+            getJoinablePCSuperclassMapping(), sm, fields, fetchState, eager);
+
+        FieldMapping[] fms = mapping.getDefinedFieldMappings();
+        boolean inEagerJoin = sel.hasEagerJoin(false);
+        int sels;
+        int jtype;
+        int mode;
+        for (int i = 0; i < fms.length; i++) {
+            if (!requiresSelect(fms[i], sm, fields, fetchState))
+                continue;
+            mode = fms[i].getEagerFetchMode();
+            if (mode == fetch.EAGER_NONE)
+                continue;
+
+            // try to select with join first
+            jtype = (fms[i].getNullValue() == fms[i].NULL_EXCEPTION)
+                ? sel.EAGER_INNER : sel.EAGER_OUTER;
+            if (mode != fetch.EAGER_PARALLEL
+                && !fms[i].isEagerSelectToMany()
+                && fms[i].supportsSelect(sel, jtype, sm, this, fetch) > 0
+                && sel.eagerClone(fms[i], jtype, false, 1) != null)
+                continue;
+
+            boolean hasJoin = fetch.hasJoin(fms[i].getFullName());
+
+            // if the field declares a preferred select mode of join or does not
+            // have a preferred mode and we're doing a by-id lookup, try
+            // to use a to-many join also.  currently we limit eager
+            // outer joins to non-LRS, non-ranged selects that don't already
+            // have an eager to-many join
+            if ((hasJoin || mode == fetch.EAGER_JOIN
+                || (mode == fetch.DEFAULT && sm != null))
+                && fms[i].isEagerSelectToMany()
+                && !inEagerJoin && !sel.hasEagerJoin(true)
+                && (!sel.getAutoDistinct() || (!sel.isLRS()
+                && sel.getStartIndex() == 0
+                && sel.getEndIndex() == Long.MAX_VALUE))
+                && fms[i].supportsSelect(sel, jtype, sm, this, fetch) > 0) {
+                if (sel.eagerClone(fms[i], jtype, true, 1) != null)
+                    eagerToMany = fms[i];
+                else
+                    continue;
+            }
+
+            // finally, try parallel
+            if (eager == fetch.EAGER_PARALLEL && (sels = fms[i].
+                supportsSelect(sel, sel.EAGER_PARALLEL, sm, this, fetch)) != 0)
+                sel.eagerClone(fms[i], Select.EAGER_PARALLEL,
+                    fms[i].isEagerSelectToMany(), sels);
+        }
+        return eagerToMany;
+    }
+
+    /**
+     * Determine if the given field needs to be selected.
+     */
+    private static boolean requiresSelect(FieldMapping fm,
+        OpenJPAStateManager sm,
+        BitSet fields, JDBCFetchState fetchState) {
+        if (fields != null)
+            return fields.get(fm.getIndex());
+        if (sm != null && sm.getPCState() != PCState.TRANSIENT
+            && sm.getLoaded().get(fm.getIndex()))
+            return false;
+        return fetchState.requiresSelect(fm, true);
+    }
+
+    /**
+     * Select the field mappings of the given class and all its superclasses.
+     *
+     * @param sel the select to use
+     * @param mapping the most-derived type to select for
+     * @param orig the original mapping type selected
+     * @param sm the instance being selected for, or null if none
+     * @param fields the fields to load
+     * @param fetch fetch configuration to use for loading relations
+     * @param eager the eager fetch mode to use
+     * @param joined whether the class has already been joined down to
+     * its base class
+     * @return &gt; 0 if the select is required, 0 if data was
+     * selected but is not required, and &lt; 0 if nothing was selected
+     */
+    private int selectBaseMappings(Select sel, ClassMapping mapping,
+        ClassMapping orig, OpenJPAStateManager sm, BitSet fields,
+        JDBCFetchState fetchState, int eager, FieldMapping eagerToMany,
+        boolean ident, boolean joined) {
+        JDBCFetchConfiguration fetch = getFetchConfiguration(fetchState);
+        ClassMapping parent = mapping.getJoinablePCSuperclassMapping();
+        if (parent == null && !mapping.isMapped())
+            throw new InvalidStateException(_loc.get("virtual-mapping",
+                mapping.getDescribedType()));
+
+        int seld = -1;
+        int pseld = -1;
+
+        // base class selects pks, etc
+        if (parent == null) {
+            // if no instance, select pks
+            if (sm == null) {
+                if (ident)
+                    sel.selectIdentifier(mapping.getPrimaryKeyColumns());
+                else
+                    sel.select(mapping.getPrimaryKeyColumns());
+                seld = 1;
+            }
+
+            // if no instance or not initialized and not exact oid, select type
+            if ((sm == null || (sm.getPCState() == PCState.TRANSIENT
+                && (!(sm.getObjectId()instanceof OpenJPAId)
+                || ((OpenJPAId) sm.getObjectId()).hasSubclasses())))
+                && mapping.getDiscriminator().select(sel, orig))
+                seld = 1;
+
+            // if no instance or no version, select version
+            if ((sm == null || sm.getVersion() == null)
+                && mapping.getVersion().select(sel, orig))
+                seld = 1;
+        } else {
+            // recurse on parent
+            pseld = selectBaseMappings(sel, parent, orig, sm, fields,
+                fetchState, eager, eagerToMany, ident, joined);
+        }
+
+        // select the mappings in the given fields set, or based on fetch
+        // configuration if no fields given
+        FieldMapping[] fms = mapping.getDefinedFieldMappings();
+        SelectExecutor esel;
+        int fseld;
+        for (int i = 0; i < fms.length; i++) {
+            // skip eager to-many select; we do that separately in calling
+            // method
+            if (fms[i] == eagerToMany)
+                continue;
+
+            // check for eager select
+            esel = sel.getEager(fms[i]);
+            if (esel != null) {
+                if (esel == sel)
+                    fms[i].selectEagerJoin(sel, sm, this, fetchState, eager);
+                else
+                    fms[i].selectEagerParallel(esel, sm, this, fetchState,
+                        eager);
+                seld = Math.max(0, seld);
+            } else if (requiresSelect(fms[i], sm, fields, fetchState)) {
+                fseld = fms[i].select(sel, sm, this, fetchState, eager);
+                seld = Math.max(fseld, seld);
+            } else if (optSelect(fms[i], sel, sm, fetchState)) {
+                fseld = fms[i].select(sel, sm, this, fetchState,
+                    fetch.EAGER_NONE);
+
+                // don't upgrade seld to > 0 based on these fields, since
+                // they're not in the calculated field set
+                if (fseld >= 0 && seld < 0)
+                    seld = 0;
+            }
+        }
+
+        // join to parent table if the parent / any ancestors have selected
+        // anything
+        if (!joined && pseld >= 0 && parent.getTable() != mapping.getTable())
+            sel.where(mapping.joinSuperclass(sel.newJoins(), false));
+
+        // return the highest value
+        return Math.max(pseld, seld);
+    }
+
+    /**
+     * When selecting fieldes, a special case is made for mappings that use
+     * 2-part selects that aren't explicitly *not* in the dfg so that they
+     * can get their primary table data. This method tests for that special
+     * case as an optimization.
+     */
+    private boolean optSelect(FieldMapping fm, Select sel,
+        OpenJPAStateManager sm,
+        JDBCFetchState fetchState) {
+        return !fm.isDefaultFetchGroupExplicit()
+            && (sm == null || sm.getPCState() == PCState.TRANSIENT
+            || !sm.getLoaded().get(fm.getIndex()))
+            && fm.supportsSelect(sel, sel.TYPE_TWO_PART, sm, this,
+            getFetchConfiguration(fetchState)) > 0
+            && fetchState.requiresSelect(fm, true);
+    }
+
+    /**
+     * Select field mappings that match the given fetch configuration for
+     * subclasses of the given type.
+     *
+     * @param sel the select to use
+     * @param mapping the type whose subclasses to select
+     * @param sm the instance being selected for, or null if none
+     * @param fetch the fetch configuration
+     */
+    private void selectSubclassMappings(Select sel, ClassMapping mapping,
+        OpenJPAStateManager sm, JDBCFetchState fetchState) {
+        loadSubclasses(mapping);
+        JDBCFetchConfiguration fetch = getFetchConfiguration(fetchState);
+        ClassMapping[] subMappings = mapping.getJoinablePCSubclassMappings();
+        if (subMappings.length == 0)
+            return;
+
+        // select all subclass mappings that match the fetch configuration
+        // and whose table is in the list of those selected so far; this
+        // way we select the max possible without selecting any tables that
+        // aren't present in all possible query matches; a special case
+        // is made for mappings that use 2-part selects that aren't
+        // explicitly *not* in the default so that they can get their
+        // primary table data
+        FieldMapping[] fms;
+        boolean joined;
+        boolean canJoin = _dict.joinSyntax != JoinSyntaxes.SYNTAX_TRADITIONAL
+            && fetch.getSubclassFetchMode(mapping) != fetch.EAGER_NONE;
+        for (int i = 0; i < subMappings.length; i++) {
+            if (!subMappings[i].supportsEagerSelect(sel, sm, this, mapping,
+                fetch))
+                continue;
+
+            // initialize so that if we can't join, we pretend we already have
+            joined = !canJoin;
+            fms = subMappings[i].getDefinedFieldMappings();
+            for (int j = 0; j < fms.length; j++) {
+                // make sure in one of configured fetch groups
+                if (!fms[j].isInDefaultFetchGroup()
+                    && !fetch.hasFetchGroup(fms[j].getFetchGroups())
+                    && !fetch.hasField(fms[j].getFullName())
+                    && (fms[j].isDefaultFetchGroupExplicit()
+                    || fms[j].supportsSelect(sel, sel.TYPE_TWO_PART, sm,
+                    this, fetch) <= 0))
+                    continue;
+
+                // if we can join to the subclass, do so; much better chance
+                // that the field will be able to select itself without joins
+                if (!joined) {
+                    // mark joined whether or not we join, so we don't have to
+                    // test conditions again for this subclass
+                    joined = true;
+                    sel.where(joinSubclass(sel, mapping, subMappings[i],
+                        null));
+                }
+
+                // if can select with tables already selected, do it
+                if (fms[j].supportsSelect(sel, sel.TYPE_JOINLESS, sm,
+                    this, fetch) > 0)
+                    fms[j].select(sel, null, this, fetchState,
+                        fetch.EAGER_NONE);
+            }
+        }
+    }
+
+    /**
+     * Helper method to join from class to its subclass. Recursive to allow
+     * for multiple hops, starting from the base class.
+     */
+    private static Joins joinSubclass(Select sel, ClassMapping base,
+        ClassMapping sub, Joins joins) {
+        if (sub == base || sub.getTable() == base.getTable()
+            || sel.isSelected(sub.getTable()))
+            return null;
+
+        // recurse first so we go least->most derived
+        ClassMapping sup = sub.getJoinablePCSuperclassMapping();
+        joins = joinSubclass(sel, base, sup, joins);
+        if (joins == null)
+            joins = sel.newJoins();
+        return sub.joinSuperclass(joins, true);
+    }
+
+    /**
+     * Makes sure all subclasses of the given type are loaded in the JVM.
+     * This is usually done automatically.
+     */
+    public void loadSubclasses(ClassMapping mapping) {
+        Discriminator dsc = mapping.getDiscriminator();
+        if (dsc.getSubclassesLoaded())
+            return;
+
+        // if the subclass list is set, no need to load subs
+        if (mapping.getRepository().getPersistentTypeNames(false,
+            _ctx.getClassLoader()) != null) {
+            dsc.setSubclassesLoaded(true);
+            return;
+        }
+
+        try {
+            dsc.loadSubclasses(this);
+        } catch (ClassNotFoundException cnfe) {
+            throw new StoreException(cnfe);
+        } catch (SQLException se) {
+            throw SQLExceptions.getStore(se, _dict);
+        }
+    }
+
+    /**
+     * Add WHERE conditions to the given select limiting the returned results
+     * to the given mapping type, possibly including subclasses.
+     *
+     * @return true if the mapping was joined down to its base class
+     * in order to add the conditions
+     */
+    public boolean addClassConditions(Select sel, ClassMapping mapping,
+        boolean subs, Joins joins) {
+        loadSubclasses(mapping);
+        if (mapping.getJoinablePCSuperclassMapping() == null
+            && mapping.getJoinablePCSubclassMappings().length == 0)
+            return false;
+
+        // join down to base class where the conditions will be added
+        ClassMapping from = mapping;
+        ClassMapping sup = mapping.getJoinablePCSuperclassMapping();
+        for (; sup != null; from = sup,
+            sup = from.getJoinablePCSuperclassMapping()) {
+            if (from.getTable() != sup.getTable()) {
+                if (joins == null)
+                    joins = sel.newJoins();
+                joins = from.joinSuperclass(joins, false);
+            }
+        }
+
+        Discriminator dsc = mapping.getDiscriminator();
+        SQLBuffer buf = dsc.getClassConditions(this, sel, joins, mapping, subs);
+        if (buf != null) {
+            sel.where(buf, joins);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Make the statement a candidate for cancellation.
+     */
+    private void beforeExecuteStatement(Statement stmnt) {
+        _stmnts.add(stmnt);
+    }
+
+    /**
+     * Remove the statement from the cancellable set.
+     */
+    private void afterExecuteStatement(Statement stmnt) {
+        _stmnts.remove(stmnt);
+    }
+
+    /**
+     * Connection returned to client code. Makes sure its wrapped connection
+     * ref count is decremented on finalize.
+     */
+    private static class ClientConnection
+        extends DelegatingConnection {
+
+        private boolean _closed = false;
+
+        public ClientConnection(Connection conn) {
+            super(conn);
+        }
+
+        public void close()
+            throws SQLException {
+            _closed = true;
+            super.close();
+        }
+
+        protected void finalize()
+            throws SQLException {
+            if (!_closed)
+                close();
+        }
+    }
+
+    /**
+     * Connection wrapper that keeps an internal ref count so that it knows
+     * when to really close.
+     */
+    private class RefCountConnection
+        extends DelegatingConnection {
+
+        private boolean _retain = false;
+        private int _refs = 0;
+        private boolean _freed = false;
+
+        public RefCountConnection(Connection conn) {
+            super(conn);
+        }
+
+        public boolean getRetain() {
+            return _retain;
+        }
+
+        public void setRetain(boolean retain) {
+            if (_retain && !retain && _refs <= 0)
+                free();
+            _retain = retain;
+        }
+
+        public void ref() {
+            // don't have to lock; called from connect(), which is locked
+            _refs++;
+        }
+
+        public void close()
+            throws SQLException {
+            // lock at broker level to avoid deadlocks
+            _ctx.lock();
+            try {
+                _refs--;
+                if (_refs <= 0 && !_retain)
+                    free();
+            } finally {
+                _ctx.unlock();
+            }
+        }
+
+        public void free() {
+            // ensure that we do not close the underlying connection
+            // multiple times; this could happen if someone (e.g., an
+            // Extent) holds a RefConnection, and then closes it (e.g., in
+            // the finalizer) after the StoreManager has already been closed.
+            if (_freed)
+                return;
+
+            try {
+                getDelegate().close();
+            } catch (SQLException se) {
+            }
+            _freed = true;
+            _conn = null;
+        }
+
+        protected Statement createStatement(boolean wrap)
+            throws SQLException {
+            return new CancelStatement(super.createStatement(false),
+                RefCountConnection.this);
+        }
+
+        protected Statement createStatement(int rsType, int rsConcur,
+            boolean wrap)
+            throws SQLException {
+            return new CancelStatement(super.createStatement(rsType,
+                rsConcur, false), RefCountConnection.this);
+        }
+
+        protected PreparedStatement prepareStatement(String sql, boolean wrap)
+            throws SQLException {
+            return new CancelPreparedStatement(super.prepareStatement
+                (sql, false), RefCountConnection.this);
+        }
+
+        protected PreparedStatement prepareStatement(String sql, int rsType,
+            int rsConcur, boolean wrap)
+            throws SQLException {
+            return new CancelPreparedStatement(super.prepareStatement
+                (sql, rsType, rsConcur, false), RefCountConnection.this);
+        }
+    }
+
+    /**
+     * Statement type that adds and removes itself from the set of active
+     * statements so that it can be canceled.
+     */
+    private class CancelStatement
+        extends DelegatingStatement {
+
+        public CancelStatement(Statement stmnt, Connection conn) {
+            super(stmnt, conn);
+        }
+
+        public int executeUpdate(String sql)
+            throws SQLException {
+            beforeExecuteStatement(this);
+            try {
+                return super.executeUpdate(sql);
+            } finally {
+                afterExecuteStatement(this);
+            }
+        }
+
+        protected ResultSet executeQuery(String sql, boolean wrap)
+            throws SQLException {
+            beforeExecuteStatement(this);
+            try {
+                return super.executeQuery(sql, wrap);
+            } finally {
+                afterExecuteStatement(this);
+            }
+        }
+    }
+
+    /**
+     * Statement type that adds and removes itself from the set of active
+     * statements so that it can be canceled.
+     */
+    private class CancelPreparedStatement
+        extends DelegatingPreparedStatement {
+
+        public CancelPreparedStatement(PreparedStatement stmnt,
+            Connection conn) {
+            super(stmnt, conn);
+        }
+
+        public int executeUpdate()
+            throws SQLException {
+            beforeExecuteStatement(this);
+            try {
+                return super.executeUpdate();
+            } finally {
+                afterExecuteStatement(this);
+            }
+        }
+
+        protected ResultSet executeQuery(boolean wrap)
+            throws SQLException {
+            beforeExecuteStatement(this);
+            try {
+                return super.executeQuery(wrap);
+            } finally {
+                afterExecuteStatement(this);
+            }
+        }
+
+        public int[] executeBatch()
+            throws SQLException {
+            beforeExecuteStatement(this);
+            try {
+                return super.executeBatch();
+            } finally {
+                afterExecuteStatement(this);
+            }
+        }
+    }
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,636 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed 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.openjpa.jdbc.kernel;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.openjpa.event.LifecycleEventManager;
+import org.apache.openjpa.jdbc.kernel.exps.GetColumn;
+import org.apache.openjpa.jdbc.kernel.exps.JDBCExpressionFactory;
+import org.apache.openjpa.jdbc.kernel.exps.JDBCStringContains;
+import org.apache.openjpa.jdbc.kernel.exps.JDBCWildcardMatch;
+import org.apache.openjpa.jdbc.kernel.exps.SQLEmbed;
+import org.apache.openjpa.jdbc.kernel.exps.SQLExpression;
+import org.apache.openjpa.jdbc.kernel.exps.SQLValue;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.jdbc.schema.Table;
+import org.apache.openjpa.jdbc.sql.DBDictionary;
+import org.apache.openjpa.jdbc.sql.SQLBuffer;
+import org.apache.openjpa.jdbc.sql.SQLExceptions;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.jdbc.sql.Union;
+import org.apache.openjpa.kernel.ExpressionStoreQuery;
+import org.apache.openjpa.kernel.OrderingMergedResultObjectProvider;
+import org.apache.openjpa.kernel.exps.ExpressionFactory;
+import org.apache.openjpa.kernel.exps.ExpressionParser;
+import org.apache.openjpa.kernel.exps.FilterListener;
+import org.apache.openjpa.kernel.exps.QueryExpressions;
+import org.apache.openjpa.lib.rop.MergedResultObjectProvider;
+import org.apache.openjpa.lib.rop.RangeResultObjectProvider;
+import org.apache.openjpa.lib.rop.ResultObjectProvider;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.ClassMetaData;
+import org.apache.openjpa.meta.ValueMetaData;
+import org.apache.openjpa.util.UserException;
+import serp.util.Numbers;
+
+/**
+ * JDBC query implementation.
+ *
+ * @author Abe White
+ * @nojavadoc
+ */
+public class JDBCStoreQuery
+    extends ExpressionStoreQuery {
+
+    private static final Table INVALID = new Table();
+
+    // add all standard filter and aggregate listeners to these maps
+    private static final Map _listeners = new HashMap();
+
+    static {
+        // deprecated extensions
+        _listeners.put(JDBCStringContains.TAG, new JDBCStringContains());
+        _listeners.put(JDBCWildcardMatch.TAG, new JDBCWildcardMatch());
+        _listeners.put(SQLExpression.TAG, new SQLExpression());
+        _listeners.put(SQLValue.TAG, new SQLValue());
+
+        // jdbc-specific extensions
+        _listeners.put(GetColumn.TAG, new GetColumn());
+        _listeners.put(SQLEmbed.TAG, new SQLEmbed());
+    }
+
+    private final transient JDBCStore _store;
+
+    /**
+     * Constructor. Supply store manager.
+     */
+    public JDBCStoreQuery(JDBCStore store, ExpressionParser parser) {
+        super(parser);
+        _store = store;
+    }
+
+    /**
+     * Return the store.
+     */
+    public JDBCStore getStore() {
+        return _store;
+    }
+
+    public FilterListener getFilterListener(String tag) {
+        return (FilterListener) _listeners.get(tag);
+    }
+
+    public Object newCompilationKey() {
+        JDBCFetchConfiguration fetch = (JDBCFetchConfiguration)
+            ctx.getFetchConfiguration();
+        return Numbers.valueOf(fetch.getJoinSyntax());
+    }
+
+    public boolean supportsDataStoreExecution() {
+        return true;
+    }
+
+    protected ClassMetaData[] getIndependentExpressionCandidates
+        (ClassMetaData meta, boolean subclasses) {
+        if (!subclasses)
+            return new ClassMapping[]{ (ClassMapping) meta };
+        return ((ClassMapping) meta).getIndependentAssignableMappings();
+    }
+
+    protected ExpressionFactory getExpressionFactory(ClassMetaData meta) {
+        return new JDBCExpressionFactory((ClassMapping) meta);
+    }
+
+    protected ResultObjectProvider executeQuery(Executor ex,
+        ClassMetaData base, ClassMetaData[] metas, boolean subclasses,
+        ExpressionFactory[] facts, QueryExpressions[] exps, Object[] params,
+        boolean lrs, long startIdx, long endIdx) {
+        if (metas.length > 1 && exps[0].aggregate)
+            throw new UserException(Localizer.forPackage(JDBCStoreQuery.class).
+                get("mult-mapping-aggregate", Arrays.asList(metas)));
+
+        ClassMapping[] mappings = (ClassMapping[]) metas;
+        JDBCFetchConfiguration fetch = (JDBCFetchConfiguration)
+            ctx.getFetchConfiguration();
+        JDBCFetchState fetchState = (JDBCFetchState) fetch.newFetchState();
+        if (exps[0].fetchPaths != null) {
+            fetch.addFields(Arrays.asList(exps[0].fetchPaths));
+            fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
+        }
+
+        int eager = calculateEagerMode(exps[0], startIdx, endIdx);
+        int subclassMode = fetch.getSubclassFetchMode((ClassMapping) base);
+        DBDictionary dict = _store.getDBDictionary();
+        long start = (mappings.length == 1 && dict.supportsSelectStartIndex)
+            ? startIdx : 0L;
+        long end = (dict.supportsSelectEndIndex) ? endIdx : Long.MAX_VALUE;
+
+        // add selects with populate WHERE conditions to list
+        List sels = new ArrayList(mappings.length);
+        List selMappings = new ArrayList(mappings.length);
+        BitSet subclassBits = new BitSet();
+        BitSet nextBits = new BitSet();
+        boolean unionable = createWhereSelects(sels, mappings, selMappings,
+            subclasses, subclassBits, nextBits, facts, exps, params, fetchState,
+            subclassMode)
+            && subclassMode == JDBCFetchConfiguration.EAGER_JOIN
+            && start == 0 && end == Long.MAX_VALUE;
+
+        // we might want to use lrs settings if we can't use the range
+        if (sels.size() > 1)
+            start = 0L;
+        lrs = lrs || (fetch.getFetchBatchSize() >= 0
+            && (start != startIdx || end != endIdx));
+
+        ResultObjectProvider[] rops = null;
+        ResultObjectProvider rop = null;
+        if (unionable) {
+            Union union = _store.getSQLFactory().newUnion((Select[])
+                sels.toArray(new Select[sels.size()]));
+            BitSet[] paged = populateUnion(union, mappings, subclasses,
+                facts, exps, params, fetchState, lrs, eager, start, end);
+            union.setLRS(lrs);
+            rop = executeUnion(union, mappings, exps, paged, fetchState);
+        } else {
+            if (sels.size() > 1)
+                rops = new ResultObjectProvider[sels.size()];
+
+            Select sel;
+            BitSet paged;
+            for (int i = 0, idx = 0; i < sels.size(); i++) {
+                sel = (Select) sels.get(i);
+                paged = populateSelect(sel, (ClassMapping) selMappings.get(i),
+                    subclassBits.get(i), (JDBCExpressionFactory) facts[idx],
+                    exps[idx], params, fetchState, lrs, eager, start, end);
+
+                rop = executeSelect(sel, (ClassMapping) selMappings.get(i),
+                    exps[idx], paged, fetchState, start, end);
+                if (rops != null)
+                    rops[i] = rop;
+
+                if (nextBits.get(i))
+                    idx++;
+            }
+        }
+
+        if (rops != null) {
+            if (exps[0].ascending.length == 0)
+                rop = new MergedResultObjectProvider(rops);
+            else {
+                rop = new OrderingMergedResultObjectProvider(rops,
+                    exps[0].ascending, ex, this, params);
+            }
+        }
+
+        // need to fake result range?
+        if ((rops != null && endIdx != Long.MAX_VALUE)
+            || start != startIdx || end != endIdx)
+            rop = new RangeResultObjectProvider(rop, startIdx, endIdx);
+        return rop;
+    }
+
+    /**
+     * Select data for the given union, returning paged fields.
+     */
+    private BitSet[] populateUnion(Union union, final ClassMapping[] mappings,
+        final boolean subclasses, final ExpressionFactory[] facts,
+        final QueryExpressions[] exps, final Object[] params,
+        final JDBCFetchState fetchState, final boolean lrs, final int eager,
+        final long start, final long end) {
+        final BitSet[] paged = (exps[0].projections.length > 0) ? null
+            : new BitSet[mappings.length];
+        union.select(new Union.Selector() {
+            public void select(Select sel, int idx) {
+                BitSet bits = populateSelect(sel, mappings[idx], subclasses,
+                    (JDBCExpressionFactory) facts[idx], exps[idx], params,
+                    fetchState, lrs, eager, start, end);
+                if (paged != null)
+                    paged[idx] = bits;
+            }
+        });
+        return paged;
+    }
+
+    /**
+     * Select data for the given select, returning paged fields.
+     */
+    private BitSet populateSelect(Select sel, ClassMapping mapping,
+        boolean subclasses, JDBCExpressionFactory fact, QueryExpressions exps,
+        Object[] params, JDBCFetchState fetchState, boolean lrs, int eager,
+        long start, long end) {
+        sel.setLRS(lrs);
+        sel.setRange(start, end);
+
+        BitSet paged = null;
+        if (exps.projections.length == 0) {
+            paged = PagingResultObjectProvider.getPagedFields(sel, mapping,
+                _store, fetchState, eager, end - start);
+            if (paged != null)
+                eager = JDBCFetchConfiguration.EAGER_JOIN;
+        }
+
+        fact.select(this, mapping, subclasses, sel, exps, params, fetchState,
+            eager);
+        return paged;
+    }
+
+    /**
+     * Execute the given union.
+     */
+    private ResultObjectProvider executeUnion(Union union,
+        ClassMapping[] mappings, QueryExpressions[] exps, BitSet[] paged,
+        JDBCFetchState fetchState) {
+        if (exps[0].projections.length > 0)
+            return new ProjectionResultObjectProvider(union, _store, fetchState,
+                exps);
+
+        if (paged != null)
+            for (int i = 0; i < paged.length; i++)
+                if (paged[i] != null)
+                    return new PagingResultObjectProvider(union, mappings,
+                        _store, fetchState, paged, Long.MAX_VALUE);
+
+        return new InstanceResultObjectProvider(union, mappings[0], _store,
+            fetchState);
+    }
+
+    /**
+     * Execute the given select.
+     */
+    private ResultObjectProvider executeSelect(Select sel,
+        ClassMapping mapping, QueryExpressions exps, BitSet paged,
+        JDBCFetchState fetchState, long start, long end) {
+        if (exps.projections.length > 0)
+            return new ProjectionResultObjectProvider(sel, _store, fetchState,
+                exps);
+        if (paged != null)
+            return new PagingResultObjectProvider(sel, mapping, _store,
+                fetchState, paged, end - start);
+        return new InstanceResultObjectProvider(sel, mapping, _store,
+            fetchState);
+    }
+
+    /**
+     * Generate the selects with WHERE conditions needed to execute the query
+     * for the given mappings.
+     */
+    private boolean createWhereSelects(List sels, ClassMapping[] mappings,
+        List selMappings, boolean subclasses, BitSet subclassBits,
+        BitSet nextBits, ExpressionFactory[] facts, QueryExpressions[] exps,
+        Object[] params, JDBCFetchState fetchState, int subclassMode) {
+        Select sel;
+        ClassMapping[] verts;
+        boolean unionable = true;
+        for (int i = 0; i < mappings.length; i++) {
+            // determine vertical mappings to select separately
+            verts = getVerticalMappings(mappings[i], subclasses, exps[i],
+                subclassMode);
+            if (verts.length == 1 && subclasses)
+                subclassBits.set(sels.size());
+
+            // create criteria select and clone for each vert mapping
+            sel = ((JDBCExpressionFactory) facts[i]).evaluate(this, fetchState,
+                exps[i], params);
+            for (int j = 0; j < verts.length; j++) {
+                selMappings.add(verts[j]);
+                if (j == verts.length - 1) {
+                    nextBits.set(sels.size());
+                    sels.add(sel);
+                } else
+                    sels.add(sel.fullClone(1));
+            }
+
+            // turn off unioning if a given independent mapping requires
+            // multiple selects, or if we're using FROM selects
+            if (verts.length > 1 || sel.getFromSelect() != null)
+                unionable = false;
+        }
+        return unionable;
+    }
+
+    /**
+     * Return all the vertical mappings to select separately. Depends on
+     * subclass fetch mode and the type of query.
+     */
+    private ClassMapping[] getVerticalMappings(ClassMapping mapping,
+        boolean subclasses, QueryExpressions exps, int subclassMode) {
+        if (!subclasses || exps.projections.length > 0)
+            return new ClassMapping[]{ mapping };
+
+        if (subclassMode != JDBCFetchConfiguration.EAGER_PARALLEL
+            || !hasVerticalSubclasses(mapping))
+            return new ClassMapping[]{ mapping };
+
+        List subs = new ArrayList(4);
+        addSubclasses(mapping, subs);
+        return (ClassMapping[]) subs.toArray(new ClassMapping[subs.size()]);
+    }
+
+    /**
+     * Recursive helper to add mappings for subclasses to the given list.
+     */
+    private void addSubclasses(ClassMapping mapping, Collection subs) {
+        // possible future optimizations:
+        // - if no fields in meta or its subclasses (and not in an
+        //   already-selected table) are in the current fetch
+        //   configuration, stop creating new executors
+        // - allow an executor to select a range of subclasses, rather
+        //   than just all subclasses / no subclasses; this would
+        //   allow us to do just one query per actual vertically-mapped
+        //   subclass, rather than one per mapped subclass, as is happening now
+
+        subs.add(mapping);
+        if (!hasVerticalSubclasses(mapping))
+            return;
+
+        // recurse on immediate subclasses
+        ClassMapping[] subMappings = mapping.getJoinablePCSubclassMappings();
+        for (int i = 0; i < subMappings.length; i++)
+            if (subMappings[i].getJoinablePCSuperclassMapping() == mapping)
+                addSubclasses(subMappings[i], subs);
+    }
+
+    /**
+     * Return whether the given class has any vertical subclasses.
+     */
+    private static boolean hasVerticalSubclasses(ClassMapping mapping) {
+        ClassMapping[] subs = mapping.getJoinablePCSubclassMappings();
+        for (int i = 0; i < subs.length; i++)
+            if (subs[i].getStrategy()instanceof VerticalClassStrategy)
+                return true;
+        return false;
+    }
+
+    /**
+     * The eager mode depends on the unique setting and range. If the range
+     * produces 0 results, use eager setting of none. If it produces 1 result
+     * or the query is unique, use an eager setting of single. Otherwise use
+     * an eager mode of multiple.
+     */
+    private int calculateEagerMode(QueryExpressions exps, long startIdx,
+        long endIdx) {
+        if (exps.projections.length > 0 || startIdx >= endIdx)
+            return EagerFetchModes.EAGER_NONE;
+        if (endIdx - startIdx == 1 || ctx.isUnique())
+            return EagerFetchModes.EAGER_JOIN;
+        return EagerFetchModes.EAGER_PARALLEL;
+    }
+
+    protected Number executeDelete(Executor ex, ClassMetaData base,
+        ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
+        QueryExpressions[] exps, Object[] params) {
+        return executeBulkOperation(ex, base, metas, subclasses, facts,
+            exps, params, null);
+    }
+
+    protected Number executeUpdate(Executor ex, ClassMetaData base,
+        ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
+        QueryExpressions[] exps, Object[] params) {
+        return executeBulkOperation(ex, base, metas, subclasses, facts,
+            exps, params, exps[0].updates);
+    }
+
+    private Number executeBulkOperation(Executor ex, ClassMetaData base,
+        ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
+        QueryExpressions[] exps, Object[] params, Map updates) {
+        // we cannot execute a bulk delete statement when have mappings in
+        // multiple tables, so indicate we want to use in-memory with null
+        ClassMapping[] mappings = (ClassMapping[]) metas;
+        boolean isUpdate = updates != null && updates.size() > 0;
+        for (int i = 0; i < mappings.length; i++) {
+            if (!isSingleTableMapping(mappings[i], subclasses) && !isUpdate)
+                return null;
+
+            if (!isUpdate) {
+                // if there are any delete callbacks, we need to
+                // execute in-memory so the callbacks are invoked
+                LifecycleEventManager mgr = ctx.getStoreContext().getBroker().
+                    getLifecycleEventManager();
+                if (mgr.hasDeleteListeners(null, mappings[i]))
+                    return null;
+            }
+        }
+
+        JDBCFetchConfiguration fetch = (JDBCFetchConfiguration)
+            ctx.getFetchConfiguration();
+        DBDictionary dict = _store.getDBDictionary();
+        int subs = (subclasses) ? Select.SUBS_JOINABLE : Select.SUBS_NONE;
+
+        SQLBuffer[] sql = new SQLBuffer[mappings.length];
+        JDBCExpressionFactory jdbcFactory;
+        Select sel;
+        for (int i = 0; i < mappings.length; i++) {
+            jdbcFactory = (JDBCExpressionFactory) facts[i];
+            JDBCFetchState fetchState = (JDBCFetchState) fetch.newFetchState();
+            sel = jdbcFactory.evaluate(this, fetchState, exps[i], params);
+            jdbcFactory.select(this, mappings[i], subclasses, sel,
+                exps[i], params, fetchState, JDBCFetchConfiguration.EAGER_NONE);
+
+            // specification of the "udpates" map indicates that this is
+            // an update query; otherwise, this is a delete statement
+            // The bulk operation will return null to indicate that the database
+            // does not support the request bulk delete operation; in
+            // this case, we need to perform the query in-memory and
+            // manually delete the instances
+            if (updates == null)
+                sql[i] = dict.toDelete(mappings[i], sel, _store, params);
+            else
+                sql[i] = dict.toUpdate(mappings[i], sel, _store, params,
+                    updates);
+
+            if (sql[i] == null)
+                return null;
+        }
+
+        // we need to make sure we have an active store connection
+        ctx.getStoreContext().beginStore();
+
+        Connection conn = _store.getConnection();
+        long count = 0;
+        try {
+            PreparedStatement stmnt;
+            for (int i = 0; i < sql.length; i++) {
+                stmnt = null;
+                try {
+                    stmnt = sql[i].prepareStatement(conn);
+                    count += stmnt.executeUpdate();
+                } finally {
+                    if (stmnt != null)
+                        try {
+                            stmnt.close();
+                        } catch (SQLException se) {
+                        }
+                }
+            }
+        }
+        catch (SQLException se) {
+            throw SQLExceptions.getStore(se, ctx, _store.getDBDictionary());
+        } finally {
+            try {
+                conn.close();
+            } catch (SQLException se) {
+            }
+        }
+        return Numbers.valueOf(count);
+    }
+
+    /**
+     * Whether the given mapping occupies only one table.
+     */
+    private boolean isSingleTableMapping(ClassMapping mapping,
+        boolean subclasses) {
+        ClassMapping root = mapping;
+        while (root.getJoinablePCSuperclassMapping() != null)
+            root = root.getJoinablePCSuperclassMapping();
+        if (hasVerticalSubclasses(root))
+            return false;
+
+        // we cannot execute a bulk delete if any of the
+        // field mappings for the candidates have columns
+        // in any other table, since bulk deleting just from the
+        // class will leave dangling relations; we might be able
+        // to issue bulk deletes separately for the joins (possibly
+        // using a temporary table to select the primary keys for
+        // all the related tables and then issing a delete against those
+        // keys), but that logic is not currently implemented
+        Table table = getTable(mapping.getFieldMappings(), null);
+        if (table == INVALID)
+            return false;
+
+        if (subclasses) {
+            // if we are including subclasses, we also need to gather
+            // all the mappings for all known subclasses
+            ClassMapping[] subs = mapping.getJoinablePCSubclassMappings();
+            for (int i = 0; subs != null && i < subs.length; i++) {
+                table = getTable(subs[i].getDefinedFieldMappings(), table);
+                if (table == INVALID)
+                    return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return the single table for the given fields, or INVALID if they
+     * use multiple tables.
+     */
+    private Table getTable(FieldMapping[] fields, Table table) {
+        for (int i = 0; i < fields.length; i++) {
+            table = getTable(fields[i], table);
+            if (table == INVALID)
+                break;
+        }
+        return table;
+    }
+
+    /**
+     * Return the table for the field if the given table hasn't been set
+     * yet, or if the tables match. If the field uses a different table,
+     * returns INVALID. Also returns INVALID if field is dependent.
+     */
+    private Table getTable(FieldMapping fm, Table table) {
+        if (fm.getCascadeDelete() != ValueMetaData.CASCADE_NONE)
+            return INVALID;
+
+        Column[] columns = fm.getColumns();
+        for (int i = 0; columns != null && i < columns.length; i++) {
+            if (table == null)
+                table = columns[i].getTable();
+            else if (table != columns[i].getTable())
+                return INVALID;
+        }
+        return table;
+    }
+
+    protected Number executeUpdate(ClassMetaData base, ClassMetaData[] metas,
+        boolean subclasses, ExpressionFactory[] facts,
+        QueryExpressions[] parsed, Object[] params) {
+        return null;
+    }
+
+    protected String[] getDataStoreActions(ClassMetaData base,
+        ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
+        QueryExpressions[] exps, Object[] params, long startIdx, long endIdx) {
+        ClassMapping[] mappings = (ClassMapping[]) metas;
+        JDBCFetchConfiguration fetch = (JDBCFetchConfiguration)
+            ctx.getFetchConfiguration();
+        JDBCFetchState fetchState = (JDBCFetchState) fetch.newFetchState();
+        if (exps[0].fetchPaths != null) {
+            fetch.addFields(Arrays.asList(exps[0].fetchPaths));
+            fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
+        }
+
+        int eager = calculateEagerMode(exps[0], startIdx, endIdx);
+        eager = Math.min(eager, JDBCFetchConfiguration.EAGER_JOIN);
+        int subclassMode = fetch.getSubclassFetchMode((ClassMapping) base);
+        DBDictionary dict = _store.getDBDictionary();
+        long start = (mappings.length == 1 && dict.supportsSelectStartIndex)
+            ? startIdx : 0L;
+        long end = (dict.supportsSelectEndIndex) ? endIdx : Long.MAX_VALUE;
+
+        // add selects with populate WHERE conditions to list
+        List sels = new ArrayList(mappings.length);
+        List selMappings = new ArrayList(mappings.length);
+        BitSet subclassBits = new BitSet();
+        BitSet nextBits = new BitSet();
+        boolean unionable = createWhereSelects(sels, mappings, selMappings,
+            subclasses, subclassBits, nextBits, facts, exps, params, fetchState,
+            subclassMode)
+            && subclassMode == JDBCFetchConfiguration.EAGER_JOIN;
+        if (sels.size() > 1)
+            start = 0L;
+
+        if (unionable) {
+            Union union = _store.getSQLFactory().newUnion((Select[])
+                sels.toArray(new Select[sels.size()]));
+            populateUnion(union, mappings, subclasses, facts, exps, params,
+                fetchState, false, eager, start, end);
+            if (union.isUnion())
+                return new String[]{ union.toSelect(false, fetch).
+                    getSQL(true) };
+            sels = Arrays.asList(union.getSelects());
+        } else {
+            Select sel;
+            for (int i = 0, idx = 0; i < sels.size(); i++) {
+                sel = (Select) sels.get(i);
+                populateSelect(sel, (ClassMapping) selMappings.get(i),
+                    subclassBits.get(i), (JDBCExpressionFactory) facts[idx],
+                    exps[idx], params, fetchState, false, eager, start, end);
+                if (nextBits.get(i))
+                    idx++;
+            }
+        }
+
+        String[] sql = new String[sels.size()];
+        for (int i = 0; i < sels.size(); i++)
+            sql[i] = ((Select) sels.get(i)).toSelect(false, fetch).
+                getSQL(true);
+        return sql;
+    }
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/LRSSizes.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/LRSSizes.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/LRSSizes.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/LRSSizes.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed 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.openjpa.jdbc.kernel;
+
+import java.sql.ResultSet;
+
+/**
+ * Ways of calculating the size of large result sets.
+ *
+ * @author Abe White
+ */
+public interface LRSSizes {
+
+    /**
+     * Mode for returning {@link Integer#MAX_VALUE} for the size of
+     * large result sets.
+     */
+    public static final int SIZE_UNKNOWN = 0;
+
+    /**
+     * Mode for using {@link ResultSet#last} to calcualte the size of
+     * large result sets.
+     */
+    public static final int SIZE_LAST = 1;
+
+    /**
+     * Mode for using a query to calculate the size of large result sets.
+     */
+    public static final int SIZE_QUERY = 2;
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/LRSSizes.java
------------------------------------------------------------------------------
    svn:executable = *