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. --- .../amazon/carbonado/cursor/IteratorCursor.java | 36 +- .../java/com/amazon/carbonado/filter/Filter.java | 2 +- .../com/amazon/carbonado/qe/AbstractQuery.java | 4 +- .../amazon/carbonado/qe/AbstractQueryExecutor.java | 2 +- .../carbonado/qe/DelegatedQueryExecutor.java | 2 +- .../java/com/amazon/carbonado/qe/EmptyQuery.java | 1 + .../amazon/carbonado/qe/FilteredQueryExecutor.java | 4 +- .../carbonado/qe/FullScanIndexedQueryExecutor.java | 10 +- .../amazon/carbonado/qe/FullScanQueryExecutor.java | 6 +- .../amazon/carbonado/qe/IndexedQueryExecutor.java | 28 +- .../amazon/carbonado/qe/IterableQueryExecutor.java | 18 +- .../amazon/carbonado/qe/JoinedQueryExecutor.java | 4 +- .../com/amazon/carbonado/qe/KeyQueryExecutor.java | 6 +- .../com/amazon/carbonado/qe/QueryExecutor.java | 2 +- .../amazon/carbonado/qe/SortedQueryExecutor.java | 4 +- .../com/amazon/carbonado/qe/StandardQuery.java | 404 +++++++++++++++++++++ .../amazon/carbonado/qe/UnionQueryExecutor.java | 4 +- .../amazon/carbonado/repo/jdbc/JDBCStorage.java | 4 - .../carbonado/spi/MasterStorableGenerator.java | 2 +- .../amazon/carbonado/spi/StorableGenerator.java | 2 +- .../carbonado/qe/TestFilteredQueryExecutor.java | 2 +- .../carbonado/qe/TestIndexedQueryAnalyzer.java | 259 +++++++++++++ .../carbonado/qe/TestIndexedQueryExecutor.java | 76 ++-- .../carbonado/qe/TestJoinedQueryExecutor.java | 203 +++++++++++ .../carbonado/qe/TestSortedQueryExecutor.java | 4 +- .../carbonado/qe/TestUnionQueryExecutor.java | 2 +- .../amazon/carbonado/repo/toy/ToyRepository.java | 106 ++++++ .../carbonado/repo/toy/ToyStorableGenerator.java | 143 ++++++++ .../com/amazon/carbonado/repo/toy/ToyStorage.java | 242 ++++++++++++ .../amazon/carbonado/repo/toy/ToyTransaction.java | 51 +++ .../amazon/carbonado/repo/toy/package-info.java | 25 ++ .../java/com/amazon/carbonado/stored/Order.java | 68 ++++ .../com/amazon/carbonado/stored/OrderItem.java | 68 ++++ .../com/amazon/carbonado/stored/Promotion.java | 55 +++ .../java/com/amazon/carbonado/stored/Shipment.java | 69 ++++ .../java/com/amazon/carbonado/stored/Shipper.java | 52 +++ .../com/amazon/carbonado/stored/UserAddress.java | 66 ++++ .../java/com/amazon/carbonado/stored/UserInfo.java | 57 +++ 38 files changed, 2002 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/qe/StandardQuery.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/repo/toy/ToyRepository.java create mode 100644 src/test/java/com/amazon/carbonado/repo/toy/ToyStorableGenerator.java create mode 100644 src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java create mode 100644 src/test/java/com/amazon/carbonado/repo/toy/ToyTransaction.java create mode 100644 src/test/java/com/amazon/carbonado/repo/toy/package-info.java create mode 100644 src/test/java/com/amazon/carbonado/stored/Order.java create mode 100644 src/test/java/com/amazon/carbonado/stored/OrderItem.java create mode 100644 src/test/java/com/amazon/carbonado/stored/Promotion.java create mode 100644 src/test/java/com/amazon/carbonado/stored/Shipment.java create mode 100644 src/test/java/com/amazon/carbonado/stored/Shipper.java create mode 100644 src/test/java/com/amazon/carbonado/stored/UserAddress.java create mode 100644 src/test/java/com/amazon/carbonado/stored/UserInfo.java diff --git a/src/main/java/com/amazon/carbonado/cursor/IteratorCursor.java b/src/main/java/com/amazon/carbonado/cursor/IteratorCursor.java index 0330629..a32b45f 100644 --- a/src/main/java/com/amazon/carbonado/cursor/IteratorCursor.java +++ b/src/main/java/com/amazon/carbonado/cursor/IteratorCursor.java @@ -21,19 +21,44 @@ package com.amazon.carbonado.cursor; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.concurrent.locks.Lock; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + /** * Adapts an Iterator into a Cursor. * * @author Brian S O'Neill */ public class IteratorCursor extends AbstractCursor { - private Iterator mIterator; + private static final AtomicReferenceFieldUpdater lockUpdater = + AtomicReferenceFieldUpdater.newUpdater(IteratorCursor.class, Lock.class, "mLock"); + + private volatile Iterator mIterator; + private volatile Lock mLock; /** * @param iterable collection to iterate over, or null for empty cursor */ public IteratorCursor(Iterable iterable) { - this(iterable == null ? (Iterator) null : iterable.iterator()); + this(iterable, null); + } + + /** + * @param iterable collection to iterate over, or null for empty cursor + * @param lock optional lock to hold while cursor is open + */ + public IteratorCursor(Iterable iterable, Lock lock) { + if (iterable == null) { + mIterator = null; + mLock = null; + } else { + if (lock != null) { + lock.lock(); + } + mIterator = iterable.iterator(); + mLock = lock; + } } /** @@ -41,10 +66,17 @@ public class IteratorCursor extends AbstractCursor { */ public IteratorCursor(Iterator iterator) { mIterator = iterator; + mLock = null; } public void close() { mIterator = null; + // Use AtomicReferenceFieldUpdater to allow close method to be safely + // called multiple times without unlocking multiple times. + Lock lock = lockUpdater.getAndSet(this, null); + if (lock != null) { + lock.unlock(); + } } public boolean hasNext() { diff --git a/src/main/java/com/amazon/carbonado/filter/Filter.java b/src/main/java/com/amazon/carbonado/filter/Filter.java index f688d60..9d55a99 100644 --- a/src/main/java/com/amazon/carbonado/filter/Filter.java +++ b/src/main/java/com/amazon/carbonado/filter/Filter.java @@ -132,7 +132,7 @@ public abstract class Filter implements Appender { synchronized (cCache) { Map> filterCache = (Map>) cCache.get(type); if (filterCache == null) { - filterCache = new SoftValuedHashMap(7); + filterCache = new SoftValuedHashMap(); cCache.put(type, filterCache); } return filterCache; diff --git a/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java b/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java index 4549151..eb2e977 100644 --- a/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java +++ b/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java @@ -44,8 +44,10 @@ import com.amazon.carbonado.util.Appender; * @author Brian S O'Neill */ public abstract class AbstractQuery implements Query, Appender { + // FIXME: remove this protected static final String[] EMPTY_ORDERINGS = {}; + // FIXME: remove this protected static String[] extractOrderingNames(OrderedProperty[] orderings) { String[] orderingStrings; if (orderings == null || orderings.length == 0) { @@ -58,8 +60,6 @@ public abstract class AbstractQuery implements Query, App return orderingStrings; } - // Note: Since constructor takes no parameters, this class is called - // Abstract instead of Base. protected AbstractQuery() { } diff --git a/src/main/java/com/amazon/carbonado/qe/AbstractQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/AbstractQueryExecutor.java index 01b4c57..dc67fc1 100644 --- a/src/main/java/com/amazon/carbonado/qe/AbstractQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/AbstractQueryExecutor.java @@ -40,7 +40,7 @@ public abstract class AbstractQueryExecutor implements Query * Counts results by opening a cursor and skipping entries. */ public long count(FilterValues values) throws FetchException { - Cursor cursor = openCursor(values); + Cursor cursor = fetch(values); try { long count = cursor.skipNext(Integer.MAX_VALUE); if (count == Integer.MAX_VALUE) { diff --git a/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutor.java index 8bde825..9c950e0 100644 --- a/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutor.java @@ -83,7 +83,7 @@ public class DelegatedQueryExecutor implements QueryExecutor return mStorage.getStorableType(); } - public Cursor openCursor(FilterValues values) throws FetchException { + public Cursor fetch(FilterValues values) throws FetchException { return applyFilterValues(values).fetch(); } diff --git a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java index 60b027f..ba18531 100644 --- a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java +++ b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java @@ -50,6 +50,7 @@ public final class EmptyQuery extends AbstractQuery { * @param storage required storage object * @param orderings optional order-by properties */ + // FIXME: remove this public EmptyQuery(Storage storage, OrderedProperty[] orderings) { if (storage == null) { throw new IllegalArgumentException(); diff --git a/src/main/java/com/amazon/carbonado/qe/FilteredQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/FilteredQueryExecutor.java index 4da38d9..d2c45aa 100644 --- a/src/main/java/com/amazon/carbonado/qe/FilteredQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/FilteredQueryExecutor.java @@ -62,8 +62,8 @@ public class FilteredQueryExecutor extends AbstractQueryExec mFilter = filter.initialFilterValues().getFilter(); } - public Cursor openCursor(FilterValues values) throws FetchException { - return FilteredCursor.applyFilter(mFilter, values, mExecutor.openCursor(values)); + public Cursor fetch(FilterValues values) throws FetchException { + return FilteredCursor.applyFilter(mFilter, values, mExecutor.fetch(values)); } /** diff --git a/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java index 33cd1c2..3ac0169 100644 --- a/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java @@ -62,8 +62,8 @@ public abstract class FullScanIndexedQueryExecutor } @Override - public Cursor openCursor(FilterValues values) throws FetchException { - return openCursor(mIndex); + public Cursor fetch(FilterValues values) throws FetchException { + return fetch(mIndex); } /** @@ -74,8 +74,8 @@ public abstract class FullScanIndexedQueryExecutor return Collections.unmodifiableList(Arrays.asList(mIndex.getOrderedProperties())); } - protected Cursor openCursor() throws FetchException { - return openCursor(mIndex); + protected Cursor fetch() throws FetchException { + return fetch(mIndex); } /** @@ -83,5 +83,5 @@ public abstract class FullScanIndexedQueryExecutor * * @param index index to open, which may be a primary key index */ - protected abstract Cursor openCursor(StorableIndex index) throws FetchException; + protected abstract Cursor fetch(StorableIndex index) throws FetchException; } diff --git a/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java index e5072ce..1e2ad16 100644 --- a/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java @@ -58,8 +58,8 @@ public abstract class FullScanQueryExecutor extends Abstract return Filter.getOpenFilter(mType); } - public Cursor openCursor(FilterValues values) throws FetchException { - return openCursor(); + public Cursor fetch(FilterValues values) throws FetchException { + return fetch(); } /** @@ -82,5 +82,5 @@ public abstract class FullScanQueryExecutor extends Abstract /** * Return a new Cursor instance. */ - protected abstract Cursor openCursor() throws FetchException; + protected abstract Cursor fetch() throws FetchException; } diff --git a/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java index 7249164..28bd7e4 100644 --- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java @@ -90,7 +90,7 @@ public abstract class IndexedQueryExecutor extends AbstractQ return mIndex.getStorableType(); } - public Cursor openCursor(FilterValues values) throws FetchException { + public Cursor fetch(FilterValues values) throws FetchException { Object[] identityValues = null; Object rangeStartValue = null; Object rangeEndValue = null; @@ -147,11 +147,11 @@ public abstract class IndexedQueryExecutor extends AbstractQ } } - return openCursor(mIndex, identityValues, - rangeStartBoundary, rangeStartValue, - rangeEndBoundary, rangeEndValue, - mReverseRange, - mReverseOrder); + return fetch(mIndex, identityValues, + rangeStartBoundary, rangeStartValue, + rangeEndBoundary, rangeEndValue, + mReverseRange, + mReverseOrder); } public Filter getFilter() { @@ -254,13 +254,13 @@ public abstract class IndexedQueryExecutor extends AbstractQ * @param reverseOrder when true, iteration should be reversed from its * natural order */ - protected abstract Cursor openCursor(StorableIndex index, - Object[] identityValues, - BoundaryType rangeStartBoundary, - Object rangeStartValue, - BoundaryType rangeEndBoundary, - Object rangeEndValue, - boolean reverseRange, - boolean reverseOrder) + protected abstract Cursor fetch(StorableIndex index, + Object[] identityValues, + BoundaryType rangeStartBoundary, + Object rangeStartValue, + BoundaryType rangeEndBoundary, + Object rangeEndValue, + boolean reverseRange, + boolean reverseOrder) throws FetchException; } diff --git a/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java index 7788fba..b80066e 100644 --- a/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java @@ -18,6 +18,8 @@ package com.amazon.carbonado.qe; +import java.util.concurrent.locks.Lock; + import com.amazon.carbonado.Cursor; import com.amazon.carbonado.Storable; @@ -31,6 +33,7 @@ import com.amazon.carbonado.cursor.IteratorCursor; */ public class IterableQueryExecutor extends FullScanQueryExecutor { private final Iterable mIterable; + private final Lock mLock; /** * @param type type of Storable @@ -38,11 +41,22 @@ public class IterableQueryExecutor extends FullScanQueryExec * @throws IllegalArgumentException if type is null */ public IterableQueryExecutor(Class type, Iterable iterable) { + this(type, iterable, null); + } + + /** + * @param type type of Storable + * @param iterable collection to iterate over, or null for empty cursor + * @param lock optional lock to hold while cursor is open + * @throws IllegalArgumentException if type is null + */ + public IterableQueryExecutor(Class type, Iterable iterable, Lock lock) { super(type); mIterable = iterable; + mLock = lock; } - protected Cursor openCursor() { - return new IteratorCursor(mIterable); + protected Cursor fetch() { + return new IteratorCursor(mIterable, mLock); } } diff --git a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java index 4d8f48d..b5dc0c9 100644 --- a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java @@ -141,8 +141,8 @@ public class JoinedQueryExecutor return mBFilter; } - public Cursor openCursor(FilterValues values) throws FetchException { - return mFactory.join(mAExecutor.openCursor(transferValues(values))); + public Cursor fetch(FilterValues values) throws FetchException { + return mFactory.join(mAExecutor.fetch(transferValues(values))); } public List> getOrdering() { diff --git a/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java index e2d692d..fe07ed6 100644 --- a/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java @@ -67,8 +67,8 @@ public abstract class KeyQueryExecutor extends AbstractQuery return mIndex.getStorableType(); } - public Cursor openCursor(FilterValues values) throws FetchException { - return openCursor(mIndex, values.getValuesFor(mKeyFilter)); + public Cursor fetch(FilterValues values) throws FetchException { + return fetch(mIndex, values.getValuesFor(mKeyFilter)); } public Filter getFilter() { @@ -106,6 +106,6 @@ public abstract class KeyQueryExecutor extends AbstractQuery * @param index index to open, which may be a primary key index * @param keyValues list of exactly matching values to apply to index */ - protected abstract Cursor openCursor(StorableIndex index, Object[] keyValues) + protected abstract Cursor fetch(StorableIndex index, Object[] keyValues) throws FetchException; } diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java index 6861b2c..024c31c 100644 --- a/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java @@ -46,7 +46,7 @@ public interface QueryExecutor { /** * Returns a new cursor using the given filter values. */ - Cursor openCursor(FilterValues values) throws FetchException; + Cursor fetch(FilterValues values) throws FetchException; /** * Counts the query results using the given filter values. diff --git a/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java index 5c5258a..822cde8 100644 --- a/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java @@ -89,8 +89,8 @@ public abstract class SortedQueryExecutor extends AbstractQu mRemainderOrderings = remainderOrderings; } - public Cursor openCursor(FilterValues values) throws FetchException { - Cursor cursor = mExecutor.openCursor(values); + public Cursor fetch(FilterValues values) throws FetchException { + Cursor cursor = mExecutor.fetch(values); SortBuffer buffer = createSortBuffer(); return new SortedCursor(cursor, buffer, mHandledComparator, mFinisherComparator); } 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; + } +} diff --git a/src/main/java/com/amazon/carbonado/qe/UnionQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/UnionQueryExecutor.java index cc3d366..39e6157 100644 --- a/src/main/java/com/amazon/carbonado/qe/UnionQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/UnionQueryExecutor.java @@ -83,10 +83,10 @@ public class UnionQueryExecutor extends AbstractQueryExecuto mOrderComparator = SortedCursor.createComparator(totalOrderings); } - public Cursor openCursor(FilterValues values) throws FetchException { + public Cursor fetch(FilterValues values) throws FetchException { Cursor cursor = null; for (QueryExecutor executor : mExecutors) { - Cursor subCursor = executor.openCursor(values); + Cursor subCursor = executor.fetch(values); cursor = (cursor == null) ? subCursor : new UnionCursor(cursor, subCursor, mOrderComparator); } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java index 99c4bea..d5b2187 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java @@ -641,10 +641,6 @@ class JDBCStorage extends BaseQueryCompiler protected BaseQuery newInstance(FilterValues values) { return new JDBCQuery(mCursorFactory, values, getOrderings()); } - - protected BaseQuery cachedInstance(Filter filter) throws FetchException { - return (BaseQuery) JDBCStorage.this.getCompiledQuery(filter); - } } /** diff --git a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java index 5ada2bd..317aac6 100644 --- a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java @@ -107,7 +107,7 @@ public final class MasterStorableGenerator { * * * Subclasses can access the MasterSupport instance via the protected field - * named by MASTER_SUPPORT_FIELD_NAME. + * named by {@link StorableGenerator#SUPPORT_FIELD_NAME SUPPORT_FIELD_NAME}. * * @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed * @throws IllegalArgumentException if type is null diff --git a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java index fd41677..4972bfa 100644 --- a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java @@ -144,7 +144,7 @@ public final class StorableGenerator { /** Constraint field names are propertyName + "$constraint$" + ordinal */ public static final String CONSTRAINT_FIELD_ELEMENT = "$constraint$"; - /** Reference to Support class */ + /** Reference to TriggerSupport or WrappedSupport instance */ public static final String SUPPORT_FIELD_NAME = "support$"; /** Property state indicating that property has never been set, loaded, or saved */ diff --git a/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java index 76ea081..1210bd4 100644 --- a/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java +++ b/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java @@ -52,6 +52,6 @@ public class TestFilteredQueryExecutor extends TestQueryExecutor { assertEquals(0, executor.getOrdering().size()); - compareElements(executor.openCursor(values.with("country_2")), 3, 4); + compareElements(executor.fetch(values.with("country_2")), 3, 4); } } diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java new file mode 100644 index 0000000..85c78cb --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java @@ -0,0 +1,259 @@ +/* + * 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.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.Storable; + +import com.amazon.carbonado.info.StorableIndex; + +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; + +import com.amazon.carbonado.repo.toy.ToyRepository; + +import com.amazon.carbonado.stored.Address; +import com.amazon.carbonado.stored.Order; +import com.amazon.carbonado.stored.Shipment; +import com.amazon.carbonado.stored.Shipper; + +import static com.amazon.carbonado.qe.TestIndexedQueryExecutor.Mock; + +/** + * + * + * @author Brian S O'Neill + */ +public class TestIndexedQueryAnalyzer extends TestCase { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestIndexedQueryAnalyzer.class); + } + + static StorableIndex makeIndex(Class type, String... props) { + return TestOrderingScore.makeIndex(type, props); + } + + public TestIndexedQueryAnalyzer(String name) { + super(name); + } + + // Note: these tests don't perform exhaustive tests to find the best index, as those tests + // are performed by TestFilteringScore and TestOrderingScore. + + public void testFullScan() throws Exception { + IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Address.class, IxProvider.INSTANCE); + Filter
filter = Filter.filterFor(Address.class, "addressZip = ?"); + filter = filter.bind(); + IndexedQueryAnalyzer.Result result = iqa.analyze(filter); + + assertFalse(result.handlesAnything()); + assertEquals(filter, result.getCompositeScore().getFilteringScore().getRemainderFilter()); + assertEquals(makeIndex(Address.class, "addressID"), result.getLocalIndex()); + assertEquals(null, result.getForeignIndex()); + assertEquals(null, result.getForeignProperty()); + } + + public void testIndexScan() throws Exception { + IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Address.class, IxProvider.INSTANCE); + Filter
filter = Filter.filterFor(Address.class, "addressID = ?"); + filter = filter.bind(); + IndexedQueryAnalyzer.Result result = iqa.analyze(filter); + + assertTrue(result.handlesAnything()); + assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter()); + assertEquals(makeIndex(Address.class, "addressID"), result.getLocalIndex()); + assertEquals(null, result.getForeignIndex()); + assertEquals(null, result.getForeignProperty()); + } + + public void testBasic() throws Exception { + IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE); + Filter filter = Filter.filterFor(Shipment.class, "shipmentID = ?"); + filter = filter.bind(); + IndexedQueryAnalyzer.Result result = iqa.analyze(filter); + + assertTrue(result.handlesAnything()); + assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter()); + assertEquals(makeIndex(Shipment.class, "shipmentID"), result.getLocalIndex()); + assertEquals(null, result.getForeignIndex()); + assertEquals(null, result.getForeignProperty()); + + filter = Filter.filterFor(Shipment.class, "orderID = ?"); + filter = filter.bind(); + result = iqa.analyze(filter); + + assertTrue(result.handlesAnything()); + assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter()); + assertEquals(makeIndex(Shipment.class, "orderID"), result.getLocalIndex()); + assertEquals(null, result.getForeignIndex()); + assertEquals(null, result.getForeignProperty()); + } + + public void testSimpleJoin() throws Exception { + IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE); + Filter filter = Filter.filterFor(Shipment.class, "order.orderTotal >= ?"); + filter = filter.bind(); + IndexedQueryAnalyzer.Result result = iqa.analyze(filter); + + assertTrue(result.handlesAnything()); + assertTrue(result.getCompositeScore().getFilteringScore().hasRangeStart()); + assertEquals(null, result.getLocalIndex()); + assertEquals(makeIndex(Order.class, "orderTotal"), result.getForeignIndex()); + assertEquals("order", result.getForeignProperty().toString()); + } + + public void testJoinPriority() throws Exception { + // Selects foreign index because filter score is better. + + IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE); + Filter filter = Filter.filterFor + (Shipment.class, "shipmentNotes = ? & order.orderTotal >= ?"); + filter = filter.bind(); + IndexedQueryAnalyzer.Result result = iqa.analyze(filter); + + assertTrue(result.handlesAnything()); + assertTrue(result.getCompositeScore().getFilteringScore().hasRangeStart()); + assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ?").bind(), + result.getCompositeScore().getFilteringScore().getRemainderFilter()); + assertEquals(null, result.getLocalIndex()); + assertEquals(makeIndex(Order.class, "orderTotal"), result.getForeignIndex()); + assertEquals("order", result.getForeignProperty().toString()); + } + + public void testJoinNonPriority() throws Exception { + // Selects local index because filter score is just as good and local + // indexes are preferred. + + IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE); + Filter filter = Filter.filterFor + (Shipment.class, "orderID >= ? & order.orderTotal >= ?"); + filter = filter.bind(); + IndexedQueryAnalyzer.Result result = iqa.analyze(filter); + + assertTrue(result.handlesAnything()); + assertTrue(result.getCompositeScore().getFilteringScore().hasRangeStart()); + assertEquals(Filter.filterFor(Shipment.class, "order.orderTotal >= ?").bind(), + result.getCompositeScore().getFilteringScore().getRemainderFilter()); + assertEquals(makeIndex(Shipment.class, "orderID"), result.getLocalIndex()); + assertEquals(null, result.getForeignIndex()); + assertEquals(null, result.getForeignProperty()); + } + + public void testChainedJoin() throws Exception { + IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE); + Filter filter = Filter.filterFor + (Shipment.class, "order.address.addressState = ?"); + filter = filter.bind(); + IndexedQueryAnalyzer.Result result = iqa.analyze(filter); + + assertTrue(result.handlesAnything()); + assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter()); + assertEquals(null, result.getLocalIndex()); + assertEquals(makeIndex(Address.class, "addressState"), result.getForeignIndex()); + assertEquals("order.address", result.getForeignProperty().toString()); + } + + public void testChainedJoinExecutor() throws Exception { + Repository repo = new ToyRepository(); + + IndexedQueryAnalyzer iqa = + new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE); + Filter filter = Filter.filterFor + (Shipment.class, "order.address.addressState = ? & order.address.addressZip = ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + IndexedQueryAnalyzer.Result result = iqa.analyze(filter); + + assertTrue(result.handlesAnything()); + assertEquals(Filter.filterFor(Shipment.class, "order.address.addressState = ?").bind(), + result.getCompositeScore().getFilteringScore().getIdentityFilter()); + assertEquals(Filter.filterFor(Shipment.class, "order.address.addressZip = ?").bind(), + result.getCompositeScore().getFilteringScore().getRemainderFilter()); + assertEquals(null, result.getLocalIndex()); + assertEquals(makeIndex(Address.class, "addressState"), result.getForeignIndex()); + assertEquals("order.address", result.getForeignProperty().toString()); + + Mock ixExec = new Mock(result.getForeignIndex(), result.getCompositeScore()); + + QueryExecutor joinExec = new JoinedQueryExecutor + (repo, result.getForeignProperty(), ixExec); + + QueryExecutor filteredExec = new FilteredQueryExecutor + (joinExec, result.getCompositeScore().getFilteringScore().getRemainderFilter()); + + //System.out.println(); + //filteredExec.printPlan(System.out, 0, null); + + joinExec.fetch(values.with("WA")); + + assertEquals(1, ixExec.mIdentityValues.length); + assertEquals("WA", ixExec.mIdentityValues[0]); + assertEquals(BoundaryType.OPEN, ixExec.mRangeStartBoundary); + assertEquals(null, ixExec.mRangeStartValue); + assertEquals(BoundaryType.OPEN, ixExec.mRangeEndBoundary); + assertEquals(null, ixExec.mRangeEndValue); + assertFalse(ixExec.mReverseRange); + assertFalse(ixExec.mReverseOrder); + } + + static class IxProvider implements IndexProvider { + static final IxProvider INSTANCE = new IxProvider(); + + public Collection> indexesFor(Class type) { + StorableIndex[] indexes; + + if (Address.class.isAssignableFrom(type)) { + indexes = new StorableIndex[] { + makeIndex(type, "addressID"), + makeIndex(type, "addressState") + }; + } else if (Order.class.isAssignableFrom(type)) { + indexes = new StorableIndex[] { + makeIndex(type, "orderID"), + makeIndex(type, "orderTotal"), + makeIndex(type, "addressID") + }; + } else if (Shipment.class.isAssignableFrom(type)) { + indexes = new StorableIndex[] { + makeIndex(type, "shipmentID"), + makeIndex(type, "orderID"), + }; + } else if (Shipper.class.isAssignableFrom(type)) { + indexes = new StorableIndex[] { + makeIndex(type, "shipperID") + }; + } else { + indexes = new StorableIndex[0]; + } + + return Arrays.asList(indexes); + } + } +} diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java index a039e63..3f865e6 100644 --- a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java +++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java @@ -78,7 +78,7 @@ public class TestIndexedQueryExecutor extends TestCase { Mock executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(1, executor.mIdentityValues.length); assertEquals(100, executor.mIdentityValues[0]); @@ -98,7 +98,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100).with(5)); + executor.fetch(values.with(100).with(5)); assertEquals(2, executor.mIdentityValues.length); assertEquals(100, executor.mIdentityValues[0]); @@ -119,7 +119,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(200)); + executor.fetch(values.with(200)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -142,7 +142,7 @@ public class TestIndexedQueryExecutor extends TestCase { Mock executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -161,7 +161,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.INCLUSIVE, executor.mRangeStartBoundary); @@ -180,7 +180,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(10).with(30)); + executor.fetch(values.with(10).with(30)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -199,7 +199,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(10).with(30)); + executor.fetch(values.with(10).with(30)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -218,7 +218,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(10).with(10)); + executor.fetch(values.with(10).with(10)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -237,7 +237,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(30).with(10)); + executor.fetch(values.with(30).with(10)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.INCLUSIVE, executor.mRangeStartBoundary); @@ -257,7 +257,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100).with(30)); + executor.fetch(values.with(100).with(30)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -278,7 +278,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -298,7 +298,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -318,7 +318,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -341,7 +341,7 @@ public class TestIndexedQueryExecutor extends TestCase { Mock executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -360,7 +360,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -379,7 +379,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(10).with(30)); + executor.fetch(values.with(10).with(30)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -398,7 +398,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(10).with(30)); + executor.fetch(values.with(10).with(30)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -417,7 +417,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(10).with(10)); + executor.fetch(values.with(10).with(10)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -436,7 +436,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(30).with(10)); + executor.fetch(values.with(30).with(10)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -456,7 +456,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100).with(30)); + executor.fetch(values.with(100).with(30)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -477,7 +477,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -497,7 +497,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -517,7 +517,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100)); + executor.fetch(values.with(100)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); @@ -543,7 +543,7 @@ public class TestIndexedQueryExecutor extends TestCase { Mock executor = new Mock(index, score); - executor.openCursor(values.with(100).with(200)); + executor.fetch(values.with(100).with(200)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -562,7 +562,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(100).with(10)); + executor.fetch(values.with(100).with(10)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.INCLUSIVE, executor.mRangeStartBoundary); @@ -582,7 +582,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(10).with(100).with(30)); + executor.fetch(values.with(10).with(100).with(30)); assertEquals(null, executor.mIdentityValues); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -614,7 +614,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(3).with(56.5).with(200.2)); + executor.fetch(values.with(3).with(56.5).with(200.2)); assertEquals(3, executor.mIdentityValues[0]); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -630,7 +630,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(3).with(56.5).with(200.2)); + executor.fetch(values.with(3).with(56.5).with(200.2)); assertEquals(3, executor.mIdentityValues[0]); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -646,7 +646,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(3).with(56.5).with(200.2)); + executor.fetch(values.with(3).with(56.5).with(200.2)); assertEquals(3, executor.mIdentityValues[0]); assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); @@ -667,7 +667,7 @@ public class TestIndexedQueryExecutor extends TestCase { executor = new Mock(index, score); - executor.openCursor(values.with(3).with(56.5).with("foo")); + executor.fetch(values.with(3).with(56.5).with("foo")); assertEquals(3, executor.mIdentityValues[0]); assertEquals(56.5, executor.mIdentityValues[1]); @@ -701,14 +701,14 @@ public class TestIndexedQueryExecutor extends TestCase { super(index, score); } - protected Cursor openCursor(StorableIndex index, - Object[] identityValues, - BoundaryType rangeStartBoundary, - Object rangeStartValue, - BoundaryType rangeEndBoundary, - Object rangeEndValue, - boolean reverseRange, - boolean reverseOrder) + protected Cursor fetch(StorableIndex index, + Object[] identityValues, + BoundaryType rangeStartBoundary, + Object rangeStartValue, + BoundaryType rangeEndBoundary, + Object rangeEndValue, + boolean reverseRange, + boolean reverseOrder) { mIdentityValues = identityValues; mRangeStartBoundary = rangeStartBoundary; diff --git a/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java new file mode 100644 index 0000000..8a82758 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java @@ -0,0 +1,203 @@ +/* + * 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.util.ArrayList; +import java.util.List; + +import junit.framework.TestSuite; + +import com.amazon.carbonado.Cursor; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.Query; +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; + +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; + +import com.amazon.carbonado.info.OrderedProperty; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; + +import com.amazon.carbonado.repo.toy.ToyRepository; + +import com.amazon.carbonado.stored.UserAddress; +import com.amazon.carbonado.stored.UserInfo; + +/** + * + * + * @author Brian S O'Neill + */ +public class TestJoinedQueryExecutor extends TestQueryExecutor { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestJoinedQueryExecutor.class); + } + + private Repository mRepository; + + protected void setUp() throws Exception { + super.setUp(); + mRepository = new ToyRepository(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testJoin() throws Exception { + QueryExecutor addressExecutor = addressExecutor(); + + QueryExecutor userExecutor = new JoinedQueryExecutor + (mRepository, UserInfo.class, "address", addressExecutor); + + assertEquals("address.state = ?", userExecutor.getFilter().toString()); + assertEquals("address.country", userExecutor.getOrdering().get(0).toString()); + + // Create some addresses + Storage addressStorage = mRepository.storageFor(UserAddress.class); + UserAddress addr = addressStorage.prepare(); + addr.setAddressID(1); + addr.setLine1("4567, 123 Street"); + addr.setCity("Springfield"); + addr.setState("IL"); + addr.setCountry("USA"); + addr.insert(); + + addr = addressStorage.prepare(); + addr.setAddressID(2); + addr.setLine1("1111 Apt 1, 1st Ave"); + addr.setCity("Somewhere"); + addr.setState("AA"); + addr.setCountry("USA"); + addr.setNeighborAddressID(1); + addr.insert(); + + addr = addressStorage.prepare(); + addr.setAddressID(3); + addr.setLine1("9999"); + addr.setCity("Chicago"); + addr.setState("IL"); + addr.setCountry("USA"); + addr.insert(); + + // Create some users + Storage userStorage = mRepository.storageFor(UserInfo.class); + UserInfo user = userStorage.prepare(); + user.setUserID(1); + user.setStateID(1); + user.setFirstName("Bob"); + user.setLastName("Loblaw"); + user.setAddressID(1); + user.insert(); + + user = userStorage.prepare(); + user.setUserID(2); + user.setStateID(1); + user.setFirstName("Deb"); + user.setLastName("Loblaw"); + user.setAddressID(1); + user.insert(); + + user = userStorage.prepare(); + user.setUserID(3); + user.setStateID(1); + user.setFirstName("No"); + user.setLastName("Body"); + user.setAddressID(2); + user.insert(); + + // Now do a basic join, finding everyone in IL. + + FilterValues values = Filter + .filterFor(UserInfo.class, "address.state = ?").initialFilterValues().with("IL"); + + Cursor cursor = userExecutor.fetch(values); + assertTrue(cursor.hasNext()); + assertEquals(1, cursor.next().getUserID()); + assertEquals(2, cursor.next().getUserID()); + assertFalse(cursor.hasNext()); + cursor.close(); + + assertEquals(2L, userExecutor.count(values)); + + // Now do a multi join, finding everyone with an explicit neighbor in IL. + + userExecutor = new JoinedQueryExecutor + (mRepository, UserInfo.class, "address.neighbor", addressExecutor); + + assertEquals("address.neighbor.state = ?", userExecutor.getFilter().toString()); + assertEquals("address.neighbor.country", userExecutor.getOrdering().get(0).toString()); + + values = Filter + .filterFor(UserInfo.class, "address.neighbor.state = ?") + .initialFilterValues().with("IL"); + + cursor = userExecutor.fetch(values); + assertTrue(cursor.hasNext()); + assertEquals(3, cursor.next().getUserID()); + assertFalse(cursor.hasNext()); + cursor.close(); + + assertEquals(1L, userExecutor.count(values)); + + } + + protected QueryExecutor addressExecutor() throws Exception { + Storage addressStorage = mRepository.storageFor(UserAddress.class); + + QueryExecutor addressExecutor = + new ScanQueryExecutor(addressStorage.query()); + + addressExecutor = new FilteredQueryExecutor + (addressExecutor, Filter.filterFor(UserAddress.class, "state = ?")); + + StorableProperty prop = StorableIntrospector + .examine(UserAddress.class).getAllProperties().get("country"); + + List> orderings = + new ArrayList>(); + + orderings.add(OrderedProperty.get(prop, null)); + + addressExecutor = new ArraySortedQueryExecutor + (addressExecutor, null, orderings); + + return addressExecutor; + } + + static class ScanQueryExecutor extends FullScanQueryExecutor { + private final Query mQuery; + + ScanQueryExecutor(Query query) { + super(query.getStorableType()); + mQuery = query; + } + + protected Cursor fetch() throws FetchException { + return mQuery.fetch(); + } + } +} diff --git a/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java index 0972e94..9e9906f 100644 --- a/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java +++ b/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java @@ -58,7 +58,7 @@ public class TestSortedQueryExecutor extends TestQueryExecutor { assertEquals(ordered, executor.getOrdering()); - compareElements(executor.openCursor(values), 1, 2, 3, 4); + compareElements(executor.fetch(values), 1, 2, 3, 4); } public void testBasicFinisherSorting() throws Exception { @@ -79,6 +79,6 @@ public class TestSortedQueryExecutor extends TestQueryExecutor { assertEquals(handled.get(0), executor.getOrdering().get(0)); assertEquals(finisher.get(0), executor.getOrdering().get(1)); - compareElements(executor.openCursor(values), 1, 2, 3, 4); + compareElements(executor.fetch(values), 1, 2, 3, 4); } } diff --git a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java index 639176c..07716bb 100644 --- a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java +++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java @@ -70,6 +70,6 @@ public class TestUnionQueryExecutor extends TestQueryExecutor { assertEquals(primary.getOrdering(), union.getOrdering()); - compareElements(union.openCursor(values), 1, 2, 3, 7, 8); + compareElements(union.fetch(values), 1, 2, 3, 7, 8); } } diff --git a/src/test/java/com/amazon/carbonado/repo/toy/ToyRepository.java b/src/test/java/com/amazon/carbonado/repo/toy/ToyRepository.java new file mode 100644 index 0000000..159f451 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/repo/toy/ToyRepository.java @@ -0,0 +1,106 @@ +/* + * 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.repo.toy; + +import java.util.HashMap; +import java.util.Map; + +import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.Transaction; + +import com.amazon.carbonado.capability.Capability; + +import com.amazon.carbonado.spi.SequenceValueGenerator; +import com.amazon.carbonado.spi.SequenceValueProducer; + +/** + * + * @author Brian S O'Neill + */ +public class ToyRepository implements Repository { + private final String mName; + private final Map mStorages; + private final Map mSequences; + + public ToyRepository() { + this("toy"); + } + + public ToyRepository(String name) { + mName = name; + mStorages = new HashMap(); + mSequences = new HashMap(); + } + + public String getName() { + return mName; + } + + public Storage storageFor(Class type) + throws SupportException, RepositoryException + { + synchronized (mStorages) { + Storage storage = (Storage) mStorages.get(type); + if (storage == null) { + storage = new ToyStorage(this, type); + mStorages.put(type, storage); + } + return storage; + } + } + + public Transaction enterTransaction() { + return new ToyTransaction(); + } + + public Transaction enterTransaction(IsolationLevel level) { + return enterTransaction(); + } + + public Transaction enterTopTransaction(IsolationLevel level) { + return enterTransaction(level); + } + + public IsolationLevel getTransactionIsolationLevel() { + return null; + } + + public C getCapability(Class capabilityType) { + return null; + } + + public void close() { + } + + SequenceValueProducer getSequenceValueProducer(String name) throws RepositoryException { + synchronized (mSequences) { + SequenceValueProducer producer = mSequences.get(name); + if (producer == null) { + producer = new SequenceValueGenerator(this, name); + mSequences.put(name, producer); + } + return producer; + } + } +} diff --git a/src/test/java/com/amazon/carbonado/repo/toy/ToyStorableGenerator.java b/src/test/java/com/amazon/carbonado/repo/toy/ToyStorableGenerator.java new file mode 100644 index 0000000..a72c7d5 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/repo/toy/ToyStorableGenerator.java @@ -0,0 +1,143 @@ +/* + * 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.repo.toy; + +import java.util.EnumSet; +import java.util.Map; + +import org.cojen.classfile.ClassFile; +import org.cojen.classfile.CodeBuilder; +import org.cojen.classfile.MethodInfo; +import org.cojen.classfile.Modifiers; +import org.cojen.classfile.TypeDesc; + +import org.cojen.util.ClassInjector; +import org.cojen.util.SoftValuedHashMap; + +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.Trigger; + +import com.amazon.carbonado.spi.MasterFeature; +import com.amazon.carbonado.spi.MasterStorableGenerator; +import com.amazon.carbonado.spi.MasterSupport; +import com.amazon.carbonado.spi.StorableGenerator; +import com.amazon.carbonado.spi.TriggerSupport; + +/** + * + * + * @author Brian S O'Neill + */ +public class ToyStorableGenerator { + private static final Map cCache; + + static { + cCache = new SoftValuedHashMap(); + } + + /** + * Generated class has a constructor that accepts a ToyStorage instance. + */ + public static Class getGeneratedClass(Class type) + throws SupportException + { + synchronized (cCache) { + Class generatedClass = (Class) cCache.get(type); + if (generatedClass != null) { + return generatedClass; + } + generatedClass = new ToyStorableGenerator(type).generateAndInjectClass(); + cCache.put(type, generatedClass); + return generatedClass; + } + } + + private final Class mStorableType; + + private final ClassInjector mClassInjector; + private final ClassFile mClassFile; + + private ToyStorableGenerator(Class type) throws SupportException { + mStorableType = type; + + EnumSet features = EnumSet + .of(MasterFeature.VERSIONING, MasterFeature.INSERT_SEQUENCES); + + final Class abstractClass = + MasterStorableGenerator.getAbstractClass(mStorableType, features); + + mClassInjector = ClassInjector.create(mStorableType.getName(), + abstractClass.getClassLoader()); + + mClassFile = new ClassFile(mClassInjector.getClassName(), abstractClass); + mClassFile.markSynthetic(); + mClassFile.setSourceFile(ToyStorableGenerator.class.getName()); + mClassFile.setTarget("1.5"); + } + + private Class generateAndInjectClass() { + TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class); + TypeDesc toyStorageType = TypeDesc.forClass(ToyStorage.class); + + // Add constructor that accepts a ToyStorage. + { + TypeDesc[] params = {toyStorageType}; + MethodInfo mi = mClassFile.addConstructor(Modifiers.PUBLIC, params); + CodeBuilder b = new CodeBuilder(mi); + b.loadThis(); + b.loadLocal(b.getParameter(0)); + b.invokeSuperConstructor(new TypeDesc[] {masterSupportType}); + b.returnVoid(); + } + + // Implement abstract methods which all delegate to ToyStorage instance. + + generateDelegatedMethod + (MasterStorableGenerator.DO_TRY_LOAD_MASTER_METHOD_NAME, "doTryLoad"); + generateDelegatedMethod + (MasterStorableGenerator.DO_TRY_INSERT_MASTER_METHOD_NAME, "doTryInsert"); + generateDelegatedMethod + (MasterStorableGenerator.DO_TRY_UPDATE_MASTER_METHOD_NAME, "doTryUpdate"); + generateDelegatedMethod + (MasterStorableGenerator.DO_TRY_DELETE_MASTER_METHOD_NAME, "doTryDelete"); + + Class generatedClass = mClassInjector.defineClass(mClassFile); + + return generatedClass; + } + + private void generateDelegatedMethod(String masterMethodName, String supportMethodName) { + TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class); + TypeDesc toyStorageType = TypeDesc.forClass(ToyStorage.class); + + TypeDesc[] storableParam = {TypeDesc.forClass(Storable.class)}; + + MethodInfo mi = mClassFile.addMethod + (Modifiers.PROTECTED, masterMethodName, TypeDesc.BOOLEAN, null); + CodeBuilder b = new CodeBuilder(mi); + + b.loadThis(); + b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); + b.checkCast(toyStorageType); + b.loadThis(); + b.invokeVirtual(toyStorageType, supportMethodName, TypeDesc.BOOLEAN, storableParam); + b.returnValue(TypeDesc.BOOLEAN); + } +} diff --git a/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java b/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java new file mode 100644 index 0000000..dd29e4b --- /dev/null +++ b/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java @@ -0,0 +1,242 @@ +/* + * 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.repo.toy; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.Query; +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.Transaction; +import com.amazon.carbonado.Trigger; + +import com.amazon.carbonado.spi.MasterSupport; +import com.amazon.carbonado.spi.SequenceValueProducer; + +import com.amazon.carbonado.util.QuickConstructorGenerator; + +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; + +import com.amazon.carbonado.info.OrderedProperty; +import com.amazon.carbonado.info.StorableIntrospector; + +import com.amazon.carbonado.qe.FilteredQueryExecutor; +import com.amazon.carbonado.qe.IterableQueryExecutor; +import com.amazon.carbonado.qe.QueryExecutor; +import com.amazon.carbonado.qe.SortedQueryExecutor; +import com.amazon.carbonado.qe.StandardQuery; + +/** + * + * @author Brian S O'Neill + */ +public class ToyStorage implements Storage, MasterSupport { + final ToyRepository mRepo; + final Class mType; + + final InstanceFactory mInstanceFactory; + + final Collection mData; + final Lock mDataLock; + + public ToyStorage(ToyRepository repo, Class type) throws SupportException { + StorableIntrospector.examine(type); + mRepo = repo; + mType = type; + + Class generatedStorableClass = ToyStorableGenerator.getGeneratedClass(type); + mInstanceFactory = QuickConstructorGenerator + .getInstance(generatedStorableClass, InstanceFactory.class); + + mData = new LinkedList(); + mDataLock = new ReentrantLock(); + } + + public Class getStorableType() { + return mType; + } + + public S prepare() { + return (S) mInstanceFactory.instantiate(this); + } + + public Query query() throws FetchException { + return new ToyQuery(null); + } + + public Query query(String filter) throws FetchException { + return query(Filter.filterFor(mType, filter)); + } + + public Query query(Filter filter) throws FetchException { + return new ToyQuery(filter.initialFilterValues()); + } + + public boolean addTrigger(Trigger trigger) { + return false; + } + + public boolean removeTrigger(Trigger trigger) { + return false; + } + + public boolean doTryLoad(S storable) { + mDataLock.lock(); + try { + for (S existing : mData) { + if (existing.equalPrimaryKeys(storable)) { + storable.markAllPropertiesDirty(); + existing.copyAllProperties(storable); + storable.markAllPropertiesClean(); + return true; + } + } + return false; + } finally { + mDataLock.unlock(); + } + } + + public boolean doTryInsert(S storable) { + mDataLock.lock(); + try { + for (S existing : mData) { + if (existing.equalPrimaryKeys(storable)) { + return false; + } + } + storable.markAllPropertiesClean(); + mData.add((S) storable.copy()); + return true; + } finally { + mDataLock.unlock(); + } + } + + public boolean doTryUpdate(S storable) { + mDataLock.lock(); + try { + for (S existing : mData) { + if (existing.equalPrimaryKeys(storable)) { + existing.markAllPropertiesDirty(); + storable.copyAllProperties(existing); + existing.markAllPropertiesClean(); + existing.copyAllProperties(storable); + storable.markAllPropertiesClean(); + return true; + } + } + return false; + } finally { + mDataLock.unlock(); + } + } + + public boolean doTryDelete(S storable) { + mDataLock.lock(); + try { + Iterator it = mData.iterator(); + while (it.hasNext()) { + S existing = it.next(); + if (existing.equalPrimaryKeys(storable)) { + it.remove(); + return true; + } + } + return false; + } finally { + mDataLock.unlock(); + } + } + + public Repository getRootRepository() { + return mRepo; + } + + public boolean isPropertySupported(String propertyName) { + return StorableIntrospector.examine(mType) + .getAllProperties().containsKey(propertyName); + } + + public Trigger getInsertTrigger() { + return null; + } + + public Trigger getUpdateTrigger() { + return null; + } + + public Trigger getDeleteTrigger() { + return null; + } + + public SequenceValueProducer getSequenceValueProducer(String name) throws PersistException { + try { + return mRepo.getSequenceValueProducer(name); + } catch (RepositoryException e) { + throw e.toPersistException(); + } + } + + public static interface InstanceFactory { + Storable instantiate(ToyStorage storage); + } + + private class ToyQuery extends StandardQuery { + ToyQuery(FilterValues values, String... orderings) { + super(values, orderings); + } + + protected Storage getStorage() { + return ToyStorage.this; + } + + protected Transaction enterTransactionForDelete(IsolationLevel level) { + return mRepo.enterTransaction(level); + } + + protected QueryExecutor getExecutor(FilterValues values, String... orderings) { + QueryExecutor executor = new IterableQueryExecutor(mType, mData, mDataLock); + + if (values != null) { + executor = new FilteredQueryExecutor(executor, values.getFilter()); + } + + // FIXME: sorting + + return executor; + } + + protected StandardQuery newInstance(FilterValues values, String... orderings) { + return new ToyQuery(values, orderings); + } + } +} diff --git a/src/test/java/com/amazon/carbonado/repo/toy/ToyTransaction.java b/src/test/java/com/amazon/carbonado/repo/toy/ToyTransaction.java new file mode 100644 index 0000000..c16d1f1 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/repo/toy/ToyTransaction.java @@ -0,0 +1,51 @@ +/* + * 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.repo.toy; + +import java.util.concurrent.TimeUnit; + +import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.Transaction; + +/** + * + * @author Brian S O'Neill + */ +public class ToyTransaction implements Transaction { + public void commit() throws PersistException { + } + + public void exit() throws PersistException { + } + + public void setForUpdate(boolean forUpdate) { + } + + public boolean isForUpdate() { + return false; + } + + public void setDesiredLockTimeout(int timeout, TimeUnit unit) { + } + + public IsolationLevel getIsolationLevel() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/test/java/com/amazon/carbonado/repo/toy/package-info.java b/src/test/java/com/amazon/carbonado/repo/toy/package-info.java new file mode 100644 index 0000000..769ea8b --- /dev/null +++ b/src/test/java/com/amazon/carbonado/repo/toy/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** + * Standalone toy repository implementation. This repository is only suitable + * for running tests that don't require anything sophisticated. It doesn't + * support transactions, nothing is actually persisted, and all queries do full + * scans. The repository is thread-safe, however. + */ +package com.amazon.carbonado.repo.toy; diff --git a/src/test/java/com/amazon/carbonado/stored/Order.java b/src/test/java/com/amazon/carbonado/stored/Order.java new file mode 100644 index 0000000..91804d9 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/Order.java @@ -0,0 +1,68 @@ +/* + * 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.stored; + +import com.amazon.carbonado.Alias; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.Join; +import com.amazon.carbonado.Nullable; +import com.amazon.carbonado.PrimaryKey; +import com.amazon.carbonado.Query; +import com.amazon.carbonado.Sequence; + +/** + * + * + * @author Brian S O'Neill + */ +@Alias("TEST_ORDER") +@PrimaryKey("orderID") +public interface Order extends Storable { + @Sequence("TEST_ORDER_ID_SEQ") + long getOrderID(); + void setOrderID(long id); + + String getOrderNumber(); + void setOrderNumber(String value); + + int getOrderTotal(); + void setOrderTotal(int value); + + @Nullable + String getOrderComments(); + void setOrderComments(String value); + + long getAddressID(); + void setAddressID(long value); + + @Join + @Nullable + Address getAddress() throws FetchException; + void setAddress(Address value); + + @Join + Query getOrderItems() throws FetchException; + + @Join + Query getShipments() throws FetchException; + + @Join + Query getPromotions() throws FetchException; +} diff --git a/src/test/java/com/amazon/carbonado/stored/OrderItem.java b/src/test/java/com/amazon/carbonado/stored/OrderItem.java new file mode 100644 index 0000000..dba2477 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/OrderItem.java @@ -0,0 +1,68 @@ +/* + * 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.stored; + +import com.amazon.carbonado.Alias; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.Index; +import com.amazon.carbonado.Indexes; +import com.amazon.carbonado.Join; +import com.amazon.carbonado.PrimaryKey; +import com.amazon.carbonado.Sequence; +import com.amazon.carbonado.constraint.IntegerConstraint; + +/** + * + * + * @author Brian S O'Neill + */ +@Alias("TEST_ORDER_ITEM") +@Indexes({@Index("+orderID"), @Index("+shipmentID")}) +@PrimaryKey("orderItemID") +public interface OrderItem extends Storable { + @Sequence("TEST_ORDER_ITEM_ID_SEQ") + long getOrderItemID(); + void setOrderItemID(long id); + + long getOrderID(); + void setOrderID(long value); + + @Join + Order getOrder() throws FetchException; + void setOrder(Order value); + + String getItemDescription(); + void setItemDescription(String value); + + int getItemQuantity(); + @IntegerConstraint(min=1, max=100) + void setItemQuantity(int value); + + int getItemPrice(); + void setItemPrice(int value); + + long getShipmentID(); + void setShipmentID(long value); + + @Join + Shipment getShipment() throws FetchException; + void setShipment(Shipment value); +} + diff --git a/src/test/java/com/amazon/carbonado/stored/Promotion.java b/src/test/java/com/amazon/carbonado/stored/Promotion.java new file mode 100644 index 0000000..18f4a2e --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/Promotion.java @@ -0,0 +1,55 @@ +/* + * 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.stored; + +import com.amazon.carbonado.Alias; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.Join; +import com.amazon.carbonado.PrimaryKey; + +/** + * + * + * @author Brian S O'Neill + */ +@Alias("TEST_PROMOTION") +@PrimaryKey({"orderID", "promotionID"}) +public interface Promotion extends Storable { + long getOrderID(); + void setOrderID(long value); + + String getPromotionID(); + void setPromotionID(String value); + + @Join + Order getOrder() throws FetchException; + void setOrder(Order value); + + @Join + Promotion getPromotion() throws FetchException; + void setPromotion(Promotion value); + + String getPromotionTitle(); + void setPromotionTitle(String value); + + String getPromotionDetails(); + void setPromotionDetails(String value); +} + diff --git a/src/test/java/com/amazon/carbonado/stored/Shipment.java b/src/test/java/com/amazon/carbonado/stored/Shipment.java new file mode 100644 index 0000000..670806d --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/Shipment.java @@ -0,0 +1,69 @@ +/* + * 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.stored; + +import org.joda.time.DateTime; + +import com.amazon.carbonado.Alias; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.Index; +import com.amazon.carbonado.Indexes; +import com.amazon.carbonado.Join; +import com.amazon.carbonado.PrimaryKey; +import com.amazon.carbonado.Query; +import com.amazon.carbonado.Sequence; + +/** + * + * + * @author Brian S O'Neill + */ +@Alias("TEST_SHIPMENT") +@Indexes(@Index("+orderID")) +@PrimaryKey("shipmentID") +public interface Shipment extends Storable { + @Sequence("TEST_SHIPMENT_ID_SEQ") + long getShipmentID(); + void setShipmentID(long id); + + String getShipmentNotes(); + void setShipmentNotes(String value); + + DateTime getShipmentDate(); + void setShipmentDate(DateTime value); + + long getOrderID(); + void setOrderID(long value); + + @Join + Order getOrder() throws FetchException; + void setOrder(Order value); + + long getShipperID(); + void setShipperID(long value); + + @Join + Shipper getShipper() throws FetchException; + void setShipper(Shipper value); + + @Join + Query getOrderItems() throws FetchException; +} + diff --git a/src/test/java/com/amazon/carbonado/stored/Shipper.java b/src/test/java/com/amazon/carbonado/stored/Shipper.java new file mode 100644 index 0000000..41e60f4 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/Shipper.java @@ -0,0 +1,52 @@ +/* + * 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.stored; + +import com.amazon.carbonado.Alias; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.Join; +import com.amazon.carbonado.PrimaryKey; +import com.amazon.carbonado.Sequence; + +/** + * + * + * @author Brian S O'Neill + */ +@Alias("TEST_SHIPPER") +@PrimaryKey("shipperID") +public interface Shipper extends Storable { + @Sequence("TEST_SHIPPER_ID_SEQ") + long getShipperID(); + void setShipperID(long id); + + String getShipperName(); + void setShipperName(String value); + + String getShipperDetails(); + void setShipperDetails(String value); + + long getAddressID(); + void setAddressID(long value); + + @Join + Address getAddress() throws FetchException; + void setAddress(Address value); +} diff --git a/src/test/java/com/amazon/carbonado/stored/UserAddress.java b/src/test/java/com/amazon/carbonado/stored/UserAddress.java new file mode 100644 index 0000000..466f6eb --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/UserAddress.java @@ -0,0 +1,66 @@ +/* + * 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.stored; + +import com.amazon.carbonado.*; + +/** + * + * + * @author Brian S O'Neill (boneill) + */ +@Indexes({ + @Index({"country", "state", "city"}), + @Index({"state", "city"}), + @Index("city") +}) +@PrimaryKey("addressID") +public abstract class UserAddress implements Storable { + public abstract int getAddressID(); + public abstract void setAddressID(int id); + + public abstract String getLine1(); + public abstract void setLine1(String value); + + @Nullable + public abstract String getLine2(); + public abstract void setLine2(String value); + + public abstract String getCity(); + public abstract void setCity(String value); + + public abstract String getState(); + public abstract void setState(String value); + + public abstract String getCountry(); + public abstract void setCountry(String value); + + @Nullable + public abstract String getPostalCode(); + public abstract void setPostalCode(String value); + + @Nullable + public abstract Integer getNeighborAddressID(); + public abstract void setNeighborAddressID(Integer id); + + @Nullable + @Join(internal="neighborAddressID", external="addressID") + public abstract UserAddress getNeighbor() throws FetchException; + public abstract void setNeighbor(UserAddress address) throws FetchException; +} diff --git a/src/test/java/com/amazon/carbonado/stored/UserInfo.java b/src/test/java/com/amazon/carbonado/stored/UserInfo.java new file mode 100644 index 0000000..e0bffa4 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/UserInfo.java @@ -0,0 +1,57 @@ +/* + * 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.stored; + +import com.amazon.carbonado.*; +import com.amazon.carbonado.constraint.*; + +/** + * + * + * @author Brian S O'Neill (boneill) + */ +@Indexes({ + @Index("firstName"), + @Index("lastName"), + @Index("addressID") +}) +@PrimaryKey("userID") +public abstract class UserInfo implements Storable { + public abstract int getUserID(); + public abstract void setUserID(int id); + + public abstract int getStateID(); + @IntegerConstraint(allowed={1, 2, 3}) + public abstract void setStateID(int state); + + public abstract String getFirstName(); + @LengthConstraint(min=1, max=50) + public abstract void setFirstName(String value); + + public abstract String getLastName(); + @LengthConstraint(min=1, max=50) + public abstract void setLastName(String value); + + public abstract int getAddressID(); + public abstract void setAddressID(int id); + + @Join + public abstract UserAddress getAddress() throws FetchException; + public abstract void setAddress(UserAddress address); +} -- cgit v1.2.3