/* * Licensed to Crate under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. Crate licenses this file * to you 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial * agreement. */ package io.crate.analyze.symbol.format; import com.google.common.collect.ImmutableMap; import io.crate.analyze.relations.AbstractTableRelation; import io.crate.analyze.relations.AnalyzedRelation; import io.crate.analyze.relations.TableRelation; import io.crate.analyze.symbol.*; import io.crate.metadata.*; import io.crate.metadata.doc.DocSchemaInfo; import io.crate.metadata.doc.DocTableInfo; import io.crate.metadata.table.TestingTableInfo; import io.crate.sql.tree.QualifiedName; import io.crate.test.integration.CrateUnitTest; import io.crate.testing.SqlExpressions; import io.crate.types.ArrayType; import io.crate.types.DataTypes; import org.apache.lucene.util.BytesRef; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import static org.hamcrest.Matchers.is; public class SymbolPrinterTest extends CrateUnitTest { private SqlExpressions sqlExpressions; private SymbolPrinter printer; private static final String TABLE_NAME = "formatter"; @Before public void prepare() throws Exception { DocTableInfo tableInfo = TestingTableInfo.builder(new TableIdent(DocSchemaInfo.NAME, TABLE_NAME), null) .add("foo", DataTypes.STRING) .add("bar", DataTypes.LONG) .add("CraZy", DataTypes.IP) .add("select", DataTypes.BYTE) .add("idx", DataTypes.INTEGER) .add("s_arr", new ArrayType(DataTypes.STRING)) .build(); Map<QualifiedName, AnalyzedRelation> sources = ImmutableMap.<QualifiedName, AnalyzedRelation>builder() .put(QualifiedName.of(TABLE_NAME), new TableRelation(tableInfo)) .build(); sqlExpressions = new SqlExpressions(sources); printer = new SymbolPrinter(sqlExpressions.functions()); } private void assertPrint(Symbol s, String formatted) { assertThat(printer.printFullQualified(s), is(formatted)); } private void assertPrintStatic(Symbol s, String formatted) { assertThat(SymbolPrinter.INSTANCE.printFullQualified(s), is(formatted)); } private void assertPrintIsParseable(String sql) { assertPrintIsParseable(sql, SymbolPrinter.Style.SIMPLE); } private void assertPrintIsParseable(String sql, SymbolPrinter.Style style) { Symbol symbol = sqlExpressions.asSymbol(sql); String formatted = printer.print(symbol, style); Symbol formattedSymbol = sqlExpressions.asSymbol(formatted); assertThat(symbol, is(formattedSymbol)); } private void assertPrintingRoundTrip(String sql, String expected) { Symbol sym = sqlExpressions.asSymbol(sql); assertPrint(sym, expected); assertPrintStatic(sym, expected); assertPrintIsParseable(sql); } @Test public void testFormatFunctionFullQualifiedInputName() throws Exception { Symbol f = sqlExpressions.asSymbol("concat('foo', foo)"); assertPrint(f, "concat('foo', doc.formatter.foo)"); } @Test public void testFormatFunctionWithoutBrackets() throws Exception { Symbol f = sqlExpressions.asSymbol("current_schema"); assertPrint(f, "current_schema"); } @Test public void testSubstrFunction() throws Exception { Symbol f = sqlExpressions.asSymbol("substr('foobar', 4)"); assertPrint(f, "substr('foobar', 4)"); } @Test public void testSubstrFunctionWithLength() throws Exception { Symbol f = sqlExpressions.asSymbol("substr('foobar', 4, 1)"); assertPrint(f, "substr('foobar', 4, 1)"); } @Test public void testFormatAggregation() throws Exception { FunctionInfo functionInfo = new FunctionInfo( new FunctionIdent("agg", Collections.singletonList(DataTypes.INTEGER)), DataTypes.LONG, FunctionInfo.Type.AGGREGATE ); Aggregation a = new Aggregation( functionInfo, DataTypes.LONG, Collections.singletonList(Literal.of(-127))); assertPrint(a, "agg(-127)"); } @Test public void testReference() throws Exception { Reference r = new Reference(new ReferenceIdent( new TableIdent("sys", "table"), new ColumnIdent("column", Arrays.asList("path", "nested"))), RowGranularity.DOC, DataTypes.STRING); assertPrint(r, "sys.\"table\".\"column\"['path']['nested']"); } @Test public void testDocReference() throws Exception { Reference r = new Reference(new ReferenceIdent( new TableIdent("doc", "table"), new ColumnIdent("column", Arrays.asList("path", "nested"))), RowGranularity.DOC, DataTypes.STRING); assertPrint(r, "doc.\"table\".\"column\"['path']['nested']"); } @Test public void testDynamicReference() throws Exception { Reference r = new DynamicReference( new ReferenceIdent(new TableIdent("schema", "table"), new ColumnIdent("column", Arrays.asList("path", "nested"))), RowGranularity.DOC); assertPrint(r, "schema.\"table\".\"column\"['path']['nested']"); } @Test public void testReferenceEscaped() throws Exception { Reference r = new Reference(new ReferenceIdent( new TableIdent("doc", "table"), new ColumnIdent("colum\"n")), RowGranularity.DOC, DataTypes.STRING); assertPrint(r, "doc.\"table\".\"colum\"\"n\""); } @Test public void testLiteralEscaped() throws Exception { assertPrint(Literal.of("bla'bla"), "'bla''bla'"); } @Test public void testObjectLiteral() throws Exception { Literal<Map<String, Object>> l = Literal.of(new HashMap<String, Object>() {{ put("field", "value"); put("array", new Integer[]{1, 2, 3}); put("nestedMap", new HashMap<String, Object>() {{ put("inner", -0.00005d); }}); }}); assertPrint(l, "{\"array\"=[1, 2, 3], \"field\"='value', \"nestedMap\"={\"inner\"=-5.0E-5}}"); } @Test public void testBooleanLiteral() throws Exception { Literal<Boolean> f = Literal.of(false); assertPrint(f, "false"); Literal<Boolean> t = Literal.of(true); assertPrint(t, "true"); } @Test public void visitStringLiteral() throws Exception { Literal<BytesRef> l = Literal.of("fooBar"); assertPrint(l, "'fooBar'"); } @Test public void visitDoubleLiteral() throws Exception { Literal<Double> d = Literal.of(-500.88765d); assertPrint(d, "-500.88765"); } @Test public void visitFloatLiteral() throws Exception { Literal<Float> f = Literal.of(500.887f); assertPrint(f, "500.887"); } @Test public void testExtract() throws Exception { assertPrintIsParseable("extract(century from '1970-01-01')"); assertPrintIsParseable("extract(day_of_week from 0)"); } @Test public void testIsNull() throws Exception { assertPrintIsParseable("null IS NULL"); assertPrintIsParseable("formatter.foo IS NULL"); assertPrintIsParseable("'123' IS NULL"); } @Test public void testQueries() throws Exception { assertPrintIsParseable("(1 + formatter.bar) * 4 = 14"); } @Test public void testCast() throws Exception { assertPrintIsParseable("CAST (formatter.bar AS TIMESTAMP)"); assertPrintIsParseable("CAST (TRUE AS string)"); assertPrintIsParseable("CAST (1+2 AS string)"); } @Test public void testNull() throws Exception { assertPrint(Literal.of(DataTypes.UNDEFINED, null), "NULL"); } @Test public void testNullKey() throws Exception { assertPrint(Literal.of(new HashMap<String, Object>() {{ put("null", null); }}), "{\"null\"=NULL}"); } @Test public void testNativeArray() throws Exception { assertPrint( Literal.of(DataTypes.GEO_SHAPE, ImmutableMap.of("type", "Point", "coordinates", new double[]{1.0d, 2.0d})), "{\"coordinates\"=[1.0, 2.0], \"type\"='Point'}"); } @Test public void testFormatQualified() throws Exception { Symbol ref = sqlExpressions.asSymbol("doc.formatter.\"CraZy\""); assertThat(printer.print(ref, 10, true, false), is("doc.formatter.\"CraZy\"")); assertThat(printer.print(ref, 10, false, false), is("\"CraZy\"")); } @Test public void testMaxDepthEllipsis() throws Exception { Symbol nestedFn = sqlExpressions.asSymbol("abs(sqrt(ln(1+1+1+1+1+1+1+1)))"); assertThat(printer.print(nestedFn, 5, true, false), is("abs(sqrt(ln(((... + ...) + 1))))")); } @Test public void testMaxDepthFail() throws Exception { expectedException.expect(MaxDepthReachedException.class); expectedException.expectMessage("max depth of 5 reached while traversing symbol"); Symbol nestedFn = sqlExpressions.asSymbol("abs(sqrt(ln(1+1+1+1+1+1+1+1)))"); printer.print(nestedFn, 5, true, true); } @Test public void testStyles() throws Exception { Symbol nestedFn = sqlExpressions.asSymbol("abs(sqrt(ln(bar+\"select\"+1+1+1+1+1+1)))"); assertThat(printer.print(nestedFn, SymbolPrinter.Style.FULL_QUALIFIED), is("abs(sqrt(ln((((((((doc.formatter.bar + doc.formatter.\"select\") + 1) + 1) + 1) + 1) + 1) + 1))))")); assertThat(printer.print(nestedFn, SymbolPrinter.Style.SIMPLE), is("abs(sqrt(ln((((((((bar + \"select\") + 1) + 1) + 1) + 1) + 1) + 1))))")); assertThat(printer.print(nestedFn, SymbolPrinter.Style.PARSEABLE), is("abs(sqrt(ln((((((((doc.formatter.bar + doc.formatter.\"select\") + 1) + 1) + 1) + 1) + 1) + 1))))")); assertThat(printer.print(nestedFn, SymbolPrinter.Style.PARSEABLE_NOT_QUALIFIED), is("abs(sqrt(ln((((((((bar + \"select\") + 1) + 1) + 1) + 1) + 1) + 1))))")); } @Test public void testFormatOperatorWithStaticInstance() throws Exception { Symbol comparisonOperator = sqlExpressions.asSymbol("bar = 1 and foo = 2"); String printed = SymbolPrinter.INSTANCE.printFullQualified(comparisonOperator); assertThat( printed, is("((doc.formatter.bar = 1) AND (doc.formatter.foo = '2'))") ); } @Test public void testPrintFetchRefs() throws Exception { Field field = (Field) sqlExpressions.asSymbol("bar"); Reference reference = ((AbstractTableRelation) field.relation()).resolveField(field); Symbol fetchRef = new FetchReference(new InputColumn(1,reference.valueType()), reference); assertPrint(fetchRef, "FETCH(INPUT(1), doc.formatter.bar)"); } @Test public void testPrintInputColumn() throws Exception { Symbol inputCol = new InputColumn(42); assertPrint(inputCol, "INPUT(42)"); } @Test public void testPrintLikeOperator() throws Exception { Symbol likeQuery = sqlExpressions.asSymbol("foo like '%bla%'"); assertPrint(likeQuery, "(doc.formatter.foo LIKE '%bla%')"); assertPrintStatic(likeQuery, "(doc.formatter.foo LIKE '%bla%')"); assertPrintIsParseable("(foo LIKE 'a')"); } @Test public void testPrintAnyEqOperator() throws Exception { assertPrintingRoundTrip("foo = ANY (['a', 'b', 'c'])", "(doc.formatter.foo = ANY(_array('a', 'b', 'c')))"); assertPrintingRoundTrip("foo = ANY(s_arr)", "(doc.formatter.foo = ANY(doc.formatter.s_arr))"); } @Test public void testAnyNeqOperator() throws Exception { assertPrintingRoundTrip("not foo != ANY (['a', 'b', 'c'])", "(NOT (doc.formatter.foo <> ANY(_array('a', 'b', 'c'))))"); assertPrintingRoundTrip("not foo != ANY(s_arr)", "(NOT (doc.formatter.foo <> ANY(doc.formatter.s_arr)))"); } @Test public void testNot() throws Exception { assertPrintingRoundTrip("not foo = 'bar'", "(NOT (doc.formatter.foo = 'bar'))"); } @Test public void testAnyLikeOperator() throws Exception { assertPrintingRoundTrip("foo LIKE ANY (s_arr)", "(doc.formatter.foo LIKE ANY(doc.formatter.s_arr))"); assertPrintingRoundTrip("foo NOT LIKE ANY (['a', 'b', 'c'])", "(doc.formatter.foo NOT LIKE ANY(_array('a', 'b', 'c')))"); } }