/** * Copies outer join data from a source MultiJoinRel to a new set of arrays. Also adjusts the * conditions to reflect the new position of an input if that input ends up being shifted to the * right. * * @param multiJoinRel the source MultiJoinRel * @param destConds the array where the join conditions will be copied * @param destJoinTypes the array where the join types will be copied * @param destPos starting position in the array where the copying starts * @param adjustmentAmount if > 0, the amount the RexInputRefs in the join conditions need to be * adjusted by * @param srcFields the source fields that the original join conditions are referencing * @param destFields the destination fields that the new join conditions will be referencing */ private void copyOuterJoinInfo( MultiJoinRel multiJoinRel, RexNode[] destConds, JoinRelType[] destJoinTypes, int destPos, int adjustmentAmount, RelDataTypeField[] srcFields, RelDataTypeField[] destFields) { RexNode[] srcConds = multiJoinRel.getOuterJoinConditions(); JoinRelType[] srcJoinTypes = multiJoinRel.getJoinTypes(); RexBuilder rexBuilder = multiJoinRel.getCluster().getRexBuilder(); int len = srcConds.length; System.arraycopy(srcJoinTypes, 0, destJoinTypes, destPos, len); if (adjustmentAmount == 0) { System.arraycopy(srcConds, 0, destConds, 0, len); } else { int nFields = srcFields.length; int[] adjustments = new int[nFields]; for (int idx = 0; idx < nFields; idx++) { adjustments[idx] = adjustmentAmount; } for (int i = 0; i < len; i++) { if (srcConds[i] != null) { destConds[i + destPos] = srcConds[i].accept( new RelOptUtil.RexInputConverter(rexBuilder, srcFields, destFields, adjustments)); } } } }
/** * SqlToRelTestBase is an abstract base for tests which involve conversion from SQL to relational * algebra. * * <p>SQL statements to be translated can use the schema defined in {@link MockCatalogReader}; note * that this is slightly different from Farrago's SALES schema. If you get a parser or validator * error from your test SQL, look down in the stack until you see "Caused by", which will usually * tell you the real error. */ public abstract class SqlToRelTestBase { // ~ Static fields/initializers --------------------------------------------- protected static final String NL = System.getProperty("line.separator"); // ~ Instance fields -------------------------------------------------------- protected final Tester tester = createTester(); // ~ Methods ---------------------------------------------------------------- public SqlToRelTestBase() { super(); } protected Tester createTester() { return new TesterImpl(getDiffRepos()); } /** * Returns the default diff repository for this test, or null if there is no repository. * * <p>The default implementation returns null. * * <p>Sub-classes that want to use a diff repository can override. Sub-sub-classes can override * again, inheriting test cases and overriding selected test results. * * <p>And individual test cases can override by providing a different tester object. * * @return Diff repository */ protected DiffRepository getDiffRepos() { return null; } // ~ Inner Interfaces ------------------------------------------------------- /** * Helper class which contains default implementations of methods used for running sql-to-rel * conversion tests. */ public interface Tester { /** * Converts a SQL string to a {@link RelNode} tree. * * @param sql SQL statement * @return Relational expression, never null */ RelNode convertSqlToRel(String sql); SqlNode parseQuery(String sql) throws Exception; /** Factory method to create a {@link SqlValidator}. */ SqlValidator createValidator( SqlValidatorCatalogReader catalogReader, RelDataTypeFactory typeFactory); /** Factory method for a {@link net.hydromatic.optiq.prepare.Prepare.CatalogReader}. */ Prepare.CatalogReader createCatalogReader(RelDataTypeFactory typeFactory); RelOptPlanner createPlanner(); /** Returns the {@link SqlOperatorTable} to use. */ SqlOperatorTable getOperatorTable(); /** Returns the SQL dialect to test. */ SqlConformance getConformance(); /** * Checks that a SQL statement converts to a given plan. * * @param sql SQL query * @param plan Expected plan */ void assertConvertsTo(String sql, String plan); /** * Checks that a SQL statement converts to a given plan, optionally trimming columns that are * not needed. * * @param sql SQL query * @param plan Expected plan */ void assertConvertsTo(String sql, String plan, boolean trim); /** * Returns the diff repository. * * @return Diff repository */ DiffRepository getDiffRepos(); /** * Returns the validator. * * @return Validator */ SqlValidator getValidator(); } // ~ Inner Classes ---------------------------------------------------------- /** Mock implementation of {@link RelOptSchema}. */ protected static class MockRelOptSchema implements RelOptSchemaWithSampling { private final SqlValidatorCatalogReader catalogReader; private final RelDataTypeFactory typeFactory; public MockRelOptSchema( SqlValidatorCatalogReader catalogReader, RelDataTypeFactory typeFactory) { this.catalogReader = catalogReader; this.typeFactory = typeFactory; } public RelOptTable getTableForMember(List<String> names) { final SqlValidatorTable table = catalogReader.getTable(names); final RelDataType rowType = table.getRowType(); final List<RelCollation> collationList = deduceMonotonicity(table); if (names.size() < 3) { String[] newNames2 = {"CATALOG", "SALES", ""}; List<String> newNames = new ArrayList<String>(); int i = 0; while (newNames.size() < newNames2.length) { newNames.add(i, newNames2[i]); ++i; } names = newNames; } return createColumnSet(table, names, rowType, collationList); } private List<RelCollation> deduceMonotonicity(SqlValidatorTable table) { final RelDataType rowType = table.getRowType(); final List<RelCollation> collationList = new ArrayList<RelCollation>(); // Deduce which fields the table is sorted on. int i = -1; for (RelDataTypeField field : rowType.getFieldList()) { ++i; final SqlMonotonicity monotonicity = table.getMonotonicity(field.getName()); if (monotonicity != SqlMonotonicity.NOT_MONOTONIC) { final RelFieldCollation.Direction direction = monotonicity.isDecreasing() ? RelFieldCollation.Direction.DESCENDING : RelFieldCollation.Direction.ASCENDING; collationList.add( RelCollationImpl.of( new RelFieldCollation( i, direction, RelFieldCollation.NullDirection.UNSPECIFIED))); } } return collationList; } public RelOptTable getTableForMember( List<String> names, final String datasetName, boolean[] usedDataset) { final RelOptTable table = getTableForMember(names); // If they're asking for a sample, just for test purposes, // assume there's a table called "<table>:<sample>". RelOptTable datasetTable = new DelegatingRelOptTable(table) { public List<String> getQualifiedName() { final List<String> list = new ArrayList<String>(super.getQualifiedName()); list.set(list.size() - 1, list.get(list.size() - 1) + ":" + datasetName); return ImmutableList.copyOf(list); } }; if (usedDataset != null) { assert usedDataset.length == 1; usedDataset[0] = true; } return datasetTable; } protected MockColumnSet createColumnSet( SqlValidatorTable table, List<String> names, final RelDataType rowType, final List<RelCollation> collationList) { return new MockColumnSet(names, rowType, collationList); } public RelDataTypeFactory getTypeFactory() { return typeFactory; } public void registerRules(RelOptPlanner planner) throws Exception {} protected class MockColumnSet implements RelOptTable { private final List<String> names; private final RelDataType rowType; private final List<RelCollation> collationList; protected MockColumnSet( List<String> names, RelDataType rowType, final List<RelCollation> collationList) { this.names = ImmutableList.copyOf(names); this.rowType = rowType; this.collationList = collationList; } public <T> T unwrap(Class<T> clazz) { if (clazz.isInstance(this)) { return clazz.cast(this); } return null; } public List<String> getQualifiedName() { return names; } public double getRowCount() { // use something other than 0 to give costing tests // some room, and make emps bigger than depts for // join asymmetry if (Iterables.getLast(names).equals("EMP")) { return 1000; } else { return 100; } } public RelDataType getRowType() { return rowType; } public RelOptSchema getRelOptSchema() { return MockRelOptSchema.this; } public RelNode toRel(ToRelContext context) { return new TableAccessRel(context.getCluster(), this); } public List<RelCollation> getCollationList() { return collationList; } public boolean isKey(BitSet columns) { return false; } public Expression getExpression(Class clazz) { return null; } } } private static class DelegatingRelOptTable implements RelOptTable { private final RelOptTable parent; public DelegatingRelOptTable(RelOptTable parent) { this.parent = parent; } public <T> T unwrap(Class<T> clazz) { if (clazz.isInstance(this)) { return clazz.cast(this); } return parent.unwrap(clazz); } public Expression getExpression(Class clazz) { return parent.getExpression(clazz); } public List<String> getQualifiedName() { return parent.getQualifiedName(); } public double getRowCount() { return parent.getRowCount(); } public RelDataType getRowType() { return parent.getRowType(); } public RelOptSchema getRelOptSchema() { return parent.getRelOptSchema(); } public RelNode toRel(ToRelContext context) { return new TableAccessRel(context.getCluster(), this); } public List<RelCollation> getCollationList() { return parent.getCollationList(); } public boolean isKey(BitSet columns) { return parent.isKey(columns); } } /** * Default implementation of {@link Tester}, using mock classes {@link MockRelOptSchema} and * {@link MockRelOptPlanner}. */ public static class TesterImpl implements Tester { private RelOptPlanner planner; private SqlOperatorTable opTab; private final DiffRepository diffRepos; private RelDataTypeFactory typeFactory; /** * Creates a TesterImpl. * * @param diffRepos Diff repository */ protected TesterImpl(DiffRepository diffRepos) { this.diffRepos = diffRepos; } public RelNode convertSqlToRel(String sql) { Util.pre(sql != null, "sql != null"); final SqlNode sqlQuery; try { sqlQuery = parseQuery(sql); } catch (Exception e) { throw Util.newInternal(e); // todo: better handling } final RelDataTypeFactory typeFactory = getTypeFactory(); final Prepare.CatalogReader catalogReader = createCatalogReader(typeFactory); final SqlValidator validator = createValidator(catalogReader, typeFactory); final SqlToRelConverter converter = createSqlToRelConverter(validator, catalogReader, typeFactory); converter.setTrimUnusedFields(true); final SqlNode validatedQuery = validator.validate(sqlQuery); final RelNode rel = converter.convertQuery(validatedQuery, false, true); Util.post(rel != null, "return != null"); return rel; } protected SqlToRelConverter createSqlToRelConverter( final SqlValidator validator, final Prepare.CatalogReader catalogReader, final RelDataTypeFactory typeFactory) { return new SqlToRelConverter( null, validator, catalogReader, getPlanner(), new RexBuilder(typeFactory), StandardConvertletTable.INSTANCE); } protected final RelDataTypeFactory getTypeFactory() { if (typeFactory == null) { typeFactory = createTypeFactory(); } return typeFactory; } protected RelDataTypeFactory createTypeFactory() { return new SqlTypeFactoryImpl(); } protected final RelOptPlanner getPlanner() { if (planner == null) { planner = createPlanner(); } return planner; } public SqlNode parseQuery(String sql) throws Exception { SqlParser parser = SqlParser.create(sql); SqlNode sqlNode = parser.parseQuery(); return sqlNode; } public SqlConformance getConformance() { return SqlConformance.DEFAULT; } public SqlValidator createValidator( SqlValidatorCatalogReader catalogReader, RelDataTypeFactory typeFactory) { boolean caseSensitive = true; return new FarragoTestValidator( getOperatorTable(), new MockCatalogReader(typeFactory, caseSensitive), typeFactory, getConformance()); } public final SqlOperatorTable getOperatorTable() { if (opTab == null) { opTab = createOperatorTable(); } return opTab; } /** * Creates an operator table. * * @return New operator table */ protected SqlOperatorTable createOperatorTable() { final MockSqlOperatorTable opTab = new MockSqlOperatorTable(SqlStdOperatorTable.instance()); MockSqlOperatorTable.addRamp(opTab); return opTab; } public Prepare.CatalogReader createCatalogReader(RelDataTypeFactory typeFactory) { boolean caseSensitive = true; return new MockCatalogReader(typeFactory, caseSensitive); } public RelOptPlanner createPlanner() { return new MockRelOptPlanner(); } public void assertConvertsTo(String sql, String plan) { assertConvertsTo(sql, plan, false); } public void assertConvertsTo(String sql, String plan, boolean trim) { String sql2 = getDiffRepos().expand("sql", sql); RelNode rel = convertSqlToRel(sql2); assertTrue(rel != null); assertValid(rel); if (trim) { final RelFieldTrimmer trimmer = createFieldTrimmer(); rel = trimmer.trim(rel); assertTrue(rel != null); assertValid(rel); } // NOTE jvs 28-Mar-2006: insert leading newline so // that plans come out nicely stacked instead of first // line immediately after CDATA start String actual = NL + RelOptUtil.toString(rel); diffRepos.assertEquals("plan", plan, actual); } /** * Creates a RelFieldTrimmer. * * @return Field trimmer */ public RelFieldTrimmer createFieldTrimmer() { return new RelFieldTrimmer(getValidator()); } /** * Checks that every node of a relational expression is valid. * * @param rel Relational expression */ protected void assertValid(RelNode rel) { SqlToRelConverterTest.RelValidityChecker checker = new SqlToRelConverterTest.RelValidityChecker(); checker.go(rel); assertEquals(0, checker.invalidCount); } public DiffRepository getDiffRepos() { return diffRepos; } public SqlValidator getValidator() { final RelDataTypeFactory typeFactory = getTypeFactory(); final SqlValidatorCatalogReader catalogReader = createCatalogReader(typeFactory); return createValidator(catalogReader, typeFactory); } } private static class FarragoTestValidator extends SqlValidatorImpl { public FarragoTestValidator( SqlOperatorTable opTab, SqlValidatorCatalogReader catalogReader, RelDataTypeFactory typeFactory, SqlConformance conformance) { super(opTab, catalogReader, typeFactory, conformance); } // override SqlValidator public boolean shouldExpandIdentifiers() { return true; } } }