Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,26 @@ public class SimpleCharStream {
String mySqlCommentError;
boolean mySqlMinusSubstitution;

// ReInit retains the configuration, which callers can mutate between parses.
private FeatureConfiguration derivedFrom;
private long derivedModificationCount;
private boolean derivedMySqlDialect;
private boolean derivedBlankLineSeparator = true;
private boolean derivedAllowHashLineComments;

private void refreshDerivedFeatures() {
derivedMySqlDialect = featureConfiguration != null
&& AbstractJSqlParser.Dialect.MYSQL.name()
.equals(featureConfiguration.getValue(Feature.dialect));
derivedBlankLineSeparator = featureConfiguration == null
|| featureConfiguration.getAsBoolean(Feature.allowBlankLinesAsStatementSeparator);
derivedAllowHashLineComments = featureConfiguration != null
&& featureConfiguration.getAsBoolean(Feature.allowHashLineComments);
derivedFrom = featureConfiguration;
derivedModificationCount = featureConfiguration == null ? 0
: featureConfiguration.getModificationCount();
}

/**
* Constructor.
*/
Expand Down Expand Up @@ -201,17 +221,18 @@ public final char BeginToken() throws java.io.IOException {

absoluteTokenBegin = totalCharsRead;

if (c == '#' && featureConfiguration != null
&& featureConfiguration.getAsBoolean(Feature.allowHashLineComments)) {
if (featureConfiguration != derivedFrom || featureConfiguration != null
&& (featureConfiguration.getModificationCount() != derivedModificationCount
|| featureConfiguration.getClass() != FeatureConfiguration.class)) {
refreshDerivedFeatures();
}

if (c == '#' && derivedAllowHashLineComments) {
buffer[bufpos] = HASH_SUBSTITUTION;
c = HASH_SUBSTITUTION;
} else if ((c == '-' || c == '/' || c == '*') && featureConfiguration != null
&& AbstractJSqlParser.Dialect.MYSQL.name()
.equals(featureConfiguration.getValue(Feature.dialect))) {
} else if ((c == '-' || c == '/' || c == '*') && derivedMySqlDialect) {
c = mySqlCommentBoundary(c);
} else if (c == '\n' && featureConfiguration != null
&& !featureConfiguration
.getAsBoolean(Feature.allowBlankLinesAsStatementSeparator)
} else if (c == '\n' && !derivedBlankLineSeparator
&& peekCharacters(2).equals("\n\n")) {
// Blanking the first newline of the run stops ST_SEMICOLON matching "\n\n\n";
// line and column tracking was already updated when the newline was read.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class FeatureConfiguration {
private static final Logger LOG = Logger.getLogger(FeatureConfiguration.class.getName());

private final Map<Feature, Object> featureEnabled = new EnumMap<>(Feature.class);
private long modificationCount;

public FeatureConfiguration() {
// set default-value for all switchable features
Expand All @@ -35,6 +36,7 @@ public FeatureConfiguration() {
public FeatureConfiguration setValue(Feature feature, Object value) {
if (feature.isConfigurable()) {
featureEnabled.put(feature, value);
modificationCount++;
} else {
if (LOG.isLoggable(Level.WARNING)) {
LOG.warning(feature.name() + " is not switchable - cannot set enabled = " + value);
Expand All @@ -43,6 +45,11 @@ public FeatureConfiguration setValue(Feature feature, Object value) {
return this;
}

/** Returns a change stamp for caches of derived feature values. */
public long getModificationCount() {
return modificationCount;
}

/**
* @param feature
* @return the configured feature value - can be <code>null</code>
Expand All @@ -57,19 +64,24 @@ public Object getValue(Feature feature) {
}

public boolean getAsBoolean(Feature f) {
return Boolean.parseBoolean(String.valueOf(getValue(f)));
Object value = getValue(f);
return value instanceof Boolean ? (Boolean) value
: Boolean.parseBoolean(String.valueOf(value));
}

public Long getAsLong(Feature f) {
return Long.valueOf(String.valueOf(getValue(f)));
Object value = getValue(f);
return value instanceof Long ? (Long) value : Long.valueOf(String.valueOf(value));
}

public int getAsInt(Feature f) {
return Integer.parseInt(String.valueOf(getValue(f)));
Object value = getValue(f);
return value instanceof Integer ? (Integer) value : Integer.parseInt(String.valueOf(value));
}

public Integer getAsInteger(Feature f) {
return Integer.parseInt(String.valueOf(getValue(f)));
Object value = getValue(f);
return value instanceof Integer ? (Integer) value : Integer.valueOf(String.valueOf(value));
}

public String getAsString(Feature f) {
Expand Down
109 changes: 89 additions & 20 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
}

private void captureOpaqueStatements(Statements statements) throws ParseException {
while (getAsBoolean(Feature.allowUnsupportedStatements) && isOpaqueStatementAhead()) {
if (!getAsBoolean(Feature.allowUnsupportedStatements)) {
return;
}
while (isOpaqueStatementAhead()) {
statements.add(UnsupportedStatement());
while (getToken(1).kind == ST_SEMICOLON) {
getNextToken();
Expand Down Expand Up @@ -694,8 +697,14 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
}

private boolean isPostgreSqlJsonAccess(Token token) {
return ("->".equals(token.image) || "->>".equals(token.image)
|| "#>".equals(token.image) || "#>>".equals(token.image))
// reject non-operator images with one charAt before the equals chain: this
// runs after every primary expression
String image = token.image;
if (image == null || image.length() < 2 || (image.charAt(0) != '-' && image.charAt(0) != '#')) {
return false;
}
return ("->".equals(image) || "->>".equals(image)
|| "#>".equals(image) || "#>>".equals(image))
&& Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect));
}

Expand Down Expand Up @@ -2704,6 +2713,67 @@ PARSER_END(CCJSqlParser)
TOKEN_MGR_DECLS : {
public FeatureConfiguration configuration = new FeatureConfiguration();

// The public configuration can be replaced or mutated, including between ReInit calls.
// Subclasses may provide dynamic values without going through setValue.
private FeatureConfiguration lexicalConfiguration;
private long lexicalModificationCount;
private boolean lexicalPostgreSqlDialect;
private boolean lexicalBigQueryDialect;
private boolean lexicalAllowHashLineComments;
private boolean lexicalAllowBackslashEscapes;
private boolean lexicalAllowDoubleQuotedStrings;
private boolean lexicalAllowSquareBracketQuotation;

private void refreshLexicalConfiguration() {
long modificationCount = configuration.getModificationCount();
if (configuration == lexicalConfiguration && modificationCount == lexicalModificationCount
&& configuration.getClass() == FeatureConfiguration.class) {
return;
}
Object dialect = configuration.getValue(Feature.dialect);
lexicalPostgreSqlDialect = AbstractJSqlParser.Dialect.POSTGRESQL.name().equals(dialect);
lexicalBigQueryDialect = AbstractJSqlParser.Dialect.BIGQUERY.name().equals(dialect);
lexicalAllowHashLineComments = Boolean.TRUE.equals(configuration.getValue(Feature.allowHashLineComments));
lexicalAllowBackslashEscapes = configuration.getAsBoolean(Feature.allowBackslashEscapeCharacter);
lexicalAllowDoubleQuotedStrings = configuration.getAsBoolean(Feature.allowDoubleQuotedStrings);
lexicalAllowSquareBracketQuotation = configuration.getAsBoolean(Feature.allowSquareBracketQuotation);
lexicalConfiguration = configuration;
lexicalModificationCount = modificationCount;
}

private boolean isPostgreSqlDialect() {
refreshLexicalConfiguration();
return lexicalPostgreSqlDialect;
}

private boolean isBigQueryDialect() {
refreshLexicalConfiguration();
return lexicalBigQueryDialect;
}

private boolean isAllowHashLineComments() {
if (input_stream.featureConfiguration == null) {
return false;
}
refreshLexicalConfiguration();
return lexicalAllowHashLineComments;
}

private boolean isAllowBackslashEscapes() {
refreshLexicalConfiguration();
return lexicalAllowBackslashEscapes;
}

private boolean isAllowDoubleQuotedStrings() {
refreshLexicalConfiguration();
return lexicalAllowDoubleQuotedStrings;
}

private boolean isAllowSquareBracketQuotation() {
refreshLexicalConfiguration();
return lexicalAllowSquareBracketQuotation;
}


// Identify the index of the quoting/escaping tokens
public int charLiteralIndex = -1;
Expand Down Expand Up @@ -2768,7 +2838,7 @@ TOKEN_MGR_DECLS : {
// disambiguation can fetch PATTERN tokens before its production is entered.
if (rowPatternContext == null && t.kind == S_IDENTIFIER
&& "MATCH_RECOGNIZE".equalsIgnoreCase(t.image)
&& AbstractJSqlParser.Dialect.BIGQUERY.name().equals(configuration.getValue(Feature.dialect))) {
&& isBigQueryDialect()) {
rowPatternContext = new RowPatternTokenContext();
}
if (rowPatternContext != null) {
Expand Down Expand Up @@ -3640,17 +3710,15 @@ TOKEN:
// that never opted in (the stream is only wired through the feature
// consumers / withConfiguration); getValue avoids the String-based
// getAsBoolean roundtrip
if (matchedToken.kind == S_IDENTIFIER && input_stream.featureConfiguration != null
&& Boolean.TRUE.equals(configuration.getValue(Feature.allowHashLineComments))) {
if (matchedToken.kind == S_IDENTIFIER && isAllowHashLineComments()) {
int hashIndex = matchedToken.image.indexOf('#');
if (hashIndex > 0) {
truncateIdentifierToken(matchedToken, hashIndex);
}
}
// PostgreSQL does not allow # or @ in unquoted identifiers. Re-lex them as
// operators, including #>, #>>, #- and @?, when touching the left operand.
if (matchedToken.kind == S_IDENTIFIER
&& AbstractJSqlParser.Dialect.POSTGRESQL.name().equals(configuration.getValue(Feature.dialect))) {
if (matchedToken.kind == S_IDENTIFIER && isPostgreSqlDialect()) {
int hashIndex = matchedToken.image.indexOf('#');
int atIndex = matchedToken.image.indexOf('@');
int operatorIndex = hashIndex < 0 ? atIndex
Expand All @@ -3676,8 +3744,7 @@ TOKEN:
// that never opted in (the stream is only wired through the feature
// consumers / withConfiguration); getValue avoids the String-based
// getAsBoolean roundtrip
if (input_stream.featureConfiguration != null
&& Boolean.TRUE.equals(configuration.getValue(Feature.allowHashLineComments))) {
if (isAllowHashLineComments()) {
int hashIndex = matchedToken.image.indexOf('#');
if (hashIndex > 0) {
truncateIdentifierToken(matchedToken, hashIndex);
Expand Down Expand Up @@ -3728,8 +3795,8 @@ TOKEN:
// escapes the next character only with backslash escapes or in a PostgreSQL E'' string;
// otherwise the literal ends at the first quote that is not doubled, so LIKE ... ESCAPE '\\'
// still works.
boolean backslashEscapes = configuration.getAsBoolean(Feature.allowBackslashEscapeCharacter)
|| (AbstractJSqlParser.Dialect.POSTGRESQL.name().equals(configuration.getValue(Feature.dialect))
boolean backslashEscapes = isAllowBackslashEscapes()
|| (isPostgreSqlDialect()
&& matchedToken.image.regionMatches(true, 0, "E'", 0, 2));
int end = closingSingleQuote(matchedToken.image, backslashEscapes);
if (end > 0 && end < matchedToken.image.length()) {
Expand All @@ -3747,22 +3814,21 @@ TOKEN:
// the active quoting rules: a backslash escapes the next character only in
// double-quoted strings with backslash escapes, as in MySQL.
int end = closingQuote(matchedToken.image, 0, '"',
configuration.getAsBoolean(Feature.allowDoubleQuotedStrings)
&& configuration.getAsBoolean(Feature.allowBackslashEscapeCharacter));
isAllowDoubleQuotedStrings() && isAllowBackslashEscapes());
if (end > 0 && end < matchedToken.image.length()) {
matchedToken.image = image.substring(0, end);
input_stream.backup(image.length() - end);
}
}
if ( !configuration.getAsBoolean(Feature.allowSquareBracketQuotation)
if ( !isAllowSquareBracketQuotation()
&& matchedToken.image.charAt(0) == '[' ) {

matchedToken.image = "[";
// `squaredBracketOpenIndex` defined in TokenManagerDeclaration above
matchedToken.kind = squaredBracketOpenIndex;
input_stream.backup(image.length() - 1);
}
if ( configuration.getAsBoolean(Feature.allowDoubleQuotedStrings)
if ( isAllowDoubleQuotedStrings()
&& matchedToken.image.charAt(0) == '"' ) {
// `charLiteralIndex` defined in TokenManagerDeclaration above
matchedToken.kind = charLiteralIndex;
Expand Down Expand Up @@ -12019,7 +12085,7 @@ Expression PrimaryExpression() #PrimaryExpression:
Token token = null;
Token sign = null;
Token innerSign;
List<Token> innerSigns = new ArrayList<Token>();
List<Token> innerSigns = null;
ColDataType type = null;
boolean not = false;
boolean exclamationMarkNot = false;
Expand All @@ -12033,7 +12099,8 @@ Expression PrimaryExpression() #PrimaryExpression:
[
( sign="+" | sign="-" | sign="~" )
// DuckDB's chained signs, e.g. - -1 or +-+1
( LOOKAHEAD(1) ( innerSign="+" | innerSign="-" | innerSign="~" ) { innerSigns.add(innerSign); } )*
( LOOKAHEAD(1) ( innerSign="+" | innerSign="-" | innerSign="~" )
{ if (innerSigns == null) { innerSigns = new ArrayList<Token>(); } innerSigns.add(innerSign); } )*
]
(
<K_NULL> { retval = new NullValue(); }
Expand Down Expand Up @@ -12249,8 +12316,10 @@ Expression PrimaryExpression() #PrimaryExpression:
timezoneExpr.setLeftExpression(retval);
retval=timezoneExpr;
}
for (int i = innerSigns.size() - 1; i >= 0; i--) {
retval = new SignedExpression(innerSigns.get(i).image.charAt(0), retval);
if (innerSigns != null) {
for (int i = innerSigns.size() - 1; i >= 0; i--) {
retval = new SignedExpression(innerSigns.get(i).image.charAt(0), retval);
}
}
if (sign != null) {
retval = new SignedExpression(sign.image.charAt(0), retval);
Expand Down
Loading
Loading