() {
@Override
public Void visit(Table table, S context) {
- if (currentCatalogName.equalsIgnoreCase(table.getUnquotedDatabaseName())) {
+ stripCurrentCatalog(table);
+ return super.visit(table, context);
+ }
+
+ @Override
+ public Void visit(Column column, S context) {
+ Table table = column.getTable();
+ if (table != null) {
+ stripCurrentCatalog(table);
+ if (currentSchemaName != null && !currentSchemaName.isEmpty()
+ && currentSchemaName.equalsIgnoreCase(table.getUnquotedSchemaName())) {
+ table.setSchemaName(null);
+ }
+ }
+ return super.visit(column, context);
+ }
+
+ private void stripCurrentCatalog(Table table) {
+ if (currentCatalogName != null && !currentCatalogName.isEmpty()
+ && currentCatalogName.equalsIgnoreCase(table.getUnquotedDatabaseName())) {
table.setDatabaseName(null);
}
- return super.visit(table, context);
}
}.getTables((Statement) select);
}
diff --git a/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/ReadOnlyStatementValidator.java b/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/ReadOnlyStatementValidator.java
index 9ba0fc8..eabd117 100644
--- a/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/ReadOnlyStatementValidator.java
+++ b/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/ReadOnlyStatementValidator.java
@@ -18,6 +18,7 @@
import net.sf.jsqlparser.statement.ParenthesedStatement;
import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.select.ForMode;
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
@@ -37,15 +38,23 @@
* on PostgreSQL/MariaDB the wrapped {@code UPDATE}/{@code INSERT}/{@code DELETE}
* executes. The upstream resolver does not populate its update/insert column
* lists for a {@code WithItem}, so it cannot be relied on to detect this;
- * {@code SELECT ... INTO newtable} — creates/writes a table
- * (PostgreSQL, SQL Server);
- * {@code SELECT ... FOR UPDATE}/{@code FOR SHARE} — takes row locks.
+ * {@code SELECT ... INTO newtable} / {@code INTO TEMP newtable} — creates/writes
+ * a table (PostgreSQL, SQL Server);
+ * {@code SELECT ... INTO OUTFILE '/path'} / {@code INTO DUMPFILE '/path'} — writes
+ * a file on the database host, and {@code SELECT ... INTO @var} — assigns session
+ * variables instead of returning rows (MySQL/MariaDB, parsed since JSqlParser 5.4);
+ * {@code SELECT ... FOR UPDATE}/{@code FOR SHARE}/{@code FOR NO KEY UPDATE}/
+ * {@code FOR KEY SHARE} — takes row locks.
*
* The validator reuses {@link TablesNamesFinder}'s full recursive traversal
* (subqueries, joins, set operations, nested {@code WITH} clauses). A
* {@code WithItem} is only permitted to wrap a {@link ParenthesedSelect}; a
* {@code SELECT INTO} or a {@code FOR UPDATE}/{@code FOR SHARE} clause anywhere
* in the tree is a violation.
+ *
+ * This validator is the guard's primary, precisely worded gate. {@code TranspilerSqlGuard}
+ * additionally consults JSqlParser's own {@code Statement.getFeatures()} classification
+ * as a second, coarser net for anything not modelled here.
*/
class ReadOnlyStatementValidator extends TablesNamesFinder {
@@ -54,6 +63,8 @@ class ReadOnlyStatementValidator extends TablesNamesFinder {
static final String INSERT_IS_NOT_PERMITTED = "INSERT is not permitted.";
static final String STATEMENT_IS_NOT_PERMITTED = "Statement is not permitted.";
static final String SELECT_INTO_IS_NOT_PERMITTED = "SELECT INTO is not permitted.";
+ static final String SELECT_INTO_FILE_OR_VARIABLE_IS_NOT_PERMITTED =
+ "SELECT INTO OUTFILE/DUMPFILE/variable is not permitted.";
static final String ROW_LOCKING_IS_NOT_PERMITTED = "Row locking (FOR UPDATE/SHARE) is not permitted.";
private String violation;
@@ -75,9 +86,15 @@ private void record(String message) {
}
}
- /** A FOR UPDATE / FOR SHARE clause can hang off any Select node. */
+ /**
+ * A FOR UPDATE / FOR SHARE clause can hang off any Select node. Every
+ * {@link ForMode} is rejected, including the harmless {@code FOR READ ONLY}: it is
+ * not part of the SQL this guard is meant to pass through, and JSqlParser's own
+ * classification treats any {@code ForMode} as a transaction side effect anyway.
+ */
private void checkRowLocking(Select select) {
- if (select.getForMode() != null || select.getForUpdateTable() != null) {
+ if (select.getForMode() != null || select.getForUpdate() != null
+ || select.getForUpdateTables() != null && !select.getForUpdateTables().isEmpty()) {
record(ROW_LOCKING_IS_NOT_PERMITTED);
}
}
@@ -88,6 +105,11 @@ public Void visit(PlainSelect plainSelect, S context) {
|| plainSelect.getIntoTempTable() != null) {
record(SELECT_INTO_IS_NOT_PERMITTED);
}
+ // MySQL INTO OUTFILE / INTO DUMPFILE write a file on the server, INTO @var
+ // assigns session variables; none of the three returns rows to the client.
+ if (plainSelect.getMySqlSelectIntoClause() != null) {
+ record(SELECT_INTO_FILE_OR_VARIABLE_IS_NOT_PERMITTED);
+ }
checkRowLocking(plainSelect);
return super.visit(plainSelect, context);
}
diff --git a/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/TranspilerSqlGuard.java b/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/TranspilerSqlGuard.java
index 3e3b869..19a6078 100644
--- a/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/TranspilerSqlGuard.java
+++ b/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/TranspilerSqlGuard.java
@@ -36,7 +36,6 @@
import ai.starlake.transpiler.CatalogNotFoundException;
import ai.starlake.transpiler.ColumnNotFoundException;
-import ai.starlake.transpiler.JSQLColumResolver;
import ai.starlake.transpiler.JSQLResolver;
import ai.starlake.transpiler.SchemaNotFoundException;
import ai.starlake.transpiler.TableNotDeclaredException;
@@ -47,8 +46,10 @@
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementFeatures;
+import net.sf.jsqlparser.statement.Statements;
+import net.sf.jsqlparser.statement.StmtFeature;
import net.sf.jsqlparser.statement.select.Select;
-import net.sf.jsqlparser.util.deparser.StatementDeParser;
public class TranspilerSqlGuard implements SqlGuard {
@@ -58,6 +59,8 @@ public class TranspilerSqlGuard implements SqlGuard {
private static final String NOTHING_WAS_SELECTED = "Nothing was selected.";
private static final String QUERY_HAS_DISAllOWED_FUNCTIONS = "Query has disallowed functions.";
private static final String COULD_NOT_VALIDATE = "Statement could not be validated.";
+ private static final String ONLY_ONE_STATEMENT_IS_PERMITTED = "Only a single statement is permitted.";
+ private static final String STATEMENT_HAS_SIDE_EFFECTS = "Statement has side effects or does not return rows.";
private static final Logger LOGGER = LoggerFactory.getLogger(TranspilerSqlGuard.class);
private JdbcMetaData jdbcMetaDataToCopy;
private List whitelistFunctionsPatterns = new ArrayList();
@@ -75,20 +78,27 @@ public TranspilerSqlGuard(String currentCatalogName, String currentSchemaName, D
@Override
public String guard(String sqlStr) throws GuardException {
- LOGGER.atInfo().log("guard incoming: %s", sqlStr);
- StringBuilder builder = new StringBuilder();
- StatementDeParser deParser = new StatementDeParser(builder);
+ LOGGER.atInfo().log("guard incoming: {}", sqlStr);
JSQLResolver resolver = new JSQLResolver(jdbcMetaDataToCopy);
- // this does not really work, when there are comments
- // @todo: apply a Regex for SQL Comments
if (sqlStr == null || sqlStr.trim().isEmpty()) {
throw new EmptyStatementGuardException();
}
try {
- Statement st = CCJSqlParserUtil.parse(sqlStr);
+ // parse the whole input as a script: CCJSqlParserUtil.parse() stops after the
+ // first statement and would silently drop a trailing "; DROP TABLE ..." (or
+ // any other statement) from what is validated and rewritten.
+ Statements statements = CCJSqlParserUtil.parseStatements(sqlStr);
+ if (statements == null || statements.isEmpty()) {
+ // e.g. a comment-only input parses to an empty script
+ throw new EmptyStatementGuardException();
+ }
+ if (statements.size() > 1) {
+ throw new UnallowedStatementTypeGuardException(ONLY_ONE_STATEMENT_IS_PERMITTED);
+ }
+ Statement st = statements.get(0);
// we can test for SELECT, though in practise it won't protect us from harmful statements
if (st instanceof Select) {
@@ -137,14 +147,7 @@ public String guard(String sqlStr) throws GuardException {
List disallowedFunctions = new ArrayList<>();
for (String function : functionNames) {
- boolean isAllowed = false;
- for (String pattern : whitelistFunctionsPatterns) {
- if (Pattern.matches(pattern, function)) {
- isAllowed = true;
- break;
- }
- }
- if (isAllowed) {
+ if (isWhitelistedFunction(function)) {
allowedFunctions.add(function);
} else {
disallowedFunctions.add(function);
@@ -155,16 +158,23 @@ public String guard(String sqlStr) throws GuardException {
throw new GuardException(QUERY_HAS_DISAllOWED_FUNCTIONS);
}
+ // Second net: JSqlParser's own statement classification (since 5.4). It
+ // covers side effects this guard does not model explicitly (session or
+ // transaction changes, opaque calls, statement types added upstream).
+ // Functions the resolver saw were already proven against the whitelist
+ // above, so this can only reject for reasons the checks before did not see.
+ validateFeatures(st, allowedFunctions);
+
// we can finally resolve for the actually returned columns
- JSQLColumResolver columResolver = new DeparserColumResolver(jdbcMetaDataToCopy, dialect,dialectDeparser);
+ // on the very same AST that was validated above: no second parse
+ DeparserColumResolver columResolver = new DeparserColumResolver(jdbcMetaDataToCopy, dialect,
+ dialectDeparser);
columResolver.setCommentFlag(false);
columResolver.setErrorMode(JdbcMetaData.ErrorMode.STRICT);
- String rewritten = columResolver.getResolvedStatementText(sqlStr);
-
- deParser.visit((Select) st);// or rewritten
+ String rewritten = columResolver.getResolvedStatementText(st);
- LOGGER.atInfo().log("guard outgoin: %s", rewritten);
+ LOGGER.atInfo().log("guard outgoing: {}", rewritten);
return rewritten;
@@ -186,6 +196,31 @@ public String guard(String sqlStr) throws GuardException {
}
+ private boolean isWhitelistedFunction(String function) {
+ for (String pattern : whitelistFunctionsPatterns) {
+ if (Pattern.matches(pattern, function)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void validateFeatures(Statement st, List allowedFunctions) throws GuardException {
+ // the feature visitor reports function names lower-cased; the whitelist patterns
+ // are case-sensitive, so accept every name the resolver-based check already passed
+ StatementFeatures features = st.getFeatures(name -> isWhitelistedFunction(name)
+ || allowedFunctions.stream().anyMatch(allowed -> allowed.equalsIgnoreCase(name)));
+ boolean sideEffect = features.may(StmtFeature.MODIFIES_DATA)
+ || features.may(StmtFeature.MODIFIES_SCHEMA)
+ || features.may(StmtFeature.MODIFIES_SESSION)
+ || features.may(StmtFeature.MODIFIES_TRANSACTION)
+ || features.may(StmtFeature.OPAQUE);
+ if (sideEffect || !features.returnsResultSet()) {
+ LOGGER.atInfo().log("{} features: {}", STATEMENT_HAS_SIDE_EFFECTS, features);
+ throw new GuardException(STATEMENT_HAS_SIDE_EFFECTS);
+ }
+ }
+
private static void validateReadOnly(Select select) throws GuardException {
Optional violation = new ReadOnlyStatementValidator().firstViolation(select);
if (violation.isPresent()) {
diff --git a/guard/jsqltranspiler/src/test/java/org/eclipse/daanse/sql/guard/jsqltranspiler/integration/SqlGuardTest.java b/guard/jsqltranspiler/src/test/java/org/eclipse/daanse/sql/guard/jsqltranspiler/integration/SqlGuardTest.java
index 71a3812..d4248be 100644
--- a/guard/jsqltranspiler/src/test/java/org/eclipse/daanse/sql/guard/jsqltranspiler/integration/SqlGuardTest.java
+++ b/guard/jsqltranspiler/src/test/java/org/eclipse/daanse/sql/guard/jsqltranspiler/integration/SqlGuardTest.java
@@ -29,7 +29,9 @@
import org.eclipse.daanse.sql.guard.api.elements.DatabaseColumn;
import org.eclipse.daanse.sql.guard.api.elements.DatabaseSchema;
import org.eclipse.daanse.sql.guard.api.elements.DatabaseTable;
+import org.eclipse.daanse.sql.guard.api.exception.EmptyStatementGuardException;
import org.eclipse.daanse.sql.guard.api.exception.GuardException;
+import org.eclipse.daanse.sql.guard.api.exception.UnallowedStatementTypeGuardException;
import org.eclipse.daanse.sql.guard.api.exception.UnresolvableObjectsGuardException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
@@ -282,6 +284,37 @@ y AS (UPDATE foo SET name='b' RETURNING id)
private static final String SELECT_INTO_IS_NOT_PERMITTED = "SELECT INTO is not permitted.";
private static final String ROW_LOCKING_IS_NOT_PERMITTED = "Row locking (FOR UPDATE/SHARE) is not permitted.";
+
+ private static final String SELECT_INTO_FILE_OR_VARIABLE_IS_NOT_PERMITTED =
+ "SELECT INTO OUTFILE/DUMPFILE/variable is not permitted.";
+
+ private static final String ONLY_ONE_STATEMENT_IS_PERMITTED = "Only a single statement is permitted.";
+
+ // MySQL file/variable targets, parsed since JSqlParser 5.4; INTO @var and INTO TEMP
+ // do not parse with the default grammar and must then fail closed.
+ private static final String SQL_SELECT_INTO_OUTFILE = "SELECT foo.id INTO OUTFILE '/tmp/x' FROM foo";
+ private static final String SQL_SELECT_INTO_OUTFILE_AFTER_FROM = "SELECT foo.id FROM foo INTO OUTFILE '/tmp/x'";
+ private static final String SQL_SELECT_INTO_DUMPFILE = "SELECT foo.id INTO DUMPFILE '/tmp/x' FROM foo";
+ private static final String SQL_SELECT_INTO_VARIABLE = "SELECT foo.id INTO @v FROM foo";
+ private static final String SQL_SELECT_INTO_TEMP = "SELECT foo.id INTO TEMP bar FROM foo";
+ private static final String SQL_SELECT_INTO_SCHEMA_QUALIFIED = "SELECT foo.id INTO public.evil FROM foo";
+
+ private static final String SQL_SELECT_FOR_UPDATE_OF = "SELECT foo.id FROM foo FOR UPDATE OF foo";
+ private static final String SQL_SELECT_FOR_NO_KEY_UPDATE = "SELECT foo.id FROM foo FOR NO KEY UPDATE";
+ private static final String SQL_SELECT_FOR_KEY_SHARE = "SELECT foo.id FROM foo FOR KEY SHARE";
+ private static final String SQL_SELECT_FOR_UPDATE_SKIP_LOCKED = "SELECT foo.id FROM foo FOR UPDATE SKIP LOCKED";
+ private static final String SQL_SELECT_LOCK_IN_SHARE_MODE = "SELECT foo.id FROM foo LOCK IN SHARE MODE";
+
+ private static final String SQL_TRAILING_DROP = "SELECT foo.id FROM foo; DROP TABLE foo";
+ private static final String SQL_TRAILING_SELECT = "SELECT foo.id FROM foo; SELECT foo.name FROM foo";
+ private static final String SQL_TRAILING_SEMICOLON = "SELECT foo.id FROM foo;";
+ private static final String SQL_COMMENT_ONLY = "/* nothing */";
+
+ private static final String SQL_CORRELATED_EXISTS = """
+ SELECT foo.id FROM foo WHERE EXISTS (SELECT 1 FROM fooFact WHERE fooFact.id = foo.id)""";
+ private static final String SQL_CORRELATED_SCALAR = """
+ SELECT foo.id, (SELECT count(fooFact.id) FROM fooFact WHERE fooFact.id = foo.id) FROM foo""";
+ private static final String SQL_WINDOW_FUNCTION = "SELECT sum(foo.id) OVER () FROM foo";
private static final String SQL_LEFT_JOIN_ON_WRONG_COLUMN = """
SELECT foo.id FROM foo LEFT JOIN fooFact ON foo.wrongCol = fooFact.id""";
@@ -383,6 +416,8 @@ WHERE foo.id IN (SELECT fooFact.id FROM fooFact)
private static final String SELECT_FROM_CATALOG_QUALIFIED_FOO = "select * from FoodMart.sch.foo";
+ private static final String SELECT_QUALIFIED_COLUMNS_FROM_QUALIFIED_FOO = "select sch.foo.id, FoodMart.sch.foo.name from sch.foo";
+
@BeforeAll
public static void setUp() {
dialect = new H2Dialect();
@@ -1150,6 +1185,80 @@ void testUnionForUpdate(@InjectService SqlGuardFactory sqlGuardFactory) throws E
.isInstanceOf(GuardException.class)
.hasMessage(ROW_LOCKING_IS_NOT_PERMITTED);
}
+
+ @Test
+ void testSelectIntoSchemaQualified(@InjectService SqlGuardFactory sqlGuardFactory) throws Exception {
+ DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
+ SqlGuard guard = sqlGuardFactory.create("", SCH, databaseCatalog, List.of(), dialect);
+ assertThatThrownBy(() -> guard.guard(SQL_SELECT_INTO_SCHEMA_QUALIFIED))
+ .isInstanceOf(GuardException.class)
+ .hasMessage(SELECT_INTO_IS_NOT_PERMITTED);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { SQL_SELECT_INTO_OUTFILE, SQL_SELECT_INTO_OUTFILE_AFTER_FROM, SQL_SELECT_INTO_DUMPFILE })
+ void testSelectIntoFile(String sql, @InjectService SqlGuardFactory sqlGuardFactory) throws Exception {
+ DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
+ SqlGuard guard = sqlGuardFactory.create("", SCH, databaseCatalog, List.of(), dialect);
+ assertThatThrownBy(() -> guard.guard(sql))
+ .isInstanceOf(GuardException.class)
+ .hasMessage(SELECT_INTO_FILE_OR_VARIABLE_IS_NOT_PERMITTED);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { SQL_SELECT_FOR_UPDATE_OF, SQL_SELECT_FOR_NO_KEY_UPDATE, SQL_SELECT_FOR_KEY_SHARE,
+ SQL_SELECT_FOR_UPDATE_SKIP_LOCKED })
+ void testSelectRowLockVariants(String sql, @InjectService SqlGuardFactory sqlGuardFactory) throws Exception {
+ DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
+ SqlGuard guard = sqlGuardFactory.create("", SCH, databaseCatalog, List.of(), dialect);
+ assertThatThrownBy(() -> guard.guard(sql))
+ .isInstanceOf(GuardException.class)
+ .hasMessage(ROW_LOCKING_IS_NOT_PERMITTED);
+ }
+
+ // Not parsed by the default grammar today: whatever the parser does with them in
+ // a future version, the guard must never let them through.
+ @ParameterizedTest
+ @ValueSource(strings = { SQL_SELECT_INTO_VARIABLE, SQL_SELECT_INTO_TEMP, SQL_SELECT_LOCK_IN_SHARE_MODE })
+ void testSideEffectingSelectFailsClosed(String sql, @InjectService SqlGuardFactory sqlGuardFactory)
+ throws Exception {
+ DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
+ SqlGuard guard = sqlGuardFactory.create("", SCH, databaseCatalog, List.of(), dialect);
+ assertThatThrownBy(() -> guard.guard(sql)).isInstanceOf(GuardException.class);
+ }
+ }
+
+ @Nested
+ @DisplayName("Script Security Tests (multiple statements)")
+ class ScriptSecurityTests {
+
+ // CCJSqlParserUtil.parse() stops after the first statement, so a trailing
+ // statement would otherwise be silently dropped instead of rejected.
+
+ @ParameterizedTest
+ @ValueSource(strings = { SQL_TRAILING_DROP, SQL_TRAILING_SELECT })
+ void testTrailingStatementIsRejected(String sql, @InjectService SqlGuardFactory sqlGuardFactory)
+ throws Exception {
+ DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
+ SqlGuard guard = sqlGuardFactory.create("", SCH, databaseCatalog, List.of(), dialect);
+ assertThatThrownBy(() -> guard.guard(sql))
+ .isInstanceOf(UnallowedStatementTypeGuardException.class)
+ .hasMessage(ONLY_ONE_STATEMENT_IS_PERMITTED);
+ }
+
+ @Test
+ void testTrailingSemicolonStillPasses(@InjectService SqlGuardFactory sqlGuardFactory) throws Exception {
+ DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
+ SqlGuard guard = sqlGuardFactory.create("", SCH, databaseCatalog, List.of(), dialect);
+ assertThat(guard.guard(SQL_TRAILING_SEMICOLON)).doesNotContain(";");
+ }
+
+ @Test
+ void testCommentOnlyIsEmpty(@InjectService SqlGuardFactory sqlGuardFactory) throws Exception {
+ DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
+ SqlGuard guard = sqlGuardFactory.create("", SCH, databaseCatalog, List.of(), dialect);
+ assertThatThrownBy(() -> guard.guard(SQL_COMMENT_ONLY)).isInstanceOf(EmptyStatementGuardException.class);
+ }
}
@Nested
@@ -1367,6 +1476,23 @@ void testCountDistinctWrongColumn(@InjectService SqlGuardFactory sqlGuardFactory
@DisplayName("Positive Tests - Valid Queries Should Pass")
class PositiveTests {
+ // Correlated sub queries resolve against the enclosing query since jsqltranspiler 1.13.
+ @ParameterizedTest
+ @ValueSource(strings = { SQL_CORRELATED_EXISTS, SQL_CORRELATED_SCALAR })
+ void testCorrelatedSubqueryPasses(String sql, @InjectService SqlGuardFactory sqlGuardFactory)
+ throws Exception {
+ DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
+ SqlGuard guard = sqlGuardFactory.create("", SCH, databaseCatalog, AGGREGATIONS, dialect);
+ assertThat(guard.guard(sql)).isNotNull();
+ }
+
+ @Test
+ void testWindowFunctionPasses(@InjectService SqlGuardFactory sqlGuardFactory) throws Exception {
+ DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
+ SqlGuard guard = sqlGuardFactory.create("", SCH, databaseCatalog, AGGREGATIONS, dialect);
+ assertThat(guard.guard(SQL_WINDOW_FUNCTION)).contains("OVER");
+ }
+
@Test
void testValidSubselect(@InjectService SqlGuardFactory sqlGuardFactory) throws Exception {
DatabaseCatalog databaseCatalog = schemaWithTwoTableTwoCol();
@@ -1441,6 +1567,14 @@ void aCatalogQualifiedNameResolvesWhenTheCatalogIsTheCurrentOne(@InjectService S
assertThat(emitted).isEqualTo(SELECT_FROM_FOO_RESULT);
}
+ @Test
+ void aQualifiedColumnReferenceIsReducedToTheTableName(@InjectService SqlGuardFactory sqlGuardFactory)
+ throws Exception {
+ SqlGuard guard = sqlGuardFactory.create(CAT, SCH, schemaWithTwoTableTwoCol(), List.of(), dialect);
+ // the FROM item carries the schema, column references only the table name
+ assertThat(guard.guard(SELECT_QUALIFIED_COLUMNS_FROM_QUALIFIED_FOO)).isEqualTo(SELECT_FROM_FOO_RESULT);
+ }
+
@Test
void aBareNameWithNoCurrentSchemaIsReportedAsUnknown(@InjectService SqlGuardFactory sqlGuardFactory) {
SqlGuard guard = sqlGuardFactory.create(CAT, "", schemaWithTwoTableTwoCol(), List.of(), dialect);
diff --git a/guard/jsqltranspiler/test.bndrun b/guard/jsqltranspiler/test.bndrun
index ed6794c..8d67daf 100644
--- a/guard/jsqltranspiler/test.bndrun
+++ b/guard/jsqltranspiler/test.bndrun
@@ -52,7 +52,7 @@
# -runbundles is calculated by the bnd-resolver-maven-plugin
-runbundles: \
- ai.starlake.transpiler;version='[1.8.0,1.8.1)',\
+ ai.starlake.transpiler;version='[1.13.0,1.13.1)',\
assertj-core;version='[3.24.2,3.24.3)',\
junit-jupiter-api;version='[5.12.2,5.12.3)',\
junit-jupiter-engine;version='[5.12.2,5.12.3)',\
@@ -62,7 +62,7 @@
junit-platform-launcher;version='[1.12.2,1.12.3)',\
net.bytebuddy.byte-buddy;version='[1.17.5,1.17.6)',\
net.bytebuddy.byte-buddy-agent;version='[1.17.5,1.17.6)',\
- net.sf.jsqlparser;version='[5.3.167,5.3.168)',\
+ net.sf.jsqlparser;version='[5.4.15,5.4.16)',\
org.apache.felix.scr;version='[2.2.10,2.2.11)',\
org.eclipse.daanse.sql.deparser.api;version='[0.0.1,0.0.2)',\
org.eclipse.daanse.sql.deparser.jsqlparser;version='[0.0.1,0.0.2)',\