/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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. */ package org.apache.metamodel.query.parser; import java.util.Arrays; import java.util.List; import org.apache.metamodel.MetaModelException; import org.apache.metamodel.MetaModelHelper; import org.apache.metamodel.MockDataContext; import org.apache.metamodel.query.FilterClause; import org.apache.metamodel.query.FilterItem; import org.apache.metamodel.query.FromItem; import org.apache.metamodel.query.OperatorType; import org.apache.metamodel.query.OrderByItem; import org.apache.metamodel.query.OrderByItem.Direction; import org.apache.metamodel.query.Query; import org.apache.metamodel.query.SelectItem; import org.apache.metamodel.schema.ColumnType; import org.apache.metamodel.schema.MutableColumn; import junit.framework.TestCase; public class QueryParserTest extends TestCase { private MockDataContext dc; @Override protected void setUp() throws Exception { super.setUp(); dc = new MockDataContext("sch", "tbl", "foo"); // set 'baz' column to an integer column (to influence query generation) MutableColumn col = (MutableColumn) dc.getColumnByQualifiedLabel("tbl.baz"); col.setType(ColumnType.INTEGER); }; public void testQueryWithParenthesis() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "select foo from sch.tbl where (foo= 1) and (foo=2)"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.foo = '1' AND tbl.foo = '2'", q.toSql()); } public void testQueryWithParenthesisAnd() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "select foo from sch.tbl where (foo= 1) and (foo=2)"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.foo = '1' AND tbl.foo = '2'", q.toSql()); } public void testQueryInLowerCase() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "select a.foo as f from sch.tbl a inner join sch.tbl b on a.foo=b.foo order by a.foo asc"); assertEquals("SELECT a.foo AS f FROM sch.tbl a INNER JOIN sch.tbl b ON a.foo = b.foo ORDER BY a.foo ASC", q.toSql()); } public void testParseScalarFunctions() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "select TO_NUM(a.foo) from sch.tbl a WHERE BOOLEAN(a.bar) = false"); assertEquals("SELECT TO_NUMBER(a.foo) FROM sch.tbl a WHERE TO_BOOLEAN(a.bar) = FALSE", q.toSql()); } public void testSelectMapValueUsingDotNotation() throws Exception { // set 'baz' column to a MAP column MutableColumn col = (MutableColumn) dc.getColumnByQualifiedLabel("tbl.baz"); col.setType(ColumnType.MAP); Query q = MetaModelHelper.parseQuery(dc, "SELECT sch.tbl.baz.foo.bar, baz.helloworld, baz.hello.world FROM sch.tbl"); assertEquals( "SELECT MAP_VALUE('foo.bar',tbl.baz), MAP_VALUE('helloworld',tbl.baz), MAP_VALUE('hello.world',tbl.baz) FROM sch.tbl", q.toSql()); } public void testSelectEverythingFromTable() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT * FROM sch.tbl"); assertEquals("SELECT tbl.foo, tbl.bar, tbl.baz FROM sch.tbl", q.toSql()); } public void testSelectEverythingFromJoin() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT * FROM sch.tbl a INNER JOIN sch.tbl b ON a.foo = b.foo"); assertEquals( "SELECT a.foo, a.bar, a.baz, b.foo, b.bar, b.baz FROM sch.tbl a INNER JOIN sch.tbl b ON a.foo = b.foo", q.toSql()); q = MetaModelHelper.parseQuery(dc, "SELECT a.foo, b.* FROM sch.tbl a INNER JOIN sch.tbl b ON a.foo = b.foo"); assertEquals("SELECT a.foo, b.foo, b.bar, b.baz FROM sch.tbl a INNER JOIN sch.tbl b ON a.foo = b.foo", q.toSql()); } public void testSelectColumnWithDotInName() throws Exception { MutableColumn col = (MutableColumn) dc.getTableByQualifiedLabel("tbl").getColumn(0); col.setName("fo.o"); Query q = MetaModelHelper.parseQuery(dc, "SELECT fo.o AS f FROM sch.tbl"); assertEquals("SELECT tbl.fo.o AS f FROM sch.tbl", q.toSql()); } public void testApproximateCountQuery() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT APPROXIMATE COUNT(*) FROM sch.tbl"); assertEquals("SELECT APPROXIMATE COUNT(*) FROM sch.tbl", q.toSql()); assertTrue(q.getSelectClause().getItem(0).isFunctionApproximationAllowed()); } public void testSelectAlias() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo AS f FROM sch.tbl"); assertEquals("SELECT tbl.foo AS f FROM sch.tbl", q.toSql()); q = MetaModelHelper.parseQuery(dc, "SELECT a.foo AS foobarbaz FROM sch.tbl a WHERE foobarbaz = '123'"); assertEquals("SELECT a.foo AS foobarbaz FROM sch.tbl a WHERE a.foo = '123'", q.toSql()); // assert that the referred "foobarbaz" is in fact the same select item // (that's not visible from the toSql() call since there // WhereItem.toSql() method will not use the alias) SelectItem selectItem1 = q.getSelectClause().getItem(0); SelectItem selectItem2 = q.getWhereClause().getItem(0).getSelectItem(); assertSame(selectItem1, selectItem2); } public void testSelectDistinct() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT DISTINCT foo, bar AS f FROM sch.tbl"); assertEquals("SELECT DISTINCT tbl.foo, tbl.bar AS f FROM sch.tbl", q.toSql()); } public void testSelectDistinctInLowerCase() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT distinct foo, bar AS f FROM sch.tbl"); assertEquals("SELECT DISTINCT tbl.foo, tbl.bar AS f FROM sch.tbl", q.toSql()); } public void testSelectMinInLowerCase() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT min(tbl.foo) FROM sch.tbl"); assertEquals("SELECT MIN(tbl.foo) FROM sch.tbl", q.toSql()); } public void testSelectEmptySpacesBeforeAs() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT tbl.foo AS alias FROM sch.tbl"); assertEquals("SELECT tbl.foo AS alias FROM sch.tbl", q.toSql()); } /** * This will test differences cases for tablename * * @throws Exception */ public void testTableName() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT tbl.foo AS alias FROM sch.tbl"); assertEquals("SELECT tbl.foo AS alias FROM sch.tbl", q.toSql()); // Missing ] Bracket try { MetaModelHelper.parseQuery(dc, "SELECT tbl.foo AS alias FROM [sch.tbl"); fail("Exception expected"); } catch (MetaModelException e) { assertEquals("Not capable of parsing FROM token: [sch.tbl. Expected end ]", e.getMessage()); } try { MetaModelHelper.parseQuery(dc, "SELECT tbl.foo AS alias FROM \"sch.tbl"); fail("Exception expected"); } catch (MetaModelException e) { assertEquals("Not capable of parsing FROM token: \"sch.tbl. Expected end \"", e.getMessage()); } // Test Delimiter in tablename try { MetaModelHelper.parseQuery(dc, "SELECT tbl.foo AS alias FROM \"sch.tbl"); fail("Exception expected"); } catch (MetaModelException e) { assertEquals("Not capable of parsing FROM token: \"sch.tbl. Expected end \"", e.getMessage()); } // Positive test case q = MetaModelHelper.parseQuery(dc, "SELECT tbl.foo AS alias FROM [sch.tbl]"); assertEquals("SELECT tbl.foo AS alias FROM sch.tbl", q.toSql()); q = MetaModelHelper.parseQuery(dc, "SELECT tbl.foo AS alias FROM \"sch.tbl\""); assertEquals("SELECT tbl.foo AS alias FROM sch.tbl", q.toSql()); } public void testSelectAvgInLowerCase() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT avg(tbl.foo) FROM sch.tbl"); assertEquals("SELECT AVG(tbl.foo) FROM sch.tbl", q.toSql()); } public void testSimpleSelectFrom() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo\nFROM sch.tbl"); assertEquals("SELECT tbl.foo FROM sch.tbl", q.toSql()); assertEquals(1, q.getFromClause().getItemCount()); FromItem fromItem = q.getFromClause().getItem(0); assertNull("FROM item was an expression based item, which indicates it was not parsed", fromItem.getExpression()); assertNotNull(fromItem.getTable()); assertEquals("tbl", fromItem.getTable().getName()); assertEquals(1, q.getSelectClause().getItemCount()); SelectItem selectItem = q.getSelectClause().getItem(0); assertNull("SELECT item was an expression based item, which indicates it was not parsed", selectItem.getExpression()); assertNotNull(selectItem.getColumn()); assertEquals("foo", selectItem.getColumn().getName()); assertNull(q.getFirstRow()); assertNull(q.getMaxRows()); } public void testCarthesianProduct() throws Exception { Query q = MetaModelHelper.parseQuery(dc, " SELECT a.foo,b.bar FROM sch.tbl a, sch.tbl b \t WHERE a.foo = b.foo"); assertEquals("SELECT a.foo, b.bar FROM sch.tbl a, sch.tbl b WHERE a.foo = b.foo", q.toSql()); List<FromItem> fromItems = q.getFromClause().getItems(); assertNotNull(fromItems.get(0).getTable()); assertNotNull(fromItems.get(1).getTable()); List<FilterItem> whereItems = q.getWhereClause().getItems(); assertNotNull(whereItems.get(0).getSelectItem().getColumn()); assertNotNull(whereItems.get(0).getSelectItem().getFromItem().getTable()); } public void testJoin() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT a.foo,b.bar FROM sch.tbl a INNER JOIN sch.tbl b ON a.foo = b.foo"); assertEquals("SELECT a.foo, b.bar FROM sch.tbl a INNER JOIN sch.tbl b ON a.foo = b.foo", q.toSql()); q = MetaModelHelper.parseQuery(dc, "SELECT COUNT(*) FROM sch.tbl a LEFT JOIN sch.tbl b ON a.foo = b.foo AND a.bar = b.baz"); assertEquals("SELECT COUNT(*) FROM sch.tbl a LEFT JOIN sch.tbl b ON a.foo = b.foo AND a.bar = b.baz", q.toSql()); } public void testSimpleSelectFromWhere() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE bar = 'baz' AND baz > 5"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.bar = 'baz' AND tbl.baz > 5", q.toSql()); FilterClause whereClause = q.getWhereClause(); assertEquals(2, whereClause.getItemCount()); assertNull("WHERE item was an expression based item, which indicates it was not parsed", whereClause.getItem(0).getExpression()); assertEquals(2, whereClause.getItemCount()); assertNull("WHERE item was an expression based item, which indicates it was not parsed", whereClause.getItem(1).getExpression()); assertEquals("baz", whereClause.getItem(0).getOperand()); assertEquals(Integer.class, whereClause.getItem(1).getOperand().getClass()); } public void testWhereStringEscaped() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE bar = 'ba\\'z'"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.bar = 'ba'z'", q.toSql()); } public void testWhereOperandIsBoolean() throws Exception { // set 'baz' column to an integer column (to influence query generation) MutableColumn col = (MutableColumn) dc.getColumnByQualifiedLabel("tbl.baz"); col.setType(ColumnType.BOOLEAN); Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE baz = TRUE"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.baz = TRUE", q.toSql()); } public void testWhereOperandIsDate() throws Exception { // set 'baz' column to an integer column (to influence query generation) MutableColumn col = (MutableColumn) dc.getColumnByQualifiedLabel("tbl.baz"); col.setType(ColumnType.TIME); Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE baz = 10:24"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.baz = TIME '10:24:00'", q.toSql()); } public void testCoumpoundWhereClause() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE (bar = 'baz' OR (baz > 5 AND baz < 7))"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE (tbl.bar = 'baz' OR (tbl.baz > 5 AND tbl.baz < 7))", q.toSql()); FilterClause wc = q.getWhereClause(); assertEquals(1, wc.getItemCount()); FilterItem item = wc.getItem(0); assertTrue(item.isCompoundFilter()); FilterItem[] childItems = item.getChildItems(); assertEquals(2, childItems.length); FilterItem bazConditions = childItems[1]; assertTrue(bazConditions.isCompoundFilter()); } public void testCoumpoundWhereClauseDelimInLoweCase() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE (bar = 'baz' OR (baz > 5 and baz < 7))"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE (tbl.bar = 'baz' OR (tbl.baz > 5 AND tbl.baz < 7))", q.toSql()); FilterClause wc = q.getWhereClause(); assertEquals(1, wc.getItemCount()); FilterItem item = wc.getItem(0); assertTrue(item.isCompoundFilter()); FilterItem[] childItems = item.getChildItems(); assertEquals(2, childItems.length); FilterItem bazConditions = childItems[1]; assertTrue(bazConditions.isCompoundFilter()); } public void testWhereSomethingIsNull() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE bar IS NULL"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.bar IS NULL", q.toSql()); assertEquals(1, q.getWhereClause().getItemCount()); assertNull("WHERE item was an expression based item, which indicates it was not parsed", q.getWhereClause().getItem(0).getExpression()); assertNull(q.getWhereClause().getItem(0).getOperand()); assertEquals(OperatorType.EQUALS_TO, q.getWhereClause().getItem(0).getOperator()); } public void testWhereSomethingIsNotNull() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE bar IS NOT NULL"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.bar IS NOT NULL", q.toSql()); assertEquals(1, q.getWhereClause().getItemCount()); assertNull("WHERE item was an expression based item, which indicates it was not parsed", q.getWhereClause().getItem(0).getExpression()); assertNull(q.getWhereClause().getItem(0).getOperand()); assertEquals(OperatorType.DIFFERENT_FROM, q.getWhereClause().getItem(0).getOperator()); } public void testLimitAndOffset() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl LIMIT 1234 OFFSET 5"); assertEquals("SELECT tbl.foo FROM sch.tbl", q.toSql()); assertEquals(1234, q.getMaxRows().intValue()); assertEquals(6, q.getFirstRow().intValue()); } public void testWhereIn() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE foo IN ('a','b',5)"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.foo IN ('a' , 'b' , '5')", q.toSql()); FilterItem whereItem = q.getWhereClause().getItem(0); assertEquals(OperatorType.IN, whereItem.getOperator()); Object operand = whereItem.getOperand(); assertTrue(operand instanceof List); assertEquals("a", ((List<?>) operand).get(0)); assertEquals("b", ((List<?>) operand).get(1)); assertEquals(5, ((List<?>) operand).get(2)); } public void testWhereInInLowerCase() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE foo in ('a','b',5)"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.foo IN ('a' , 'b' , '5')", q.toSql()); FilterItem whereItem = q.getWhereClause().getItem(0); assertEquals(OperatorType.IN, whereItem.getOperator()); Object operand = whereItem.getOperand(); assertTrue(operand instanceof List); assertEquals("a", ((List<?>) operand).get(0)); assertEquals("b", ((List<?>) operand).get(1)); assertEquals(5, ((List<?>) operand).get(2)); } public void testWhereLikeInLowerCase() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo FROM sch.tbl WHERE foo like 'a%'"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.foo LIKE 'a%'", q.toSql()); FilterItem whereItem = q.getWhereClause().getItem(0); assertEquals(OperatorType.LIKE, whereItem.getOperator()); Object operand = whereItem.getOperand(); assertTrue(operand instanceof String); assertEquals("a%", operand); } public void testSimpleSubQuery() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT f.foo AS fo FROM (SELECT * FROM sch.tbl) f"); assertEquals("SELECT f.foo AS fo FROM (SELECT tbl.foo, tbl.bar, tbl.baz FROM sch.tbl) f", q.toSql()); } public void testSelectEverythingFromSubQuery() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT * FROM (SELECT foo, bar FROM sch.tbl) f"); assertEquals("SELECT f.foo, f.bar FROM (SELECT tbl.foo, tbl.bar FROM sch.tbl) f", q.toSql()); } public void testGetIndicesVanillaScenario() throws Exception { QueryParser qp = new QueryParser(dc, "SELECT ... FROM ... BAR BAZ"); assertEquals("[0, 7]", Arrays.toString(qp.indexesOf("SELECT ", null))); assertEquals("[10, 16]", Arrays.toString(qp.indexesOf(" FROM ", null))); } public void testGetIndicesIgnoreWhiteSpaceAndCaseDifferences() throws Exception { QueryParser qp = new QueryParser(dc, " \t\r\n select ... from ... BAR BAZ"); assertEquals("[0, 7]", Arrays.toString(qp.indexesOf("SELECT ", null))); assertEquals("[10, 16]", Arrays.toString(qp.indexesOf(" FROM ", null))); } public void testInvalidQueries() throws Exception { try { MetaModelHelper.parseQuery(dc, "foobar"); fail("Exception expected"); } catch (MetaModelException e) { assertEquals("SELECT not found in query: foobar", e.getMessage()); } try { MetaModelHelper.parseQuery(dc, "SELECT foobar"); fail("Exception expected"); } catch (MetaModelException e) { assertEquals("FROM not found in query: SELECT foobar", e.getMessage()); } } public void testFullQuery() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT foo, COUNT(* ), MAX( baz ) FROM sch.tbl WHERE bar = 'baz' AND foo = bar AND baz > 5 " + "GROUP BY foo HAVING COUNT(*) > 2 ORDER BY foo LIMIT 20 OFFSET 10"); assertEquals( "SELECT tbl.foo, COUNT(*), MAX(tbl.baz) FROM sch.tbl WHERE tbl.bar = 'baz' AND tbl.foo = tbl.bar AND tbl.baz > 5 " + "GROUP BY tbl.foo HAVING COUNT(*) > 2 ORDER BY tbl.foo ASC", q.toSql()); assertEquals(20, q.getMaxRows().intValue()); assertEquals(11, q.getFirstRow().intValue()); // SELECT ... // tbl.foo assertNotNull("SelectItem 1 should be a column", q.getSelectClause().getItem(0).getColumn()); // COUNT(*) assertNotNull("SelectItem 2 should be a Function", q.getSelectClause().getItem(1).getAggregateFunction()); assertNotNull("SelectItem 2 should be a Function of '*'", q.getSelectClause().getItem(1).getExpression()); // MAX(tbl.baz) assertNotNull("SelectItem 3 should be a Function", q.getSelectClause().getItem(2).getAggregateFunction()); assertNotNull("SelectItem 4 should be a Function of a column", q.getSelectClause().getItem(2).getColumn()); // FROM tbl.foo assertNotNull(q.getFromClause().getItem(0).getTable()); // GROUP BY tbl.foo assertNotNull(q.getGroupByClause().getItem(0).getSelectItem().getColumn()); // HAVING COUNT(*) > 2 FilterItem havingItem = q.getHavingClause().getItem(0); assertNull(havingItem.getExpression()); assertNotNull(havingItem.getSelectItem().getAggregateFunction()); assertEquals("*", havingItem.getSelectItem().getExpression()); // ORDER BY tbl.foo ASC OrderByItem orderByItem = q.getOrderByClause().getItem(0); assertNull(orderByItem.getSelectItem().getExpression()); assertNotNull(orderByItem.getSelectItem().getColumn()); assertEquals(Direction.ASC, orderByItem.getDirection()); } }