/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.test.it.qp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Random; import java.util.Set; import com.foundationdb.junit.SelectedParameterizedRunner; import com.foundationdb.sql.embedded.EmbeddedJDBCITBase; import com.foundationdb.util.RandomRule; import com.foundationdb.util.Strings; import com.google.common.collect.Sets; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.ComparisonFailure; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.Parameterized.Parameters; @RunWith(SelectedParameterizedRunner.class) public class IndexScanUnboundedMixedOrderDT extends EmbeddedJDBCITBase { protected static final String TABLE_NAME ="t"; protected static final String INDEX_NAME = "idx"; protected static final List<String> COLUMNS = Arrays.asList("t0", "t1", "t2", "t3"); protected static final Integer TOTAL_ROWS = 100; protected static final Integer TOTAL_COLS = COLUMNS.size(); static final Integer MAX_VALUE = 100; static final Integer MIN_VALUE = 0; @SuppressWarnings("unchecked") static final Comparator ASC_COMPARATOR = new Comparator() { @Override public int compare(Object o1, Object o2) { if(o1 == null) { return (o2 == null) ? 0 : -1; } if(o2 == null) { return 1; } return ((Comparable)o1).compareTo(o2); } }; @SuppressWarnings("unchecked") static final Comparator DESC_COMPARATOR = new Comparator() { @Override public int compare(Object o1, Object o2) { return - ASC_COMPARATOR.compare(o1, o2); } }; static enum OrderByOptions { NONE, ASC, DESC ; @SuppressWarnings("unchecked") public <T> Comparator<T> getComparator(Class<T> clazz) { switch(this) { case NONE: return null; case ASC: return ASC_COMPARATOR; case DESC: return DESC_COMPARATOR; default: throw new IllegalStateException(this.name()); } } public String getOrderingString() { switch(this) { case NONE: return null; default: return name(); } } } static class IndexComparison<T> { private final int index; private final Comparator<T> comp; public IndexComparison(int index, Comparator<T> comp) { this.index = index; this.comp = comp; } } static class ListComparator<T> implements Comparator<List<T>> { public final List<IndexComparison<T>> comps; public ListComparator(List<IndexComparison<T>> comps) { this.comps = comps; } @Override public int compare(List<T> a, List<T> b) { assert a.size() == b.size(); for(IndexComparison<T> c : comps) { int i = c.index; int r = c.comp.compare(a.get(i), b.get(i)); if(r != 0) { return r; } } return 0; } } @ClassRule public static final RandomRule classRandom = new RandomRule(); @Rule public final RandomRule randomRule = classRandom; protected final List<OrderByOptions> orderings; protected final List<List<Integer>> DB; protected final ListComparator<Integer> rowComparator; protected String query; public IndexScanUnboundedMixedOrderDT(String name, List<OrderByOptions> orderings) { this.orderings = orderings; this.rowComparator = buildListComparator(orderings); this.DB = buildDB(randomRule.getRandom()); } private static ListComparator<Integer> buildListComparator(List<OrderByOptions> orderings) { List<IndexComparison<Integer>> comps = new ArrayList<>(); for(int i = 0; i < orderings.size(); ++i) { Comparator<Integer> comp = orderings.get(i).getComparator(Integer.class); if(comp != null) { comps.add(new IndexComparison<>(i, comp)); } } return new ListComparator<>(comps); } private static List<List<Integer>> buildDB(Random r) { List<List<Integer>> db = new ArrayList<>(); for (int i = 0; i < TOTAL_ROWS; i++) { List<Integer> row = new ArrayList<>(); for (int j = 0; j < TOTAL_COLS; j++) { int next = r.nextInt(MAX_VALUE + 10); row.add(next > MAX_VALUE ? null : next); } db.add(row); } return db; } @Before public void setup() { sql("CREATE TABLE " + TABLE_NAME + "(id SERIAL PRIMARY KEY, t0 INT, t1 INT, t2 INT, t3 INT)"); sql("CREATE INDEX " + INDEX_NAME + " ON t(t0, t1, t2, t3)"); StringBuilder sb = new StringBuilder("INSERT INTO " + TABLE_NAME + "(t0,t1,t2,t3) VALUES "); for(int i = 0; i < DB.size(); ++i) { if(i > 0) { sb.append(", "); } sb.append('(').append(Strings.join(DB.get(i), ",")).append(')'); } sql(sb.toString()); } @Test public void testQuery() { this.query = buildQuery(); List<List<?>> results = sql(query); compare(expectedRows(), results); } @SuppressWarnings("unchecked") protected void compare(List<List<Integer>> expectedResults, List<List<?>> results) { int eSize = expectedResults.size(); int aSize = results.size(); boolean match = true; for(int i = 0; match && i < Math.min(eSize, aSize); ++i) { match = rowComparator.compare(expectedResults.get(i), (List<Integer>)results.get(i)) == 0; } if(!match || (eSize != aSize)) { throw new ComparisonFailure("row mismatch", Strings.join(expectedResults), Strings.join(results)); } } protected String buildQuery() { StringBuilder sb = new StringBuilder(); sb.append("SELECT "); for(int i = 0; i < TOTAL_COLS; ++i) { if(i > 0) { sb.append(", "); } sb.append(COLUMNS.get(i)); } sb.append(" FROM "); sb.append(TABLE_NAME); boolean first = true; for(int i = 0; i < orderings.size(); i++) { String oStr = orderings.get(i).getOrderingString(); if(oStr != null) { if(first) { first = false; sb.append(" ORDER BY "); } else { sb.append(", "); } sb.append(COLUMNS.get(i)); sb.append(" "); sb.append(oStr); } } return sb.toString(); } protected List<List<Integer>> expectedRows() { List<List<Integer>> sorted = new ArrayList<>(DB); Collections.sort(sorted, rowComparator); return sorted; } public static Collection<List<OrderByOptions>> orderByPermutations() { List<Set<OrderByOptions>> optSets = new ArrayList<>(); for(int i = 0; i < TOTAL_COLS; ++i) { optSets.add(EnumSet.allOf(OrderByOptions.class)); } return Sets.cartesianProduct(optSets); } @Parameters(name="{0}") public static List<Object[]> params() throws Exception { List<Object[]> params = new ArrayList<>(); for(List<OrderByOptions> p : orderByPermutations()) { String name = makeTestName(p); params.add(new Object[]{ name, p }); } return params; } protected static String makeTestName(List<OrderByOptions> orderings) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < orderings.size(); ++i) { String oStr = orderings.get(i).getOrderingString(); if(oStr != null) { if(sb.length() > 0) { sb.append('_'); } sb.append(COLUMNS.get(i)); sb.append('_').append(oStr); } } if(sb.length() == 0) { sb.append("no_order"); } return sb.toString(); } }