blob: 8adec8a7070d3df038992c6fa9cf5327318694e9 [file] [log] [blame]
/*
* 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.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.ExpContext;
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.PCPath;
import org.apache.openjpa.jdbc.kernel.exps.QueryExpressionsState;
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.kernel.exps.Val;
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.strats.NoneDiscriminatorStrategy;
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.PostgresDictionary;
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.SelectImpl;
import org.apache.openjpa.jdbc.sql.Union;
import org.apache.openjpa.kernel.ExpressionStoreQuery;
import org.apache.openjpa.kernel.Filters;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.OrderingMergedResultObjectProvider;
import org.apache.openjpa.kernel.QueryHints;
import org.apache.openjpa.kernel.exps.Constant;
import org.apache.openjpa.kernel.exps.Context;
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.Literal;
import org.apache.openjpa.kernel.exps.QueryExpressions;
import org.apache.openjpa.kernel.exps.StringContains;
import org.apache.openjpa.kernel.exps.WildcardMatch;
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.JavaTypes;
import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;
/**
* JDBC query implementation.
*
* @author Abe White
*/
public class JDBCStoreQuery
extends ExpressionStoreQuery {
private static final long serialVersionUID = 1L;
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(StringContains.TAG, new JDBCStringContains());
_listeners.put(WildcardMatch.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;
private static ThreadLocalContext localContext = new ThreadLocalContext();
/**
* Constructor. Supply store manager.
*/
public JDBCStoreQuery(JDBCStore store, ExpressionParser parser) {
super(parser);
_store = store;
}
/**
* Return the store.
*/
public JDBCStore getStore() {
return _store;
}
@Override
public FilterListener getFilterListener(String tag) {
return (FilterListener) _listeners.get(tag);
}
@Override
public Object newCompilationKey() {
JDBCFetchConfiguration fetch = (JDBCFetchConfiguration) ctx
.getFetchConfiguration();
return fetch.getJoinSyntax();
}
@Override
public boolean supportsDataStoreExecution() {
return true;
}
@Override
protected ClassMetaData[] getIndependentExpressionCandidates(
ClassMetaData meta, boolean subclasses) {
if (!subclasses)
return new ClassMapping[] { (ClassMapping) meta };
return ((ClassMapping) meta).getIndependentAssignableMappings();
}
@Override
protected ExpressionFactory getExpressionFactory(ClassMetaData meta) {
JDBCExpressionFactory factory = new JDBCExpressionFactory((ClassMapping) meta);
if (_store.getDBDictionary() instanceof PostgresDictionary)
factory.setBooleanLiteralAsNumeric(false);
return factory;
}
@Override
protected ResultObjectProvider executeQuery(Executor ex,
ClassMetaData base, ClassMetaData[] metas, boolean subclasses,
ExpressionFactory[] facts, QueryExpressions[] exps, Object[] params,
Range range) {
Context[] ctxs = new Context[exps.length];
for (int i = 0; i < exps.length; i++)
ctxs[i] = exps[i].ctx();
localContext.set(clone(ctxs, null));
if (metas.length > 1 && exps[0].isAggregate())
throw new UserException(Localizer.forPackage(JDBCStoreQuery.class).
get("mult-mapping-aggregate", Arrays.asList(metas)));
ClassMapping[] mappings = (ClassMapping[]) metas;
JDBCFetchConfiguration fetch = (JDBCFetchConfiguration)
ctx.getFetchConfiguration();
if (exps[0].fetchPaths != null) {
fetch.addFields(Arrays.asList(exps[0].fetchPaths));
fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
}
if (exps[0].fetchInnerPaths != null)
fetch.addFetchInnerJoins(Arrays.asList(exps[0].fetchInnerPaths));
int eager = calculateEagerMode(exps[0], range.start, range.end);
int subclassMode = fetch.getSubclassFetchMode((ClassMapping) base);
DBDictionary dict = _store.getDBDictionary();
long start = (mappings.length == 1 && dict.supportsSelectStartIndex)
? range.start : 0L;
long end = (dict.supportsSelectEndIndex) ? range.end : Long.MAX_VALUE;
QueryExpressionsState[] states = new QueryExpressionsState[exps.length];
for (int i = 0; i < states.length; i++) {
states[i] = new QueryExpressionsState();
exps[i].state = states[i];
}
ExpContext ctx = new ExpContext(_store, params, fetch);
// 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, states, ctx,
subclassMode)
&& subclassMode == EagerFetchModes.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;
boolean lrs = range.lrs || (fetch.getFetchBatchSize() >= 0
&& (start != range.start || end != range.end));
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, states, ctx, lrs, eager, start, end);
union.setLRS(lrs);
rop = executeUnion(union, mappings, exps, states, ctx, paged);
} 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], states[idx], ctx, lrs, eager, start, end);
rop = executeSelect(sel, (ClassMapping) selMappings.get(i),
exps[idx], states[idx], ctx, paged, 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 && range.end != Long.MAX_VALUE)
|| start != range.start || end != range.end)
rop = new RangeResultObjectProvider(rop, range.start, range.end);
localContext.remove();
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 QueryExpressionsState[] states,
final ExpContext ctx, 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() {
@Override
public void select(Select sel, int idx) {
BitSet bits = populateSelect(sel, mappings[idx], subclasses,
(JDBCExpressionFactory) facts[idx], exps[idx], states[idx],
ctx, 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,
QueryExpressionsState state, ExpContext ctx, 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, ctx.fetch, eager, end - start);
if (paged != null)
eager = EagerFetchModes.EAGER_JOIN;
}
fact.getSelectConstructor().select(sel, ctx, mapping, subclasses, exps,
state, eager);
return paged;
}
/**
* Execute the given union.
*/
private ResultObjectProvider executeUnion(Union union,
ClassMapping[] mappings, QueryExpressions[] exps,
QueryExpressionsState[] states, ExpContext ctx, BitSet[] paged) {
if (exps[0].projections.length > 0)
return new ProjectionResultObjectProvider(union, exps, states, ctx);
if (paged != null)
for (int i = 0; i < paged.length; i++)
if (paged[i] != null)
return new PagingResultObjectProvider(union, mappings,
_store, ctx.fetch, paged, Long.MAX_VALUE);
return new InstanceResultObjectProvider(union, mappings[0], _store,
ctx.fetch);
}
/**
* Execute the given select.
*/
private ResultObjectProvider executeSelect(Select sel, ClassMapping mapping,
QueryExpressions exps, QueryExpressionsState state, ExpContext ctx,
BitSet paged, long start, long end) {
if (exps.projections.length > 0)
return new ProjectionResultObjectProvider(sel, exps, state, ctx);
if (paged != null)
return new PagingResultObjectProvider(sel, mapping, _store,
ctx.fetch, paged, end - start);
return new InstanceResultObjectProvider(sel, mapping, _store,
ctx.fetch);
}
/**
* 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,
QueryExpressionsState[] states, ExpContext ctx, int subclassMode) {
Number optHint = (Number) ctx.fetch.getHint
(QueryHints.HINT_RESULT_COUNT);
ClassMapping[] verts;
boolean unionable = true;
Select sel;
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());
Discriminator disc = mappings[i].getDiscriminator();
if (mappings.length > 1 && disc != null && disc.getColumns().length == 0 &&
disc.getStrategy() instanceof NoneDiscriminatorStrategy)
ctx.tpcMeta = mappings[i];
// create criteria select and clone for each vert mapping
sel = ((JDBCExpressionFactory) facts[i]).getSelectConstructor().
evaluate(ctx, null, null, exps[i], states[i]);
if (optHint != null)
sel.setExpectedResultCount(optHint.intValue(), true);
else if (this.ctx.isUnique())
sel.setExpectedResultCount(1, false);
List selectFrom = getJoinedTableMeta(sel);
int size = 0;
if (selectFrom != null) {
size = selectFrom.size();
for (int j = 0; j < size; j++) {
ClassMapping vert = (ClassMapping)selectFrom.get(j);
selMappings.add(vert);
if (j == size - 1) {
nextBits.set(sels.size());
sel.select(vert.getPrimaryKeyColumns(), null);
sels.add(sel);
} else {
SelectImpl selClone = (SelectImpl)sel.fullClone(1);
selClone.select(vert.getPrimaryKeyColumns(), null);
sels.add(selClone);
}
}
} else {
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 || size > 1 || sel.getFromSelect() != null)
unionable = false;
}
return unionable;
}
private List getJoinedTableMeta(Select sel) {
List selectFrom = sel.getJoinedTableClassMeta();
List exSelectFrom = sel.getExcludedJoinedTableClassMeta();
if (exSelectFrom == null)
return selectFrom;
if (selectFrom == null)
return null;
int size = selectFrom.size();
List retList = new ArrayList(size);
for (int i = 0; i < size; i++) {
Object obj = selectFrom.get(i);
if (!exSelectFrom.contains(obj))
retList.add(obj);
}
return retList;
}
/**
* 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 != EagerFetchModes.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 start,
long end) {
if (exps.projections.length > 0 || start >= end)
return EagerFetchModes.EAGER_NONE;
if (end - start == 1 || ctx.isUnique())
return EagerFetchModes.EAGER_JOIN;
return EagerFetchModes.EAGER_PARALLEL;
}
@Override
protected Number executeDelete(Executor ex, ClassMetaData base,
ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
QueryExpressions[] exps, Object[] params) {
return executeBulkOperation(metas, subclasses, facts, exps,
params, null);
}
@Override
protected Number executeUpdate(Executor ex, ClassMetaData base,
ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
QueryExpressions[] exps, Object[] params) {
return executeBulkOperation(metas, subclasses, facts, exps,
params, exps[0].updates);
}
private Number executeBulkOperation(ClassMetaData[] metas,
boolean subclasses, ExpressionFactory[] facts, QueryExpressions[] exps,
Object[] params, Map updates) {
Context[] ctxs = new Context[exps.length];
for (int i = 0; i < exps.length; i++)
ctxs[i] = exps[i].ctx();
localContext.set(clone(ctxs, null));
// 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;
// specification of the "updates" map indicates that this is
// an update query; otherwise, this is a delete statement
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();
ExpContext ctx = new ExpContext(_store, params, fetch);
DBDictionary dict = _store.getDBDictionary();
QueryExpressionsState[] state = new QueryExpressionsState[exps.length];
for (int i = 0; i < state.length; i++)
state[i] = new QueryExpressionsState();
SQLBuffer[] sql = new SQLBuffer[mappings.length];
JDBCExpressionFactory jdbcFactory;
Select sel;
for (int i = 0; i < mappings.length; i++) {
jdbcFactory = (JDBCExpressionFactory) facts[i];
sel = jdbcFactory.getSelectConstructor().evaluate(ctx, null, null,
exps[i], state[i]);
jdbcFactory.getSelectConstructor().select(sel, ctx, mappings[i],
subclasses, exps[i], state[i],
EagerFetchModes.EAGER_NONE);
// 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 (!isUpdate)
sql[i] = dict.toDelete(mappings[i], sel, 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
_store.getContext().beginStore();
Connection conn = _store.getConnection();
long count = 0;
try {
PreparedStatement stmnt;
for (int i = 0; i < sql.length; i++) {
stmnt = null;
try {
stmnt = prepareStatement(conn, sql[i]);
dict.setTimeouts(stmnt, fetch, true);
count += executeUpdate(conn, stmnt, sql[i], isUpdate);
} catch (SQLException se) {
throw SQLExceptions.getStore(se, sql[i].getSQL(),
_store.getDBDictionary());
} finally {
if (stmnt != null)
try { stmnt.close(); } catch (SQLException se) {}
}
}
} finally {
try {
if (conn.getAutoCommit())
conn.close();
} catch (SQLException se) {
}
}
localContext.remove();
return 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
&& !fm.isEmbeddedPC())
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;
}
if (fm.isBidirectionalJoinTableMappingOwner())
return INVALID;
return table;
}
protected Number executeUpdate(ClassMetaData base, ClassMetaData[] metas,
boolean subclasses, ExpressionFactory[] facts,
QueryExpressions[] parsed, Object[] params) {
return null;
}
@Override
protected String[] getDataStoreActions(ClassMetaData base,
ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
QueryExpressions[] exps, Object[] params, Range range) {
Context[] ctxs = new Context[exps.length];
for (int i = 0; i < exps.length; i++)
ctxs[i] = exps[i].ctx();
localContext.set(clone(ctxs, null));
ClassMapping[] mappings = (ClassMapping[]) metas;
JDBCFetchConfiguration fetch = (JDBCFetchConfiguration) ctx.
getFetchConfiguration();
if (exps[0].fetchPaths != null) {
fetch.addFields(Arrays.asList(exps[0].fetchPaths));
fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
}
if (exps[0].fetchInnerPaths != null)
fetch.addFetchInnerJoins(Arrays.asList(exps[0].fetchInnerPaths));
int eager = calculateEagerMode(exps[0], range.start, range.end);
eager = Math.min(eager, EagerFetchModes.EAGER_JOIN);
int subclassMode = fetch.getSubclassFetchMode((ClassMapping) base);
DBDictionary dict = _store.getDBDictionary();
long start = (mappings.length == 1 && dict.supportsSelectStartIndex)
? range.start : 0L;
long end = (dict.supportsSelectEndIndex) ? range.end : Long.MAX_VALUE;
QueryExpressionsState[] states = new QueryExpressionsState[exps.length];
for (int i = 0; i < states.length; i++)
states[i] = new QueryExpressionsState();
ExpContext ctx = new ExpContext(_store, params, fetch);
// 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, states, ctx,
subclassMode) && subclassMode == EagerFetchModes.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, states, ctx,
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], states[idx], ctx, 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);
localContext.remove();
return sql;
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of executing update.
*/
protected int executeUpdate(Connection conn, PreparedStatement stmnt,
SQLBuffer sqlBuf, boolean isUpdate) throws SQLException {
return stmnt.executeUpdate();
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of preparing statement.
*/
protected PreparedStatement prepareStatement(Connection conn, SQLBuffer sql)
throws SQLException {
return sql.prepareStatement(conn);
}
@Override
public Object evaluate(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
int id = 0;
if (value instanceof org.apache.openjpa.jdbc.kernel.exps.Val)
id = ((org.apache.openjpa.jdbc.kernel.exps.Val)value).getId();
else
throw new UnsupportedException();
switch(id) {
case Val.MATH_VAL:
return handleMathVal(value, ob, params, sm);
case Val.CONCAT_VAL:
return handleConcatVal(value, ob, params, sm);
case Val.SUBSTRING_VAL:
return handleSubstringVal(value, ob, params, sm);
case Val.ARGS_VAL:
return handleArgsVal(value, ob, params, sm);
case Val.LOWER_VAL:
return handleLowerVal(value, ob, params, sm);
case Val.UPPER_VAL:
return handleUpperVal(value, ob, params, sm);
case Val.LENGTH_VAL:
return handleLengthVal(value, ob, params, sm);
case Val.TRIM_VAL:
return handleTrimVal(value, ob, params, sm);
case Val.INDEXOF_VAL:
return handleIndexOfVal(value, ob, params, sm);
case Val.ABS_VAL:
return handleAbsVal(value, ob, params, sm);
case Val.SQRT_VAL:
return handleSqrtVal(value, ob, params, sm);
default:
throw new UnsupportedException();
}
}
private Object handleMathVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
org.apache.openjpa.jdbc.kernel.exps.Math mathVal =
(org.apache.openjpa.jdbc.kernel.exps.Math) value;
Val value1 = mathVal.getVal1();
Object val1 = getValue(value1, ob, params, sm);
Class c1 = value1.getType();
Val value2 = mathVal.getVal2();
Object val2 = getValue(value2, ob, params, sm);
Class c2 = value2.getType();
String op = mathVal.getOperation();
if (op.equals(org.apache.openjpa.jdbc.kernel.exps.Math.ADD))
return Filters.add(val1, c1, val2, c2);
else if (op.equals(
org.apache.openjpa.jdbc.kernel.exps.Math.SUBTRACT))
return Filters.subtract(val1, c1, val2, c2);
else if (op.equals(
org.apache.openjpa.jdbc.kernel.exps.Math.MULTIPLY))
return Filters.multiply(val1, c1, val2, c2);
else if (op.equals(
org.apache.openjpa.jdbc.kernel.exps.Math.DIVIDE))
return Filters.divide(val1, c1, val2, c2);
else if (op.equals(org.apache.openjpa.jdbc.kernel.exps.Math.MOD))
return Filters.mod(val1, c1, val2, c2);
throw new UnsupportedException();
}
private Object handleConcatVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
org.apache.openjpa.jdbc.kernel.exps.Concat concatVal =
(org.apache.openjpa.jdbc.kernel.exps.Concat)value;
Val value1 = concatVal.getVal1();
Object val1 = getValue(value1, ob, params, sm);
Val value2 = concatVal.getVal2();
Object val2 = getValue(value2, ob, params, sm);
return new StringBuilder(100).append(val1).append(val2).toString();
}
private Object handleSubstringVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
org.apache.openjpa.jdbc.kernel.exps.Substring substrVal =
(org.apache.openjpa.jdbc.kernel.exps.Substring) value;
Val value1 = substrVal.getVal1();
String val1 = (String) getValue(value1, ob, params, sm);
Val value2 = substrVal.getVal2();
Object val2 = getValue(value2, ob, params, sm);
org.apache.openjpa.kernel.exps.Value[] valAry2 =
(org.apache.openjpa.kernel.exps.Value[]) val2;
Object arg1 = getValue(valAry2[0], ob, params, sm); //starting pos
Object arg2 = getValue(valAry2[1], ob, params, sm); // length
int startIdx = ((Long) arg1).intValue();
int length = ((Long) arg2).intValue();
int endIdx = startIdx + length;
return val1.substring(startIdx, endIdx);
}
private Object handleArgsVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
org.apache.openjpa.jdbc.kernel.exps.Args argsVal =
(org.apache.openjpa.jdbc.kernel.exps.Args) value;
return argsVal.getValues();
}
private Object handleLowerVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
org.apache.openjpa.jdbc.kernel.exps.ToLowerCase lowerVal =
(org.apache.openjpa.jdbc.kernel.exps.ToLowerCase) value;
Val val = lowerVal.getValue();
return ((String) getValue(val, ob, params, sm)).toLowerCase();
}
private Object handleUpperVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm){
org.apache.openjpa.jdbc.kernel.exps.ToUpperCase upperVal =
(org.apache.openjpa.jdbc.kernel.exps.ToUpperCase) value;
Val val = upperVal.getValue();
return ((String) getValue(val, ob, params, sm)).toUpperCase();
}
private Object handleLengthVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm){
org.apache.openjpa.jdbc.kernel.exps.StringLength strLenVal =
(org.apache.openjpa.jdbc.kernel.exps.StringLength) value;
Val val = strLenVal.getValue();
return ((String) getValue(val, ob, params, sm)).length();
}
private Object handleTrimVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
org.apache.openjpa.jdbc.kernel.exps.Trim trimVal =
(org.apache.openjpa.jdbc.kernel.exps.Trim) value;
Val val = trimVal.getVal();
String valStr = (String) getValue(val, ob, params, sm);
Val trimChar = trimVal.getTrimChar();
char trimCharObj = ((String) getValue(trimChar, ob, params, sm)).
charAt(0);
Boolean where = trimVal.getWhere();
if (where == null) { //trim both
return trimLeading(trimTrailing(valStr, trimCharObj), trimCharObj);
} else if (where) { // trim leading
return trimLeading(valStr, trimCharObj);
} else { // trim trailing
return trimTrailing(valStr, trimCharObj);
}
}
private String trimLeading(String value, char trimChar) {
int startIdx = 0;
int len = value.length();
for (int i = 0; i < len; i++) {
if (value.charAt(i) != trimChar) {
startIdx = i;
break;
}
}
return value.substring(startIdx);
}
private String trimTrailing(String value, char trimChar) {
int endIdx = 0;
int len = value.length();
for (int i = len-1; i >= 0; i--) {
if (value.charAt(i) != trimChar) {
endIdx = i;
break;
}
}
return value.substring(0, endIdx+1);
}
private Object handleIndexOfVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
org.apache.openjpa.jdbc.kernel.exps.IndexOf locateVal =
(org.apache.openjpa.jdbc.kernel.exps.IndexOf) value;
String val1 = (String) getValue(locateVal.getVal1(), ob, params, sm);
Val[] val2 = (Val[]) getValue(locateVal.getVal2(), ob, params, sm);
String strVal = (String) getValue(val2[0], ob, params, sm);
int idx = ((Long) getValue(val2[1], ob, params, sm)).intValue();
return strVal.indexOf(val1, idx);
}
private Object handleAbsVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
org.apache.openjpa.jdbc.kernel.exps.Abs absVal =
(org.apache.openjpa.jdbc.kernel.exps.Abs) value;
Object val = getValue(absVal.getValue(), ob, params, sm);
Class c = val.getClass();
if (c == Integer.class)
return Math.abs(((Integer) val).intValue());
else if (c == Float.class)
return Math.abs(((Float) val).floatValue());
else if (c == Double.class)
return Math.abs(((Double) val).doubleValue());
else if (c == Long.class)
return Math.abs(((Long) val).longValue());
throw new UnsupportedException();
}
private Object handleSqrtVal(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
org.apache.openjpa.jdbc.kernel.exps.Sqrt sqrtVal =
(org.apache.openjpa.jdbc.kernel.exps.Sqrt) value;
Object val = getValue(sqrtVal.getValue(), ob, params, sm);
Class c = val.getClass();
if (c == Integer.class)
return Math.sqrt(((Integer) val).doubleValue());
else if (c == Float.class)
return Math.sqrt(((Float) val).floatValue());
else if (c == Double.class)
return Math.sqrt(((Double) val).doubleValue());
else if (c == Long.class)
return Math.sqrt(((Long) val).doubleValue());
throw new UnsupportedException();
}
private Object getValue(Object value, Object ob, Object[] params,
OpenJPAStateManager sm) {
if (value instanceof PCPath) {
FieldMapping fm = (FieldMapping)((PCPath) value).last();
return getValue(ob, fm, sm);
} else if (value instanceof Literal) {
return ((Literal) value).getValue();
} else if (value instanceof Constant) {
return ((Constant) value).getValue(params);
} else {
return evaluate(value, ob, params, sm);
}
}
private Object getValue(Object ob, FieldMapping fmd,
OpenJPAStateManager sm) {
int i = fmd.getIndex();
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.BOOLEAN:
return sm.fetchBooleanField(i);
case JavaTypes.BYTE:
return sm.fetchByteField(i);
case JavaTypes.CHAR:
return sm.fetchCharField(i);
case JavaTypes.DOUBLE:
return sm.fetchDoubleField(i);
case JavaTypes.FLOAT:
return sm.fetchFloatField(i);
case JavaTypes.INT:
return sm.fetchIntField(i);
case JavaTypes.LONG:
return sm.fetchLongField(i);
case JavaTypes.SHORT:
return sm.fetchShortField(i);
case JavaTypes.STRING:
return sm.fetchStringField(i);
case JavaTypes.DATE:
case JavaTypes.NUMBER:
case JavaTypes.BOOLEAN_OBJ:
case JavaTypes.BYTE_OBJ:
case JavaTypes.CHAR_OBJ:
case JavaTypes.DOUBLE_OBJ:
case JavaTypes.FLOAT_OBJ:
case JavaTypes.INT_OBJ:
case JavaTypes.LONG_OBJ:
case JavaTypes.SHORT_OBJ:
case JavaTypes.BIGDECIMAL:
case JavaTypes.BIGINTEGER:
case JavaTypes.LOCALE:
case JavaTypes.OBJECT:
case JavaTypes.OID:
return sm.fetchObjectField(i);
default:
throw new UnsupportedException();
}
}
private static class ThreadLocalContext extends ThreadLocal<Context[]> {
@Override
public Context[] initialValue() {
return null;
}
}
public static Context[] getThreadLocalContext() {
return localContext.get();
}
public static Context getThreadLocalContext(Context orig) {
Context[] root = localContext.get();
for (int i = 0; i < root.length; i++) {
Context lctx = getThreadLocalContext(root[i], orig);
if (lctx != null)
return lctx;
}
return null;
}
public static Select getThreadLocalSelect(Select select) {
if (select == null)
return null;
Context[] lctx = JDBCStoreQuery.getThreadLocalContext();
Context cloneFrom = select.ctx();
for (int i = 0; i < lctx.length; i++) {
Context cloneTo = getThreadLocalContext(lctx[i], cloneFrom);
if (cloneTo != null)
return (Select)cloneTo.getSelect();
}
return select;
}
public static Context getThreadLocalContext(Context lctx, Context cloneFrom) {
if (lctx.cloneFrom == cloneFrom)
return lctx;
java.util.List<Context> subselCtxs = lctx.getSubselContexts();
if (subselCtxs != null) {
for (Context subselCtx : subselCtxs) {
Context ctx = getThreadLocalContext(subselCtx, cloneFrom);
if (ctx != null)
return ctx;
}
}
return null;
}
private static Context[] clone(Context[] orig, Context parent) {
Context[] newCtx = new Context[orig.length];
for (int i = 0; i < orig.length; i++) {
newCtx[i] = clone(orig[i], parent);
}
return newCtx;
}
private static Context clone(Context orig, Context parent) {
Context myParent = null;
if (parent == null) {
Context origParent = orig.getParent();
if (origParent != null)
myParent = clone(orig.getParent(), null);
} else
myParent = parent;
Context newCtx = new Context(orig.parsed, null, myParent);
newCtx.from = orig.from;
newCtx.meta = orig.meta;
newCtx.schemaAlias = orig.schemaAlias;
newCtx.setSchemas(orig.getSchemas());
newCtx.setVariables(orig.getVariables());
newCtx.cloneFrom = orig;
Object select = orig.getSelect();
if (select != null)
newCtx.setSelect(((SelectImpl)select).clone(newCtx));
newCtx.subquery = orig.subquery;
List<Context> subsels = orig.getSubselContexts();
if (subsels != null) {
for (Context subsel : subsels)
newCtx.addSubselContext(clone(subsel, newCtx));
}
return newCtx;
}
}