From 053dcf53eabc2e3130e4d94d65104625580f1293 Mon Sep 17 00:00:00 2001 From: tanjiro Kamado Date: Fri, 30 Oct 2026 01:34:44 +0530 Subject: [PATCH] fix: validate select items and skipped clauses in SelectValidator SelectValidator never validated the select list (the call was commented out), nor DISTINCT ON, TOP, QUALIFY, WINDOW definitions and the LIMIT, OFFSET and FETCH row counts, so no capability saw the expressions there. They are now validated like WHERE, with the select list after the FROM items so table aliases resolve. --- .../validation/validator/LimitValidator.java | 3 +++ .../validation/validator/SelectValidator.java | 27 +++++++++++++++---- .../DatabaseMetaDataValidationTest.java | 8 ++++++ .../validator/SelectValidatorTest.java | 23 ++++++++++++++++ 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/LimitValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/LimitValidator.java index 7089d67e6d..8d001d88da 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/LimitValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/LimitValidator.java @@ -27,5 +27,8 @@ public void validate(Limit limit) { validateFeature(c, limit.isLimitAll(), Feature.limitAll); validateOptionalFeature(c, limit.getOffset(), Feature.limitOffset); } + + validateOptionalExpression(limit.getRowCount()); + validateOptionalExpression(limit.getOffset()); } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java index cea33e89d1..b06c8a89f5 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java @@ -15,6 +15,7 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.MySQLIndexHint; import net.sf.jsqlparser.expression.SQLServerHints; +import net.sf.jsqlparser.expression.WindowDefinition; import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.imprt.Import; @@ -152,10 +153,17 @@ public Void visit(PlainSelect plainSelect, S context) { validateOptionalFromItems(plainSelect.getIntoTables()); validateOptionalJoins(plainSelect.getJoins()); - // to correctly recognize aliased tables - // @todo: fix this properly, I don't understand functional syntax - // validateOptionalList(plainSelect.getSelectItems(), () -> this, SelectItem::accept, - // context); + // after the FROM items, to correctly recognize aliased tables + if (plainSelect.getDistinct() != null + && isNotEmpty(plainSelect.getDistinct().getOnSelectItems())) { + plainSelect.getDistinct().getOnSelectItems() + .forEach(item -> item.accept(this, context)); + } + validateOptional(plainSelect.getTop(), + top -> validateOptionalExpression(top.getExpression())); + if (isNotEmpty(plainSelect.getSelectItems())) { + plainSelect.getSelectItems().forEach(item -> item.accept(this, context)); + } validateOptionalExpression(plainSelect.getPreWhere()); validateOptionalExpression(plainSelect.getWhere()); @@ -168,6 +176,13 @@ public Void visit(PlainSelect plainSelect, S context) { } validateOptionalExpression(plainSelect.getHaving()); + validateOptionalExpression(plainSelect.getQualify()); + if (isNotEmpty(plainSelect.getWindowDefinitions())) { + for (WindowDefinition windowDefinition : plainSelect.getWindowDefinitions()) { + validateOptionalExpressions(windowDefinition.getPartitionExpressionList()); + validateOptionalOrderByElements(windowDefinition.getOrderByElements()); + } + } validateOptionalOrderByElements(plainSelect.getOrderByElements()); validateOptionalInterpolate(plainSelect.getInterpolate()); validateOptionalOption(plainSelect.getOption()); @@ -332,6 +347,8 @@ public void validateOffset(Offset offset) { validateFeature(c, Feature.offset); validateOptionalFeature(c, offset.getOffsetParam(), Feature.offsetParam); } + + validateOptionalExpression(offset.getOffset()); } public void validateFetch(Fetch fetch) { @@ -341,7 +358,7 @@ public void validateFetch(Fetch fetch) { validateFeature(c, !fetch.isFetchParamFirst(), Feature.fetchNext); } - validateOptionalExpression(fetch.getFetchJdbcParameter()); + validateOptionalExpression(fetch.getExpression()); } public void validateOptionalJoins(List joins) { diff --git a/src/test/java/net/sf/jsqlparser/util/validation/metadata/DatabaseMetaDataValidationTest.java b/src/test/java/net/sf/jsqlparser/util/validation/metadata/DatabaseMetaDataValidationTest.java index 7c21680f06..1d0aaa81fa 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/metadata/DatabaseMetaDataValidationTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/metadata/DatabaseMetaDataValidationTest.java @@ -156,4 +156,12 @@ public void testValidationMetadataSelectWithColumnsAndAlias2() new JdbcDatabaseMetaDataCapability(connection, NamesLookup.UPPERCASE); validateNoErrors(sql, 1, DatabaseType.H2, meta); // no errors } + + @Test + public void testValidationMetadataSelectListError() throws JSQLParserException, SQLException { + String sql = "select my.id, my.price from mytable as my"; + JdbcDatabaseMetaDataCapability meta = + new JdbcDatabaseMetaDataCapability(connection, NamesLookup.UPPERCASE); + validateMetadata(sql, 1, 1, meta, true, "my.price"); + } } diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/SelectValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/SelectValidatorTest.java index 9c372310da..c6ffec2fd8 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/validator/SelectValidatorTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/SelectValidatorTest.java @@ -246,6 +246,29 @@ public void testValidateTableFunctionArguments(String sql) { validateNotAllowed(sql, 1, 1, SELECT_WITH_TABLE_FUNCTIONS, Feature.jdbcParameter); } + @ParameterizedTest + @ValueSource(strings = {"SELECT upper(a) FROM t", + "SELECT DISTINCT ON (upper(a)) a FROM t", + "SELECT TOP (upper(a)) a FROM t", + "SELECT a FROM t QUALIFY upper(a) = 'X'", + "SELECT a FROM t WINDOW w AS (PARTITION BY upper(a))", + "SELECT a FROM t WINDOW w AS (ORDER BY upper(a))", + "SELECT a FROM t LIMIT upper(a)", + "SELECT a FROM t LIMIT 1 OFFSET upper(a)", + "SELECT a FROM t OFFSET upper(a) ROWS", + "SELECT a FROM t FETCH FIRST upper(a) ROWS ONLY", + "SELECT a FROM t UNION SELECT b FROM u LIMIT upper(a)"}) + public void testValidateFunctionOutsideWhere(String sql) { + validateNotAllowed(sql, 1, 1, + new FeaturesAllowed(Feature.values()).remove(Feature.function), Feature.function); + } + + @Test + public void testValidateSubSelectInSelectList() { + validateNotAllowed("SELECT (SELECT a FROM t FOR UPDATE)", 1, 1, FeaturesAllowed.SELECT, + Feature.selectForUpdate); + } + @Test public void testValidateLateral() throws JSQLParserException { validateNoErrors(