/* * Licensed to Crate.IO GmbH ("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.planner.fetch; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import io.crate.analyze.OrderBy; import io.crate.analyze.QuerySpec; import io.crate.analyze.SelectAnalyzedStatement; import io.crate.analyze.relations.DocTableRelation; import io.crate.analyze.relations.QueriedDocTable; import io.crate.analyze.symbol.Function; import io.crate.analyze.symbol.Symbol; import io.crate.metadata.*; import io.crate.metadata.doc.DocSysColumns; import io.crate.metadata.doc.DocTableInfo; import io.crate.metadata.table.TestingTableInfo; import io.crate.operation.scalar.arithmetic.AbsFunction; import io.crate.test.integration.CrateDummyClusterServiceUnitTest; import io.crate.testing.SQLExecutor; import io.crate.testing.T3; import io.crate.types.DataTypes; import org.junit.Before; import org.junit.Test; import java.util.List; import static io.crate.testing.SymbolMatchers.isFunction; import static io.crate.testing.SymbolMatchers.isReference; import static io.crate.testing.TestingHelpers.isSQL; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; public class FetchRewriterTest extends CrateDummyClusterServiceUnitTest { private static final TableIdent TABLE_IDENT = new TableIdent("s", "t"); private static final DocTableInfo TABLE_INFO = TestingTableInfo.builder(TABLE_IDENT, mock(Routing.class)).build(); private static final DocTableRelation TABLE_REL = new DocTableRelation(TABLE_INFO); private static final Reference REF_SCORE = DocSysColumns.forTable(TABLE_IDENT, DocSysColumns.SCORE); private static final Reference REF_I = new Reference( new ReferenceIdent(new TableIdent("s", "t"), "i"), RowGranularity.DOC, DataTypes.INTEGER); private static final Reference REF_A = new Reference( new ReferenceIdent(new TableIdent("s", "t"), "a"), RowGranularity.DOC, DataTypes.STRING); private SQLExecutor e; @Before public void setupExecutor() throws Exception { e = SQLExecutor.builder(clusterService).addDocTable(T3.T1_INFO).build(); } @Test public void testRewriteWithoutOrderBy() throws Exception { QuerySpec qs = new QuerySpec(); qs.outputs(Lists.newArrayList(REF_A, REF_I)); QueriedDocTable table = new QueriedDocTable(TABLE_REL, qs); FetchRewriter.rewrite(table); assertThat(table.querySpec(), isSQL("SELECT s.t._fetchid")); } @Test public void testRewriteWithOrderBy() throws Exception { QuerySpec qs = new QuerySpec(); qs.outputs(Lists.newArrayList(REF_A, REF_I)); qs.orderBy(new OrderBy(Lists.newArrayList(REF_I), new boolean[]{true}, new Boolean[]{false})); QueriedDocTable table = new QueriedDocTable(TABLE_REL, qs); FetchRewriter.rewrite(table); assertThat(table.querySpec(), isSQL("SELECT s.t._fetchid, s.t.i ORDER BY s.t.i DESC NULLS LAST")); } @Test public void testRewriteWithFunctionInOrderBy() throws Exception { QuerySpec qs = new QuerySpec(); qs.outputs(Lists.newArrayList(REF_A, REF_I)); qs.orderBy(new OrderBy(Lists.newArrayList(abs(REF_I)), new boolean[]{true}, new Boolean[]{false})); QueriedDocTable table = new QueriedDocTable(TABLE_REL, qs); FetchRewriter.rewrite(table); assertThat(table.querySpec(), isSQL("SELECT s.t._fetchid, abs(s.t.i) ORDER BY abs(s.t.i) DESC NULLS LAST")); } @Test public void testPushDownWithNestedOrderInOutput() throws Exception { QuerySpec qs = new QuerySpec(); Function funcOfI = abs(REF_I); qs.outputs(Lists.newArrayList(REF_A, REF_I, funcOfI)); qs.orderBy(new OrderBy(Lists.newArrayList(funcOfI), new boolean[]{true}, new Boolean[]{false})); QueriedDocTable table = new QueriedDocTable(TABLE_REL, qs); FetchRewriter.rewrite(table); assertThat(table.querySpec(), isSQL("SELECT s.t._fetchid, abs(s.t.i) ORDER BY abs(s.t.i) DESC NULLS LAST")); } @Test public void testPushDownOrderRefUsedInFunction() throws Exception { QuerySpec qs = new QuerySpec(); Function funcOfI = abs(REF_I); qs.outputs(Lists.newArrayList(REF_A, REF_I, funcOfI)); qs.orderBy(new OrderBy( Lists.newArrayList(REF_I), new boolean[]{true}, new Boolean[]{false})); QueriedDocTable table = new QueriedDocTable(TABLE_REL, qs); FetchRewriter.rewrite(table); assertThat(table.querySpec(), isSQL("SELECT s.t._fetchid, s.t.i ORDER BY s.t.i DESC NULLS LAST")); } @Test public void testNoPushDownWithOrder() throws Exception { QuerySpec qs = new QuerySpec(); qs.outputs(Lists.newArrayList(REF_I)); qs.orderBy(new OrderBy(ImmutableList.of(REF_I), new boolean[]{true}, new Boolean[]{false})); assertThat(FetchRewriter.isFetchFeasible(qs), is(false)); } @Test public void testScoreDoesNotRequireFetch() throws Exception { QuerySpec qs = new QuerySpec(); qs.outputs(Lists.newArrayList(REF_I, REF_SCORE)); qs.orderBy(new OrderBy(ImmutableList.of(REF_I), new boolean[]{true}, new Boolean[]{false})); assertThat(FetchRewriter.isFetchFeasible(qs), is(false)); } @Test public void testScoreGetsPushedDown() throws Exception { QuerySpec qs = new QuerySpec(); qs.outputs(Lists.newArrayList(REF_A, REF_I, REF_SCORE)); qs.orderBy(new OrderBy(Lists.newArrayList(REF_I), new boolean[]{true}, new Boolean[]{false})); QueriedDocTable table = new QueriedDocTable(TABLE_REL, qs); FetchRewriter.rewrite(table); assertThat(table.querySpec(), isSQL("SELECT s.t._fetchid, s.t.i, s.t._score ORDER BY s.t.i DESC NULLS LAST")); } @Test public void testQuerySymbolsAreNotFetchedIfUsedWithinSelectListFunction() throws Exception { SelectAnalyzedStatement stmt = e.analyze("select x + x, a from t1 order by x"); QueriedDocTable docTable = (QueriedDocTable) stmt.relation(); FetchRewriter.FetchDescription fetchDescription = FetchRewriter.rewrite(docTable); assertThat(fetchDescription.postFetchOutputs, contains(isFunction("add"), isReference("_doc['a']"))); assertThat(((Function) fetchDescription.postFetchOutputs.get(0)).arguments().get(0), isReference("x")); List<Symbol> fetchOutputs = FetchRewriter.generateFetchOutputs(fetchDescription); assertThat(fetchOutputs, isSQL("add(INPUT(1), INPUT(1)), FETCH(INPUT(0), doc.t1._doc['a'])")); } @Test public void testPreFetchOutputCanContainFunctions() throws Exception { SelectAnalyzedStatement stmt = e.analyze("select x + x, a from t1 order by 1"); QueriedDocTable docTable = (QueriedDocTable) stmt.relation(); FetchRewriter.FetchDescription fetchDescription = FetchRewriter.rewrite(docTable); assertThat(fetchDescription.preFetchOutputs(), contains(isReference("_fetchid"), isFunction("add"))); assertThat(fetchDescription.postFetchOutputs, contains(isFunction("add"), isReference("_doc['a']"))); List<Symbol> fetchOutputs = FetchRewriter.generateFetchOutputs(fetchDescription); assertThat(fetchOutputs, isSQL("INPUT(1), FETCH(INPUT(0), doc.t1._doc['a'])")); } private static Function abs(Symbol symbol) { return new Function(new AbsFunction(symbol.valueType()).info(), Lists.newArrayList(symbol)); } }