Repository: metamodel Updated Branches: refs/heads/master 19564d0d8 -> f7989b85f
METAMODEL-15: Fixed query parsing for table names Fixes #72 Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/f7989b85 Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/f7989b85 Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/f7989b85 Branch: refs/heads/master Commit: f7989b85f2212a65881bfa5cbabb4577fd92e533 Parents: 19564d0 Author: Ashwin Rayaprolu <[email protected]> Authored: Fri Nov 27 08:27:49 2015 +0100 Committer: Kasper Sørensen <[email protected]> Committed: Fri Nov 27 08:27:49 2015 +0100 ---------------------------------------------------------------------- CHANGES.md | 1 + .../metamodel/query/parser/FromItemParser.java | 81 +++++++++++++++----- .../metamodel/query/parser/QueryParserTest.java | 55 ++++++++++--- 3 files changed, 109 insertions(+), 28 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metamodel/blob/f7989b85/CHANGES.md ---------------------------------------------------------------------- diff --git a/CHANGES.md b/CHANGES.md index bdf3696..cdf244a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ * [METAMODEL-207] - Ensured the serializability of the SingleLineCsvRow class. * [METAMODEL-211] - Fixed a bug related to lookup by primary key (_id) on MongoDB. + * [METAMODEL-15] - Query parser support for table names with space. Delimitters can be double quote or square brackets. ### Apache MetaModel 4.4.1 http://git-wip-us.apache.org/repos/asf/metamodel/blob/f7989b85/core/src/main/java/org/apache/metamodel/query/parser/FromItemParser.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/parser/FromItemParser.java b/core/src/main/java/org/apache/metamodel/query/parser/FromItemParser.java index cc8916a..f24f83d 100644 --- a/core/src/main/java/org/apache/metamodel/query/parser/FromItemParser.java +++ b/core/src/main/java/org/apache/metamodel/query/parser/FromItemParser.java @@ -19,8 +19,10 @@ package org.apache.metamodel.query.parser; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.metamodel.DataContext; @@ -33,7 +35,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; final class FromItemParser implements QueryPartProcessor { - + + /** + * This field will hold start and end character for delimiter that can be + * used + */ + private static final Map<Character, Character> delimiterMap = new HashMap<Character, Character>(); + static { + delimiterMap.put('\"', '\"'); + delimiterMap.put('[', ']'); + } + private static final Logger logger = LoggerFactory.getLogger(FromItemParser.class); private final Query _query; @@ -80,44 +92,75 @@ final class FromItemParser implements QueryPartProcessor { } private FromItem parseTableItem(String itemToken) { - final String[] tokens = itemToken.split(" "); - final String alias; - if (tokens.length == 2) { - alias = tokens[1]; - } else if (tokens.length == 1) { - alias = null; + // From token can be starting with [ + final String tableNameToken; + final String aliasToken; + + char startDelimiter = itemToken.trim().charAt(0); + if (delimiterMap.containsKey(startDelimiter)) { + char endDelimiter = delimiterMap.get(startDelimiter); + int endIndex = itemToken.trim().lastIndexOf(endDelimiter, itemToken.trim().length()); + if (endIndex <= 0) { + throw new QueryParserException("Not capable of parsing FROM token: " + itemToken + ". Expected end " + + endDelimiter); + } + tableNameToken = itemToken.trim().substring(1, endIndex).trim(); + + if (itemToken.trim().substring(1 + endIndex).trim().equalsIgnoreCase("")) { + /* + * As per code in FromClause Method: getItemByReference(FromItem + * item, String reference) if (alias == null && table != null && + * reference.equals(table.getName())) { Either we have to change + * the code to add alias.equals("") there or return null here. + */ + aliasToken = null; + } else { + aliasToken = itemToken.trim().substring(1 + endIndex).trim(); + } + } else { - throw new QueryParserException("Not capable of parsing FROM token: " + itemToken); + // Default assumption is space being delimiter for tablename and + // alias.. If otherwise use DoubleQuotes or [] around tableName + final String[] tokens = itemToken.split(" "); + tableNameToken = tokens[0]; + if (tokens.length == 2) { + aliasToken = tokens[1]; + } else if (tokens.length == 1) { + aliasToken = null; + } else { + throw new QueryParserException("Not capable of parsing FROM token: " + itemToken); + } } - final Table table = _dataContext.getTableByQualifiedLabel(tokens[0]); + final Table table = _dataContext.getTableByQualifiedLabel(tableNameToken); if (table == null) { throw new QueryParserException("Not capable of parsing FROM token: " + itemToken); } final FromItem result = new FromItem(table); - result.setAlias(alias); + result.setAlias(aliasToken); result.setQuery(_query); return result; } - + private FromItem parseAllJoinItems(final String itemToken) { String[] joinSplit = itemToken.split("(?i) JOIN "); List<String> joinsList = new ArrayList<String>(); - for(int i = 0 ; i < joinSplit.length -1 ; i++) { + for (int i = 0; i < joinSplit.length - 1; i++) { joinSplit[i] = joinSplit[i].trim(); - joinSplit[i+1] = joinSplit[i+1].trim(); + joinSplit[i + 1] = joinSplit[i + 1].trim(); String leftPart = joinSplit[i].substring(0, joinSplit[i].lastIndexOf(" ")); String joinType = joinSplit[i].substring(joinSplit[i].lastIndexOf(" ")); - String rightPart = (i+1 == joinSplit.length-1) ? joinSplit[i+1] : joinSplit[i+1].substring(0, joinSplit[i+1].lastIndexOf(" ")); + String rightPart = (i + 1 == joinSplit.length - 1) ? joinSplit[i + 1] : joinSplit[i + 1].substring(0, + joinSplit[i + 1].lastIndexOf(" ")); joinsList.add((leftPart + " " + joinType + " JOIN " + rightPart).replaceAll(" +", " ")); String rightTable = rightPart.substring(0, rightPart.toUpperCase().lastIndexOf(" ON ")); - String nextJoinType = joinSplit[i+1].substring(joinSplit[i+1].lastIndexOf(" ")); - joinSplit[i+1] = rightTable + " " + nextJoinType; + String nextJoinType = joinSplit[i + 1].substring(joinSplit[i + 1].lastIndexOf(" ")); + joinSplit[i + 1] = rightTable + " " + nextJoinType; } Set<FromItem> fromItems = new HashSet<FromItem>(); FromItem leftFromItem = null; - for(String token : joinsList) { + for (String token : joinsList) { leftFromItem = parseJoinItem(leftFromItem, token, fromItems); } return leftFromItem; @@ -150,7 +193,7 @@ final class FromItemParser implements QueryPartProcessor { final FromItem leftSide = parseTableItem(firstTableToken); final FromItem rightSide = parseTableItem(secondTableToken); - + fromItems.add(leftSide); fromItems.add(rightSide); @@ -169,7 +212,7 @@ final class FromItemParser implements QueryPartProcessor { leftOn[i] = findSelectItem(leftPart, fromItems.toArray(new FromItem[fromItems.size()])); rightOn[i] = findSelectItem(rightPart, fromItems.toArray(new FromItem[fromItems.size()])); } - + final FromItem leftItem = (leftFromItem != null) ? leftFromItem : leftSide; final FromItem result = new FromItem(joinType, leftItem, rightSide, leftOn, rightOn); result.setQuery(_query); http://git-wip-us.apache.org/repos/asf/metamodel/blob/f7989b85/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java b/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java index 92befe2..1059f9a 100644 --- a/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java +++ b/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java @@ -51,11 +51,9 @@ public class QueryParserTest extends TestCase { col.setType(ColumnType.INTEGER); }; - 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 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 { @@ -64,10 +62,9 @@ public class QueryParserTest extends TestCase { 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"); + 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()); } @@ -94,7 +91,7 @@ public class QueryParserTest extends TestCase { 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()); @@ -136,6 +133,46 @@ public class QueryParserTest extends TestCase { 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());
