From 09ba75fe788295c3c770bb6dcf5435a6c94f1f3c Mon Sep 17 00:00:00 2001 From: Julian Hyde Date: Fri, 30 Jul 2010 00:43:55 +0000 Subject: [PATCH] Fix bug 3035910, "Cannot parse sub-query". (Fix contributed by Thomas Klute.) ParseTreeWriter can now generate indented output. git-svn-id: https://olap4j.svn.sourceforge.net/svnroot/olap4j/trunk@333 c6a108a4-781c-0410-a6c6-c2d559e19af0 --- .../driver/xmla/XmlaOlap4jConnection.java | 2 +- .../driver/xmla/XmlaOlap4jStatement.java | 4 +- src/org/olap4j/mdx/MdxUtil.java | 4 +- src/org/olap4j/mdx/ParseTreeWriter.java | 58 +++++++++++++++++-- src/org/olap4j/mdx/SelectNode.java | 24 +++++--- .../mdx/parser/impl/DefaultMdxParser.cup | 22 ++++++- testsrc/org/olap4j/ConnectionTest.java | 37 ++++++++++-- testsrc/org/olap4j/test/ParserTest.java | 16 +++++ testsrc/org/olap4j/test/TestContext.java | 7 ++- 9 files changed, 144 insertions(+), 30 deletions(-) diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java index f0cd47f..e5dfb9a 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java @@ -2180,7 +2180,7 @@ public SelectNode validateSelect( throws OlapException { StringWriter sw = new StringWriter(); - selectNode.unparse(new ParseTreeWriter(new PrintWriter(sw))); + selectNode.unparse(new ParseTreeWriter(sw)); String mdx = sw.toString(); final XmlaOlap4jConnection olap4jConnection = (XmlaOlap4jConnection) connection; diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jStatement.java b/src/org/olap4j/driver/xmla/XmlaOlap4jStatement.java index 6f2ef1f..bbedc31 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jStatement.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jStatement.java @@ -405,10 +405,8 @@ byte[] getBytes() throws OlapException { */ private static String toString(ParseTreeNode node) { StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - ParseTreeWriter parseTreeWriter = new ParseTreeWriter(pw); + ParseTreeWriter parseTreeWriter = new ParseTreeWriter(sw); node.unparse(parseTreeWriter); - pw.flush(); return sw.toString(); } } diff --git a/src/org/olap4j/mdx/MdxUtil.java b/src/org/olap4j/mdx/MdxUtil.java index 15a88fb..db2f26f 100644 --- a/src/org/olap4j/mdx/MdxUtil.java +++ b/src/org/olap4j/mdx/MdxUtil.java @@ -43,10 +43,8 @@ static String quoteForMdx(String val) { static String toString(ParseTreeNode node) { StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - ParseTreeWriter parseTreeWriter = new ParseTreeWriter(pw); + ParseTreeWriter parseTreeWriter = new ParseTreeWriter(sw); node.unparse(parseTreeWriter); - pw.flush(); return sw.toString(); } diff --git a/src/org/olap4j/mdx/ParseTreeWriter.java b/src/org/olap4j/mdx/ParseTreeWriter.java index 9c8b390..18949e9 100644 --- a/src/org/olap4j/mdx/ParseTreeWriter.java +++ b/src/org/olap4j/mdx/ParseTreeWriter.java @@ -10,6 +10,7 @@ package org.olap4j.mdx; import java.io.PrintWriter; +import java.io.Writer; /** * Writer for MDX parse tree. @@ -38,24 +39,71 @@ */ public class ParseTreeWriter { private final PrintWriter pw; + private int linePrefixLength; + private String linePrefix; + + private static final int INDENT = 4; + private static String bigString = " "; /** * Creates a ParseTreeWriter. * - * @param pw Underlying writer + * @param w Underlying writer */ - public ParseTreeWriter(PrintWriter pw) { - this.pw = pw; + public ParseTreeWriter(Writer w) { + this.pw = new PrintWriter(w) { + @Override + public void println() { + super.println(); + print(linePrefix); + } + + }; + this.linePrefixLength = 0; + setPrefix(); } /** - * Returns the underlying writer. + * Returns the print writer. * - * @return underlying writer + * @return print writer */ public PrintWriter getPrintWriter() { return pw; } + + /** + * Increases the indentation level. + */ + public void indent() { + linePrefixLength += INDENT; + setPrefix(); + } + + private void setPrefix() { + linePrefix = spaces(linePrefixLength); + } + + /** + * Decreases the indentation level. + */ + public void outdent() { + linePrefixLength -= INDENT; + setPrefix(); + } + + /** + * Returns a string of N spaces. + * @param n Number of spaces + * @return String of N spaces + */ + private static synchronized String spaces(int n) + { + while (n > bigString.length()) { + bigString = bigString + bigString; + } + return bigString.substring(0, n); + } } // End ParseTreeWriter.java diff --git a/src/org/olap4j/mdx/SelectNode.java b/src/org/olap4j/mdx/SelectNode.java index e870184..e085692 100644 --- a/src/org/olap4j/mdx/SelectNode.java +++ b/src/org/olap4j/mdx/SelectNode.java @@ -38,7 +38,7 @@ public class SelectNode implements ParseTreeNode { * @param withList List of members and sets defined in this query using * a WITH clause * @param axisList List of axes - * @param from Name of cube + * @param from Contents of FROM clause (name of cube, or subquery) * @param filterAxis Filter axis * @param cellPropertyList List of properties */ @@ -102,9 +102,8 @@ public Type getType() { public String toString() { StringWriter sw = new StringWriter(); - ParseTreeWriter pw = new ParseTreeWriter(new PrintWriter(sw)); + ParseTreeWriter pw = new ParseTreeWriter(sw); unparse(pw); - sw.flush(); return sw.toString(); } @@ -129,7 +128,15 @@ public void unparse(ParseTreeWriter writer) { } pw.println(); pw.print("FROM "); - from.unparse(writer); + if (from instanceof SelectNode) { + writer.indent(); + pw.println("("); + from.unparse(writer); + pw.print(")"); + writer.outdent(); + } else { + from.unparse(writer); + } if (filterAxis.getExpression() != null) { pw.println(); pw.print("WHERE "); @@ -201,7 +208,8 @@ public AxisNode getFilterAxis() { /** * Returns the node representing the FROM clause of this SELECT statement. - * The node is typically an {@link IdentifierNode} or a {@link CubeNode}. + * The node is typically an {@link IdentifierNode}, a {@link CubeNode} or + * a {@link SelectNode}. * * @return FROM clause */ @@ -217,10 +225,10 @@ public ParseTreeNode getFrom() { * a {@link org.olap4j.mdx.CubeNode} referencing an explicit * {@link org.olap4j.metadata.Cube} object. * - * @param fromNode FROM clause + * @param from FROM clause */ - public void setFrom(ParseTreeNode fromNode) { - this.from = fromNode; + public void setFrom(ParseTreeNode from) { + this.from = from; } /** diff --git a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup index 3ca05bb..62c3790 100644 --- a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup +++ b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup @@ -135,7 +135,7 @@ parser code {: ParseRegion region, List withList, List axisList, - IdentifierNode cubeName, + ParseTreeNode from, ParseTreeNode filter, List cellProps) { @@ -155,7 +155,7 @@ parser code {: } }); return new SelectNode( - region, withList, axisList, cubeName, filterAxis, cellProps); + region, withList, axisList, from, filterAxis, cellProps); } // Override lr_parser methods for NLS. With this error handling scheme, @@ -334,6 +334,7 @@ non terminal AxisNode axis_specification; non terminal ParseTreeNode case_expression, + cube_specification_or_select_statement, else_clause_opt, expression, expression_or_empty, @@ -1320,7 +1321,15 @@ exp_list ::= // FROM [] // WHERE [] // [] +// | +// [WITH ] +// SELECT [ +// [, ...]] +// FROM ( select_statement ) +// WHERE [] +// [] // jhyde: The above is wrong... you can omit 'WHERE'. + statement ::= select_statement | _VALUE_EXPRESSION value_expression:e {: @@ -1328,10 +1337,17 @@ statement ::= :} ; +cube_specification_or_select_statement ::= + cube_specification + | LPAREN select_statement:s RPAREN {: + RESULT = s; + :} + ; + select_statement ::= with_formula_specification_opt:f SELECT:select axis_specification_list_opt:a - FROM cube_specification:c + FROM cube_specification_or_select_statement:c where_clause_opt:w cell_props_opt:cp {: ParseRegion region = createRegion(selectleft, selectright); diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java index 5215892..ea5c1d9 100644 --- a/testsrc/org/olap4j/ConnectionTest.java +++ b/testsrc/org/olap4j/ConnectionTest.java @@ -1308,10 +1308,8 @@ private void checkUnparsedMdx( String expectedMdx) { StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw); - ParseTreeWriter parseTreeWriter = new ParseTreeWriter(pw); + ParseTreeWriter parseTreeWriter = new ParseTreeWriter(sw); select.unparse(parseTreeWriter); - pw.flush(); String mdx = sw.toString(); TestContext.assertEqualsVerbose( expectedMdx, mdx); @@ -1324,11 +1322,13 @@ private void checkUnparsedMdx( public void testUnparsing() { // Note that the select statement constructed here is equivalent // to the one in testParsing. + final IdentifierNode cubeName = + new IdentifierNode(new IdentifierNode.NameSegment("sales")); SelectNode select = new SelectNode( null, new ArrayList(), new ArrayList(), - new IdentifierNode(new IdentifierNode.NameSegment("sales")), + cubeName, new AxisNode( null, false, @@ -1388,7 +1388,36 @@ public void testUnparsing() { new IdentifierNode.NameSegment("1997"), new IdentifierNode.NameSegment("Q4"))); + assertEquals(select.getFrom(), cubeName); checkUnparsedMdx(select); + + // Now with a subquery in the FROM clause. + SelectNode subSelect = new SelectNode( + null, + new ArrayList(), + new ArrayList(), + new IdentifierNode(new IdentifierNode.NameSegment("warehouse")), + new AxisNode( + null, + false, + Axis.FILTER, + new ArrayList(), + null), + new ArrayList()); + select.setFrom(subSelect); + + assertEquals(select.getFrom(), subSelect); + checkUnparsedMdx( + select, + "WITH\n" + + "MEMBER [Measures].[Foo] AS '[Measures].[Bar]', FORMAT_STRING = \"xxx\"\n" + + "SELECT\n" + + "{[Gender]} ON COLUMNS,\n" + + "{[Store].Children} ON ROWS\n" + + "FROM (\n" + + " SELECT\n" + + " FROM [warehouse])\n" + + "WHERE [Time].[1997].[Q4]"); } public void testBuildParseTree() { diff --git a/testsrc/org/olap4j/test/ParserTest.java b/testsrc/org/olap4j/test/ParserTest.java index bd9800c..cc645eb 100644 --- a/testsrc/org/olap4j/test/ParserTest.java +++ b/testsrc/org/olap4j/test/ParserTest.java @@ -820,6 +820,22 @@ public void testEmptyExpr() { + "FROM [cube]"); } + /** + * Test case for SELECT in the FROM clause. + */ + public void testInnerSelect() { + assertParseQuery( + "SELECT FROM " + + "(SELECT ({[ProductDim].[Product Group].&[Mobile Phones]}) " + + "ON COLUMNS FROM [cube]) CELL PROPERTIES VALUE", + "SELECT\n" + + "FROM (\n" + + " SELECT\n" + + " ({[ProductDim].[Product Group].&[Mobile Phones]}) ON COLUMNS\n" + + " FROM [cube])\n" + + "CELL PROPERTIES VALUE"); + } + /** * Parses an MDX query and asserts that the result is as expected when * unparsed. diff --git a/testsrc/org/olap4j/test/TestContext.java b/testsrc/org/olap4j/test/TestContext.java index 41a082a..58718d1 100644 --- a/testsrc/org/olap4j/test/TestContext.java +++ b/testsrc/org/olap4j/test/TestContext.java @@ -81,15 +81,16 @@ public static SafeString fold(String string) { */ public static String toString(ParseTreeNode node) { StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - ParseTreeWriter parseTreeWriter = new ParseTreeWriter(pw); + ParseTreeWriter parseTreeWriter = new ParseTreeWriter(sw); node.unparse(parseTreeWriter); - pw.flush(); return sw.toString(); } /** * Formats a {@link org.olap4j.CellSet}. + * + * @param cellSet Cell set + * @return String representation of cell set */ public static String toString(CellSet cellSet) { StringWriter sw = new StringWriter();