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 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 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