diff --git a/CCL_REFERENCE.md b/CCL_REFERENCE.md index 62034cd..21f591f 100644 --- a/CCL_REFERENCE.md +++ b/CCL_REFERENCE.md @@ -483,6 +483,41 @@ bindings on navigation stops, scope prefixes, and transitive keys are not yet supported. Evaluating what a range read returns is a server-side concern and is outside the grammar. +#### Modification marker (leaf keys) + +A leaf key can bind to the values whose **add** occurred at or after a +timestamp, using a trailing `~` marker. The form `key[t~]` binds the key +over the half-open window `[t, now)`: start-inclusive at `t`, +end-exclusive at the present moment. + +``` +locked[1718380800000000~] = null -- lock values ADDED at or after t +find locked[1700000000~] = "pod-A" -- ...added at or after t, equal to pod-A +``` + +This is distinct from both the single-instant and the range forms. The +single-instant `foo[t]` binds the value *present at* the instant `t` +regardless of when it was added; `foo[t~]` keys strictly off the +add-event, so a value added before `t` (even if it is still present) is +not in range. The range `foo[t1...t2]` binds anything *present during* +the interval regardless of add time. The motivating use is stale-lock +detection: a record whose only lock value was added before the cutoff +binds to nothing under `locked[~]` and is therefore claimable. + +A marker binds a single instant, so it cannot be combined with a range +(`foo[t1...t2~]` is rejected), and a key still carries at most one +bracket annotation (`foo[t1~][t2]` is rejected). The leading form +`foo[~t]` (added-before) is **not** supported and is rejected at parse +time; it is semantically equivalent to an existence-before range and is +deferred to that sibling work. + +The canonical serialization renders the timestamp as microseconds with a +trailing `~` and omits any keyword, for example `locked[1700000000~]`. + +Modification markers are currently supported only on flat leaf keys, not +on navigation stops or scope prefixes. Evaluating what a marked read +returns is a server-side concern and is outside the grammar. + #### Per-stop navigation Navigation keys carry one bracket per stop. Each annotation binds only diff --git a/grammar/grammar.jjt b/grammar/grammar.jjt index a3a08ec..a7352db 100644 --- a/grammar/grammar.jjt +++ b/grammar/grammar.jjt @@ -763,8 +763,14 @@ TimestampSymbol Timestamp() : // Bracket-form key parameter: the raw content between a leaf key's // brackets. The leading at/on/during keyword is optional (the brackets // themselves are the pivot) and is consumed without being returned. -// Interpreting the content -- single instant vs. temporal range -- is -// deferred to Parsing.applyKeyBracket so the meaning lives in one place. +// Interpreting the content -- single instant, temporal range, or +// modification marker -- is deferred to Parsing.applyKeyBracket so the +// meaning lives in one place. SEARCH_MATCH is accepted here only so a +// standalone '~' modification marker (e.g. after a quoted timestamp) is +// captured into the content; its meaning as a search operator outside +// brackets is unaffected. The SEARCH_MATCH token also lexes the +// 'search_match'/'contains' keyword spellings, which are meaningless +// inside a bracket, so the action rejects those and admits only '~'. String KeyBracketParameter() : { Token word; @@ -772,7 +778,14 @@ String KeyBracketParameter() : } { ()? - (LOOKAHEAD(2) (word= | word= | word= | word= | word= | word= | word=) { parameter += (parameter.equals("")) ? word.image : " " + word.image; })+ + (LOOKAHEAD(2) (word= | word= | word= | word= | word= | word= | word= | word=) + { + if(word.kind == GrammarConstants.SEARCH_MATCH && !word.image.equals("~")) { + throw new ParseException("only the '~' modification marker is allowed " + + "inside a key bracket, not the '" + word.image + "' keyword"); + } + parameter += (parameter.equals("")) ? word.image : " " + word.image; + })+ { return parameter; } } diff --git a/src/main/java/com/cinchapi/ccl/Parsing.java b/src/main/java/com/cinchapi/ccl/Parsing.java index 87e74c2..6583dc1 100644 --- a/src/main/java/com/cinchapi/ccl/Parsing.java +++ b/src/main/java/com/cinchapi/ccl/Parsing.java @@ -28,6 +28,7 @@ import com.cinchapi.ccl.grammar.ConjunctionSymbol; import com.cinchapi.ccl.grammar.ExpressionSymbol; import com.cinchapi.ccl.grammar.KeyTokenSymbol; +import com.cinchapi.ccl.grammar.ModificationKeySymbol; import com.cinchapi.ccl.grammar.NavigationKeySymbol; import com.cinchapi.ccl.grammar.OperatorSymbol; import com.cinchapi.ccl.grammar.ParenthesisSymbol; @@ -60,34 +61,68 @@ public final class Parsing { */ private static final String RANGE_SEPARATOR = "..."; + /** + * The literal marker that, when trailing a bracket timestamp, denotes + * an addition-relative binding (for example {@code foo[t1~]}): the + * values whose add occurred at or after the timestamp. + */ + private static final String MODIFICATION_MARKER = "~"; + /** * Interpret the raw {@code content} of a leaf key's bracket parameter * and pair it with {@code base} as the appropriate parameterized key. * - *

Content containing the range separator {@code ...} produces a - * {@link TemporalRangeKeySymbol} over the half-open interval its - * endpoints describe; either endpoint may be omitted for an - * open-ended range. All other content is a single instant and - * produces a {@link TemporalKeySymbol}, or, when {@code base} is a - * {@link NavigationKeySymbol}, folds the timestamp onto the path's - * last stop. + *

Content ending in the modification marker {@code ~} produces a + * {@link ModificationKeySymbol} bound to the values added at or after + * the timestamp ({@code foo[t1~]}). Content containing the range + * separator {@code ...} produces a {@link TemporalRangeKeySymbol} over + * the half-open interval its endpoints describe; either endpoint may + * be omitted for an open-ended range. All other content is a single + * instant and produces a {@link TemporalKeySymbol}, or, when + * {@code base} is a {@link NavigationKeySymbol}, folds the timestamp + * onto the path's last stop. + * + *

