From 39fce59a840b723eb013bc79285687986592b2da Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 3 Sep 2006 06:14:41 +0000 Subject: More work on query engine. --- .../com/amazon/carbonado/qe/StandardQuery.java | 404 +++++++++++++++++++++ 1 file changed, 404 insertions(+) create mode 100644 src/main/java/com/amazon/carbonado/qe/StandardQuery.java (limited to 'src/main/java/com/amazon/carbonado/qe/StandardQuery.java') diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java new file mode 100644 index 0000000..7152e55 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java @@ -0,0 +1,404 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * 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 com.amazon.carbonado.qe; + +import java.io.IOException; + +import org.cojen.util.BeanPropertyAccessor; + +import com.amazon.carbonado.Cursor; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.PersistMultipleException; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Transaction; +import com.amazon.carbonado.Query; + +import com.amazon.carbonado.filter.ClosedFilter; +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; +import com.amazon.carbonado.filter.OpenFilter; +import com.amazon.carbonado.filter.RelOp; + +import com.amazon.carbonado.info.OrderedProperty; + +import com.amazon.carbonado.util.Appender; + +/** + * Abstract query implementation which uses a {@link QueryExecutor}. + * + * @author Brian S O'Neill + */ +public abstract class StandardQuery extends AbstractQuery + implements Appender +{ + // Values for this query, which may be null. + private final FilterValues mValues; + // Properties that this query is ordered by. + private final String[] mOrderings; + + private volatile QueryExecutor mExecutor; + + /** + * @param values optional values object, defaults to open filter if null + * @param orderings optional order-by properties + */ + // FIXME: remove this + protected StandardQuery(FilterValues values, OrderedProperty... orderings) { + this(values, extractOrderingNames(orderings)); + } + + /** + * @param values optional values object, defaults to open filter if null + * @param orderings optional order-by properties, not cloned, which may be + * prefixed with '+' or '-' + */ + protected StandardQuery(FilterValues values, String... orderings) { + mValues = values; + mOrderings = orderings == null ? EMPTY_ORDERINGS : orderings; + } + + public Class getStorableType() { + return getStorage().getStorableType(); + } + + public Filter getFilter() { + FilterValues values = mValues; + if (values != null) { + return values.getFilter(); + } + return Filter.getOpenFilter(getStorage().getStorableType()); + } + + public FilterValues getFilterValues() { + return mValues; + } + + public int getBlankParameterCount() { + return mValues == null ? 0 : mValues.getBlankParameterCount(); + } + + public Query with(int value) { + return newInstance(requireValues().with(value)); + } + + public Query with(long value) { + return newInstance(requireValues().with(value)); + } + + public Query with(float value) { + return newInstance(requireValues().with(value)); + } + + public Query with(double value) { + return newInstance(requireValues().with(value)); + } + + public Query with(boolean value) { + return newInstance(requireValues().with(value)); + } + + public Query with(char value) { + return newInstance(requireValues().with(value)); + } + + public Query with(byte value) { + return newInstance(requireValues().with(value)); + } + + public Query with(short value) { + return newInstance(requireValues().with(value)); + } + + public Query with(Object value) { + return newInstance(requireValues().with(value)); + } + + public Query withValues(Object... values) { + if (values == null || values.length == 0) { + return this; + } + return newInstance(requireValues().withValues(values)); + } + + public Query and(Filter filter) throws FetchException { + FilterValues values = mValues; + Query newQuery; + if (values == null) { + newQuery = getStorage().query(filter); + } else { + newQuery = getStorage().query(values.getFilter().and(filter)); + newQuery = newQuery.withValues(values.getValues()); + } + return mOrderings.length == 0 ? newQuery : newQuery.orderBy(mOrderings); + } + + public Query or(Filter filter) throws FetchException { + FilterValues values = mValues; + if (values == null) { + throw new IllegalStateException("Query is already guaranteed to fetch everything"); + } + Query newQuery = getStorage().query(values.getFilter().or(filter)); + newQuery = newQuery.withValues(values.getValues()); + return mOrderings.length == 0 ? newQuery : newQuery.orderBy(mOrderings); + } + + public Query not() throws FetchException { + FilterValues values = mValues; + if (values == null) { + return new EmptyQuery(getStorage(), mOrderings); + } + Query newQuery = getStorage().query(values.getFilter().not()); + newQuery = newQuery.withValues(values.getSuppliedValues()); + return mOrderings.length == 0 ? newQuery : newQuery.orderBy(mOrderings); + } + + public Query orderBy(String property) throws FetchException { + StandardQuery query = newInstance(mValues, property); + // Get executor to ensure order property is correct. + query.executor(); + return query; + } + + public Query orderBy(String... properties) throws FetchException { + StandardQuery query = newInstance(mValues, properties); + // Get executor to ensure order properties are correct. + query.executor(); + return query; + } + + public Cursor fetch() throws FetchException { + return executor().fetch(mValues); + } + + public Cursor fetchAfter(S start) throws FetchException { + String[] orderings; + if (start == null || (orderings = mOrderings).length == 0) { + return fetch(); + } + + Class storableType = getStorage().getStorableType(); + Filter orderFilter = Filter.getClosedFilter(storableType); + Filter lastSubFilter = Filter.getOpenFilter(storableType); + BeanPropertyAccessor accessor = BeanPropertyAccessor.forClass(storableType); + + Object[] values = new Object[orderings.length]; + + for (int i=0;;) { + String propertyName = orderings[i]; + RelOp operator = RelOp.GT; + char c = propertyName.charAt(0); + if (c == '-') { + propertyName = propertyName.substring(1); + operator = RelOp.LT; + } else if (c == '+') { + propertyName = propertyName.substring(1); + } + + values[i] = accessor.getPropertyValue(start, propertyName); + + orderFilter = orderFilter.or(lastSubFilter.and(propertyName, operator)); + + if (++i >= orderings.length) { + break; + } + + lastSubFilter = lastSubFilter.and(propertyName, RelOp.EQ); + } + + Query newQuery = this.and(orderFilter); + + for (int i=0; i cursor = fetch(); + boolean result; + try { + if (cursor.hasNext()) { + S obj = cursor.next(); + if (cursor.hasNext()) { + throw new PersistMultipleException(toString()); + } + result = obj.tryDelete(); + } else { + return false; + } + } finally { + cursor.close(); + } + if (txn != null) { + txn.commit(); + } + return result; + } catch (FetchException e) { + throw e.toPersistException(); + } finally { + if (txn != null) { + txn.exit(); + } + } + } + + public void deleteAll() throws PersistException { + Transaction txn = enterTransactionForDelete(IsolationLevel.READ_COMMITTED); + try { + Cursor cursor = fetch(); + try { + while (cursor.hasNext()) { + cursor.next().tryDelete(); + } + } finally { + cursor.close(); + } + if (txn != null) { + txn.commit(); + } + } catch (FetchException e) { + throw e.toPersistException(); + } finally { + if (txn != null) { + txn.exit(); + } + } + } + + public long count() throws FetchException { + return executor().count(mValues); + } + + public boolean printNative(Appendable app, int indentLevel) throws IOException { + return executor().printNative(app, indentLevel, mValues); + } + + public boolean printPlan(Appendable app, int indentLevel) throws IOException { + return executor().printPlan(app, indentLevel, mValues); + } + + @Override + public int hashCode() { + int hash = getStorage().hashCode() * 31; + if (mValues != null) { + hash += mValues.hashCode(); + } + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof StandardQuery) { + StandardQuery other = (StandardQuery) obj; + return getStorage().equals(other.getStorage()) && + (mValues == null ? (other.mValues == null) : (mValues.equals(other.mValues))); + } + return false; + } + + public void appendTo(Appendable app) throws IOException { + app.append("Query {type="); + app.append(getStorableType().getName()); + app.append(", filter="); + Filter filter = getFilter(); + if (filter instanceof OpenFilter || filter instanceof ClosedFilter) { + filter.appendTo(app); + } else { + app.append('"'); + filter.appendTo(app, mValues); + app.append('"'); + } + + if (mOrderings != null && mOrderings.length > 0) { + app.append(", orderBy=["); + for (int i=0; i 0) { + app.append(", "); + } + app.append(mOrderings[i]); + } + app.append(']'); + } + + app.append('}'); + } + + private FilterValues requireValues() { + FilterValues values = mValues; + if (values == null) { + throw new IllegalStateException("Query doesn't have any parameters"); + } + return values; + } + + /** + * Return the Storage object that the query is operating on. + */ + protected abstract Storage getStorage(); + + /** + * Enter a transaction as needed by the standard delete operation, or null + * if transactions are not supported. + * + * @param level minimum desired isolation level + */ + protected abstract Transaction enterTransactionForDelete(IsolationLevel level); + + /** + * Return a new or cached executor. + * + * @param values optional values object, defaults to open filter if null + * @param orderings optional order-by properties, which may be prefixed + * with '+' or '-' + */ + protected abstract QueryExecutor getExecutor(FilterValues values, String... orderings); + + /** + * Return a new or cached instance of StandardQuery implementation, using + * new filter values. The Filter in the FilterValues is the same as was + * passed in the constructor. + * + * @param values optional values object, defaults to open filter if null + * @param orderings order-by properties, never null + */ + protected abstract StandardQuery newInstance(FilterValues values, String... orderings); + + private StandardQuery newInstance(FilterValues values) { + return newInstance(values, mOrderings); + } + + private QueryExecutor executor() { + QueryExecutor executor = mExecutor; + if (executor == null) { + mExecutor = executor = getExecutor(mValues, mOrderings); + } + return executor; + } +} -- cgit v1.2.3