From 3bbe4b1e33e2ab6274a35b80e0f5b69d487a72bd Mon Sep 17 00:00:00 2001 From: Julian Hyde Date: Fri, 17 Dec 2010 07:49:03 +0000 Subject: [PATCH] Use BigDecimal, not Double or Integer, to hold the value of numeric LiteralNode. Deprecate corresponding LiteralNode.create methods. git-svn-id: https://olap4j.svn.sourceforge.net/svnroot/olap4j/trunk@381 c6a108a4-781c-0410-a6c6-c2d559e19af0 --- src/org/olap4j/mdx/LiteralNode.java | 36 +++++++++++- .../mdx/parser/impl/DefaultMdxParser.cup | 9 ++- src/org/olap4j/mdx/parser/impl/Scanner.java | 2 +- testsrc/org/olap4j/ConnectionTest.java | 20 +++++-- testsrc/org/olap4j/mdx/MdxTest.java | 2 +- testsrc/org/olap4j/test/ParserTest.java | 56 ++++++++++--------- 6 files changed, 86 insertions(+), 39 deletions(-) diff --git a/src/org/olap4j/mdx/LiteralNode.java b/src/org/olap4j/mdx/LiteralNode.java index 65f4b9e..2bc6698 100644 --- a/src/org/olap4j/mdx/LiteralNode.java +++ b/src/org/olap4j/mdx/LiteralNode.java @@ -9,9 +9,11 @@ */ package org.olap4j.mdx; +import org.olap4j.impl.Olap4jUtil; import org.olap4j.type.*; import java.io.PrintWriter; +import java.math.BigDecimal; /** * Represents a constant value, such as a string or number, in a parse tree. @@ -50,6 +52,9 @@ private LiteralNode( { assert type != null; assert (type instanceof NullType) == (value == null); + assert (type instanceof StringType || type instanceof SymbolType) + == (value instanceof String); + assert (type instanceof NumericType) == (value instanceof BigDecimal); this.region = region; this.type = type; this.value = value; @@ -112,6 +117,8 @@ public static LiteralNode createSymbol( * @param value Value of literal; must not be null * * @return literal representing the floating-point value + * + * @deprecated Use {@link #createNumeric} */ public static LiteralNode create( ParseRegion region, @@ -120,7 +127,7 @@ public static LiteralNode create( if (value == null) { throw new IllegalArgumentException("value must not be null"); } - return new LiteralNode(region, new NumericType(), value); + return createNumeric(region, new BigDecimal(value), true); } /** @@ -130,6 +137,8 @@ public static LiteralNode create( * @param value Value of literal; must not be null * * @return literal representing the integer value + * + * @deprecated Use {@link #createNumeric} */ public static LiteralNode create( ParseRegion region, @@ -138,6 +147,27 @@ public static LiteralNode create( if (value == null) { throw new IllegalArgumentException("value must not be null"); } + return createNumeric(region, new BigDecimal(value), false); + } + + /** + * Creates a numeric literal. + * + * @param region Region of source code + * @param value Value of literal; must not be null + * @param approximate Whether the literal is approximate + * + * @return literal representing the integer value + */ + public static LiteralNode createNumeric( + ParseRegion region, + BigDecimal value, + boolean approximate) + { + if (value == null) { + throw new IllegalArgumentException("value must not be null"); + } + Olap4jUtil.discard(approximate); // reserved for future use return new LiteralNode(region, new NumericType(), value); } @@ -156,6 +186,10 @@ public ParseRegion getRegion() { /** * Returns the value of this literal. * + *

