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.