diff --git a/src/org/olap4j/mdx/CallNode.java b/src/org/olap4j/mdx/CallNode.java index 7489b87..f1bcafd 100644 --- a/src/org/olap4j/mdx/CallNode.java +++ b/src/org/olap4j/mdx/CallNode.java @@ -85,6 +85,9 @@ public CallNode( case Internal: assert name.startsWith("$"); break; + case Empty: + assert name.equals(""); + break; default: assert !name.startsWith("$") && !name.equals("{}") diff --git a/src/org/olap4j/mdx/Syntax.java b/src/org/olap4j/mdx/Syntax.java index 2f3e6ea..60edb7d 100644 --- a/src/org/olap4j/mdx/Syntax.java +++ b/src/org/olap4j/mdx/Syntax.java @@ -284,7 +284,27 @@ public void unparse( * Defines syntax for expression invoked object.[&PROPERTY] * (a variant of {@link #Property}). */ - AmpersandQuotedProperty; + AmpersandQuotedProperty, + + /** + * Defines the syntax for an empty expression. Empty expressions can occur + * within function calls, and are denoted by a pair of commas with only + * whitespace between them, for example + * + *
+ * DrillDownLevelTop({[Product].[All Products]}, 3, , + * [Measures].[Unit Sales]) + *
+ */ + Empty { + public void unparse( + String operatorName, + List argList, + ParseTreeWriter writer) + { + assert argList.size() == 0; + } + }; /** * Converts a call to a function of this syntax into source code. diff --git a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup index b5f6037..3ca05bb 100644 --- a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup +++ b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup @@ -336,6 +336,7 @@ non terminal ParseTreeNode case_expression, else_clause_opt, expression, + expression_or_empty, factor, filter_specification, term, @@ -1244,6 +1245,13 @@ expression ::= :} | value_expression ; +expression_or_empty ::= + expression + | /* empty */ {: + final ParseRegion region = createRegion(0, 0); + RESULT = new CallNode(region, "", Syntax.Empty); + :} + ; exp_list_opt ::= /* empty */ {: RESULT = new LinkedList(); @@ -1255,7 +1263,7 @@ exp_list ::= RESULT = new LinkedList(); RESULT.add(e); :} - | expression:e COMMA exp_list:list {: + | expression_or_empty:e COMMA exp_list:list {: list.add(0, e); RESULT = list; :} ; diff --git a/testsrc/org/olap4j/test/ParserTest.java b/testsrc/org/olap4j/test/ParserTest.java index 5854bab..bd9800c 100644 --- a/testsrc/org/olap4j/test/ParserTest.java +++ b/testsrc/org/olap4j/test/ParserTest.java @@ -789,6 +789,37 @@ public void testIdentifier() { } } + /** + * Test case for empty expressions. Test case for bug 3030772, "DrilldownLevelTop parser error". + */ + public void testEmptyExpr() { + assertParseQuery( + "select NON EMPTY HIERARCHIZE(\n" + + " {DrillDownLevelTop(\n" + + " {[Product].[All Products]},3,,[Measures].[Unit Sales])}" + + " ) ON COLUMNS\n" + + "from [Sales]\n", + "SELECT\n" + + "NON EMPTY HIERARCHIZE({DrillDownLevelTop({[Product].[All Products]}, 3.0, , [Measures].[Unit Sales])}) ON COLUMNS\n" + + "FROM [Sales]"); + + // more advanced; the actual test case in the bug + assertParseQuery( + "SELECT {[Measures].[NetSales]}" + + " DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON COLUMNS ," + + " NON EMPTY HIERARCHIZE(AddCalculatedMembers(" + + "{DrillDownLevelTop({[ProductDim].[Name].[All]}, 10, ," + + " [Measures].[NetSales])}))" + + " DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON ROWS " + + "FROM [cube]", + "SELECT\n" + + "{[Measures].[NetSales]} DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON COLUMNS,\n" + + "NON EMPTY HIERARCHIZE(AddCalculatedMembers({DrillDownLevelTop({[ProductDim].[Name].[All]}, 10.0, , [Measures].[NetSales])})) DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON ROWS\n" + + "FROM [cube]"); + } + /** * Parses an MDX query and asserts that the result is as expected when * unparsed.