@@ -182,7 +182,10 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
182182 }
183183
184184 private void captureOpaqueStatements(Statements statements) throws ParseException {
185- while (getAsBoolean(Feature.allowUnsupportedStatements) && isOpaqueStatementAhead()) {
185+ if (!getAsBoolean(Feature.allowUnsupportedStatements)) {
186+ return;
187+ }
188+ while (isOpaqueStatementAhead()) {
186189 statements.add(UnsupportedStatement());
187190 while (getToken(1).kind == ST_SEMICOLON) {
188191 getNextToken();
@@ -694,8 +697,14 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
694697 }
695698
696699 private boolean isPostgreSqlJsonAccess(Token token) {
697- return ("->".equals(token.image) || "->>".equals(token.image)
698- || "#>".equals(token.image) || "#>>".equals(token.image))
700+ // reject non-operator images with one charAt before the equals chain: this
701+ // runs after every primary expression
702+ String image = token.image;
703+ if (image == null || image.length() < 2 || (image.charAt(0) != '-' && image.charAt(0) != '#')) {
704+ return false;
705+ }
706+ return ("->".equals(image) || "->>".equals(image)
707+ || "#>".equals(image) || "#>>".equals(image))
699708 && Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect));
700709 }
701710
@@ -2704,6 +2713,95 @@ PARSER_END(CCJSqlParser)
27042713TOKEN_MGR_DECLS : {
27052714 public FeatureConfiguration configuration = new FeatureConfiguration();
27062715
2716+ // The configuration is wired before parsing starts (CCJSqlParser.getConfiguration /
2717+ // withConfiguration) and no lexical action mutates it mid-parse. Cache the dialect and
2718+ // flag lookups the per-token actions need, keyed on the wired configuration's identity,
2719+ // so every token pays a field read instead of a map lookup.
2720+ private FeatureConfiguration lexicalConfiguration;
2721+ private boolean lexicalPostgreSqlDialect;
2722+ private boolean lexicalBigQueryDialect;
2723+ private boolean lexicalAllowHashLineComments;
2724+ private boolean lexicalAllowBackslashEscapes;
2725+ private boolean lexicalAllowDoubleQuotedStrings;
2726+ private boolean lexicalAllowSquareBracketQuotation;
2727+
2728+ private void refreshLexicalConfiguration(FeatureConfiguration wired) {
2729+ lexicalConfiguration = wired;
2730+ Object dialect = wired.getValue(Feature.dialect);
2731+ lexicalPostgreSqlDialect = AbstractJSqlParser.Dialect.POSTGRESQL.name().equals(dialect);
2732+ lexicalBigQueryDialect = AbstractJSqlParser.Dialect.BIGQUERY.name().equals(dialect);
2733+ lexicalAllowHashLineComments = Boolean.TRUE.equals(wired.getValue(Feature.allowHashLineComments));
2734+ lexicalAllowBackslashEscapes = wired.getAsBoolean(Feature.allowBackslashEscapeCharacter);
2735+ lexicalAllowDoubleQuotedStrings = wired.getAsBoolean(Feature.allowDoubleQuotedStrings);
2736+ lexicalAllowSquareBracketQuotation = wired.getAsBoolean(Feature.allowSquareBracketQuotation);
2737+ }
2738+
2739+ private boolean isPostgreSqlDialect() {
2740+ FeatureConfiguration wired = input_stream.featureConfiguration;
2741+ if (wired == null) {
2742+ return false;
2743+ }
2744+ if (wired != lexicalConfiguration) {
2745+ refreshLexicalConfiguration(wired);
2746+ }
2747+ return lexicalPostgreSqlDialect;
2748+ }
2749+
2750+ private boolean isBigQueryDialect() {
2751+ FeatureConfiguration wired = input_stream.featureConfiguration;
2752+ if (wired == null) {
2753+ return false;
2754+ }
2755+ if (wired != lexicalConfiguration) {
2756+ refreshLexicalConfiguration(wired);
2757+ }
2758+ return lexicalBigQueryDialect;
2759+ }
2760+
2761+ private boolean isAllowHashLineComments() {
2762+ FeatureConfiguration wired = input_stream.featureConfiguration;
2763+ if (wired == null) {
2764+ return false;
2765+ }
2766+ if (wired != lexicalConfiguration) {
2767+ refreshLexicalConfiguration(wired);
2768+ }
2769+ return lexicalAllowHashLineComments;
2770+ }
2771+
2772+ private boolean isAllowBackslashEscapes() {
2773+ FeatureConfiguration wired = input_stream.featureConfiguration;
2774+ if (wired == null) {
2775+ return false;
2776+ }
2777+ if (wired != lexicalConfiguration) {
2778+ refreshLexicalConfiguration(wired);
2779+ }
2780+ return lexicalAllowBackslashEscapes;
2781+ }
2782+
2783+ private boolean isAllowDoubleQuotedStrings() {
2784+ FeatureConfiguration wired = input_stream.featureConfiguration;
2785+ if (wired == null) {
2786+ return false;
2787+ }
2788+ if (wired != lexicalConfiguration) {
2789+ refreshLexicalConfiguration(wired);
2790+ }
2791+ return lexicalAllowDoubleQuotedStrings;
2792+ }
2793+
2794+ private boolean isAllowSquareBracketQuotation() {
2795+ FeatureConfiguration wired = input_stream.featureConfiguration;
2796+ if (wired == null) {
2797+ return false;
2798+ }
2799+ if (wired != lexicalConfiguration) {
2800+ refreshLexicalConfiguration(wired);
2801+ }
2802+ return lexicalAllowSquareBracketQuotation;
2803+ }
2804+
27072805
27082806 // Identify the index of the quoting/escaping tokens
27092807 public int charLiteralIndex = -1;
@@ -2768,7 +2866,7 @@ TOKEN_MGR_DECLS : {
27682866 // disambiguation can fetch PATTERN tokens before its production is entered.
27692867 if (rowPatternContext == null && t.kind == S_IDENTIFIER
27702868 && "MATCH_RECOGNIZE".equalsIgnoreCase(t.image)
2771- && AbstractJSqlParser.Dialect.BIGQUERY.name().equals(configuration.getValue(Feature.dialect) )) {
2869+ && isBigQueryDialect( )) {
27722870 rowPatternContext = new RowPatternTokenContext();
27732871 }
27742872 if (rowPatternContext != null) {
@@ -3640,17 +3738,15 @@ TOKEN:
36403738 // that never opted in (the stream is only wired through the feature
36413739 // consumers / withConfiguration); getValue avoids the String-based
36423740 // getAsBoolean roundtrip
3643- if (matchedToken.kind == S_IDENTIFIER && input_stream.featureConfiguration != null
3644- && Boolean.TRUE.equals(configuration.getValue(Feature.allowHashLineComments))) {
3741+ if (matchedToken.kind == S_IDENTIFIER && isAllowHashLineComments()) {
36453742 int hashIndex = matchedToken.image.indexOf('#');
36463743 if (hashIndex > 0) {
36473744 truncateIdentifierToken(matchedToken, hashIndex);
36483745 }
36493746 }
36503747 // PostgreSQL does not allow # or @ in unquoted identifiers. Re-lex them as
36513748 // operators, including #>, #>>, #- and @?, when touching the left operand.
3652- if (matchedToken.kind == S_IDENTIFIER
3653- && AbstractJSqlParser.Dialect.POSTGRESQL.name().equals(configuration.getValue(Feature.dialect))) {
3749+ if (matchedToken.kind == S_IDENTIFIER && isPostgreSqlDialect()) {
36543750 int hashIndex = matchedToken.image.indexOf('#');
36553751 int atIndex = matchedToken.image.indexOf('@');
36563752 int operatorIndex = hashIndex < 0 ? atIndex
@@ -3676,8 +3772,7 @@ TOKEN:
36763772 // that never opted in (the stream is only wired through the feature
36773773 // consumers / withConfiguration); getValue avoids the String-based
36783774 // getAsBoolean roundtrip
3679- if (input_stream.featureConfiguration != null
3680- && Boolean.TRUE.equals(configuration.getValue(Feature.allowHashLineComments))) {
3775+ if (isAllowHashLineComments()) {
36813776 int hashIndex = matchedToken.image.indexOf('#');
36823777 if (hashIndex > 0) {
36833778 truncateIdentifierToken(matchedToken, hashIndex);
@@ -3728,8 +3823,8 @@ TOKEN:
37283823 // escapes the next character only with backslash escapes or in a PostgreSQL E'' string;
37293824 // otherwise the literal ends at the first quote that is not doubled, so LIKE ... ESCAPE '\\'
37303825 // still works.
3731- boolean backslashEscapes = configuration.getAsBoolean(Feature.allowBackslashEscapeCharacter )
3732- || (AbstractJSqlParser.Dialect.POSTGRESQL.name().equals(configuration.getValue(Feature.dialect) )
3826+ boolean backslashEscapes = isAllowBackslashEscapes( )
3827+ || (isPostgreSqlDialect( )
37333828 && matchedToken.image.regionMatches(true, 0, "E'", 0, 2));
37343829 int end = closingSingleQuote(matchedToken.image, backslashEscapes);
37353830 if (end > 0 && end < matchedToken.image.length()) {
@@ -3747,22 +3842,21 @@ TOKEN:
37473842 // the active quoting rules: a backslash escapes the next character only in
37483843 // double-quoted strings with backslash escapes, as in MySQL.
37493844 int end = closingQuote(matchedToken.image, 0, '"',
3750- configuration.getAsBoolean(Feature.allowDoubleQuotedStrings)
3751- && configuration.getAsBoolean(Feature.allowBackslashEscapeCharacter));
3845+ isAllowDoubleQuotedStrings() && isAllowBackslashEscapes());
37523846 if (end > 0 && end < matchedToken.image.length()) {
37533847 matchedToken.image = image.substring(0, end);
37543848 input_stream.backup(image.length() - end);
37553849 }
37563850 }
3757- if ( !configuration.getAsBoolean(Feature.allowSquareBracketQuotation )
3851+ if ( !isAllowSquareBracketQuotation( )
37583852 && matchedToken.image.charAt(0) == '[' ) {
37593853
37603854 matchedToken.image = "[";
37613855 // `squaredBracketOpenIndex` defined in TokenManagerDeclaration above
37623856 matchedToken.kind = squaredBracketOpenIndex;
37633857 input_stream.backup(image.length() - 1);
37643858 }
3765- if ( configuration.getAsBoolean(Feature.allowDoubleQuotedStrings )
3859+ if ( isAllowDoubleQuotedStrings( )
37663860 && matchedToken.image.charAt(0) == '"' ) {
37673861 // `charLiteralIndex` defined in TokenManagerDeclaration above
37683862 matchedToken.kind = charLiteralIndex;
@@ -12019,7 +12113,7 @@ Expression PrimaryExpression() #PrimaryExpression:
1201912113 Token token = null;
1202012114 Token sign = null;
1202112115 Token innerSign;
12022- List<Token> innerSigns = new ArrayList<Token>() ;
12116+ List<Token> innerSigns = null ;
1202312117 ColDataType type = null;
1202412118 boolean not = false;
1202512119 boolean exclamationMarkNot = false;
@@ -12033,7 +12127,8 @@ Expression PrimaryExpression() #PrimaryExpression:
1203312127 [
1203412128 ( sign="+" | sign="-" | sign="~" )
1203512129 // DuckDB's chained signs, e.g. - -1 or +-+1
12036- ( LOOKAHEAD(1) ( innerSign="+" | innerSign="-" | innerSign="~" ) { innerSigns.add(innerSign); } )*
12130+ ( LOOKAHEAD(1) ( innerSign="+" | innerSign="-" | innerSign="~" )
12131+ { if (innerSigns == null) { innerSigns = new ArrayList<Token>(); } innerSigns.add(innerSign); } )*
1203712132 ]
1203812133 (
1203912134 <K_NULL> { retval = new NullValue(); }
@@ -12249,8 +12344,10 @@ Expression PrimaryExpression() #PrimaryExpression:
1224912344 timezoneExpr.setLeftExpression(retval);
1225012345 retval=timezoneExpr;
1225112346 }
12252- for (int i = innerSigns.size() - 1; i >= 0; i--) {
12253- retval = new SignedExpression(innerSigns.get(i).image.charAt(0), retval);
12347+ if (innerSigns != null) {
12348+ for (int i = innerSigns.size() - 1; i >= 0; i--) {
12349+ retval = new SignedExpression(innerSigns.get(i).image.charAt(0), retval);
12350+ }
1225412351 }
1225512352 if (sign != null) {
1225612353 retval = new SignedExpression(sign.image.charAt(0), retval);
0 commit comments