The leading modification form {@code key[~t]} (added-before) is + * not yet supported and is rejected; it is semantically equivalent to + * an existence-before range and is deferred to that sibling work. * * @param base the key the bracket parameter binds to * @param content the raw bracket content; the leading {@code at} / * {@code on} / {@code during} keyword, if any, has already * been consumed by the grammar * @return the parameterized {@link KeyTokenSymbol} - * @throws IllegalArgumentException if a range binding is applied to a - * navigation key, if both endpoints are omitted, if more - * than one range separator is present, or if an endpoint - * cannot be parsed as a timestamp + * @throws IllegalArgumentException if a range or modification binding + * is applied to a navigation key, if a range omits both + * endpoints or has more than one separator, if the + * unsupported leading {@code ~t} form is used, or if a + * timestamp cannot be parsed */ public static KeyTokenSymbol applyKeyBracket(KeyTokenSymbol base, String content) { - int separator = indexOfRangeSeparator(content, 0); + String trimmed = content.trim(); + if(trimmed.startsWith(MODIFICATION_MARKER)) { + throw new IllegalArgumentException( + "the leading modification form key[~t] (added-before) is " + + "not yet supported; use a single instant key[t] " + + "or a range key[t1...t2]"); + } + if(trimmed.endsWith(MODIFICATION_MARKER)) { + String timestamp = trimmed + .substring(0, + trimmed.length() - MODIFICATION_MARKER.length()) + .trim(); + Preconditions.checkArgument( + indexOfRangeSeparator(timestamp, 0) < 0, + "a modification marker binds a single instant, not a " + + "range; '%s' combines '%s' with '%s'", + trimmed, RANGE_SEPARATOR, MODIFICATION_MARKER); + return new ModificationKeySymbol(base, + new TimestampSymbol(NaturalLanguage.parseMicros(timestamp))); + } + int separator = indexOfRangeSeparator(trimmed, 0); if(separator < 0) { TimestampSymbol timestamp = new TimestampSymbol( - NaturalLanguage.parseMicros(content.trim())); + NaturalLanguage.parseMicros(trimmed)); if(base instanceof NavigationKeySymbol) { return NavigationKeySymbol.withTimestampOnLastStop( (NavigationKeySymbol) base, timestamp); @@ -98,13 +133,13 @@ public static KeyTokenSymbol applyKeyBracket(KeyTokenSymbol base, } else { Preconditions.checkArgument( - indexOfRangeSeparator(content, + indexOfRangeSeparator(trimmed, separator + RANGE_SEPARATOR.length()) < 0, "a temporal range has exactly two endpoints separated by " + "a single '%s'; '%s' has more than one", - RANGE_SEPARATOR, content); - String startText = content.substring(0, separator).trim(); - String endText = content + RANGE_SEPARATOR, trimmed); + String startText = trimmed.substring(0, separator).trim(); + String endText = trimmed .substring(separator + RANGE_SEPARATOR.length()).trim(); TimestampSymbol start = startText.isEmpty() ? null : new TimestampSymbol( diff --git a/src/main/java/com/cinchapi/ccl/generated/Grammar.java b/src/main/java/com/cinchapi/ccl/generated/Grammar.java index fa2d98b..41dad50 100644 --- a/src/main/java/com/cinchapi/ccl/generated/Grammar.java +++ b/src/main/java/com/cinchapi/ccl/generated/Grammar.java @@ -1621,8 +1621,14 @@ final public void RelationalExpressionNoTimestamp() throws ParseException {/*@bg // Bracket-form key parameter: the raw content between a leaf key's // brackets. The leading at/on/during keyword is optional (the brackets // themselves are the pivot) and is consumed without being returned. -// Interpreting the content -- single instant vs. temporal range -- is -// deferred to Parsing.applyKeyBracket so the meaning lives in one place. +// Interpreting the content -- single instant, temporal range, or +// modification marker -- is deferred to Parsing.applyKeyBracket so the +// meaning lives in one place. SEARCH_MATCH is accepted here only so a +// standalone '~' modification marker (e.g. after a quoted timestamp) is +// captured into the content; its meaning as a search operator outside +// brackets is unaffected. The SEARCH_MATCH token also lexes the +// 'search_match'/'contains' keyword spellings, which are meaningless +// inside a bracket, so the action rejects those and admits only '~'. final public String KeyBracketParameter() throws ParseException {Token word; String parameter = ""; switch ((jj_ntk==-1)?jj_ntk_f():jj_ntk) { @@ -1665,12 +1671,20 @@ final public void RelationalExpressionNoTimestamp() throws ParseException {/*@bg word = jj_consume_token(ASTERISK_SUFFIXED_STRING); break; } + case SEARCH_MATCH:{ + word = jj_consume_token(SEARCH_MATCH); + break; + } default: jj_la1[40] = jj_gen; jj_consume_token(-1); throw new ParseException(); } -parameter += (parameter.equals("")) ? word.image : " " + word.image; +if(word.kind == GrammarConstants.SEARCH_MATCH && !word.image.equals("~")) { + {if (true) throw new ParseException("only the '~' modification marker is allowed " + + "inside a key bracket, not the '" + word.image + "' keyword");} + } + parameter += (parameter.equals("")) ? word.image : " " + word.image; if (jj_2_20(2)) { ; } else { @@ -5615,21 +5629,17 @@ private boolean jj_2_71(int xla) finally { jj_save(70, xla); } } - private boolean jj_3_29() + private boolean jj_3R_39() { - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(53)) { - jj_scanpos = xsp; - if (jj_3R_40()) return true; - } + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; return false; } - private boolean jj_3_54() + private boolean jj_3_31() { - if (jj_3R_55()) return true; - if (jj_scan_token(QUOTED_STRING)) return true; + if (jj_scan_token(OPEN_BRACKET)) return true; + if (jj_scan_token(NUMERIC)) return true; return false; } @@ -5656,52 +5666,6 @@ private boolean jj_3R_27() return false; } - private boolean jj_3_32() - { - if (jj_3R_42()) return true; - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(40)) { - jj_scanpos = xsp; - if (jj_scan_token(37)) { - jj_scanpos = xsp; - if (jj_scan_token(39)) return true; - } - } - return false; - } - - private boolean jj_3R_23() - { - if (jj_3R_69()) return true; - return false; - } - - private boolean jj_3R_29() - { - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(82)) { - jj_scanpos = xsp; - if (jj_scan_token(83)) return true; - } - return false; - } - - private boolean jj_3R_39() - { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; - return false; - } - - private boolean jj_3_31() - { - if (jj_scan_token(OPEN_BRACKET)) return true; - if (jj_scan_token(NUMERIC)) return true; - return false; - } - private boolean jj_3_71() { Token xsp; @@ -5717,12 +5681,6 @@ private boolean jj_3_71() return false; } - private boolean jj_3R_108() - { - if (jj_scan_token(BINARY_OPERATOR)) return true; - return false; - } - private boolean jj_3_70() { Token xsp; @@ -5742,27 +5700,30 @@ private boolean jj_3_70() return false; } - private boolean jj_3R_65() + private boolean jj_3R_23() { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; + if (jj_3R_69()) return true; return false; } - private boolean jj_3R_106() + private boolean jj_3R_29() { Token xsp; xsp = jj_scanpos; - if (jj_scan_token(60)) { - jj_scanpos = xsp; - if (jj_scan_token(62)) { + if (jj_scan_token(82)) { jj_scanpos = xsp; - if (jj_scan_token(63)) return true; - } + if (jj_scan_token(83)) return true; } return false; } + private boolean jj_3R_65() + { + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; + return false; + } + private boolean jj_3_28() { Token xsp; @@ -5774,46 +5735,43 @@ private boolean jj_3_28() return false; } - private boolean jj_3_2() + private boolean jj_3R_38() { - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(56)) jj_scanpos = xsp; - if (jj_3R_22()) return true; - xsp = jj_scanpos; - if (jj_3R_25()) jj_scanpos = xsp; - xsp = jj_scanpos; - if (jj_3R_26()) jj_scanpos = xsp; + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; return false; } - private boolean jj_3R_38() + private boolean jj_3R_108() { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; + if (jj_scan_token(BINARY_OPERATOR)) return true; return false; } - private boolean jj_3R_104() + private boolean jj_3R_106() { Token xsp; xsp = jj_scanpos; - if (jj_scan_token(71)) { - jj_scanpos = xsp; - if (jj_scan_token(72)) { + if (jj_scan_token(60)) { jj_scanpos = xsp; - if (jj_scan_token(73)) { + if (jj_scan_token(62)) { jj_scanpos = xsp; - if (jj_scan_token(74)) return true; - } + if (jj_scan_token(63)) return true; } } return false; } - private boolean jj_3R_102() + private boolean jj_3_2() { - if (jj_scan_token(LINKS_TO)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(56)) jj_scanpos = xsp; + if (jj_3R_22()) return true; + xsp = jj_scanpos; + if (jj_3R_25()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_26()) jj_scanpos = xsp; return false; } @@ -5849,20 +5807,6 @@ private boolean jj_3_53() return false; } - private boolean jj_3_1() - { - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(56)) jj_scanpos = xsp; - if (jj_3R_22()) return true; - xsp = jj_scanpos; - if (jj_3R_23()) jj_scanpos = xsp; - xsp = jj_scanpos; - if (jj_3R_24()) jj_scanpos = xsp; - if (jj_scan_token(102)) return true; - return false; - } - private boolean jj_3R_37() { if (jj_scan_token(NUMERIC)) return true; @@ -5881,9 +5825,20 @@ private boolean jj_3_27() return false; } - private boolean jj_3R_116() + private boolean jj_3R_104() { - if (jj_scan_token(QUOTED_STRING)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(71)) { + jj_scanpos = xsp; + if (jj_scan_token(72)) { + jj_scanpos = xsp; + if (jj_scan_token(73)) { + jj_scanpos = xsp; + if (jj_scan_token(74)) return true; + } + } + } return false; } @@ -5894,6 +5849,26 @@ private boolean jj_3_52() return false; } + private boolean jj_3R_102() + { + if (jj_scan_token(LINKS_TO)) return true; + return false; + } + + private boolean jj_3_1() + { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(56)) jj_scanpos = xsp; + if (jj_3R_22()) return true; + xsp = jj_scanpos; + if (jj_3R_23()) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_3R_24()) jj_scanpos = xsp; + if (jj_scan_token(102)) return true; + return false; + } + private boolean jj_3_47() { Token xsp; @@ -5934,6 +5909,30 @@ private boolean jj_3_26() return false; } + private boolean jj_3R_116() + { + if (jj_scan_token(QUOTED_STRING)) return true; + return false; + } + + private boolean jj_3R_52() + { + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; + return false; + } + + private boolean jj_3_46() + { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(53)) { + jj_scanpos = xsp; + if (jj_3R_53()) return true; + } + return false; + } + private boolean jj_3R_115() { Token xsp; @@ -5960,6 +5959,13 @@ private boolean jj_3R_115() return false; } + private boolean jj_3R_63() + { + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; + return false; + } + private boolean jj_3R_109() { Token xsp; @@ -5980,25 +5986,33 @@ private boolean jj_3_18() return false; } - private boolean jj_3R_52() + private boolean jj_3_51() { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; + if (jj_3R_43()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(40)) { + jj_scanpos = xsp; + if (jj_scan_token(37)) { + jj_scanpos = xsp; + if (jj_scan_token(39)) return true; + } + } return false; } - private boolean jj_3_46() + private boolean jj_3_45() { Token xsp; xsp = jj_scanpos; if (jj_scan_token(53)) { jj_scanpos = xsp; - if (jj_3R_53()) return true; + if (jj_3R_52()) return true; } return false; } - private boolean jj_3R_63() + private boolean jj_3R_62() { if (jj_scan_token(NUMERIC)) return true; if (jj_scan_token(COMMA)) return true; @@ -6011,40 +6025,33 @@ private boolean jj_3R_101() return false; } - private boolean jj_3_51() + private boolean jj_3_66() { - if (jj_3R_43()) return true; Token xsp; xsp = jj_scanpos; - if (jj_scan_token(40)) { - jj_scanpos = xsp; - if (jj_scan_token(37)) { + if (jj_scan_token(53)) { jj_scanpos = xsp; - if (jj_scan_token(39)) return true; - } + if (jj_3R_63()) return true; } return false; } - private boolean jj_3_45() + private boolean jj_3_50() { + if (jj_3R_42()) return true; Token xsp; xsp = jj_scanpos; - if (jj_scan_token(53)) { + if (jj_scan_token(40)) { jj_scanpos = xsp; - if (jj_3R_52()) return true; + if (jj_scan_token(37)) { + jj_scanpos = xsp; + if (jj_scan_token(39)) return true; + } } return false; } - private boolean jj_3R_62() - { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; - return false; - } - - private boolean jj_3_16() + private boolean jj_3_16() { Token xsp; xsp = jj_scanpos; @@ -6067,6 +6074,13 @@ private boolean jj_3_16() return false; } + private boolean jj_3_49() + { + if (jj_3R_43()) return true; + if (jj_scan_token(WHERE)) return true; + return false; + } + private boolean jj_3R_100() { Token xsp; @@ -6078,29 +6092,21 @@ private boolean jj_3R_100() return false; } - private boolean jj_3_66() + private boolean jj_3_65() { Token xsp; xsp = jj_scanpos; if (jj_scan_token(53)) { jj_scanpos = xsp; - if (jj_3R_63()) return true; + if (jj_3R_62()) return true; } return false; } - private boolean jj_3_50() + private boolean jj_3_48() { if (jj_3R_42()) return true; - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(40)) { - jj_scanpos = xsp; - if (jj_scan_token(37)) { - jj_scanpos = xsp; - if (jj_scan_token(39)) return true; - } - } + if (jj_scan_token(WHERE)) return true; return false; } @@ -6124,20 +6130,14 @@ private boolean jj_3_17() return false; } - private boolean jj_3_49() - { - if (jj_3R_43()) return true; - if (jj_scan_token(WHERE)) return true; - return false; - } - - private boolean jj_3_65() + private boolean jj_3_67() { + if (jj_3R_42()) return true; Token xsp; xsp = jj_scanpos; - if (jj_scan_token(53)) { + if (jj_scan_token(37)) { jj_scanpos = xsp; - if (jj_3R_62()) return true; + if (jj_scan_token(39)) return true; } return false; } @@ -6148,25 +6148,6 @@ private boolean jj_3R_112() return false; } - private boolean jj_3_48() - { - if (jj_3R_42()) return true; - if (jj_scan_token(WHERE)) return true; - return false; - } - - private boolean jj_3_67() - { - if (jj_3R_42()) return true; - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(37)) { - jj_scanpos = xsp; - if (jj_scan_token(39)) return true; - } - return false; - } - private boolean jj_3_14() { Token xsp; @@ -6199,6 +6180,20 @@ private boolean jj_3_14() return false; } + private boolean jj_3R_49() + { + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; + return false; + } + + private boolean jj_3_44() + { + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; + return false; + } + private boolean jj_3_15() { Token xsp; @@ -6242,17 +6237,21 @@ private boolean jj_3R_111() return false; } - private boolean jj_3R_49() + private boolean jj_3R_61() { if (jj_scan_token(NUMERIC)) return true; if (jj_scan_token(COMMA)) return true; return false; } - private boolean jj_3_44() + private boolean jj_3_39() { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(53)) { + jj_scanpos = xsp; + if (jj_3R_49()) return true; + } return false; } @@ -6282,30 +6281,6 @@ private boolean jj_3R_105() return false; } - private boolean jj_3R_61() - { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; - return false; - } - - private boolean jj_3_39() - { - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(53)) { - jj_scanpos = xsp; - if (jj_3R_49()) return true; - } - return false; - } - - private boolean jj_3R_103() - { - if (jj_scan_token(NUMERIC)) return true; - return false; - } - private boolean jj_3R_36() { if (jj_scan_token(OPEN_BRACKET)) return true; @@ -6329,12 +6304,6 @@ private boolean jj_3R_35() return false; } - private boolean jj_3R_114() - { - if (jj_scan_token(QUOTED_STRING)) return true; - return false; - } - private boolean jj_3R_46() { if (jj_scan_token(NUMERIC)) return true; @@ -6381,6 +6350,12 @@ private boolean jj_3R_34() return false; } + private boolean jj_3R_103() + { + if (jj_scan_token(NUMERIC)) return true; + return false; + } + private boolean jj_3R_33() { if (jj_scan_token(CLOSE_PARENTHESES)) return true; @@ -6435,7 +6410,13 @@ private boolean jj_3_40() return false; } - private boolean jj_3_12() + private boolean jj_3R_114() + { + if (jj_scan_token(QUOTED_STRING)) return true; + return false; + } + + private boolean jj_3_24() { Token xsp; xsp = jj_scanpos; @@ -6443,12 +6424,8 @@ private boolean jj_3_12() jj_scanpos = xsp; if (jj_scan_token(91)) { jj_scanpos = xsp; - if (jj_scan_token(88)) { - jj_scanpos = xsp; if (jj_scan_token(92)) { jj_scanpos = xsp; - if (jj_scan_token(97)) { - jj_scanpos = xsp; if (jj_scan_token(93)) { jj_scanpos = xsp; if (jj_scan_token(96)) return true; @@ -6456,23 +6433,33 @@ private boolean jj_3_12() } } } - } + xsp = jj_scanpos; + if (jj_3R_33()) { + jj_scanpos = xsp; + if (jj_3R_34()) return true; } return false; } - private boolean jj_3R_113() + private boolean jj_3_37() { Token xsp; - if (jj_3_12()) return true; - while (true) { - xsp = jj_scanpos; - if (jj_3_12()) { jj_scanpos = xsp; break; } + xsp = jj_scanpos; + if (jj_scan_token(53)) { + jj_scanpos = xsp; + if (jj_3R_46()) return true; } return false; } - private boolean jj_3_24() + private boolean jj_3R_32() + { + if (jj_scan_token(ALPHANUMERIC)) return true; + if (jj_scan_token(OPEN_PARENTHESES)) return true; + return false; + } + + private boolean jj_3_12() { Token xsp; xsp = jj_scanpos; @@ -6480,8 +6467,12 @@ private boolean jj_3_24() jj_scanpos = xsp; if (jj_scan_token(91)) { jj_scanpos = xsp; + if (jj_scan_token(88)) { + jj_scanpos = xsp; if (jj_scan_token(92)) { jj_scanpos = xsp; + if (jj_scan_token(97)) { + jj_scanpos = xsp; if (jj_scan_token(93)) { jj_scanpos = xsp; if (jj_scan_token(96)) return true; @@ -6489,61 +6480,51 @@ private boolean jj_3_24() } } } - xsp = jj_scanpos; - if (jj_3R_33()) { - jj_scanpos = xsp; - if (jj_3R_34()) return true; - } - return false; - } - - private boolean jj_3R_107() - { - Token xsp; - xsp = jj_scanpos; - if (jj_3_13()) { - jj_scanpos = xsp; - if (jj_3R_113()) { - jj_scanpos = xsp; - if (jj_3R_114()) return true; } } return false; } - private boolean jj_3_13() + private boolean jj_3R_48() { - if (jj_3R_32()) return true; + if (jj_scan_token(NUMERIC)) return true; return false; } - private boolean jj_3_37() + private boolean jj_3R_113() { Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(53)) { - jj_scanpos = xsp; - if (jj_3R_46()) return true; + if (jj_3_12()) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3_12()) { jj_scanpos = xsp; break; } } return false; } - private boolean jj_3R_32() + private boolean jj_3R_47() { - if (jj_scan_token(ALPHANUMERIC)) return true; - if (jj_scan_token(OPEN_PARENTHESES)) return true; + if (jj_3R_77()) return true; return false; } - private boolean jj_3R_48() + private boolean jj_3R_107() { - if (jj_scan_token(NUMERIC)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_13()) { + jj_scanpos = xsp; + if (jj_3R_113()) { + jj_scanpos = xsp; + if (jj_3R_114()) return true; + } + } return false; } - private boolean jj_3R_47() + private boolean jj_3_13() { - if (jj_3R_77()) return true; + if (jj_3R_32()) return true; return false; } @@ -6597,14 +6578,6 @@ private boolean jj_3_42() return false; } - private boolean jj_3_11() - { - if (jj_scan_token(OPEN_BRACKET)) return true; - if (jj_3R_31()) return true; - if (jj_scan_token(CLOSE_BRACKET)) return true; - return false; - } - private boolean jj_3_63() { if (jj_3R_43()) return true; @@ -6636,51 +6609,77 @@ private boolean jj_3_38() return false; } - private boolean jj_3R_76() + private boolean jj_3_41() { - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(93)) { - jj_scanpos = xsp; - if (jj_scan_token(96)) return true; - } + if (jj_scan_token(OPEN_BRACKET)) return true; + if (jj_scan_token(NUMERIC)) return true; return false; } - private boolean jj_3R_75() + private boolean jj_3_23() { - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(57)) { - jj_scanpos = xsp; - if (jj_scan_token(90)) { - jj_scanpos = xsp; - if (jj_scan_token(91)) { - jj_scanpos = xsp; - if (jj_scan_token(88)) { - jj_scanpos = xsp; - if (jj_scan_token(92)) return true; - } - } - } - } + if (jj_3R_30()) return true; return false; } - private boolean jj_3_41() + private boolean jj_3_11() { if (jj_scan_token(OPEN_BRACKET)) return true; - if (jj_scan_token(NUMERIC)) return true; + if (jj_3R_31()) return true; + if (jj_scan_token(CLOSE_BRACKET)) return true; return false; } - private boolean jj_3_10() + private boolean jj_3_62() { - if (jj_3R_30()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(53)) { + jj_scanpos = xsp; + if (jj_3R_60()) return true; + } return false; } - private boolean jj_3_23() + private boolean jj_3R_76() + { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(93)) { + jj_scanpos = xsp; + if (jj_scan_token(96)) return true; + } + return false; + } + + private boolean jj_3R_59() + { + if (jj_3R_43()) return true; + if (jj_scan_token(COMMA)) return true; + return false; + } + + private boolean jj_3R_75() + { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(57)) { + jj_scanpos = xsp; + if (jj_scan_token(90)) { + jj_scanpos = xsp; + if (jj_scan_token(91)) { + jj_scanpos = xsp; + if (jj_scan_token(88)) { + jj_scanpos = xsp; + if (jj_scan_token(92)) return true; + } + } + } + } + return false; + } + + private boolean jj_3_10() { if (jj_3R_30()) return true; return false; @@ -6702,24 +6701,17 @@ private boolean jj_3R_43() return false; } - private boolean jj_3_62() + private boolean jj_3_61() { Token xsp; xsp = jj_scanpos; if (jj_scan_token(53)) { jj_scanpos = xsp; - if (jj_3R_60()) return true; + if (jj_3R_59()) return true; } return false; } - private boolean jj_3R_59() - { - if (jj_3R_43()) return true; - if (jj_scan_token(COMMA)) return true; - return false; - } - private boolean jj_3R_72() { Token xsp; @@ -6751,6 +6743,13 @@ private boolean jj_3R_71() return false; } + private boolean jj_3R_58() + { + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; + return false; + } + private boolean jj_3R_28() { Token xsp; @@ -6762,18 +6761,18 @@ private boolean jj_3R_28() return false; } - private boolean jj_3_61() + private boolean jj_3_60() { Token xsp; xsp = jj_scanpos; if (jj_scan_token(53)) { jj_scanpos = xsp; - if (jj_3R_59()) return true; + if (jj_3R_58()) return true; } return false; } - private boolean jj_3R_58() + private boolean jj_3R_45() { if (jj_scan_token(NUMERIC)) return true; if (jj_scan_token(COMMA)) return true; @@ -6793,33 +6792,39 @@ private boolean jj_3_9() return false; } - private boolean jj_3_60() + private boolean jj_3_7() + { + if (jj_3R_27()) return true; + return false; + } + + private boolean jj_3_36() { Token xsp; xsp = jj_scanpos; if (jj_scan_token(53)) { jj_scanpos = xsp; - if (jj_3R_58()) return true; + if (jj_3R_45()) return true; } return false; } - private boolean jj_3R_45() + private boolean jj_3_4() { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; + if (jj_3R_27()) return true; return false; } - private boolean jj_3_7() + private boolean jj_3R_69() { - if (jj_3R_27()) return true; + if (jj_scan_token(ORDER)) return true; return false; } - private boolean jj_3_4() + private boolean jj_3R_44() { - if (jj_3R_27()) return true; + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; return false; } @@ -6835,30 +6840,6 @@ private boolean jj_3_5() return false; } - private boolean jj_3_36() - { - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(53)) { - jj_scanpos = xsp; - if (jj_3R_45()) return true; - } - return false; - } - - private boolean jj_3R_69() - { - if (jj_scan_token(ORDER)) return true; - return false; - } - - private boolean jj_3R_44() - { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; - return false; - } - private boolean jj_3_59() { if (jj_scan_token(NUMERIC)) return true; @@ -6915,30 +6896,12 @@ private boolean jj_3R_89() return false; } - private boolean jj_3R_99() - { - if (jj_3R_29()) return true; - if (jj_3R_94()) return true; - Token xsp; - xsp = jj_scanpos; - if (jj_3_7()) jj_scanpos = xsp; - return false; - } - private boolean jj_3R_81() { if (jj_3R_90()) return true; return false; } - private boolean jj_3R_98() - { - if (jj_3R_108()) return true; - if (jj_3R_109()) return true; - if (jj_3R_109()) return true; - return false; - } - private boolean jj_3R_80() { if (jj_3R_89()) return true; @@ -6956,16 +6919,6 @@ private boolean jj_3_57() return false; } - private boolean jj_3R_97() - { - if (jj_3R_106()) return true; - if (jj_3R_107()) return true; - Token xsp; - xsp = jj_scanpos; - if (jj_3_5()) jj_scanpos = xsp; - return false; - } - private boolean jj_3R_70() { Token xsp; @@ -6977,52 +6930,21 @@ private boolean jj_3R_70() return false; } - private boolean jj_3R_96() - { - if (jj_3R_104()) return true; - if (jj_3R_105()) return true; - Token xsp; - xsp = jj_scanpos; - if (jj_3_4()) jj_scanpos = xsp; - return false; - } - - private boolean jj_3R_95() - { - if (jj_3R_102()) return true; - if (jj_3R_103()) return true; - Token xsp; - xsp = jj_scanpos; - if (jj_3_3()) jj_scanpos = xsp; - return false; - } - - private boolean jj_3R_93() + private boolean jj_3R_99() { - if (jj_3R_43()) return true; + if (jj_3R_29()) return true; + if (jj_3R_94()) return true; Token xsp; xsp = jj_scanpos; - if (jj_3R_95()) { - jj_scanpos = xsp; - if (jj_3R_96()) { - jj_scanpos = xsp; - if (jj_3R_97()) { - jj_scanpos = xsp; - if (jj_3R_98()) { - jj_scanpos = xsp; - if (jj_3R_99()) return true; - } - } - } - } + if (jj_3_7()) jj_scanpos = xsp; return false; } - private boolean jj_3_8() + private boolean jj_3R_98() { - if (jj_3R_28()) return true; - if (jj_3R_29()) return true; - if (jj_3R_94()) return true; + if (jj_3R_108()) return true; + if (jj_3R_109()) return true; + if (jj_3R_109()) return true; return false; } @@ -7052,6 +6974,16 @@ private boolean jj_3_21() return false; } + private boolean jj_3R_97() + { + if (jj_3R_106()) return true; + if (jj_3R_107()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_5()) jj_scanpos = xsp; + return false; + } + private boolean jj_3R_55() { Token xsp; @@ -7083,13 +7015,43 @@ private boolean jj_3R_55() return false; } - private boolean jj_3R_92() + private boolean jj_3R_96() { + if (jj_3R_104()) return true; + if (jj_3R_105()) return true; Token xsp; xsp = jj_scanpos; - if (jj_3_8()) { + if (jj_3_4()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_95() + { + if (jj_3R_102()) return true; + if (jj_3R_103()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3_3()) jj_scanpos = xsp; + return false; + } + + private boolean jj_3R_93() + { + if (jj_3R_43()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_3R_95()) { jj_scanpos = xsp; - if (jj_3R_93()) return true; + if (jj_3R_96()) { + jj_scanpos = xsp; + if (jj_3R_97()) { + jj_scanpos = xsp; + if (jj_3R_98()) { + jj_scanpos = xsp; + if (jj_3R_99()) return true; + } + } + } } return false; } @@ -7127,6 +7089,14 @@ private boolean jj_3R_74() return false; } + private boolean jj_3_8() + { + if (jj_3R_28()) return true; + if (jj_3R_29()) return true; + if (jj_3R_94()) return true; + return false; + } + private boolean jj_3_22() { Token xsp; @@ -7153,6 +7123,17 @@ private boolean jj_3_22() return false; } + private boolean jj_3R_92() + { + Token xsp; + xsp = jj_scanpos; + if (jj_3_8()) { + jj_scanpos = xsp; + if (jj_3R_93()) return true; + } + return false; + } + private boolean jj_3R_73() { if (jj_scan_token(OPEN_BRACKET)) return true; @@ -7173,18 +7154,6 @@ private boolean jj_3R_41() return false; } - private boolean jj_3R_79() - { - if (jj_scan_token(CONJUNCTION)) return true; - return false; - } - - private boolean jj_3R_68() - { - if (jj_scan_token(DISJUNCTION)) return true; - return false; - } - private boolean jj_3R_42() { Token xsp; @@ -7196,13 +7165,6 @@ private boolean jj_3R_42() return false; } - private boolean jj_3R_91() - { - if (jj_scan_token(NAVIGATION_SCOPE_OPEN)) return true; - if (jj_3R_22()) return true; - return false; - } - private boolean jj_3R_66() { if (jj_scan_token(NUMERIC)) return true; @@ -7233,39 +7195,67 @@ private boolean jj_3R_85() return false; } - private boolean jj_3R_26() + private boolean jj_3R_40() { - if (jj_3R_70()) return true; + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; return false; } - private boolean jj_3R_88() + private boolean jj_3_34() { - if (jj_3R_92()) return true; + if (jj_scan_token(NUMERIC)) return true; + if (jj_scan_token(COMMA)) return true; return false; } - private boolean jj_3R_40() + private boolean jj_3R_79() { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; + if (jj_scan_token(CONJUNCTION)) return true; return false; } - private boolean jj_3_34() + private boolean jj_3R_68() { - if (jj_scan_token(NUMERIC)) return true; - if (jj_scan_token(COMMA)) return true; + if (jj_scan_token(DISJUNCTION)) return true; return false; } - private boolean jj_3R_87() + private boolean jj_3R_84() { - if (jj_scan_token(OPEN_PARENTHESES)) return true; + if (jj_scan_token(OPEN_BRACKET)) return true; + return false; + } + + private boolean jj_3R_91() + { + if (jj_scan_token(NAVIGATION_SCOPE_OPEN)) return true; if (jj_3R_22()) return true; return false; } + private boolean jj_3_30() + { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(53)) { + jj_scanpos = xsp; + if (jj_3R_41()) return true; + } + return false; + } + + private boolean jj_3R_77() + { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_84()) { + jj_scanpos = xsp; + if (jj_3R_85()) return true; + } + return false; + } + private boolean jj_3_20() { Token xsp; @@ -7282,7 +7272,10 @@ private boolean jj_3_20() jj_scanpos = xsp; if (jj_scan_token(97)) { jj_scanpos = xsp; - if (jj_scan_token(96)) return true; + if (jj_scan_token(96)) { + jj_scanpos = xsp; + if (jj_scan_token(82)) return true; + } } } } @@ -7292,9 +7285,50 @@ private boolean jj_3_20() return false; } - private boolean jj_3R_84() + private boolean jj_3R_31() { - if (jj_scan_token(OPEN_BRACKET)) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(55)) jj_scanpos = xsp; + if (jj_3_20()) return true; + while (true) { + xsp = jj_scanpos; + if (jj_3_20()) { jj_scanpos = xsp; break; } + } + return false; + } + + private boolean jj_3R_26() + { + if (jj_3R_70()) return true; + return false; + } + + private boolean jj_3R_88() + { + if (jj_3R_92()) return true; + return false; + } + + private boolean jj_3_33() + { + if (jj_3R_43()) return true; + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(40)) { + jj_scanpos = xsp; + if (jj_scan_token(37)) { + jj_scanpos = xsp; + if (jj_scan_token(39)) return true; + } + } + return false; + } + + private boolean jj_3R_87() + { + if (jj_scan_token(OPEN_PARENTHESES)) return true; + if (jj_3R_22()) return true; return false; } @@ -7344,38 +7378,21 @@ private boolean jj_3_19() return false; } - private boolean jj_3R_31() - { - Token xsp; - xsp = jj_scanpos; - if (jj_scan_token(55)) jj_scanpos = xsp; - if (jj_3_20()) return true; - while (true) { - xsp = jj_scanpos; - if (jj_3_20()) { jj_scanpos = xsp; break; } - } - return false; - } - - private boolean jj_3_30() + private boolean jj_3_29() { Token xsp; xsp = jj_scanpos; if (jj_scan_token(53)) { jj_scanpos = xsp; - if (jj_3R_41()) return true; + if (jj_3R_40()) return true; } return false; } - private boolean jj_3R_77() + private boolean jj_3_54() { - Token xsp; - xsp = jj_scanpos; - if (jj_3R_84()) { - jj_scanpos = xsp; - if (jj_3R_85()) return true; - } + if (jj_3R_55()) return true; + if (jj_scan_token(QUOTED_STRING)) return true; return false; } @@ -7396,9 +7413,9 @@ private boolean jj_3R_67() return false; } - private boolean jj_3_33() + private boolean jj_3_32() { - if (jj_3R_43()) return true; + if (jj_3R_42()) return true; Token xsp; xsp = jj_scanpos; if (jj_scan_token(40)) { @@ -7446,7 +7463,7 @@ private static void jj_la1_init_1() { jj_la1_1 = new int[] {0x1000000,0x0,0x0,0x1f,0x0,0x1000000,0x0,0x0,0x1f,0x0,0x8000000,0x4000000,0x2080000,0xf0000000,0x2000000,0x8000000,0x4000000,0x2080000,0xf0000000,0x2000000,0x2000000,0x0,0x2000000,0x2000000,0x0,0x2000000,0x0,0x0,0x0,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,0xd0000000,0x0,0x0,0x800000,0x0,0x800400,0x0,0x0,0x800400,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc2000000,0x800000,0xc0000000,0x0,0x0,0x0,0x0,0x100000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x100000,0x0,0x1f,0x0,0x2a0,0x0,0x2a0,0xa0,0x0,0x1a0,0x0,0x1a0,0x0,0x1a0,0x0,0x0,0xa0,0xa0,0x2e0,0x0,0x2e0,0x0,0x0,0x0,0x800400,0x1a0,0x800400,0x0,0x0,0x800400,0x0,0x0,0x0,0x800400,0x0,0x0,0x1000000,0x1a0,0x800400,0x0,0x0,0x800400,0x0,0x0,0x0,0x800400,0x0,0x0,0x1000000,0x800400,0x0,0x0,0x800400,0x800400,0x1000000,0x0,0x800400,0x0,0x0,0x800400,0x0,0x0,0x1a0,0x800400,0x0,0x0,0x800400,0x0,0x0,0x0,0x1a0,0x800400,0x0,0x0,0x800400,0x0,0x0,0x0,0x800400,0x0,0x0,0x1a0,0x800400,0x0,0x0,0x800400,0x0,0x0,0x0,0x800400,0x800400,0x800400,0x10001a0,0x0,0x800400,0x0,0x0,0x800400,0x800400,0x800400,0xa0,0x800400,0x2000000,0x800400,0xa0,0x200,0x100,0xa0,0x200,0x200,0x200,0x2000000,0x0,0x4000,0x800400,0x2000000,0x800400,0x1a0,0x800400,0x800400,0x0,0x10001a0,0xa0,0xa0,0x200,0x100,0x800400,0x800400,0x0,0x1,0xa0,0x0,0xa0,0x0,0x2000000,0x1c,0x0,0x0,0x1a0,0x800400,0x1a0,0x800400,0x800400,0x800400,0x1000000,0x0,0x0,0x200000,0x0,0x0,0x200000,0x0,0x0,0x2200000,}; } private static void jj_la1_init_2() { - jj_la1_2 = new int[] {0x0,0x8000,0x7000,0x0,0x3d00f000,0x0,0x8000,0x7000,0x0,0x3d00f000,0x0,0x0,0x7d000000,0xc07c0,0x3d000000,0x0,0x0,0x7d000000,0xc07c0,0x3d000000,0x1d000000,0x20000000,0x3d000000,0x1d000000,0x20000000,0x3d000000,0x3d000000,0x3d200000,0x3d000000,0x3d000000,0x3d200000,0x1d000000,0x1d200000,0x3d000000,0x3d200000,0x780,0x0,0xc0000,0x1d200000,0x0,0x1d200000,0x0,0x1d200000,0x1d200000,0x0,0x1d200000,0x4000,0x3000,0x7000,0x3000,0x2000000,0x30000,0x3d000000,0x0,0x0,0x30000,0x10000000,0x3d000000,0x3c000000,0x2000000,0x3c000000,0x5000000,0x2000000,0x5000000,0x5000000,0x2000000,0x5000000,0x5000000,0x3c000000,0x2000000,0x3c000000,0x0,0x0,0x0,0x1000000,0x0,0x0,0x1000000,0x0,0x1000000,0x0,0x1000000,0x0,0x1000000,0x1000000,0x0,0x0,0x0,0x1000000,0x0,0x0,0x1000000,0x0,0x0,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x8000,0x7000,0x0,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x8000,0x7000,0x0,0x0,0x8000,0x7000,0x0,0x0,0x0,0x1000000,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x8000,0x7000,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x0,0x0,0x0,0x1000000,0x0,0x8000,0x7000,0x0,0x0,0x0,0x0,0x0,0x3d000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3d000000,0x1000000,0x0,0x0,0x3d000000,0x0,0x0,0x0,0x0,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000000,0x0,0x0,0x1000000,0x0,0x1000000,0x3d000000,0x0,0x1000000,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2000000,0x2000000,0x3d200000,0x2000000,0x2000000,0x1000000,0x2000000,0x2000000,0x3d000000,}; + jj_la1_2 = new int[] {0x0,0x8000,0x7000,0x0,0x3d00f000,0x0,0x8000,0x7000,0x0,0x3d00f000,0x0,0x0,0x7d000000,0xc07c0,0x3d000000,0x0,0x0,0x7d000000,0xc07c0,0x3d000000,0x1d000000,0x20000000,0x3d000000,0x1d000000,0x20000000,0x3d000000,0x3d000000,0x3d200000,0x3d000000,0x3d000000,0x3d200000,0x1d000000,0x1d200000,0x3d000000,0x3d200000,0x780,0x0,0xc0000,0x1d200000,0x0,0x1d240000,0x0,0x1d200000,0x1d200000,0x0,0x1d200000,0x4000,0x3000,0x7000,0x3000,0x2000000,0x30000,0x3d000000,0x0,0x0,0x30000,0x10000000,0x3d000000,0x3c000000,0x2000000,0x3c000000,0x5000000,0x2000000,0x5000000,0x5000000,0x2000000,0x5000000,0x5000000,0x3c000000,0x2000000,0x3c000000,0x0,0x0,0x0,0x1000000,0x0,0x0,0x1000000,0x0,0x1000000,0x0,0x1000000,0x0,0x1000000,0x1000000,0x0,0x0,0x0,0x1000000,0x0,0x0,0x1000000,0x0,0x0,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x8000,0x7000,0x0,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x8000,0x7000,0x0,0x0,0x8000,0x7000,0x0,0x0,0x0,0x1000000,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x8000,0x7000,0x0,0x0,0x8000,0x7000,0x0,0x8000,0x7000,0x1000000,0x0,0x0,0x0,0x0,0x1000000,0x0,0x8000,0x7000,0x0,0x0,0x0,0x0,0x0,0x3d000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3d000000,0x1000000,0x0,0x0,0x3d000000,0x0,0x0,0x0,0x0,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000000,0x0,0x0,0x1000000,0x0,0x1000000,0x3d000000,0x0,0x1000000,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2000000,0x2000000,0x3d200000,0x2000000,0x2000000,0x1000000,0x2000000,0x2000000,0x3d000000,}; } private static void jj_la1_init_3() { jj_la1_3 = new int[] {0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x1,0x0,0x1,0x0,0x0,0x1,0x0,0x1,0x0,0x1,0x1,0x0,0x1,0x1,0x3,0x3,0x183,0x183,0x183,0x3,0x3,0x3,0x3,0x0,0x0,0x0,0x3,0x0,0x3,0x0,0x3,0x3,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x1,0x1,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x1,}; diff --git a/src/main/java/com/cinchapi/ccl/grammar/ModificationKeySymbol.java b/src/main/java/com/cinchapi/ccl/grammar/ModificationKeySymbol.java new file mode 100644 index 0000000..178749c --- /dev/null +++ b/src/main/java/com/cinchapi/ccl/grammar/ModificationKeySymbol.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2013-2026 Cinchapi Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.cinchapi.ccl.grammar; + +import java.util.Objects; + +import com.google.common.base.Preconditions; + +/** + * A {@link ModificationKeySymbol} pairs a {@link KeyTokenSymbol} with a + * timestamp that marks an addition-relative binding: the key binds to the + * values whose add occurred in the half-open interval + * {@code [timestamp, now)}, start-inclusive at the timestamp and + * end-exclusive at the present moment ({@code key[~]}). + * + *

This differs from a {@link TemporalKeySymbol}, which binds the single + * value present at the instant regardless of when it was added, + * and from a {@link TemporalRangeKeySymbol}, which binds the values + * present during an interval regardless of their add time. A + * value added before the timestamp is not in range here even if it is + * still present. + * + *

This symbol records the endpoint only; it does not decide what a + * read returns. That evaluation lives server-side and is out of scope for + * the grammar layer. + * + *

As with {@link TemporalKeySymbol}, a {@link NavigationKeySymbol} is + * never wrapped and an already-parameterized key is rejected, enforcing + * the "at most one bracket-timestamp parameter per key" invariant the + * rest of the AST relies on. + * + * @author Jeff Nelson + */ +public final class ModificationKeySymbol + extends KeyTokenSymbol> { + + /** + * The inclusive start of the addition window; the end of the window is + * the (exclusive) present moment. + */ + private final TimestampSymbol timestamp; + + /** + * Construct a new {@link ModificationKeySymbol}. + * + * @param key the wrapped {@link KeyTokenSymbol}; must not be a + * {@link NavigationKeySymbol} (navigation timestamps live + * on the path's stops) and must not already be + * parameterized (a key carries at most one + * bracket-timestamp parameter) + * @param timestamp the inclusive start of the addition window + * @throws IllegalArgumentException if {@code key} is a + * {@link NavigationKeySymbol} or already parameterized + */ + public ModificationKeySymbol(KeyTokenSymbol key, + TimestampSymbol timestamp) { + super(Preconditions.checkNotNull(key)); + Preconditions.checkArgument(!(key instanceof NavigationKeySymbol), + "ModificationKeySymbol cannot wrap a NavigationKeySymbol; " + + "modification bindings on navigation keys are not " + + "yet supported"); + Preconditions.checkArgument(!key.isParameterized(), + "ModificationKeySymbol cannot wrap an already-parameterized " + + "key; a key carries at most one bracket-timestamp " + + "parameter"); + this.timestamp = Preconditions.checkNotNull(timestamp); + } + + /** + * Return the inclusive start of the addition window. + * + * @return the timestamp {@link TimestampSymbol} + */ + public TimestampSymbol timestamp() { + return timestamp; + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + else if(!(obj instanceof ModificationKeySymbol)) { + return false; + } + ModificationKeySymbol other = (ModificationKeySymbol) obj; + return key.equals(other.key) && timestamp.equals(other.timestamp); + } + + @Override + public int hashCode() { + return Objects.hash(key, timestamp); + } + + @Override + public String toString() { + return key.toString() + "[" + timestamp.timestamp() + "~]"; + } + + @Override + public String baseKey() { + return key.baseKey(); + } + + @Override + public boolean isParameterized() { + return true; + } + + @Override + public KeyTokenSymbol stripParameters() { + return key.stripParameters(); + } + +} diff --git a/src/test/java/com/cinchapi/ccl/BracketTimestampCommandTest.java b/src/test/java/com/cinchapi/ccl/BracketTimestampCommandTest.java index bb706fc..9709a18 100644 --- a/src/test/java/com/cinchapi/ccl/BracketTimestampCommandTest.java +++ b/src/test/java/com/cinchapi/ccl/BracketTimestampCommandTest.java @@ -28,6 +28,7 @@ import com.cinchapi.ccl.grammar.NavigationKeyStop; import com.cinchapi.ccl.grammar.OrderComponentSymbol; import com.cinchapi.ccl.grammar.OrderSymbol; +import com.cinchapi.ccl.grammar.ModificationKeySymbol; import com.cinchapi.ccl.grammar.TemporalKeySymbol; import com.cinchapi.ccl.grammar.TemporalRangeKeySymbol; import com.cinchapi.ccl.grammar.command.BrowseSymbol; @@ -218,6 +219,49 @@ public void testAuditRejectsRangeKey() { "audit command"); } + @Test + public void testSelectModificationKeyBracket() { + SelectSymbol cmd = parseCommand( + String.format("select locked[%d~] from 1", T1), + SelectSymbol.class); + assertModificationKey(cmd.keys().iterator().next(), "locked", T1); + } + + @Test + public void testGetModificationKeyBracket() { + GetSymbol cmd = parseCommand( + String.format("get locked[%d~] from 1", T1), + GetSymbol.class); + assertModificationKey(cmd.keys().iterator().next(), "locked", T1); + } + + @Test + public void testFindModificationKeyBracket() { + AbstractSyntaxTree tree = compiler().parse( + String.format("find locked[%d~] = \"pod-A\"", T1)); + Assert.assertTrue(tree instanceof CommandTree); + com.cinchapi.ccl.syntax.ConditionTree cond = + ((CommandTree) tree).conditionTree(); + com.cinchapi.ccl.grammar.ExpressionSymbol expr = + (com.cinchapi.ccl.grammar.ExpressionSymbol) + ((com.cinchapi.ccl.syntax.ExpressionTree) cond).root(); + assertModificationKey(expr.key(), "locked", T1); + } + + @Test + public void testAddRejectsModificationKey() { + assertCommandRejected( + String.format("add locked[%d~] as \"pod-A\" in 1", T1), + "add command"); + } + + @Test + public void testAuditRejectsModificationKey() { + assertCommandRejected( + String.format("audit locked[%d~] in 1", T1), + "audit command"); + } + @Test public void testAddRejectsBracketKey() { assertCommandRejected( @@ -535,6 +579,19 @@ private void assertTemporalRangeKey(KeyTokenSymbol key, Assert.assertEquals(expectedEnd, range.end().timestamp()); } + private void assertModificationKey(KeyTokenSymbol key, + String expectedKey, long expectedTs) { + Assert.assertTrue( + "expected ModificationKeySymbol but got " + + key.getClass().getSimpleName(), + key instanceof ModificationKeySymbol); + ModificationKeySymbol modification = (ModificationKeySymbol) key; + Assert.assertTrue(modification.key() instanceof KeySymbol); + Assert.assertEquals(expectedKey, + ((KeySymbol) modification.key()).key()); + Assert.assertEquals(expectedTs, modification.timestamp().timestamp()); + } + private static final Function VALUE_FN = Convert::stringToJava; private static final Function OP_FN = Convert::stringToOperator; diff --git a/src/test/java/com/cinchapi/ccl/BracketTimestampMatrixTest.java b/src/test/java/com/cinchapi/ccl/BracketTimestampMatrixTest.java index 0fa83b3..4ec015e 100644 --- a/src/test/java/com/cinchapi/ccl/BracketTimestampMatrixTest.java +++ b/src/test/java/com/cinchapi/ccl/BracketTimestampMatrixTest.java @@ -26,6 +26,7 @@ import com.cinchapi.ccl.grammar.ExpressionSymbol; import com.cinchapi.ccl.grammar.KeySymbol; import com.cinchapi.ccl.grammar.KeyTokenSymbol; +import com.cinchapi.ccl.grammar.ModificationKeySymbol; import com.cinchapi.ccl.grammar.NavigationKeyStop; import com.cinchapi.ccl.grammar.NavigationKeySymbol; import com.cinchapi.ccl.grammar.Symbol; @@ -347,6 +348,178 @@ public void testRoundTripRangeResolvedEndpoints() { assertRoundTrip("foo[last week...] = \"X\""); } + /** + * Goal: Verify that an addition-marker on a flat leaf + * ({@code foo[t1~] = X}) parses to a {@link ModificationKeySymbol} + * carrying the marked timestamp. The half-open {@code [t1, now)} + * add-window intent is documented here even though the parser only + * records the endpoint: a value added at or after {@code t1} is in + * range, a value added before {@code t1} (even if still present) is + * not. + */ + @Test + public void testM1_AfterMarkerNumeric() { + String ccl = String.format("foo[%d~] = \"X\"", T1); + ModificationKeySymbol modification = assertLeafKeyModification( + parseExpression(ccl), "foo"); + Assert.assertEquals(T1, modification.timestamp().timestamp()); + } + + /** + * Goal: Verify that an addition-marker whose + * timestamp is a quoted natural-language phrase + * ({@code foo["last week"~] = X}) parses to a + * {@link ModificationKeySymbol}. This exercises the detached + * {@code ~} token path (the marker is lexed as a {@code SEARCH_MATCH} + * separate from the quoted timestamp), which the numeric case, where + * {@code ~} attaches to the digits, does not. Compared at day + * precision because the phrase is anchored to the current instant. + */ + @Test + public void testM2_AfterMarkerQuotedNaturalLanguage() { + TimestampSymbol expected = new TimestampSymbol( + NaturalLanguage.parseMicros("last week"), TimeUnit.DAYS); + ModificationKeySymbol modification = assertLeafKeyModification( + parseExpression("foo[\"last week\"~] = \"X\""), "foo"); + Assert.assertEquals(expected, modification.timestamp()); + } + + /** + * Goal: Verify that the optional {@code at} keyword + * is accepted in front of the marked timestamp + * ({@code foo[at t1~] = X}) and yields the same + * {@link ModificationKeySymbol} as the keyword-less form. + */ + @Test + public void testM3_AfterMarkerKeywordEquivalence() { + ModificationKeySymbol withKeyword = assertLeafKeyModification( + parseExpression(String.format("foo[at %d~] = \"X\"", T1)), + "foo"); + Assert.assertEquals(T1, withKeyword.timestamp().timestamp()); + } + + /** + * Goal: Verify that the leading before-marker form + * ({@code foo[~t1] = X}) is rejected at parse time, since the + * added-before semantic is not yet supported. + */ + @Test + public void testM4_BeforeMarkerRejected() { + String ccl = String.format("foo[~%d] = \"X\"", T1); + try { + compiler().parse(ccl); + Assert.fail("expected SyntaxException for before-marker in: " + + ccl); + } + catch (SyntaxException e) { + Assert.assertTrue( + "expected message to mention 'not yet supported' but " + + "was: " + e.getMessage(), + e.getMessage().contains("not yet supported")); + } + } + + /** + * Goal: Verify that combining a range with an + * addition-marker ({@code foo[t1...t2~] = X}) is rejected, since a + * marker binds a single instant rather than a range. + */ + @Test + public void testM5_RangeWithMarkerRejected() { + String ccl = String.format("foo[%d...%d~] = \"X\"", T1, T2); + try { + compiler().parse(ccl); + Assert.fail("expected SyntaxException for range-with-marker in: " + + ccl); + } + catch (SyntaxException e) { + Assert.assertTrue( + "expected message to mention 'single instant, not a " + + "range' but was: " + e.getMessage(), + e.getMessage().contains("single instant, not a range")); + } + } + + /** + * Goal: Verify that a marker bracket followed by a + * second bracket on one leaf ({@code foo[t1~][t2] = X}) is rejected, + * since {@code Key()} accepts at most one trailing bracket. + */ + @Test + public void testM6_MarkerBeforeSingleBracketRejected() { + String ccl = String.format("foo[%d~][%d] = \"X\"", T1, T2); + try { + compiler().parse(ccl); + Assert.fail("expected SyntaxException for double bracket with " + + "marker in: " + ccl); + } + catch (SyntaxException e) { + // Pass — Key()'s optional bracket cannot fire twice. + } + } + + /** + * Goal: Verify lossless round-trip of the addition + * marker. The canonical {@code toString()} renders the timestamp as + * resolved microseconds with a trailing {@code ~}, so re-parsing the + * re-emitted form must yield an equal AST. + */ + @Test + public void testRoundTripModification() { + assertRoundTrip(String.format("foo[%d~] = \"X\"", T1)); + assertRoundTrip("foo[\"last week\"~] = \"X\""); + } + + /** + * Goal: Verify that a {@code ~} appearing + * inside a quoted timestamp ({@code foo["last week ~"] = X}) + * is not mistaken for a trailing modification marker. The marker is + * recognized only when {@code ~} is the final, unquoted character of + * the bracket content; because the closing quote is the last + * character here, the content resolves as an ordinary single-instant + * timestamp and yields a {@link TemporalKeySymbol}, not a + * {@link ModificationKeySymbol}. + */ + @Test + public void testM7_QuotedTildeIsNotMarker() { + KeyTokenSymbol key = parseExpression("foo[\"last week ~\"] = \"X\"") + .key(); + Assert.assertFalse( + "a '~' inside quotes must not be read as a modification " + + "marker, but parsed to a ModificationKeySymbol", + key instanceof ModificationKeySymbol); + Assert.assertTrue( + "expected TemporalKeySymbol but was " + + key.getClass().getSimpleName(), + key instanceof TemporalKeySymbol); + Assert.assertEquals("foo", + ((KeySymbol) ((TemporalKeySymbol) key).key()).key().toString()); + } + + /** + * Goal: Verify that the {@code search_match} / + * {@code contains} keyword spellings of the {@code SEARCH_MATCH} + * token are rejected inside a key bracket. The token is admitted only + * to capture the standalone {@code ~} marker; a keyword spelling is + * meaningless here and must fail at parse time with a clear message + * rather than fall through to a confusing timestamp-parse error. + */ + @Test + public void testM8_SearchKeywordInBracketRejected() { + String ccl = String.format("foo[%d contains] = \"X\"", T1); + try { + compiler().parse(ccl); + Assert.fail("expected SyntaxException for search keyword in " + + "bracket in: " + ccl); + } + catch (SyntaxException e) { + Assert.assertTrue( + "expected message to mention the '~' marker but was: " + + e.getMessage(), + e.getMessage().contains("'~' modification marker")); + } + } + /** * Goal: Verify that an unbracketed navigation key * ({@code a.foo = X}) parses to a {@link NavigationKeySymbol} @@ -1079,6 +1252,20 @@ private void assertLeafKeyTemporalRange(ExpressionSymbol expr, Assert.assertEquals(expectedEnd, range.end().timestamp()); } + private ModificationKeySymbol assertLeafKeyModification( + ExpressionSymbol expr, String expectedKey) { + KeyTokenSymbol key = expr.key(); + Assert.assertTrue( + "expected ModificationKeySymbol but was " + + key.getClass().getSimpleName(), + key instanceof ModificationKeySymbol); + ModificationKeySymbol modification = (ModificationKeySymbol) key; + Assert.assertTrue(modification.key() instanceof KeySymbol); + Assert.assertEquals(expectedKey, + ((KeySymbol) modification.key()).key().toString()); + return modification; + } + private void assertLeafKeyUnstamped(AbstractSyntaxTree leaf) { ExpressionTree exprTree = (ExpressionTree) leaf; ExpressionSymbol expr = (ExpressionSymbol) exprTree.root(); diff --git a/src/test/java/com/cinchapi/ccl/grammar/ModificationKeySymbolTest.java b/src/test/java/com/cinchapi/ccl/grammar/ModificationKeySymbolTest.java new file mode 100644 index 0000000..b0d2415 --- /dev/null +++ b/src/test/java/com/cinchapi/ccl/grammar/ModificationKeySymbolTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2013-2026 Cinchapi Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.cinchapi.ccl.grammar; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit tests for {@link ModificationKeySymbol}. + * + * @author Jeff Nelson + */ +public class ModificationKeySymbolTest { + + @Test(expected = NullPointerException.class) + public void testConstructRejectsNullKey() { + new ModificationKeySymbol(null, new TimestampSymbol(1L)); + } + + @Test(expected = NullPointerException.class) + public void testConstructRejectsNullTimestamp() { + new ModificationKeySymbol(new KeySymbol("foo"), null); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructRejectsNavigationKeySymbolWrapping() { + new ModificationKeySymbol(new NavigationKeySymbol("a.b"), + new TimestampSymbol(1L)); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructRejectsParameterizedKeyWrapping() { + TemporalKeySymbol inner = new TemporalKeySymbol(new KeySymbol("foo"), + new TimestampSymbol(1L)); + new ModificationKeySymbol(inner, new TimestampSymbol(2L)); + } + + @Test + public void testTimestampAccessor() { + TimestampSymbol ts = new TimestampSymbol(1L); + ModificationKeySymbol symbol = new ModificationKeySymbol( + new KeySymbol("foo"), ts); + Assert.assertSame(ts, symbol.timestamp()); + } + + @Test + public void testToString() { + ModificationKeySymbol symbol = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(123L)); + Assert.assertEquals("foo[123~]", symbol.toString()); + } + + @Test + public void testEqualsWhenKeyAndTimestampMatch() { + ModificationKeySymbol a = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(1L)); + ModificationKeySymbol b = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(1L)); + Assert.assertEquals(a, b); + Assert.assertEquals(a.hashCode(), b.hashCode()); + } + + @Test + public void testNotEqualsWhenTimestampDiffers() { + ModificationKeySymbol a = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(1L)); + ModificationKeySymbol b = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(2L)); + Assert.assertNotEquals(a, b); + } + + @Test + public void testNotEqualsWhenKeyDiffers() { + ModificationKeySymbol a = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(1L)); + ModificationKeySymbol b = new ModificationKeySymbol( + new KeySymbol("bar"), new TimestampSymbol(1L)); + Assert.assertNotEquals(a, b); + } + + @Test + public void testNotEqualsToTemporalKeySymbol() { + ModificationKeySymbol modification = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(1L)); + Assert.assertNotEquals(modification, + new TemporalKeySymbol(new KeySymbol("foo"), + new TimestampSymbol(1L))); + } + + @Test + public void testNotEqualsToTemporalRangeKeySymbol() { + ModificationKeySymbol modification = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(1L)); + Assert.assertNotEquals(modification, + new TemporalRangeKeySymbol(new KeySymbol("foo"), + new TimestampSymbol(1L), null)); + } + + @Test + public void testBaseKeyDelegatesToWrappedKey() { + ModificationKeySymbol symbol = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(1L)); + Assert.assertEquals("foo", symbol.baseKey()); + } + + @Test + public void testIsParameterized() { + ModificationKeySymbol symbol = new ModificationKeySymbol( + new KeySymbol("foo"), new TimestampSymbol(1L)); + Assert.assertTrue(symbol.isParameterized()); + } + + @Test + public void testStripParametersReturnsWrappedKey() { + KeySymbol inner = new KeySymbol("foo"); + ModificationKeySymbol symbol = new ModificationKeySymbol(inner, + new TimestampSymbol(1L)); + Assert.assertEquals(inner, symbol.stripParameters()); + } + +}