From 38ae88a820d7737721cdc0406226b27fd678c148 Mon Sep 17 00:00:00 2001 From: dengliming Date: Sat, 14 Mar 2026 22:53:36 +0800 Subject: [PATCH] fix(parser): parse mysql fulltext AGAINST concat expression --- .../operators/relational/FullTextSearch.java | 23 ++++++++++++------- .../validator/ExpressionValidator.java | 1 + .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 12 ++-------- .../statement/select/SelectTest.java | 13 +++++++++++ .../validator/ExpressionValidatorTest.java | 11 +++++++++ 5 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/FullTextSearch.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/FullTextSearch.java index f191ae1a1..0bf79f0ec 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/FullTextSearch.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/FullTextSearch.java @@ -9,6 +9,10 @@ */ package net.sf.jsqlparser.expression.operators.relational; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.expression.JdbcNamedParameter; @@ -17,11 +21,6 @@ import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import net.sf.jsqlparser.schema.Column; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.Optional; - public class FullTextSearch extends ASTNodeAccessImpl implements Expression { private ExpressionList _matchColumns; @@ -44,16 +43,20 @@ public Expression getAgainstValue() { return this._againstValue; } - public void setAgainstValue(StringValue val) { + public void setAgainstValue(Expression val) { this._againstValue = val; } + public void setAgainstValue(StringValue val) { + setAgainstValue((Expression) val); + } + public void setAgainstValue(JdbcNamedParameter val) { - this._againstValue = val; + setAgainstValue((Expression) val); } public void setAgainstValue(JdbcParameter val) { - this._againstValue = val; + setAgainstValue((Expression) val); } public String getSearchModifier() { @@ -92,6 +95,10 @@ public FullTextSearch withMatchColumns(ExpressionList matchColumns) { } public FullTextSearch withAgainstValue(StringValue againstValue) { + return withAgainstValue((Expression) againstValue); + } + + public FullTextSearch withAgainstValue(Expression againstValue) { this.setAgainstValue(againstValue); return this; } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java index 1ad32ec91..f017cc979 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java @@ -275,6 +275,7 @@ public Void visit(ExcludesExpression excludesExpression, S context) { @Override public Void visit(FullTextSearch fullTextSearch, S context) { validateOptionalExpressions(fullTextSearch.getMatchColumns()); + validateOptionalExpression(fullTextSearch.getAgainstValue(), this); return null; } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 351f5432f..78ce9e69c 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -8810,22 +8810,14 @@ Execute Execute(): { FullTextSearch FullTextSearch() : { Token searchModifier; - Token againstValue; - JdbcParameter jdbcParameter; - JdbcNamedParameter jdbcNamedParameter; + Expression againstValue; FullTextSearch fs = new FullTextSearch(); ExpressionList matchedColumns; } { "(" matchedColumns=ColumnList() ")" "(" - ( - againstValue= { fs.setAgainstValue(new StringValue(againstValue.image)); } - | - jdbcParameter=JdbcParameter() { fs.setAgainstValue( jdbcParameter ); } - | - jdbcNamedParameter=JdbcNamedParameter() { fs.setAgainstValue( jdbcNamedParameter ); } - ) + againstValue=SimpleExpression() { fs.setAgainstValue(againstValue); } [ ( searchModifier="IN NATURAL LANGUAGE MODE" diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index 6375210cc..a398efaf0 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -55,6 +55,7 @@ import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.FullTextSearch; import net.sf.jsqlparser.expression.operators.relational.GreaterThan; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.LikeExpression; @@ -2279,6 +2280,18 @@ public void testFullTextSearchInDefaultMode() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(statement); } + @Test + public void testFullTextSearchAgainstFunctionInBooleanMode() throws JSQLParserException { + String statement = + "SELECT MATCH (name) AGAINST (concat('',?,'') IN BOOLEAN MODE) AS full_text FROM commodity"; + Select select = (Select) assertSqlCanBeParsedAndDeparsed(statement); + FullTextSearch fullTextSearch = assertInstanceOf(FullTextSearch.class, + select.getPlainSelect().getSelectItem(0).getExpression()); + + assertInstanceOf(Function.class, fullTextSearch.getAgainstValue()); + assertEquals("IN BOOLEAN MODE", fullTextSearch.getSearchModifier()); + } + @Test public void testIsTrue() throws JSQLParserException { String statement = "SELECT col FROM tbl WHERE col IS TRUE"; diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java index d83bb2e51..94429af53 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.util.validation.validator; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.util.validation.ValidationTestAsserts; import net.sf.jsqlparser.util.validation.feature.DatabaseType; import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; @@ -216,6 +217,16 @@ public void testOneColumnFullTextSearchMySQL() throws JSQLParserException { EXPRESSIONS); } + @Test + public void testFullTextSearchAgainstFunctionRequiresJdbcFeature() throws JSQLParserException { + validateNotAllowed( + "SELECT * FROM commodity WHERE MATCH (name) AGAINST (concat('',?,'') IN BOOLEAN MODE)", + 1, + 1, + EXPRESSIONS, + Feature.jdbcParameter); + } + @Test public void testAnalyticFunctionFilter() throws JSQLParserException { validateNoErrors(