From a40886cf200b8c6aa12fcc201a41ca67b46ca046 Mon Sep 17 00:00:00 2001
From: Julian Hyde
Date: Fri, 8 May 2009 09:31:37 +0000
Subject: [PATCH] Parser and parse tree model can now handle key path
expressions, including compound keys such as [Customers].[State].&[CA]&[USA].
Fixes bug 2782515, "Parser cannot handle &key in member names".
git-svn-id: https://olap4j.svn.sourceforge.net/svnroot/olap4j/trunk@228 c6a108a4-781c-0410-a6c6-c2d559e19af0
---
.../olap4j/driver/xmla/XmlaOlap4jCube.java | 7 +-
.../olap4j/mdx/DefaultMdxValidatorImpl.java | 15 +-
src/org/olap4j/mdx/IdentifierNode.java | 249 +++++++++++++-----
.../mdx/parser/impl/DefaultMdxParser.cup | 77 ++++--
src/org/olap4j/mdx/parser/impl/Scanner.java | 137 +++++++---
src/org/olap4j/sample/SimpleQuerySample.java | 6 +-
testsrc/org/olap4j/ConnectionTest.java | 24 +-
testsrc/org/olap4j/mdx/MdxTest.java | 60 +++--
testsrc/org/olap4j/test/ParserTest.java | 109 +++++++-
9 files changed, 497 insertions(+), 187 deletions(-)
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java
index 020ce0d..4731a65 100644
--- a/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java
@@ -2,7 +2,7 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2008 Julian Hyde
+// Copyright (C) 2007-2009 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -52,7 +52,6 @@ class XmlaOlap4jCube implements Cube, Named
* @param olap4jSchema Schema
* @param name Name
* @param description Description
- * @param connection
*/
XmlaOlap4jCube(
XmlaOlap4jSchema olap4jSchema,
@@ -201,7 +200,7 @@ public Member lookupMember(String... nameParts) throws OlapException {
List segmentList =
new ArrayList();
for (String namePart : nameParts) {
- segmentList.add(new IdentifierNode.Segment(namePart));
+ segmentList.add(new IdentifierNode.NameSegment(namePart));
}
return lookupMember(segmentList);
}
@@ -247,7 +246,7 @@ public List lookupMembers(
if (buf.length() > 0) {
buf.append('.');
}
- buf.append(new IdentifierNode.Segment(namePart));
+ buf.append(new IdentifierNode.NameSegment(namePart));
}
final String uniqueName = buf.toString();
final List list =
diff --git a/src/org/olap4j/mdx/DefaultMdxValidatorImpl.java b/src/org/olap4j/mdx/DefaultMdxValidatorImpl.java
index 20250fa..82423e8 100644
--- a/src/org/olap4j/mdx/DefaultMdxValidatorImpl.java
+++ b/src/org/olap4j/mdx/DefaultMdxValidatorImpl.java
@@ -3,7 +3,7 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2008 Julian Hyde
+// Copyright (C) 2007-2009 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -33,6 +33,11 @@ class DefaultMdxValidatorImpl
private Stack scalarStack = new Stack();
private final SelectNode selectNode;
+ /**
+ * Creates a DefaultMdxValidatorImpl.
+ *
+ * @param selectNode Root of parse tree
+ */
protected DefaultMdxValidatorImpl(SelectNode selectNode) {
this.selectNode = selectNode;
}
@@ -177,10 +182,12 @@ public ParseTreeNode acceptScalar(ParseTreeNode node) {
public ParseTreeNode accept(IdentifierNode identifier) {
if (identifier.getSegmentList().size() == 1) {
final IdentifierNode.Segment s = identifier.getSegmentList().get(0);
- if (s.quoting == IdentifierNode.Quoting.UNQUOTED &&
- isReserved(s.name)) {
+ if (s.getQuoting() == IdentifierNode.Quoting.UNQUOTED
+ && isReserved(s.getName()))
+ {
return LiteralNode.createSymbol(
- s.getRegion(), s.name.toUpperCase());
+ s.getRegion(),
+ s.getName().toUpperCase());
}
}
final ParseTreeNode element =
diff --git a/src/org/olap4j/mdx/IdentifierNode.java b/src/org/olap4j/mdx/IdentifierNode.java
index 123edd8..2c22e9b 100644
--- a/src/org/olap4j/mdx/IdentifierNode.java
+++ b/src/org/olap4j/mdx/IdentifierNode.java
@@ -3,7 +3,7 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2008 Julian Hyde
+// Copyright (C) 2007-2009 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -19,6 +19,52 @@
*
* An identifier is immutable.
*
+ *
An identifer consists of one or more {@link Segment}s. A segment is
+ * either:
+ * - An unquoted value such as 'CA',
+ *
- A value quoted in brackets, such as '[San Francisco]', or
+ *
- A key of one or more parts, each of which is prefixed with '&',
+ * such as '&[Key 1]&Key2&[5]'.
+ *
+ *
+ * Segment types are indicated by the {@link Quoting} enumeration.
+ *
+ *
A key segment is of type {@link Quoting#KEY}, and has one or more
+ * component parts accessed via the
+ * {@link Segment#getKeyParts()} method. The parts
+ * are of type {@link Quoting#UNQUOTED} or {@link Quoting#QUOTED}.
+ *
+ *
A simple example is the identifier {@code Measures.[Unit Sales]}. It
+ * has two segments:
+ * - Segment #0 is
+ * {@link org.olap4j.mdx.IdentifierNode.Quoting#UNQUOTED UNQUOTED},
+ * name "Measures"
+ * - Segment #1 is
+ * {@link org.olap4j.mdx.IdentifierNode.Quoting#QUOTED QUOTED},
+ * name "Unit Sales"
+ *
+ *
+ * A more complex example illustrates a compound key. The identifier
+ * {@code [Customers].[City].&[San Francisco]&CA&USA.&[cust1234]} contains four
+ * segments as follows:
+ *
+ * - Segment #0 is QUOTED, name "Customers"
+ * - Segment #1 is QUOTED, name "City"
+ * - Segment #2 is a {@link org.olap4j.mdx.IdentifierNode.Quoting#KEY KEY}.
+ * It has 3 sub-segments:
+ *
+ * - Sub-segment #0 is QUOTED, name "San Francisco"
+ * - Sub-segment #1 is UNQUOTED, name "CA"
+ * - Sub-segment #2 is UNQUOTED, name "USA"
+ *
+ *
+ * - Segment #3 is a KEY. It has 1 sub-segment:
+ *
+ * - Sub-segment #0 is QUOTED, name "cust1234"
+ *
+ *
+ *
+ *
* @version $Id$
* @author jhyde
*/
@@ -67,26 +113,29 @@ public List getSegmentList() {
public ParseRegion getRegion() {
// Region is the span from the first segment to the last.
+ return sumSegmentRegions(segments);
+ }
+
+ /**
+ * Returns a region encompassing the regions of the first through the last
+ * of a list of segments.
+ *
+ * @param segments List of segments
+ * @return Region encompassed by list of segments
+ */
+ private static ParseRegion sumSegmentRegions(
+ final List extends Segment> segments)
+ {
return ParseRegion.sum(
- new Iterable() {
- public Iterator iterator() {
- final Iterator segmentIter = segments.iterator();
- return new Iterator() {
- public boolean hasNext() {
- return segmentIter.hasNext();
- }
-
- public ParseRegion next() {
- return segmentIter.next().region;
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
+ new AbstractList() {
+ public ParseRegion get(int index) {
+ return segments.get(index).getRegion();
}
- }
- );
+
+ public int size() {
+ return segments.size();
+ }
+ });
}
/**
@@ -118,21 +167,7 @@ public String toString() {
if (k++ > 0) {
buf.append('.');
}
- switch (s.quoting) {
- case UNQUOTED:
- buf.append(s.name);
- break;
- case KEY:
- buf.append("&[");
- buf.append(MdxUtil.mdxEncodeString(s.name));
- buf.append("]");
- break;
- case QUOTED:
- buf.append("[");
- buf.append(MdxUtil.mdxEncodeString(s.name));
- buf.append("]");
- break;
- }
+ s.toString(buf);
}
return buf.toString();
}
@@ -173,8 +208,8 @@ public IdentifierNode deepCopy() {
*/
public static List parseIdentifier(String identifier) {
if (!identifier.startsWith("[")) {
- return Collections.singletonList(
- new Segment(null, identifier, Quoting.UNQUOTED));
+ return Collections.singletonList(
+ new NameSegment(null, identifier, Quoting.UNQUOTED));
}
List list = new ArrayList();
@@ -205,7 +240,7 @@ public static List parseIdentifier(String identifier) {
}
list.add(
- new Segment(
+ new NameSegment(
null,
Olap4jUtil.replace(
identifier.substring(i + 1, j), "]]", "]"),
@@ -279,22 +314,59 @@ static void quoteMdxIdentifier(String id, StringBuilder buf) {
* @param ids List of segments
* @return Segments as quoted string
*/
- static String unparseIdentifierList(List ids) {
+ static String unparseIdentifierList(List extends Segment> ids) {
StringBuilder sb = new StringBuilder(64);
- quoteMdxIdentifier(ids, sb);
- return sb.toString();
- }
-
- static void quoteMdxIdentifier(
- List ids,
- StringBuilder sb)
- {
for (int i = 0; i < ids.size(); i++) {
if (i > 0) {
sb.append('.');
}
sb.append(ids.get(i).toString());
}
+ return sb.toString();
+ }
+
+ public interface Segment {
+
+ /**
+ * Appends this segment to a StringBuffer
+ *
+ * @param buf StringBuffer
+ */
+ void toString(StringBuilder buf);
+
+ /**
+ * Returns the region of the source code which this Segment was created
+ * from, if it was created by parsing.
+ *
+ * @return region of source code
+ */
+ ParseRegion getRegion();
+
+ /**
+ * Returns how this Segment is quoted.
+ *
+ * @return how this Segment is quoted
+ */
+ Quoting getQuoting();
+
+ /**
+ * Returns the name of this Segment.
+ * Returns {@code null} if this Segment represents a key.
+ *
+ * @return name of this Segment
+ */
+ String getName();
+
+ /**
+ * Returns the key components, if this Segment is a key. (That is,
+ * if {@link #getQuoting()} returns
+ * {@link org.olap4j.mdx.IdentifierNode.Quoting#KEY}.)
+ *
+ * Returns null otherwise.
+ *
+ * @return Components of key, or null if this Segment is not a key
+ */
+ List getKeyParts();
}
/**
@@ -312,7 +384,7 @@ static void quoteMdxIdentifier(
* To parse an identifier into a list of segments, use the method
* {@link IdentifierNode#parseIdentifier(String)}.
*/
- public static class Segment {
+ public static class NameSegment implements Segment {
final String name;
final IdentifierNode.Quoting quoting;
private final ParseRegion region;
@@ -324,7 +396,7 @@ public static class Segment {
* @param name Name
* @param quoting Quoting style
*/
- public Segment(
+ public NameSegment(
ParseRegion region,
String name,
IdentifierNode.Quoting quoting)
@@ -339,7 +411,7 @@ public Segment(
*
* @param name Name of segment
*/
- public Segment(String name) {
+ public NameSegment(String name) {
this(null, name, Quoting.QUOTED);
}
@@ -363,12 +435,7 @@ public String toString() {
}
}
- /**
- * Appends this segment to a StringBuffer
- *
- * @param buf StringBuffer
- */
- void toString(StringBuilder buf) {
+ public void toString(StringBuilder buf) {
switch (quoting) {
case UNQUOTED:
buf.append(name);
@@ -384,32 +451,65 @@ void toString(StringBuilder buf) {
throw Olap4jUtil.unexpected(quoting);
}
}
- /**
- * Returns the region of the source code which this Segment was created
- * from, if it was created by parsing.
- *
- * @return region of source code
- */
public ParseRegion getRegion() {
return region;
}
- /**
- * Returns the name of this Segment.
- *
- * @return name of this Segment
- */
public String getName() {
return name;
}
+ public Quoting getQuoting() {
+ return quoting;
+ }
+
+ public List getKeyParts() {
+ return null;
+ }
+ }
+
+ /**
+ * Segment that represents a key or compound key.
+ *
+ * Such a segment appears in an identifier with each component prefixed
+ * with '&'. For example, in the identifier
+ * '[Customer].[State].&[WA]&[USA]', the third segment is a compound key
+ * whose parts are "WA" and "USA".
+ */
+ public static class KeySegment implements Segment {
+ private final List subSegmentList;
+
/**
- * Returns how this Segment is quoted.
+ * Creates a KeySegment.
*
- * @return how this Segment is quoted
+ * @param subSegmentList List if sub-segments
*/
+ public KeySegment(List subSegmentList) {
+ this.subSegmentList = new ArrayList(subSegmentList);
+ assert subSegmentList.size() >= 1;
+ }
+
+ public void toString(StringBuilder buf) {
+ for (Segment segment : subSegmentList) {
+ buf.append('&');
+ segment.toString(buf);
+ }
+ }
+
+ public ParseRegion getRegion() {
+ return sumSegmentRegions(subSegmentList);
+ }
+
public Quoting getQuoting() {
- return quoting;
+ return Quoting.KEY;
+ }
+
+ public String getName() {
+ return null;
+ }
+
+ public List getKeyParts() {
+ return subSegmentList;
}
}
@@ -430,10 +530,15 @@ public enum Quoting {
QUOTED,
/**
- * Identifier quoted with an ampersand to indicate a key value, for example
- * the second segment in "[Employees].&[89]".
+ * Identifier quoted with an ampersand and brackets to indicate a key
+ * value, for example the second segment in "[Employees].&[89]".
+ *
+ * Such a segment has one or more sub-segments. Each segment is
+ * either quoted or unquoted. For example, the second segment in
+ * "[Employees].&[89]&[San Francisco]&CA&USA" has four sub-segments,
+ * two quoted and two unquoted.
*/
- KEY
+ KEY,
}
}
diff --git a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup
index ae342e9..8e6de39 100644
--- a/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup
+++ b/src/org/olap4j/mdx/parser/impl/DefaultMdxParser.cup
@@ -4,7 +4,7 @@
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
// Copyright (C) 1999-2002 Kana Software, Inc.
-// Copyright (C) 2002-2008 Julian Hyde and others.
+// Copyright (C) 2002-2009 Julian Hyde and others.
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -332,6 +332,7 @@ terminal Double NUMBER;
terminal String ID;
terminal String QUOTED_ID;
terminal String AMP_QUOTED_ID;
+terminal String AMP_UNQUOTED_ID;
terminal String STRING;
terminal String FORMULA_STRING;
terminal String UNKNOWN; // a token the lexer doesn't like!
@@ -372,9 +373,14 @@ non terminal String
keyword;
non terminal IdentifierNode.Segment
identifier,
+ key_identifier,
quoted_identifier,
unquoted_identifier,
- amp_quoted_identifier;
+ amp_identifier,
+ amp_quoted_identifier,
+ amp_unquoted_identifier;
+non terminal List
+ amp_identifier_list;
non terminal WithMemberNode
member_specification;
non terminal WithSetNode
@@ -425,31 +431,69 @@ start with statement;
quoted_identifier ::=
QUOTED_ID:i {:
ParseRegion region = createRegion(ileft, iright);
- RESULT = new IdentifierNode.Segment(region, i, IdentifierNode.Quoting.QUOTED);
+ RESULT = new IdentifierNode.NameSegment(
+ region, i, IdentifierNode.Quoting.QUOTED);
:}
;
-amp_quoted_identifier ::=
- AMP_QUOTED_ID:i {:
+unquoted_identifier ::=
+ ID:i {:
+ ParseRegion region = createRegion(ileft, iright);
+ RESULT = new IdentifierNode.NameSegment(
+ region, i, IdentifierNode.Quoting.UNQUOTED);
+ :}
+ | keyword:i {:
ParseRegion region = createRegion(ileft, iright);
- RESULT = new IdentifierNode.Segment(region, i, IdentifierNode.Quoting.KEY);
+ RESULT = new IdentifierNode.NameSegment(
+ region, i, IdentifierNode.Quoting.UNQUOTED);
:}
;
-unquoted_identifier ::=
- ID:i {:
+// for example '&foo&[1]&bar' in '[x].&foo&[1]&bar.[y]'
+key_identifier ::=
+ amp_identifier_list:list {:
+ RESULT = new IdentifierNode.KeySegment(list);
+ :}
+ ;
+
+amp_identifier_list ::=
+ amp_identifier:i {:
+ RESULT = new ArrayList();
+ RESULT.add(i);
+ :}
+ |
+ amp_identifier_list:list amp_identifier:i {:
+ list.add(i);
+ RESULT = list;
+ :}
+ ;
+
+amp_identifier ::=
+ amp_quoted_identifier
+ |
+ amp_unquoted_identifier
+ ;
+
+amp_quoted_identifier ::=
+ AMP_QUOTED_ID:i {:
ParseRegion region = createRegion(ileft, iright);
- RESULT = new IdentifierNode.Segment(region, i, IdentifierNode.Quoting.UNQUOTED);
+ RESULT = new IdentifierNode.NameSegment(
+ region, i, IdentifierNode.Quoting.QUOTED);
:}
- | keyword:i {:
+ ;
+
+amp_unquoted_identifier ::=
+ AMP_UNQUOTED_ID:i {:
ParseRegion region = createRegion(ileft, iright);
- RESULT = new IdentifierNode.Segment(region, i, IdentifierNode.Quoting.UNQUOTED);
+ RESULT = new IdentifierNode.NameSegment(
+ region, i, IdentifierNode.Quoting.UNQUOTED);
:}
;
identifier ::=
unquoted_identifier
| quoted_identifier
+ | key_identifier
;
// a keyword (unlike a reserved word) can be converted back into an
@@ -470,7 +514,6 @@ compound_id ::=
| compound_id:hd DOT identifier:tl {:
RESULT = hd.append(tl);
:}
-
;
//
@@ -839,17 +882,17 @@ value_expression_primary ::=
RESULT = ((IdentifierNode) i).append(j);
} else {
ParseRegion region = createRegion(ileft, jright);
- RESULT = new CallNode(region,
- j.getName(), Syntax.QuotedProperty, i);
+ RESULT = new CallNode(
+ region, j.getName(), Syntax.QuotedProperty, i);
}
:}
- | value_expression_primary:i DOT amp_quoted_identifier:j {:
+ | value_expression_primary:i DOT key_identifier:j {:
if (i instanceof IdentifierNode) {
RESULT = ((IdentifierNode) i).append(j);
} else {
ParseRegion region = createRegion(ileft, jright);
- RESULT = new CallNode(region,
- j.getName(), Syntax.AmpersandQuotedProperty, i);
+ RESULT = new CallNode(
+ region, j.getName(), Syntax.AmpersandQuotedProperty, i);
}
:}
| value_expression_primary:i DOT identifier:j LPAREN exp_list_opt:lis RPAREN:rparen {:
diff --git a/src/org/olap4j/mdx/parser/impl/Scanner.java b/src/org/olap4j/mdx/parser/impl/Scanner.java
index 0066b98..1a6d847 100644
--- a/src/org/olap4j/mdx/parser/impl/Scanner.java
+++ b/src/org/olap4j/mdx/parser/impl/Scanner.java
@@ -3,7 +3,7 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2008 Julian Hyde
+// Copyright (C) 2007-2009 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -318,11 +318,21 @@ private Symbol makeNumber(BigDecimal mantissa, int exponent) {
}
private Symbol makeId(String s, boolean quoted, boolean ampersand) {
- return makeSymbol(
- quoted && ampersand ? DefaultMdxParserSym.AMP_QUOTED_ID :
- quoted ? DefaultMdxParserSym.QUOTED_ID :
- DefaultMdxParserSym.ID,
- s);
+ final int id;
+ if (quoted) {
+ if (ampersand) {
+ id = DefaultMdxParserSym.AMP_QUOTED_ID;
+ } else {
+ id = DefaultMdxParserSym.QUOTED_ID;
+ }
+ } else {
+ if (ampersand) {
+ id = DefaultMdxParserSym.AMP_UNQUOTED_ID;
+ } else {
+ id = DefaultMdxParserSym.ID;
+ }
+ }
+ return makeSymbol(id, s);
}
/**
@@ -457,6 +467,7 @@ public Symbol next_token() throws IOException {
boolean ampersandId = false;
for (;;) {
searchForComments();
+ mainSwitch:
switch (nextChar) {
case '.':
switch (lookahead()) {
@@ -485,14 +496,14 @@ public Symbol next_token() throws IOException {
int digitCount = 0, exponent = 0;
boolean positive = true;
BigDecimal mantissa = BigDecimalZero;
- Scanner.State state = Scanner.State.leftOfPoint;
+ State state = State.leftOfPoint;
for (;;) {
switch (nextChar) {
case '.':
switch (state) {
case leftOfPoint:
- state = Scanner.State.rightOfPoint;
+ state = State.rightOfPoint;
mantissa = n;
n = BigDecimalZero;
digitCount = 0;
@@ -543,7 +554,7 @@ public Symbol next_token() throws IOException {
n = BigDecimalZero;
positive = true;
advance();
- state = Scanner.State.inExponent;
+ state = State.inExponent;
break;
case'0': case'1': case'2': case'3': case'4':
@@ -556,8 +567,7 @@ public Symbol next_token() throws IOException {
case '+':
case '-':
- if (state == Scanner.State.inExponent &&
- digitCount == 0) {
+ if (state == State.inExponent && digitCount == 0) {
// We're looking at the sign after the 'e'.
positive = !positive;
advance();
@@ -600,7 +610,7 @@ public Symbol next_token() throws IOException {
/* parse an identifier */
id = new StringBuilder();
for (;;) {
- id.append((char)nextChar);
+ id.append((char) nextChar);
advance();
switch (nextChar) {
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
@@ -623,7 +633,7 @@ public Symbol next_token() throws IOException {
strId.toUpperCase());
if (i == null) {
// identifier
- return makeId(strId, false, false);
+ return makeId(strId, false, ampersandId);
} else {
// reserved word
return makeRes(i);
@@ -633,10 +643,26 @@ public Symbol next_token() throws IOException {
case '&':
advance();
- if (nextChar == '[') {
+ switch (nextChar) {
+ case '[':
+ // fall through to parse a delimited identifier
ampersandId = true;
- // fall through
- } else {
+ break;
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
+ case 's': case 't': case 'u': case 'v': case 'w': case 'x':
+ case 'y': case 'z':
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ // fall into logic to create identifer
+ ampersandId = true;
+ break mainSwitch;
+ default:
+ // error
return makeToken(DefaultMdxParserSym.UNKNOWN, "&");
}
@@ -669,39 +695,72 @@ public Symbol next_token() throws IOException {
return makeId(id.toString(), true, false);
}
default:
- id.append((char)nextChar);
+ id.append((char) nextChar);
}
}
- case ':': advance(); return makeToken(DefaultMdxParserSym.COLON, ":");
- case ',': advance(); return makeToken(DefaultMdxParserSym.COMMA, ",");
- case '=': advance(); return makeToken(DefaultMdxParserSym.EQ, "=");
+ case ':':
+ advance();
+ return makeToken(DefaultMdxParserSym.COLON, ":");
+ case ',':
+ advance();
+ return makeToken(DefaultMdxParserSym.COMMA, ",");
+ case '=':
+ advance();
+ return makeToken(DefaultMdxParserSym.EQ, "=");
case '<':
advance();
switch (nextChar) {
- case '>': advance(); return makeToken(DefaultMdxParserSym.NE, "<>");
- case '=': advance(); return makeToken(DefaultMdxParserSym.LE, "<=");
- default: return makeToken(DefaultMdxParserSym.LT, "<");
+ case '>':
+ advance();
+ return makeToken(DefaultMdxParserSym.NE, "<>");
+ case '=':
+ advance();
+ return makeToken(DefaultMdxParserSym.LE, "<=");
+ default:
+ return makeToken(DefaultMdxParserSym.LT, "<");
}
case '>':
advance();
switch (nextChar) {
- case '=': advance(); return makeToken(DefaultMdxParserSym.GE, ">=");
- default: return makeToken(DefaultMdxParserSym.GT, ">");
+ case '=':
+ advance();
+ return makeToken(DefaultMdxParserSym.GE, ">=");
+ default:
+ return makeToken(DefaultMdxParserSym.GT, ">");
}
- case '{': advance(); return makeToken(DefaultMdxParserSym.LBRACE, "{");
- case '(': advance(); return makeToken(DefaultMdxParserSym.LPAREN, "(");
- case '}': advance(); return makeToken(DefaultMdxParserSym.RBRACE, "}");
- case ')': advance(); return makeToken(DefaultMdxParserSym.RPAREN, ")");
- case '+': advance(); return makeToken(DefaultMdxParserSym.PLUS, "+");
- case '-': advance(); return makeToken(DefaultMdxParserSym.MINUS, "-");
- case '*': advance(); return makeToken(DefaultMdxParserSym.ASTERISK, "*");
- case '/': advance(); return makeToken(DefaultMdxParserSym.SOLIDUS, "/");
+ case '{':
+ advance();
+ return makeToken(DefaultMdxParserSym.LBRACE, "{");
+ case '(':
+ advance();
+ return makeToken(DefaultMdxParserSym.LPAREN, "(");
+ case '}':
+ advance();
+ return makeToken(DefaultMdxParserSym.RBRACE, "}");
+ case ')':
+ advance();
+ return makeToken(DefaultMdxParserSym.RPAREN, ")");
+ case '+':
+ advance();
+ return makeToken(DefaultMdxParserSym.PLUS, "+");
+ case '-':
+ advance();
+ return makeToken(DefaultMdxParserSym.MINUS, "-");
+ case '*':
+ advance();
+ return makeToken(DefaultMdxParserSym.ASTERISK, "*");
+ case '/':
+ advance();
+ return makeToken(DefaultMdxParserSym.SOLIDUS, "/");
case '|':
advance();
switch (nextChar) {
- case '|': advance(); return makeToken(DefaultMdxParserSym.CONCAT, "||");
- default: return makeToken(DefaultMdxParserSym.UNKNOWN, "|");
+ case '|':
+ advance();
+ return makeToken(DefaultMdxParserSym.CONCAT, "||");
+ default:
+ return makeToken(DefaultMdxParserSym.UNKNOWN, "|");
}
case '"':
@@ -723,7 +782,7 @@ public Symbol next_token() throws IOException {
case -1:
return makeString(id.toString());
default:
- id.append((char)nextChar);
+ id.append((char) nextChar);
}
}
@@ -750,7 +809,7 @@ public Symbol next_token() throws IOException {
case -1:
return makeString(id.toString());
default:
- id.append((char)nextChar);
+ id.append((char) nextChar);
}
}
@@ -760,8 +819,8 @@ public Symbol next_token() throws IOException {
default:
// If it's whitespace, skip over it.
- if (nextChar <= Character.MAX_VALUE &&
- Character.isWhitespace(nextChar))
+ if (nextChar <= Character.MAX_VALUE
+ && Character.isWhitespace(nextChar))
{
// fall through
} else {
diff --git a/src/org/olap4j/sample/SimpleQuerySample.java b/src/org/olap4j/sample/SimpleQuerySample.java
index 1203fb4..89d137b 100644
--- a/src/org/olap4j/sample/SimpleQuerySample.java
+++ b/src/org/olap4j/sample/SimpleQuerySample.java
@@ -260,7 +260,7 @@ void executeSelectNode(OlapConnection connection) {
SelectNode query = new SelectNode();
query.setFrom(
new IdentifierNode(
- new IdentifierNode.Segment("Sales")));
+ new IdentifierNode.NameSegment("Sales")));
query.getAxisList().add(
new AxisNode(
null,
@@ -272,8 +272,8 @@ void executeSelectNode(OlapConnection connection) {
"{}",
Syntax.Braces,
new IdentifierNode(
- new IdentifierNode.Segment("Measures"),
- new IdentifierNode.Segment("Unit Sales")))));
+ new IdentifierNode.NameSegment("Measures"),
+ new IdentifierNode.NameSegment("Unit Sales")))));
// Create a statement based upon the query model.
OlapStatement stmt;
diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java
index b47124d..12960d3 100644
--- a/testsrc/org/olap4j/ConnectionTest.java
+++ b/testsrc/org/olap4j/ConnectionTest.java
@@ -1134,7 +1134,7 @@ public void testUnparsing() {
null,
new ArrayList(),
new ArrayList(),
- new IdentifierNode(new IdentifierNode.Segment("sales")),
+ new IdentifierNode(new IdentifierNode.NameSegment("sales")),
new AxisNode(
null,
false,
@@ -1146,11 +1146,11 @@ public void testUnparsing() {
new WithMemberNode(
null,
new IdentifierNode(
- new IdentifierNode.Segment("Measures"),
- new IdentifierNode.Segment("Foo")),
+ new IdentifierNode.NameSegment("Measures"),
+ new IdentifierNode.NameSegment("Foo")),
new IdentifierNode(
- new IdentifierNode.Segment("Measures"),
- new IdentifierNode.Segment("Bar")),
+ new IdentifierNode.NameSegment("Measures"),
+ new IdentifierNode.NameSegment("Bar")),
Arrays.asList(
new PropertyValueNode(
null,
@@ -1171,7 +1171,7 @@ public void testUnparsing() {
Arrays.asList(
(ParseTreeNode)
new IdentifierNode(
- new IdentifierNode.Segment("Gender"))))));
+ new IdentifierNode.NameSegment("Gender"))))));
select.getAxisList().add(
new AxisNode(
null,
@@ -1187,12 +1187,12 @@ public void testUnparsing() {
"Children",
Syntax.Property,
new IdentifierNode(
- new IdentifierNode.Segment("Store"))))));
+ new IdentifierNode.NameSegment("Store"))))));
select.getFilterAxis().setExpression(
new IdentifierNode(
- new IdentifierNode.Segment("Time"),
- new IdentifierNode.Segment("1997"),
- new IdentifierNode.Segment("Q4")));
+ new IdentifierNode.NameSegment("Time"),
+ new IdentifierNode.NameSegment("1997"),
+ new IdentifierNode.NameSegment("Q4")));
checkUnparsedMdx(select);
}
@@ -2175,7 +2175,9 @@ public void testBuildQuery2() throws ClassNotFoundException, SQLException {
assertEquals("Unit Sales", measure.getName());
Dimension dimPromotionMedia = cube.getDimensions().get("Promotion Media");
//
- // IdentifierNode cubeNode = new IdentifierNode(new IdentifierNode.Segment(cube.getUniqueName()));
+ // IdentifierNode cubeNode =
+ // new IdentifierNode(
+ // new IdentifierNode.NameSegment(cube.getUniqueName()));
CubeNode cubeNode = new CubeNode(null, cube);
MemberNode measuresQuantity = new MemberNode(null, measure);
HierarchyNode promotionHierarchyNode =
diff --git a/testsrc/org/olap4j/mdx/MdxTest.java b/testsrc/org/olap4j/mdx/MdxTest.java
index 397d54f..1327456 100644
--- a/testsrc/org/olap4j/mdx/MdxTest.java
+++ b/testsrc/org/olap4j/mdx/MdxTest.java
@@ -37,20 +37,21 @@ public void testQuoteMdxIdentifier() {
"[Store].[USA].[California]",
IdentifierNode.unparseIdentifierList(
Arrays.asList(
- new IdentifierNode.Segment(
+ new IdentifierNode.NameSegment(
null, "Store", IdentifierNode.Quoting.QUOTED),
- new IdentifierNode.Segment(
+ new IdentifierNode.NameSegment(
null, "USA", IdentifierNode.Quoting.QUOTED),
- new IdentifierNode.Segment(
+ new IdentifierNode.NameSegment(
null, "California", IdentifierNode.Quoting.QUOTED))));
}
public void testImplode() {
- List fooBar = Arrays.asList(
- new IdentifierNode.Segment(
- null, "foo", IdentifierNode.Quoting.UNQUOTED),
- new IdentifierNode.Segment(
- null, "bar", IdentifierNode.Quoting.QUOTED));
+ List fooBar =
+ Arrays.asList(
+ new IdentifierNode.NameSegment(
+ null, "foo", IdentifierNode.Quoting.UNQUOTED),
+ new IdentifierNode.NameSegment(
+ null, "bar", IdentifierNode.Quoting.QUOTED));
assertEquals(
"foo.[bar]",
IdentifierNode.unparseIdentifierList(fooBar));
@@ -58,33 +59,38 @@ public void testImplode() {
List empty = Collections.emptyList();
assertEquals("", IdentifierNode.unparseIdentifierList(empty));
- List nasty = Arrays.asList(
- new IdentifierNode.Segment(
- null, "string", IdentifierNode.Quoting.QUOTED),
- new IdentifierNode.Segment(
- null, "with", IdentifierNode.Quoting.QUOTED),
- new IdentifierNode.Segment(
- null, "a [bracket] in it", IdentifierNode.Quoting.QUOTED));
+ List nasty =
+ Arrays.asList(
+ new IdentifierNode.NameSegment(
+ null, "string", IdentifierNode.Quoting.QUOTED),
+ new IdentifierNode.NameSegment(
+ null, "with", IdentifierNode.Quoting.QUOTED),
+ new IdentifierNode.NameSegment(
+ null, "a [bracket] in it", IdentifierNode.Quoting.QUOTED));
assertEquals(
"[string].[with].[a [bracket]] in it]",
IdentifierNode.unparseIdentifierList(nasty));
}
public void testParseIdentifier() {
- List strings =
- IdentifierNode.parseIdentifier(
- "[string].[with].[a [bracket]] in it]");
- assertEquals(3, strings.size());
- assertEquals("a [bracket] in it", strings.get(2).name);
+ List segments =
+ IdentifierNode.parseIdentifier(
+ "[string].[with].[a [bracket]] in it]");
+ assertEquals(3, segments.size());
+ assertEquals(
+ "a [bracket] in it",
+ segments.get(2).getName());
- strings = IdentifierNode.parseIdentifier(
+ segments = IdentifierNode.parseIdentifier(
"[Worklog].[All].[calendar-[LANGUAGE]].js]");
- assertEquals(3, strings.size());
- assertEquals("calendar-[LANGUAGE].js", strings.get(2).name);
+ assertEquals(3, segments.size());
+ assertEquals(
+ "calendar-[LANGUAGE].js",
+ segments.get(2).getName());
try {
- strings = IdentifierNode.parseIdentifier("[foo].bar");
- fail("expected exception, got " + strings);
+ segments = IdentifierNode.parseIdentifier("[foo].bar");
+ fail("expected exception, got " + segments);
} catch (IllegalArgumentException e) {
assertEquals(
"Invalid member identifier '[foo].bar'",
@@ -92,8 +98,8 @@ public void testParseIdentifier() {
}
try {
- strings = IdentifierNode.parseIdentifier("[foo].[bar");
- fail("expected exception, got " + strings);
+ segments = IdentifierNode.parseIdentifier("[foo].[bar");
+ fail("expected exception, got " + segments);
} catch (IllegalArgumentException e) {
assertEquals(
"Invalid member identifier '[foo].[bar'",
diff --git a/testsrc/org/olap4j/test/ParserTest.java b/testsrc/org/olap4j/test/ParserTest.java
index 46bead9..e0afea5 100644
--- a/testsrc/org/olap4j/test/ParserTest.java
+++ b/testsrc/org/olap4j/test/ParserTest.java
@@ -16,6 +16,7 @@
import org.olap4j.mdx.*;
import org.olap4j.OlapConnection;
import org.olap4j.Axis;
+import org.olap4j.impl.Olap4jUtil;
import java.sql.SQLException;
import java.sql.Connection;
@@ -447,7 +448,9 @@ public void testMultipleAxes() throws Exception {
CallNode fun = (CallNode)colsSetExpr;
IdentifierNode identifier = (IdentifierNode) (fun.getArgList().get(0));
assertEquals(1, identifier.getSegmentList().size());
- assertEquals("Correct member on axis", "axis0mbr",
+ assertEquals(
+ "Correct member on axis",
+ "axis0mbr",
identifier.getSegmentList().get(0).getName());
ParseTreeNode rowsSetExpr = axes.get(1).getExpression();
@@ -456,7 +459,9 @@ public void testMultipleAxes() throws Exception {
fun = (CallNode) rowsSetExpr;
identifier = (IdentifierNode) (fun.getArgList().get(0));
assertEquals(1, identifier.getSegmentList().size());
- assertEquals("Correct member on axis", "axis1mbr",
+ assertEquals(
+ "Correct member on axis",
+ "axis1mbr",
identifier.getSegmentList().get(0).getName());
}
@@ -488,11 +493,11 @@ public void testCaseSwitch() {
public void testDimensionProperties() {
assertParseQuery(
- "select {[foo]} properties p1, p2 on columns from [cube]",
- TestContext.fold(
- "SELECT\n" +
- "{[foo]} DIMENSION PROPERTIES p1, p2 ON COLUMNS\n" +
- "FROM [cube]"));
+ "select {[foo]} properties p1, p2 on columns from [cube]",
+ TestContext.fold(
+ "SELECT\n"
+ + "{[foo]} DIMENSION PROPERTIES p1, p2 ON COLUMNS\n"
+ + "FROM [cube]"));
}
public void testCellProperties() {
@@ -561,6 +566,90 @@ public void testId() {
assertParseExpr("[Foo].&[Bar]", "[Foo].&[Bar]");
}
+ public void testIdWithKey() {
+ // two segments each with a compound key
+ final String mdx = "[Foo].&Key1&Key2.&[Key3]&Key4&[5]";
+ assertParseExpr(mdx, mdx);
+
+ MdxParser p = createParser();
+ final String mdxQuery = wrapExpr(mdx);
+ final SelectNode selectNode = p.parseSelect(mdxQuery);
+ assertEquals(1, selectNode.getWithList().size());
+ WithMemberNode withMember =
+ (WithMemberNode) selectNode.getWithList().get(0);
+ final ParseTreeNode expr = withMember.getExpression();
+ IdentifierNode id = (IdentifierNode) expr;
+ assertNotNull(id.getRegion());
+ assertEquals(3, id.getSegmentList().size());
+
+ final IdentifierNode.Segment seg0 = id.getSegmentList().get(0);
+ assertNotNull(seg0.getRegion());
+ assertEquals("Foo", seg0.getName());
+ assertEquals(IdentifierNode.Quoting.QUOTED, seg0.getQuoting());
+
+ final IdentifierNode.Segment seg1 = id.getSegmentList().get(1);
+ assertEquals(IdentifierNode.Quoting.KEY, seg1.getQuoting());
+ assertNull(seg1.getName());
+ List keyParts = seg1.getKeyParts();
+ assertNotNull(keyParts);
+ assertEquals(2, keyParts.size());
+ assertEquals("Key1", keyParts.get(0).getName());
+ assertEquals(
+ IdentifierNode.Quoting.UNQUOTED, keyParts.get(0).getQuoting());
+ assertEquals("Key2", keyParts.get(1).getName());
+ assertEquals(
+ IdentifierNode.Quoting.UNQUOTED, keyParts.get(1).getQuoting());
+
+ final IdentifierNode.Segment seg2 = id.getSegmentList().get(2);
+ assertNotNull(seg2.getRegion());
+ assertEquals(IdentifierNode.Quoting.KEY, seg2.getQuoting());
+ List keyParts2 = seg2.getKeyParts();
+ assertNotNull(keyParts2);
+ assertEquals(3, keyParts2.size());
+ assertEquals(
+ IdentifierNode.Quoting.QUOTED, keyParts2.get(0).getQuoting());
+ assertEquals(
+ IdentifierNode.Quoting.UNQUOTED, keyParts2.get(1).getQuoting());
+ assertEquals(
+ IdentifierNode.Quoting.QUOTED, keyParts2.get(2).getQuoting());
+ assertEquals("5", keyParts2.get(2).getName());
+ assertNotNull(keyParts2.get(2).getRegion());
+
+ final String actual = TestContext.toString(expr);
+ TestContext.assertEqualsVerbose(mdx, actual);
+ }
+
+ public void testIdComplex() {
+ // simple key
+ assertParseExpr(
+ "[Foo].&[Key1]&[Key2].[Bar]",
+ "[Foo].&[Key1]&[Key2].[Bar]");
+ // compound key
+ assertParseExpr(
+ "[Foo].&[1]&[Key 2]&[3].[Bar]",
+ "[Foo].&[1]&[Key 2]&[3].[Bar]");
+ // compound key sans brackets
+ assertParseExpr(
+ "[Foo].&Key1&Key2 + 4",
+ "([Foo].&Key1&Key2 + 4.0)");
+ // brackets are requred for numbers
+ assertParseExprFails(
+ "[Foo].&[1]&[Key2]&^3.[Bar]",
+ "Syntax error at \\[1:51\\], token '&'");
+ // space between ampersand and key is unacceptable
+ assertParseExprFails(
+ "[Foo].&^ [Key2].[Bar]",
+ "Syntax error at \\[1:40\\], token '&'");
+ // underscore after ampersand is unacceptable
+ assertParseExprFails(
+ "[Foo].&^_Key2.[Bar]",
+ "Syntax error at \\[1:40\\], token '&'");
+ // but underscore is OK within brackets
+ assertParseExpr(
+ "[Foo].&[_Key2].[Bar]",
+ "[Foo].&[_Key2].[Bar]");
+ }
+
// todo: enable this
public void _testCloneQuery() throws SQLException {
OlapConnection olapConnection = getOlapConnection();
@@ -653,12 +742,12 @@ public void testIdentifier() {
}
id = new IdentifierNode(
- new IdentifierNode.Segment("foo"));
+ new IdentifierNode.NameSegment("foo"));
assertEquals("[foo]", id.toString());
// append does not mutate
IdentifierNode id2 = id.append(
- new IdentifierNode.Segment(
+ new IdentifierNode.NameSegment(
null, "bar", IdentifierNode.Quoting.KEY));
assertTrue(id != id2);
assertEquals("[foo]", id.toString());
@@ -680,7 +769,7 @@ public void testIdentifier() {
}
try {
segments.add(
- new IdentifierNode.Segment("baz"));
+ new IdentifierNode.NameSegment("baz"));
fail("expected exception");
} catch (UnsupportedOperationException e) {
// ok