/* * Copyright 2006-2012 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 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.RepositoryException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.Transaction; import com.amazon.carbonado.Query; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.filter.FilterValues; import com.amazon.carbonado.filter.RelOp; import com.amazon.carbonado.info.Direction; 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 { // Filter for this query, which may be null. private final Filter mFilter; // Values for this query, which may be null. private final FilterValues mValues; // Properties that this query is ordered by. private final OrderingList mOrdering; // Optional query hints. private final QueryHints mHints; private volatile QueryExecutor mExecutor; /** * @param filter optional filter object, defaults to open filter if null * @param values optional values object, defaults to filter initial values * @param ordering optional order-by properties * @param hints optional query hints */ protected StandardQuery(Filter filter, FilterValues values, OrderingList ordering, QueryHints hints) { if (filter != null && filter.isOpen()) { filter = null; } if (values == null) { if (filter != null) { values = filter.initialFilterValues(); } } mFilter = filter; mValues = values; if (ordering == null) { ordering = OrderingList.emptyList(); } mOrdering = ordering; mHints = hints; } @Override public Class getStorableType() { return queryFactory().getStorableType(); } @Override public Filter getFilter() { Filter filter = mFilter; if (filter == null) { return Filter.getOpenFilter(getStorableType()); } return filter; } @Override public FilterValues getFilterValues() { return mValues; } @Override public int getBlankParameterCount() { return mValues == null ? 0 : mValues.getBlankParameterCount(); } @Override public Query with(int value) { return newInstance(requireValues().with(value)); } @Override public Query with(long value) { return newInstance(requireValues().with(value)); } @Override public Query with(float value) { return newInstance(requireValues().with(value)); } @Override public Query with(double value) { return newInstance(requireValues().with(value)); } @Override public Query with(boolean value) { return newInstance(requireValues().with(value)); } @Override public Query with(char value) { return newInstance(requireValues().with(value)); } @Override public Query with(byte value) { return newInstance(requireValues().with(value)); } @Override public Query with(short value) { return newInstance(requireValues().with(value)); } @Override public Query with(Object value) { return newInstance(requireValues().with(value)); } @Override public Query withValues(Object... values) { if (values == null || values.length == 0) { return this; } return newInstance(requireValues().withValues(values)); } @Override public Query and(Filter filter) throws FetchException { Filter newFilter; FilterValues newValues; if (mFilter == null) { newFilter = filter; newValues = filter.initialFilterValues(); } else { if (getBlankParameterCount() > 0) { throw new IllegalStateException("Blank parameters exist in query: " + this); } newFilter = mFilter.and(filter); newValues = newFilter.initialFilterValues(); if (mValues != null) { newValues = newValues.withValues(mValues.getSuppliedValues()); } } return createQuery(newFilter, newValues, mOrdering, mHints); } @Override public Query or(Filter filter) throws FetchException { if (mFilter == null) { throw new IllegalStateException("Query is already guaranteed to fetch everything"); } if (getBlankParameterCount() > 0) { throw new IllegalStateException("Blank parameters exist in query: " + this); } Filter newFilter = mFilter.or(filter); FilterValues newValues = newFilter.initialFilterValues(); if (mValues != null) { newValues = newValues.withValues(mValues.getSuppliedValues()); } return createQuery(newFilter, newValues, mOrdering, mHints); } @Override public Query not() throws FetchException { if (mFilter == null) { return new EmptyQuery(queryFactory(), mOrdering); } Filter newFilter = mFilter.not(); FilterValues newValues = newFilter.initialFilterValues(); if (mValues != null) { newValues = newValues.withValues(mValues.getSuppliedValues()); } return createQuery(newFilter, newValues, mOrdering, mHints); } @Override public Query orderBy(String property) throws FetchException { return createQuery(mFilter, mValues, OrderingList.get(getStorableType(), property), mHints); } @Override public Query orderBy(String... properties) throws FetchException { return createQuery(mFilter, mValues, OrderingList.get(getStorableType(), properties), mHints); } @Override public Query after(T start) throws FetchException { OrderingList orderings; if (start == null || (orderings = mOrdering).size() == 0) { return this; } return buildAfter(start, orderings); } private Query buildAfter(T start, OrderingList orderings) throws FetchException { Class storableType = getStorableType(); Filter orderFilter = Filter.getClosedFilter(storableType); Filter openFilter = Filter.getOpenFilter(storableType); Filter lastSubFilter = openFilter; Object[] values = new Object[orderings.size()]; for (int i=0;;) { OrderedProperty property = orderings.get(i); RelOp operator = RelOp.GT; if (property.getDirection() == Direction.DESCENDING) { operator = RelOp.LT; } String propertyName = property.getChainedProperty().toString(); values[i] = start.getPropertyValue(propertyName); orderFilter = orderFilter.or(lastSubFilter.and(propertyName, operator)); if (++i >= orderings.size()) { break; } Filter propFilter = openFilter.and(propertyName, RelOp.EQ).bind(); lastSubFilter = lastSubFilter.and(propFilter); } Query query = this.and(orderFilter); for (int i=0; i fetch() throws FetchException { try { return executor().fetch(mValues); } catch (RepositoryException e) { throw e.toFetchException(); } } @Override public Cursor fetch(Controller controller) throws FetchException { try { return executor().fetch(mValues, controller); } catch (RepositoryException e) { throw e.toFetchException(); } } @Override public Cursor fetchSlice(long from, Long to) throws FetchException { return fetchSlice(from, to, null); } @Override public Cursor fetchSlice(long from, Long to, Controller controller) throws FetchException { if (!checkSliceArguments(from, to)) { return fetch(controller); } try { QueryHints hints = QueryHints.emptyHints().with(QueryHint.CONSUME_SLICE); return executorFactory().executor(mFilter, mOrdering, hints) .fetchSlice(mValues, from, to, controller); } catch (RepositoryException e) { throw e.toFetchException(); } } @Override public boolean tryDeleteOne() throws PersistException { return tryDeleteOne(null); } @Override public boolean tryDeleteOne(Controller controller) throws PersistException { Transaction txn = enterTransaction(IsolationLevel.READ_COMMITTED); try { Cursor cursor = fetch(controller); 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(); } } } @Override public void deleteAll() throws PersistException { deleteAll(null); } @Override public void deleteAll(Controller controller) throws PersistException { Transaction txn = enterTransaction(IsolationLevel.READ_COMMITTED); try { Cursor cursor = fetch(controller); 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(); } } } @Override public long count() throws FetchException { try { return executor().count(mValues); } catch (RepositoryException e) { throw e.toFetchException(); } } @Override public long count(Controller controller) throws FetchException { try { return executor().count(mValues, controller); } catch (RepositoryException e) { throw e.toFetchException(); } } @Override public boolean exists() throws FetchException { return exists(null); } @Override public boolean exists(Controller controller) throws FetchException { Cursor cursor = fetchSlice(0L, 1L, controller); try { return cursor.skipNext(1) > 0; } finally { cursor.close(); } } @Override public boolean printNative(Appendable app, int indentLevel) throws IOException { try { return executor().printNative(app, indentLevel, mValues); } catch (RepositoryException e) { return false; } } @Override public boolean printPlan(Appendable app, int indentLevel) throws IOException { try { return executor().printPlan(app, indentLevel, mValues); } catch (RepositoryException e) { return false; } } @Override public int hashCode() { int hash = queryFactory().hashCode(); hash = hash * 31 + executorFactory().hashCode(); if (mFilter != null) { hash = hash * 31 + mFilter.hashCode(); } if (mValues != null) { hash = hash * 31 + mValues.hashCode(); } hash = hash * 31 + mOrdering.hashCode(); return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof StandardQuery) { StandardQuery other = (StandardQuery) obj; return queryFactory().equals(other.queryFactory()) && executorFactory().equals(other.executorFactory()) && (mFilter == null ? (other.mFilter == null) : (mFilter.equals(other.mFilter))) && (mValues == null ? (other.mValues == null) : (mValues.equals(other.mValues))) && mOrdering.equals(other.mOrdering); } return false; } @Override public void appendTo(Appendable app) throws IOException { app.append("Query {type="); app.append(getStorableType().getName()); app.append(", filter="); Filter filter = getFilter(); if (filter.isOpen() || filter.isClosed()) { filter.appendTo(app); } else { app.append('"'); filter.appendTo(app, mValues); app.append('"'); } if (mOrdering != null && mOrdering.size() > 0) { app.append(", orderBy=["); for (int i=0; i 0) { app.append(", "); } app.append(mOrdering.get(i).toString()); } app.append(']'); } app.append('}'); } private FilterValues requireValues() { FilterValues values = mValues; if (values == null) { throw new IllegalStateException("Query doesn't have any parameters"); } return values; } protected OrderingList getOrdering() { return mOrdering; } /** * Returns the executor in use by this query. */ protected QueryExecutor executor() throws RepositoryException { QueryExecutor executor = mExecutor; if (executor == null) { mExecutor = executor = executorFactory().executor(mFilter, mOrdering, null); } return executor; } /** * Ensures that a cached query executor reference is available. If not, the * query executor factory is called and the executor is cached. */ protected void setExecutor() throws RepositoryException { executor(); } /** * Resets any cached reference to a query executor. If a reference is * available, it is replaced, but a clear reference is not set. */ protected void resetExecutor() throws RepositoryException { if (mExecutor != null) { mExecutor = executorFactory().executor(mFilter, mOrdering, null); } } /** * Clears any cached reference to a query executor. The next time this * Query is used, it will get an executor from the query executor factory * and cache a reference to it. */ protected void clearExecutor() { mExecutor = null; } /** * 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 enterTransaction(IsolationLevel level); /** * Return a QueryFactory which is used to form new queries from this one. */ protected abstract QueryFactory queryFactory(); /** * Return a QueryExecutorFactory which is used to get an executor. */ protected abstract QueryExecutorFactory executorFactory(); /** * 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 non-null values object * @param ordering order-by properties, never null */ protected abstract StandardQuery newInstance(FilterValues values, OrderingList ordering, QueryHints hints); private StandardQuery newInstance(FilterValues values) { StandardQuery query = newInstance(values, mOrdering, mHints); query.mExecutor = this.mExecutor; return query; } private Query createQuery(Filter filter, FilterValues values, OrderingList ordering, QueryHints hints) throws FetchException { return queryFactory().query(filter, values, ordering, hints); } }