/* Copyright 2013 Red Hat, Inc. and/or its affiliates. This file is part of lightblue. This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.redhat.lightblue.assoc; import java.util.List; import org.junit.Test; import org.junit.Assert; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.redhat.lightblue.util.Path; import com.redhat.lightblue.util.JsonUtils; import com.redhat.lightblue.util.test.AbstractJsonNodeTest; import com.redhat.lightblue.metadata.CompositeMetadata; import com.redhat.lightblue.metadata.AbstractGetMetadata; import com.redhat.lightblue.metadata.EntityMetadata; import com.redhat.lightblue.metadata.TypeResolver; import com.redhat.lightblue.metadata.types.DefaultTypes; import com.redhat.lightblue.metadata.parser.Extensions; import com.redhat.lightblue.metadata.parser.JSONMetadataParser; import com.redhat.lightblue.query.QueryExpression; import com.redhat.lightblue.query.Projection; import com.redhat.lightblue.TestDataStoreParser; import com.redhat.lightblue.assoc.iterators.*; import com.redhat.lightblue.assoc.scorers.*; public class QueryPlanChooserTest extends AbstractJsonNodeTest { private static final JsonNodeFactory factory = JsonNodeFactory.withExactBigDecimals(true); private static JsonNode json(String q) { try { return JsonUtils.json(q.replace('\'', '\"')); } catch (Exception e) { throw new RuntimeException(e); } } private EntityMetadata getMd(String fname) throws Exception { JsonNode node = loadJsonNode(fname); Extensions<JsonNode> extensions = new Extensions<>(); extensions.addDefaultExtensions(); extensions.registerDataStoreParser("mongo", new TestDataStoreParser<JsonNode>()); TypeResolver resolver = new DefaultTypes(); JSONMetadataParser parser = new JSONMetadataParser(extensions, resolver, factory); return parser.parseEntityMetadata(node); } private QueryExpression query(String s) throws Exception { return QueryExpression.fromJson(JsonUtils.json(s.replace('\'', '\"'))); } private Projection projection(String s) throws Exception { return Projection.fromJson(JsonUtils.json(s.replace('\'', '\"'))); } private class GMD extends AbstractGetMetadata { public GMD(Projection p, QueryExpression q) { super(p, q); } @Override protected EntityMetadata retrieveMetadata(Path injectionField, String entityName, String version) { try { return getMd("composite/" + entityName + ".json"); } catch (Exception e) { throw new RuntimeException(e); } } } @Test public void constructionTest() throws Exception { GMD gmd = new GMD(projection("{'field':'obj1.c','include':1}"), null); CompositeMetadata md = CompositeMetadata.buildCompositeMetadata(getMd("composite/A.json"), gmd); QueryPlanChooser chooser = new QueryPlanChooser(md, new BruteForceQueryPlanIterator(), new IndexedFieldScorer(), query("{'field':'field1','op':'=','rvalue':'s'}"), null); QueryPlanNode[] nodes = chooser.getQueryPlan().getAllNodes(); QueryPlanNode anode = null; QueryPlanNode cnode = null; for (QueryPlanNode node : nodes) { if (node.getMetadata().getName().equals("A")) { anode = node; } else if (node.getMetadata().getName().equals("C")) { cnode = node; } } Assert.assertNotNull(anode); Assert.assertNotNull(cnode); Assert.assertTrue(chooser.getQueryPlan().isUndirectedConnected(anode, cnode)); List<Conjunct> edgeData = chooser.getQueryPlan().getEdgeData(anode, cnode).getConjuncts(); Assert.assertEquals(edgeData, chooser.getQueryPlan().getEdgeData(cnode, anode).getConjuncts()); // The request query must be associated with A Assert.assertTrue(anode.getData().getConjuncts().size() == 1); System.out.println(chooser.getQueryPlan().treeToString()); for (QueryPlanNode node : chooser.getQueryPlan().getAllNodes()) { System.out.println(node.getName() + ":" + node.getData().getConjuncts()); } } @Test public void iterationTestA() throws Exception { GMD gmd = new GMD(projection("[{'field':'obj1.c','include':1},{'field':'b','include':1}]"), null); CompositeMetadata md = CompositeMetadata.buildCompositeMetadata(getMd("composite/A.json"), gmd); QueryPlanChooser chooser = new QueryPlanChooser(md, new BruteForceQueryPlanIterator(), new IndexedFieldScorer(), query("{'field':'field1','op':'=','rvalue':'s'}"), null); // choose a plan chooser.choose(); Assert.assertNotNull(chooser.getBestPlan()); System.out.println("Best plan:" + chooser.getBestPlan().treeToString()); // Best plan should have A first Assert.assertEquals(1, chooser.getBestPlan().getSources().length); Assert.assertEquals("A", chooser.getBestPlan().getSources()[0].getMetadata().getName()); } @Test public void iterationTestC() throws Exception { GMD gmd = new GMD(projection("[{'field':'obj1.c','include':1},{'field':'b','include':1}]"), null); CompositeMetadata md = CompositeMetadata.buildCompositeMetadata(getMd("composite/A.json"), gmd); QueryPlanChooser chooser = new QueryPlanChooser(md, new BruteForceQueryPlanIterator(), new IndexedFieldScorer(), query("{'field':'obj1.c.*.field1','op':'=','rvalue':'s'}"), null); // choose a plan chooser.choose(); Assert.assertNotNull(chooser.getBestPlan()); System.out.println("Best plan:" + chooser.getBestPlan().mxToString()); System.out.println("Best plan:" + chooser.getBestPlan().treeToString()); // Best plan should have C first Assert.assertEquals(1, chooser.getBestPlan().getSources().length); Assert.assertEquals("C", chooser.getBestPlan().getSources()[0].getMetadata().getName()); } @Test public void relationshipWithArrayElemMatchTest() throws Exception { GMD gmd = new GMD(projection("{'field':'relationships'}"), null); CompositeMetadata md = CompositeMetadata.buildCompositeMetadata(getMd("composite/parent_w_elem.json"), gmd); QueryPlanChooser chooser = new QueryPlanChooser(md, new BruteForceQueryPlanIterator(), new IndexedFieldScorer(), query("{'field':'code1','op':'=','rvalue':'X'}"), null); chooser.choose(); System.out.println("Best plan:" + chooser.getBestPlan().mxToString()); System.out.println("Best plan:" + chooser.getBestPlan().treeToString()); Assert.assertEquals(1, chooser.getBestPlan().getSources().length); Assert.assertEquals("parent_w_elem", chooser.getBestPlan().getSources()[0].getMetadata().getName()); } @Test public void preferPrimaryKeys() throws Exception { GMD gmd = new GMD(projection("{'field':'us'}"), null); CompositeMetadata md = CompositeMetadata.buildCompositeMetadata(getMd("composite/L.json"), gmd); QueryPlanChooser chooser = new QueryPlanChooser(md, new BruteForceQueryPlanIterator(), new IndexedFieldScorer(), query("{'$and':[{'field':'_id','op':'$in','values':[1,2]}," + "{'field':'us.*.authentications','op':'=','rvalue':null}]}"), null); chooser.choose(); System.out.println("Best plan:" + chooser.getBestPlan().mxToString()); System.out.println("Best plan:" + chooser.getBestPlan().treeToString()); Assert.assertEquals("L", chooser.getBestPlan().getSources()[0].getMetadata().getName()); } @Test public void preferQueryOverNothing() throws Exception { GMD gmd = new GMD(projection("{'field':'us'}"), null); CompositeMetadata md = CompositeMetadata.buildCompositeMetadata(getMd("composite/L.json"), gmd); QueryPlanChooser chooser = new QueryPlanChooser(md, new BruteForceQueryPlanIterator(), new IndexedFieldScorer(), query("{'field':'us.*.authentications','op':'=','rvalue':null}"), null); chooser.choose(); System.out.println("Best plan:" + chooser.getBestPlan().mxToString()); System.out.println("Best plan:" + chooser.getBestPlan().treeToString()); Assert.assertEquals("U", chooser.getBestPlan().getSources()[0].getMetadata().getName()); } @Test public void preferIndexedSearchOverNonIndexed() throws Exception { GMD gmd = new GMD(projection("{'field':'us'}"), null); CompositeMetadata md = CompositeMetadata.buildCompositeMetadata(getMd("composite/L.json"), gmd); QueryPlanChooser chooser = new QueryPlanChooser(md, new BruteForceQueryPlanIterator(), new IndexedFieldScorer(), query("{'$and':[{'field':'us.*.authentications.*.principal','op':'=','rvalue':'x'}," + "{'field':'status','op':'=','rvalue':'r'}]}"), null); chooser.choose(); System.out.println("Best plan:" + chooser.getBestPlan().mxToString()); System.out.println("Best plan:" + chooser.getBestPlan().treeToString()); Assert.assertEquals("U", chooser.getBestPlan().getSources()[0].getMetadata().getName()); } @Test public void testNestedElemMatchAssignedToNode() throws Exception { GMD gmd = new GMD(projection("{'field':'us','include':1}"), null); CompositeMetadata md = CompositeMetadata.buildCompositeMetadata(getMd("composite/L.json"), gmd); QueryPlanChooser chooser = new QueryPlanChooser(md, new BruteForceQueryPlanIterator(), new IndexedFieldScorer(), query("{'array':'us','elemMatch':{'array':'authentications','elemMatch':{ '$and':[ { 'field':'principal','op':'$in','values':['a']}, {'field':'providerName','op':'$eq','rvalue':'p'} ] } }}"), null); chooser.choose(); System.out.println("Best plan:" + chooser.getBestPlan().mxToString()); System.out.println("Best plan:" + chooser.getBestPlan().treeToString()); System.out.println("Conjunct:" + chooser.getBestPlan().getSources()[0].getData().getConjuncts()); Assert.assertEquals("U", chooser.getBestPlan().getSources()[0].getMetadata().getName()); Assert.assertEquals(1, chooser.getBestPlan().getSources()[0].getData().getConjuncts().size()); } @Test public void three_level_search() throws Exception { GMD gmd=new GMD(projection("{'field':'obj1.c.*.b'}"),null); CompositeMetadata md = CompositeMetadata.buildCompositeMetadata(getMd("composite/A.json"), gmd); QueryPlanChooser chooser = new QueryPlanChooser(md, new BruteForceQueryPlanIterator(), new IndexedFieldScorer(), query("{'array':'obj1.c','elemMatch':{'field':'b.*.field1','op':'=','rvalue':'F, BLYO4OjLMAT aG.4qJ'}}"), null); chooser.choose(); System.out.println("Best plan:" + chooser.getBestPlan().mxToString()); System.out.println("Best plan:" + chooser.getBestPlan().treeToString()); System.out.println("Conjunct:" + chooser.getBestPlan().getSources()[0].getData().getConjuncts()); Assert.assertEquals("B", chooser.getBestPlan().getSources()[0].getMetadata().getName()); Assert.assertEquals(1, chooser.getBestPlan().getSources()[0].getData().getConjuncts().size()); } }