diff options
Diffstat (limited to 'src/test/java/com/amazon/carbonado')
64 files changed, 14830 insertions, 0 deletions
diff --git a/src/test/java/com/amazon/carbonado/TestQueryLogic.java b/src/test/java/com/amazon/carbonado/TestQueryLogic.java new file mode 100644 index 0000000..4b4e917 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/TestQueryLogic.java @@ -0,0 +1,835 @@ +/*
 + * 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;
 +
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.filter.Filter;
 +
 +import com.amazon.carbonado.repo.toy.ToyRepository;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +import com.amazon.carbonado.stored.StorableTestBasicIndexed;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestQueryLogic extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        TestSuite suite = new TestSuite();
 +        suite.addTestSuite(TestQueryLogic.class);
 +        return suite;
 +    }
 +
 +    private Repository mRepository;
 +
 +    protected void setUp() throws Exception {
 +        mRepository = new ToyRepository();
 +    }
 +
 +    protected void tearDown() throws Exception {
 +        if (mRepository != null) {
 +            mRepository.close();
 +            mRepository = null;
 +        }
 +    }
 +
 +    protected Repository getRepository() {
 +        return mRepository;
 +    }
 +
 +    public TestQueryLogic(String name) {
 +        super(name);
 +    }
 +
 +    public void test_not() throws Exception {
 +        final int count = 10;
 +        populate(count);
 +
 +        final Storage<StorableTestBasic> storage =
 +            getRepository().storageFor(StorableTestBasic.class);
 +
 +        StorableTestBasic stb;
 +        Query<StorableTestBasic> query;
 +        List<StorableTestBasic> results;
 +
 +        {
 +            // Create query that returns nothing and verify.
 +            query = storage.query();
 +            assertEquals(Filter.getOpenFilter(StorableTestBasic.class), query.getFilter());
 +            query = query.not();
 +            assertEquals(Filter.getClosedFilter(StorableTestBasic.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(0, results.size());
 +            
 +            // Verify that "not" again produces everything.
 +            query = query.not();
 +            results = query.fetch().toList();
 +            assertEquals(count, results.size());
 +            assertOrder(results, true);
 +        }
 +
 +        {
 +            // Create ordered query that returns nothing and verify.
 +            query = storage.query();
 +            query = query.orderBy("-intProp");
 +            assertEquals(Filter.getOpenFilter(StorableTestBasic.class), query.getFilter());
 +            query = query.not();
 +            assertEquals(Filter.getClosedFilter(StorableTestBasic.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(0, results.size());
 +            
 +            // Verify that order is preserved when "not" again.
 +            query = query.not();
 +            assertEquals(Filter.getOpenFilter(StorableTestBasic.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(count, results.size());
 +            assertOrder(results, false);
 +        }
 +
 +        {
 +            // Do the ordered "not" test again, but change the sequence to build the query.
 +            query = storage.query().not(); // do "not" before orderBy.
 +            query = query.orderBy("-intProp");
 +            assertEquals(Filter.getClosedFilter(StorableTestBasic.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(0, results.size());
 +            
 +            // Verify that order is preserved when "not" again.
 +            query = query.not();
 +            assertEquals(Filter.getOpenFilter(StorableTestBasic.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(count, results.size());
 +            assertOrder(results, false);
 +        }
 +    }
 +
 +    public void test_demorgans() throws Exception {
 +        final int count = 10;
 +        populate(count);
 +
 +        final Storage<StorableTestBasic> storage =
 +            getRepository().storageFor(StorableTestBasic.class);
 +
 +        Query<StorableTestBasic> query; 
 +        List<StorableTestBasic> results;
 +
 +        {
 +            query = storage.query("intProp = ? & (stringProp >= ? | longProp < ?)");
 +            Filter<StorableTestBasic> filter1 = query.getFilter();
 +            query = query.not();
 +            Filter<StorableTestBasic> filter2 = query.getFilter();
 +            query = storage.query("intProp != ? | (stringProp < ? & longProp >= ?)");
 +            Filter<StorableTestBasic> filter3 = query.getFilter();
 +            
 +            assertEquals(filter3, filter2);
 +        }
 +
 +        // Try again, making sure that values are preserved in the query.
 +        {
 +            query = storage.query("intProp = ? & (stringProp >= ? | longProp < ?)");
 +            query = query.with(5);
 +
 +            results = query.with("a").with(10).fetch().toList();
 +            assertEquals(1, results.size());
 +
 +            query = query.not();
 +            results = query.with("a").with(10).fetch().toList();
 +            assertEquals(count - 1, results.size());
 +
 +            // Make sure record 5 is the one that's missing.
 +            for (StorableTestBasic stb : results) {
 +                assertFalse(5 == stb.getId());
 +            }
 +
 +            // Should be back to the original query.
 +            query = query.not();
 +
 +            results = query.with("a").with(10).fetch().toList();
 +            assertEquals(1, results.size());
 +        }
 +
 +        // Try again, making sure that a full set of values is preserved in the query.
 +        {
 +            query = storage.query("intProp = ? & (stringProp >= ? | longProp < ?)");
 +            query = query.with(2).with("a").with(10);
 +
 +            results = query.fetch().toList();
 +            assertEquals(1, results.size());
 +
 +            query = query.not();
 +            results = query.fetch().toList();
 +            assertEquals(count - 1, results.size());
 +
 +            // Make sure record 2 is the one that's missing.
 +            for (StorableTestBasic stb : results) {
 +                assertFalse(2 == stb.getId());
 +            }
 +
 +            // Should be back to the original query.
 +            query = query.not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(1, results.size());
 +        }
 +    }
 +
 +    public void test_and() throws Exception {
 +        final int count = 10;
 +        populate(count, new long[] {45, 12, 34, 12, 12, 45, 0, 0, 10, 2});
 +
 +        final Storage<StorableTestBasic> storage =
 +            getRepository().storageFor(StorableTestBasic.class);
 +
 +        Query<StorableTestBasic> query; 
 +        List<StorableTestBasic> results;
 +
 +        {
 +            query = storage.query("longProp = ?");
 +            try {
 +                query = query.and("stringProp != ?");
 +                fail();
 +            } catch (IllegalStateException e) {
 +                // Good. Blank params exist.
 +            }
 +        }
 +
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.and("stringProp != ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(2, results.size());
 +            assertEquals(2, results.get(0).getId());
 +            assertEquals(5, results.get(1).getId());
 +        }
 +
 +        // Different value.
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.and("stringProp != ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(2, results.size());
 +            assertEquals(1, results.get(0).getId());
 +            assertEquals(6, results.get(1).getId());
 +        }
 +
 +        // Different value with ordering.
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.orderBy("-intProp");
 +            query = query.and("stringProp != ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(2, results.size());
 +            assertEquals(1, results.get(1).getId());
 +            assertEquals(6, results.get(0).getId());
 +        }
 +
 +        // Try again with ordering.
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.orderBy("-intProp");
 +            query = query.and("stringProp != ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(2, results.size());
 +            assertEquals(2, results.get(1).getId());
 +            assertEquals(5, results.get(0).getId());
 +        }
 +
 +        // Invert the selection
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.and("stringProp != ?").with("str_4").not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(count - 2, results.size());
 +            assertEquals(1, results.get(0).getId());
 +            assertEquals(3, results.get(1).getId());
 +            assertEquals(4, results.get(2).getId());
 +            assertEquals(6, results.get(3).getId());
 +            assertEquals(7, results.get(4).getId());
 +            assertEquals(8, results.get(5).getId());
 +            assertEquals(9, results.get(6).getId());
 +            assertEquals(10, results.get(7).getId());
 +        }
 +
 +        // Invert the ordered selection
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.orderBy("-intProp");
 +            query = query.and("stringProp != ?").with("str_4").not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(count - 2, results.size());
 +            assertEquals(1, results.get(7).getId());
 +            assertEquals(3, results.get(6).getId());
 +            assertEquals(4, results.get(5).getId());
 +            assertEquals(6, results.get(4).getId());
 +            assertEquals(7, results.get(3).getId());
 +            assertEquals(8, results.get(2).getId());
 +            assertEquals(9, results.get(1).getId());
 +            assertEquals(10, results.get(0).getId());
 +        }
 +    }
 +
 +    public void test_or() throws Exception {
 +        final int count = 10;
 +        populate(count, new long[] {45, 12, 34, 12, 12, 45, 0, 0, 10, 2});
 +
 +        final Storage<StorableTestBasic> storage =
 +            getRepository().storageFor(StorableTestBasic.class);
 +
 +        Query<StorableTestBasic> query; 
 +        List<StorableTestBasic> results;
 +
 +        {
 +            query = storage.query("longProp = ?");
 +            try {
 +                query = query.or("stringProp = ?");
 +                fail();
 +            } catch (IllegalStateException e) {
 +                // Good. Blank params exist.
 +            }
 +        }
 +
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.or("stringProp = ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(3, results.size());
 +            assertEquals(1, results.get(0).getId());
 +            assertEquals(4, results.get(1).getId());
 +            assertEquals(6, results.get(2).getId());
 +        }
 +
 +        // Different value.
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.or("stringProp = ?").with("str_3");
 +
 +            results = query.fetch().toList();
 +            assertEquals(4, results.size());
 +            assertEquals(2, results.get(0).getId());
 +            assertEquals(3, results.get(1).getId());
 +            assertEquals(4, results.get(2).getId());
 +            assertEquals(5, results.get(3).getId());
 +        }
 +
 +        // Different value with ordering.
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.orderBy("-intProp");
 +            query = query.or("stringProp = ?").with("str_3");
 +
 +            results = query.fetch().toList();
 +            assertEquals(4, results.size());
 +            assertEquals(2, results.get(3).getId());
 +            assertEquals(3, results.get(2).getId());
 +            assertEquals(4, results.get(1).getId());
 +            assertEquals(5, results.get(0).getId());
 +        }
 +
 +        // Try again with ordering.
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.orderBy("-intProp");
 +            query = query.or("stringProp = ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(3, results.size());
 +            assertEquals(1, results.get(2).getId());
 +            assertEquals(4, results.get(1).getId());
 +            assertEquals(6, results.get(0).getId());
 +        }
 +
 +        // Invert the selection
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.or("stringProp = ?").with("str_4").not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(count - 3, results.size());
 +            assertEquals(2, results.get(0).getId());
 +            assertEquals(3, results.get(1).getId());
 +            assertEquals(5, results.get(2).getId());
 +            assertEquals(7, results.get(3).getId());
 +            assertEquals(8, results.get(4).getId());
 +            assertEquals(9, results.get(5).getId());
 +            assertEquals(10, results.get(6).getId());
 +        }
 +
 +        // Invert the ordered selection
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.orderBy("-intProp");
 +            query = query.or("stringProp = ?").with("str_4").not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(count - 3, results.size());
 +            assertEquals(2, results.get(6).getId());
 +            assertEquals(3, results.get(5).getId());
 +            assertEquals(5, results.get(4).getId());
 +            assertEquals(7, results.get(3).getId());
 +            assertEquals(8, results.get(2).getId());
 +            assertEquals(9, results.get(1).getId());
 +            assertEquals(10, results.get(0).getId());
 +        }
 +    }
 +
 +    //
 +    // Same tests again, except against an indexed storable.
 +    //
 +
 +    public void test_indexed_not() throws Exception {
 +        final int count = 10;
 +        populateIndexed(count);
 +
 +        final Storage<StorableTestBasicIndexed> storage =
 +            getRepository().storageFor(StorableTestBasicIndexed.class);
 +
 +        StorableTestBasicIndexed stb;
 +        Query<StorableTestBasicIndexed> query;
 +        List<StorableTestBasicIndexed> results;
 +
 +        {
 +            // Create query that returns nothing and verify.
 +            query = storage.query();
 +            assertEquals(Filter.getOpenFilter(StorableTestBasicIndexed.class), query.getFilter());
 +            query = query.not();
 +            assertEquals(Filter.getClosedFilter(StorableTestBasicIndexed.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(0, results.size());
 +            
 +            // Verify that "not" again produces everything.
 +            query = query.not();
 +            results = query.fetch().toList();
 +            assertEquals(count, results.size());
 +            assertOrderIndexed(results, true);
 +        }
 +
 +        {
 +            // Create ordered query that returns nothing and verify.
 +            query = storage.query();
 +            query = query.orderBy("-intProp");
 +            assertEquals(Filter.getOpenFilter(StorableTestBasicIndexed.class), query.getFilter());
 +            query = query.not();
 +            assertEquals(Filter.getClosedFilter(StorableTestBasicIndexed.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(0, results.size());
 +            
 +            // Verify that order is preserved when "not" again.
 +            query = query.not();
 +            assertEquals(Filter.getOpenFilter(StorableTestBasicIndexed.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(count, results.size());
 +            assertOrderIndexed(results, false);
 +        }
 +
 +        {
 +            // Do the ordered "not" test again, but change the sequence to build the query.
 +            query = storage.query().not(); // do "not" before orderBy.
 +            query = query.orderBy("-intProp");
 +            assertEquals(Filter.getClosedFilter(StorableTestBasicIndexed.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(0, results.size());
 +            
 +            // Verify that order is preserved when "not" again.
 +            query = query.not();
 +            assertEquals(Filter.getOpenFilter(StorableTestBasicIndexed.class), query.getFilter());
 +            results = query.fetch().toList();
 +            assertEquals(count, results.size());
 +            assertOrderIndexed(results, false);
 +        }
 +    }
 +
 +    public void test_indexed_demorgans() throws Exception {
 +        final int count = 10;
 +        populateIndexed(count);
 +
 +        final Storage<StorableTestBasicIndexed> storage =
 +            getRepository().storageFor(StorableTestBasicIndexed.class);
 +
 +        Query<StorableTestBasicIndexed> query; 
 +        List<StorableTestBasicIndexed> results;
 +
 +        {
 +            query = storage.query("intProp = ? & (stringProp >= ? | longProp < ?)");
 +            Filter<StorableTestBasicIndexed> filter1 = query.getFilter();
 +            query = query.not();
 +            Filter<StorableTestBasicIndexed> filter2 = query.getFilter();
 +            query = storage.query("intProp != ? | (stringProp < ? & longProp >= ?)");
 +            Filter<StorableTestBasicIndexed> filter3 = query.getFilter();
 +            
 +            assertEquals(filter3, filter2);
 +        }
 +
 +        // Try again, making sure that values are preserved in the query.
 +        {
 +            query = storage.query("intProp = ? & (stringProp >= ? | longProp < ?)");
 +            query = query.with(5);
 +
 +            results = query.with("a").with(10).fetch().toList();
 +            assertEquals(1, results.size());
 +
 +            query = query.not();
 +            results = query.with("a").with(10).fetch().toList();
 +            assertEquals(count - 1, results.size());
 +
 +            // Make sure record 5 is the one that's missing.
 +            for (StorableTestBasicIndexed stb : results) {
 +                assertFalse(5 == stb.getId());
 +            }
 +
 +            // Should be back to the original query.
 +            query = query.not();
 +
 +            results = query.with("a").with(10).fetch().toList();
 +            assertEquals(1, results.size());
 +        }
 +
 +        // Try again, making sure that a full set of values is preserved in the query.
 +        {
 +            query = storage.query("intProp = ? & (stringProp >= ? | longProp < ?)");
 +            query = query.with(2).with("a").with(10);
 +
 +            results = query.fetch().toList();
 +            assertEquals(1, results.size());
 +
 +            query = query.not();
 +            results = query.fetch().toList();
 +            assertEquals(count - 1, results.size());
 +
 +            // Make sure record 2 is the one that's missing.
 +            for (StorableTestBasicIndexed stb : results) {
 +                assertFalse(2 == stb.getId());
 +            }
 +
 +            // Should be back to the original query.
 +            query = query.not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(1, results.size());
 +        }
 +    }
 +
 +    public void test_indexed_and() throws Exception {
 +        final int count = 10;
 +        populateIndexed(count, new long[] {45, 12, 34, 12, 12, 45, 0, 0, 10, 2});
 +
 +        final Storage<StorableTestBasicIndexed> storage =
 +            getRepository().storageFor(StorableTestBasicIndexed.class);
 +
 +        Query<StorableTestBasicIndexed> query; 
 +        List<StorableTestBasicIndexed> results;
 +
 +        {
 +            query = storage.query("longProp = ?");
 +            try {
 +                query = query.and("stringProp != ?");
 +                fail();
 +            } catch (IllegalStateException e) {
 +                // Good. Blank params exist.
 +            }
 +        }
 +
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.and("stringProp != ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(2, results.size());
 +            assertEquals(2, results.get(0).getId());
 +            assertEquals(5, results.get(1).getId());
 +        }
 +
 +        // Different value.
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.and("stringProp != ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(2, results.size());
 +            assertEquals(1, results.get(0).getId());
 +            assertEquals(6, results.get(1).getId());
 +        }
 +
 +        // Different value with ordering.
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.orderBy("-intProp");
 +            query = query.and("stringProp != ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(2, results.size());
 +            assertEquals(1, results.get(1).getId());
 +            assertEquals(6, results.get(0).getId());
 +        }
 +
 +        // Try again with ordering.
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.orderBy("-intProp");
 +            query = query.and("stringProp != ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(2, results.size());
 +            assertEquals(2, results.get(1).getId());
 +            assertEquals(5, results.get(0).getId());
 +        }
 +
 +        // Invert the selection
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.and("stringProp != ?").with("str_4").not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(count - 2, results.size());
 +            assertEquals(1, results.get(0).getId());
 +            assertEquals(3, results.get(1).getId());
 +            assertEquals(4, results.get(2).getId());
 +            assertEquals(6, results.get(3).getId());
 +            assertEquals(7, results.get(4).getId());
 +            assertEquals(8, results.get(5).getId());
 +            assertEquals(9, results.get(6).getId());
 +            assertEquals(10, results.get(7).getId());
 +        }
 +
 +        // Invert the ordered selection
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.orderBy("-intProp");
 +            query = query.and("stringProp != ?").with("str_4").not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(count - 2, results.size());
 +            assertEquals(1, results.get(7).getId());
 +            assertEquals(3, results.get(6).getId());
 +            assertEquals(4, results.get(5).getId());
 +            assertEquals(6, results.get(4).getId());
 +            assertEquals(7, results.get(3).getId());
 +            assertEquals(8, results.get(2).getId());
 +            assertEquals(9, results.get(1).getId());
 +            assertEquals(10, results.get(0).getId());
 +        }
 +    }
 +
 +    public void test_indexed_or() throws Exception {
 +        final int count = 10;
 +        populateIndexed(count, new long[] {45, 12, 34, 12, 12, 45, 0, 0, 10, 2});
 +
 +        final Storage<StorableTestBasicIndexed> storage =
 +            getRepository().storageFor(StorableTestBasicIndexed.class);
 +
 +        Query<StorableTestBasicIndexed> query; 
 +        List<StorableTestBasicIndexed> results;
 +
 +        {
 +            query = storage.query("longProp = ?");
 +            try {
 +                query = query.or("stringProp = ?");
 +                fail();
 +            } catch (IllegalStateException e) {
 +                // Good. Blank params exist.
 +            }
 +        }
 +
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.or("stringProp = ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(3, results.size());
 +            assertEquals(1, results.get(0).getId());
 +            assertEquals(4, results.get(1).getId());
 +            assertEquals(6, results.get(2).getId());
 +        }
 +
 +        // Different value.
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.or("stringProp = ?").with("str_3");
 +
 +            results = query.fetch().toList();
 +            assertEquals(4, results.size());
 +            assertEquals(2, results.get(0).getId());
 +            assertEquals(3, results.get(1).getId());
 +            assertEquals(4, results.get(2).getId());
 +            assertEquals(5, results.get(3).getId());
 +        }
 +
 +        // Different value with ordering.
 +        {
 +            query = storage.query("longProp = ?").with(12);
 +            query = query.orderBy("-intProp");
 +            query = query.or("stringProp = ?").with("str_3");
 +
 +            results = query.fetch().toList();
 +            assertEquals(4, results.size());
 +            assertEquals(2, results.get(3).getId());
 +            assertEquals(3, results.get(2).getId());
 +            assertEquals(4, results.get(1).getId());
 +            assertEquals(5, results.get(0).getId());
 +        }
 +
 +        // Try again with ordering.
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.orderBy("-intProp");
 +            query = query.or("stringProp = ?").with("str_4");
 +
 +            results = query.fetch().toList();
 +            assertEquals(3, results.size());
 +            assertEquals(1, results.get(2).getId());
 +            assertEquals(4, results.get(1).getId());
 +            assertEquals(6, results.get(0).getId());
 +        }
 +
 +        // Invert the selection
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.or("stringProp = ?").with("str_4").not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(count - 3, results.size());
 +            assertEquals(2, results.get(0).getId());
 +            assertEquals(3, results.get(1).getId());
 +            assertEquals(5, results.get(2).getId());
 +            assertEquals(7, results.get(3).getId());
 +            assertEquals(8, results.get(4).getId());
 +            assertEquals(9, results.get(5).getId());
 +            assertEquals(10, results.get(6).getId());
 +        }
 +
 +        // Invert the ordered selection
 +        {
 +            query = storage.query("longProp = ?").with(45);
 +            query = query.orderBy("-intProp");
 +            query = query.or("stringProp = ?").with("str_4").not();
 +
 +            results = query.fetch().toList();
 +            assertEquals(count - 3, results.size());
 +            assertEquals(2, results.get(6).getId());
 +            assertEquals(3, results.get(5).getId());
 +            assertEquals(5, results.get(4).getId());
 +            assertEquals(7, results.get(3).getId());
 +            assertEquals(8, results.get(2).getId());
 +            assertEquals(9, results.get(1).getId());
 +            assertEquals(10, results.get(0).getId());
 +        }
 +    }
 +
 +    private void populate(int count) throws Exception {
 +        populate(count, null);
 +    }
 +
 +    private void populate(int count, long[] longValues) throws Exception {
 +        final Storage<StorableTestBasic> storage =
 +            getRepository().storageFor(StorableTestBasic.class);
 +
 +        StorableTestBasic stb;
 +
 +        // Insert some test data
 +        for (int i=1; i<=count; i++) {
 +            stb = storage.prepare();
 +            stb.initBasicProperties();
 +            stb.setId(i);
 +            stb.setIntProp(i);
 +            stb.setStringProp("str_" + i);
 +            if (longValues != null && i <= longValues.length) {
 +                stb.setLongProp(longValues[i - 1]);
 +            }
 +            stb.insert();
 +        }
 +    }
 +
 +    private void populateIndexed(int count) throws Exception {
 +        populateIndexed(count, null);
 +    }
 +
 +    private void populateIndexed(int count, long[] longValues) throws Exception {
 +        final Storage<StorableTestBasicIndexed> storage =
 +            getRepository().storageFor(StorableTestBasicIndexed.class);
 +
 +        StorableTestBasicIndexed stb;
 +
 +        // Insert some test data
 +        for (int i=1; i<=count; i++) {
 +            stb = storage.prepare();
 +            stb.initBasicProperties();
 +            stb.setId(i);
 +            stb.setIntProp(i);
 +            stb.setStringProp("str_" + i);
 +            if (longValues != null && i <= longValues.length) {
 +                stb.setLongProp(longValues[i - 1]);
 +            }
 +            stb.insert();
 +        }
 +    }
 +
 +    private void assertOrder(List<StorableTestBasic> list, boolean ascending) throws Exception {
 +        StorableTestBasic last = null;
 +        for (StorableTestBasic stb : list) {
 +            if (last != null) {
 +                if (ascending) {
 +                    if (stb.getIntProp() <= last.getIntProp()) {
 +                        fail();
 +                    }
 +                } else {
 +                    if (stb.getIntProp() >= last.getIntProp()) {
 +                        fail();
 +                    }
 +                }
 +            }
 +            last = stb;
 +        }
 +    }
 +
 +    private void assertOrderIndexed(List<StorableTestBasicIndexed> list, boolean ascending)
 +        throws Exception
 +    {
 +        StorableTestBasicIndexed last = null;
 +        for (StorableTestBasicIndexed stb : list) {
 +            if (last != null) {
 +                if (ascending) {
 +                    if (stb.getIntProp() <= last.getIntProp()) {
 +                        fail();
 +                    }
 +                } else {
 +                    if (stb.getIntProp() >= last.getIntProp()) {
 +                        fail();
 +                    }
 +                }
 +            }
 +            last = stb;
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/TestStorables.java b/src/test/java/com/amazon/carbonado/TestStorables.java new file mode 100644 index 0000000..90048d7 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/TestStorables.java @@ -0,0 +1,2263 @@ +/* + * 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; + +import java.util.Comparator; +import java.util.List; + +import junit.framework.TestCase; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import com.amazon.carbonado.ConstraintException; +import com.amazon.carbonado.Cursor; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.FetchNoneException; +import com.amazon.carbonado.OptimisticLockException; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.PersistMultipleException; +import com.amazon.carbonado.PersistNoneException; +import com.amazon.carbonado.PrimaryKey; +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.Trigger; +import com.amazon.carbonado.UniqueConstraintException; + +import com.amazon.carbonado.cursor.SortedCursor; + +import com.amazon.carbonado.spi.RepairExecutor; +import com.amazon.carbonado.spi.WrappedSupport; + +import com.amazon.carbonado.stored.*; + +/** + * Runs an extensive set of acceptance tests for a repository. Must be + * subclassed to specify a repository to use. + * + * @author Don Schneider + * @author Brian S O'Neill + */ +public abstract class TestStorables extends TestCase { + +    public static final long sSetId = 0x1L << 0;           // 0x0001 +    public static final long sGetStringProp = 0x1L << 1;   // 0x0002 +    public static final long sSetStringProp = 0x1L << 2;   // 0x0004 +    public static final long sGetIntProp = 0x1L << 3;      // 0x0008 +    public static final long sSetIntProp = 0x1L << 4;      // 0x0010 +    public static final long sGetLongProp = 0x1L << 5;     // 0x0020 +    public static final long sSetLongProp = 0x1L << 6;     // 0x0040 +    public static final long sGetDoubleProp = 0x1L << 7;   // 0x0080 +    public static final long sSetDoubleProp = 0x1L << 8;   // 0x0100 +    public static final long sLoad = 0x1L << 9;            // 0x0200 +    public static final long sTryLoad = 0x1L << 10;        // 0x0400 +    public static final long sInsert = 0x1L << 11;         // 0x0800 +    public static final long sTryInsert = 0x1L << 31;      // 0x8000 0000 +    public static final long sUpdate = 0x1L << 32;         // 0x0001 0000 0000 +    public static final long sTryUpdate = 0x1L << 12;      // 0x1000 +    public static final long sDelete = 0x1L << 33;         // 0x0002 0000 0000 +    public static final long sTryDelete = 0x1L << 13;      // 0x2000 +    public static final long sStorage = 0x1L << 14;        // 0x4000 +    public static final long sCopy = 0x1L << 15;           // 0x8000 +    public static final long sToStringKeyOnly = 0x1L << 16;          // 0x0001 0000 +    public static final long sGetId = 0x1L << 17;                    // 0x0002 0000 +    public static final long sCopyAllProperties = 0x1L << 18;        // 0x0004 0000 +    public static final long sCopyPrimaryKeyProperties = 0x1L << 19; // 0x0080 0000 +    public static final long sCopyUnequalProperties = 0x1L << 20;    // 0x0010 0000 +    public static final long sCopyDirtyProperties = 0x1L << 21;      // 0x0020 0000 +    public static final long sHasDirtyProperties = 0x1L << 25;       // 0x0040 0000 +    public static final long sEqualKeys = 0x1L << 22;                // 0x0080 0000 +    public static final long sEqualProperties = 0x1L << 23;          // 0x0100 0000 +    public static final long sCopyVersionProperty = 0x1L << 24;      // 0x0200 0000 +    public static final long sMarkPropertiesClean = 0x1L << 26;      // 0x0400 0000 +    public static final long sMarkAllPropertiesClean = 0x1L << 27;   // 0x0800 0000 +    public static final long sMarkPropertiesDirty = 0x1L << 28;      // 0x1000 0000 +    public static final long sMarkAllPropertiesDirty = 0x1L << 29;   // 0x2000 0000 +    public static final long sStorableType = 0x1L << 30;             // 0x4000 0000 + +    public static final long ALL_SET_METHODS =       // 0x00000155; +            sSetId + sSetStringProp + sSetIntProp + sSetLongProp + sSetDoubleProp; +    public static final long ALL_GET_METHODS =       // 0x000200AA; +            sGetId + sGetStringProp + sGetIntProp + sGetLongProp + sGetDoubleProp; +    public static final long ALL_PRIMARY_KEYS = sSetId;     // 0x00000001; +    public static final long ALL_COPY_PROP_METHODS =  // 0x003C0000; +            sCopyAllProperties + sCopyPrimaryKeyProperties + sCopyUnequalProperties + +            sCopyDirtyProperties + sCopyVersionProperty; +    public static final long ALL_INTERFACE_METHODS = // 0x43C1FE00; +            sLoad + sTryLoad + sInsert + sUpdate + sDelete + sStorage + sCopy + sToStringKeyOnly + +            ALL_COPY_PROP_METHODS + sHasDirtyProperties + sEqualKeys + sEqualProperties + +            sMarkPropertiesClean + sMarkPropertiesDirty + +            sMarkAllPropertiesClean + sMarkAllPropertiesDirty + sStorableType; + +    private Repository mRepository; +    private static int s_Ids = 0; + +    public TestStorables(String s) { +        super(s); +    } + +    protected void setUp() throws Exception { +        super.setUp(); +    } + +    protected void tearDown() throws Exception { +        super.tearDown(); +        if (mRepository != null) { +            // The test may have thrown exceptions which cause some +            // repositories to kick off asynchronous repairs. They will +            // immediately fail since the repository is about to be +            // closed. This just eliminates uninteresting errors from being +            // logged. +            try { +                RepairExecutor.waitForRepairsToFinish(10000); +            } +            catch (InterruptedException e) { +            } + +            mRepository.close(); +            mRepository = null; +        } +    } + +    /** +     * Subclasses must implement this method to specify a repository. +     */ +    protected abstract Repository newRepository(boolean isMaster) +        throws RepositoryException; + +    /** +     * provide subsequent access to the repository so the tests can do fancy things if +     * interested. +     * @return +     */ +    protected Repository getRepository() throws RepositoryException { +        if (mRepository == null) { +            mRepository = newRepository(true); +        } +        return mRepository; +    } + +    /** +     * Create a random ID to eliminate optimistic lock conflicts due to ID collisions +     * @param seed +     * @return +     */ +    private int generateId(int seed) { +        return seed*10000 + ((int)System.currentTimeMillis()) + s_Ids++; +    } + +    public void test_createAndRetrieve() throws Exception { +        Storage<StorableTestBasic> storageSB = +            getRepository().storageFor(StorableTestBasic.class); + +        StorableTestBasic sb = storageSB.prepare(); +        final int id = generateId(0); +        sb.setId(id); +        sb.setIntProp(1); +        sb.setLongProp(1); +        sb.setDoubleProp(1.1); +        sb.setStringProp("one"); +        sb.setDate(new DateTime("2005-08-26T08:09:00.000")); +        sb.insert(); + +        StorableTestBasic sb_load = storageSB.prepare(); +        sb_load.setId(id); +        sb_load.load(); +        assertEquals(sb, sb_load); + +        // Try re-inserting +        // First, make sure the system disallows setting pk for loaded object +        try { +            sb.setId(id); +            fail("successfully set pk on loaded object"); +        } +        catch (Exception e) { +        } + +        // Then the more common way: just create an identical new one +        StorableTestBasic sbr = storageSB.prepare(); +        sbr.setId(id); +        sbr.setIntProp(1); +        sbr.setLongProp(1); +        sbr.setDoubleProp(1.1); +        sbr.setStringProp("one"); +        sb.setDate(new DateTime("2005-08-26T08:09:00.000")); +        try { +            sbr.insert(); +            fail("PK constraint violation ignored"); +        } +        catch (UniqueConstraintException e) { +        } + + +        Storage<StorableTestMultiPK> storageMPK = +                getRepository().storageFor(StorableTestMultiPK.class); +        StorableTestMultiPK smpk = storageMPK.prepare(); +        smpk.setIdPK(0); +        smpk.setStringPK("zero"); +        smpk.setStringData("and some data"); +        smpk.insert(); + +        StorableTestMultiPK smpk_load = storageMPK.prepare(); +        smpk_load.setIdPK(0); +        smpk_load.setStringPK("zero"); +        smpk_load.load(); +        assertEquals(smpk, smpk_load); +    } + +    public void test_storableStorableStates() throws Exception { + +        Storage<StorableTestKeyValue> storageMinimal = +            getRepository().storageFor(StorableTestKeyValue.class); + +        // Start by just putting some targets in the repository +        for (int i = 0; i < 10; i++) { +            insert(storageMinimal, 100+i, 200+i); +        } + +        StorableTestKeyValue s = storageMinimal.prepare(); +        StorableTestKeyValue s2 = storageMinimal.prepare(); + +        // State: unloaded +        // pk incomplete +        assertInoperable(s, "new - untouched"); +        assertFalse(s.hasDirtyProperties()); + +        // new --set(pk)--> loadable incomplete +        s.setKey1(0); +        assertInoperable(s, "Loadable Incomplete"); +        assertFalse(s.hasDirtyProperties()); + +        s.setKey1(101); +        assertInoperable(s, "loadable incomplete (2nd)"); +        assertFalse(s.hasDirtyProperties()); + +        // loadable incomplete --pkFilled--> loadable ready +        s.setKey2(201); +        assertEquals(true, s.tryDelete()); +        assertFalse(s.hasDirtyProperties()); + +        s.setKey1(102); +        s.setKey2(202); +        assertEquals(true, s.tryDelete()); +        assertFalse(s.hasDirtyProperties()); + +        // loadable ready --load()--> loaded +        s.setKey1(103); +        s.setKey2(203); +        s.load(); +        assertEquals(s.getValue1(), 1030); +        assertEquals(s.getValue2(), 20300); +        assertNoInsert(s, "written"); +        assertNoSetPK(s, "written"); +        assertFalse(s.hasDirtyProperties()); + +        s2.setKey1(103); +        s2.setKey2(203); +        assertFalse(s2.hasDirtyProperties()); +        s2.load(); +        assertFalse(s2.hasDirtyProperties()); + +        assertTrue(s.equalPrimaryKeys(s2)); +        assertTrue(s.equalProperties(s2)); +        assertEquals(s.storableType(), s2.storableType()); +        assertTrue(s.equals(s2)); +        assertEquals(s, s2); +        s.setValue1(11); +        s.setValue2(11111); +        assertEquals(true, s.tryUpdate()); +        assertEquals(11, s.getValue1()); +        assertEquals(11111, s.getValue2()); +        s2.load(); +        assertEquals(s, s2); + +        StorableTestKeyValue s3 = storageMinimal.prepare(); +        s.copyPrimaryKeyProperties(s3); +        s3.tryUpdate(); +        assertEquals(s, s3); + +        s.setValue2(222222); +        assertTrue(s.hasDirtyProperties()); +        s.load(); +        assertFalse(s.hasDirtyProperties()); +        assertEquals(s, s2); + +        // Update should return true, even though it probably didn't actually +        // touch the storage layer. +        assertEquals(true, s.tryUpdate()); + +        s.tryDelete(); +        assertNoLoad(s, "deleted"); +        // After delete, saved properties remain dirty. +        assertTrue(s.hasDirtyProperties()); + +        s.insert(); +        assertFalse(s.hasDirtyProperties()); +        s.load(); +        assertFalse(s.hasDirtyProperties()); +        assertEquals(s, s2); +    } + +    public void test_storableInteractions() throws Exception { +        Storage<StorableTestBasic> storage = getRepository().storageFor(StorableTestBasic.class); +        StorableTestBasic s = storage.prepare(); +        final int id = generateId(111); +        s.setId(id); +        s.initBasicProperties(); +        assertTrue(s.hasDirtyProperties()); +        s.insert(); +        assertFalse(s.hasDirtyProperties()); + +        StorableTestBasic s2 = storage.prepare(); +        s2.setId(id); +        s2.load(); +        assertTrue(s2.equalPrimaryKeys(s)); +        assertTrue(s2.equalProperties(s)); +        assertTrue(s2.equals(s)); + +        StorableTestBasic s3 = storage.prepare(); +        s3.setId(id); +        s3.tryDelete(); + +        // Should be able to re-insert. +        s2.insert(); +        s2.load(); +        assertTrue(s2.equalPrimaryKeys(s)); +        assertTrue(s2.equalProperties(s)); +        assertTrue(s2.equals(s)); + +        // Delete in preparation for next test. +        s3.tryDelete(); + +        s.setStringProp("updated by s"); +        // The object is gone, we can't update it +        assertEquals(false, s.tryUpdate()); +        // ...or load it +        try { +            s2.load(); +            fail("shouldn't be able to load a deleted object"); +        } +        catch (FetchException e) { +        } +    } + +    public void test_assymetricStorable() throws Exception { +        try { +            Storage<StorableTestAssymetric> storage = +                    getRepository().storageFor(StorableTestAssymetric.class); +        } +        catch (Exception e) { +            fail("exception creating storable with assymetric concrete getter?" + e); +        } +        try { +            Storage<StorableTestAssymetricGet> storage = +                    getRepository().storageFor(StorableTestAssymetricGet.class); +            fail("Created assymetric storabl`e"); +        } +        catch (Exception e) { +        } +        try { +            Storage<StorableTestAssymetricSet> storage = +                    getRepository().storageFor(StorableTestAssymetricSet.class); +            fail("Created assymetric storable"); +        } +        catch (Exception e) { +        } +        try { +            Storage<StorableTestAssymetricGetSet> storage = +                    getRepository().storageFor(StorableTestAssymetricGetSet.class); +            fail("Created assymetric storable"); +        } +        catch (Exception e) { +        } +    } + +    private void assertNoSetPK(StorableTestKeyValue aStorableTestKeyValue, +                               String aState) +    { +        try { +            aStorableTestKeyValue.setKey1(1111); +            fail("'set pk' for '" + aState + "' succeeded"); +        } +        catch (Exception e) { +        } +    } + +    private void assertInoperable(Storable aStorable, String aState) { +        assertNoInsert(aStorable, aState); +        assertNoUpdate(aStorable, aState); +        assertNoLoad(aStorable, aState); +        assertNoDelete(aStorable, aState); +    } + +    private void assertNoDelete(Storable aStorable, String aState) { +        try { +            aStorable.tryDelete(); +            fail("'delete' for '" + aState + "' succeeded"); +        } +        catch (PersistException e) { +        } +        catch (IllegalStateException e) { +        } +    } + +    private void assertNoLoad(Storable aStorable, String aState) { +        try { +            aStorable.load(); +            fail("'load' for '" + aState + "' succeeded"); +        } +        catch (FetchException e) { +        } +        catch (IllegalStateException e) { +        } +    } + +    private void assertNoInsert(Storable aStorable, String aState) { +        try { +            aStorable.insert(); +            fail("'insert' for '" + aState + "' succeeded"); +        } +        catch (PersistException e) { +        } +        catch (IllegalStateException e) { +        } +    } + +    private void assertNoUpdate(Storable aStorable, String aState) { +        try { +            aStorable.tryUpdate(); +            fail("'update' for '" + aState + "' succeeded"); +        } +        catch (PersistException e) { +        } +        catch (IllegalStateException e) { +        } +    } + +    private void insert(Storage<StorableTestKeyValue> aStorageMinimal, +                        int key1, +                        int key2) throws PersistException +    { +        StorableTestKeyValue s = aStorageMinimal.prepare(); +        s.setKey1(key1); +        s.setKey2(key2); +        s.setValue1(key1*10); +        s.setValue2(key2*100); +        s.insert(); +    } + +    public void test_copyStorableProperties() throws Exception { +        Storage<StorableTestBasic> storage = getRepository().storageFor(StorableTestBasic.class); +        StorableTestBasic storable = storage.prepare(); + +        InvocationTracker tracker = new InvocationTracker("tracker", false); +        storable.copyAllProperties(tracker); +        // unloaded, untouched; nothing happens +        tracker.assertTrack(0); + +        storable.setId(generateId(1)); +        storable.setIntProp(1); +        storable.copyAllProperties(tracker); +        tracker.assertTrack(0x1 + 0x10); +        tracker.clearTracks(); + +        storable.initBasicProperties(); +        storable.copyAllProperties(tracker); +        tracker.assertTrack(ALL_SET_METHODS); +        tracker.clearTracks(); + +        storable = storage.prepare(); +        storable.copyPrimaryKeyProperties(tracker); +        tracker.assertTrack(0); +        storable.initPrimaryKeyProperties(); +        storable.copyPrimaryKeyProperties(tracker); +        tracker.assertTrack(ALL_PRIMARY_KEYS); +        tracker.clearTracks(); + +        storable = storage.prepare(); +        storable.copyUnequalProperties(tracker); +        tracker.assertTrack(0); +        storable.setIntProp(0);  // this will now be dirty, and equal +        storable.copyUnequalProperties(tracker); +        tracker.assertTrack(0x8); +        storable.setIntProp(1);  // this will now be dirty and not equal +        storable.copyUnequalProperties(tracker); +        tracker.assertTrack(0x8 | 0x10); + +        // get a fresh one +        storable = storage.prepare(); +        storable.setStringProp("hi"); +        storable.setId(22); +        storable.copyPrimaryKeyProperties(tracker); +        storable.copyDirtyProperties(tracker); +        tracker.assertTrack(0x05); +    } + +    public void test_copy() throws Exception { +        Storage<StorableTestBasic> storage = getRepository().storageFor(StorableTestBasic.class); +        StorableTestBasic storable = storage.prepare(); + +        storable.setId(5); +        storable.setStringProp("hello"); +        storable.setIntProp(512); +        storable.setLongProp(45354L); +        storable.setDoubleProp(56734.234); + +        Storable copy = storable.copy(); + +        assertEquals(storable.getClass(), copy.getClass()); +        assertEquals(storable, copy); + +        StorableTestBasic castedCopy = (StorableTestBasic) copy; + +        assertEquals(storable.getId(), castedCopy.getId()); +        assertEquals(storable.getStringProp(), castedCopy.getStringProp()); +        assertEquals(storable.getIntProp(), castedCopy.getIntProp()); +        assertEquals(storable.getLongProp(), castedCopy.getLongProp()); +        assertEquals(storable.getDoubleProp(), castedCopy.getDoubleProp()); +    } + +    public void test_invalidStorables() throws Exception { +        try { +            getRepository().storageFor(StorableTestInvalid.class); +            fail("prepared invalid storable"); +        } +        catch (RepositoryException e) { +        } +    } + +    public void test_invalidPatterns() throws Exception { +        // Minimal -- try setting with no PK +        Storage<StorableTestMinimal> storageMinimal = +                getRepository().storageFor(StorableTestMinimal.class); +        StorableTestMinimal s = storageMinimal.prepare(); +        assertNoInsert(s, "new (minimal)"); + +        s.setId(generateId(0)); +        s.insert(); + +        // Basic -- try combinations of PK, fields +        // First, fill in all the fields but no PK +        Storage<StorableTestBasic> storageBasic = +                getRepository().storageFor(StorableTestBasic.class); +        StorableTestBasic sb = storageBasic.prepare(); +        assertNoInsert(sb, "new (basic)");; + +        sb.setIntProp(0); +        sb.setIntProp(1); +        sb.setLongProp(1); +        sb.setDoubleProp(1.1); +        sb.setStringProp("one"); +        sb.setDate(new DateTime("2005-08-26T08:09:00.000")); +        assertNoInsert(sb, "SB: Storable incomplete (pkMissing)"); + +        sb.setId(generateId(2)); +        sb.insert(); + +        // Now try leaving one of the fields empty. +        sb = storageBasic.prepare(); +        final int id = generateId(3); +        sb.setId(id); +        sb.setIntProp(0); +        sb.setIntProp(1); +        sb.setLongProp(1); +        sb.setDoubleProp(1.1); +        try { +            sb.insert(); +            fail(); +        } catch (ConstraintException e) { +        } + +        sb = storageBasic.prepare(); +        sb.setId(id); +        try { +            sb.load(); +            fail(); +        } catch (FetchNoneException e) { +        } +    } + +    public void test_nonDestructiveUpdate() throws Exception { +        Storage<StorableTestBasic> storage = getRepository().storageFor(StorableTestBasic.class); +        StorableTestBasic s = storage.prepare(); + +        int id = generateId(3943945); +        s.setId(id); +        s.setStringProp("hello"); +        s.setIntProp(56); +        s.setLongProp(99999999999999999L); +        s.setDoubleProp(Double.NaN); + +        s.insert(); + +        s = storage.prepare(); + +        s.setId(id); +        s.setIntProp(100); +        assertEquals(true, s.tryUpdate()); + +        assertEquals("hello", s.getStringProp()); +        assertEquals(100, s.getIntProp()); +        assertEquals(99999999999999999L, s.getLongProp()); +        assertEquals(Double.NaN, s.getDoubleProp()); + +        s = storage.prepare(); + +        s.setId(id); +        s.load(); + +        assertEquals("hello", s.getStringProp()); +        assertEquals(100, s.getIntProp()); +        assertEquals(99999999999999999L, s.getLongProp()); +        assertEquals(Double.NaN, s.getDoubleProp()); +    } + +    public void test_updateLoadSideEffect() throws Exception { +        Storage<StorableTestBasic> storage = getRepository().storageFor(StorableTestBasic.class); + +        StorableTestBasic s = storage.prepare(); +        final int id = generateId(500); +        s.setId(id); +        s.setStringProp("hello"); +        s.setIntProp(10); +        s.setLongProp(123456789012345L); +        s.setDoubleProp(Double.POSITIVE_INFINITY); +        s.insert(); + +        s = storage.prepare(); +        s.setId(id); + +        assertEquals(id, s.getId()); +        assertEquals(0, s.getIntProp()); +        assertEquals(0L, s.getLongProp()); +        assertEquals(0.0, s.getDoubleProp()); + +        assertFalse(s.hasDirtyProperties()); + +        // Even if nothing was updated, must load fresh copy. +        assertTrue(s.tryUpdate()); +        assertEquals(id, s.getId()); +        assertEquals(10, s.getIntProp()); +        assertEquals(123456789012345L, s.getLongProp()); +        assertEquals(Double.POSITIVE_INFINITY, s.getDoubleProp()); +    } + +    public void test_versioning() throws Exception { +        Storage<StorableVersioned> storage = getRepository().storageFor(StorableVersioned.class); + +        StorableVersioned s = storage.prepare(); +        s.setID(500); +        s.setValue("hello"); +        try { +            // Require version property to be set. +            s.tryUpdate(); +            fail(); +        } catch (IllegalStateException e) { +        } + +        s.setVersion(1); +        try { +            // Cannot update that which does not exist. +            s.update(); +            fail(); +        } catch (PersistNoneException e) { +        } + +        s.insert(); + +        s.setVersion(2); +        try { +            // Record version mismatch. +            s.tryUpdate(); +            fail(); +        } catch (OptimisticLockException e) { +        } + +        s.setVersion(1); +        s.setValue("world"); +        s.tryUpdate(); + +        assertEquals(2, s.getVersion()); +        assertEquals("world", s.getValue()); + +        // Since no properties changed, update does not increase version. +        assertEquals(true, s.tryUpdate()); +        assertEquals(2, s.getVersion()); + +        // Simple test to ensure that version property doesn't need to be +        // dirtied. +        s = storage.prepare(); +        s.setID(500); +        s.load(); +        s.setValue("hello"); +        assertTrue(s.tryUpdate()); +    } + +    public void test_versioningWithLong() throws Exception { +        Storage<StorableVersionedWithLong> storage = +            getRepository().storageFor(StorableVersionedWithLong.class); + +        StorableVersionedWithLong s = storage.prepare(); +        s.setID(500); +        s.setValue("hello"); +        try { +            // Require version property to be set. +            s.tryUpdate(); +            fail(); +        } catch (IllegalStateException e) { +        } + +        s.setVersion(1); +        try { +            // Cannot update that which does not exist. +            s.update(); +            fail(); +        } catch (PersistNoneException e) { +        } + +        s.insert(); + +        s.setVersion(2); +        try { +            // Record version mismatch. +            s.tryUpdate(); +            fail(); +        } catch (OptimisticLockException e) { +        } + +        s.setVersion(1); +        s.setValue("world"); +        s.tryUpdate(); + +        assertEquals(2, s.getVersion()); +        assertEquals("world", s.getValue()); + +        // Since no properties changed, update does not increase version. +        assertEquals(true, s.tryUpdate()); +        assertEquals(2, s.getVersion()); +    } + +    public void test_versioningWithObj() throws Exception { +        Storage<StorableVersionedWithObj> storage = +            getRepository().storageFor(StorableVersionedWithObj.class); + +        StorableVersionedWithObj s = storage.prepare(); +        s.setID(generateId(500)); +        s.setValue("hello"); +        try { +            // Require version property to be set. +            s.tryUpdate(); +            fail(); +        } catch (IllegalStateException e) { +        } + +        s.setVersion(null); +        try { +            // Cannot update that which does not exist. +            s.update(); +            fail(); +        } catch (PersistNoneException e) { +        } + +        s.insert(); + +        assertNull(s.getVersion()); + +        s.setVersion(2); +        try { +            // Record version mismatch. +            s.tryUpdate(); +            fail(); +        } catch (OptimisticLockException e) { +        } + +        s.setVersion(null); +        s.setValue("world"); +        s.tryUpdate(); + +        assertEquals((Integer) 1, s.getVersion()); +        assertEquals("world", s.getValue()); + +        s.setValue("value"); +        s.tryUpdate(); + +        assertEquals((Integer) 2, s.getVersion()); +        assertEquals("value", s.getValue()); + +        // Since no properties changed, update does not increase version. +        assertEquals(true, s.tryUpdate()); +        assertEquals((Integer) 2, s.getVersion()); +    } + +    public void test_versioningWithLongObj() throws Exception { +        Storage<StorableVersionedWithLongObj> storage = +            getRepository().storageFor(StorableVersionedWithLongObj.class); + +        StorableVersionedWithLongObj s = storage.prepare(); +        s.setID(500); +        s.setValue("hello"); +        try { +            // Require version property to be set. +            s.tryUpdate(); +            fail(); +        } catch (IllegalStateException e) { +        } + +        s.setVersion(null); +        try { +            // Cannot update that which does not exist. +            s.update(); +            fail(); +        } catch (PersistNoneException e) { +        } + +        s.insert(); + +        assertNull(s.getVersion()); + +        s.setVersion(2L); +        try { +            // Record version mismatch. +            s.tryUpdate(); +            fail(); +        } catch (OptimisticLockException e) { +        } + +        s.setVersion(null); +        s.setValue("world"); +        s.tryUpdate(); + +        assertEquals((Long) 1L, s.getVersion()); +        assertEquals("world", s.getValue()); + +        s.setValue("value"); +        s.tryUpdate(); + +        assertEquals((Long) 2L, s.getVersion()); +        assertEquals("value", s.getValue()); + +        // Since no properties changed, update does not increase version. +        assertEquals(true, s.tryUpdate()); +        assertEquals((Long) 2L, s.getVersion()); +    } + +    public void test_initialVersion() throws Exception { +        Storage<StorableVersioned> storage = getRepository().storageFor(StorableVersioned.class); + +        StorableVersioned s = storage.prepare(); +        s.setID(987); +        s.setValue("hello"); +        assertEquals(0, s.getVersion()); +        s.insert(); +        assertEquals(1, s.getVersion()); + +        s = storage.prepare(); +        s.setID(12345); +        s.setValue("world"); +        assertEquals(0, s.getVersion()); +        s.setVersion(56); +        assertEquals(56, s.getVersion()); +        s.insert(); +        assertEquals(56, s.getVersion()); +    } + +    public void test_initialVersionWithLong() throws Exception { +        Storage<StorableVersionedWithLong> storage = +            getRepository().storageFor(StorableVersionedWithLong.class); + +        StorableVersionedWithLong s = storage.prepare(); +        s.setID(987); +        s.setValue("hello"); +        assertEquals(0, s.getVersion()); +        s.insert(); +        assertEquals(1, s.getVersion()); + +        s = storage.prepare(); +        s.setID(12345); +        s.setValue("world"); +        assertEquals(0, s.getVersion()); +        s.setVersion(56); +        assertEquals(56, s.getVersion()); +        s.insert(); +        assertEquals(56, s.getVersion()); +    } + +    public void test_initialVersionWithObj() throws Exception { +        Storage<StorableVersionedWithObj> storage = +            getRepository().storageFor(StorableVersionedWithObj.class); + +        StorableVersionedWithObj s = storage.prepare(); +        s.setID(987); +        s.setValue("hello"); +        assertNull(s.getVersion()); +        s.insert(); +        assertEquals((Integer) 1, s.getVersion()); + +        s = storage.prepare(); +        s.setID(12345); +        s.setValue("world"); +        assertNull(s.getVersion()); +        s.setVersion(56); +        assertEquals((Integer) 56, s.getVersion()); +        s.insert(); +        assertEquals((Integer) 56, s.getVersion()); +    } + +    public void test_initialVersionWithLongObj() throws Exception { +        Storage<StorableVersionedWithLongObj> storage = +            getRepository().storageFor(StorableVersionedWithLongObj.class); + +        StorableVersionedWithLongObj s = storage.prepare(); +        s.setID(987); +        s.setValue("hello"); +        assertNull(s.getVersion()); +        s.insert(); +        assertEquals((Long) 1L, s.getVersion()); + +        s = storage.prepare(); +        s.setID(12345); +        s.setValue("world"); +        assertNull(s.getVersion()); +        s.setVersion(56L); +        assertEquals((Long) 56L, s.getVersion()); +        s.insert(); +        assertEquals((Long) 56L, s.getVersion()); +    } + +    public void test_versioningMissingRecord() throws Exception { +        Storage<StorableVersioned> storage = getRepository().storageFor(StorableVersioned.class); + +        StorableVersioned s = storage.prepare(); +        s.setID(500); +        s.setValue("hello"); +        s.insert(); + +        // Now delete it from under our feet. +        StorableVersioned s2 = storage.prepare(); +        s2.setID(500); +        s2.delete(); + +        s.setValue("world"); +        s.tryUpdate(); + +        s.insert(); + +        // Delete it again. +        s2.delete(); + +        // Update without changing and properties must still reload, which should fail. +        assertFalse(s.tryUpdate()); +    } + +    public void test_versioningDisabled() throws Exception { +        // Make sure repository works properly when configured as non-master. +        Repository repo = newRepository(false); +        Storage<StorableVersioned> storage = repo.storageFor(StorableVersioned.class); + +        StorableVersioned s = storage.prepare(); +        s.setID(500); +        s.setValue("hello"); +        try { +            // Require version property to be set. +            s.tryUpdate(); +            fail(); +        } catch (IllegalStateException e) { +        } + +        s.setVersion(1); +        assertEquals(false, s.tryUpdate()); + +        s.insert(); + +        s.setVersion(2); +        assertEquals(true, s.tryUpdate()); + +        s.setVersion(1); +        s.setValue("world"); +        s.tryUpdate(); + +        assertEquals(1, s.getVersion()); +        assertEquals("world", s.getValue()); + +        RepairExecutor.waitForRepairsToFinish(10000); + +        repo.close(); +        repo = null; +    } + +    public void test_sequences() throws Exception { +        Storage<StorableSequenced> storage = getRepository().storageFor(StorableSequenced.class); + +        StorableSequenced seq = storage.prepare(); +        seq.setData("hello"); +        seq.insert(); + +        assertEquals(1L, seq.getID()); +        assertEquals(1, seq.getSomeInt()); +        assertEquals(Integer.valueOf(1), seq.getSomeIntegerObj()); +        assertEquals(1L, seq.getSomeLong()); +        assertEquals(Long.valueOf(1L), seq.getSomeLongObj()); +        assertEquals("1", seq.getSomeString()); +        assertEquals("hello", seq.getData()); + +        seq = storage.prepare(); +        seq.setData("foo"); +        seq.insert(); + +        assertEquals(2L, seq.getID()); +        assertEquals(2, seq.getSomeInt()); +        assertEquals(Integer.valueOf(2), seq.getSomeIntegerObj()); +        assertEquals(2L, seq.getSomeLong()); +        assertEquals(Long.valueOf(2L), seq.getSomeLongObj()); +        assertEquals("2", seq.getSomeString()); +        assertEquals("foo", seq.getData()); +         +        seq = storage.prepare(); +        seq.setSomeInt(100); +        seq.setSomeLongObj(null); +        seq.setData("data"); +        seq.insert(); + +        assertEquals(3L, seq.getID()); +        assertEquals(100, seq.getSomeInt()); +        assertEquals(null, seq.getSomeLongObj()); + +        seq = storage.prepare(); +        seq.setData("world"); +        seq.insert(); + +        assertEquals(4L, seq.getID()); +        assertEquals(3, seq.getSomeInt()); +        assertEquals(Integer.valueOf(4), seq.getSomeIntegerObj()); +        assertEquals(4L, seq.getSomeLong()); +        assertEquals(Long.valueOf(3L), seq.getSomeLongObj()); +        assertEquals("4", seq.getSomeString()); +    } + +    public void test_oldIndexEntryDeletion() throws Exception { +        // Very simple test that ensures that old index entries are deleted +        // when the master record is updated. There is no guarantee that the +        // chosen repository supports indexes, and there is no guarantee that +        // it is selecting the desired index. Since the index set is simple and +        // so are the queries, I think it is safe to assume that the selected +        // index is what I expect it to be. Repositories that support +        // custom indexing should have more rigorous tests. + +        Storage<StorableTestBasicIndexed> storage = +            getRepository().storageFor(StorableTestBasicIndexed.class); + +        StorableTestBasicIndexed s = storage.prepare(); +        final int id1 = generateId(1); +        s.setId(id1); +        s.setStringProp("hello"); +        s.setIntProp(3); +        s.setLongProp(4); +        s.setDoubleProp(5); +        s.insert(); + +        s = storage.prepare(); +        final int id6 = generateId(6); +        s.setId(id6); +        s.setStringProp("hello"); +        s.setIntProp(8); +        s.setLongProp(9); +        s.setDoubleProp(10); +        s.insert(); + +        s = storage.prepare(); +        final int id11 = generateId(11); +        s.setId(id11); +        s.setStringProp("world"); +        s.setIntProp(3); +        s.setLongProp(14); +        s.setDoubleProp(15); +        s.insert(); + +        // First verify that queries report what we expect. Don't perform an +        // orderBy on query, as that might interfere with index selection. +        Query<StorableTestBasicIndexed> q = storage.query("stringProp = ?").with("hello"); +        List<StorableTestBasicIndexed> list = q.fetch().toList(); +        assertEquals(2, list.size()); +        if (list.get(0).getId() == id1) { +            assertEquals(id6, list.get(1).getId()); +        } else { +            assertEquals(id6, list.get(0).getId()); +        } + +        q = storage.query("stringProp = ?").with("world"); +        list = q.fetch().toList(); +        assertEquals(1, list.size()); +        assertEquals(id11, list.get(0).getId()); + +        q = storage.query("intProp = ?").with(3); +        list = q.fetch().toList(); +        assertEquals(2, list.size()); +        if (list.get(0).getId() == id1) { +            assertEquals(id11, list.get(1).getId()); +        } else { +            assertEquals(id11, list.get(0).getId()); +        } + +        // Now update and verify changes to query results. +        s = storage.prepare(); +        s.setId(id1); +        s.load(); +        s.setStringProp("world"); +        s.tryUpdate(); + +        q = storage.query("stringProp = ?").with("hello"); +        list = q.fetch().toList(); +        assertEquals(1, list.size()); +        assertEquals(id6, list.get(0).getId()); + +        q = storage.query("stringProp = ?").with("world"); +        list = q.fetch().toList(); +        assertEquals(2, list.size()); +        if (list.get(0).getId() == id1) { +            assertEquals(id11, list.get(1).getId()); +        } else { +            assertEquals(id11, list.get(0).getId()); +        } + +        q = storage.query("intProp = ?").with(3); +        list = q.fetch().toList(); +        assertEquals(2, list.size()); +        if (list.get(0).getId() == id1) { +            assertEquals(id11, list.get(1).getId()); +        } else { +            assertEquals(id11, list.get(0).getId()); +        } +    } + +    public void test_falseDoubleUpdate() throws Exception { +        Storage<StorableTestBasic> storage = getRepository().storageFor(StorableTestBasic.class); + +        StorableTestBasic s = storage.prepare(); +        s.setId(56789); + +        assertFalse(s.tryUpdate()); +        assertFalse(s.tryUpdate()); +    } + +    public void test_dateTimeIndex() throws Exception { +        Storage<StorableDateIndex> storage = getRepository().storageFor(StorableDateIndex.class); + +        DateTimeZone original = DateTimeZone.getDefault(); +        // Set time zone different than defined in storable. +        DateTimeZone.setDefault(DateTimeZone.forID("America/Los_Angeles")); +        try { +            DateTime now = new DateTime(); + +            StorableDateIndex sdi = storage.prepare(); +            sdi.setID(1); +            sdi.setOrderDate(now); +            sdi.insert(); + +            sdi.load(); + +            assertEquals(now.getMillis(), sdi.getOrderDate().getMillis()); +            // Time zones will differ, since adapter is applied upon load. +            assertFalse(now.equals(sdi.getOrderDate())); + +            Query<StorableDateIndex> query = storage.query("orderDate=?").with(now); +            // Tests that adapter is applied to index. Otherwise, consistency +            // check will reject loaded storable. +            StorableDateIndex sdi2 = query.tryLoadOne(); +            assertNotNull(sdi2); +        } finally { +            DateTimeZone.setDefault(original); +        } +    } + +    public void test_joinCache() throws Exception { +        Storage<UserAddress> uaStorage = getRepository().storageFor(UserAddress.class); +        Storage<UserInfo> uiStorage = getRepository().storageFor(UserInfo.class); + +        UserAddress addr = uaStorage.prepare(); +        addr.setAddressID(5); +        addr.setLine1("123"); +        addr.setCity("Seattle"); +        addr.setState("WA"); +        addr.setCountry("USA"); +        addr.insert(); + +        UserInfo user = uiStorage.prepare(); +        user.setUserID(1); +        user.setStateID(1); +        user.setFirstName("John"); +        user.setLastName("Smith"); +        user.setAddress(addr); + +        assertEquals("Seattle", user.getAddress().getCity()); + +        user.insert(); + +        addr.setCity("Bellevue"); +        addr.tryUpdate(); + +        // Should still refer to same address instance. +        assertEquals("Bellevue", user.getAddress().getCity()); + +        UserAddress addr2 = uaStorage.prepare(); +        addr2.setAddressID(5); +        addr2.setCity("Kirkland"); +        addr2.tryUpdate(); + +        // Should still refer to same address instance. +        assertEquals("Bellevue", user.getAddress().getCity()); + +        // Force reload of user should flush cache. +        user.load(); + +        assertEquals("Kirkland", user.getAddress().getCity()); + +        addr2.setCity("Redmond"); +        addr2.tryUpdate(); + +        // Should still refer to same address instance. +        assertEquals("Kirkland", user.getAddress().getCity()); + +        // Update of user should flush cache (even if nothing changed) +        assertEquals(true, user.tryUpdate()); + +        assertEquals("Redmond", user.getAddress().getCity()); + +        addr2.setCity("Renton"); +        addr2.tryUpdate(); + +        // Should still refer to same address instance. +        assertEquals("Redmond", user.getAddress().getCity()); + +        // Update of user should flush cache (when something changed) +        user.setFirstName("Jim"); +        assertEquals(true, user.tryUpdate()); + +        assertEquals("Renton", user.getAddress().getCity()); + +        addr2.setCity("Tacoma"); +        addr2.tryUpdate(); + +        // Should still refer to same address instance. +        assertEquals("Renton", user.getAddress().getCity()); + +        // Delete of user should flush cache +        assertEquals(true, user.tryDelete()); + +        assertEquals("Tacoma", user.getAddress().getCity()); + +        addr2.setCity("Shoreline"); +        addr2.tryUpdate(); + +        // Should still refer to same address instance. +        assertEquals("Tacoma", user.getAddress().getCity()); + +        // Failed load of user should flush cache +        assertEquals(false, user.tryLoad()); + +        assertEquals("Shoreline", user.getAddress().getCity()); + +        addr2.setCity("Vancouver"); +        addr2.tryUpdate(); + +        // Should still refer to same address instance. +        assertEquals("Shoreline", user.getAddress().getCity()); + +        // Insert of user should flush cache +        user.insert(); + +        assertEquals("Vancouver", user.getAddress().getCity()); +    } + +    public void test_updateReload() throws Exception { +        Storage<UserInfo> uiStorage = getRepository().storageFor(UserInfo.class); + +        UserInfo user = uiStorage.prepare(); +        user.setUserID(1); +        user.setStateID(1); +        user.setFirstName("John"); +        user.setLastName("Smith"); +        user.setAddressID(0); +        user.insert(); + +        UserInfo user2 = uiStorage.prepare(); +        user2.setUserID(1); +        user2.setFirstName("Jim"); +        user2.tryUpdate(); + +        assertEquals("John", user.getFirstName()); +        user.tryUpdate(); +        assertEquals("Jim", user.getFirstName()); + +        user2.setFirstName("Bob"); +        user2.tryUpdate(); + +        assertEquals("Jim", user.getFirstName()); +        user.setLastName("Jones"); +        user.tryUpdate(); +        assertEquals("Bob", user.getFirstName()); +        assertEquals("Jones", user.getLastName()); +    } + +    public void test_deleteState() throws Exception { +        Storage<UserInfo> uiStorage = getRepository().storageFor(UserInfo.class); + +        UserInfo user = uiStorage.prepare(); +        user.setUserID(1); +        user.setStateID(1); +        user.setFirstName("John"); +        user.setLastName("Smith"); +        user.setAddressID(0); +        user.insert(); + +        UserInfo user2 = uiStorage.prepare(); +        user2.setUserID(1); +        user2.tryDelete(); + +        assertFalse(user.tryLoad()); + +        // Should be able to change pk now. +        user.setUserID(2); +        assertFalse(user.tryUpdate()); +        user.setUserID(1); +        user.insert(); + +        user2.tryDelete(); + +        assertFalse(user.tryUpdate()); + +        // Should be able to change pk now. +        user.setUserID(2); +        assertFalse(user.tryUpdate()); +        user.setUserID(1); +        user.insert(); +        user2.tryDelete(); + +        user.setFirstName("Jim"); +        assertFalse(user.tryUpdate()); + +        // Should be able to change pk now. +        user.setUserID(2); +        assertFalse(user.tryUpdate()); +        user.setUserID(1); +        user.insert(); +        assertEquals("Jim", user.getFirstName()); +    } + +    public void test_deleteUpdate() throws Exception { +        Storage<UserInfo> uiStorage = getRepository().storageFor(UserInfo.class); + +        UserInfo user = uiStorage.prepare(); +        user.setUserID(1); +        user.setStateID(1); +        user.setFirstName("John"); +        user.setLastName("Smith"); +        user.setAddressID(0); +        user.insert(); + +        // Just want to change first name now +        user.setFirstName("Bob"); + +        // Concurrently, someone else deletes the user +        UserInfo user2 = uiStorage.prepare(); +        user2.setUserID(1); +        assertTrue(user2.tryDelete()); + +        assertFalse(user.tryUpdate()); + +        // Update failed... perhaps we should try again... (wrong decision) + +        // Concurrently, someone inserts a different user with re-used id. (wrong decision) +        user2 = uiStorage.prepare(); +        user2.setUserID(1); +        user2.setStateID(1); +        user2.setFirstName("Mike"); +        user2.setLastName("Jones"); +        user2.setAddressID(0); +        user2.insert(); + +        // Trying the failed update again should totally blow away the sneaked in insert. +        assertTrue(user.tryUpdate()); + +        assertEquals("Bob", user.getFirstName()); +        assertEquals("Smith", user.getLastName()); + +        // The failed update earlier dirtied all the properties, including ones +        // that were not modified. As a result, the second update replaces all +        // properties. This is a special edge case destructive update for which +        // there is no clear solution. If just the one property was changed as +        // instructed earlier, then the record would have been corrupted. This +        // feels much worse. This can still happen if the record was not +        // originally fully clean. + +        // The cause of the error is the user creating a new record with a +        // re-used id. This problem could be prevented using transactions, or +        // it might be detected with optimistic locking. +    } + +    public void test_deleteOne() throws Exception { +        Storage<UserInfo> uiStorage = getRepository().storageFor(UserInfo.class); + +        UserInfo user = uiStorage.prepare(); +        user.setUserID(1); +        user.setStateID(1); +        user.setFirstName("Bob"); +        user.setLastName("Smith"); +        user.setAddressID(0); +        user.insert(); + +        user = uiStorage.prepare(); +        user.setUserID(2); +        user.setStateID(1); +        user.setFirstName("Bob"); +        user.setLastName("Jones"); +        user.setAddressID(0); +        user.insert(); +         +        user = uiStorage.prepare(); +        user.setUserID(3); +        user.setStateID(1); +        user.setFirstName("Indiana"); +        user.setLastName("Jones"); +        user.setAddressID(0); +        user.insert(); + +        try { +            uiStorage.query("lastName = ?").with("Jones").deleteOne(); +            fail(); +        } catch (PersistMultipleException e) { +        } + +        List<UserInfo> list = uiStorage.query().fetch().toList(); +        assertEquals(3, list.size()); + +        uiStorage.query("lastName = ? & firstName = ?").with("Jones").with("Bob").deleteOne(); + +        list = uiStorage.query().orderBy("userID").fetch().toList(); +        assertEquals(2, list.size()); + +        assertEquals("Bob", list.get(0).getFirstName()); +        assertEquals("Smith", list.get(0).getLastName()); +        assertEquals("Indiana", list.get(1).getFirstName()); +        assertEquals("Jones", list.get(1).getLastName()); +    } + +    public void test_triggers() throws Exception { +        Storage<StorableVersioned> storage = getRepository().storageFor(StorableVersioned.class); + +        class InsertTrigger extends Trigger<StorableVersioned> { +            Object mState; + +            @Override +            public Object beforeInsert(StorableVersioned s) { +                assertEquals(1, s.getID()); +                mState = new Object(); +                return mState; +            } + +            @Override +            public void afterInsert(StorableVersioned s, Object state) { +                assertEquals(1, s.getID()); +                assertEquals(mState, state); +            } +        }; + +        InsertTrigger it = new InsertTrigger(); + +        assertTrue(storage.addTrigger(it)); + +        StorableVersioned s = storage.prepare(); +        s.setID(1); +        s.setValue("value"); +        s.insert(); + +        assertTrue(it.mState != null); + +        assertTrue(storage.removeTrigger(it)); + +        class UpdateTrigger extends Trigger<StorableVersioned> { +            Object mState; +            int mVersion; + +            @Override +            public Object beforeUpdate(StorableVersioned s) { +                assertEquals(1, s.getID()); +                mState = new Object(); +                mVersion = s.getVersion(); +                return mState; +            } + +            @Override +            public void afterUpdate(StorableVersioned s, Object state) { +                assertEquals(1, s.getID()); +                assertEquals(mState, state); +                assertEquals(mVersion + 1, s.getVersion()); +            } +        }; + +        UpdateTrigger ut = new UpdateTrigger(); + +        assertTrue(storage.addTrigger(ut)); + +        s.setValue("value2"); +        s.update(); + +        assertTrue(ut.mState != null); + +        assertTrue(storage.removeTrigger(ut)); + +        class DeleteTrigger extends Trigger<StorableVersioned> { +            Object mState; + +            @Override +            public Object beforeDelete(StorableVersioned s) { +                assertEquals(1, s.getID()); +                mState = new Object(); +                return mState; +            } + +            @Override +            public void afterDelete(StorableVersioned s, Object state) { +                assertEquals(1, s.getID()); +                assertEquals(mState, state); +            } +        }; + +        DeleteTrigger dt = new DeleteTrigger(); + +        assertTrue(storage.addTrigger(dt)); + +        s.delete(); + +        assertTrue(dt.mState != null); + +        assertTrue(storage.removeTrigger(dt)); +    } + +    public void test_triggerFailure() throws Exception { +        Storage<StorableVersioned> storage = getRepository().storageFor(StorableVersioned.class); + +        class InsertTrigger extends Trigger<StorableVersioned> { +            boolean failed; + +            @Override +            public Object beforeInsert(StorableVersioned s) { +                throw new RuntimeException(); +            } + +            @Override +            public void afterInsert(StorableVersioned s, Object state) { +                fail(); +            } + +            @Override +            public void failedInsert(StorableVersioned s, Object state) { +                failed = true; +            } +        }; + +        InsertTrigger it = new InsertTrigger(); + +        assertTrue(storage.addTrigger(it)); + +        StorableVersioned s = storage.prepare(); +        s.setID(1); +        s.setValue("value"); +        try { +            s.insert(); +            fail(); +        } catch (RuntimeException e) { +        } + +        assertTrue(it.failed); + +        class UpdateTrigger extends Trigger<StorableVersioned> { +            boolean failed; + +            @Override +            public Object beforeUpdate(StorableVersioned s) { +                throw new RuntimeException(); +            } + +            @Override +            public void afterUpdate(StorableVersioned s, Object state) { +                fail(); +            } + +            @Override +            public void failedUpdate(StorableVersioned s, Object state) { +                failed = true; +            } +        }; + +        UpdateTrigger ut = new UpdateTrigger(); + +        assertTrue(storage.addTrigger(ut)); + +        s = storage.prepare(); +        s.setID(1); +        s.setVersion(3); +        s.setValue("value"); +        try { +            s.update(); +            fail(); +        } catch (RuntimeException e) { +        } + +        assertTrue(ut.failed); + +        class DeleteTrigger extends Trigger<StorableVersioned> { +            boolean failed; + +            @Override +            public Object beforeDelete(StorableVersioned s) { +                throw new RuntimeException(); +            } + +            @Override +            public void afterDelete(StorableVersioned s, Object state) { +                fail(); +            } + +            @Override +            public void failedDelete(StorableVersioned s, Object state) { +                failed = true; +            } +        }; + +        DeleteTrigger dt = new DeleteTrigger(); + +        assertTrue(storage.addTrigger(dt)); + +        s = storage.prepare(); +        s.setID(1); +        try { +            s.delete(); +            fail(); +        } catch (RuntimeException e) { +        } + +        assertTrue(dt.failed); +    } + +    public void test_triggerChecks() throws Exception { +        Storage<StorableTimestamped> storage = +            getRepository().storageFor(StorableTimestamped.class); + +        StorableTimestamped st = storage.prepare(); +        st.setId(1); +        st.setValue("value"); + +        try { +            st.insert(); +            fail(); +        } catch (ConstraintException e) { +            // We forgot to set submitDate and modifyDate. +        } + +        // Install trigger that sets the timestamp properties. + +        storage.addTrigger(new Trigger<Timestamped>() { +            @Override +            public Object beforeInsert(Timestamped st) { +                DateTime now = new DateTime(); +                st.setSubmitDateTime(now); +                st.setModifyDateTime(now); +                return null; +            } + +            @Override +            public Object beforeUpdate(Timestamped st) { +                DateTime now = new DateTime(); +                st.setModifyDateTime(now); +                return null; +            } +        }); + +        st.insert(); + +        assertNotNull(st.getSubmitDateTime()); +        assertNotNull(st.getModifyDateTime()); + +        DateTime dt = st.getModifyDateTime(); + +        Thread.sleep(500); + +        st.setValue("foo"); +        st.update(); + +        assertTrue(st.getModifyDateTime().getMillis() >= dt.getMillis() + 450); +    } + +    public void test_hashCode() throws Exception { +        Storage<StorableTestBasic> storage = +            getRepository().storageFor(StorableTestBasic.class); + +        StorableTestBasic stb = storage.prepare(); +        // Just tests to make sure generated code doesn't throw an error. +        int hashCode = stb.hashCode(); +    } + +    public void test_stateMethods() throws Exception { +        Storage<Order> storage = getRepository().storageFor(Order.class); + +        Order order = storage.prepare(); + +        assertUninitialized(true, order, "orderID", "orderNumber", "orderTotal", "orderComments"); +        assertDirty(false, order, "orderID", "orderNumber", "orderTotal", "orderComments"); +        assertClean(false, order, "orderID", "orderNumber", "orderTotal", "orderComments"); + +        order.setOrderID(1); + +        assertUninitialized(false, order, "orderID"); +        assertUninitialized(true, order, "orderNumber", "orderTotal", "orderComments"); +        assertDirty(false, order, "orderNumber", "orderTotal", "orderComments"); +        assertDirty(true, order, "orderID"); +        assertClean(false, order, "orderID", "orderNumber", "orderTotal", "orderComments"); + +        order.setOrderNumber("123"); +        order.setOrderTotal(456); +        order.setAddressID(789); + +        assertUninitialized(false, order, "orderID", "orderNumber", "orderTotal"); +        assertUninitialized(true, order, "orderComments"); +        assertDirty(true, order, "orderID", "orderNumber", "orderTotal"); +        assertDirty(false, order, "orderComments"); +        assertClean(false, order, "orderID", "orderNumber", "orderTotal", "orderComments"); + +        // Get unknown property +        try { +            order.isPropertyUninitialized("foo"); +            fail(); +        } catch (IllegalArgumentException e) { +        } + +        // Get unknown property +        try { +            order.isPropertyDirty("foo"); +            fail(); +        } catch (IllegalArgumentException e) { +        } + +        // Get unknown property +        try { +            order.isPropertyClean("foo"); +            fail(); +        } catch (IllegalArgumentException e) { +        } + +        // Get join property +        try { +            order.isPropertyUninitialized("address"); +            fail(); +        } catch (IllegalArgumentException e) { +        } + +        // Get join property +        try { +            order.isPropertyDirty("address"); +            fail(); +        } catch (IllegalArgumentException e) { +        } + +        // Get join property +        try { +            order.isPropertyClean("address"); +            fail(); +        } catch (IllegalArgumentException e) { +        } + +        order.insert(); + +        assertUninitialized(false, order, "orderID", "orderNumber", "orderTotal", "orderComments"); +        assertDirty(false, order, "orderID", "orderNumber", "orderTotal", "orderComments"); +        assertClean(true, order, "orderID", "orderNumber", "orderTotal", "orderComments"); +    } + +    public void test_count() throws Exception { +        test_count(false); +    } +    public void test_countIndexed() throws Exception { +        test_count(true); +    } + +    private void test_count(boolean indexed) throws Exception { +        Storage<? extends StorableTestBasic> storage; +        if (indexed) { +            storage = getRepository().storageFor(StorableTestBasicIndexed.class); +        } else { +            storage = getRepository().storageFor(StorableTestBasic.class); +        } + +        assertEquals(0, storage.query().count()); + +        StorableTestBasic sb = storage.prepare(); +        sb.setId(1); +        sb.setIntProp(1); +        sb.setLongProp(1); +        sb.setDoubleProp(1.1); +        sb.setStringProp("one"); +        sb.insert(); + +        assertEquals(1, storage.query().count()); +        assertEquals(0, storage.query().not().count()); +        assertEquals(1, storage.query("stringProp = ?").with("one").count()); +        assertEquals(0, storage.query("stringProp = ?").with("two").count()); + +        sb = storage.prepare(); +        sb.setId(2); +        sb.setIntProp(2); +        sb.setLongProp(2); +        sb.setDoubleProp(2.1); +        sb.setStringProp("two"); +        sb.insert(); + +        sb = storage.prepare(); +        sb.setId(3); +        sb.setIntProp(3); +        sb.setLongProp(3); +        sb.setDoubleProp(3.1); +        sb.setStringProp("three"); +        sb.insert(); + +        assertEquals(3, storage.query().count()); +        assertEquals(0, storage.query().not().count()); +        assertEquals(1, storage.query("stringProp = ?").with("one").count()); +        assertEquals(1, storage.query("stringProp = ?").with("two").count()); +        assertEquals(1, storage.query("stringProp = ?").with("three").count()); +        assertEquals(2, storage.query("stringProp = ?").not().with("one").count()); +        assertEquals(2, storage.query("stringProp = ?").not().with("two").count()); +        assertEquals(2, storage.query("stringProp = ?").not().with("three").count()); +        assertEquals(2, storage.query("stringProp > ?").with("one").count()); +        assertEquals(0, storage.query("stringProp > ?").with("two").count()); +        assertEquals(1, storage.query("stringProp > ?").with("three").count()); +    } + +    public void test_fetchAfter() throws Exception { +        Storage<ManyKeys2> storage = getRepository().storageFor(ManyKeys2.class); + +        final int groupSize = 4; +        final int aliasing = groupSize + 1; + +        int total = 0; +        for (int a=0; a<groupSize; a++) { +            for (int b=0; b<groupSize; b++) { +                for (int c=0; c<groupSize; c++) { +                    for (int d=0; d<groupSize; d++) { +                        for (int e=0; e<groupSize; e++) { +                            for (int f=0; f<groupSize; f++) { +                                ManyKeys2 obj = storage.prepare(); +                                obj.setA(a); +                                obj.setB(b); +                                obj.setC(c); +                                obj.setD(d); +                                obj.setE(e); +                                obj.setF(f); +                                obj.insert(); +                                total++; +                            } +                        } +                    } +                } +            } +        } + +        String[] orderBy = {"a", "b", "c", "d", "e", "f"}; +        Query<ManyKeys2> query = storage.query().orderBy(orderBy); +        Cursor<ManyKeys2> cursor = query.fetch(); +        Comparator<ManyKeys2> comparator = SortedCursor.createComparator(ManyKeys2.class, orderBy); + +        int actual = 0; +        ManyKeys2 last = null; +        while (cursor.hasNext()) { +            actual++; +            ManyKeys2 obj = cursor.next(); +            if (last != null) { +                assertTrue(comparator.compare(last, obj) < 0); +            } +            if (actual % aliasing == 0) { +                cursor.close(); +                cursor = query.fetchAfter(obj); +            } +            last = obj; +        } + +        assertEquals(total, actual); + +        // Try again in reverse + +        orderBy = new String[] {"-a", "-b", "-c", "-d", "-e", "-f"}; +        query = storage.query().orderBy(orderBy); +        cursor = query.fetch(); + +        actual = 0; +        last = null; +        while (cursor.hasNext()) { +            actual++; +            ManyKeys2 obj = cursor.next(); +            if (last != null) { +                assertTrue(comparator.compare(last, obj) > 0); +            } +            if (actual % aliasing == 0) { +                cursor.close(); +                cursor = query.fetchAfter(obj); +            } +            last = obj; +        } + +        assertEquals(total, actual); + +        // Try again with funny mix of orderings. This will likely cause sort +        // operations to be performed, thus making it very slow. + +        orderBy = new String[] {"-a", "b", "-c", "d", "-e", "f"}; +        query = storage.query().orderBy(orderBy); +        cursor = query.fetch(); +        comparator = SortedCursor.createComparator(ManyKeys2.class, orderBy); + +        actual = 0; +        last = null; +        while (cursor.hasNext()) { +            actual++; +            ManyKeys2 obj = cursor.next(); +            if (last != null) { +                assertTrue(comparator.compare(last, obj) < 0); +            } +            if (actual % aliasing == 0) { +                cursor.close(); +                cursor = query.fetchAfter(obj); +            } +            last = obj; +        } + +        assertEquals(total, actual); +    } + +    private void assertUninitialized(boolean expected, Storable storable, String... properties) { +        for (String property : properties) { +            assertEquals(expected, storable.isPropertyUninitialized(property)); +        } +    } + +    private void assertDirty(boolean expected, Storable storable, String... properties) { +        for (String property : properties) { +            assertEquals(expected, storable.isPropertyDirty(property)); +        } +    } + +    private void assertClean(boolean expected, Storable storable, String... properties) { +        for (String property : properties) { +            assertEquals(expected, storable.isPropertyClean(property)); +        } +    } + +    @PrimaryKey("id") +    public interface StorableTestAssymetricGet extends Storable{ +        public abstract int getId(); +        public abstract void setId(int id); + +        public abstract int getAssymetricGET(); +    } + +    @PrimaryKey("id") +    public interface StorableTestAssymetricSet extends Storable{ +        public abstract int getId(); +        public abstract void setId(int id); + +        public abstract int setAssymetricSET(); +    } + +    @PrimaryKey("id") +    public interface StorableTestAssymetricGetSet extends Storable{ +        public abstract int getId(); +        public abstract void setId(int id); + +        public abstract int getAssymetricGET(); + +        public abstract int setAssymetricSET(); +    } + +    public static class InvocationTracker extends StorableTestBasic implements WrappedSupport { +        String mName; +        long mInvocationTracks; + +        boolean mTrace; + +        public InvocationTracker(String name) { +            this(name, false); +        } + +        public InvocationTracker(final String name, boolean trace) { +            mName = name; +            mTrace = trace; +            clearTracks(); +        } + +        public void clearTracks() { +            mInvocationTracks = 0; +        } + +        public long getTracks() { +            return mInvocationTracks; +        } + +        public void assertTrack(long value) { +            assertEquals(value, getTracks()); +            clearTracks(); +        } + + +        public void setId(int id) { +            if (mTrace) System.out.println("setId"); +            mInvocationTracks |= sSetId; +        } + +        // Basic coverage of the primitives +        public String getStringProp() { +            if (mTrace) System.out.println("getStringProp"); +            mInvocationTracks |= sGetStringProp;  // 0x2 +            return null; +        } + +        public void setStringProp(String aStringThing) { +            if (mTrace) System.out.println("setStringProp"); +            mInvocationTracks |= sSetStringProp;  // 0x4 +        } + +        public int getIntProp() { +            if (mTrace) System.out.println("getIntProp"); +            mInvocationTracks |= sGetIntProp; // 0x8 +            return 0; +        } + +        public void setIntProp(int anInt) { +            if (mTrace) System.out.println("setIntProp"); +            mInvocationTracks |= sSetIntProp; // 0x10 +        } + +        public long getLongProp() { +            if (mTrace) System.out.println("getLongProp"); +            mInvocationTracks |= sGetLongProp;  // 0x20 +            return 0; +        } + +        public void setLongProp(long aLong) { +            if (mTrace) System.out.println("setLongProp"); +            mInvocationTracks |= sSetLongProp;   // 0x40 +        } + +        public double getDoubleProp() { +            if (mTrace) System.out.println("getDoubleProp"); +            mInvocationTracks |= sGetDoubleProp;   // 0x80 +            return 0; +        } + +        public void setDoubleProp(double aDouble) { +            if (mTrace) System.out.println("setDoubleProp"); +            mInvocationTracks |= sSetDoubleProp;   // 0x100 +        } + +        public DateTime getDate() { +            return null; +        } + +        public void setDate(DateTime date) { +        } + +        public void load() throws FetchException { +            if (mTrace) System.out.println("load"); +            mInvocationTracks |= sLoad;  // 0x200 +        } + +        public boolean tryLoad() throws FetchException { +            if (mTrace) System.out.println("tryLoad"); +            mInvocationTracks |= sTryLoad;  // 0x400 +            return false; +        } + +        public void insert() throws PersistException { +            if (mTrace) System.out.println("insert"); +            mInvocationTracks |= sInsert;  // 0x800 +        } + +        public boolean tryInsert() throws PersistException { +            if (mTrace) System.out.println("tryInsert"); +            mInvocationTracks |= sTryInsert; +            return false; +        } + +        public void update() throws PersistException { +            if (mTrace) System.out.println("update"); +            mInvocationTracks |= sUpdate; +        } + +        public boolean tryUpdate() throws PersistException { +            if (mTrace) System.out.println("tryUpdate"); +            mInvocationTracks |= sTryUpdate;    // 0x1000 +            return false; +        } + +        public void delete() throws PersistException { +            if (mTrace) System.out.println("delete"); +            mInvocationTracks |= sDelete; +        } + +        public boolean tryDelete() throws PersistException { +            if (mTrace) System.out.println("tryDelete"); +            mInvocationTracks |= sTryDelete;    // 0x2000 +            return false; +        } + +        public WrappedSupport createSupport(Storable storable) { +            return new InvocationTracker(mName, mTrace); +        } + +        public Storage storage() { +            if (mTrace) System.out.println("storage"); +            mInvocationTracks |= sStorage;    // 0x4000 +            return null; +        } + +        public Storable copy() { +            if (mTrace) System.out.println("copy"); +            mInvocationTracks |= sCopy;    // 0x8000 +            return null; +        } + +        public String toStringKeyOnly() { +            if (mTrace) System.out.println("toStringKeyOnly"); +            mInvocationTracks |= sToStringKeyOnly;    // 0x10000 +            return null; +        } + +        public int getId() { +            if (mTrace) System.out.println("getId"); +            mInvocationTracks |= sGetId;   // 0x20000 +            return 0; +        } + +        public void copyAllProperties(Storable storable) { +            if (mTrace) System.out.println("copyAllProperties"); +            mInvocationTracks |= sCopyAllProperties;   // 0x40000 +        } + +        public void copyPrimaryKeyProperties(Storable storable) { +            if (mTrace) System.out.println("copyPrimaryKeyProperties"); +            mInvocationTracks |= sCopyPrimaryKeyProperties;   // 0x80000 +        } + +        public void copyUnequalProperties(Storable storable) { +            if (mTrace) System.out.println("copyUnequalProperties"); +            mInvocationTracks |= sCopyUnequalProperties;   // 0x10 0000 +        } + +        public void copyDirtyProperties(Storable storable) { +            if (mTrace) System.out.println("copyDirtyProperties"); +            mInvocationTracks |= sCopyDirtyProperties;   // 0x20 0000 +        } + +        public boolean hasDirtyProperties() { +            if (mTrace) System.out.println("hasDirtyProperties"); +            mInvocationTracks |= sHasDirtyProperties;   // 0x200 0000 +            return false; +        } + +        public boolean equalPrimaryKeys(Object obj) { +            if (mTrace) System.out.println("equalPrimaryKeys"); +            mInvocationTracks |= sEqualKeys;   // 0x40 0000 +            return true; +        } + +        public boolean equalProperties(Object obj) { +            if (mTrace) System.out.println("equalProperties"); +            mInvocationTracks |= sEqualProperties;   // 0x80 0000 +            return true; +        } + +        public void copyVersionProperty(Storable storable) { +            if (mTrace) System.out.println("copyVersionProperty"); +            mInvocationTracks |= sCopyVersionProperty;   // 0x100 0000 +        } + +        public void markPropertiesClean() { +            if (mTrace) System.out.println("markPropertiesClean"); +            mInvocationTracks |= sMarkPropertiesClean;   // 0x400 0000 +        } + +        public void markAllPropertiesClean() { +            if (mTrace) System.out.println("markAllPropertiesClean"); +            mInvocationTracks |= sMarkAllPropertiesClean;   // 0x800 0000 +        } + +        public void markPropertiesDirty() { +            if (mTrace) System.out.println("markPropertiesDirty"); +            mInvocationTracks |= sMarkPropertiesDirty;   // 0x1000 0000 +        } + +        public void markAllPropertiesDirty() { +            if (mTrace) System.out.println("markAllPropertiesDirty"); +            mInvocationTracks |= sMarkAllPropertiesDirty;   // 0x2000 0000 +        } + +        public Class storableType() { +            if (mTrace) System.out.println("storableType"); +            mInvocationTracks |= sStorableType;   // 0x4000 0000 +            return Storable.class; +        } + +        public boolean isPropertyUninitialized(String name) { +            if (mTrace) System.out.println("isPropertyUninitialized"); +            return false; +        } + +        public boolean isPropertyDirty(String name) { +            if (mTrace) System.out.println("isPropertyDirty"); +            return false; +        } + +        public boolean isPropertyClean(String name) { +            if (mTrace) System.out.println("isPropertyClean"); +            return false; +        } + +        public boolean isPropertySupported(String name) { +            if (mTrace) System.out.println("isPropertySupported"); +            return false; +        } + +        public Repository getRootRepository() { +            return null; +        } + +        public Trigger getInsertTrigger() { +            return null; +        } + +        public Trigger getUpdateTrigger() { +            return null; +        } + +        public Trigger getDeleteTrigger() { +            return null; +        } +    } + +} diff --git a/src/test/java/com/amazon/carbonado/cursor/EmptyCursorFactory.java b/src/test/java/com/amazon/carbonado/cursor/EmptyCursorFactory.java new file mode 100644 index 0000000..fa27350 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/cursor/EmptyCursorFactory.java @@ -0,0 +1,30 @@ +/*
 + * 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.cursor;
 +
 +/**
 + * Creates new EmptyCursor instances, for tests in other packages.
 + *
 + * @author Brian S O'Neill
 + */
 +public class EmptyCursorFactory {
 +    public static EmptyCursor newEmptyCursor() {
 +        return new EmptyCursor();
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/cursor/TestCursors.java b/src/test/java/com/amazon/carbonado/cursor/TestCursors.java new file mode 100644 index 0000000..fa5d8a7 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/cursor/TestCursors.java @@ -0,0 +1,428 @@ +/*
 + * 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.cursor;
 +
 +import java.util.Arrays;
 +import java.util.Comparator;
 +import java.util.List;
 +import java.util.NoSuchElementException;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Cursor;
 +import com.amazon.carbonado.FetchException;
 +
 +import com.amazon.carbonado.stored.Dummy;
 +import com.amazon.carbonado.stored.StorableTestMinimal;
 +
 +/**
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestCursors extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestCursors.class);
 +    }
 +
 +    public TestCursors(String name) {
 +        super(name);
 +    }
 +
 +    protected void setUp() {
 +    }
 +
 +    protected void tearDown() {
 +    }
 +
 +    public void testSingleton() throws Exception {
 +        try {
 +            new SingletonCursor<Object>(null);
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +
 +        Cursor<String> cursor = new SingletonCursor<String>("hello");
 +
 +        assertTrue(cursor.hasNext());
 +        assertEquals("hello", cursor.next());
 +        assertFalse(cursor.hasNext());
 +        try {
 +            cursor.next();
 +            fail();
 +        } catch (NoSuchElementException e) {
 +        }
 +
 +        assertEquals(0, cursor.skipNext(1));
 +
 +        cursor = new SingletonCursor<String>("world");
 +        List<String> list = cursor.toList(0);
 +        assertEquals(0, list.size());
 +        list = cursor.toList(10);
 +        assertEquals(1, list.size());
 +        assertEquals("world", list.get(0));
 +
 +        cursor = new SingletonCursor<String>("world");
 +        cursor.close();
 +        assertFalse(cursor.hasNext());
 +
 +        cursor = new SingletonCursor<String>("world");
 +        assertEquals(1, cursor.skipNext(2));
 +        assertEquals(0, cursor.skipNext(1));
 +        assertFalse(cursor.hasNext());
 +    }
 +
 +    public void testUnion() throws Exception {
 +        Cursor<Element> left, right, union;
 +
 +        // Two empty sets.
 +        left  = createElements();
 +        right = createElements();
 +        union = new UnionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(union);
 +
 +        // Right set empty.
 +        left  = createElements(1, 2, 3, 4);
 +        right = createElements();
 +        union = new UnionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(union, 1, 2, 3, 4);
 +
 +        // Left set empty.
 +        left  = createElements();
 +        right = createElements(3, 4, 5, 6);
 +        union = new UnionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(union, 3, 4, 5, 6);
 +
 +        // No overlap.
 +        left  = createElements(1, 2, 3         );
 +        right = createElements(         4, 5, 6);
 +        union = new UnionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(union, 1, 2, 3, 4, 5, 6);
 +
 +        // Overlap.
 +        left  = createElements(1, 2, 3, 4      );
 +        right = createElements(      3, 4, 5, 6);
 +        union = new UnionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(union, 1, 2, 3, 4, 5, 6);
 +
 +        // Swapped overlap.
 +        left  = createElements(      3, 4, 5, 6);
 +        right = createElements(1, 2, 3, 4      );
 +        union = new UnionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(union, 1, 2, 3, 4, 5, 6);
 +
 +        // Equivalent.
 +        left  = createElements(1, 2, 3, 4, 5, 6);
 +        right = createElements(1, 2, 3, 4, 5, 6);
 +        union = new UnionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(union, 1, 2, 3, 4, 5, 6);
 +
 +        // Complex.
 +        left  = createElements(1, 2, 3,    5, 6, 7,           11, 12, 13,             17, 18, 19);
 +        right = createElements(1, 2,    4, 5, 6,       9, 10,         13, 14,         17, 18    );
 +        union = new UnionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(union, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 17, 18, 19);
 +
 +        // Dups.
 +        left  = createElements(1,             2, 2,    3, 3, 3, 3         );
 +        right = createElements(1, 1, 1, 1, 1, 2, 2, 2,             4, 4, 4);
 +        union = new UnionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(union, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4);
 +    }
 +
 +    public void testIntersection() throws Exception {
 +        Cursor<Element> left, right, inter;
 +
 +        // Two empty sets.
 +        left  = createElements();
 +        right = createElements();
 +        inter = new IntersectionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(inter);
 +
 +        // Right set empty.
 +        left  = createElements(1, 2, 3, 4);
 +        right = createElements();
 +        inter = new IntersectionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(inter);
 +
 +        // Left set empty.
 +        left  = createElements();
 +        right = createElements(3, 4, 5, 6);
 +        inter = new IntersectionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(inter);
 +
 +        // No overlap.
 +        left  = createElements(1, 2, 3         );
 +        right = createElements(         4, 5, 6);
 +        inter = new IntersectionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(inter);
 +
 +        // Overlap.
 +        left  = createElements(1, 2, 3, 4      );
 +        right = createElements(      3, 4, 5, 6);
 +        inter = new IntersectionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(inter, 3, 4);
 +
 +        // Swapped overlap.
 +        left  = createElements(      3, 4, 5, 6);
 +        right = createElements(1, 2, 3, 4      );
 +        inter = new IntersectionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(inter, 3, 4);
 +
 +        // Equivalent.
 +        left  = createElements(1, 2, 3, 4, 5, 6);
 +        right = createElements(1, 2, 3, 4, 5, 6);
 +        inter = new IntersectionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(inter, 1, 2, 3, 4, 5, 6);
 +
 +        // Complex.
 +        left  = createElements(1, 2, 3,    5, 6, 7,           11, 12, 13,             17, 18, 19);
 +        right = createElements(1, 2,    4, 5, 6,       9, 10,         13, 14,         17, 18    );
 +        inter = new IntersectionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(inter, 1, 2, 5, 6, 13, 17, 18);
 +
 +        // Dups.
 +        left  = createElements(1,             2, 2,    3, 3, 3, 3         );
 +        right = createElements(1, 1, 1, 1, 1, 2, 2, 2,             4, 4, 4);
 +        inter = new IntersectionCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(inter, 1, 2, 2);
 +    }
 +
 +    public void testDifference() throws Exception {
 +        Cursor<Element> left, right, diff;
 +
 +        // Two empty sets.
 +        left  = createElements();
 +        right = createElements();
 +        diff  = new DifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff);
 +
 +        // Right set empty.
 +        left  = createElements(1, 2, 3, 4);
 +        right = createElements();
 +        diff  = new DifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 1, 2, 3, 4);
 +
 +        // Left set empty.
 +        left  = createElements();
 +        right = createElements(3, 4, 5, 6);
 +        diff  = new DifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff);
 +
 +        // No overlap.
 +        left  = createElements(1, 2, 3         );
 +        right = createElements(         4, 5, 6);
 +        diff  = new DifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 1, 2, 3);
 +
 +        // Overlap.
 +        left  = createElements(1, 2, 3, 4      );
 +        right = createElements(      3, 4, 5, 6);
 +        diff  = new DifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 1, 2);
 +
 +        // Swapped overlap.
 +        left  = createElements(      3, 4, 5, 6);
 +        right = createElements(1, 2, 3, 4      );
 +        diff  = new DifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 5, 6);
 +
 +        // Equivalent.
 +        left  = createElements(1, 2, 3, 4, 5, 6);
 +        right = createElements(1, 2, 3, 4, 5, 6);
 +        diff  = new DifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff);
 +
 +        // Complex.
 +        left  = createElements(1, 2, 3,    5, 6, 7,           11, 12, 13,             17, 18, 19);
 +        right = createElements(1, 2,    4, 5, 6,       9, 10,         13, 14,         17, 18    );
 +        diff  = new DifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 3, 7, 11, 12, 19);
 +
 +        // Dups.
 +        left  = createElements(1,             2, 2,    3, 3, 3, 3         );
 +        right = createElements(1, 1, 1, 1, 1, 2, 2, 2,             4, 4, 4);
 +        diff  = new DifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 3, 3, 3, 3);
 +    }
 +
 +    public void testSymmetricDifference() throws Exception {
 +        Cursor<Element> left, right, diff;
 +
 +        // Two empty sets.
 +        left  = createElements();
 +        right = createElements();
 +        diff  = new SymmetricDifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff);
 +
 +        // Right set empty.
 +        left  = createElements(1, 2, 3, 4);
 +        right = createElements();
 +        diff  = new SymmetricDifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 1, 2, 3, 4);
 +
 +        // Left set empty.
 +        left  = createElements();
 +        right = createElements(3, 4, 5, 6);
 +        diff  = new SymmetricDifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 3, 4, 5, 6);
 +
 +        // No overlap.
 +        left  = createElements(1, 2, 3         );
 +        right = createElements(         4, 5, 6);
 +        diff  = new SymmetricDifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 1, 2, 3, 4, 5, 6);
 +
 +        // Overlap.
 +        left  = createElements(1, 2, 3, 4      );
 +        right = createElements(      3, 4, 5, 6);
 +        diff  = new SymmetricDifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 1, 2, 5, 6);
 +
 +        // Swapped overlap.
 +        left  = createElements(      3, 4, 5, 6);
 +        right = createElements(1, 2, 3, 4      );
 +        diff  = new SymmetricDifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 1, 2, 5, 6);
 +
 +        // Equivalent.
 +        left  = createElements(1, 2, 3, 4, 5, 6);
 +        right = createElements(1, 2, 3, 4, 5, 6);
 +        diff  = new SymmetricDifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff);
 +
 +        // Complex.
 +        left  = createElements(1, 2, 3,    5, 6, 7,           11, 12, 13,             17, 18, 19);
 +        right = createElements(1, 2,    4, 5, 6,       9, 10,         13, 14,         17, 18    );
 +        diff  = new SymmetricDifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 3, 4, 7, 9, 10, 11, 12, 14, 19);
 +
 +        // Dups.
 +        left  = createElements(1,             2, 2,    3, 3, 3, 3         );
 +        right = createElements(1, 1, 1, 1, 1, 2, 2, 2,             4, 4, 4);
 +        diff  = new SymmetricDifferenceCursor<Element>(left, right, new ElementComparator());
 +
 +        compareElements(diff, 1, 1, 1, 1, 2, 3, 3, 3, 3, 4, 4, 4);
 +    }
 +
 +    private Cursor<Element> createElements(int... ids) {
 +        Arrays.sort(ids);
 +        Element[] elements = new Element[ids.length];
 +        for (int i=0; i<ids.length; i++) {
 +            elements[i] = new Element(ids[i]);
 +        }
 +        return new IteratorCursor<Element>(Arrays.asList(elements));
 +    }
 +
 +    private void compareElements(Cursor<Element> elements, int... expectedIDs)
 +        throws FetchException
 +    {
 +        for (int id : expectedIDs) {
 +            if (elements.hasNext()) {
 +                Element e = elements.next();
 +                if (e.getId() != id) {
 +                    fail("Element mismatch: expected=" + id + ", actual=" + e.getId());
 +                    elements.close();
 +                    return;
 +                }
 +            } else {
 +                fail("Too few elements in cursor");
 +                return;
 +            }
 +        }
 +
 +        if (elements.hasNext()) {
 +            Element e = elements.next();
 +            fail("Too many elements in cursor: " + e.getId());
 +            elements.close();
 +        }
 +    }
 +
 +    private static class Element extends Dummy implements StorableTestMinimal {
 +        private int mID;
 +
 +        Element(int id) {
 +            mID = id;
 +        }
 +
 +        public int getId() {
 +            return mID;
 +        }
 +
 +        public void setId(int id) {
 +            mID = id;
 +        }
 +
 +        public String toString() {
 +            return "element " + mID;
 +        }
 +    }
 +
 +    private static class ElementComparator implements Comparator<Element> {
 +        public int compare(Element a, Element b) {
 +            int ai = a.getId();
 +            int bi = b.getId();
 +            if (ai < bi) {
 +                return -1;
 +            } else if (ai > bi) {
 +                return 1;
 +            }
 +            return 0;
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/cursor/TestGroupedCursor.java b/src/test/java/com/amazon/carbonado/cursor/TestGroupedCursor.java new file mode 100644 index 0000000..3d5e42f --- /dev/null +++ b/src/test/java/com/amazon/carbonado/cursor/TestGroupedCursor.java @@ -0,0 +1,184 @@ +/*
 + * 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.cursor;
 +
 +import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Cursor;
 +import com.amazon.carbonado.FetchException;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestGroupedCursor extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestGroupedCursor.class);
 +    }
 +
 +    public TestGroupedCursor(String name) {
 +        super(name);
 +    }
 +
 +    protected void setUp() {
 +    }
 +
 +    protected void tearDown() {
 +    }
 +
 +    public void testGrouping() throws Exception {
 +        List<Triple> triples = new ArrayList<Triple>();
 +        triples.add(new Triple(1, 1, 1));
 +        triples.add(new Triple(1, 1, 2));
 +        triples.add(new Triple(1, 1, 3));
 +        triples.add(new Triple(1, 2, 5));
 +        triples.add(new Triple(1, 2, 9));
 +        triples.add(new Triple(3, 1, 10));
 +        triples.add(new Triple(3, 2, 16));
 +        triples.add(new Triple(3, 2, 100));
 +
 +        // Should already be sorted, but no harm done
 +        Collections.sort(triples);
 +
 +        Cursor<Pair> cursor = new GroupedCursor<Triple, Pair>
 +            (new IteratorCursor<Triple>(triples), Triple.class, "a", "b")
 +        {
 +            private Pair aggregate;
 +
 +            protected void beginGroup(Triple groupLeader) {
 +                aggregate = new Pair(groupLeader.getA(), groupLeader.getB());
 +                aggregate.add(groupLeader.getC());
 +            }
 +
 +            protected void addToGroup(Triple groupMember) {
 +                aggregate.add(groupMember.getC());
 +            }
 +
 +            protected Pair finishGroup() {
 +                return aggregate;
 +            }
 +        };
 +
 +        List<Pair> pairs = new ArrayList<Pair>();
 +
 +        while (cursor.hasNext()) {
 +            pairs.add(cursor.next());
 +        }
 +
 +        assertEquals(4, pairs.size());
 +
 +        assertEquals(1, pairs.get(0).getA());
 +        assertEquals(1, pairs.get(0).getB());
 +        assertEquals(6, pairs.get(0).sum);
 +
 +        assertEquals(1, pairs.get(1).getA());
 +        assertEquals(2, pairs.get(1).getB());
 +        assertEquals(14, pairs.get(1).sum);
 +
 +        assertEquals(3, pairs.get(2).getA());
 +        assertEquals(1, pairs.get(2).getB());
 +        assertEquals(10, pairs.get(2).sum);
 +
 +        assertEquals(3, pairs.get(3).getA());
 +        assertEquals(2, pairs.get(3).getB());
 +        assertEquals(116, pairs.get(3).sum);
 +    }
 +
 +    static int compare(int x, int y) {
 +        if (x < y) {
 +            return -1;
 +        } else if (x > y) {
 +            return 1;
 +        }
 +        return 0;
 +    }
 +
 +    public static class Pair implements Comparable {
 +        final int a;
 +        final int b;
 +
 +        int sum;
 +
 +        Pair(int a, int b) {
 +            this.a = a;
 +            this.b = b;
 +        }
 +
 +        public int getA() {
 +            return a;
 +        }
 +
 +        public int getB() {
 +            return b;
 +        }
 +
 +        public void add(int x) {
 +            sum += x;
 +        }
 +
 +        public int compareTo(Object obj) {
 +            Pair other = (Pair) obj;
 +            int result = compare(a, other.a);
 +            if (result == 0) {
 +                result = compare(b, other.b);
 +            }
 +            return result;
 +        }
 +
 +        public String toString() {
 +            return "a=" + a + ", b=" + b + ", sum=" + sum;
 +        }
 +    }
 +
 +    public static class Triple extends Pair {
 +        final int c;
 +
 +        Triple(int a, int b, int c) {
 +            super(a, b);
 +            this.c = c;
 +        }
 +
 +        public int getC() {
 +            return c;
 +        }
 +
 +        public int compareTo(Object obj) {
 +            int result = super.compareTo(obj);
 +            if (result == 0) {
 +                Triple other = (Triple) obj;
 +                result = compare(c, other.c);
 +            }
 +            return result;
 +        }
 +
 +        public String toString() {
 +            return "a=" + a + ", b=" + b + ", c=" + c;
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/filter/TestFilterAsJoinedFrom.java b/src/test/java/com/amazon/carbonado/filter/TestFilterAsJoinedFrom.java new file mode 100644 index 0000000..3b6bfde --- /dev/null +++ b/src/test/java/com/amazon/carbonado/filter/TestFilterAsJoinedFrom.java @@ -0,0 +1,156 @@ +/*
 + * 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.filter;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.stored.Address;
 +import com.amazon.carbonado.stored.Order;
 +import com.amazon.carbonado.stored.Shipment;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestFilterAsJoinedFrom extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestFilterAsJoinedFrom.class);
 +    }
 +
 +    public TestFilterAsJoinedFrom(String name) {
 +        super(name);
 +    }
 +
 +    public void testOpen() {
 +	Filter<Address> filter = Filter.getOpenFilter(Address.class);
 +
 +	Filter<Order> f2 = filter.asJoinedFrom(Order.class, "address");
 +	assertTrue(f2 instanceof OpenFilter);
 +	assertEquals(Order.class, f2.getStorableType());
 +
 +	Filter<Shipment> f3 = filter.asJoinedFrom(Shipment.class, "order.address");
 +	assertTrue(f3 instanceof OpenFilter);
 +	assertEquals(Shipment.class, f3.getStorableType());
 +
 +	Filter<Shipment> f4 = f2.asJoinedFrom(Shipment.class, "order");
 +	assertEquals(f3, f4);
 +	assertTrue(f3 == f4);
 +    }
 +
 +    public void testClosed() {
 +	Filter<Address> filter = Filter.getClosedFilter(Address.class);
 +
 +	Filter<Order> f2 = filter.asJoinedFrom(Order.class, "address");
 +	assertTrue(f2 instanceof ClosedFilter);
 +	assertEquals(Order.class, f2.getStorableType());
 +
 +	Filter<Shipment> f3 = filter.asJoinedFrom(Shipment.class, "order.address");
 +	assertTrue(f3 instanceof ClosedFilter);
 +	assertEquals(Shipment.class, f3.getStorableType());
 +
 +	Filter<Shipment> f4 = f2.asJoinedFrom(Shipment.class, "order");
 +	assertEquals(f3, f4);
 +	assertTrue(f3 == f4);
 +    }
 +
 +    public void testSimple() {
 +	Filter<Address> filter = Filter.filterFor(Address.class, "addressCity = ?");
 +
 +	Filter<Order> f2 = filter.asJoinedFrom(Order.class, "address");
 +	assertEquals(f2, Filter.filterFor(Order.class, "address.addressCity = ?"));
 +
 +	Filter<Shipment> f3 = filter.asJoinedFrom(Shipment.class, "order.address");
 +	assertEquals(f3, Filter.filterFor(Shipment.class, "order.address.addressCity = ?"));
 +
 +	Filter<Shipment> f4 = f2.asJoinedFrom(Shipment.class, "order");
 +	assertEquals(f3, f4);
 +	assertTrue(f3 == f4);
 +    }
 +
 +    public void testAnd() {
 +	Filter<Address> filter = Filter.filterFor
 +	    (Address.class, "addressCity = ? & addressZip != ?");
 +
 +	Filter<Order> f2 = filter.asJoinedFrom(Order.class, "address");
 +	assertEquals(f2, Filter.filterFor
 +		     (Order.class, "address.addressCity = ? & address.addressZip != ?"));
 +
 +	Filter<Shipment> f3 = filter.asJoinedFrom(Shipment.class, "order.address");
 +	assertEquals(f3, Filter.filterFor
 +		     (Shipment.class,
 +		      "order.address.addressCity = ? & order.address.addressZip != ?"));
 +
 +	Filter<Shipment> f4 = f2.asJoinedFrom(Shipment.class, "order");
 +	assertEquals(f3, f4);
 +	assertTrue(f3 == f4);
 +    }
 +
 +    public void testOr() {
 +	Filter<Address> filter = Filter.filterFor
 +	    (Address.class, "addressCity = ? | addressZip != ?");
 +
 +	Filter<Order> f2 = filter.asJoinedFrom(Order.class, "address");
 +	assertEquals(f2, Filter.filterFor
 +		     (Order.class, "address.addressCity = ? | address.addressZip != ?"));
 +
 +	Filter<Shipment> f3 = filter.asJoinedFrom(Shipment.class, "order.address");
 +	assertEquals(f3, Filter.filterFor
 +		     (Shipment.class,
 +		      "order.address.addressCity = ? | order.address.addressZip != ?"));
 +
 +	Filter<Shipment> f4 = f2.asJoinedFrom(Shipment.class, "order");
 +	assertEquals(f3, f4);
 +	assertTrue(f3 == f4);
 +    }
 +
 +    public void testMixed() {
 +	Filter<Address> filter = Filter.filterFor
 +	    (Address.class, "addressCity = ? & addressZip != ? | addressState > ?");
 +
 +	Filter<Order> f2 = filter.asJoinedFrom(Order.class, "address");
 +	assertEquals
 +	    (f2, Filter.filterFor
 +	     (Order.class,
 +	      "address.addressCity = ? & address.addressZip != ? | address.addressState > ?"));
 +
 +	Filter<Shipment> f3 = filter.asJoinedFrom(Shipment.class, "order.address");
 +	assertEquals(f3, Filter.filterFor
 +		     (Shipment.class,
 +		      "order.address.addressCity = ? & order.address.addressZip != ? " +
 +		      " | order.address.addressState > ?"));
 +	Filter<Shipment> f4 = f2.asJoinedFrom(Shipment.class, "order");
 +	assertEquals(f3, f4);
 +	assertTrue(f3 == f4);
 +    }
 +
 +    public void testError() {
 +	try {
 +	    Filter<Address> filter = Filter.filterFor(Address.class, "addressCity = ?");
 +	    Filter<Order> f2 = filter.asJoinedFrom(Order.class, "orderTotal");
 +	    fail();
 +	} catch (IllegalArgumentException e) {
 +	}
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/filter/TestFilterNotJoinedFrom.java b/src/test/java/com/amazon/carbonado/filter/TestFilterNotJoinedFrom.java new file mode 100644 index 0000000..1e97d89 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/filter/TestFilterNotJoinedFrom.java @@ -0,0 +1,271 @@ +/*
 + * 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.filter;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.stored.Address;
 +import com.amazon.carbonado.stored.Order;
 +import com.amazon.carbonado.stored.Shipment;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestFilterNotJoinedFrom extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestFilterNotJoinedFrom.class);
 +    }
 +
 +    public TestFilterNotJoinedFrom(String name) {
 +        super(name);
 +    }
 +
 +    public void testOpen() {
 +        Filter<Shipment> filter = Filter.getOpenFilter(Shipment.class);
 +
 +        Filter<Shipment>.NotJoined nj = filter.notJoinedFrom("order");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +    }
 +
 +    public void testClosed() {
 +        Filter<Shipment> filter = Filter.getClosedFilter(Shipment.class);
 +
 +        Filter<Shipment>.NotJoined nj = filter.notJoinedFrom("order");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +    }
 +
 +    public void testSimple() {
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentNotes != ?");
 +        filter = filter.bind();
 +
 +        Filter<Shipment>.NotJoined nj = filter.notJoinedFrom("order");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        filter = Filter.filterFor(Shipment.class, "order.orderTotal < ?");
 +        filter = filter.bind();
 +
 +        nj = filter.notJoinedFrom("order");
 +        assertEquals(Filter.filterFor(Order.class, "orderTotal < ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.getOpenFilter(Shipment.class), nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        filter = Filter.filterFor(Shipment.class, "order.address.addressCity = ?");
 +        filter = filter.bind();
 +
 +        nj = filter.notJoinedFrom("order");
 +        assertEquals(Filter.filterFor(Order.class, "address.addressCity = ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.getOpenFilter(Shipment.class), nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertEquals(Filter.filterFor(Address.class, "addressCity = ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.getOpenFilter(Shipment.class), nj.getRemainderFilter());
 +
 +        try {
 +            nj = filter.notJoinedFrom("order.address.addressCity");
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +    }
 +
 +    public void testAnd() {
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes != ? & shipmentDate > ?");
 +        filter = filter.bind();
 +
 +        Filter<Shipment>.NotJoined nj = filter.notJoinedFrom("order");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        filter = Filter.filterFor
 +            (Shipment.class,
 +             "order.orderTotal < ? & shipmentNotes != ? & order.orderComments = ?");
 +        filter = filter.bind();
 +
 +        nj = filter.notJoinedFrom("order");
 +        assertEquals(Filter.filterFor(Order.class, "orderTotal < ? & orderComments = ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes != ?").bind(),
 +                     nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        filter = Filter.filterFor
 +            (Shipment.class,
 +             "order.orderTotal < ? & order.address.addressCity != ? " +
 +             "& order.address.addressZip = ? & shipmentNotes != ?");
 +        filter = filter.bind();
 +
 +        nj = filter.notJoinedFrom("order");
 +        assertEquals(Filter.filterFor(Order.class, "orderTotal < ? & address.addressCity != ? " +
 +                                      "& address.addressZip = ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes != ?").bind(),
 +                     nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertEquals(Filter.filterFor(Address.class, "addressCity != ? & addressZip = ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.filterFor
 +                     (Shipment.class, "order.orderTotal < ? & shipmentNotes != ?").bind(),
 +                     nj.getRemainderFilter());
 +    }
 +
 +    public void testOr() {
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes != ? | shipmentDate > ?");
 +        filter = filter.bind();
 +
 +        Filter<Shipment>.NotJoined nj = filter.notJoinedFrom("order");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        filter = Filter.filterFor
 +            (Shipment.class,
 +             "order.orderTotal < ? | order.orderComments = ?");
 +        filter = filter.bind();
 +
 +        nj = filter.notJoinedFrom("order");
 +        assertEquals(Filter.filterFor(Order.class, "orderTotal < ? | orderComments = ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.getOpenFilter(Shipment.class), nj.getRemainderFilter());
 +
 +        filter = Filter.filterFor
 +            (Shipment.class,
 +             "order.orderTotal < ? | shipmentNotes != ? | order.orderComments = ?");
 +        filter = filter.bind();
 +
 +        nj = filter.notJoinedFrom("order");
 +        assertEquals(Filter.getOpenFilter(Order.class), nj.getNotJoinedFilter());
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertTrue(nj.getNotJoinedFilter() instanceof OpenFilter);
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        filter = Filter.filterFor
 +            (Shipment.class,
 +             "order.address.addressCity != ? | order.address.addressZip = ?");
 +        filter = filter.bind();
 +
 +        nj = filter.notJoinedFrom("order");
 +        assertEquals(Filter.filterFor(Order.class,
 +                                      "address.addressCity != ? | address.addressZip = ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.getOpenFilter(Shipment.class), nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertEquals(Filter.filterFor(Address.class, "addressCity != ? | addressZip = ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.getOpenFilter(Shipment.class), nj.getRemainderFilter());
 +
 +        filter = Filter.filterFor
 +            (Shipment.class,
 +             "order.orderTotal < ? | order.address.addressCity != ? " +
 +             "| order.address.addressZip = ? | shipmentNotes != ?");
 +        filter = filter.bind();
 +
 +        nj = filter.notJoinedFrom("order");
 +        assertEquals(Filter.getOpenFilter(Order.class), nj.getNotJoinedFilter());
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertEquals(Filter.getOpenFilter(Address.class), nj.getNotJoinedFilter());
 +        assertEquals(filter, nj.getRemainderFilter());
 +    }
 +
 +    public void testMixed() {
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class,
 +             "(order.address.addressCity = ? & order.address.addressZip = ?) " +
 +             "| order.orderTotal != ?");
 +        filter = filter.bind();
 +
 +        Filter<Shipment>.NotJoined nj = filter.notJoinedFrom("order");
 +        assertEquals(Filter.filterFor
 +                     (Order.class,
 +                      "address.addressCity = ? & address.addressZip = ? | orderTotal != ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.getOpenFilter(Shipment.class), nj.getRemainderFilter());
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertEquals(Filter.getOpenFilter(Address.class), nj.getNotJoinedFilter());
 +        assertEquals(filter, nj.getRemainderFilter());
 +
 +        filter = filter.and("order.address.customData > ?");
 +        filter = filter.bind();
 +                     
 +        nj = filter.notJoinedFrom("order.address");
 +        assertEquals(Filter.filterFor(Address.class, "customData > ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.filterFor
 +                     (Shipment.class,
 +                      "(order.address.addressCity = ? | order.orderTotal != ?) " +
 +                      "& (order.address.addressZip = ? | order.orderTotal != ?)"),
 +                     nj.getRemainderFilter().unbind());
 +
 +        filter = filter.disjunctiveNormalForm();
 +
 +        nj = filter.notJoinedFrom("order.address");
 +        assertEquals(Filter.filterFor(Address.class, "customData > ?").bind(),
 +                     nj.getNotJoinedFilter());
 +        assertEquals(Filter.filterFor
 +                     (Shipment.class,
 +                      "order.address.addressCity = ? & order.address.addressZip = ? " +
 +                      "| order.orderTotal != ?").bind(),
 +                     nj.getRemainderFilter());
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java new file mode 100644 index 0000000..1210bd4 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.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.qe;
 +
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +
 +import com.amazon.carbonado.stored.Address;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestFilteredQueryExecutor extends TestQueryExecutor {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestFilteredQueryExecutor.class);
 +    }
 +
 +    public void testBasicFiltering() throws Exception {
 +        QueryExecutor<Address> unfiltered = createExecutor(1, 2, 3, 4);
 +        Filter<Address> filter = Filter.filterFor(Address.class, "addressCountry > ?");
 +        FilterValues<Address> values = filter.initialFilterValues();
 +
 +        QueryExecutor<Address> executor = new FilteredQueryExecutor<Address>(unfiltered, filter);
 +
 +        assertEquals(values.getFilter(), executor.getFilter());
 +
 +        assertEquals(2, executor.count(values.with("country_2")));
 +
 +        assertEquals(0, executor.getOrdering().size());
 +
 +        compareElements(executor.fetch(values.with("country_2")), 3, 4);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java b/src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java new file mode 100644 index 0000000..e6b4374 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java @@ -0,0 +1,746 @@ +/*
 + * 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.Comparator;
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Storable;
 +
 +import com.amazon.carbonado.info.Direction;
 +import static com.amazon.carbonado.info.Direction.*;
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableIndex;
 +import com.amazon.carbonado.info.StorableInfo;
 +import com.amazon.carbonado.info.StorableIntrospector;
 +import com.amazon.carbonado.info.StorableProperty;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.PropertyFilter;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestFilteringScore extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestFilteringScore.class);
 +    }
 +
 +    static <S extends Storable> StorableIndex<S> makeIndex(Class<S> type, String... props) {
 +        return TestOrderingScore.makeIndex(type, props);
 +    }
 +
 +    public TestFilteringScore(String name) {
 +        super(name);
 +    }
 +
 +    public void testNoFilter() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id");
 +        Filter<StorableTestBasic> filter = Filter.getOpenFilter(StorableTestBasic.class);
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(1, score.getIndexPropertyCount());
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +    }
 +
 +    public void testSimpleIndexMisses() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "intProp");
 +        // Filter by a property not in index.
 +        Filter<StorableTestBasic> filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(1, score.getIndexPropertyCount());
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(1, score.getRemainderFilters().size());
 +        assertEquals(filter, score.getRemainderFilters().get(0));
 +        assertEquals(filter, score.getRemainderFilter());
 +
 +        // Try again with matching property, but with an operator that cannot be used by index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp != ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(1, score.getIndexPropertyCount());
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(1, score.getRemainderFilters().size());
 +        assertEquals(filter, score.getRemainderFilters().get(0));
 +        assertEquals(filter, score.getRemainderFilter());
 +    }
 +
 +    public void testSimpleIndexMatches() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id");
 +        // Filter by a property in index.
 +        Filter<StorableTestBasic> filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(1, score.getIndexPropertyCount());
 +
 +        assertEquals(1, score.getIdentityCount());
 +        assertEquals(1, score.getArrangementScore());
 +        assertEquals(filter, score.getIdentityFilters().get(0));
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        // Try again with open ranges.
 +        for (int i=0; i<4; i++) {
 +            String expr;
 +            switch (i) {
 +            default: expr = "id > ?"; break;
 +            case 1: expr = "id >= ?"; break;
 +            case 2: expr = "id < ?"; break;
 +            case 3: expr = "id <= ?"; break;
 +            }
 +
 +            filter = Filter.filterFor(StorableTestBasic.class, expr);
 +
 +            score = FilteringScore.evaluate(ix, filter);
 +
 +            assertEquals(0, score.getIdentityCount());
 +            assertEquals(0, score.getIdentityFilters().size());
 +            assertFalse(score.hasRangeMatch());
 +            assertTrue(score.hasAnyMatches());
 +            assertEquals(0, score.getRemainderCount());
 +            assertEquals(0, score.getRemainderFilters().size());
 +            assertEquals(null, score.getRemainderFilter());
 +
 +            if (i < 2) {
 +                assertTrue(score.hasRangeStart());
 +                assertEquals(1, score.getRangeStartFilters().size());
 +                assertEquals(filter, score.getRangeStartFilters().get(0));
 +                assertFalse(score.hasRangeEnd());
 +                assertEquals(0, score.getRangeEndFilters().size());
 +            } else {
 +                assertFalse(score.hasRangeStart());
 +                assertEquals(0, score.getRangeStartFilters().size());
 +                assertTrue(score.hasRangeEnd());
 +                assertEquals(1, score.getRangeEndFilters().size());
 +                assertEquals(filter, score.getRangeEndFilters().get(0));
 +            }
 +        }
 +
 +        // Try with duplicate open ranges.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id > ? & id > ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        assertTrue(score.hasRangeStart());
 +        assertEquals(2, score.getRangeStartFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(1));
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +
 +        // Try with duplicate end ranges and slightly different operator.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id < ? & id <= ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(2, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"),
 +                     score.getRangeEndFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"),
 +                     score.getRangeEndFilters().get(1));
 +
 +        // Try with complete range.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id > ? & id < ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertTrue(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        assertTrue(score.hasRangeStart());
 +        assertEquals(1, score.getRangeStartFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(0));
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(1, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"),
 +                     score.getRangeEndFilters().get(0));
 +
 +        // Try with an identity filter consuming others.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id > ? & id = ? & id = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(1, score.getIdentityCount());
 +        assertEquals(1, score.getIdentityFilters().size());
 +        assertEquals(1, score.getArrangementScore());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id = ?"),
 +                     score.getIdentityFilters().get(0));
 +
 +        assertFalse(score.hasRangeStart());
 +        assertFalse(score.hasRangeEnd());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(2, score.getRemainderCount());
 +        assertEquals(2, score.getRemainderFilters().size());
 +        // Order of properties in filter accounts for sorting by PropertyFilterList.
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id = ? & id > ?"),
 +                     score.getRemainderFilter());
 +
 +        // Try with complex mix of non-identity matches.
 +        filter = Filter.filterFor
 +            (StorableTestBasic.class,
 +             "id > ? & id != ? & id <= ? & id >= ? & id != ? & id > ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +
 +        assertTrue(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +
 +        assertTrue(score.hasRangeStart());
 +        assertEquals(3, score.getRangeStartFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id >= ?"),
 +                     score.getRangeStartFilters().get(1));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(2));
 +
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(1, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"),
 +                     score.getRangeEndFilters().get(0));
 +
 +        assertEquals(2, score.getRemainderCount());
 +        assertEquals(2, score.getRemainderFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id != ? & id != ?"),
 +                     score.getRemainderFilter());
 +    }
 +
 +    public void testCompositeIndexMatches() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class,
 +                                                        "id", "intProp", "stringProp");
 +        // Filter by a property in index.
 +        Filter<StorableTestBasic> filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(3, score.getIndexPropertyCount());
 +
 +        assertEquals(1, score.getIdentityCount());
 +        assertEquals(1, score.getArrangementScore());
 +        assertEquals(filter, score.getIdentityFilters().get(0));
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        // Filter by a property with a gap. (filter must include "id" to use index)
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(1, score.getRemainderFilters().size());
 +        assertEquals(filter, score.getRemainderFilter());
 +
 +        // Filter by a property with a gap and a range operator. (index still cannot be used)
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp = ? & stringProp < ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(2, score.getRemainderCount());
 +        assertEquals(2, score.getRemainderFilters().size());
 +        assertEquals(filter, score.getRemainderFilter());
 +
 +        // Filter with range match before identity match. Identity cannot be used.
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp = ? & id < ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertTrue(score.hasAnyMatches());
 +
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(1, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"),
 +                     score.getRangeEndFilters().get(0));
 +
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(1, score.getRemainderFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp = ?"),
 +                     score.getRemainderFilter());
 +
 +        // Filter with fully specified identity match, but a few remaining.
 +        filter = Filter.filterFor
 +            (StorableTestBasic.class,
 +             "intProp = ? & id = ? & stringProp = ? & stringProp > ? & doubleProp = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(3, score.getIdentityCount());
 +        assertEquals(2, score.getArrangementScore());
 +
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id = ?"),
 +                     score.getIdentityFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp = ?"),
 +                     score.getIdentityFilters().get(1));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp = ?"),
 +                     score.getIdentityFilters().get(2));
 +
 +        assertEquals(2, score.getRemainderCount());
 +        assertEquals(2, score.getRemainderFilters().size());
 +        // Order of remainder properties accounts for sorting by PropertyFilterList.
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "doubleProp = ?"),
 +                     score.getRemainderFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp > ?"),
 +                     score.getRemainderFilters().get(1));
 +
 +        // Filter with identity and range matches.
 +        filter = Filter.filterFor
 +            (StorableTestBasic.class,
 +             "intProp > ? & id = ? & stringProp = ? & intProp <= ? & " +
 +             "stringProp < ? & doubleProp = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(1, score.getIdentityCount());
 +        assertEquals(1, score.getArrangementScore());
 +
 +        assertEquals(3, score.getRemainderCount());
 +        assertEquals(3, score.getRemainderFilters().size());
 +        // Order of remainder properties accounts for sorting by PropertyFilterList.
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp = ?"),
 +                     score.getRemainderFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "doubleProp = ?"),
 +                     score.getRemainderFilters().get(1));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp < ?"),
 +                     score.getRemainderFilters().get(2));
 +
 +        assertTrue(score.hasRangeMatch());
 +        assertTrue(score.hasRangeStart());
 +        assertEquals(1, score.getRangeStartFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp > ?"),
 +                     score.getRangeStartFilters().get(0));
 +
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(1, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp <= ?"),
 +                     score.getRangeEndFilters().get(0));
 +
 +        // Filter with fully specified identity match, with backwards arrangement.
 +        filter = Filter.filterFor
 +            (StorableTestBasic.class, "stringProp = ? & intProp = ? & id = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(3, score.getIdentityCount());
 +        // Arrangement score is always at least one if identity count is not zero.
 +        assertEquals(1, score.getArrangementScore());
 +    }
 +
 +    public void testReverseRange() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class,
 +                                                        "id", "intProp");
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?");
 +
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.shouldReverseRange());
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "-intProp");
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertTrue(score.shouldReverseRange());
 +    }
 +
 +    public void testRangeComparator() throws Exception {
 +        StorableIndex<StorableTestBasic> ix_1, ix_2;
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score_1, score_2;
 +        Comparator<FilteringScore<?>> comp = FilteringScore.rangeComparator();
 +
 +        ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +        ix_2 = makeIndex(StorableTestBasic.class, "id", "stringProp", "intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp != ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Both indexes are as good, since range is open.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp > ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "id = ? & intProp = ? & stringProp > ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        // Test range match with tie resolved by clustered index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2.clustered(true), filter);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Test open range match with tie still not resolved by clustered index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2.clustered(true), filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +    }
 +
 +    public void testFullComparator() throws Exception {
 +        StorableIndex<StorableTestBasic> ix_1, ix_2;
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score_1, score_2;
 +        Comparator<FilteringScore<?>> comp = FilteringScore.fullComparator();
 +
 +        ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +        ix_2 = makeIndex(StorableTestBasic.class, "id", "stringProp", "intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Second is better because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Although no property matches, second is better just because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Although no property matches, first is better just because it is clustered.
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp != ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Second is better because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Second index is better since the open range matches.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp > ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "id = ? & intProp = ? & stringProp > ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        // Test range match with tie resolved by clustered index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Second is better because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // First is better because it is clusted.
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        // Test range match with tie resolved by clustered index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2.clustered(true), filter);
 +
 +        // Second is better because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +    }
 +
 +    public void testArrangementFullComparator() throws Exception {
 +        StorableIndex<StorableTestBasic> ix_1, ix_2;
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score_1, score_2;
 +
 +        ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "-stringProp");
 +        ix_2 = makeIndex(StorableTestBasic.class, "id", "stringProp", "intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "id = ? & intProp = ? & stringProp = ?");
 +
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        Comparator<FilteringScore<?>> comp = FilteringScore.rangeComparator();
 +
 +        // With just range comparison, either index works.
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        comp = FilteringScore.fullComparator();
 +
 +        // First index is better because property arrangement matches.
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +    }
 +
 +    public void testRangeFilterSubset() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id");
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score;
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id >= ? & id < ? & id <= ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(2, score.getRangeStartFilters().size());
 +
 +        List<PropertyFilter<?>> exStart = score.getExclusiveRangeStartFilters();
 +        assertEquals(0, exStart.size());
 +
 +        List<PropertyFilter<?>> inStart = score.getInclusiveRangeStartFilters();
 +        assertEquals(2, inStart.size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id >= ?"), inStart.get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id >= ?"), inStart.get(1));
 +
 +        assertEquals(2, score.getRangeEndFilters().size());
 +
 +        List<PropertyFilter<?>> exEnd = score.getExclusiveRangeEndFilters();
 +        assertEquals(1, exEnd.size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"), exEnd.get(0));
 +
 +        List<PropertyFilter<?>> inEnd = score.getInclusiveRangeEndFilters();
 +        assertEquals(1, inEnd.size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"), inEnd.get(0));
 +    }
 +
 +    public void testKeyMatch() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id", "intProp");
 +        ix = ix.unique(true);
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score;
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        score = FilteringScore.evaluate(ix, filter);
 +        assertFalse(score.isKeyMatch());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?");
 +        score = FilteringScore.evaluate(ix, filter);
 +        assertFalse(score.isKeyMatch());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +        score = FilteringScore.evaluate(ix, filter);
 +        assertTrue(score.isKeyMatch());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "id = ? & intProp = ? & doubleProp = ?");
 +        score = FilteringScore.evaluate(ix, filter);
 +        assertTrue(score.isKeyMatch());
 +    }
 +}
 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..6b1919a --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java @@ -0,0 +1,460 @@ +/*
 + * 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 java.util.List;
 +
 +import junit.framework.TestCase;
 +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.RepositoryException;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.Storage;
 +
 +import com.amazon.carbonado.cursor.ArraySortBuffer;
 +import com.amazon.carbonado.cursor.SortBuffer;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableIndex;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +import com.amazon.carbonado.filter.PropertyFilter;
 +
 +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 com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + * 
 + *
 + * @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 <S extends Storable> StorableIndex<S> makeIndex(Class<S> 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, RepoAccess.INSTANCE);
 +        Filter<Address> filter = Filter.filterFor(Address.class, "addressZip = ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        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, RepoAccess.INSTANCE);
 +        Filter<Address> filter = Filter.filterFor(Address.class, "addressID = ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        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, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentID = ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        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, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
 +                     result.getLocalIndex());
 +        assertEquals(null, result.getForeignIndex());
 +        assertEquals(null, result.getForeignProperty());
 +
 +        filter = Filter.filterFor(Shipment.class, "orderID > ?");
 +        filter = filter.bind();
 +        result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertTrue(result.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertFalse(result.getCompositeScore().getFilteringScore().hasRangeEnd());
 +        List<PropertyFilter<Shipment>> rangeFilters =
 +            result.getCompositeScore().getFilteringScore().getRangeStartFilters();
 +        assertEquals(1, rangeFilters.size());
 +        assertEquals(filter, rangeFilters.get(0));
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
 +                     result.getLocalIndex());
 +        assertEquals(null, result.getForeignIndex());
 +        assertEquals(null, result.getForeignProperty());
 +    }
 +
 +    public void testSimpleJoin() throws Exception {
 +        IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class, "order.orderTotal >= ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        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, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes = ? & order.orderTotal >= ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        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, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "orderID >= ? & order.orderTotal >= ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        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", "shipmentNotes"),
 +                     result.getLocalIndex());
 +        assertEquals(null, result.getForeignIndex());
 +        assertEquals(null, result.getForeignProperty());
 +    }
 +
 +    public void testChainedJoin() throws Exception {
 +        IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "order.address.addressState = ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(null, result.getLocalIndex());
 +        assertEquals(makeIndex(Address.class, "addressState", "-addressCity"),
 +                     result.getForeignIndex());
 +        assertEquals("order.address", result.getForeignProperty().toString());
 +    }
 +
 +    public void testChainedJoinExecutor() throws Exception {
 +        IndexedQueryAnalyzer<Shipment> iqa =
 +            new IndexedQueryAnalyzer<Shipment>(Shipment.class, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "order.address.addressState = ? & order.address.addressZip = ?");
 +        FilterValues<Shipment> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +        IndexedQueryAnalyzer<Shipment>.Result result = iqa.analyze(filter, null);
 +
 +        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", "-addressCity"),
 +                     result.getForeignIndex());
 +        assertEquals("order.address", result.getForeignProperty().toString());
 +
 +        QueryExecutor<Shipment> joinExec = JoinedQueryExecutor.build
 +            (RepoAccess.INSTANCE,
 +             result.getForeignProperty(), result.getFilter(), result.getOrdering());
 +
 +        FilterValues<Shipment> fv = values.with("WA").with("12345");
 +
 +        StringBuffer buf = new StringBuffer();
 +        joinExec.printPlan(buf, 0, fv);
 +        String plan = buf.toString();
 +
 +        // This is actually a pretty terrible plan due to the iterators. This
 +        // is expected however, since we lied and said we had indexes.
 +        String expected =
 +            "join: com.amazon.carbonado.stored.Shipment\n" +
 +            "...inner loop: order\n" +
 +            "  filter: orderID = ?\n" +
 +            "    collection iterator: com.amazon.carbonado.stored.Shipment\n" +
 +            "...outer loop\n" +
 +            "  join: com.amazon.carbonado.stored.Order\n" +
 +            "  ...inner loop: address\n" +
 +            "    filter: addressID = ?\n" +
 +            "      collection iterator: com.amazon.carbonado.stored.Order\n" +
 +            "  ...outer loop\n" +
 +            "    filter: addressState = WA & addressZip = 12345\n" +
 +            "      collection iterator: com.amazon.carbonado.stored.Address\n";
 +
 +        assertEquals(expected, plan);
 +
 +        joinExec.fetch(fv);
 +
 +    }
 +    
 +    public void testComplexChainedJoinExecutor() throws Exception {
 +        IndexedQueryAnalyzer<Shipment> iqa =
 +            new IndexedQueryAnalyzer<Shipment>(Shipment.class, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class,
 +             "order.address.addressState = ? & order.address.addressID != ? " +
 +             "& order.address.addressZip = ? & order.orderTotal > ? & shipmentNotes <= ? " +
 +             "& order.addressID > ?");
 +        FilterValues<Shipment> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +        OrderingList<Shipment> ordering = OrderingList
 +            .get(Shipment.class, "order.address.addressCity", "shipmentNotes", "order.orderTotal");
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, ordering);
 +
 +        assertTrue(result.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "order.address.addressState = ?").bind(),
 +                     result.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(null, result.getLocalIndex());
 +        assertEquals(makeIndex(Address.class, "addressState", "-addressCity"),
 +                     result.getForeignIndex());
 +        assertEquals("order.address", result.getForeignProperty().toString());
 +
 +        QueryExecutor<Shipment> joinExec = JoinedQueryExecutor.build
 +            (RepoAccess.INSTANCE,
 +             result.getForeignProperty(), result.getFilter(), result.getOrdering());
 +
 +        FilterValues<Shipment> fv =
 +            values.with("WA").with(45).with("12345").with(100).with("Z").with(2);
 +
 +        StringBuffer buf = new StringBuffer();
 +        joinExec.printPlan(buf, 0, fv);
 +        String plan = buf.toString();
 +
 +        // This is actually a pretty terrible plan due to the iterators. This
 +        // is expected however, since we lied and said we had indexes.
 +        String expected =
 +            "sort: [+order.address.addressCity, +shipmentNotes], [+order.orderTotal]\n" +
 +            "  join: com.amazon.carbonado.stored.Shipment\n" +
 +            "  ...inner loop: order\n" +
 +            "    sort: [+shipmentNotes]\n" +
 +            "      filter: shipmentNotes <= Z & orderID = ?\n" +
 +            "        collection iterator: com.amazon.carbonado.stored.Shipment\n" +
 +            "  ...outer loop\n" +
 +            "    join: com.amazon.carbonado.stored.Order\n" +
 +            "    ...inner loop: address\n" +
 +            "      filter: orderTotal > 100 & addressID > 2 & addressID = ?\n" +
 +            "        collection iterator: com.amazon.carbonado.stored.Order\n" +
 +            "    ...outer loop\n" +
 +            "      sort: [+addressCity]\n" +
 +            "        filter: addressState = WA & addressID != 45 & addressZip = 12345\n" +
 +            "          collection iterator: com.amazon.carbonado.stored.Address\n";
 +
 +        //System.out.println(plan);
 +        assertEquals(expected, plan);
 +
 +        joinExec.fetch(fv);
 +
 +        // Now do it the easier way and compare plans.
 +        QueryExecutor<Shipment> joinExec2 = result.createExecutor();
 +
 +        StringBuffer buf2 = new StringBuffer();
 +        joinExec2.printPlan(buf2, 0, fv);
 +        String plan2 = buf2.toString();
 +
 +        assertEquals(expected, plan2);
 +
 +        Filter<Shipment> expectedFilter = Filter.filterFor
 +            (Shipment.class,
 +             "order.address.addressState = ? & order.address.addressID != ? " +
 +             "& order.address.addressZip = ? & order.orderTotal > ? " +
 +             "& order.addressID > ?" +
 +             "& shipmentNotes <= ? ");
 +
 +        assertEquals(expectedFilter.disjunctiveNormalForm(),
 +                     joinExec2.getFilter().unbind().disjunctiveNormalForm());
 +    }
 +
 +    static class RepoAccess implements RepositoryAccess {
 +        static final RepoAccess INSTANCE = new RepoAccess();
 +
 +        public Repository getRootRepository() {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        public <S extends Storable> StorageAccess<S> storageAccessFor(Class<S> type) {
 +            return new StoreAccess<S>(type);
 +        }
 +    }
 +
 +    /**
 +     * Partially implemented StorageAccess which only supplies information
 +     * about indexes.
 +     */
 +    static class StoreAccess<S extends Storable>
 +        implements StorageAccess<S>, QueryExecutorFactory<S>
 +    {
 +        private final Class<S> mType;
 +
 +        StoreAccess(Class<S> type) {
 +            mType = type;
 +        }
 +
 +        public Class<S> getStorableType() {
 +            return mType;
 +        }
 +
 +        public QueryExecutorFactory<S> getQueryExecutorFactory() {
 +            return this;
 +        }
 +
 +        public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering) {
 +            Iterable<S> iterable = Collections.emptyList();
 +
 +            QueryExecutor<S> exec = new IterableQueryExecutor<S>
 +                (filter.getStorableType(), iterable);
 +
 +            if (filter != null) {
 +                exec = new FilteredQueryExecutor<S>(exec, filter);
 +            }
 +
 +            if (ordering != null && ordering.size() > 0) {
 +                exec = new SortedQueryExecutor<S>(null, exec, null, ordering);
 +            }
 +
 +            return exec;
 +        }
 +
 +        public Collection<StorableIndex<S>> getAllIndexes() {
 +            StorableIndex<S>[] indexes;
 +
 +            if (Address.class.isAssignableFrom(mType)) {
 +                indexes = new StorableIndex[] {
 +                    makeIndex(mType, "addressID"),
 +                    makeIndex(mType, "addressState", "-addressCity")
 +                };
 +            } else if (Order.class.isAssignableFrom(mType)) {
 +                indexes = new StorableIndex[] {
 +                    makeIndex(mType, "orderID"),
 +                    makeIndex(mType, "orderTotal"),
 +                    makeIndex(mType, "addressID", "orderTotal")
 +                };
 +            } else if (Shipment.class.isAssignableFrom(mType)) {
 +                indexes = new StorableIndex[] {
 +                    makeIndex(mType, "shipmentID"),
 +                    makeIndex(mType, "orderID", "shipmentNotes"),
 +                };
 +            } else if (Shipper.class.isAssignableFrom(mType)) {
 +                indexes = new StorableIndex[] {
 +                    makeIndex(mType, "shipperID")
 +                };
 +            } else if (StorableTestBasic.class.isAssignableFrom(mType)) {
 +                indexes = new StorableIndex[] {
 +                    makeIndex(mType, "id").unique(true).clustered(true),
 +                    makeIndex(mType, "stringProp", "doubleProp").unique(true),
 +                    makeIndex(mType, "-stringProp", "-intProp", "~id").unique(true),
 +                    makeIndex(mType, "+intProp", "stringProp", "~id").unique(true),
 +                    makeIndex(mType, "-doubleProp", "+longProp", "~id").unique(true),
 +                };
 +            } else {
 +                indexes = new StorableIndex[0];
 +            }
 +
 +            return Arrays.asList(indexes);
 +        }
 +
 +        public Storage<S> storageDelegate(StorableIndex<S> index) {
 +            return null;
 +        }
 +
 +        public SortBuffer<S> createSortBuffer() {
 +            return new ArraySortBuffer<S>();
 +        }
 +
 +        public long countAll() {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        public Cursor<S> fetchAll() {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        public Cursor<S> fetchOne(StorableIndex<S> index, Object[] identityValues) {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        public Cursor<S> fetchSubset(StorableIndex<S> index,
 +                                     Object[] identityValues,
 +                                     BoundaryType rangeStartBoundary,
 +                                     Object rangeStartValue,
 +                                     BoundaryType rangeEndBoundary,
 +                                     Object rangeEndValue,
 +                                     boolean reverseRange,
 +                                     boolean reverseOrder)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java new file mode 100644 index 0000000..a68fbac --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java @@ -0,0 +1,754 @@ +/*
 + * 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 java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Cursor;
 +import com.amazon.carbonado.Storable;
 +
 +import com.amazon.carbonado.cursor.IteratorCursor;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableIndex;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestIndexedQueryExecutor extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestIndexedQueryExecutor.class);
 +    }
 +
 +    static <S extends Storable> StorableIndex<S> makeIndex(Class<S> type, String... props) {
 +        return TestOrderingScore.makeIndex(type, props);
 +    }
 +
 +    static <S extends Storable> OrderingList<S> makeOrdering(Class<S> type, String... props) {
 +        return TestOrderingScore.makeOrdering(type, props);
 +    }
 +
 +    public TestIndexedQueryExecutor(String name) {
 +        super(name);
 +    }
 +
 +    public void testIdentityMatch() throws Exception {
 +        StorableIndex<StorableTestBasic> index =
 +            makeIndex(StorableTestBasic.class, "id", "-intProp", "doubleProp");
 +
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        FilterValues<StorableTestBasic> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
 +
 +        Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(1, executor.mIdentityValues.length);
 +        assertEquals(100, executor.mIdentityValues[0]);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(5));
 +
 +        assertEquals(2, executor.mIdentityValues.length);
 +        assertEquals(100, executor.mIdentityValues[0]);
 +        assertEquals(5, executor.mIdentityValues[1]);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp = ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(200));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +    }
 +
 +    public void testOpenRangeStartMatch() throws Exception {
 +        StorableIndex<StorableTestBasic> index = makeIndex(StorableTestBasic.class, "intProp");
 +
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +        FilterValues<StorableTestBasic> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
 +
 +        Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(30, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(30, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(10, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(30).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(30, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +
 +        ///////
 +        index = makeIndex(StorableTestBasic.class, "-intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +    }
 +
 +    public void testOpenRangeEndMatch() throws Exception {
 +        StorableIndex<StorableTestBasic> index = makeIndex(StorableTestBasic.class, "intProp");
 +
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "intProp < ?");
 +        FilterValues<StorableTestBasic> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
 +
 +        Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp <= ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp <= ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp <= ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp <= ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(30).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(30, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +
 +        ///////
 +        index = makeIndex(StorableTestBasic.class, "-intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +    }
 +
 +    public void testClosedRangeMatch() throws Exception {
 +        // These tests are not as exhaustive, as I don't expect the combination
 +        // of start and end ranges to interfere with each other.
 +
 +        StorableIndex<StorableTestBasic> index = makeIndex(StorableTestBasic.class, "intProp");
 +
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "intProp > ? & intProp < ?");
 +        FilterValues<StorableTestBasic> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
 +
 +        Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(200));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(200, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ? & intProp <= ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "intProp > ? & intProp < ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(100).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(30, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +    }
 +
 +    public void testIdentityAndRangeMatch() throws Exception {
 +        // These tests are not as exhaustive, as I don't expect the combination
 +        // of identity and ranges to interfere with each other.
 +
 +        StorableIndex<StorableTestBasic> index;
 +        Filter<StorableTestBasic> filter;
 +        FilterValues<StorableTestBasic> values;
 +        CompositeScore<StorableTestBasic> score;
 +        Mock<StorableTestBasic> executor;
 +
 +        index = makeIndex(StorableTestBasic.class, "intProp", "-doubleProp", "stringProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "intProp = ? & doubleProp > ? & doubleProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(3).with(56.5).with(200.2));
 +
 +        assertEquals(3, executor.mIdentityValues[0]);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(56.5, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(200.2, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "doubleProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(3).with(56.5).with(200.2));
 +
 +        assertEquals(3, executor.mIdentityValues[0]);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(56.5, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(200.2, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +
 +        ///////
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "stringProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(3).with(56.5).with(200.2));
 +
 +        assertEquals(3, executor.mIdentityValues[0]);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(56.5, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(200.2, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "intProp = ? & doubleProp = ? & stringProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-stringProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(3).with(56.5).with("foo"));
 +
 +        assertEquals(3, executor.mIdentityValues[0]);
 +        assertEquals(56.5, executor.mIdentityValues[1]);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals("foo", executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +
 +        assertEquals(values.getFilter(), executor.getFilter());
 +        List<OrderedProperty<StorableTestBasic>> expectedOrdering =
 +            makeOrdering(StorableTestBasic.class, "-stringProp");
 +        assertEquals(expectedOrdering, executor.getOrdering());
 +    }
 +
 +    public void testHandledOrdering() throws Exception {
 +        // Tests that ordering of executor only reveals what it actually uses.
 +
 +        StorableIndex<StorableTestBasic> index;
 +        Filter<StorableTestBasic> filter;
 +        FilterValues<StorableTestBasic> values;
 +        CompositeScore<StorableTestBasic> score;
 +        Mock<StorableTestBasic> executor;
 +
 +        index = makeIndex(StorableTestBasic.class, "intProp", "-doubleProp", "stringProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp = ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate
 +            (index, filter,
 +             makeOrdering(StorableTestBasic.class, "intProp", "doubleProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        assertEquals(values.getFilter(), executor.getFilter());
 +        List<OrderedProperty<StorableTestBasic>> expectedOrdering =
 +            makeOrdering(StorableTestBasic.class, "+doubleProp");
 +        assertEquals(expectedOrdering, executor.getOrdering());
 +    }
 +
 +    /**
 +     * Mock object doesn't really open a cursor -- it just captures the passed
 +     * parameters.
 +     */
 +    static class Mock<S extends Storable> extends IndexedQueryExecutor<S>
 +        implements IndexedQueryExecutor.Support<S>
 +    {
 +        Object[] mIdentityValues;
 +        BoundaryType mRangeStartBoundary;
 +        Object mRangeStartValue;
 +        BoundaryType mRangeEndBoundary;
 +        Object mRangeEndValue;
 +        boolean mReverseRange;
 +        boolean mReverseOrder;
 +
 +        public Mock(StorableIndex<S> index, CompositeScore<S> score) {
 +            super(null, index, score);
 +        }
 +
 +        public Cursor<S> fetchSubset(StorableIndex<S> index,
 +                                     Object[] identityValues,
 +                                     BoundaryType rangeStartBoundary,
 +                                     Object rangeStartValue,
 +                                     BoundaryType rangeEndBoundary,
 +                                     Object rangeEndValue,
 +                                     boolean reverseRange,
 +                                     boolean reverseOrder)
 +        {
 +            mIdentityValues = identityValues;
 +            mRangeStartBoundary = rangeStartBoundary;
 +            mRangeStartValue = rangeStartValue;
 +            mRangeEndBoundary = rangeEndBoundary;
 +            mRangeEndValue = rangeEndValue;
 +            mReverseRange = reverseRange;
 +            mReverseOrder = reverseOrder;
 +
 +            Collection<S> empty = Collections.emptyList();
 +            return new IteratorCursor<S>(empty);
 +        }
 +    }
 +}
 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..e1abca9 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java @@ -0,0 +1,298 @@ +/*
 + * 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.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.List;
 +import java.util.Map;
 +
 +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.RepositoryException;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.Storage;
 +
 +import com.amazon.carbonado.cursor.ArraySortBuffer;
 +import com.amazon.carbonado.cursor.SortBuffer;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +
 +import com.amazon.carbonado.info.ChainedProperty;
 +import com.amazon.carbonado.info.Direction;
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableIndex;
 +import com.amazon.carbonado.info.StorableInfo;
 +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 {
 +        StorableInfo<UserInfo> info = StorableIntrospector.examine(UserInfo.class);
 +        Map<String, ? extends StorableProperty<UserInfo>> properties = info.getAllProperties();
 +
 +        RepositoryAccess repoAccess = new RepoAccess();
 +
 +        ChainedProperty<UserInfo> targetToSourceProperty =
 +            ChainedProperty.get(properties.get("address"));
 +
 +        Filter<UserInfo> targetFilter = Filter.filterFor(UserInfo.class, "address.state = ?");
 +        OrderingList<UserInfo> targetOrdering =
 +            OrderingList.get(UserInfo.class, "+address.country");
 +
 +        QueryExecutor<UserInfo> userExecutor = JoinedQueryExecutor.build
 +            (repoAccess, targetToSourceProperty, targetFilter, targetOrdering);
 +
 +        //System.out.println();
 +        //userExecutor.printPlan(System.out, 0, null);
 +
 +        assertEquals("address.state = ?", userExecutor.getFilter().toString());
 +        assertEquals("+address.country", userExecutor.getOrdering().get(0).toString());
 +
 +        // Create some addresses
 +        Storage<UserAddress> 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<UserInfo> 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<UserInfo> values = Filter
 +            .filterFor(UserInfo.class, "address.state = ?").initialFilterValues().with("IL");
 +
 +        Cursor<UserInfo> 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.
 +
 +        targetToSourceProperty = ChainedProperty.parse(info, "address.neighbor");
 +
 +        targetFilter = Filter.filterFor(UserInfo.class, "address.neighbor.state = ?");
 +        targetOrdering = OrderingList.get(UserInfo.class, "+address.neighbor.country");
 +
 +        userExecutor = JoinedQueryExecutor.build
 +            (repoAccess, targetToSourceProperty, targetFilter, targetOrdering);
 +
 +        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));
 +    }
 +
 +    class RepoAccess implements RepositoryAccess {
 +        public Repository getRootRepository() {
 +            return mRepository;
 +        }
 +
 +        public <S extends Storable> StorageAccess<S> storageAccessFor(Class<S> type) {
 +            return new StoreAccess<S>(type);
 +        }
 +    }
 +
 +    class StoreAccess<S extends Storable> implements StorageAccess<S>, QueryExecutorFactory<S> {
 +        private final Class<S> mType;
 +
 +        StoreAccess(Class<S> type) {
 +            mType = type;
 +        }
 +
 +        public Class<S> getStorableType() {
 +            return mType;
 +        }
 +
 +        public QueryExecutorFactory<S> getQueryExecutorFactory() {
 +            return this;
 +        }
 +
 +        public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
 +            throws RepositoryException
 +        {
 +            Storage<S> storage = mRepository.storageFor(mType);
 +
 +            QueryExecutor<S> exec = new FullScanQueryExecutor<S>
 +                (new ScanQuerySupport<S>(storage.query()));
 +
 +            if (filter != null) {
 +                exec = new FilteredQueryExecutor<S>(exec, filter);
 +            }
 +
 +            if (ordering != null && ordering.size() > 0) {
 +                exec = new SortedQueryExecutor<S>(null, exec, null, ordering);
 +            }
 +
 +            return exec;
 +        }
 +
 +        public Collection<StorableIndex<S>> getAllIndexes() {
 +            StorableIndex<S>[] indexes = new StorableIndex[0];
 +            return Arrays.asList(indexes);
 +        }
 +
 +        public Storage<S> storageDelegate(StorableIndex<S> index) {
 +            return null;
 +        }
 +
 +        public SortBuffer<S> createSortBuffer() {
 +            return new ArraySortBuffer<S>();
 +        }
 +
 +        public long countAll() {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        public Cursor<S> fetchAll() {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        public Cursor<S> fetchOne(StorableIndex<S> index, Object[] identityValues) {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        public Cursor<S> fetchSubset(StorableIndex<S> index,
 +                                     Object[] identityValues,
 +                                     BoundaryType rangeStartBoundary,
 +                                     Object rangeStartValue,
 +                                     BoundaryType rangeEndBoundary,
 +                                     Object rangeEndValue,
 +                                     boolean reverseRange,
 +                                     boolean reverseOrder)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +    }
 +
 +    static class ScanQuerySupport<S extends Storable> implements FullScanQueryExecutor.Support<S> {
 +        private final Query<S> mQuery;
 +
 +        ScanQuerySupport(Query<S> query) {
 +            mQuery = query;
 +        }
 +
 +        public Class<S> getStorableType() {
 +            return mQuery.getStorableType();
 +        }
 +
 +        public long countAll() throws FetchException {
 +            return mQuery.count();
 +        }
 +
 +        public Cursor<S> fetchAll() throws FetchException {
 +            return mQuery.fetch();
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestOrderingList.java b/src/test/java/com/amazon/carbonado/qe/TestOrderingList.java new file mode 100644 index 0000000..8af7a2e --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestOrderingList.java @@ -0,0 +1,225 @@ +/*
 + * 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.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestOrderingList extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestOrderingList.class);
 +    }
 +
 +    public TestOrderingList(String name) {
 +        super(name);
 +    }
 +
 +    public void testEmpty() throws Exception {
 +        assertEquals(0, OrderingList.get(StorableTestBasic.class).size());
 +    }
 +
 +    public void testSingle() throws Exception {
 +        List<OrderedProperty<StorableTestBasic>> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date");
 +
 +        assertEquals(1, list_1.size());
 +        assertEquals("+date", list_1.get(0).toString());
 +
 +        List<OrderedProperty<StorableTestBasic>> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "+date");
 +
 +        assertEquals(1, list_2.size());
 +        assertEquals("+date", list_2.get(0).toString());
 +        assertEquals(list_1, list_2);
 +        assertTrue(list_1 == list_2);
 +
 +        List<OrderedProperty<StorableTestBasic>> list_3 =
 +            OrderingList.get(StorableTestBasic.class, "-date");
 +
 +        assertEquals(1, list_3.size());
 +        assertEquals("-date", list_3.get(0).toString());
 +        assertFalse(list_2.equals(list_3));
 +        assertFalse(list_2 == list_3);
 +    }
 +
 +    public void testDouble() throws Exception {
 +        List<OrderedProperty<StorableTestBasic>> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date", "intProp");
 +
 +        assertEquals(2, list_1.size());
 +        assertEquals("+date", list_1.get(0).toString());
 +        assertEquals("+intProp", list_1.get(1).toString());
 +
 +        List<OrderedProperty<StorableTestBasic>> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "+date", "+intProp");
 +
 +        assertEquals(2, list_2.size());
 +        assertEquals("+date", list_2.get(0).toString());
 +        assertEquals("+intProp", list_2.get(1).toString());
 +        assertEquals(list_1, list_2);
 +        assertTrue(list_1 == list_2);
 +
 +        List<OrderedProperty<StorableTestBasic>> list_3 =
 +            OrderingList.get(StorableTestBasic.class, "-date", "-intProp");
 +
 +        assertEquals(2, list_3.size());
 +        assertEquals("-date", list_3.get(0).toString());
 +        assertEquals("-intProp", list_3.get(1).toString());
 +        assertFalse(list_2.equals(list_3));
 +        assertFalse(list_2 == list_3);
 +    }
 +
 +    public void testIllegal() throws Exception {
 +        try {
 +            OrderingList.get(StorableTestBasic.class, "foo");
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +    }
 +
 +    public void testImmutable() throws Exception {
 +        List<OrderedProperty<StorableTestBasic>> list =
 +            OrderingList.get(StorableTestBasic.class, "~date");
 +        try {
 +            list.set(0, list.get(0).reverse());
 +            fail();
 +        } catch (UnsupportedOperationException e) {
 +        }
 +    }
 +
 +    public void testConcatList() throws Exception {
 +        OrderingList<StorableTestBasic> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp");
 +
 +        OrderingList<StorableTestBasic> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "longProp", "doubleProp");
 +
 +        OrderingList<StorableTestBasic> list_3 = list_1.concat(list_2);
 +
 +        assertEquals(5, list_3.size());
 +        assertEquals("+date", list_3.get(0).toString());
 +        assertEquals("-intProp", list_3.get(1).toString());
 +        assertEquals("~stringProp", list_3.get(2).toString());
 +        assertEquals("+longProp", list_3.get(3).toString());
 +        assertEquals("+doubleProp", list_3.get(4).toString());
 +
 +        OrderingList<StorableTestBasic> list_4 =
 +            OrderingList.get(StorableTestBasic.class,
 +                             "+date", "-intProp", "~stringProp", "longProp", "+doubleProp");
 +
 +        assertEquals(list_3, list_4);
 +        assertTrue(list_3 == list_4);
 +    }
 +
 +    public void testReverseDirections() throws Exception {
 +        OrderingList<StorableTestBasic> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp");
 +
 +        list_1 = list_1.reverseDirections();
 +
 +        assertEquals(3, list_1.size());
 +        assertEquals("-date", list_1.get(0).toString());
 +        assertEquals("+intProp", list_1.get(1).toString());
 +        assertEquals("~stringProp", list_1.get(2).toString());
 +
 +        OrderingList<StorableTestBasic> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "-date", "intProp", "~stringProp");
 +
 +        assertEquals(list_1, list_2);
 +        assertTrue(list_1 == list_2);
 +    }
 +
 +    public void testReplace() throws Exception {
 +        OrderingList<StorableTestBasic> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp");
 +
 +        OrderedProperty<StorableTestBasic> op_0 = list_1.get(0);
 +        OrderedProperty<StorableTestBasic> op_1 = list_1.get(1);
 +        OrderedProperty<StorableTestBasic> op_2 = list_1.get(2);
 +
 +        list_1 = list_1.replace(0, op_1);
 +        list_1 = list_1.replace(1, op_2);
 +        list_1 = list_1.replace(2, op_0);
 +
 +        assertEquals(3, list_1.size());
 +        assertEquals("-intProp", list_1.get(0).toString());
 +        assertEquals("~stringProp", list_1.get(1).toString());
 +        assertEquals("+date", list_1.get(2).toString());
 +
 +        OrderingList<StorableTestBasic> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "-intProp", "~stringProp", "+date");
 +
 +        assertEquals(list_1, list_2);
 +        assertTrue(list_1 == list_2);
 +    }
 +
 +    public void testSubList() throws Exception {
 +        OrderingList<StorableTestBasic> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp");
 +
 +        assertEquals(0, list_1.subList(0, 0).size());
 +        assertEquals(list_1, list_1.subList(0, 3));
 +
 +        OrderingList<StorableTestBasic> sub = list_1.subList(0, 1);
 +        assertEquals(1, sub.size());
 +        assertEquals("+date", sub.get(0).toString());
 +
 +        sub = list_1.subList(1, 3);
 +        assertEquals(2, sub.size());
 +    }
 +
 +    public void testAsArray() throws Exception {
 +        OrderingList<StorableTestBasic> list =
 +            OrderingList.get(StorableTestBasic.class, "date", "intProp", "stringProp");
 +
 +        OrderedProperty<StorableTestBasic>[] array = list.asArray();
 +
 +        assertEquals(3, array.length);
 +        assertEquals("+date", array[0].toString());
 +        assertEquals("+intProp", array[1].toString());
 +        assertEquals("+stringProp", array[2].toString());
 +    }
 +
 +    public void testAsStringArray() throws Exception {
 +        OrderingList<StorableTestBasic> list =
 +            OrderingList.get(StorableTestBasic.class, "date", "intProp", "stringProp");
 +
 +        String[] array = list.asStringArray();
 +
 +        assertEquals(3, array.length);
 +        assertEquals("+date", array[0]);
 +        assertEquals("+intProp", array[1]);
 +        assertEquals("+stringProp", array[2]);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java b/src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java new file mode 100644 index 0000000..785ef69 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java @@ -0,0 +1,673 @@ +/*
 + * 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.Collections;
 +import java.util.Comparator;
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Storable;
 +
 +import com.amazon.carbonado.info.Direction;
 +import static com.amazon.carbonado.info.Direction.*;
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableIndex;
 +import com.amazon.carbonado.info.StorableInfo;
 +import com.amazon.carbonado.info.StorableIntrospector;
 +import com.amazon.carbonado.info.StorableProperty;
 +
 +import com.amazon.carbonado.filter.Filter;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestOrderingScore extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestOrderingScore.class);
 +    }
 +
 +    static <S extends Storable> StorableIndex<S> makeIndex(Class<S> type, String... props) {
 +        return new StorableIndex<S>(makeOrdering(type, props).asArray(), UNSPECIFIED);
 +    }
 +
 +    static <S extends Storable> OrderingList<S> makeOrdering(Class<S> type, String... props) {
 +        return OrderingList.get(type, props);
 +    }
 +
 +    public TestOrderingScore(String name) {
 +        super(name);
 +    }
 +
 +    public void testEmpty() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id");
 +
 +        OrderingScore score = OrderingScore.evaluate(ix, null, null);
 +
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +    }
 +
 +    public void testOneProp() throws Exception {
 +        StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore<StorableTestBasic> score;
 +
 +        /////////////
 +        ix = makeIndex(StorableTestBasic.class, "id");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "+id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        /////////////
 +        ix = makeIndex(StorableTestBasic.class, "+id");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "+id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        /////////////
 +        ix = makeIndex(StorableTestBasic.class, "-id");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "+id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        /////////////
 +        ix = makeIndex(StorableTestBasic.class, "intProp");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+id", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +    }
 +
 +    public void testMultipleProps() throws Exception {
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore<StorableTestBasic> score;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "-intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "+intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+intProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "+id", "-intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-intProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+intProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Gap is allowed if identity filtered.
 +
 +        Filter<StorableTestBasic> filter;
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-intProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp", "id");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        // Since "id" is filtered, don't count as remainder.
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-intProp", "id");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-intProp", score.getHandledOrdering().get(0).toString());
 +        // Since "id" is filtered, don't count as remainder.
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp", "doubleProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+doubleProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp", "-doubleProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-doubleProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id > ? & doubleProp = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+intProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "doubleProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "doubleProp = ? & id = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "doubleProp", "-intProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +    }
 +
 +    public void testMidGap() throws Exception {
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(4, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(2).toString());
 +        assertEquals("-stringProp", score.getHandledOrdering().get(3).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Now ignore mid index properties, creating a gap.
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-stringProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Gap can be bridged if property is filtered out. First test with
 +        // incomplete bridges.
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "doubleProp = ? & intProp > ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-stringProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "doubleProp >= ? & intProp = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-stringProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Now a complete bridge.
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "doubleProp = ? & intProp = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-stringProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Again in reverse.
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+stringProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        // Failed double reverse.
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-stringProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(true, score.shouldReverseOrder());
 +    }
 +
 +    public void testComparator() throws Exception {
 +        StorableIndex<StorableTestBasic> ix_1, ix_2;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score_1, score_2;
 +        Filter<StorableTestBasic> filter;
 +        Comparator<OrderingScore<?>> comp = OrderingScore.fullComparator();
 +
 +        ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +        ix_2 = makeIndex(StorableTestBasic.class, "intProp", "doubleProp", "id");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "-intProp");
 +        score_1 = OrderingScore.evaluate(ix_1, null, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, null, ops);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        // Index 2 has less properties, so it is better.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Keep ix_2 slightly better by matching desired order.
 +        ix_2 = makeIndex(StorableTestBasic.class, "-intProp", "doubleProp", "id", "stringProp");
 +
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Make ix_1 slightly better by making it clustered.
 +        ix_1 = ix_1.clustered(true);
 +
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        // Make ix_2 better when clustered.
 +        ix_2 = ix_2.clustered(true);
 +
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Make ix_1 same by reversing order.
 +        ix_1 = ix_1.reverse();
 +
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +    }
 +
 +    public void testIndexNotNeeded() throws Exception {
 +        // Test an index which matches desired orderings, but ordering
 +        // properties are filtered out. Thus the index is not needed.
 +
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(3, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(2).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ? & doubleProp =?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +    }
 +
 +    public void testUniqueIndexNotNeeded() throws Exception {
 +        // Test a unique index which has been fully specified. Ordering is not
 +        // needed at all.
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp").unique(true);
 +        ops = makeOrdering(StorableTestBasic.class, "stringProp", "doubleProp");
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ? & doubleProp =?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +    }
 +
 +    public void testReduce() throws Exception {
 +        // Tests that redundant ordering properties are removed.
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +        ops = makeOrdering(StorableTestBasic.class, 
 +                            "intProp", "intProp", "id", "doubleProp", "intProp", "doubleProp",
 +                            "longProp", "longProp", "id", "intProp", "doubleProp");
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("+longProp", score.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testUnspecifiedDirection() throws Exception {
 +        // Tests that an originally unspecified ordering direction is determined.
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +        ops = makeOrdering(StorableTestBasic.class, "~intProp", "-doubleProp");
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        assertEquals("-intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-doubleProp", score.getHandledOrdering().get(1).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "intProp", "~doubleProp");
 +
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(3, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(2).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp", "~doubleProp");
 +
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(3, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("-doubleProp", score.getHandledOrdering().get(2).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp", "~longProp");
 +
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("~longProp", score.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testFreeOrdering() throws Exception {
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter = null;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +        ops = makeOrdering(StorableTestBasic.class, "~id");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +        assertEquals(2, score.getFreeOrdering().size());
 +        assertEquals("~intProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals("~doubleProp", score.getFreeOrdering().get(1).toString());
 +        assertEquals(0, score.getUnusedOrdering().size());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +        assertEquals(1, score.getFreeOrdering().size());
 +        assertEquals("-doubleProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals(0, score.getUnusedOrdering().size());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp", "+doubleProp");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+doubleProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(true, score.shouldReverseOrder());
 +        assertEquals(1, score.getFreeOrdering().size());
 +        assertEquals("-doubleProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals(0, score.getUnusedOrdering().size());
 +    }
 +
 +    public void testFreeAndUnusedOrdering() throws Exception {
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "stringProp", "id", "intProp", "doubleProp");
 +        ops = makeOrdering(StorableTestBasic.class, "~id");
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +        assertEquals(2, score.getFreeOrdering().size());
 +        assertEquals("~intProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals("~doubleProp", score.getFreeOrdering().get(1).toString());
 +        assertEquals(1, score.getUnusedOrdering().size());
 +        assertEquals("~stringProp", score.getUnusedOrdering().get(0).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +        assertEquals(1, score.getFreeOrdering().size());
 +        assertEquals("-doubleProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals(1, score.getUnusedOrdering().size());
 +        assertEquals("~stringProp", score.getUnusedOrdering().get(0).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp", "+doubleProp");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+doubleProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(true, score.shouldReverseOrder());
 +        assertEquals(1, score.getFreeOrdering().size());
 +        assertEquals("-doubleProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals(1, score.getUnusedOrdering().size());
 +        assertEquals("~stringProp", score.getUnusedOrdering().get(0).toString());
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java b/src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java new file mode 100644 index 0000000..b0af52f --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java @@ -0,0 +1,108 @@ +/*
 + * 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.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.PropertyFilter;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestPropertyFilterList extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestPropertyFilterList.class);
 +    }
 +
 +    public TestPropertyFilterList(String name) {
 +        super(name);
 +    }
 +
 +    public void testNull() throws Exception {
 +        assertEquals(0, PropertyFilterList.get(null).size());
 +    }
 +
 +    public void testOpen() throws Exception {
 +        Filter<StorableTestBasic> filter = Filter.getOpenFilter(StorableTestBasic.class);
 +        assertEquals(0, PropertyFilterList.get(filter).size());
 +    }
 +
 +    public void testClosed() throws Exception {
 +        Filter<StorableTestBasic> filter = Filter.getClosedFilter(StorableTestBasic.class);
 +        assertEquals(0, PropertyFilterList.get(filter).size());
 +    }
 +
 +    public void testSingleton() throws Exception {
 +        Filter<StorableTestBasic> filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        List<PropertyFilter<StorableTestBasic>> list = PropertyFilterList.get(filter);
 +
 +        assertEquals(1, list.size());
 +        assertEquals(filter, list.get(0));
 +
 +        List<PropertyFilter<StorableTestBasic>> list2 = PropertyFilterList.get(filter);
 +
 +        assertTrue(list == list2);
 +    }
 +
 +    public void testMultiple() throws Exception {
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?");
 +
 +        List<PropertyFilter<StorableTestBasic>> list = PropertyFilterList.get(filter);
 +
 +        assertEquals(2, list.size());
 +
 +        Filter<StorableTestBasic> subFilter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        assertEquals(subFilter, list.get(0));
 +
 +        subFilter = Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +
 +        assertEquals(subFilter, list.get(1));
 +
 +        List<PropertyFilter<StorableTestBasic>> list2 = PropertyFilterList.get(filter);
 +
 +        assertTrue(list == list2);
 +    }
 +
 +    public void testIllegal() throws Exception {
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ? | intProp > ?");
 +
 +        try {
 +            List<PropertyFilter<StorableTestBasic>> list = PropertyFilterList.get(filter);
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java new file mode 100644 index 0000000..07558f1 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java @@ -0,0 +1,186 @@ +/*
 + * 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 java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Comparator;
 +import java.util.List;
 +import java.util.Map;
 +
 +import junit.framework.TestCase;
 +
 +import com.amazon.carbonado.Cursor;
 +import com.amazon.carbonado.FetchException;
 +
 +import com.amazon.carbonado.info.Direction;
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableInfo;
 +import com.amazon.carbonado.info.StorableIntrospector;
 +import com.amazon.carbonado.info.StorableProperty;
 +
 +import com.amazon.carbonado.stored.Dummy;
 +import com.amazon.carbonado.stored.Address;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public abstract class TestQueryExecutor extends TestCase {
 +
 +    protected QueryExecutor<Address> createExecutor(int... ids) {
 +        return new IterableQueryExecutor<Address>(Address.class, createCollection(ids));
 +    }
 +
 +    protected Collection<Address> createCollection(int... ids) {
 +        Collection<Address> elements = new ArrayList<Address>(ids.length);
 +        for (int i=0; i<ids.length; i++) {
 +            elements.add(new DummyAddress(ids[i]));
 +        }
 +        return elements;
 +    }
 +
 +    protected void compareElements(Cursor<Address> elements, int... expectedIDs)
 +        throws FetchException
 +    {
 +        for (int id : expectedIDs) {
 +            if (elements.hasNext()) {
 +                Address e = elements.next();
 +                if (e.getAddressID() != id) {
 +                    fail("Element mismatch: expected=" + id + ", actual=" + e.getAddressID());
 +                    elements.close();
 +                    return;
 +                }
 +            } else {
 +                fail("Too few elements in cursor");
 +                return;
 +            }
 +        }
 +
 +        if (elements.hasNext()) {
 +            Address e = elements.next();
 +            fail("Too many elements in cursor: " + e.getAddressID());
 +            elements.close();
 +        }
 +    }
 +
 +    protected OrderingList<Address> createOrdering(String... properties) {
 +        return OrderingList.get(Address.class, properties);
 +    }
 +
 +    static void printPlan(QueryExecutor executor) {
 +        try {
 +            executor.printPlan(System.out, 0, null);
 +        } catch (IOException e) {
 +        }
 +    }
 +
 +    private static class DummyAddress extends Dummy implements Address {
 +        private long mID;
 +        private String mLine1;
 +        private String mLine2;
 +        private String mCity;
 +        private String mState;
 +        private String mZip;
 +        private String mCountry;
 +        private String mData;
 +
 +        DummyAddress(long id) {
 +            mID = id;
 +            mLine1 = "line1_" + id;
 +            mLine2 = "line2_" + id;
 +            mCity = "city_" + id;
 +            mState = "state_" + id;
 +            mZip = "zip_" + id;
 +            mCountry = "country_" + id;
 +            mData = "data_" + id;
 +        }
 +
 +        public long getAddressID() {
 +            return mID;
 +        }
 +
 +        public void setAddressID(long id) {
 +            mID = id;
 +        }
 +
 +        public String getAddressLine1() {
 +            return mLine1;
 +        }
 +
 +        public void setAddressLine1(String value) {
 +            mLine1 = value;
 +        }
 +
 +        public String getAddressLine2() {
 +            return mLine2;
 +        }
 +
 +        public void setAddressLine2(String value) {
 +            mLine2 = value;
 +        }
 +
 +        public String getAddressCity() {
 +            return mCity;
 +        }
 +
 +        public void setAddressCity(String value) {
 +            mCity = value;
 +        }
 +
 +        public String getAddressState() {
 +            return mState;
 +        }
 +
 +        public void setAddressState(String value) {
 +            mState = value;
 +        }
 +
 +        public String getAddressZip() {
 +            return mZip;
 +        }
 +
 +        public void setAddressZip(String value) {
 +            mZip = value;
 +        }
 +
 +        public String getAddressCountry() {
 +            return mCountry;
 +        }
 +
 +        public void setAddressCountry(String value) {
 +            mCountry = value;
 +        }
 +
 +        public String getCustomData() {
 +            return mData;
 +        }
 +
 +        public void setCustomData(String str) {
 +            mData = str;
 +        }
 +
 +        public String toString() {
 +            return "address " + mID;
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java new file mode 100644 index 0000000..53c47d6 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java @@ -0,0 +1,84 @@ +/*
 + * 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.List;
 +
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +
 +import com.amazon.carbonado.stored.Address;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestSortedQueryExecutor extends TestQueryExecutor {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestSortedQueryExecutor.class);
 +    }
 +
 +    public void testBasicSorting() throws Exception {
 +        QueryExecutor<Address> unsorted = createExecutor(4, 2, 3, 1);
 +        Filter<Address> filter = Filter.getOpenFilter(Address.class);
 +        FilterValues<Address> values = filter.initialFilterValues();
 +        OrderingList<Address> ordered = createOrdering("addressCountry");
 +
 +        QueryExecutor<Address> executor =
 +            new SortedQueryExecutor<Address>(null, unsorted, null, ordered);
 +
 +        assertEquals(filter, executor.getFilter());
 +
 +        assertEquals(4, executor.count(values));
 +
 +        assertEquals(ordered, executor.getOrdering());
 +
 +        compareElements(executor.fetch(values), 1, 2, 3, 4);
 +    }
 +
 +    public void testBasicFinisherSorting() throws Exception {
 +        QueryExecutor<Address> unsorted = createExecutor(1, 2, 3, 4);
 +        Filter<Address> filter = Filter.getOpenFilter(Address.class);
 +        FilterValues<Address> values = filter.initialFilterValues();
 +        OrderingList<Address> handled = createOrdering("addressCountry");
 +        OrderingList<Address> finisher = createOrdering("addressState");
 +
 +        QueryExecutor<Address> executor =
 +            new SortedQueryExecutor<Address>(null, unsorted, handled, finisher);
 +
 +        assertEquals(filter, executor.getFilter());
 +
 +        assertEquals(4, executor.count(values));
 +
 +        assertEquals(2, executor.getOrdering().size());
 +        assertEquals(handled.get(0), executor.getOrdering().get(0));
 +        assertEquals(finisher.get(0), executor.getOrdering().get(1));
 +
 +        compareElements(executor.fetch(values), 1, 2, 3, 4);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java new file mode 100644 index 0000000..21d0b0c --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java @@ -0,0 +1,565 @@ +/*
 + * 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.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Repository;
 +import com.amazon.carbonado.Storable;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableIndex;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +import com.amazon.carbonado.filter.PropertyFilter;
 +
 +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 com.amazon.carbonado.stored.StorableTestBasic;
 +
 +import static com.amazon.carbonado.qe.TestIndexedQueryExecutor.Mock;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestUnionQueryAnalyzer extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestUnionQueryAnalyzer.class);
 +    }
 +
 +    static <S extends Storable> StorableIndex<S> makeIndex(Class<S> type, String... props) {
 +        return TestOrderingScore.makeIndex(type, props);
 +    }
 +
 +    static <S extends Storable> OrderingList<S> makeOrdering(Class<S> type, String... props) {
 +        return TestOrderingScore.makeOrdering(type, props);
 +    }
 +
 +    public TestUnionQueryAnalyzer(String name) {
 +        super(name);
 +    }
 +
 +    public void testNullFilter() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        UnionQueryAnalyzer.Result result = uqa.analyze(null, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +    }
 +
 +    public void testSingleSubResult() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentID = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +    }
 +
 +    public void testSingleSubResultUnspecifiedDirection() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentID > ?");
 +        filter = filter.bind();
 +        OrderingList<Shipment> orderings =
 +            makeOrdering(Shipment.class, "~shipmentID", "~orderID");
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, orderings);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +        List<OrderedProperty<Shipment>> handled = 
 +            subResults.get(0).getCompositeScore().getOrderingScore().getHandledOrdering();
 +        assertEquals(1, handled.size());
 +        assertEquals("+shipmentID", handled.get(0).toString());
 +    }
 +
 +    public void testSimpleUnion() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class,
 +                                                   "shipmentID = ? | orderID = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(Shipment.class, "+shipmentID"), result.getTotalOrdering());
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ?").bind(),
 +                     res_0.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +
 +        assertTrue(res_1.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "orderID = ?").bind(),
 +                     res_1.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
 +                     res_1.getLocalIndex());
 +        assertEquals(null, res_1.getForeignIndex());
 +        assertEquals(null, res_1.getForeignProperty());
 +        assertEquals(1, res_1.getRemainderOrdering().size());
 +        assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
 +    }
 + 
 +    public void testSimpleUnion2() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class,
 +                                                   "shipmentID = ? | orderID > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(Shipment.class, "+shipmentID"), result.getTotalOrdering());
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ?").bind(),
 +                     res_0.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +
 +        assertTrue(res_1.handlesAnything());
 +        assertTrue(res_1.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertFalse(res_1.getCompositeScore().getFilteringScore().hasRangeEnd());
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
 +                     res_1.getLocalIndex());
 +        assertEquals(null, res_1.getForeignIndex());
 +        assertEquals(null, res_1.getForeignProperty());
 +        assertEquals(1, res_1.getRemainderOrdering().size());
 +        assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testSimpleUnion3() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class,
 +                                                   "shipmentID = ? | orderID > ? & orderID <= ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(Shipment.class, "+shipmentID"), result.getTotalOrdering());
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ?").bind(),
 +                     res_0.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +
 +        // Note: index that has proper filtering is preferred because
 +        // "orderId > ? & orderID <= ?" filter specifies a complete range.
 +        // We'll have to do a sort, but it isn't expected to be over that many records.
 +        assertTrue(res_1.handlesAnything());
 +        assertTrue(res_1.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertTrue(res_1.getCompositeScore().getFilteringScore().hasRangeEnd());
 +        List<PropertyFilter<Shipment>> rangeFilters =
 +            res_1.getCompositeScore().getFilteringScore().getRangeStartFilters();
 +        assertEquals(1, rangeFilters.size());
 +        assertEquals(Filter.filterFor(Shipment.class, "orderID > ?").bind(), rangeFilters.get(0));
 +        rangeFilters = res_1.getCompositeScore().getFilteringScore().getRangeEndFilters();
 +        assertEquals(1, rangeFilters.size());
 +        assertEquals(Filter.filterFor(Shipment.class, "orderID <= ?").bind(), rangeFilters.get(0));
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"), res_1.getLocalIndex());
 +        assertEquals(null, res_1.getForeignIndex());
 +        assertEquals(null, res_1.getForeignProperty());
 +        // Sort operation required because the "shipmentID" index was not chosen.
 +        assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testSimpleUnionUnspecifiedDirection() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class,
 +                                                   "shipmentID > ? | orderID = ?");
 +        filter = filter.bind();
 +        OrderingList<Shipment> orderings =
 +            makeOrdering(Shipment.class, "~shipmentID", "~orderID");
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, orderings);
 +        assertEquals(OrderingList.get(Shipment.class, "+shipmentID", "+orderID"),
 +                     result.getTotalOrdering());
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        List<OrderedProperty<Shipment>> handled =
 +            res_0.getCompositeScore().getOrderingScore().getHandledOrdering();
 +        assertEquals(1, handled.size());
 +        assertEquals("+shipmentID", handled.get(0).toString());
 +
 +        handled = res_1.getCompositeScore().getOrderingScore().getHandledOrdering();
 +        assertEquals(0, handled.size());
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertTrue(res_0.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertFalse(res_0.getCompositeScore().getFilteringScore().hasRangeEnd());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(1, res_0.getRemainderOrdering().size());
 +        assertEquals("+orderID", res_0.getRemainderOrdering().get(0).toString());
 +
 +        assertTrue(res_1.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "orderID = ?").bind(),
 +                     res_1.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"), res_1.getLocalIndex());
 +        assertEquals(null, res_1.getForeignIndex());
 +        assertEquals(null, res_1.getForeignProperty());
 +        assertEquals(1, res_1.getRemainderOrdering().size());
 +        assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testSimpleMerge() throws Exception {
 +        // Because query has an 'or' operation, the analyzer will initially
 +        // split this into a union. After futher analysis, it should decide
 +        // that this offers no benefit and will merge them back.
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class,
 +             "shipmentID = ? & (shipmentID = ? | orderID = ?)");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ?").bind(),
 +                     res_0.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ? | orderID = ?"),
 +                     res_0.getRemainderFilter().unbind());
 +    }
 +
 +    public void testFullScan() throws Exception {
 +        // Because no indexes were selected, there's no union to perform.
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes = ? | shipperID = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +
 +        assertFalse(res_0.handlesAnything());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ? | shipperID = ?").bind(),
 +                     res_0.getRemainderFilter());
 +    }
 +
 +    public void testFullScanFallback() throws Exception {
 +        // Because not all sub-results of union use an index, just fallback to
 +        // doing a full scan.
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes = ? | orderID = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +
 +        assertFalse(res_0.handlesAnything());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ? | orderID = ?").bind(),
 +                     res_0.getRemainderFilter());
 +    }
 +
 +    public void testFullScanExempt() throws Exception {
 +        // Although not all sub-results use an index, one that does has a join
 +        // so it is exempt from folding into the full scan.
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes = ? | orderID = ? & order.orderTotal > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(1, res_0.getRemainderOrdering().size());
 +        assertEquals("+shipmentID", res_0.getRemainderOrdering().get(0).toString());
 +        assertEquals(Filter.filterFor(Shipment.class, "order.orderTotal > ?").bind(),
 +                     res_0.getRemainderFilter());
 +
 +        assertTrue(res_1.handlesAnything());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_1.getLocalIndex());
 +        assertEquals(null, res_1.getForeignIndex());
 +        assertEquals(null, res_1.getForeignProperty());
 +        assertEquals(0, res_1.getRemainderOrdering().size());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ?").bind(),
 +                     res_1.getRemainderFilter());
 +
 +    }
 +
 +    public void testComplexUnionPlan() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "doubleProp = ? | (stringProp = ? & intProp = ?) | id > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+id"), result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +
 +        assertEquals(filter, exec.getFilter());
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+id"), exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(3, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  sort: [+id]\n" +
 +            "    index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[-doubleProp, +longProp, ~id], unique=true}\n" +
 +            "    ...identity filter: doubleProp = ?\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[-stringProp, -intProp, ~id], unique=true}\n" +
 +            "  ...identity filter: stringProp = ? & intProp = ?\n" +
 +            "  clustered index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+id], unique=true}\n" +
 +            "  ...range filter: id > ?\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +
 +    public void testComplexUnionPlan2() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "doubleProp = ? | stringProp = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+stringProp"),
 +                     result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +
 +        assertEquals(filter, exec.getFilter());
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+stringProp"),
 +                     exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  sort: [+stringProp]\n" +
 +            "    index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[-doubleProp, +longProp, ~id], unique=true}\n" +
 +            "    ...identity filter: doubleProp = ?\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +
 +    public void testComplexUnionPlan3() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "stringProp = ? | stringProp = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
 +                     result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
 +                     exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?[2]\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +
 +    public void testComplexUnionPlan4() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "doubleProp = ? | stringProp = ? | id > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+id"),
 +                     result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +
 +        assertEquals(filter, exec.getFilter());
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+id"),
 +                     exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(3, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  sort: [+id]\n" +
 +            "    index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[-doubleProp, +longProp, ~id], unique=true}\n" +
 +            "    ...identity filter: doubleProp = ?\n" +
 +            "  sort: [+doubleProp], [+id]\n" +
 +            "    index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "    ...identity filter: stringProp = ?\n" +
 +            "  sort: [+doubleProp, +id]\n" +
 +            "    clustered index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[+id], unique=true}\n" +
 +            "    ...range filter: id > ?\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +
 +    public void testComplexUnionPlan5() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "stringProp = ? | stringProp = ? | id > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
 +                     result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +
 +        assertEquals(filter, exec.getFilter());
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
 +                     exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(3, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?[2]\n" +
 +            "  sort: [+stringProp, +doubleProp]\n" +
 +            "    clustered index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[+id], unique=true}\n" +
 +            "    ...range filter: id > ?\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java new file mode 100644 index 0000000..9fccbc7 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java @@ -0,0 +1,75 @@ +/*
 + * 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.List;
 +
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +
 +import com.amazon.carbonado.stored.Address;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestUnionQueryExecutor extends TestQueryExecutor {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestUnionQueryExecutor.class);
 +    }
 +
 +    public void testBasicUnion() throws Exception {
 +        QueryExecutor<Address> primary = new SortedQueryExecutor<Address>
 +            (null, createExecutor(1, 2, 3, 4, 5, 6, 7, 8), null, createOrdering("addressID"));
 +
 +        Filter<Address> filter_1 = Filter.filterFor(Address.class, "addressCountry > ?");
 +        FilterValues<Address> values_1 = filter_1.initialFilterValues();
 +        QueryExecutor<Address> executor_1 = new FilteredQueryExecutor<Address>(primary, filter_1);
 +
 +        Filter<Address> filter_2 = Filter.filterFor(Address.class, "addressState <= ?");
 +        FilterValues<Address> values_2 = filter_2.initialFilterValues();
 +        QueryExecutor<Address> executor_2 = new FilteredQueryExecutor<Address>(primary, filter_2);
 +
 +        QueryExecutor<Address> union = new UnionQueryExecutor<Address>(executor_1, executor_2);
 +
 +        Filter<Address> filter = Filter
 +            .filterFor(Address.class, "addressCountry > ? | addressState <= ?");
 +        FilterValues<Address> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        assertEquals(filter, union.getFilter());
 +
 +        values = values.with("country_6").with("state_3");
 +
 +        assertEquals(5, union.count(values));
 +
 +        assertEquals(primary.getOrdering(), union.getOrdering());
 +
 +        compareElements(union.fetch(values), 1, 2, 3, 7, 8);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/raw/TestDataEncoding.java b/src/test/java/com/amazon/carbonado/raw/TestDataEncoding.java new file mode 100644 index 0000000..bff5820 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/raw/TestDataEncoding.java @@ -0,0 +1,515 @@ +/*
 + * 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.raw;
 +
 +import java.util.Random;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +/**
 + * Test case for {@link DataEncoder} and {@link DataDecoder}. 
 + * <p>
 + * It generates random data values, checks that the decoding produces the
 + * original results, and it checks that the order of the encoded bytes matches
 + * the order of the original data values.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestDataEncoding extends TestCase {
 +    private static final int SHORT_TEST = 100;
 +    private static final int MEDIUM_TEST = 500;
 +    private static final int LONG_TEST = 1000;
 +
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestDataEncoding.class);
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static int byteArrayCompare(byte[] aa, byte[] ab) {
 +        int len = Math.min(aa.length, ab.length);
 +        int result = byteArrayCompare(aa, ab, len);
 +        if (result == 0 && aa.length != ab.length) {
 +            if (aa.length == len) {
 +                return -1;
 +            }
 +            if (ab.length == len) {
 +                return 1;
 +            }
 +        }
 +        return result;
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static int byteArrayCompare(byte[] aa, byte[] ab, int len) {
 +        for (int i=0; i<len; i++) {
 +            int a = aa[i] & 0xff;
 +            int b = ab[i] & 0xff;
 +            if (a < b) {
 +                return -1;
 +            }
 +            if (a > b) {
 +                return 1;
 +            }
 +        }
 +        return 0;
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static int byteArrayOrNullCompare(byte[] aa, byte[] ab) {
 +        if (aa == null) {
 +            if (ab == null) {
 +                return 0;
 +            } else {
 +                return 1;
 +            }
 +        } else if (ab == null) {
 +            return -1;
 +        } else {
 +            return byteArrayCompare(aa, ab);
 +        }
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static int byteArrayOrNullCompare(byte[] aa, byte[] ab, int len) {
 +        if (aa == null) {
 +            if (ab == null) {
 +                return 0;
 +            } else {
 +                return 1;
 +            }
 +        } else if (ab == null) {
 +            return -1;
 +        } else {
 +            return byteArrayCompare(aa, ab, len);
 +        }
 +    }
 +
 +    /**
 +     * @return -1, 0, or 1
 +     */
 +    static <C extends Comparable> int compare(C a, C b) {
 +        if (a == null) {
 +            if (b == null) {
 +                return 0;
 +            } else {
 +                return 1;
 +            }
 +        } else if (b == null) {
 +            return -1;
 +        } else {
 +            return Integer.signum(a.compareTo(b));
 +        }
 +    }
 +
 +    private final long mSeed;
 +
 +    private Random mRandom;
 +
 +    public TestDataEncoding(String name) {
 +        super(name);
 +        mSeed = 5399777425345431L;
 +    }
 +
 +    protected void setUp() {
 +        mRandom = new Random(mSeed);
 +    }
 +
 +    protected void tearDown() {
 +    }
 +
 +    public void test_boolean() throws Exception {
 +        byte[] bytes = new byte[1];
 +        boolean lastValue = false;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            boolean value = mRandom.nextBoolean();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeBoolean(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Boolean() throws Exception {
 +        byte[] bytes = new byte[1];
 +        Boolean lastValue = false;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Boolean value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = mRandom.nextBoolean();
 +            }
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeBooleanObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_byte() throws Exception {
 +        byte[] bytes = new byte[1];
 +        byte lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            byte value = (byte) mRandom.nextInt();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeByte(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Byte() throws Exception {
 +        byte[] bytes = new byte[2];
 +        Byte lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Byte value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = (byte) mRandom.nextInt();
 +                assertEquals(2, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeByteObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_short() throws Exception {
 +        byte[] bytes = new byte[2];
 +        short lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            short value = (short) mRandom.nextInt();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeShort(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Short() throws Exception {
 +        byte[] bytes = new byte[3];
 +        Short lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Short value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = (short) mRandom.nextInt();
 +                assertEquals(3, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeShortObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_char() throws Exception {
 +        byte[] bytes = new byte[2];
 +        char lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            char value = (char) mRandom.nextInt();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeChar(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Character() throws Exception {
 +        byte[] bytes = new byte[3];
 +        Character lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Character value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = (char) mRandom.nextInt();
 +                assertEquals(3, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeCharacterObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_int() throws Exception {
 +        byte[] bytes = new byte[4];
 +        int lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            int value = mRandom.nextInt();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeInt(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Integer() throws Exception {
 +        byte[] bytes = new byte[5];
 +        Integer lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Integer value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = mRandom.nextInt();
 +                assertEquals(5, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeIntegerObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_long() throws Exception {
 +        byte[] bytes = new byte[8];
 +        long lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            long value = mRandom.nextLong();
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeLong(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Long() throws Exception {
 +        byte[] bytes = new byte[9];
 +        Long lastValue = 0L;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Long value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, DataEncoder.encode(value, bytes, 0));
 +            } else {
 +                value = mRandom.nextLong();
 +                assertEquals(9, DataEncoder.encode(value, bytes, 0));
 +            }
 +            assertEquals(value, DataDecoder.decodeLongObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_float() throws Exception {
 +        byte[] bytes = new byte[4];
 +        float lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            float value = Float.intBitsToFloat(mRandom.nextInt());
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeFloat(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Float() throws Exception {
 +        byte[] bytes = new byte[4];
 +        Float lastValue = 0f;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            Float value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = Float.intBitsToFloat(mRandom.nextInt());
 +            }
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeFloatObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_double() throws Exception {
 +        byte[] bytes = new byte[8];
 +        double lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            double value = Double.longBitsToDouble(mRandom.nextLong());
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeDouble(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_Double() throws Exception {
 +        byte[] bytes = new byte[8];
 +        Double lastValue = 0d;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            Double value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = Double.longBitsToDouble(mRandom.nextLong());
 +            }
 +            DataEncoder.encode(value, bytes, 0);
 +            assertEquals(value, DataDecoder.decodeDoubleObj(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = compare(value, lastValue);
 +                assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_String() throws Exception {
 +        String[] ref = new String[1];
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            String value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length;
 +                switch (mRandom.nextInt(15)) {
 +                default:
 +                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
 +                    length = mRandom.nextInt(100);
 +                    break;
 +                case 8: case 9: case 10: case 11:
 +                    length = mRandom.nextInt(200);
 +                    break;
 +                case 12: case 13:
 +                    length = mRandom.nextInt(20000);
 +                    break;
 +                case 14:
 +                    length = mRandom.nextInt(3000000);
 +                    break;
 +                }
 +                char[] chars = new char[length];
 +                for (int j=0; j<length; j++) {
 +                    char c;
 +                    switch (mRandom.nextInt(7)) {
 +                    default:
 +                    case 0: case 1: case 2: case 3:
 +                        c = (char) mRandom.nextInt(128);
 +                        break;
 +                    case 4: case 5:
 +                        c = (char) mRandom.nextInt(4000);
 +                        break;
 +                    case 6:
 +                        c = (char) mRandom.nextInt();
 +                        break;
 +                    }
 +                    chars[j] = c;
 +                }
 +                value = new String(chars);
 +            }
 +
 +            byte[] bytes = new byte[DataEncoder.calculateEncodedStringLength(value)];
 +            assertEquals(bytes.length, DataEncoder.encode(value, bytes, 0));
 +            assertEquals(bytes.length, DataDecoder.decodeString(bytes, 0, ref));
 +            assertEquals(value, ref[0]);
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/raw/TestEncodingStrategy.java b/src/test/java/com/amazon/carbonado/raw/TestEncodingStrategy.java new file mode 100644 index 0000000..11a4eca --- /dev/null +++ b/src/test/java/com/amazon/carbonado/raw/TestEncodingStrategy.java @@ -0,0 +1,857 @@ +/*
 + * 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.raw;
 +
 +import java.lang.reflect.*;
 +import java.util.*;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import org.cojen.classfile.*;
 +import org.cojen.util.*;
 +
 +import com.amazon.carbonado.*;
 +import com.amazon.carbonado.info.*;
 +import com.amazon.carbonado.spi.*;
 +
 +/**
 + * Test case for {@link GenericEncodingStrategy}.
 + * <p>
 + * It generates random selections of properties, encodes with random values,
 + * and checks that the decoding produces the original results. In addition, the
 + * proper ordering of encoded keys is checked.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestEncodingStrategy extends TestCase {
 +    private static final int SHORT_TEST = 100;
 +    private static final int MEDIUM_TEST = 500;
 +    private static final int LONG_TEST = 1000;
 +
 +    private static final int ENCODE_OBJECT_ARRAY = 0;
 +    private static final int DECODE_OBJECT_ARRAY = 1;
 +    private static final int ENCODE_OBJECT_ARRAY_PARTIAL = 2;
 +
 +    private static final int BOGUS_GENERATION = 99;
 +
 +    // Make sure BOGUS_GENERATION is not included.
 +    private static final int[] GENERATIONS = {-1, 0, 1, 2, 127, 128, 129, Integer.MAX_VALUE};
 +
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestEncodingStrategy.class);
 +    }
 +
 +    private final long mSeed;
 +    private final StorableProperty<TestStorable>[] mProperties;
 +
 +    private Random mRandom;
 +
 +    public TestEncodingStrategy(String name) {
 +        super(name);
 +        mSeed = 986184829029842L;
 +        Collection<? extends StorableProperty<TestStorable>> properties =
 +            StorableIntrospector.examine(TestStorable.class).getAllProperties().values();
 +        mProperties = properties.toArray(new StorableProperty[0]);
 +    }
 +
 +    protected void setUp() {
 +        mRandom = new Random(mSeed);
 +    }
 +
 +    protected void tearDown() {
 +    }
 +
 +    public void test_dataEncoding_noProperties() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_noProperties(0, 0, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_noProperties_prefix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_noProperties(5, 0, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_noProperties_suffix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_noProperties(0, 7, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_noProperties_prefixAndSuffix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_noProperties(5, 7, generation);
 +        }
 +    }
 +
 +    private void test_dataEncoding_noProperties(int prefix, int suffix, int generation)
 +        throws Exception
 +    {
 +        GenericEncodingStrategy strategy = new GenericEncodingStrategy
 +            (TestStorable.class, null, 0, 0, prefix, suffix);
 +
 +        Method[] methods = generateCodecMethods
 +            (strategy, new StorableProperty[0], null, generation);
 +
 +        byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +            .invoke(null, new Object[] {new Object[0]});
 +
 +        int generationPrefix;
 +        if (generation < 0) {
 +            generationPrefix = 0;
 +        } else if (generation < 128) {
 +            generationPrefix = 1;
 +        } else {
 +            generationPrefix = 4;
 +        }
 +
 +        assertEquals(encoded.length, prefix + generationPrefix + suffix);
 +
 +        if (generation >= 0) {
 +            if (generationPrefix == 1) {
 +                assertEquals(generation, encoded[prefix]);
 +            } else {
 +                int actualGeneration = DataDecoder.decodeInt(encoded, prefix);
 +                assertEquals(generation, actualGeneration);
 +            }
 +        }
 +
 +        // Decode should not throw an exception.
 +        methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
 +
 +        // Generation mismatch should throw an exception.
 +        if (generation >= 0) {
 +            encoded[prefix] = BOGUS_GENERATION;
 +            try {
 +                methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
 +                fail();
 +            } catch (InvocationTargetException e) {
 +                Throwable cause = e.getCause();
 +                if (cause instanceof CorruptEncodingException) {
 +                    CorruptEncodingException cee = (CorruptEncodingException) cause;
 +                    // Make sure error message includes actual generation.
 +                    assertTrue(cee.getMessage().indexOf(String.valueOf(BOGUS_GENERATION)) >= 0);
 +                } else {
 +                    throw e;
 +                }
 +            }
 +        }
 +    }
 +
 +    public void test_dataEncoding_multipleProperties() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_multipleProperties(0, 0, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_multipleProperties_prefix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_multipleProperties(5, 0, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_multipleProperties_suffix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_multipleProperties(0, 7, generation);
 +        }
 +    }
 +
 +    public void test_dataEncoding_multipleProperties_prefixAndSuffix() throws Exception {
 +        for (int generation : GENERATIONS) {
 +            test_dataEncoding_multipleProperties(5, 7, generation);
 +        }
 +    }
 +
 +    /**
 +     * @param generation when non-negative, encode a storable layout generation
 +     * value in one or four bytes.
 +     */
 +    private void test_dataEncoding_multipleProperties(int prefix, int suffix, int generation)
 +        throws Exception
 +    {
 +        GenericEncodingStrategy strategy = new GenericEncodingStrategy
 +            (TestStorable.class, null, 0, 0, prefix, suffix);
 +
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            StorableProperty<TestStorable>[] properties = selectProperties(1, 50);
 +
 +            Method[] methods = generateCodecMethods(strategy, properties, null, generation);
 +            Object[] values = selectPropertyValues(properties);
 +
 +            byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +                .invoke(null, new Object[] {values});
 +
 +            int generationPrefix;
 +            if (generation < 0) {
 +                generationPrefix = 0;
 +            } else if (generation < 128) {
 +                generationPrefix = 1;
 +            } else {
 +                generationPrefix = 4;
 +            }
 +
 +            assertTrue(encoded.length > (prefix + generationPrefix + suffix));
 +
 +            if (generation >= 0) {
 +                if (generationPrefix == 1) {
 +                    assertEquals(generation, encoded[prefix]);
 +                } else {
 +                    int actualGeneration = DataDecoder.decodeInt(encoded, prefix);
 +                    assertEquals(generation, actualGeneration);
 +                }
 +            }
 +
 +            if (prefix > 0) {
 +                // Fill in with data which should be ignored by decoder.
 +                for (int p=0; p<prefix; p++) {
 +                    encoded[p] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            if (suffix > 0) {
 +                // Fill in with data which should be ignored by decoder.
 +                for (int p=0; p<suffix; p++) {
 +                    encoded[encoded.length - p - 1] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            Object[] decodedValues = new Object[values.length];
 +            methods[DECODE_OBJECT_ARRAY].invoke(null, decodedValues, encoded);
 +
 +            for (int j=0; j<properties.length; j++) {
 +                Object a = values[j];
 +                Object b = decodedValues[j];
 +                if (properties[j].getType() == byte[].class) {
 +                    assertTrue(0 == TestDataEncoding.byteArrayOrNullCompare
 +                               ((byte[]) a, (byte[]) b));
 +                } else {
 +                    assertEquals(a, b);
 +                }
 +            }
 +
 +            // Generation mismatch should throw an exception.
 +            if (generation >= 0) {
 +                encoded[prefix] = BOGUS_GENERATION;
 +                try {
 +                    methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
 +                    fail();
 +                } catch (InvocationTargetException e) {
 +                    Throwable cause = e.getCause();
 +                    if (cause instanceof CorruptEncodingException) {
 +                        CorruptEncodingException cee = (CorruptEncodingException) cause;
 +                        // Make sure error message includes actual generation.
 +                        assertTrue
 +                            (cee.getMessage().indexOf(String.valueOf(BOGUS_GENERATION)) >= 0);
 +                    } else {
 +                        throw e;
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    public void test_keyEncoding_noProperties() throws Exception {
 +        test_keyEncoding_noProperties(0, 0);
 +    }
 +
 +    public void test_keyEncoding_noProperties_prefix() throws Exception {
 +        test_keyEncoding_noProperties(5, 0);
 +    }
 +
 +    public void test_keyEncoding_noProperties_suffix() throws Exception {
 +        test_keyEncoding_noProperties(0, 7);
 +    }
 +
 +    public void test_keyEncoding_noProperties_prefixAndSuffix() throws Exception {
 +        test_keyEncoding_noProperties(5, 7);
 +    }
 +
 +    private void test_keyEncoding_noProperties(int prefix, int suffix) throws Exception {
 +        GenericEncodingStrategy strategy = new GenericEncodingStrategy
 +            (TestStorable.class, null, prefix, suffix, 0, 0);
 +
 +        Method[] methods = generateCodecMethods
 +            (strategy, new StorableProperty[0], new Direction[0], -1);
 +
 +        // Encode should return an empty array.
 +        byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +            .invoke(null, new Object[] {new Object[0]});
 +        assertEquals(encoded.length, prefix + suffix);
 +
 +        // Decode should not throw an exception.
 +        methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
 +    }
 +
 +    public void test_keyEncoding_multipleProperties() throws Exception {
 +        test_keyEncoding_multipleProperties(0, 0);
 +    }
 +
 +    public void test_keyEncoding_multipleProperties_prefix() throws Exception {
 +        test_keyEncoding_multipleProperties(5, 0);
 +    }
 +
 +    public void test_keyEncoding_multipleProperties_suffix() throws Exception {
 +        test_keyEncoding_multipleProperties(0, 7);
 +    }
 +
 +    public void test_keyEncoding_multipleProperties_prefixAndSuffix() throws Exception {
 +        test_keyEncoding_multipleProperties(5, 7);
 +    }
 +
 +    private void test_keyEncoding_multipleProperties(int prefix, int suffix) throws Exception {
 +        GenericEncodingStrategy strategy = new GenericEncodingStrategy
 +            (TestStorable.class, null, prefix, suffix, 0, 0);
 +
 +        for (int i=0; i<MEDIUM_TEST; i++) {
 +            StorableProperty<TestStorable>[] properties = selectProperties(1, 50);
 +            Direction[] directions = selectDirections(properties.length);
 +
 +            Method[] methods = generateCodecMethods(strategy, properties, directions, -1);
 +            Object[] values = selectPropertyValues(properties);
 +
 +            byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +                .invoke(null, new Object[] {values});
 +
 +            assertTrue(encoded.length > (prefix + suffix));
 +
 +            // Encode using partial encoding method, but do all
 +            // properties. Ensure that the encoding is exactly the same.
 +            byte[] encoded2 = (byte[]) methods[ENCODE_OBJECT_ARRAY_PARTIAL]
 +                .invoke(null, new Object[] {values, 0, properties.length});
 +            assertTrue(Arrays.equals(encoded, encoded2));
 +
 +            if (prefix > 0) {
 +                // Fill in with data which should be ignored by decoder.
 +                for (int p=0; p<prefix; p++) {
 +                    encoded[p] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            if (suffix > 0) {
 +                // Fill in with data which should be ignored by decoder.
 +                for (int p=0; p<suffix; p++) {
 +                    encoded[encoded.length - p - 1] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            Object[] decodedValues = new Object[values.length];
 +            methods[DECODE_OBJECT_ARRAY].invoke(null, decodedValues, encoded);
 +
 +            for (int j=0; j<properties.length; j++) {
 +                Object a = values[j];
 +                Object b = decodedValues[j];
 +                if (properties[j].getType() == byte[].class) {
 +                    assertTrue(0 == TestDataEncoding.byteArrayOrNullCompare
 +                               ((byte[]) a, (byte[]) b));
 +                } else {
 +                    assertEquals(a, b);
 +                }
 +            }
 +
 +            // Now test partial encoding of keys.
 +
 +            // Clear out random affixes, since we don't need specific values
 +            // anymore and it interferes with next test.
 +            if (prefix > 0) {
 +                for (int p=0; p<prefix; p++) {
 +                    encoded[p] = 0;
 +                }
 +            }
 +            if (suffix > 0) {
 +                for (int p=0; p<suffix; p++) {
 +                    encoded[encoded.length - p - 1] = 0;
 +                }
 +            }
 +
 +            for (int j=0; j<SHORT_TEST; j++) {
 +                int start, end;
 +                if (properties.length == 1) {
 +                    start = 0;
 +                    end = 1;
 +                } else {
 +                    start = mRandom.nextInt(properties.length - 1);
 +                    // Partial encoding doesn't support zero properties, so
 +                    // ensure randomly selected stride is more than zero.
 +                    int stride;
 +                    do {
 +                        stride = mRandom.nextInt(properties.length - start);
 +                    } while (stride == 0);
 +                    end = start + stride + 1;
 +                }
 +
 +                Object[] partialValues = new Object[end - start];
 +                System.arraycopy(values, start, partialValues, 0, partialValues.length);
 +                
 +                byte[] partial = (byte[]) methods[ENCODE_OBJECT_ARRAY_PARTIAL]
 +                    .invoke(null, new Object[] {partialValues, start, end});
 +                
 +                // Partial key must be substring of full key.
 +                int searchStart = start == 0 ? 0 : prefix;
 +                int index = indexOf(encoded, searchStart, encoded.length - searchStart,
 +                                    partial, 0, partial.length, 0);
 +
 +                if (start == 0) {
 +                    assertEquals(0, index);
 +                } else {
 +                    assertTrue(index > 0);
 +                }
 +
 +                if (properties.length == 1) {
 +                    break;
 +                }
 +            }
 +        }
 +    }
 +
 +    public void test_keyEncoding_ordering() throws Exception {
 +        GenericEncodingStrategy strategy =
 +            new GenericEncodingStrategy(TestStorable.class, null, 0, 0, 0, 0);
 +
 +        for (int i=0; i<MEDIUM_TEST; i++) {
 +            StorableProperty<TestStorable>[] properties = selectProperties(1, 50);
 +            Direction[] directions = selectDirections(properties.length);
 +            Method[] methods = generateCodecMethods(strategy, properties, directions, -1);
 +
 +            Object[] values_1 = selectPropertyValues(properties);
 +            byte[] encoded_1 = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +                .invoke(null, new Object[] {values_1});
 +
 +            Object[] values_2 = selectPropertyValues(properties);
 +            byte[] encoded_2 = (byte[]) methods[ENCODE_OBJECT_ARRAY]
 +                .invoke(null, new Object[] {values_2});
 +
 +            int byteOrder = TestDataEncoding.byteArrayCompare(encoded_1, encoded_2);
 +            int valueOrder = compareValues(properties, directions, values_1, values_2);
 +
 +            assertEquals(valueOrder, byteOrder);
 +        }
 +    }
 +
 +    private int compareValues(StorableProperty<?>[] properties, Direction[] directions,
 +                              Object[] values_1, Object[] values_2) {
 +        int length = directions.length;
 +        for (int i=0; i<length; i++) {
 +            StorableProperty<?> property = properties[i];
 +            Direction direction = directions[i];
 +
 +            Object value_1 = values_1[i];
 +            Object value_2 = values_2[i];
 +
 +            int result;
 +            if (property.getType() == byte[].class) {
 +                result = TestDataEncoding.byteArrayOrNullCompare
 +                    ((byte[]) value_1, (byte[]) value_2);
 +            } else {
 +                if (value_1 == null) {
 +                    if (value_2 == null) {
 +                        result = 0;
 +                    } else {
 +                        result = 1;
 +                    }
 +                } else if (value_2 == null) {
 +                    result = -1;
 +                } else {
 +                    result = Integer.signum(((Comparable) value_1).compareTo(value_2));
 +                }
 +            }
 +
 +            if (result != 0) {
 +                if (direction == Direction.DESCENDING) {
 +                    result = -result;
 +                }
 +                return result;
 +            }
 +        }
 +
 +        return 0;
 +    }
 +
 +    // Method taken from String class and modified a bit.
 +    private static int indexOf(byte[] source, int sourceOffset, int sourceCount,
 +                               byte[] target, int targetOffset, int targetCount,
 +                               int fromIndex) {
 +        if (fromIndex >= sourceCount) {
 +            return (targetCount == 0 ? sourceCount : -1);
 +        }
 +        if (fromIndex < 0) {
 +            fromIndex = 0;
 +        }
 +        if (targetCount == 0) {
 +            return fromIndex;
 +        }
 +
 +        byte first  = target[targetOffset];
 +        int max = sourceOffset + (sourceCount - targetCount);
 +        
 +        for (int i = sourceOffset + fromIndex; i <= max; i++) {
 +            // Look for first byte.
 +            if (source[i] != first) {
 +                while (++i <= max && source[i] != first);
 +            }
 +            
 +            // Found first byte, now look at the rest of v2
 +            if (i <= max) {
 +                int j = i + 1;
 +                int end = j + targetCount - 1;
 +                for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++);
 +                
 +                if (j == end) {
 +                    // Found whole string.
 +                    return i - sourceOffset;
 +                }
 +            }
 +        }
 +        return -1;
 +    }
 +
 +    /**
 +     * First method is the encoder, second is the decoder. Both methods are
 +     * static. Encoder accepts an object array of property values, and it
 +     * returns a byte array. Decoder accepts an object array to receive
 +     * property values, an encoded byte array, and it returns void.
 +     *
 +     * <p>If generating key encoding and the property count is more than zero,
 +     * then third element in array is a key encoder that supports partial
 +     * encoding. In addition to the object array argument, it also accepts an
 +     * int start and an int end argument. The start of the range is inclusive,
 +     * and the end is exclusive.
 +     *
 +     * @param directions when supplied, build key encoding/decoding. Otherwise,
 +     * build data encoding/decoding.
 +     * @param generation when non-negative, encode a storable layout generation
 +     * value in one or four bytes.
 +     */
 +    private Method[] generateCodecMethods(GenericEncodingStrategy strategy,
 +                                          StorableProperty<TestStorable>[] properties,
 +                                          Direction[] directions,
 +                                          int generation)
 +        throws Exception
 +    {
 +        ClassInjector ci = ClassInjector.create(TestStorable.class.getName(), null);
 +        ClassFile cf = new ClassFile(ci.getClassName());
 +        cf.markSynthetic();
 +        cf.setTarget("1.5");
 +
 +        cf.addDefaultConstructor();
 +
 +        TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
 +        TypeDesc objectArrayType = TypeDesc.forClass(Object[].class);
 +
 +        // Build encode method.
 +        MethodInfo mi = cf.addMethod(Modifiers.PUBLIC_STATIC, "encode", byteArrayType,
 +                                     new TypeDesc[] {objectArrayType});
 +        CodeBuilder b = new CodeBuilder(mi);
 +        LocalVariable encodedVar;
 +        if (directions != null) {
 +            OrderedProperty<TestStorable>[] ordered =
 +                makeOrderedProperties(properties, directions);
 +            encodedVar =
 +                strategy.buildKeyEncoding(b, ordered, b.getParameter(0), null, false, null, null);
 +        } else {
 +            encodedVar = strategy.buildDataEncoding
 +                (b, properties, b.getParameter(0), null, false, generation);
 +        }
 +        b.loadLocal(encodedVar);
 +        b.returnValue(byteArrayType);
 +
 +        // Build decode method.
 +        mi = cf.addMethod(Modifiers.PUBLIC_STATIC, "decode", null,
 +                          new TypeDesc[] {objectArrayType, byteArrayType});
 +        b = new CodeBuilder(mi);
 +        if (directions != null) {
 +            OrderedProperty<TestStorable>[] ordered =
 +                makeOrderedProperties(properties, directions);
 +            strategy.buildKeyDecoding
 +                (b, ordered, b.getParameter(0), null, false, b.getParameter(1));
 +        } else {
 +            strategy.buildDataDecoding
 +                (b, properties, b.getParameter(0), null, false,
 +                 generation, null, b.getParameter(1));
 +        }
 +        b.returnVoid();
 +
 +        if (directions != null && properties.length > 0) {
 +            // Build encode partial key method.
 +            mi = cf.addMethod
 +                (Modifiers.PUBLIC_STATIC, "encodePartial", byteArrayType,
 +                 new TypeDesc[] {objectArrayType, TypeDesc.INT, TypeDesc.INT});
 +            b = new CodeBuilder(mi);
 +            OrderedProperty<TestStorable>[] ordered =
 +                makeOrderedProperties(properties, directions);
 +            encodedVar =
 +                strategy.buildKeyEncoding(b, ordered, b.getParameter(0), null, false,
 +                                          b.getParameter(1), b.getParameter(2));
 +            b.loadLocal(encodedVar);
 +            b.returnValue(byteArrayType);
 +        }
 +
 +        Class<?> clazz = ci.defineClass(cf);
 +
 +        Method encode = clazz.getMethod("encode", Object[].class);
 +        Method decode = clazz.getMethod("decode", Object[].class, byte[].class);
 +        Method encodePartial = null;
 +        if (directions != null && properties.length > 0) {
 +            encodePartial = clazz.getMethod("encodePartial", Object[].class, int.class, int.class);
 +        }
 +
 +        Method[] methods = new Method[3];
 +        methods[ENCODE_OBJECT_ARRAY] = encode;
 +        methods[DECODE_OBJECT_ARRAY] = decode;
 +        methods[ENCODE_OBJECT_ARRAY_PARTIAL] = encodePartial;
 +
 +        return methods;
 +    }
 +
 +    private StorableProperty<TestStorable>[] selectProperties(int minCount, int maxCount) {
 +        int length = (minCount == maxCount) ? minCount
 +            : (mRandom.nextInt(maxCount - minCount + 1) + minCount);
 +
 +        StorableProperty<TestStorable>[] selection = new StorableProperty[length];
 +
 +        for (int i=length; --i>=0; ) {
 +            selection[i] = mProperties[mRandom.nextInt(mProperties.length)];
 +        }
 +
 +        return selection;
 +    }
 +
 +    private Direction[] selectDirections(int count) {
 +        Direction[] directions = new Direction[count];
 +
 +        for (int i=count; --i>=0; ) {
 +            Direction dir;
 +            switch (mRandom.nextInt(3)) {
 +            default:
 +                dir = Direction.UNSPECIFIED;
 +                break;
 +            case 1:
 +                dir = Direction.ASCENDING;
 +                break;
 +            case 2:
 +                dir = Direction.DESCENDING;
 +                break;
 +            }
 +            directions[i] = dir;
 +        }
 +
 +        return directions;
 +    }
 +
 +    private OrderedProperty<TestStorable>[] makeOrderedProperties
 +        (StorableProperty<TestStorable>[] properties, Direction[] directions) {
 +
 +        int length = properties.length;
 +        OrderedProperty<TestStorable>[] ordered = new OrderedProperty[length];
 +        for (int i=length; --i>=0; ) {
 +            ordered[i] = OrderedProperty.get(properties[i], directions[i]);
 +        }
 +
 +        return ordered;
 +    }
 +
 +    /**
 +     * Returns an array of the same size with randomly selected values filled
 +     * in that match the property type.
 +     */
 +    private Object[] selectPropertyValues(StorableProperty<?>[] properties) {
 +        int length = properties.length;
 +        Object[] values = new Object[length];
 +
 +        for (int i=length; --i>=0; ) {
 +            StorableProperty<?> property = properties[i];
 +            TypeDesc type = TypeDesc.forClass(property.getType());
 +
 +            Object value;
 +
 +            if (property.isNullable() && mRandom.nextInt(100) == 0) {
 +                value = null;
 +            } else {
 +                TypeDesc prim = type.toPrimitiveType();
 +                if (prim != null) {
 +                    switch (prim.getTypeCode()) {
 +                    case TypeDesc.BOOLEAN_CODE: default:
 +                        value = mRandom.nextBoolean();
 +                        break;
 +                    case TypeDesc.CHAR_CODE:
 +                        value = (char) mRandom.nextInt();
 +                        break;
 +                    case TypeDesc.FLOAT_CODE:
 +                        value = Float.intBitsToFloat(mRandom.nextInt());
 +                        break;
 +                    case TypeDesc.DOUBLE_CODE:
 +                        value = Double.longBitsToDouble(mRandom.nextLong());
 +                        break;
 +                    case TypeDesc.BYTE_CODE:
 +                        value = (byte) mRandom.nextInt();
 +                        break;
 +                    case TypeDesc.SHORT_CODE:
 +                        value = (short) mRandom.nextInt();
 +                        break;
 +                    case TypeDesc.INT_CODE:
 +                        value = mRandom.nextInt();
 +                        break;
 +                    case TypeDesc.LONG_CODE:
 +                        value = mRandom.nextLong();
 +                        break;
 +                    }
 +                } else if (type == TypeDesc.STRING) {
 +                    int len = mRandom.nextInt(100);
 +                    StringBuilder sb = new StringBuilder(len);
 +                    for (int j=0; j<len; j++) {
 +                        sb.append((char) mRandom.nextInt());
 +                    }
 +                    value = sb.toString();
 +                } else {
 +                    int len = mRandom.nextInt(100);
 +                    byte[] bytes = new byte[len];
 +                    for (int j=0; j<len; j++) {
 +                        bytes[j] = (byte) mRandom.nextInt();
 +                    }
 +                    value = bytes;
 +                }
 +            }
 +
 +            values[i] = value;
 +        }
 +
 +        return values;
 +    }
 +
 +    /**
 +     * Just a collection of storable properties. At least one property defined
 +     * per supported type.
 +     */
 +    @PrimaryKey("byte") // I don't really care what the primary key is.
 +    public static interface TestStorable extends Storable {
 +        byte getByte();
 +
 +        void setByte(byte b);
 +
 +        Byte getByteObj();
 +
 +        void setByteObj(Byte b);
 +
 +        @Nullable
 +        Byte getNullableByteObj();
 +
 +        void setNullableByteObj(Byte b);
 +
 +        short getShort();
 +
 +        void setShort(short s);
 +
 +        Short getShortObj();
 +
 +        void setShortObj(Short s);
 +
 +        @Nullable
 +        Short getNullableShortObj();
 +
 +        void setNullableShortObj(Short s);
 +
 +        char getChar();
 +
 +        void setChar(char c);
 +
 +        Character getCharacterObj();
 +
 +        void setCharacterObj(Character c);
 +
 +        @Nullable
 +        Character getNullableCharacterObj();
 +
 +        void setNullableCharacterObj(Character c);
 +
 +        int getInt();
 +
 +        void setInt(int i);
 +
 +        Integer getIntegerObj();
 +
 +        void setIntegerObj(Integer obj);
 +
 +        @Nullable
 +        Integer getNullableIntegerObj();
 +
 +        void setNullableIntegerObj(Integer obj);
 +
 +        long getLong();
 +
 +        void setLong(long i);
 +
 +        Long getLongObj();
 +
 +        void setLongObj(Long i);
 +
 +        @Nullable
 +        Long getNullableLongObj();
 +
 +        void setNullableLongObj(Long i);
 +
 +        float getFloat();
 +
 +        void setFloat(float f);
 +
 +        Float getFloatObj();
 +
 +        void setFloatObj(Float f);
 +
 +        @Nullable
 +        Float getNullableFloatObj();
 +
 +        void setNullableFloatObj(Float f);
 +
 +        double getDouble();
 +
 +        void setDouble(double d);
 +
 +        Double getDoubleObj();
 +
 +        void setDoubleObj(Double d);
 +
 +        @Nullable
 +        Double getNullableDoubleObj();
 +
 +        void setNullableDoubleObj(Double d);
 +
 +        byte[] getByteArray();
 +
 +        void setByteArray(byte[] b);
 +
 +        @Nullable
 +        byte[] getNullableByteArray();
 +
 +        void setNullableByteArray(byte[] b);
 +
 +        String getString();
 +
 +        void setString(String s);
 +
 +        @Nullable
 +        String getNullableString();
 +
 +        void setNullableString(String s);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/raw/TestKeyEncoding.java b/src/test/java/com/amazon/carbonado/raw/TestKeyEncoding.java new file mode 100644 index 0000000..3dba00e --- /dev/null +++ b/src/test/java/com/amazon/carbonado/raw/TestKeyEncoding.java @@ -0,0 +1,571 @@ +/*
 + * 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.raw;
 +
 +import java.util.Random;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +/**
 + * Test case for {@link KeyEncoder} and {@link KeyDecoder}. 
 + * <p>
 + * It generates random data values, checks that the decoding produces the
 + * original results, and it checks that the order of the encoded bytes matches
 + * the order of the original data values.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestKeyEncoding extends TestCase {
 +    private static final int SHORT_TEST = 100;
 +    private static final int MEDIUM_TEST = 500;
 +    private static final int LONG_TEST = 1000;
 +
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestKeyEncoding.class);
 +    }
 +
 +    private final long mSeed;
 +
 +    private Random mRandom;
 +
 +    public TestKeyEncoding(String name) {
 +        super(name);
 +        mSeed = 5399777425345431L;
 +    }
 +
 +    protected void setUp() {
 +        mRandom = new Random(mSeed);
 +    }
 +
 +    protected void tearDown() {
 +    }
 +
 +    public void test_booleanDesc() throws Exception {
 +        byte[] bytes = new byte[1];
 +        boolean lastValue = false;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            boolean value = mRandom.nextBoolean();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeBooleanDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_BooleanDesc() throws Exception {
 +        byte[] bytes = new byte[1];
 +        Boolean lastValue = false;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Boolean value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = mRandom.nextBoolean();
 +            }
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeBooleanObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_byteDesc() throws Exception {
 +        byte[] bytes = new byte[1];
 +        byte lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            byte value = (byte) mRandom.nextInt();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeByteDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_ByteDesc() throws Exception {
 +        byte[] bytes = new byte[2];
 +        Byte lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Byte value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = (byte) mRandom.nextInt();
 +                assertEquals(2, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeByteObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_shortDesc() throws Exception {
 +        byte[] bytes = new byte[2];
 +        short lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            short value = (short) mRandom.nextInt();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeShortDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_ShortDesc() throws Exception {
 +        byte[] bytes = new byte[3];
 +        Short lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Short value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = (short) mRandom.nextInt();
 +                assertEquals(3, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeShortObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_charDesc() throws Exception {
 +        byte[] bytes = new byte[2];
 +        char lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            char value = (char) mRandom.nextInt();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeCharDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_CharacterDesc() throws Exception {
 +        byte[] bytes = new byte[3];
 +        Character lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Character value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = (char) mRandom.nextInt();
 +                assertEquals(3, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeCharacterObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_intDesc() throws Exception {
 +        byte[] bytes = new byte[4];
 +        int lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            int value = mRandom.nextInt();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeIntDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_IntegerDesc() throws Exception {
 +        byte[] bytes = new byte[5];
 +        Integer lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Integer value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = mRandom.nextInt();
 +                assertEquals(5, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeIntegerObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_longDesc() throws Exception {
 +        byte[] bytes = new byte[8];
 +        long lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            long value = mRandom.nextLong();
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeLongDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_LongDesc() throws Exception {
 +        byte[] bytes = new byte[9];
 +        Long lastValue = 0L;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            Long value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +                assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
 +            } else {
 +                value = mRandom.nextLong();
 +                assertEquals(9, KeyEncoder.encodeDesc(value, bytes, 0));
 +            }
 +            assertEquals(value, KeyDecoder.decodeLongObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_floatDesc() throws Exception {
 +        byte[] bytes = new byte[4];
 +        float lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            float value = Float.intBitsToFloat(mRandom.nextInt());
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeFloatDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_FloatDesc() throws Exception {
 +        byte[] bytes = new byte[4];
 +        Float lastValue = 0f;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            Float value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = Float.intBitsToFloat(mRandom.nextInt());
 +            }
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeFloatObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_doubleDesc() throws Exception {
 +        byte[] bytes = new byte[8];
 +        double lastValue = 0;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            double value = Double.longBitsToDouble(mRandom.nextLong());
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeDoubleDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_DoubleDesc() throws Exception {
 +        byte[] bytes = new byte[8];
 +        Double lastValue = 0d;
 +        byte[] lastBytes = null;
 +        for (int i=0; i<LONG_TEST; i++) {
 +            Double value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                value = Double.longBitsToDouble(mRandom.nextLong());
 +            }
 +            KeyEncoder.encodeDesc(value, bytes, 0);
 +            assertEquals(value, KeyDecoder.decodeDoubleObjDesc(bytes, 0));
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_String() throws Exception {
 +        String lastValue = null;
 +        byte[] lastBytes = null;
 +        String[] ref = new String[1];
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            String value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length;
 +                switch (mRandom.nextInt(15)) {
 +                default:
 +                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
 +                    length = mRandom.nextInt(100);
 +                    break;
 +                case 8: case 9: case 10: case 11:
 +                    length = mRandom.nextInt(200);
 +                    break;
 +                case 12: case 13:
 +                    length = mRandom.nextInt(20000);
 +                    break;
 +                case 14:
 +                    length = mRandom.nextInt(3000000);
 +                    break;
 +                }
 +                char[] chars = new char[length];
 +                for (int j=0; j<length; j++) {
 +                    char c;
 +                    switch (mRandom.nextInt(7)) {
 +                    default:
 +                    case 0: case 1: case 2: case 3:
 +                        c = (char) mRandom.nextInt(128);
 +                        break;
 +                    case 4: case 5:
 +                        c = (char) mRandom.nextInt(4000);
 +                        break;
 +                    case 6:
 +                        c = (char) mRandom.nextInt();
 +                        break;
 +                    }
 +                    chars[j] = c;
 +                }
 +                value = new String(chars);
 +            }
 +
 +            byte[] bytes = new byte[KeyEncoder.calculateEncodedStringLength(value)];
 +            assertEquals(bytes.length, KeyEncoder.encode(value, bytes, 0));
 +            assertEquals(bytes.length, KeyDecoder.decodeString(bytes, 0, ref));
 +            assertEquals(value, ref[0]);
 +
 +            if (lastBytes != null) {
 +                int sgn = TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_StringDesc() throws Exception {
 +        String lastValue = null;
 +        byte[] lastBytes = null;
 +        String[] ref = new String[1];
 +        for (int i=0; i<SHORT_TEST; i++) {
 +            String value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length;
 +                switch (mRandom.nextInt(15)) {
 +                default:
 +                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
 +                    length = mRandom.nextInt(100);
 +                    break;
 +                case 8: case 9: case 10: case 11:
 +                    length = mRandom.nextInt(200);
 +                    break;
 +                case 12: case 13:
 +                    length = mRandom.nextInt(20000);
 +                    break;
 +                case 14:
 +                    length = mRandom.nextInt(3000000);
 +                    break;
 +                }
 +                char[] chars = new char[length];
 +                for (int j=0; j<length; j++) {
 +                    char c;
 +                    switch (mRandom.nextInt(7)) {
 +                    default:
 +                    case 0: case 1: case 2: case 3:
 +                        c = (char) mRandom.nextInt(128);
 +                        break;
 +                    case 4: case 5:
 +                        c = (char) mRandom.nextInt(4000);
 +                        break;
 +                    case 6:
 +                        c = (char) mRandom.nextInt();
 +                        break;
 +                    }
 +                    chars[j] = c;
 +                }
 +                value = new String(chars);
 +            }
 +
 +            byte[] bytes = new byte[KeyEncoder.calculateEncodedStringLength(value)];
 +            assertEquals(bytes.length, KeyEncoder.encodeDesc(value, bytes, 0));
 +            assertEquals(bytes.length, KeyDecoder.decodeStringDesc(bytes, 0, ref));
 +            assertEquals(value, ref[0]);
 +
 +
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.compare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_byteArray() throws Exception {
 +        byte[] lastValue = null;
 +        byte[] lastBytes = null;
 +        byte[][] ref = new byte[1][];
 +        for (int i=0; i<LONG_TEST; i++) {
 +            byte[] value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length = mRandom.nextInt(4000);
 +                value = new byte[length];
 +                for (int j=0; j<length; j++) {
 +                    value[j] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            byte[] bytes = new byte[KeyEncoder.calculateEncodedLength(value)];
 +            assertEquals(bytes.length, KeyEncoder.encode(value, bytes, 0));
 +            assertEquals(bytes.length, KeyDecoder.decode(bytes, 0, ref));
 +            if (ref[0] == null) {
 +                assertEquals(value, null);
 +            } else if (value == null) {
 +                assertEquals(value, ref[0]);
 +            } else {
 +                assertEquals(0, TestDataEncoding.byteArrayCompare(value, ref[0], value.length));
 +            }
 +
 +            if (lastBytes != null) {
 +                int sgn = TestDataEncoding.byteArrayOrNullCompare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +
 +    public void test_byteArrayDesc() throws Exception {
 +        byte[] lastValue = null;
 +        byte[] lastBytes = null;
 +        byte[][] ref = new byte[1][];
 +        for (int i=0; i<LONG_TEST; i++) {
 +            byte[] value;
 +            if (mRandom.nextInt(10) == 1) {
 +                value = null;
 +            } else {
 +                int length = mRandom.nextInt(4000);
 +                value = new byte[length];
 +                for (int j=0; j<length; j++) {
 +                    value[j] = (byte) mRandom.nextInt();
 +                }
 +            }
 +
 +            byte[] bytes = new byte[KeyEncoder.calculateEncodedLength(value)];
 +            assertEquals(bytes.length, KeyEncoder.encodeDesc(value, bytes, 0));
 +            assertEquals(bytes.length, KeyDecoder.decodeDesc(bytes, 0, ref));
 +            if (ref[0] == null) {
 +                assertEquals(value, null);
 +            } else if (value == null) {
 +                assertEquals(value, ref[0]);
 +            } else {
 +                assertEquals(0, TestDataEncoding.byteArrayCompare(value, ref[0], value.length));
 +            }
 +
 +            if (lastBytes != null) {
 +                int sgn = -TestDataEncoding.byteArrayOrNullCompare(value, lastValue);
 +                assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
 +            }
 +            lastValue = value;
 +            lastBytes = bytes.clone();
 +        }
 +    }
 +}
 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<Class, Storage> mStorages;
 +    private final Map<String, SequenceValueProducer> mSequences;
 +
 +    public ToyRepository() {
 +        this("toy");
 +    }
 +
 +    public ToyRepository(String name) {
 +        mName = name;
 +        mStorages = new HashMap<Class, Storage>();
 +        mSequences = new HashMap<String, SequenceValueProducer>();
 +    }
 +
 +    public String getName() {
 +        return mName;
 +    }
 +
 +    public <S extends Storable> Storage<S> storageFor(Class<S> type)
 +        throws SupportException, RepositoryException
 +    {
 +        synchronized (mStorages) {
 +            Storage<S> storage = (Storage<S>) mStorages.get(type);
 +            if (storage == null) {
 +                storage = new ToyStorage<S>(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 extends Capability> C getCapability(Class<C> 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<S extends Storable> {
 +    private static final Map<Class, Class> cCache;
 +
 +    static {
 +        cCache = new SoftValuedHashMap();
 +    }
 +
 +    /**
 +     * Generated class has a constructor that accepts a ToyStorage instance.
 +     */
 +    public static <S extends Storable> Class<? extends S> getGeneratedClass(Class<S> type)
 +        throws SupportException
 +    {
 +        synchronized (cCache) {
 +            Class<? extends S> generatedClass = (Class<? extends S>) cCache.get(type);
 +            if (generatedClass != null) {
 +                return generatedClass;
 +            }
 +            generatedClass = new ToyStorableGenerator<S>(type).generateAndInjectClass();
 +            cCache.put(type, generatedClass);
 +            return generatedClass;
 +        }
 +    }
 +
 +    private final Class<S> mStorableType;
 +
 +    private final ClassInjector mClassInjector;
 +    private final ClassFile mClassFile;
 +
 +    private ToyStorableGenerator(Class<S> type) throws SupportException {
 +        mStorableType = type;
 +
 +        EnumSet<MasterFeature> features = EnumSet
 +            .of(MasterFeature.VERSIONING, MasterFeature.INSERT_SEQUENCES);
 +
 +        final Class<? extends S> 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<? extends S> 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<? extends S> 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..71a597d --- /dev/null +++ b/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java @@ -0,0 +1,265 @@ +/*
 + * 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.QueryExecutorFactory;
 +import com.amazon.carbonado.qe.QueryFactory;
 +import com.amazon.carbonado.qe.SortedQueryExecutor;
 +import com.amazon.carbonado.qe.FilteredQueryExecutor;
 +import com.amazon.carbonado.qe.IterableQueryExecutor;
 +import com.amazon.carbonado.qe.OrderingList;
 +import com.amazon.carbonado.qe.QueryExecutor;
 +import com.amazon.carbonado.qe.StandardQuery;
 +
 +/**
 + *
 + * @author Brian S O'Neill
 + */
 +public class ToyStorage<S extends Storable>
 +    implements Storage<S>, MasterSupport<S>, QueryFactory<S>, QueryExecutorFactory<S>
 +{
 +    final ToyRepository mRepo;
 +    final Class<S> mType;
 +
 +    final InstanceFactory mInstanceFactory;
 +
 +    final Collection<S> mData;
 +    final Lock mDataLock;
 +
 +    public ToyStorage(ToyRepository repo, Class<S> type) throws SupportException {
 +        StorableIntrospector.examine(type);
 +        mRepo = repo;
 +        mType = type;
 +
 +        Class<? extends S> generatedStorableClass = ToyStorableGenerator.getGeneratedClass(type);
 +        mInstanceFactory = QuickConstructorGenerator
 +            .getInstance(generatedStorableClass, InstanceFactory.class);
 +
 +        mData = new LinkedList<S>();
 +        mDataLock = new ReentrantLock();
 +    }
 +
 +    public Class<S> getStorableType() {
 +        return mType;
 +    }
 +
 +    public S prepare() {
 +        return (S) mInstanceFactory.instantiate(this);
 +    }
 +
 +    public Query<S> query() {
 +        return new ToyQuery(null, null, null);
 +    }
 +
 +    public Query<S> query(String filter) {
 +        return query(Filter.filterFor(mType, filter));
 +    }
 +
 +    public Query<S> query(Filter<S> filter) {
 +        return new ToyQuery(filter.initialFilterValues(), null, null);
 +    }
 +
 +    public Query<S> query(FilterValues<S> values, OrderingList<S> ordering) {
 +        return new ToyQuery(values, ordering, null);
 +    }
 +
 +    public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering) {
 +        QueryExecutor<S> executor = new IterableQueryExecutor<S>(mType, mData, mDataLock);
 +
 +        if (filter != null) {
 +            executor = new FilteredQueryExecutor<S>(executor, filter);
 +        }
 +
 +        if (ordering.size() > 0) {
 +            executor = new SortedQueryExecutor<S>(null, executor, null, ordering);
 +        }
 +
 +        return executor;
 +    }
 +
 +    public boolean addTrigger(Trigger<? super S> trigger) {
 +        return false;
 +    }
 +
 +    public boolean removeTrigger(Trigger<? super S> 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)) {
 +                    // Copy altered values to existing object.
 +                    existing.markAllPropertiesDirty();
 +                    storable.copyAllProperties(existing);
 +                    existing.markAllPropertiesClean();
 +
 +                    // Copy all values to user object, to simulate a reload.
 +                    storable.markAllPropertiesDirty();
 +                    existing.copyAllProperties(storable);
 +                    storable.markAllPropertiesClean();
 +
 +                    return true;
 +                }
 +            }
 +            return false;
 +        } finally {
 +            mDataLock.unlock();
 +        }
 +    }
 +
 +    public boolean doTryDelete(S storable) {
 +        mDataLock.lock();
 +        try {
 +            Iterator<S> 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<? super S> getInsertTrigger() {
 +        return null;
 +    }
 +
 +    public Trigger<? super S> getUpdateTrigger() {
 +        return null;
 +    }
 +
 +    public Trigger<? super S> 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<S> {
 +        ToyQuery(FilterValues<S> values, OrderingList<S> ordering, QueryExecutor<S> executor) {
 +            super(values, ordering, executor);
 +        }
 +
 +        protected Transaction enterTransaction(IsolationLevel level) {
 +            return mRepo.enterTransaction(level);
 +        }
 +
 +        protected QueryFactory<S> queryFactory() {
 +            return ToyStorage.this;
 +        }
 +
 +        protected QueryExecutorFactory<S> executorFactory() {
 +            return ToyStorage.this;
 +        }
 +
 +        protected StandardQuery<S> newInstance(FilterValues<S> values,
 +                                               OrderingList<S> ordering,
 +                                               QueryExecutor<S> executor)
 +        {
 +            return new ToyQuery(values, ordering, executor);
 +        }
 +    }
 +}
 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/sample/FileInfoTest.java b/src/test/java/com/amazon/carbonado/sample/FileInfoTest.java new file mode 100644 index 0000000..0f38de7 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/sample/FileInfoTest.java @@ -0,0 +1,92 @@ +/*
 + * 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.sample;
 +
 +import java.io.File;
 +import java.io.InputStream;
 +
 +import org.joda.time.DateTime;
 +
 +import com.amazon.carbonado.Cursor;
 +import com.amazon.carbonado.Query;
 +import com.amazon.carbonado.Repository;
 +import com.amazon.carbonado.Storage;
 +
 +import com.amazon.carbonado.lob.FileBlob;
 +
 +import com.amazon.carbonado.repo.sleepycat.BDBRepositoryBuilder;
 +
 +import com.amazon.carbonado.stored.FileInfo;
 +
 +/**
 + * Test program which implements storage for a simple indexed file system.
 + *
 + * @author Brian S O'Neill
 + */
 +public class FileInfoTest {
 +    public static Storage<FileInfo> openStorage(String envHome) throws Exception {
 +        BDBRepositoryBuilder builder = new BDBRepositoryBuilder();
 +        builder.setName("Simple file system");
 +        builder.setEnvironmentHome(envHome);
 +        builder.setTransactionNoSync(true);
 +        //builder.setProduct("DB");
 +
 +        Repository repository = builder.build();
 +        return repository.storageFor(FileInfo.class);
 +    }
 +
 +    /**
 +     * @param args [0] - BDB environment home, [1] - optional directory to read
 +     * files from for populating the simple file system
 +     */
 +    public static void main(String[] args) throws Exception {
 +        Storage<FileInfo> storage = openStorage(args[0]);
 +        if (args.length > 1) {
 +            populate(storage, new File(args[1]), null);
 +        }
 +    }
 +
 +    private static void populate(Storage<FileInfo> storage, File file, FileInfo parent)
 +        throws Exception
 +    {
 +        FileInfo info = storage.prepare();
 +        if (parent != null) {
 +            info.setParentID(parent.getID());
 +        }
 +        info.setName(file.getName());
 +        info.setLastModified(new DateTime(file.lastModified()));
 +
 +        if (file.isFile()) {
 +            info.setDirectory(false);
 +            info.setLength(file.length());
 +            info.setFileData(new FileBlob(file));
 +            info.insert();
 +        } else if (file.isDirectory()) {
 +            info.setDirectory(true);
 +            info.setLength(-1);
 +            info.insert();
 +            File[] children = file.listFiles();
 +            if (children != null) {
 +                for (int i=0; i<children.length; i++) {
 +                    populate(storage, children[i], info);
 +                }
 +            }
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/spi/TestConversionComparator.java b/src/test/java/com/amazon/carbonado/spi/TestConversionComparator.java new file mode 100644 index 0000000..5cd7caa --- /dev/null +++ b/src/test/java/com/amazon/carbonado/spi/TestConversionComparator.java @@ -0,0 +1,179 @@ +/*
 + * 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.spi;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.spi.ConversionComparator;
 +
 +/**
 + * Test cases for {@link ConversionComparator}.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestConversionComparator extends TestCase {
 +    private static final int BOOLEAN_CODE = 0;
 +    private static final int BYTE_CODE = 1;
 +    private static final int SHORT_CODE = 2;
 +    private static final int CHAR_CODE = 3;
 +    private static final int INT_CODE = 4;
 +    private static final int FLOAT_CODE = 5;
 +    private static final int LONG_CODE = 6;
 +    private static final int DOUBLE_CODE = 7;
 +
 +    private static final Class[] PRIMITIVE_CLASSES = {
 +        boolean.class, byte.class, short.class, char.class,
 +        int.class, float.class, long.class, double.class
 +    };
 +
 +    private static final Class[] BOXED_PRIMITIVE_CLASSES = {
 +        Boolean.class, Byte.class, Short.class, Character.class,
 +        Integer.class, Float.class, Long.class, Double.class
 +    };
 +
 +    // States which primitive conversions are allowed.
 +    private static final boolean[][] PRIMITIVE_MATRIX = {
 +        // from...
 +        // boolean  byte   short  char   int    float  long   double
 +        {  true,    false, false, false, false, false, false, false }, // to boolean
 +        {  false,   true,  false, false, false, false, false, false }, // to byte
 +        {  false,   true,  true,  false, false, false, false, false }, // to short
 +        {  false,   false, false, true,  false, false, false, false }, // to char
 +        {  false,   true,  true,  false, true,  false, false, false }, // to int
 +        {  false,   true,  true,  false, false, true,  false, false }, // to float
 +        {  false,   true,  true,  false, true,  false, true,  false }, // to long
 +        {  false,   true,  true,  false, true,  true,  false, true  }, // to double
 +    };
 +
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestConversionComparator.class);
 +    }
 +
 +    public TestConversionComparator(String name) {
 +        super(name);
 +    }
 +
 +    public void test_isConversionPossible_basics() {
 +        ConversionComparator cc = new ConversionComparator(Object.class);
 +        assertEquals(true, cc.isConversionPossible(Object.class));
 +        assertEquals(false, cc.isConversionPossible(String.class));
 +        assertEquals(false, cc.isConversionPossible(boolean.class));
 +        assertEquals(false, cc.isConversionPossible(Integer.class));
 +        assertEquals(false, cc.isConversionPossible(int.class));
 +
 +        cc = new ConversionComparator(String.class);
 +        assertEquals(true, cc.isConversionPossible(Object.class));
 +        assertEquals(true, cc.isConversionPossible(String.class));
 +        assertEquals(false, cc.isConversionPossible(boolean.class));
 +        assertEquals(false, cc.isConversionPossible(Integer.class));
 +        assertEquals(false, cc.isConversionPossible(int.class));
 +
 +        cc = new ConversionComparator(boolean.class);
 +        assertEquals(true, cc.isConversionPossible(Object.class));
 +        assertEquals(false, cc.isConversionPossible(String.class));
 +        assertEquals(true, cc.isConversionPossible(boolean.class));
 +        assertEquals(false, cc.isConversionPossible(Integer.class));
 +        assertEquals(false, cc.isConversionPossible(int.class));
 +
 +        cc = new ConversionComparator(Integer.class);
 +        assertEquals(true, cc.isConversionPossible(Object.class));
 +        assertEquals(false, cc.isConversionPossible(String.class));
 +        assertEquals(false, cc.isConversionPossible(boolean.class));
 +        assertEquals(true, cc.isConversionPossible(Integer.class));
 +        assertEquals(true, cc.isConversionPossible(int.class));
 +
 +        cc = new ConversionComparator(int.class);
 +        assertEquals(true, cc.isConversionPossible(Object.class));
 +        assertEquals(false, cc.isConversionPossible(String.class));
 +        assertEquals(false, cc.isConversionPossible(boolean.class));
 +        assertEquals(true, cc.isConversionPossible(Integer.class));
 +        assertEquals(true, cc.isConversionPossible(int.class));
 +    }
 +
 +    public void test_isConversionPossible_primitives() {
 +        test_isConversionPossible_primitives(false, false);
 +        test_isConversionPossible_primitives(false, true);
 +        test_isConversionPossible_primitives(true, false);
 +        test_isConversionPossible_primitives(true, true);
 +    }
 +
 +    private void test_isConversionPossible_primitives(boolean fromBoxed, boolean toBoxed) {
 +        for (int fromCode = BOOLEAN_CODE; fromCode <= DOUBLE_CODE; fromCode++) {
 +            ConversionComparator cc = new ConversionComparator
 +                (fromBoxed ? BOXED_PRIMITIVE_CLASSES[fromCode] : PRIMITIVE_CLASSES[fromCode]);
 +            for (int toCode = BOOLEAN_CODE; toCode <= DOUBLE_CODE; toCode++) {
 +                boolean expected = PRIMITIVE_MATRIX[toCode][fromCode];
 +                Class toType = toBoxed ? BOXED_PRIMITIVE_CLASSES[toCode] : PRIMITIVE_CLASSES[toCode];
 +                assertEquals(expected, cc.isConversionPossible(toType));
 +            }
 +        }
 +    }
 +
 +    public void test_compare() {
 +        ConversionComparator cc = new ConversionComparator(Object.class);
 +        assertEquals(true, cc.compare(Object.class, String.class) < 0);
 +        assertEquals(true, cc.compare(String.class, Object.class) > 0);
 +        assertEquals(0, cc.compare(Object.class, Object.class));
 +        assertEquals(0, cc.compare(String.class, String.class));
 +        assertEquals(0, cc.compare(String.class, String.class));
 +        assertEquals(0, cc.compare(int.class, Number.class));
 +
 +        cc = new ConversionComparator(String.class);
 +        assertEquals(true, cc.compare(String.class, Object.class) < 0);
 +        assertEquals(true, cc.compare(Object.class, String.class) > 0);
 +        assertEquals(0, cc.compare(String.class, String.class));
 +        assertEquals(true, cc.compare(int.class, String.class) > 0);
 +
 +        cc = new ConversionComparator(Integer.class);
 +        assertEquals(true, cc.compare(String.class, Object.class) > 0);
 +        assertEquals(true, cc.compare(Object.class, String.class) < 0);
 +        assertEquals(true, cc.compare(Object.class, Number.class) > 0);
 +        assertEquals(true, cc.compare(Integer.class, Number.class) < 0);
 +        assertEquals(true, cc.compare(int.class, Number.class) > 0);
 +        assertEquals(true, cc.compare(long.class, Number.class) > 0);
 +        assertEquals(true, cc.compare(long.class, Long.class) < 0);
 +
 +        cc = new ConversionComparator(int.class);
 +        assertEquals(true, cc.compare(String.class, Object.class) > 0);
 +        assertEquals(true, cc.compare(Object.class, String.class) < 0);
 +        assertEquals(true, cc.compare(Object.class, Number.class) > 0);
 +        assertEquals(true, cc.compare(Integer.class, Number.class) < 0);
 +        assertEquals(true, cc.compare(int.class, Number.class) < 0);
 +        assertEquals(true, cc.compare(long.class, Number.class) < 0);
 +        assertEquals(true, cc.compare(long.class, Long.class) < 0);
 +
 +        cc = new ConversionComparator(Byte.class);
 +        assertEquals(true, cc.compare(int.class, Number.class) > 0);
 +        assertEquals(true, cc.compare(long.class, Number.class) > 0);
 +        assertEquals(true, cc.compare(long.class, Integer.class) < 0);
 +
 +        cc = new ConversionComparator(byte.class);
 +        assertEquals(true, cc.compare(short.class, int.class) < 0);
 +        assertEquals(true, cc.compare(long.class, int.class) > 0);
 +
 +        cc = new ConversionComparator(java.util.Date.class);
 +        assertEquals(true, cc.compare(Object.class, Comparable.class) > 0);
 +        assertEquals(0, cc.compare(java.io.Serializable.class, Comparable.class));
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/spi/TestCursorList.java b/src/test/java/com/amazon/carbonado/spi/TestCursorList.java new file mode 100644 index 0000000..605e981 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/spi/TestCursorList.java @@ -0,0 +1,224 @@ +/*
 + * 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.spi;
 +
 +import com.amazon.carbonado.Cursor;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.cursor.EmptyCursorFactory;
 +
 +/**
 + * Test case for TransactionManager.CursorList.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestCursorList extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestCursorList.class);
 +    }
 +
 +    TransactionManager.CursorList mList;
 +
 +    public TestCursorList(String name) {
 +        super(name);
 +    }
 +
 +    protected void setUp() {
 +       mList = new TransactionManager.CursorList();
 +    }
 +
 +    public void testRegisterFew() {
 +        assertEquals(0, mList.size());
 +
 +        {
 +            Cursor cursor = EmptyCursorFactory.newEmptyCursor();
 +            mList.register(cursor, null);
 +            assertEquals(1, mList.size());
 +            assertEquals(cursor, mList.getCursor(0));
 +            assertEquals(null, mList.getValue(0));
 +            Object value = mList.unregister(cursor);
 +            assertEquals(0, mList.size());
 +            assertEquals(null, value);
 +        }
 +
 +        {
 +            Cursor cursor_1 = EmptyCursorFactory.newEmptyCursor();
 +            Cursor cursor_2 = EmptyCursorFactory.newEmptyCursor();
 +            mList.register(cursor_1, null);
 +            assertEquals(1, mList.size());
 +            mList.register(cursor_2, null);
 +            assertEquals(2, mList.size());
 +            assertEquals(cursor_1, mList.getCursor(0));
 +            assertEquals(cursor_2, mList.getCursor(1));
 +            assertEquals(null, mList.getValue(0));
 +            assertEquals(null, mList.getValue(1));
 +
 +            Object value = mList.unregister(cursor_2);
 +            assertEquals(1, mList.size());
 +            assertEquals(cursor_1, mList.getCursor(0));
 +            assertEquals(null, value);
 +            mList.unregister(cursor_2);
 +            assertEquals(1, mList.size());
 +            mList.unregister(cursor_1);
 +            assertEquals(0, mList.size());
 +        }
 +
 +        // unregister in reverse
 +        {
 +            Cursor cursor_1 = EmptyCursorFactory.newEmptyCursor();
 +            Cursor cursor_2 = EmptyCursorFactory.newEmptyCursor();
 +            mList.register(cursor_1, null);
 +            mList.register(cursor_2, null);
 +
 +            mList.unregister(cursor_1);
 +            assertEquals(1, mList.size());
 +            assertEquals(cursor_2, mList.getCursor(0));
 +
 +            mList.unregister(cursor_1);
 +            assertEquals(1, mList.size());
 +            mList.unregister(cursor_2);
 +            assertEquals(0, mList.size());
 +        }
 +    }
 +
 +    public void testRegisterFewValue() {
 +        Cursor cursor_1 = EmptyCursorFactory.newEmptyCursor();
 +        Cursor cursor_2 = EmptyCursorFactory.newEmptyCursor();
 +        String value_1 = "1";
 +        String value_2 = "2";
 +
 +        mList.register(cursor_1, value_1);
 +        assertEquals(1, mList.size());
 +        assertEquals(cursor_1, mList.getCursor(0));
 +        assertEquals(value_1, mList.getValue(0));
 +
 +        mList.register(cursor_2, value_2);
 +        assertEquals(2, mList.size());
 +        assertEquals(cursor_1, mList.getCursor(0));
 +        assertEquals(value_1, mList.getValue(0));
 +        assertEquals(cursor_2, mList.getCursor(1));
 +        assertEquals(value_2, mList.getValue(1));
 +
 +        Object value = mList.unregister(cursor_2);
 +        assertEquals(1, mList.size());
 +        assertEquals(cursor_1, mList.getCursor(0));
 +        assertEquals(value_1, mList.getValue(0));
 +        assertEquals(value_2, value);
 +
 +        value = mList.unregister(cursor_2);
 +        assertEquals(1, mList.size());
 +        assertEquals(null, value);
 +        value = mList.unregister(cursor_1);
 +        assertEquals(0, mList.size());
 +        assertEquals(value_1, value);
 +    }
 +
 +    // Tests that the array expands properly.
 +    public void testRegisterMany() {
 +        final int count = 50;
 +        Cursor[] cursors = new Cursor[count];
 +        for (int i=0; i<count; i++) {
 +            cursors[i] = EmptyCursorFactory.newEmptyCursor();
 +            mList.register(cursors[i], null);
 +            assertEquals(i + 1, mList.size());
 +        }
 +
 +        for (int i=0; i<count; i++) {
 +            assertEquals(cursors[i], mList.getCursor(i));
 +            assertEquals(null, mList.getValue(i));
 +        }
 +
 +        for (int i=0; i<count; i++) {
 +            mList.unregister(cursors[i]);
 +            assertEquals(count - i - 1, mList.size());
 +        }
 +    }
 +
 +    // Tests that the arrays expand properly and store values.
 +    public void testRegisterManyValues() {
 +        final int count = 50;
 +        Cursor[] cursors = new Cursor[count];
 +        Integer[] values = new Integer[count];
 +        for (int i=0; i<count; i++) {
 +            cursors[i] = EmptyCursorFactory.newEmptyCursor();
 +            values[i] = i;
 +            mList.register(cursors[i], values[i]);
 +            assertEquals(i + 1, mList.size());
 +        }
 +
 +        for (int i=0; i<count; i++) {
 +            assertEquals(cursors[i], mList.getCursor(i));
 +            assertEquals(values[i], mList.getValue(i));
 +        }
 +
 +        for (int i=0; i<count; i++) {
 +            Object value = mList.unregister(cursors[i]);
 +            assertEquals(count - i - 1, mList.size());
 +            assertEquals(values[i], value);
 +        }
 +    }
 +
 +    public void testCloseCursors() throws Exception {
 +        final int count = 50;
 +
 +        // Without values
 +        {
 +            Cursor[] cursors = new Cursor[count];
 +            for (int i=0; i<count; i++) {
 +                cursors[i] = EmptyCursorFactory.newEmptyCursor();
 +                mList.register(cursors[i], null);
 +            }
 +
 +            mList.closeCursors();
 +            assertEquals(0, mList.size());
 +
 +            /*
 +            for (int i=0; i<count; i++) {
 +                assertEquals(true, cursors[i].isClosed());
 +            }
 +            */
 +        }
 +
 +        // With values
 +        {
 +            Cursor[] cursors = new Cursor[count];
 +            Integer[] values = new Integer[count];
 +            for (int i=0; i<count; i++) {
 +                cursors[i] = EmptyCursorFactory.newEmptyCursor();
 +                values[i] = i;
 +                mList.register(cursors[i], values[i]);
 +            }
 +
 +            mList.closeCursors();
 +            assertEquals(0, mList.size());
 +
 +            /*
 +            for (int i=0; i<count; i++) {
 +                assertEquals(true, cursors[i].isClosed());
 +            }
 +            */
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/spi/TestSequenceValueGenerator.java b/src/test/java/com/amazon/carbonado/spi/TestSequenceValueGenerator.java new file mode 100644 index 0000000..ae17e5d --- /dev/null +++ b/src/test/java/com/amazon/carbonado/spi/TestSequenceValueGenerator.java @@ -0,0 +1,258 @@ +/*
 + * 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.spi;
 +
 +import java.util.Random;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.PersistException;
 +import com.amazon.carbonado.Repository;
 +import com.amazon.carbonado.Storage;
 +
 +import com.amazon.carbonado.repo.toy.ToyRepository;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestSequenceValueGenerator extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestSequenceValueGenerator.class);
 +    }
 +
 +    private Repository mRepository;
 +
 +    public TestSequenceValueGenerator(String name) {
 +        super(name);
 +    }
 +
 +    protected void setUp() throws Exception {
 +        super.setUp();
 +        mRepository = new ToyRepository();
 +    }
 +
 +    protected void tearDown() throws Exception {
 +        super.tearDown();
 +    }
 +
 +    public void test_basics() throws Exception {
 +        SequenceValueGenerator generator = new SequenceValueGenerator(mRepository, "foo");
 +
 +        for (int i=1; i<=950; i++) {
 +            assertEquals(i, generator.nextLongValue());
 +        }
 +
 +        generator.reset(1);
 +
 +        for (int i=1; i<=950; i++) {
 +            assertEquals(i, generator.nextIntValue());
 +        }
 +
 +        generator.reset(1);
 +
 +        for (int i=1; i<=950; i++) {
 +            assertEquals(String.valueOf(i), generator.nextDecimalValue());
 +        }
 +
 +        // Make sure data is persisted
 +
 +        generator = new SequenceValueGenerator(mRepository, "foo");
 +
 +        assertTrue(generator.nextLongValue() > 950);
 +
 +        // Make sure data is isolated
 +
 +        generator = new SequenceValueGenerator(mRepository, "another");
 +
 +        for (int i=1; i<=1050; i++) {
 +            assertEquals(i, generator.nextLongValue());
 +        }
 +        
 +        // Make sure reserved values can be returned
 +
 +        generator.returnReservedValues();
 +        generator = new SequenceValueGenerator(mRepository, "another");
 +
 +        assertEquals(1051, generator.nextLongValue());
 +    }
 +
 +    public void test_highIncrement() throws Exception {
 +        SequenceValueGenerator generator =
 +            new SequenceValueGenerator(mRepository, "foo", 1, 125);
 +
 +        for (int i=0; i<950; i++) {
 +            assertEquals(i * 125 + 1, generator.nextLongValue());
 +        }
 +    }
 +
 +    public void test_highInitialAndHighIncrement() throws Exception {
 +        SequenceValueGenerator generator =
 +            new SequenceValueGenerator(mRepository, "foo", 0x500000000L, 125);
 +
 +        for (int i=0; i<950; i++) {
 +            assertEquals(i * 125 + 0x500000000L, generator.nextLongValue());
 +        }
 +
 +        try {
 +            // Doesn't fit in an int.
 +            generator.nextIntValue();
 +            fail();
 +        } catch (PersistException e) {
 +        }
 +    }
 +
 +    public void test_lowReserve() throws Exception {
 +        SequenceValueGenerator generator =
 +            new SequenceValueGenerator(mRepository, "goo", 1, 1, 1);
 +
 +        for (int i=1; i<=950; i++) {
 +            assertEquals(i, generator.nextLongValue());
 +        }
 +    }
 +
 +    public void test_overflow() throws Exception {
 +        Storage<StoredSequence> storage = mRepository.storageFor(StoredSequence.class);
 +        StoredSequence seq = storage.prepare();
 +        seq.setName("overflow");
 +        seq.setInitialValue(1);
 +        seq.setNextValue(Long.MAX_VALUE - 50);
 +        seq.insert();
 +
 +        SequenceValueGenerator generator = new SequenceValueGenerator(mRepository, "overflow");
 +
 +        for (int i=-50; i<=-1; i++) {
 +            assertEquals(i, generator.nextLongValue());
 +        }
 +
 +        // Although next value could be zero, overflow logic doesn't work this
 +        // way. Its not really worth the trouble to allow zero to be returned
 +        // before overflowing.
 +
 +        try {
 +            // Overflow.
 +            generator.nextLongValue();
 +            fail();
 +        } catch (PersistException e) {
 +        }
 +    }
 +
 +    public void test_largeNumericalValue() throws Exception {
 +        // Tests string conversion to ensure large unsigned values are properly
 +        // generated.
 +
 +        SequenceValueGenerator generator =
 +            new SequenceValueGenerator(mRepository, "goo", Long.MAX_VALUE, 1);
 +
 +        assertEquals("9223372036854775807", generator.nextDecimalValue());
 +        // Next values are too large to fit in an unsigned long
 +        assertEquals("9223372036854775808", generator.nextDecimalValue());
 +        assertEquals("9223372036854775809", generator.nextDecimalValue());
 +    }
 +
 +    public void test_radix() throws Exception {
 +        SequenceValueGenerator generator = new SequenceValueGenerator(mRepository, "goo");
 +
 +        for (int i=1; i<=1000; i++) {
 +            assertEquals(Integer.toString(i, 36), generator.nextNumericalValue(36, 1));
 +        }
 +    }
 +
 +    public void test_pad() throws Exception {
 +        SequenceValueGenerator generator = new SequenceValueGenerator(mRepository, "goo");
 +
 +        for (int i=1; i<=2000; i++) {
 +            String next = generator.nextNumericalValue(10, 3);
 +            assertTrue(next.length() >= 3);
 +            int value = Integer.parseInt(next);
 +            assertEquals(i, value);
 +        }
 +    }
 +
 +    public void test_concurrentAccess() throws Exception {
 +        // Simple test ensuring that values are reserved properly even when
 +        // multiple processes may be sharing the sequence.
 +
 +        SequenceValueGenerator g1 = new SequenceValueGenerator(mRepository, "goo", 1, 1, 100);
 +        SequenceValueGenerator g2 = new SequenceValueGenerator(mRepository, "goo", 1, 1, 100);
 +
 +        for (int i=1; i<=100; i++) {
 +            assertEquals(i, g1.nextLongValue());
 +            assertEquals(i + 100, g2.nextLongValue());
 +        }
 +
 +        for (int i=201; i<=300; i++) {
 +            assertEquals(i, g2.nextLongValue());
 +            assertEquals(i + 100, g1.nextLongValue());
 +        }
 +
 +        assertTrue(g1.returnReservedValues());
 +        assertFalse(g2.returnReservedValues());
 +    }
 +
 +    // FIXME: move this test somewhere else
 +    /* Takes too long
 +    public void test_heavyConcurrentAccess() throws Exception {
 +        // Heavy test with multiple processes sharing the sequence.
 +
 +        final Storage<StorableTestBasic> storage =
 +            mRepository.storageFor(StorableTestBasic.class);
 +        final Random rnd = new Random(376296292);
 +        final int loopCount = 10000;
 +
 +        Thread[] threads = new Thread[10];
 +        for (int i=0; i<threads.length; i++) {
 +            threads[i] = new Thread() {
 +                public void run() {
 +                    try {
 +                        SequenceValueGenerator generator =
 +                            new SequenceValueGenerator(mRepository, "seq");
 +                        for (int i=0; i<loopCount; i++) {
 +                            StorableTestBasic stb = storage.prepare();
 +                            stb.setId(generator.nextIntValue());
 +                            stb.initBasicProperties();
 +                            stb.insert();
 +                            if (rnd.nextInt(500) == 0) {
 +                                generator.returnReservedValues();
 +                            }
 +                        }
 +                    } catch (Exception e) {
 +                        e.printStackTrace(System.out);
 +                        fail(e.toString());
 +                    }
 +                }
 +            };
 +
 +            threads[i].start();
 +        }
 +
 +        for (Thread t : threads) {
 +            t.join();
 +        }
 +    }
 +    */
 +}
 diff --git a/src/test/java/com/amazon/carbonado/spi/TestStorableSerializer.java b/src/test/java/com/amazon/carbonado/spi/TestStorableSerializer.java new file mode 100644 index 0000000..664bf12 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/spi/TestStorableSerializer.java @@ -0,0 +1,115 @@ +/*
 + * 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.spi;
 +
 +import java.io.*;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.*;
 +import com.amazon.carbonado.lob.*;
 +
 +import com.amazon.carbonado.repo.toy.ToyRepository;
 +import com.amazon.carbonado.stored.*;
 +
 +/**
 + * Test case for {@link StorableSerializer}.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestStorableSerializer extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestStorableSerializer.class);
 +    }
 +
 +    private Repository mRepository;
 +
 +    public TestStorableSerializer(String name) {
 +        super(name);
 +    }
 +
 +    protected void setUp() {
 +        mRepository = new ToyRepository();
 +    }
 +
 +    protected void tearDown() {
 +        mRepository.close();
 +        mRepository = null;
 +    }
 +
 +    public void testReadAndWrite() throws Exception {
 +        Storage<StorableTestBasic> storage = mRepository.storageFor(StorableTestBasic.class);
 +        StorableTestBasic stb = storage.prepare();
 +        stb.setId(50);
 +        stb.setStringProp("hello");
 +        stb.setIntProp(100);
 +        stb.setLongProp(999);
 +        stb.setDoubleProp(2.718281828d);
 +
 +        StorableSerializer<StorableTestBasic> serializer = 
 +            StorableSerializer.forType(StorableTestBasic.class);
 +
 +        ByteArrayOutputStream bout = new ByteArrayOutputStream();
 +        DataOutputStream dout = new DataOutputStream(bout);
 +
 +        serializer.write(stb, (DataOutput) dout);
 +        dout.flush();
 +
 +        byte[] bytes = bout.toByteArray();
 +
 +        ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
 +        DataInputStream din = new DataInputStream(bin);
 +
 +        StorableTestBasic stb2 = serializer.read(storage, (DataInput) din);
 +
 +        assertEquals(stb, stb2);
 +    }
 +
 +    /*
 +    public void testReadAndWriteLobs() throws Exception {
 +        Storage<StorableWithLobs> storage = mRepository.storageFor(StorableWithLobs.class);
 +        StorableWithLobs s = storage.prepare();
 +        s.setBlobValue(new ByteArrayBlob("Hello Blob".getBytes()));
 +        s.setClobValue(new StringClob("Hello Clob"));
 +
 +        StorableSerializer<StorableWithLobs> serializer = 
 +            StorableSerializer.forType(StorableWithLobs.class);
 +
 +        ByteArrayOutputStream bout = new ByteArrayOutputStream();
 +        DataOutputStream dout = new DataOutputStream(bout);
 +
 +        serializer.write(s, (DataOutput) dout);
 +        dout.flush();
 +
 +        byte[] bytes = bout.toByteArray();
 +
 +        ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
 +        DataInputStream din = new DataInputStream(bin);
 +
 +        StorableWithLobs s2 = serializer.read(storage, (DataInput) din);
 +
 +        assertEquals(s, s2);
 +    }
 +    */
 +}
 diff --git a/src/test/java/com/amazon/carbonado/spi/TestTriggerManager.java b/src/test/java/com/amazon/carbonado/spi/TestTriggerManager.java new file mode 100644 index 0000000..cad4f0f --- /dev/null +++ b/src/test/java/com/amazon/carbonado/spi/TestTriggerManager.java @@ -0,0 +1,334 @@ +/*
 + * 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.spi;
 +
 +import java.util.ArrayList;
 +import java.util.List;
 +
 +import junit.framework.Assert;
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.Trigger;
 +
 +import com.amazon.carbonado.stored.Dummy;
 +
 +/**
 + * Tests for TriggerManager.
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestTriggerManager extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestTriggerManager.class);
 +    }
 +
 +    public TestTriggerManager(String name) {
 +        super(name);
 +    }
 +
 +    @Override
 +    protected void setUp() {
 +        beforeTriggers = new ArrayList<TestTrigger>();
 +        afterTriggers = new ArrayList<TestTrigger>();
 +        failedTriggers = new ArrayList<TestTrigger>();
 +    }
 +
 +    @Override
 +    protected void tearDown() {
 +    }
 +
 +    List<TestTrigger> beforeTriggers;
 +    List<TestTrigger> afterTriggers;
 +    List<TestTrigger> failedTriggers;
 +
 +    public void testAddAndRemove() {
 +        TriggerManager<Dummy> set = new TriggerManager<Dummy>(null, null);
 +        Trigger<Dummy> trigger = new TestTrigger<Dummy>();
 +
 +        assertNull(set.getInsertTrigger());
 +        assertNull(set.getUpdateTrigger());
 +        assertNull(set.getDeleteTrigger());
 +
 +        assertTrue(set.addTrigger(trigger));
 +        assertNotNull(set.getInsertTrigger());
 +        assertNotNull(set.getUpdateTrigger());
 +        assertNotNull(set.getDeleteTrigger());
 +
 +        assertFalse(set.addTrigger(trigger));
 +        assertNotNull(set.getInsertTrigger());
 +        assertNotNull(set.getUpdateTrigger());
 +        assertNotNull(set.getDeleteTrigger());
 +
 +        assertTrue(set.removeTrigger(trigger));
 +        assertNull(set.getInsertTrigger());
 +        assertNull(set.getUpdateTrigger());
 +        assertNull(set.getDeleteTrigger());
 +
 +        assertFalse(set.removeTrigger(trigger));
 +        assertNull(set.getInsertTrigger());
 +        assertNull(set.getUpdateTrigger());
 +        assertNull(set.getDeleteTrigger());
 +
 +        Trigger<Dummy> trigger2 = new TestTrigger<Dummy>();
 +        assertTrue(set.addTrigger(trigger));
 +        assertTrue(set.addTrigger(trigger2));
 +        assertNotNull(set.getInsertTrigger());
 +        assertNotNull(set.getUpdateTrigger());
 +        assertNotNull(set.getDeleteTrigger());
 +
 +        assertTrue(set.removeTrigger(trigger));
 +        assertNotNull(set.getInsertTrigger());
 +        assertNotNull(set.getUpdateTrigger());
 +        assertNotNull(set.getDeleteTrigger());
 +        assertTrue(set.removeTrigger(trigger2));
 +        assertNull(set.getInsertTrigger());
 +        assertNull(set.getUpdateTrigger());
 +        assertNull(set.getDeleteTrigger());
 +    }
 +
 +    public void testBeforeAndAfterOps() throws Exception {
 +        TriggerManager<Dummy> set = new TriggerManager<Dummy>(null, null);
 +        TestTrigger<Dummy> trigger = new TestTrigger<Dummy>();
 +        set.addTrigger(trigger);
 +        Dummy d = new Dummy();
 +
 +        Object state = set.getInsertTrigger().beforeInsert(d);
 +        assertEquals(1, trigger.beforeInsertCount);
 +        assertEquals(0, trigger.beforeUpdateCount);
 +        assertEquals(0, trigger.beforeDeleteCount);
 +
 +        set.getInsertTrigger().afterInsert(d, state);
 +        assertEquals(1, trigger.afterInsertCount);
 +        assertEquals(0, trigger.afterUpdateCount);
 +        assertEquals(0, trigger.afterDeleteCount);
 +
 +        state = set.getUpdateTrigger().beforeUpdate(d);
 +        assertEquals(1, trigger.beforeUpdateCount);
 +        assertEquals(0, trigger.beforeDeleteCount);
 +
 +        set.getUpdateTrigger().afterUpdate(d, state);
 +        assertEquals(1, trigger.afterUpdateCount);
 +        assertEquals(0, trigger.afterDeleteCount);
 +
 +        state = set.getDeleteTrigger().beforeDelete(d);
 +        assertEquals(1, trigger.beforeDeleteCount);
 +
 +        set.getDeleteTrigger().afterDelete(d, state);
 +        assertEquals(1, trigger.afterDeleteCount);
 +    }
 +
 +    public void testBeforeAndFailedOps() throws Exception {
 +        TriggerManager<Dummy> set = new TriggerManager<Dummy>(null, null);
 +        TestTrigger<Dummy> trigger = new TestTrigger<Dummy>();
 +        set.addTrigger(trigger);
 +        Dummy d = new Dummy();
 +
 +        Object state = set.getInsertTrigger().beforeInsert(d);
 +        assertEquals(1, trigger.beforeInsertCount);
 +        assertEquals(0, trigger.beforeUpdateCount);
 +        assertEquals(0, trigger.beforeDeleteCount);
 +
 +        set.getInsertTrigger().failedInsert(d, state);
 +        assertEquals(1, trigger.failedInsertCount);
 +        assertEquals(0, trigger.failedUpdateCount);
 +        assertEquals(0, trigger.failedDeleteCount);
 +
 +        state = set.getUpdateTrigger().beforeUpdate(d);
 +        assertEquals(1, trigger.beforeUpdateCount);
 +        assertEquals(0, trigger.beforeDeleteCount);
 +
 +        set.getUpdateTrigger().failedUpdate(d, state);
 +        assertEquals(1, trigger.failedUpdateCount);
 +        assertEquals(0, trigger.failedDeleteCount);
 +
 +        state = set.getDeleteTrigger().beforeDelete(d);
 +        assertEquals(1, trigger.beforeDeleteCount);
 +
 +        set.getDeleteTrigger().failedDelete(d, state);
 +        assertEquals(1, trigger.failedDeleteCount);
 +    }
 +
 +    public void testExecutionOrder() throws Exception {
 +        TriggerManager<Dummy> set = new TriggerManager<Dummy>(null, null);
 +        TestTrigger<Dummy> trigger = new TestTrigger<Dummy>(null);
 +        TestTrigger<Dummy> trigger2 = new TestTrigger<Dummy>();
 +        set.addTrigger(trigger);
 +        set.addTrigger(trigger2);
 +        Dummy d = new Dummy();
 +
 +        // Insert
 +        {
 +            Object state = set.getInsertTrigger().beforeInsert(d);
 +            assertEquals(2, beforeTriggers.size());
 +            assertEquals(trigger2, beforeTriggers.get(0));
 +            assertEquals(trigger, beforeTriggers.get(1));
 +
 +            set.getInsertTrigger().afterInsert(d, state);
 +            assertEquals(2, afterTriggers.size());
 +            assertEquals(trigger, afterTriggers.get(0));
 +            assertEquals(trigger2, afterTriggers.get(1));
 +
 +            state = set.getInsertTrigger().beforeInsert(d);
 +            set.getInsertTrigger().failedInsert(d, state);
 +            assertEquals(2, failedTriggers.size());
 +            assertEquals(trigger, failedTriggers.get(0));
 +            assertEquals(trigger2, failedTriggers.get(1));
 +        }
 +
 +        beforeTriggers.clear();
 +        afterTriggers.clear();
 +        failedTriggers.clear();
 +
 +        // Update
 +        {
 +            Object state = set.getUpdateTrigger().beforeUpdate(d);
 +            assertEquals(2, beforeTriggers.size());
 +            assertEquals(trigger2, beforeTriggers.get(0));
 +            assertEquals(trigger, beforeTriggers.get(1));
 +
 +            set.getUpdateTrigger().afterUpdate(d, state);
 +            assertEquals(2, afterTriggers.size());
 +            assertEquals(trigger, afterTriggers.get(0));
 +            assertEquals(trigger2, afterTriggers.get(1));
 +
 +            state = set.getUpdateTrigger().beforeUpdate(d);
 +            set.getUpdateTrigger().failedUpdate(d, state);
 +            assertEquals(2, failedTriggers.size());
 +            assertEquals(trigger, failedTriggers.get(0));
 +            assertEquals(trigger2, failedTriggers.get(1));
 +        }
 +
 +        beforeTriggers.clear();
 +        afterTriggers.clear();
 +        failedTriggers.clear();
 +
 +        // Delete
 +        {
 +            Object state = set.getDeleteTrigger().beforeDelete(d);
 +            assertEquals(2, beforeTriggers.size());
 +            assertEquals(trigger2, beforeTriggers.get(0));
 +            assertEquals(trigger, beforeTriggers.get(1));
 +
 +            set.getDeleteTrigger().afterDelete(d, state);
 +            assertEquals(2, afterTriggers.size());
 +            assertEquals(trigger, afterTriggers.get(0));
 +            assertEquals(trigger2, afterTriggers.get(1));
 +
 +            state = set.getDeleteTrigger().beforeDelete(d);
 +            set.getDeleteTrigger().failedDelete(d, state);
 +            assertEquals(2, failedTriggers.size());
 +            assertEquals(trigger, failedTriggers.get(0));
 +            assertEquals(trigger2, failedTriggers.get(1));
 +        }
 +    }
 +
 +    class TestTrigger<S extends Storable> extends Trigger<S> {
 +        final Object stateObj;
 +
 +        int beforeInsertCount;
 +        int afterInsertCount;
 +        int failedInsertCount;
 +
 +        int beforeUpdateCount;
 +        int afterUpdateCount;
 +        int failedUpdateCount;
 +
 +        int beforeDeleteCount;
 +        int afterDeleteCount;
 +        int failedDeleteCount;
 +
 +        TestTrigger() {
 +            this.stateObj = new Object();
 +        }
 +
 +        TestTrigger(Object stateObj) {
 +            this.stateObj = stateObj;
 +        }
 +
 +        @Override
 +        public Object beforeInsert(S storable) {
 +            beforeInsertCount++;
 +            beforeTriggers.add(this);
 +            return stateObj;
 +        }
 +
 +        @Override
 +        public void afterInsert(S storable, Object state) {
 +            Assert.assertEquals(stateObj, state);
 +            afterTriggers.add(this);
 +            afterInsertCount++;
 +        }
 +
 +        @Override
 +        public void failedInsert(S storable, Object state) {
 +            Assert.assertEquals(stateObj, state);
 +            failedTriggers.add(this);
 +            failedInsertCount++;
 +        }
 +
 +        @Override
 +        public Object beforeUpdate(S storable) {
 +            beforeUpdateCount++;
 +            beforeTriggers.add(this);
 +            return stateObj;
 +        }
 +
 +        @Override
 +        public void afterUpdate(S storable, Object state) {
 +            Assert.assertEquals(stateObj, state);
 +            afterTriggers.add(this);
 +            afterUpdateCount++;
 +        }
 +
 +        @Override
 +        public void failedUpdate(S storable, Object state) {
 +            Assert.assertEquals(stateObj, state);
 +            failedTriggers.add(this);
 +            failedUpdateCount++;
 +        }
 +
 +        @Override
 +        public Object beforeDelete(S storable) {
 +            beforeDeleteCount++;
 +            beforeTriggers.add(this);
 +            return stateObj;
 +        }
 +
 +        @Override
 +        public void afterDelete(S storable, Object state) {
 +            Assert.assertEquals(stateObj, state);
 +            afterTriggers.add(this);
 +            afterDeleteCount++;
 +        }
 +
 +        @Override
 +        public void failedDelete(S storable, Object state) {
 +            Assert.assertEquals(stateObj, state);
 +            failedTriggers.add(this);
 +            failedDeleteCount++;
 +        }
 +
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/Address.java b/src/test/java/com/amazon/carbonado/stored/Address.java new file mode 100644 index 0000000..3043e54 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/Address.java @@ -0,0 +1,64 @@ +/*
 + * 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.Independent;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.Nullable;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Sequence;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +@Alias("TEST_ADDRESS")
 +@PrimaryKey("addressID")
 +public interface Address extends Storable {
 +    @Sequence("TEST_ADDRESS_ID_SEQ")
 +    long getAddressID();
 +    void setAddressID(long id);
 +
 +    String getAddressLine1();
 +    void setAddressLine1(String value);
 +
 +    @Nullable
 +    String getAddressLine2();
 +    void setAddressLine2(String value);
 +
 +    String getAddressCity();
 +    void setAddressCity(String value);
 +
 +    @Nullable
 +    String getAddressState();
 +    void setAddressState(String value);
 +
 +    String getAddressZip();
 +    void setAddressZip(String value);
 +
 +    String getAddressCountry();
 +    void setAddressCountry(String value);
 +
 +    @Independent
 +    String getCustomData();
 +    void setCustomData(String str);
 +}
 +
 diff --git a/src/test/java/com/amazon/carbonado/stored/Dummy.java b/src/test/java/com/amazon/carbonado/stored/Dummy.java new file mode 100644 index 0000000..980c718 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/Dummy.java @@ -0,0 +1,145 @@ +/*
 + * 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.*;
 +
 +/**
 + * Implements all the Storable methods, but each throws
 + * UnsupportedOperationException. Methods defined in Object are left alone.
 + *
 + * @author Brian S O'Neill
 + */
 +public class Dummy implements Storable {
 +    public void load() throws FetchException {
 +        throw error();
 +    }
 +
 +    public boolean tryLoad() throws FetchException {
 +        throw error();
 +    }
 +
 +    public void insert() throws PersistException {
 +        throw error();
 +    }
 +
 +    public boolean tryInsert() throws PersistException {
 +        throw error();
 +    }
 +
 +    public void update() throws PersistException {
 +        throw error();
 +    }
 +
 +    public boolean tryUpdate() throws PersistException {
 +        throw error();
 +    }
 +
 +    public void delete() throws PersistException {
 +        throw error();
 +    }
 +
 +    public boolean tryDelete() throws PersistException {
 +        throw error();
 +    }
 +
 +    public Storage storage() {
 +        throw error();
 +    }
 +
 +    public Class storableType() {
 +        throw error();
 +    }
 +
 +    public void copyAllProperties(Storable target) {
 +        throw error();
 +    }
 +
 +    public void copyPrimaryKeyProperties(Storable target) {
 +        throw error();
 +    }
 +
 +    public void copyVersionProperty(Storable target) {
 +        throw error();
 +    }
 +
 +    public void copyUnequalProperties(Storable target) {
 +        throw error();
 +    }
 +
 +    public void copyDirtyProperties(Storable target) {
 +        throw error();
 +    }
 +
 +    public boolean hasDirtyProperties() {
 +        throw error();
 +    }
 +
 +    public void markPropertiesClean() {
 +        throw error();
 +    }
 +
 +    public void markAllPropertiesClean() {
 +        throw error();
 +    }
 +
 +    public void markPropertiesDirty() {
 +        throw error();
 +    }
 +
 +    public void markAllPropertiesDirty() {
 +        throw error();
 +    }
 +
 +    public boolean isPropertyUninitialized(String propertyName) {
 +        throw error();
 +    }
 +
 +    public boolean isPropertyDirty(String propertyName) {
 +        throw error();
 +    }
 +
 +    public boolean isPropertyClean(String propertyName) {
 +        throw error();
 +    }
 +
 +    public boolean isPropertySupported(String propertyName) {
 +        throw error();
 +    }
 +
 +    public Storable copy() {
 +        throw error();
 +    }
 +
 +    public boolean equalPrimaryKeys(Object obj) {
 +        throw error();
 +    }
 +
 +    public boolean equalProperties(Object obj) {
 +        throw error();
 +    }
 +
 +    public String toStringKeyOnly() {
 +        throw error();
 +    }
 +
 +    protected UnsupportedOperationException error() {
 +        return new UnsupportedOperationException();
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/FileInfo.java b/src/test/java/com/amazon/carbonado/stored/FileInfo.java new file mode 100644 index 0000000..c3245e3 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/FileInfo.java @@ -0,0 +1,100 @@ +/*
 + * 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 java.io.*;
 +
 +import org.joda.time.DateTime;
 +
 +import com.amazon.carbonado.*;
 +import com.amazon.carbonado.lob.*;
 +import com.amazon.carbonado.adapter.*;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +@Indexes({
 +    @Index("name"),
 +    @Index({"length", "lastModified"}),
 +    @Index("lastModified"),
 +    @Index("parentID")
 +})
 +@Alias("CBN_TEST_FILE_INFO")
 +@AlternateKeys({
 +    @Key({"parentID", "name"})
 +})
 +@PrimaryKey("ID")
 +public abstract class FileInfo implements Storable {
 +    @Sequence("com.amazon.carbonado.storables.FileInfo")
 +    public abstract int getID();
 +    public abstract void setID(int value);
 +
 +    @Nullable
 +    public abstract Integer getParentID();
 +    public abstract void setParentID(Integer id);
 +
 +    @Nullable
 +    @Join(internal="parentID", external="ID")
 +    public abstract FileInfo getParent() throws FetchException;
 +    public abstract void setParent(FileInfo value);
 +
 +    @Join(internal="ID", external="parentID")
 +    public abstract Query<FileInfo> getChildren() throws FetchException;
 +
 +    @Alias("FILE_NAME")
 +    public abstract String getName();
 +    public abstract void setName(String value);
 +
 +    @YesNoAdapter
 +    public abstract boolean isDirectory();
 +    public abstract void setDirectory(boolean value);
 +
 +    @Alias("FILE_LENGTH")
 +    public abstract long getLength();
 +    public abstract void setLength(long value);
 +
 +    @Nullable
 +    public abstract DateTime getLastModified();
 +    public abstract void setLastModified(DateTime value);
 +
 +    @Version
 +    @Alias("RECORD_VERSION_NUMBER")
 +    public abstract int getVersionNumber();
 +    public abstract void setVersionNumber(int version);
 +
 +    @Nullable
 +    public abstract Blob getFileData();
 +    public abstract void setFileData(Blob data);
 +
 +    public String getFullPath() throws FetchException {
 +        FileInfo parent;
 +        try {
 +            parent = getParent();
 +        } catch (FetchNoneException e) {
 +            parent = null;
 +        }
 +        if (parent == null) {
 +            return getName();
 +        } else {
 +            return parent.getFullPath() + '/' + getName();
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/ManyKeys2.java b/src/test/java/com/amazon/carbonado/stored/ManyKeys2.java new file mode 100644 index 0000000..282ff02 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/ManyKeys2.java @@ -0,0 +1,47 @@ +/*
 + * 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
 + */
 +@PrimaryKey({"a", "b", "c", "d", "e", "f"})
 +public interface ManyKeys2 extends Storable {
 +    int getA();
 +    void setA(int value);
 +
 +    int getB();
 +    void setB(int value);
 +
 +    int getC();
 +    void setC(int value);
 +
 +    int getD();
 +    void setD(int value);
 +
 +    int getE();
 +    void setE(int value);
 +
 +    int getF();
 +    void setF(int value);
 +}
 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<Order> {
 +    @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<OrderItem> getOrderItems() throws FetchException;
 +
 +    @Join
 +    Query<Shipment> getShipments() throws FetchException;
 +
 +    @Join
 +    Query<Promotion> 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<OrderItem> {
 +    @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<Promotion> {
 +    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<Shipment> {
 +    @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<OrderItem> 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<Shipper> {
 +    @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/StorableDateIndex.java b/src/test/java/com/amazon/carbonado/stored/StorableDateIndex.java new file mode 100644 index 0000000..5e7d063 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableDateIndex.java @@ -0,0 +1,46 @@ +/*
 + * 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.Index;
 +import com.amazon.carbonado.Indexes;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.adapter.DateTimeAdapter;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +@Indexes(@Index("orderDate"))
 +@PrimaryKey("ID")
 +public interface StorableDateIndex extends Storable {
 +    int getID();
 +
 +    void setID(int id);
 +
 +    @DateTimeAdapter(timeZone="America/New_York")
 +    DateTime getOrderDate();
 +
 +    void setOrderDate(DateTime date);
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/StorableSequenced.java b/src/test/java/com/amazon/carbonado/stored/StorableSequenced.java new file mode 100644 index 0000000..770d6f0 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableSequenced.java @@ -0,0 +1,67 @@ +/*
 + * 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.Nullable;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Sequence;
 +import com.amazon.carbonado.Storable;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +@PrimaryKey("ID")
 +public interface StorableSequenced extends Storable<StorableSequenced> {
 +    @Sequence("pk")
 +    long getID();
 +
 +    void setID(long id);
 +
 +    @Sequence("some_int")
 +    int getSomeInt();
 +
 +    void setSomeInt(int i);
 +
 +    @Sequence("some_IntegerObj")
 +    Integer getSomeIntegerObj();
 +
 +    void setSomeIntegerObj(Integer i);
 +
 +    @Sequence("some_long")
 +    long getSomeLong();
 +
 +    void setSomeLong(long i);
 +
 +    @Sequence("some_LongObj")
 +    @Nullable
 +    Long getSomeLongObj();
 +
 +    void setSomeLongObj(Long i);
 +
 +    @Sequence("some_String")
 +    String getSomeString();
 +
 +    void setSomeString(String str);
 +
 +    String getData();
 +
 +    void setData(String data);
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/StorableTestAssymetric.java b/src/test/java/com/amazon/carbonado/stored/StorableTestAssymetric.java new file mode 100644 index 0000000..3808c1f --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableTestAssymetric.java @@ -0,0 +1,38 @@ +/* + * 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.Storable; +import com.amazon.carbonado.PrimaryKey; + +/* + * StorableTestAssymetric + * + * @author Don Schneider + */ +@PrimaryKey("id") +public abstract class StorableTestAssymetric implements Storable { +    public abstract int getId(); +    public abstract void setId(int id); + +    public int getAssymetricGet() +    { +       return getId()*3; +    }; +} diff --git a/src/test/java/com/amazon/carbonado/stored/StorableTestBasic.java b/src/test/java/com/amazon/carbonado/stored/StorableTestBasic.java new file mode 100644 index 0000000..0857518 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableTestBasic.java @@ -0,0 +1,114 @@ +/*
 + * 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 java.util.Random;
 +
 +import org.joda.time.DateTime;
 +
 +import com.amazon.carbonado.Nullable;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Storage;
 +import com.amazon.carbonado.Repository;
 +import com.amazon.carbonado.RepositoryException;
 +
 +/**
 + * StorableTestBasic
 + *
 + * @author Don Schneider
 + */
 +@PrimaryKey("id")
 +public abstract class StorableTestBasic implements Storable {
 +    public abstract int getId();
 +    public abstract void setId(int id);
 +
 +    // Basic coverage of the primitives
 +    public abstract String getStringProp();
 +    public abstract void setStringProp(String aStringThing);
 +
 +    public abstract int getIntProp();
 +    public abstract void setIntProp(int anInt);
 +
 +    public abstract long getLongProp();
 +    public abstract void setLongProp(long aLong);
 +
 +    public abstract double getDoubleProp();
 +    public abstract void setDoubleProp(double aDouble);
 +
 +    @Nullable
 +    public abstract DateTime getDate();
 +    public abstract void setDate(DateTime aDate);
 +
 +    public void initPrimaryKeyProperties() {
 +        setId(10);
 +    }
 +
 +    public void initBasicProperties() {
 +        setStringProp("foo");
 +        setIntProp(10);
 +        setLongProp(120);
 +        setDoubleProp(1.2);
 +    }
 +
 +    public void initPropertiesRandomly(int id) {
 +        setId(id);
 +
 +        Random random = new Random(1000);
 +
 +        setIntProp(random.nextInt());
 +        setLongProp(random.nextLong());
 +        setDoubleProp(random.nextDouble());
 +        setStringProp("imaString_" + id % 10);
 +    }
 +
 +    public void initPropertiesPredictably(int id) {
 +        setId(id);
 +
 +        setIntProp(id*10);
 +        setLongProp(id*10);
 +        setDoubleProp(id/2.0);
 +        setStringProp("string-" + id % 100);
 +    }
 +
 +    public static void insertBunches(Repository repository, int count)
 +            throws RepositoryException
 +    {
 +        insertBunches(repository, count, 0, true);
 +    }
 +
 +
 +    public static void insertBunches(Repository repository,
 +                                     int count, int startId,
 +                                     boolean doRandom)
 +        throws RepositoryException
 +    {
 +        Storage<StorableTestBasic> storage = repository.storageFor(StorableTestBasic.class);
 +        StorableTestBasic s;
 +
 +        for (int i = 0; i < count; i ++) {
 +            s = storage.prepare();
 +            if (doRandom) {
 +                s.initPropertiesRandomly(i);
 +            } else {
 +                s.initPropertiesPredictably(i+startId);
 +            }
 +            s.insert();
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/StorableTestBasicIndexed.java b/src/test/java/com/amazon/carbonado/stored/StorableTestBasicIndexed.java new file mode 100644 index 0000000..c9de396 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableTestBasicIndexed.java @@ -0,0 +1,37 @@ +/* + * 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.Index; +import com.amazon.carbonado.Indexes; +import com.amazon.carbonado.PrimaryKey; + +/** + * StorableTestBasicIndexed + * + * @author Brian S O'Neill + */ +@Indexes({ +    @Index("stringProp"), +    @Index("intProp"), +    @Index("longProp"), +    @Index("doubleProp") +}) +@PrimaryKey("id") +public abstract class StorableTestBasicIndexed extends StorableTestBasic { +} diff --git a/src/test/java/com/amazon/carbonado/stored/StorableTestInvalid.java b/src/test/java/com/amazon/carbonado/stored/StorableTestInvalid.java new file mode 100644 index 0000000..bd9fed2 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableTestInvalid.java @@ -0,0 +1,40 @@ +/* + * 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.Storable; +import com.amazon.carbonado.PrimaryKey; + +/** + * StorableTestInvalid + * + * @author Don Schneider + */ +@PrimaryKey("pk") +public interface StorableTestInvalid extends Storable { +    int getPk(); +    void setPk(int id); + +    Custom getCustom(); +    void setCustom(Custom aCustom); + + +    // Probably more than is needed +    class Custom {    } +} diff --git a/src/test/java/com/amazon/carbonado/stored/StorableTestKeyValue.java b/src/test/java/com/amazon/carbonado/stored/StorableTestKeyValue.java new file mode 100644 index 0000000..44ecc2f --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableTestKeyValue.java @@ -0,0 +1,43 @@ +/* + * 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.Storable; +import com.amazon.carbonado.PrimaryKey; + +/** + * StorableTestKeyValue + * + * @author Don Schneider + */ +@PrimaryKey({"key1", "key2"}) +public interface StorableTestKeyValue extends Storable { +    int getKey1(); +    void setKey1(int id); + +    int getKey2(); +    void setKey2(int id); + +    int getValue1(); +    void setValue1(int value); + +    int getValue2(); +    void setValue2(int value); + +} diff --git a/src/test/java/com/amazon/carbonado/stored/StorableTestMinimal.java b/src/test/java/com/amazon/carbonado/stored/StorableTestMinimal.java new file mode 100644 index 0000000..a1aae70 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableTestMinimal.java @@ -0,0 +1,32 @@ +/*
 + * 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.Storable;
 +import com.amazon.carbonado.PrimaryKey;
 +
 +/**
 + * StorableTestBasic
 + *
 + * @author Don Schneider
 + */
 +@PrimaryKey("id")
 +public interface StorableTestMinimal extends Storable {
 +    int getId();
 +    void setId(int id);
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/StorableTestMultiPK.java b/src/test/java/com/amazon/carbonado/stored/StorableTestMultiPK.java new file mode 100644 index 0000000..ace031b --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableTestMultiPK.java @@ -0,0 +1,40 @@ +/* + * 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.PrimaryKey; +import com.amazon.carbonado.Storable; + +/** + * StorableTestMultiPK + * + * @author Don Schneider + */ +@PrimaryKey({"idPK", "stringPK"}) +public interface StorableTestMultiPK extends Storable { +    int getIdPK(); +    void setIdPK(int id); + +    // Basic coverage of the primitives +    String getStringPK(); +    void setStringPK(String aString); + +    String getStringData(); +    void setStringData(String aData); +} diff --git a/src/test/java/com/amazon/carbonado/stored/StorableTimestamped.java b/src/test/java/com/amazon/carbonado/stored/StorableTimestamped.java new file mode 100644 index 0000000..d93ffeb --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableTimestamped.java @@ -0,0 +1,38 @@ +/*
 + * 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.Storable;
 +import com.amazon.carbonado.PrimaryKey;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +@PrimaryKey("id")
 +public interface StorableTimestamped extends Storable, Timestamped {
 +    int getId();
 +    void setId(int id);
 +
 +    String getValue();
 +    void setValue(String str);
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/StorableVersioned.java b/src/test/java/com/amazon/carbonado/stored/StorableVersioned.java new file mode 100644 index 0000000..2de9640 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableVersioned.java @@ -0,0 +1,50 @@ +/*
 + * 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.Nullable;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Version;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +@PrimaryKey("ID")
 +public interface StorableVersioned extends Storable {
 +    int getID();
 +
 +    void setID(int id);
 +
 +    String getValue();
 +
 +    void setValue(String value);
 +
 +    @Nullable
 +    String getName();
 +
 +    void setName(String name);
 +
 +    @Version
 +    int getVersion();
 +
 +    void setVersion(int version);
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/StorableVersionedIndexed.java b/src/test/java/com/amazon/carbonado/stored/StorableVersionedIndexed.java new file mode 100644 index 0000000..906e8be --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableVersionedIndexed.java @@ -0,0 +1,36 @@ +/*
 + * 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.Index;
 +import com.amazon.carbonado.Indexes;
 +import com.amazon.carbonado.PrimaryKey;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +@Indexes({
 +    @Index("value"),
 +    @Index("name")
 +})
 +@PrimaryKey("ID")
 +public interface StorableVersionedIndexed extends StorableVersioned {
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/StorableVersionedWithLong.java b/src/test/java/com/amazon/carbonado/stored/StorableVersionedWithLong.java new file mode 100644 index 0000000..6d55d54 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableVersionedWithLong.java @@ -0,0 +1,44 @@ +/*
 + * 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.Storable;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Version;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +@PrimaryKey("ID")
 +public interface StorableVersionedWithLong extends Storable {
 +    int getID();
 +
 +    void setID(int id);
 +
 +    String getValue();
 +
 +    void setValue(String value);
 +
 +    @Version
 +    long getVersion();
 +
 +    void setVersion(long version);
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/StorableVersionedWithLongObj.java b/src/test/java/com/amazon/carbonado/stored/StorableVersionedWithLongObj.java new file mode 100644 index 0000000..2acfdf8 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableVersionedWithLongObj.java @@ -0,0 +1,46 @@ +/*
 + * 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.Nullable;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Version;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +@PrimaryKey("ID")
 +public interface StorableVersionedWithLongObj extends Storable {
 +    int getID();
 +
 +    void setID(int id);
 +
 +    String getValue();
 +
 +    void setValue(String value);
 +
 +    @Version
 +    @Nullable
 +    Long getVersion();
 +
 +    void setVersion(Long version);
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/StorableVersionedWithObj.java b/src/test/java/com/amazon/carbonado/stored/StorableVersionedWithObj.java new file mode 100644 index 0000000..29f313c --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/StorableVersionedWithObj.java @@ -0,0 +1,46 @@ +/*
 + * 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.Nullable;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Version;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +@PrimaryKey("ID")
 +public interface StorableVersionedWithObj extends Storable {
 +    int getID();
 +
 +    void setID(int id);
 +
 +    String getValue();
 +
 +    void setValue(String value);
 +
 +    @Version
 +    @Nullable
 +    Integer getVersion();
 +
 +    void setVersion(Integer version);
 +}
 diff --git a/src/test/java/com/amazon/carbonado/stored/Timestamped.java b/src/test/java/com/amazon/carbonado/stored/Timestamped.java new file mode 100644 index 0000000..b38ee28 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/stored/Timestamped.java @@ -0,0 +1,34 @@ +/*
 + * 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;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public interface Timestamped {
 +    DateTime getSubmitDateTime();
 +    void setSubmitDateTime(DateTime dt);
 +
 +    DateTime getModifyDateTime();
 +    void setModifyDateTime(DateTime dt);
 +}
 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<UserAddress> {
 +    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<UserInfo> {
 +    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);
 +}
 diff --git a/src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java b/src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java new file mode 100644 index 0000000..0b93822 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java @@ -0,0 +1,755 @@ +/* + * 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.synthetic; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Random; +import java.util.Set; +import java.util.Map; +import java.util.HashMap; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import org.joda.time.DateTime; + +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.PrimaryKey; +import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.Version; +import com.amazon.carbonado.Nullable; +import com.amazon.carbonado.Join; + +import com.amazon.carbonado.adapter.YesNoAdapter; +import com.amazon.carbonado.adapter.TextAdapter; +import com.amazon.carbonado.info.Direction; +import com.amazon.carbonado.info.StorableInfo; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; + +import com.amazon.carbonado.repo.toy.ToyRepository; + +import com.amazon.carbonado.stored.StorableTestBasic; + +/** + * @author Don Schneider + */ +public class TestSyntheticStorableBuilders extends TestCase { +    public static void main(String[] args) { +        junit.textui.TestRunner.run(suite()); +    } + +    public static TestSuite suite() { +        return new TestSuite(TestSyntheticStorableBuilders.class); +    } + +    private static final ValueVendor VENDOR = new ValueVendor(); + +    public static final String VERSION = '@' +            + Version.class.toString().substring(10) + "()"; + +    public static final String NULLABLE = '@' +            + Nullable.class.toString().substring(10) + "()"; + +    public static final String JOIN = '@' + Join.class.toString().substring(10) + '('; + +    public static final String INTERNAL = "internal=[]"; + +    public static final String EXTERNAL = "external=[]"; + +    public final static String YESNO = '@' +            + YesNoAdapter.class.toString().substring(10) + "(lenient=true)"; + +    public static final String TEXT = '@' +            + TextAdapter.class.toString().substring(10) + "(charset=UTF-8, altCharsets=[])"; + +    public static final String TEXT_2 = '@' +            + TextAdapter.class.toString().substring(10) + "(altCharsets=[], charset=UTF-8)"; + +    private static Set s_storableInterfaceMethods = new HashSet(); + +    public static final TestDef[] TESTS = new TestDef[] { +            new TestDef("string/long", +                        StorableTestBasic.class, +                        StorableTestBasic.class.getName() + "~N+stringProp-longProp", +                        new IndexDef[] { +                                new IndexDef("stringProp", Direction.ASCENDING), +                                new IndexDef("longProp", Direction.DESCENDING), }, +                        new MethodDef[] { +                                new MethodDef("getStringProp", new String[] { TEXT }), +                                new MethodDef("getLongProp", null), +                                new MethodDef("getId", null), +                                new MethodDef("getIntProp", null), +                                new MethodDef("getDoubleProp", null), +                                // FIXME: add version prop to STB +//                                new MethodDef("getVersionNumber", +//                                              new String[] { VERSION }), +                                new MethodDef("getMaster_0", new String[] { +                                        NULLABLE, JOIN }), +                                new MethodDef("getIsConsistent_0", null), +//                                new MethodDef("setVersionNumber", null), +                                new MethodDef("setAllProperties_0", null), +                                new MethodDef("setMaster_0", null), +                                new MethodDef("setId", null), + +                                new MethodDef("setStringProp", null), +                                new MethodDef("setLongProp", null), +                                new MethodDef("setID", null), +                                new MethodDef("setIntProp", null), +                                new MethodDef("setDoubleProp", null), + +                                new MethodDef("getClass", null), +                                new MethodDef("wait", null), +                                new MethodDef("notify", null), +                                new MethodDef("notifyAll", null), }, +                        null), +            new TestDef("string/long+synthetic", +                        StorableTestBasic.class, +                        StorableTestBasic.class.getName() + "~N+stringProp-longProp", +                        new IndexDef[] { +                                new IndexDef("stringProp", Direction.ASCENDING), +                                new IndexDef("longProp", Direction.DESCENDING), }, +                        new MethodDef[] { +                                new MethodDef("getStringProp", new String[] { TEXT }), +                                new MethodDef("getLongProp", null), +                                new MethodDef("getId", null), +                                new MethodDef("getIntProp", null), +                                new MethodDef("getDoubleProp", null), +                                // FIXME: add version prop to STB +//                                new MethodDef("getVersionNumber", +//                                              new String[] { VERSION }), +                                new MethodDef("getMaster_0", new String[] { +                                        NULLABLE, JOIN }), +                                new MethodDef("getIsConsistent_0", null), +//                                new MethodDef("setVersionNumber", null), +                                new MethodDef("setAllProperties_0", null), +                                new MethodDef("setMaster_0", null), +                                new MethodDef("setId", null), + +                                new MethodDef("setStringProp", null), +                                new MethodDef("setLongProp", null), +                                new MethodDef("setID", null), +                                new MethodDef("setIntProp", null), +                                new MethodDef("setDoubleProp", null), + +                                new MethodDef("getClass", null), +                                new MethodDef("wait", null), +                                new MethodDef("notify", null), +                                new MethodDef("notifyAll", null), + +                                new MethodDef("getTestSynth", new String[] { NULLABLE }), +                                new MethodDef("setTestSynth", null), +                        }, +                        new SyntheticProperty[] { new SyntheticProperty("testSynth", +                                                                        String.class, +                                                                        true, +                                                                        false) }), +            new TestDef("string/long+versionedSynthetic", +                        StorableTestBasic.class, +                        StorableTestBasic.class.getName() + "~N+stringProp-longProp", +                        new IndexDef[] { +                                new IndexDef("stringProp", Direction.ASCENDING), +                                new IndexDef("longProp", Direction.DESCENDING), }, +                        new MethodDef[] { + +                                new MethodDef("getStringProp", new String[] { TEXT }), +                                new MethodDef("getLongProp", null), +                                new MethodDef("getId", null), +                                new MethodDef("getIntProp", null), +                                new MethodDef("getDoubleProp", null), +                                new MethodDef("getMaster_0", new String[] { +                                        NULLABLE, JOIN }), +                                new MethodDef("getIsConsistent_0", null), +                                new MethodDef("setAllProperties_0", null), +                                new MethodDef("setMaster_0", null), +                                new MethodDef("setId", null), + +                                new MethodDef("setStringProp", null), +                                new MethodDef("setLongProp", null), +                                new MethodDef("setID", null), +                                new MethodDef("setIntProp", null), +                                new MethodDef("setDoubleProp", null), + +                                new MethodDef("getClass", null), +                                new MethodDef("wait", null), +                                new MethodDef("notify", null), +                                new MethodDef("notifyAll", null), + +                                new MethodDef("getTestSynth", new String[] { VERSION  }), +                                new MethodDef("setTestSynth", null), +                        }, +                        new SyntheticProperty[] { new SyntheticProperty("testSynth", +                                                                        int.class, +                                                                        false, +                                                                        true) }) }; + +    static { +        for (Method m : Storable.class.getMethods()) { +            s_storableInterfaceMethods.add(m.getName()); +        } +    } + +    protected Repository mRepository; + +    public TestSyntheticStorableBuilders(String name) { +        super(name); +    } + +    /* (non-Javadoc) +     * @see junit.framework.TestCase#setUp() +     */ +    @Override +    protected void setUp() throws Exception +    { +        super.setUp(); +        mRepository = new ToyRepository(); +    } + +    @Override +    protected void tearDown() throws Exception { +        if (mRepository != null) { +            mRepository.close(); +            mRepository = null; +        } +    } + +    public void testSanity() throws Exception { +        exerciseStorable(StorableTestBasic.class); +    } + + +    /** +     * Test syntheticBuilder +     */ +    public <S extends Storable> void test_syntheticBuilder() throws Exception { +        SyntheticProperty[] props = new SyntheticProperty[] { +                new SyntheticProperty("synthId", +                                      int.class, +                                      false, +                                      false), +                new SyntheticProperty("synthStr", +                                      String.class, +                                      true, +                                      false), +                new SyntheticProperty("synthInt", +                                      int.class, +                                      false, +                                      false), +                new SyntheticProperty("synthVers", +                                      int.class, +                                      false, +                                      true), +                new SyntheticProperty("synthBool", +                                      boolean.class, +                                      false, +                                      false), }; + +        TestDef test = new TestDef("synthetic", +                                   null, +                                   "TSSB~synthId~synthStr~synthInt~synthVers~synthBool", +                                   null, +                                   new MethodDef[] { +                                           new MethodDef("getSynthId", +                                                         null), +                                           new MethodDef("getSynthStr", +                                                         new String[] { NULLABLE }), +                                           new MethodDef("getSynthInt", null), +                                           new MethodDef("getSynthVers", +                                                         new String[] { VERSION }), +                                           new MethodDef("isSynthBool", null), + +                                           new MethodDef("setSynthId", null), +                                           new MethodDef("setSynthStr", null), +                                           new MethodDef("setSynthInt", null), +                                           new MethodDef("setSynthVers", null), +                                           new MethodDef("setSynthBool", null), + +                                           new MethodDef("getClass", null), +                                           new MethodDef("wait", null), +                                           new MethodDef("notify", null), +                                           new MethodDef("notifyAll", null), }, +                                   props); + +        SyntheticStorableBuilder b = new SyntheticStorableBuilder( +                                               "TSSB", this.getClass().getClassLoader()); +        int i = 0; +        for (SyntheticProperty source : props) { +            b.addProperty(source); +            if (i == 0) { +                // First is primary key. +                b.addPrimaryKey().addProperty(source.getName()); +            } +            i++; +        } +        Class synth = b.build(); +        validateIndexEntry(test, synth); + +        exerciseStorable(synth); +    } + + + +    public void testSyntheticReference() throws Exception { +        for (TestDef test : TESTS) { +            StorableInfo info = StorableIntrospector.examine(test.mClass); + +            SyntheticStorableReferenceBuilder<StorableTestBasic> builder +            = new SyntheticStorableReferenceBuilder<StorableTestBasic>(test.mClass, false); + +            for (IndexDef refProp : test.mProps) { +                StorableProperty storableProperty = ((StorableProperty) info.getAllProperties() +                                                                            .get(refProp.mProp)); +                builder.addKeyProperty(refProp.mProp, refProp.getDir()); +            } +            builder.build(); +            Class s = builder.getStorableClass(); +            validateIndexEntry(test, s); +            exerciseStorable(s); + +            StorableTestBasic master = +                mRepository.storageFor(StorableTestBasic.class).prepare(); +            populate(master); +            master.insert(); + +            Storable index = mRepository.storageFor(s).prepare(); +            builder.setAllProperties(index, master); +            index.insert(); + +            Storable indexChecker = mRepository.storageFor(s).prepare(); +            builder.setAllProperties(indexChecker, master); +            assertTrue(indexChecker.tryLoad()); + +            StorableTestBasic masterChecker = builder.loadMaster(indexChecker); +            assertEquals(master, masterChecker); + +            assertTrue(builder.isConsistent(index, master)); +            masterChecker = +                mRepository.storageFor(StorableTestBasic.class).prepare(); +            master.copyAllProperties(masterChecker); +            assertTrue(builder.isConsistent(index, masterChecker)); +            masterChecker.setId(-42); +            assertFalse(builder.isConsistent(index, masterChecker)); + +        } +    } + +    /** +     * @param <S> +     * @param storableType +     * @throws SupportException +     * @throws RepositoryException +     * @throws IllegalAccessException +     * @throws InvocationTargetException +     * @throws PersistException +     * @throws FetchException +     */ +    protected <T extends Storable> void exerciseStorable(Class<T> storableType) throws SupportException, RepositoryException, IllegalAccessException, InvocationTargetException, PersistException, FetchException { +        T persister = mRepository.storageFor(storableType).prepare(); +        Map<String, Object> valueMap = populate(persister); + +        persister.insert(); +        T reader = mRepository.storageFor(storableType).prepare(); +        persister.copyPrimaryKeyProperties(reader); +        assertTrue(reader.tryLoad()); + +        for (Method method : storableType.getMethods()) { +            if (method.getName().startsWith("get") ) { +                Class returnType = method.getReturnType(); +                Class[] paramTypes = method.getParameterTypes(); +                if (VENDOR.hasValue(returnType) && paramTypes.length == 0) { +                    Object expectedValue = valueMap.get(method.getName().substring(3)); +                    Object actualValue = method.invoke(persister, (Object[]) null); +                    assertEquals(expectedValue, actualValue); +                } +            } +        } +    } + +    /** +     * Populates a storable with random values.  Any complex properties will be skipped. +     * @param storable to populate +     * @return map from property name to value set into it +     * @throws IllegalAccessException +     * @throws InvocationTargetException +     */ +    protected <T extends Storable> Map<String, Object> populate(T storable) +            throws IllegalAccessException, InvocationTargetException { +        ValueVendor vendor = new ValueVendor(); +        Map<String, Object> valueMap = new HashMap<String, Object>(); +        vendor.reset(); +        for (Method method : storable.getClass().getMethods()) { +            if (method.getName().startsWith("set") ) { +                Class[] paramTypes = method.getParameterTypes(); +                Class type = paramTypes[0]; +                if (vendor.hasValue(type)) { +                    Object value = vendor.getValue(type); +                    method.invoke(storable, new Object[] {value}); +                    valueMap.put(method.getName().substring(3), value); +                } +            } +        } +        return valueMap; +    } + + + + +    protected void validateIndexEntry(TestDef test, Class syntheticClass) { +        assertEquals(test.mClassName, syntheticClass.getName()); +        Map<String, String[]> expectedMethods = new HashMap<String, String[]>(); +        if (null != test.mMethods) { +            for (MethodDef m : test.mMethods) { +                expectedMethods.put(m.mName, m.mAnnotations); +            } +        } + +        Class iface = syntheticClass.getInterfaces()[0]; +        assertEquals(test.mTestMoniker, Storable.class, iface); + +        boolean missed = false; +        int storableIfaceMethodCount = 0; +        int methodCount = 0; +        for (Method m : syntheticClass.getMethods()) { +            if (s_storableInterfaceMethods.contains(m.getName())) { +                storableIfaceMethodCount++; +            } else { +                Set expectedAnnotations = null; +                if (expectedMethods.containsKey(m.getName())) { +                    expectedAnnotations = new HashSet(); +                    String[] expectedAnnotationArray = expectedMethods.get(m.getName()); +                    if (null != expectedAnnotationArray) { +                        for (String a : expectedAnnotationArray) { +                            expectedAnnotations.add(a); +                        } +                    } +                } else { +                    System.out.println("missed method " + methodCount +                            + "\nMethodDef(\"" + m.getName() + "\", null),"); +                    missed = true; +                } + +                if (expectedAnnotations != null) { +                    assertEquals(test.mTestMoniker + ": " + m.getName(), +                                 expectedAnnotations.size(), +                                 m.getDeclaredAnnotations().length); +                } else { +                    assertEquals(test.mTestMoniker + ": " + m.getName(), +                                 0, +                                 m.getDeclaredAnnotations().length); +                } + +                for (Annotation a : m.getDeclaredAnnotations()) { +                    if (missed) { +                        System.out.println("    " + a); +                    } +                    if (expectedAnnotations != null) { +                        if (!expectedAnnotations.contains(a.toString())) { +                            boolean found = false; +                            for (Object candidate : expectedAnnotations.toArray()) { +                                if (a.toString().startsWith((String) candidate)) { +                                    found = true; +                                    break; +                                } +                                // Special case to handle arbitrary ordering of +                                // annotation parameters. +                                if (candidate == TEXT && a.toString().startsWith(TEXT_2)) { +                                    found = true; +                                    break; +                                } +                            } +                            assertTrue(test.mTestMoniker + ':' + m.getName() +                                    + " -- unexpected annotation " + a, found); +                        } // else we're ok +                    } +                    ; +                } +            } +            methodCount++; +        } +        assertEquals(test.mTestMoniker, +                     s_storableInterfaceMethods.size(), +                     storableIfaceMethodCount); +        assertFalse(test.mTestMoniker, missed); +    } + +    static class MethodDef { +        String mName; + +        String[] mAnnotations; + +        public MethodDef(String name, String[] annotations) { +            mName = name; +            mAnnotations = annotations; +        } + +        /* +         * (non-Javadoc) +         * +         * @see java.lang.Object#toString() +         */ +        @Override +        public String toString() +        { +            String annotations = null; +            if (null != mAnnotations) { +                for (int i = 0; i < mAnnotations.length; i++) { +                    if (null == annotations) { +                        annotations = ""; +                    } else { +                        annotations += ", "; +                    } +                    annotations += mAnnotations[i]; +                } +            } else { +                annotations = " -- "; +            } +            return mName + " [" + annotations + ']'; +        } + +    } + +    /** +     * More intuitive mechanism for defining index properties; propClass is +     * optionally used for synthetic references (since we don't formally define +     * properties in this test environment) +     */ +    public static class IndexDef { +        private String mProp; + +        private Direction mDir; + +        public IndexDef(String prop, Direction dir) { +            mProp = prop; +            mDir = dir; +        } + +        public String getProp() { +            return mProp; +        } + +        public Direction getDir() { +            return mDir; +        } +    } + +    public static class TestDef { +        public String mTestMoniker; + +        public Class mClass; + +        public String mClassName; + +        public IndexDef[] mProps; + +        public MethodDef[] mMethods; + +        public SyntheticProperty[] mSynths; + +        public TestDef(String moniker, +                       Class aClass, +                       String aClassName, +                       IndexDef[] props, +                       MethodDef[] methods, +                       SyntheticProperty[] synths) { +            mTestMoniker = moniker; +            mClass = aClass; +            mClassName = aClassName; +            mProps = props; +            mMethods = methods; +            mSynths = synths; +        } + +        public String toString() { +            return mTestMoniker; +        } +    } + +    public interface ValueGetter { +        public void reset(); +        public Object getValue(); +    } + +    static class BoolGetter implements ValueGetter { +        final boolean start = false; +        boolean mNext = start; +        public void reset() { +            mNext = start; +        } +        public Boolean getValue() { +            mNext = !mNext; +            return mNext; +        } +    } + +    static class IntGetter implements ValueGetter { +        // hang onto the seed so we get a reproducible stream +        int mSeed; +        Random mNext; + +        IntGetter() { +            reset(); +        } + +        public  void reset() { +            Random seeder = new Random(); +            while (mSeed == 0) { +                mSeed = seeder.nextInt(); +            } +            mNext = new Random(mSeed); +        } + +        public Integer getValue() { +            return mNext.nextInt(); +        } + +        public Integer getPositiveValue() { +            return Math.abs(mNext.nextInt()); +        } +    } + +    static class LongGetter implements ValueGetter { +        final long start = 56789; +        long mNext = start; +        public void reset() { +            mNext = start; +        } +        public Long getValue() { +            return mNext++; +        } +    } + +    static class FloatGetter implements ValueGetter { +        final float start = (float) Math.PI; +        float mNext = start; +        public void reset() { +            mNext = start; +        } +        public Float getValue() { +            float next = mNext; +            mNext = next + 0.1f; +            return next; +        } +    } + +    static class DoubleGetter implements ValueGetter { +        final double start = Math.PI; +        double mNext = start; +        public void reset() { +            mNext = start; +        } +        public Double getValue() { +            double next = mNext; +            mNext = next + 0.1; +            return next; +        } +    } + +    static class CharGetter implements ValueGetter { + +        static final String source = "zookeepers fly piglatin homogeneous travesty"; + +        IntGetter mNext = new IntGetter(); + +        public  void reset() { +            mNext.reset(); +        } + + +        public Character getValue() { +            return source.charAt(mNext.getPositiveValue() % source.length()); +        } +    } + +    static class StringGetter implements ValueGetter { +        IntGetter mSizer = new IntGetter(); +        CharGetter mNext = new CharGetter(); + +        public  void reset() { +            mSizer.reset(); +            mNext.reset(); +        } +        public String getValue() { +            int size = mSizer.getPositiveValue() % 255; +            StringBuilder sb = new StringBuilder(size); +            for (int i = 0; i<size; i++) { +                sb.append(mNext.getValue()); +            } +            return sb.toString(); +        } +    } + +    static class DateTimeGetter implements ValueGetter { +        static final DateTime start = new DateTime("2005-01-02"); +        DateTime mNext = start; +        public void reset() { +            mNext = start; +        } +        public DateTime getValue() { +            DateTime next = mNext; +            mNext = mNext.dayOfYear().addToCopy(1); +            return next; +        } +    } + +    static class ValueVendor { +        Map<Class, ValueGetter> getters = new HashMap(10); +        ValueVendor() { +            getters.put(String.class, new StringGetter()); +            getters.put(DateTime.class, new DateTimeGetter()); + +            getters.put(Boolean.class, new BoolGetter()); +            getters.put(Integer.class, new IntGetter()); +            getters.put(Long.class, new LongGetter()); +            getters.put(Float.class, new FloatGetter()); +            getters.put(Double.class, new DoubleGetter()); +            getters.put(Character.class, new CharGetter()); + +            getters.put(boolean.class, new BoolGetter()); +            getters.put(int.class, new IntGetter()); +            getters.put(long.class, new LongGetter()); +            getters.put(float.class, new FloatGetter()); +            getters.put(double.class, new DoubleGetter()); +            getters.put(char.class, new CharGetter()); +        } + +        void reset() { +            for (Iterator iter = getters.values().iterator(); iter.hasNext();) { +                ValueGetter getter = (ValueGetter) iter.next(); +                getter.reset(); +            } +        } + +        boolean hasValue(Class type) { +            return getters.containsKey(type); +        } + + +        Object getValue(Class type) { +            return getters.get(type).getValue(); +        } +    } +} diff --git a/src/test/java/com/amazon/carbonado/util/TestBelatedCreator.java b/src/test/java/com/amazon/carbonado/util/TestBelatedCreator.java new file mode 100644 index 0000000..99e927f --- /dev/null +++ b/src/test/java/com/amazon/carbonado/util/TestBelatedCreator.java @@ -0,0 +1,244 @@ +/*
 + * 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.util;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestBelatedCreator extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestBelatedCreator.class);
 +    }
 +
 +    public TestBelatedCreator(String name) {
 +        super(name);
 +    }
 +
 +    public void test_noProblems() {
 +        Creator c = new Creator(0, 0, false);
 +        TheObject obj = c.get(1000);
 +        assertTrue(obj.isReal());
 +        assertEquals("real", obj.toString());
 +
 +        obj.doSomething();
 +        assertEquals(100, obj.getValue());
 +        assertEquals("12a", obj.doSomething(1, 2, "a"));
 +
 +        assertEquals(0, c.mTimedOutCount);
 +        assertEquals(1, c.mCreatedCount);
 +    }
 +
 +    public void test_immediateFailure() {
 +        Creator c = new Creator(60000, 0, true);
 +        TheObject obj = c.get(1000);
 +        assertFalse(obj.isReal());
 +        assertEquals("bogus", obj.toString());
 +
 +        try {
 +            obj.doSomething();
 +            fail();
 +        } catch (RuntimeException e) {
 +        }
 +        assertEquals(-1, obj.getValue());
 +        assertNull(obj.doSomething(1, 2, "a"));
 +
 +        assertEquals(0, c.mTimedOutCount);
 +        assertEquals(0, c.mCreatedCount);
 +
 +        assertTrue(obj == c.get(1000));
 +        assertTrue(obj.equals(c.get(1000)));
 +    }
 +
 +    public void test_longDelay() {
 +        Creator c = new Creator(60000, 5000, false);
 +
 +        long start, end;
 +        TheObject obj;
 +
 +        start = System.nanoTime();
 +        obj = c.get(1000);
 +        end = System.nanoTime();
 +        assertFalse(obj.isReal());
 +        assertEquals("bogus", obj.toString());
 +        assertTrue((end - start) >= 1000L * 1000000);
 +
 +        start = System.nanoTime();
 +        obj = c.get(1000);
 +        end = System.nanoTime();
 +        assertFalse(obj.isReal());
 +        assertEquals("bogus", obj.toString());
 +        assertTrue((end - start) >= 1000L * 1000000);
 +
 +        assertEquals(-1, obj.getValue());
 +        assertNull(obj.doSomething(1, 2, "a"));
 +
 +        assertEquals(2, c.mTimedOutCount);
 +        assertEquals(0, c.mCreatedCount);
 +
 +        start = System.nanoTime();
 +        TheObject obj2 = c.get(5000);
 +        end = System.nanoTime();
 +        assertTrue(obj2.isReal());
 +        assertEquals("real", obj.toString());
 +        assertTrue((end - start) <= 5000L * 1000000);
 +
 +        assertFalse(obj == obj2);
 +        assertTrue(obj.isReal());
 +        assertEquals("real", obj.toString());
 +
 +        assertEquals(100, obj.getValue());
 +        assertEquals("12a", obj.doSomething(1, 2, "a"));
 +        assertEquals(100, obj2.getValue());
 +        assertEquals("23b", obj2.doSomething(2, 3, "b"));
 +
 +        assertEquals(2, c.mTimedOutCount);
 +        assertEquals(1, c.mCreatedCount);
 +
 +        start = System.nanoTime();
 +        TheObject obj3 = c.get(1000);
 +        end = System.nanoTime();
 +        assertTrue(obj3.isReal());
 +        assertEquals("real", obj3.toString());
 +        assertTrue((end - start) <= 1000L * 1000000);
 +
 +        assertTrue(obj2 == obj3);
 +        assertEquals(2, c.mTimedOutCount);
 +        assertEquals(1, c.mCreatedCount);
 +    }
 +
 +    public void test_retry() throws Exception {
 +        Creator c = new Creator(5000, 0, true);
 +        TheObject obj = c.get(1000);
 +        assertFalse(obj.isReal());
 +        assertEquals("bogus", obj.toString());
 +
 +        Thread.sleep(6000);
 +
 +        assertTrue(obj.isReal());
 +        assertEquals("real", obj.toString());
 +        obj = c.get(1000);
 +        assertTrue(obj.isReal());
 +        assertEquals("real", obj.toString());
 +    }
 +
 +    public interface TheObject {
 +        public boolean isReal();
 +
 +        public void doSomething();
 +
 +        public int getValue();
 +
 +        public String doSomething(int a, long b, Object q);
 +
 +        public String toString();
 +    }
 +
 +    private class Creator extends BelatedCreator<TheObject, RuntimeException> {
 +        final int mCreateDelay;
 +        boolean mFailOnce;
 +
 +        int mTimedOutCount;
 +        int mCreatedCount;
 +
 +        Creator(int retryMillis, int createDelay, boolean failOnce) {
 +            super(TheObject.class, retryMillis);
 +            mCreateDelay = createDelay;
 +            mFailOnce = failOnce;
 +        }
 +
 +        protected TheObject createReal() {
 +            assertTrue(mCreatedCount == 0);
 +            try {
 +                Thread.sleep(mCreateDelay);
 +            } catch (InterruptedException e) {
 +            }
 +
 +            if (mFailOnce) {
 +                mFailOnce = false;
 +                return null;
 +            }
 +
 +            return new TheObject() {
 +                public boolean isReal() {
 +                    return true;
 +                }
 +
 +                public void doSomething() {
 +                }
 +
 +                public int getValue() {
 +                    return 100;
 +                }
 +
 +                public String doSomething(int a, long b, Object q) {
 +                    return "" + a + b + q;
 +                }
 +
 +                public String toString() {
 +                    return "real";
 +                }
 +            };
 +        }
 +
 +        protected TheObject createBogus() {
 +            return new TheObject() {
 +                public boolean isReal() {
 +                    return false;
 +                }
 +
 +                public void doSomething() {
 +                    throw new RuntimeException();
 +                }
 +
 +                public int getValue() {
 +                    return -1;
 +                }
 +
 +                public String doSomething(int a, long b, Object q) {
 +                    return null;
 +                }
 +
 +                public String toString() {
 +                    return "bogus";
 +                }
 +            };
 +        }
 +
 +        protected void timedOutNotification(long timedOutMillis) {
 +            assertTrue(timedOutMillis >= 0);
 +            mTimedOutCount++;
 +        }
 +
 +        @Override
 +        protected void createdNotification(TheObject object) {
 +            assertNotNull(object);
 +            mCreatedCount++;
 +        }
 +    }
 +}
 +
 diff --git a/src/test/java/com/amazon/carbonado/util/TestQuickConstructorGenerator.java b/src/test/java/com/amazon/carbonado/util/TestQuickConstructorGenerator.java new file mode 100644 index 0000000..c9ba833 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/util/TestQuickConstructorGenerator.java @@ -0,0 +1,102 @@ +/*
 + * 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.util;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestQuickConstructorGenerator extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestQuickConstructorGenerator.class);
 +    }
 +
 +    public TestQuickConstructorGenerator(String name) {
 +        super(name);
 +    }
 +
 +    public void testStringMaker() throws Exception {
 +        StringMaker maker = QuickConstructorGenerator.getInstance(String.class, StringMaker.class);
 +        assertEquals("", maker.newEmptyString());
 +        assertEquals("hello", maker.newStringFromChars(new char[] {'h', 'e', 'l', 'l', 'o'}));
 +        assertEquals("hello",
 +                     maker.newStringFromBytes(new byte[] {'h', 'e', 'l', 'l', 'o'}, "US-ASCII"));
 +    }
 +
 +    public void testIllegalArgs() {
 +        try {
 +            QuickConstructorGenerator.getInstance(String.class, String.class);
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +
 +        try {
 +            QuickConstructorGenerator.getInstance(String.class, BadStringMaker.class);
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +
 +        try {
 +            QuickConstructorGenerator.getInstance(String.class, BadStringMaker2.class);
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +
 +        try {
 +            QuickConstructorGenerator.getInstance(String.class, BadStringMaker3.class);
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +
 +        try {
 +            QuickConstructorGenerator.getInstance(byte[].class, StringMaker.class);
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +    }
 +
 +    public static interface StringMaker {
 +        Object newEmptyString();
 +
 +        String newStringFromChars(char[] chars);
 +
 +        String newStringFromBytes(byte[] bytes, String charsetName)
 +            throws java.io.UnsupportedEncodingException;
 +    }
 +
 +    public static interface BadStringMaker {
 +        String newStringFromBytes(byte[] bytes, String charsetName);
 +    }
 +
 +    public static interface BadStringMaker2 {
 +        String newStringFromClass(Class clazz);
 +    }
 +
 +    public static interface BadStringMaker3 {
 +        Class newEmptyString();
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/util/TestThrowUnchecked.java b/src/test/java/com/amazon/carbonado/util/TestThrowUnchecked.java new file mode 100644 index 0000000..739a709 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/util/TestThrowUnchecked.java @@ -0,0 +1,54 @@ +/*
 + * 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.util;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestThrowUnchecked extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestThrowUnchecked.class);
 +    }
 +
 +    public TestThrowUnchecked(String name) {
 +        super(name);
 +    }
 +
 +    public void test() {
 +        ThrowUnchecked.fire(null);
 +
 +        Exception e = new java.io.IOException();
 +
 +        try {
 +            ThrowUnchecked.fire(e);
 +            fail();
 +        } catch (Exception e2) {
 +            assertEquals(e, e2);
 +        }
 +    }
 +}
  | 
