From 4df4e09dc8f5868138e92e0f90211bf175c69e90 Mon Sep 17 00:00:00 2001 From: minleejae Date: Sat, 12 Sep 2026 21:49:14 +0900 Subject: [PATCH] Protect shared dialect presets from external mutation --- .../jsqlparser/parser/AbstractJSqlParser.java | 8 +++- .../parser/DialectPresetIsolationTest.java | 48 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/test/java/net/sf/jsqlparser/parser/DialectPresetIsolationTest.java diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java index 0e494d6b3..a39455845 100644 --- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java +++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java @@ -13,6 +13,7 @@ import net.sf.jsqlparser.parser.feature.FeatureConfiguration; import java.util.ArrayList; +import java.util.Collections; import java.util.Arrays; import java.util.EnumSet; import java.util.List; @@ -61,8 +62,9 @@ public enum Dialect { this(AdjacentStringLiterals.OFF, lexerFeatures); } + /** Returns the immutable lexer defaults shared by this dialect preset. */ public Set getLexerFeatures() { - return lexerFeatures; + return Collections.unmodifiableSet(lexerFeatures); } public AdjacentStringLiterals getAdjacentStringLiterals() { @@ -111,6 +113,10 @@ public P withTimeOut(long timeOutMillSeconds) { return withFeature(Feature.timeOut, timeOutMillSeconds); } + /** + * Applies this dialect's enabled lexer defaults to the current configuration. Existing options + * remain set; apply explicit overrides after this call, or use a new parser for a fresh preset. + */ public P withDialect(Dialect dialect) { withFeature(Feature.dialect, dialect.name()); if (dialect.getAdjacentStringLiterals() != AdjacentStringLiterals.OFF) { diff --git a/src/test/java/net/sf/jsqlparser/parser/DialectPresetIsolationTest.java b/src/test/java/net/sf/jsqlparser/parser/DialectPresetIsolationTest.java new file mode 100644 index 000000000..e1c1bd56d --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/DialectPresetIsolationTest.java @@ -0,0 +1,48 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import static org.junit.jupiter.api.Assertions.*; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.feature.Feature; +import org.junit.jupiter.api.Test; + +class DialectPresetIsolationTest { + @Test + void callersCannotMutateSharedPresets() { + for (Dialect dialect : Dialect.values()) { + assertThrows(UnsupportedOperationException.class, + () -> dialect.getLexerFeatures().add(Feature.allowHashLineComments)); + assertThrows(UnsupportedOperationException.class, + () -> dialect.getLexerFeatures().clear()); + } + } + + @Test + void explicitParserOverridesAreLocalAndDoNotModifyPresetDefaults() { + CCJSqlParser first = CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.MYSQL) + .withHashLineComments(false); + CCJSqlParser second = CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.MYSQL); + assertFalse(first.getAsBoolean(Feature.allowHashLineComments)); + assertTrue(second.getAsBoolean(Feature.allowHashLineComments)); + assertTrue(Dialect.MYSQL.getLexerFeatures().contains(Feature.allowHashLineComments)); + } + + @Test + void preservesAdditivePresetApplicationAndExplicitOverrideOrder() { + CCJSqlParser parser = CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.SQLSERVER) + .withDialect(Dialect.POSTGRESQL); + assertTrue(parser.getAsBoolean(Feature.allowSquareBracketQuotation)); + parser.withSquareBracketQuotation(false); + assertFalse(parser.getAsBoolean(Feature.allowSquareBracketQuotation)); + assertFalse(CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.POSTGRESQL) + .getAsBoolean(Feature.allowSquareBracketQuotation)); + } +}