Value is always of type {@link String} (if the literal is a string or + * a symbol), of type {@link java.math.BigDecimal} (if the literal is + * numeric), or null (if the literal is of null type). + * * @return value */ public Object getValue() { diff --git a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup index 8827681..acc2a31 100644 --- a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup +++ b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup @@ -10,7 +10,10 @@ */ import java_cup.runtime.*; + +import java.math.BigDecimal; import java.util.*; + import org.olap4j.Axis; import org.olap4j.mdx.*; import org.olap4j.mdx.parser.MdxParseException; @@ -320,7 +323,7 @@ terminal SOLIDUS; // / // c. Typed terminals -terminal Double NUMBER; +terminal BigDecimal NUMBER; terminal String ID; terminal String QUOTED_ID; terminal String AMP_QUOTED_ID; @@ -410,7 +413,7 @@ non terminal List non terminal ParseTreeNode[] when_clause; -non terminal Double +non terminal BigDecimal axis_number; // Start symbol @@ -855,7 +858,7 @@ value_expression_primary ::= :} | NUMBER:d {: ParseRegion region = createRegion(dleft, dright); - RESULT = LiteralNode.create(region, d); + RESULT = LiteralNode.createNumeric(region, d, false); :} | identifier:i {: RESULT = new IdentifierNode(i); diff --git a/src/org/olap4j/mdx/parser/impl/Scanner.java b/src/org/olap4j/mdx/parser/impl/Scanner.java index 28100b9..dadb565 100644 --- a/src/org/olap4j/mdx/parser/impl/Scanner.java +++ b/src/org/olap4j/mdx/parser/impl/Scanner.java @@ -297,7 +297,7 @@ private Symbol makeSymbol(int id, Object o) { * @return number literal token */ private Symbol makeNumber(BigDecimal mantissa, int exponent) { - double d = mantissa.movePointRight(exponent).doubleValue(); + BigDecimal d = mantissa.movePointRight(exponent); return makeSymbol(DefaultMdxParserSym.NUMBER, d); } diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java index 111c1ec..c497566 100644 --- a/testsrc/org/olap4j/ConnectionTest.java +++ b/testsrc/org/olap4j/ConnectionTest.java @@ -2425,14 +2425,19 @@ public void run() { ).start(); try { final CellSet cellSet = olapStatement.executeOlapQuery( - "SELECT [Customers].Members * \n" - + " [Time].Members on columns\n" + "SELECT Filter(\n" + + " [Product].Members *\n" + + " [Customers].Members *\n" + + " [Time].[Time].Members,\n" + + " 1 = 0) on columns\n" + "from [Sales]"); fail( "expected exception indicating stmt had been canceled," + " got cellSet " + cellSet); } catch (OlapException e) { - assertTrue(e.getMessage().indexOf("Query canceled") >= 0); + assertTrue( + e.getMessage(), + e.getMessage().indexOf("Query canceled") >= 0); } if (exceptions[0] != null) { throw exceptions[0]; @@ -2455,15 +2460,18 @@ public void testStatementTimeout() throws Throwable { try { final CellSet cellSet = olapStatement.executeOlapQuery( - "SELECT [Store].Members * \n" + "SELECT Filter(\n" + + " [Store].Members * \n" + " [Customers].Members * \n" - + " [Time].Members on columns\n" + + " [Time].[Time].Members, 1 = 0) on columns\n" + "from [Sales]"); fail( "expected exception indicating timeout," + " got cellSet " + cellSet); } catch (OlapException e) { - assertTrue(e.getMessage().indexOf("Query timeout of ") >= 0); + assertTrue( + e.getMessage(), + e.getMessage().indexOf("Query timeout of ") >= 0); } } diff --git a/testsrc/org/olap4j/mdx/MdxTest.java b/testsrc/org/olap4j/mdx/MdxTest.java index e785f3f..e5b0d4e 100644 --- a/testsrc/org/olap4j/mdx/MdxTest.java +++ b/testsrc/org/olap4j/mdx/MdxTest.java @@ -190,7 +190,7 @@ public void testQuoteEscaping() { TestContext.assertEqualsVerbose( "WITH\n" + "SET Foo AS\n" - + " Filter(Bar.Members, (Instr(Name, \"'\") > 0.0))\n" + + " Filter(Bar.Members, (Instr(Name, \"'\") > 0))\n" + "SELECT\n" + "FROM [Cube]", rootNode.toString()); diff --git a/testsrc/org/olap4j/test/ParserTest.java b/testsrc/org/olap4j/test/ParserTest.java index 35281a9..f739cb2 100644 --- a/testsrc/org/olap4j/test/ParserTest.java +++ b/testsrc/org/olap4j/test/ParserTest.java @@ -184,7 +184,7 @@ public void testScannerPunc() { + "from _Bar_Baz", "WITH\n" + "MEMBER [Measures].__Foo AS\n" - + " (1.0 + 2.0)\n" + + " (1 + 2)\n" + "SELECT\n" + "__Foo ON COLUMNS\n" + "FROM _Bar_Baz"); @@ -208,7 +208,7 @@ public void testScannerPunc() { + "from Bar$Baz", "WITH\n" + "MEMBER [Measures].$Foo AS\n" - + " (1.0 + 2.0)\n" + + " (1 + 2)\n" + "SELECT\n" + "$Foo ON COLUMNS\n" + "FROM Bar$Baz"); @@ -234,7 +234,7 @@ public void testUnparse() { + "where [Marital Status].[S]", "WITH\n" + "MEMBER [Measures].[Foo] AS\n" - + " 123.0\n" + + " 123\n" + "SELECT\n" + "{[Measures].members} ON COLUMNS,\n" + "CrossJoin([Product].members, {[Gender].Children}) ON ROWS\n" @@ -514,7 +514,7 @@ public void testCaseSwitch() { + "select {[foo]} on axis(0) from cube", "WITH\n" + "MEMBER [Measures].[Foo] AS\n" - + " CASE x WHEN 1.0 THEN 2.0 WHEN 3.0 THEN 4.0 ELSE 5.0 END\n" + + " CASE x WHEN 1 THEN 2 WHEN 3 THEN 4 ELSE 5 END\n" + "SELECT\n" + "{[foo]} ON COLUMNS\n" + "FROM cube"); @@ -544,12 +544,12 @@ public void testIsEmpty() { assertParseExpr( "[Measures].[Unit Sales] IS EMPTY AND 1 IS NULL", - "(([Measures].[Unit Sales] IS EMPTY) AND (1.0 IS NULL))"); + "(([Measures].[Unit Sales] IS EMPTY) AND (1 IS NULL))"); - // FIXME: "NULL" should associate as "IS NULL" rather than "NULL + 56.0" + // FIXME: "NULL" should associate as "IS NULL" rather than "NULL + 56" assertParseExpr( "- x * 5 is empty is empty is null + 56", - "(((((- x) * 5.0) IS EMPTY) IS EMPTY) IS (NULL + 56.0))"); + "(((((- x) * 5) IS EMPTY) IS EMPTY) IS (NULL + 56))"); } public void testIs() { @@ -565,27 +565,27 @@ public void testIsNull() { assertParseExpr( "[Measures].[Unit Sales] IS NULL AND 1 <> 2", - "(([Measures].[Unit Sales] IS NULL) AND (1.0 <> 2.0))"); + "(([Measures].[Unit Sales] IS NULL) AND (1 <> 2))"); assertParseExpr( "x is null or y is null and z = 5", - "((x IS NULL) OR ((y IS NULL) AND (z = 5.0)))"); + "((x IS NULL) OR ((y IS NULL) AND (z = 5)))"); assertParseExpr( "(x is null) + 56 > 6", - "(((x IS NULL) + 56.0) > 6.0)"); + "(((x IS NULL) + 56) > 6)"); // FIXME: Should be: - // "(((((x IS NULL) AND (a = b)) OR ((c = (d + 5.0))) IS NULL) + 5.0)" + // "(((((x IS NULL) AND (a = b)) OR ((c = (d + 5))) IS NULL) + 5)" assertParseExpr( "x is null and a = b or c = d + 5 is null + 5", - "(((x IS NULL) AND (a = b)) OR ((c = (d + 5.0)) IS (NULL + 5.0)))"); + "(((x IS NULL) AND (a = b)) OR ((c = (d + 5)) IS (NULL + 5)))"); } public void testNull() { assertParseExpr( "Filter({[Measures].[Foo]}, Iif(1 = 2, NULL, 'X'))", - "Filter({[Measures].[Foo]}, Iif((1.0 = 2.0), NULL, \"X\"))"); + "Filter({[Measures].[Foo]}, Iif((1 = 2), NULL, \"X\"))"); } public void testCast() { @@ -595,7 +595,7 @@ public void testCast() { assertParseExpr( "Cast(1 + 2 AS String)", - "CAST((1.0 + 2.0) AS String)"); + "CAST((1 + 2) AS String)"); } public void testId() { @@ -670,7 +670,7 @@ public void testIdComplex() { // compound key sans brackets assertParseExpr( "[Foo].&Key1&Key2 + 4", - "([Foo].&Key1&Key2 + 4.0)"); + "([Foo].&Key1&Key2 + 4)"); // brackets are requred for numbers assertParseExprFails( "[Foo].&[1]&[Key2]&^3.[Bar]", @@ -713,36 +713,38 @@ public void _testCloneQuery() throws SQLException { */ public void testNumbers() { // Number: [+-] [ . ] [e [+-] ] - assertParseExpr("2", "2.0"); + assertParseExpr("2", "2"); // leading '-' is treated as an operator -- that's ok - assertParseExpr("-3", "(- 3.0)"); + assertParseExpr("-3", "(- 3)"); // leading '+' is ignored -- that's ok - assertParseExpr("+45", "45.0"); + assertParseExpr("+45", "45"); // space bad assertParseExprFails( "4 ^5^", - "Syntax error at \\[1:35\\], token '5\\.0'"); + "Syntax error at \\[1:35\\], token '5\\'"); assertParseExpr("3.14", "3.14"); assertParseExpr(".12345", "0.12345"); // lots of digits left and right of point - assertParseExpr("31415926535.89793", "3.141592653589793E10"); + assertParseExpr("31415926535.89793", "31415926535.89793"); assertParseExpr( - "31415926535897.9314159265358979", "3.141592653589793E13"); + "31415926535897.9314159265358979", + "31415926535897.9314159265358979"); assertParseExpr("3.141592653589793", "3.141592653589793"); assertParseExpr( - "-3141592653589793.14159265358979", "(- 3.141592653589793E15)"); + "-3141592653589793.14159265358979", + "(- 3141592653589793.14159265358979)"); // exponents akimbo - assertParseExpr("1e2", "100.0"); + assertParseExpr("1e2", "100"); assertParseExprFails( "1e2e^3^", // todo: fix parser; should be "1e2^e3^" "Syntax error at .* token 'e3'"); - assertParseExpr("1.2e3", "1200.0"); + assertParseExpr("1.2e3", "1200"); assertParseExpr("-1.2345e3", "(- 1234.5)"); assertParseExprFails( "1.2e3.^4^", // todo: fix parser; should be "1.2e3^.4^" @@ -769,7 +771,7 @@ public void testLargePrecision() { + "where ([Time].[1997].[Q2].[4])", "WITH\n" + "MEMBER [Measures].[Small Number] AS\n" - + " ([Measures].[Store Sales] / 9000.0)\n" + + " ([Measures].[Store Sales] / 9000)\n" + "SELECT\n" + "{[Measures].[Small Number]} ON COLUMNS,\n" + "{Filter([Product].[Product Department].members, (([Measures].[Small Number] >= 0.3) AND ([Measures].[Small Number] <= 0.5000001234)))} ON ROWS\n" @@ -836,7 +838,7 @@ public void testEmptyExpr() { + " ) ON COLUMNS\n" + "from [Sales]\n", "SELECT\n" - + "NON EMPTY HIERARCHIZE({DrillDownLevelTop({[Product].[All Products]}, 3.0, , [Measures].[Unit Sales])}) ON COLUMNS\n" + + "NON EMPTY HIERARCHIZE({DrillDownLevelTop({[Product].[All Products]}, 3, , [Measures].[Unit Sales])}) ON COLUMNS\n" + "FROM [Sales]"); // more advanced; the actual test case in the bug @@ -850,7 +852,7 @@ public void testEmptyExpr() { + "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" + + "NON EMPTY HIERARCHIZE(AddCalculatedMembers({DrillDownLevelTop({[ProductDim].[Name].[All]}, 10, , [Measures].[NetSales])})) DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON ROWS\n" + "FROM [cube]"); }