Skip to content

Commit bcdbd29

Browse files
Merge branch 'master' of github.com:JSQLParser/JSqlParser
2 parents 0409f8b + be39a92 commit bcdbd29

File tree

8 files changed

+188
-26
lines changed

8 files changed

+188
-26
lines changed

src/main/java/net/sf/jsqlparser/expression/operators/relational/FullTextSearch.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
*/
1010
package net.sf.jsqlparser.expression.operators.relational;
1111

12+
import java.util.Arrays;
13+
import java.util.Collection;
14+
import java.util.Iterator;
15+
import java.util.Optional;
1216
import net.sf.jsqlparser.expression.Expression;
1317
import net.sf.jsqlparser.expression.ExpressionVisitor;
1418
import net.sf.jsqlparser.expression.JdbcNamedParameter;
@@ -17,11 +21,6 @@
1721
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
1822
import net.sf.jsqlparser.schema.Column;
1923

20-
import java.util.Arrays;
21-
import java.util.Collection;
22-
import java.util.Iterator;
23-
import java.util.Optional;
24-
2524
public class FullTextSearch extends ASTNodeAccessImpl implements Expression {
2625

2726
private ExpressionList<Column> _matchColumns;
@@ -44,16 +43,20 @@ public Expression getAgainstValue() {
4443
return this._againstValue;
4544
}
4645

47-
public void setAgainstValue(StringValue val) {
46+
public void setAgainstValue(Expression val) {
4847
this._againstValue = val;
4948
}
5049

50+
public void setAgainstValue(StringValue val) {
51+
setAgainstValue((Expression) val);
52+
}
53+
5154
public void setAgainstValue(JdbcNamedParameter val) {
52-
this._againstValue = val;
55+
setAgainstValue((Expression) val);
5356
}
5457

5558
public void setAgainstValue(JdbcParameter val) {
56-
this._againstValue = val;
59+
setAgainstValue((Expression) val);
5760
}
5861

5962
public String getSearchModifier() {
@@ -92,6 +95,10 @@ public FullTextSearch withMatchColumns(ExpressionList<Column> matchColumns) {
9295
}
9396

9497
public FullTextSearch withAgainstValue(StringValue againstValue) {
98+
return withAgainstValue((Expression) againstValue);
99+
}
100+
101+
public FullTextSearch withAgainstValue(Expression againstValue) {
95102
this.setAgainstValue(againstValue);
96103
return this;
97104
}

src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ public <S> Void visit(ExcludesExpression excludesExpression, S context) {
275275
@Override
276276
public <S> Void visit(FullTextSearch fullTextSearch, S context) {
277277
validateOptionalExpressions(fullTextSearch.getMatchColumns());
278+
validateOptionalExpression(fullTextSearch.getAgainstValue(), this);
278279
return null;
279280
}
280281

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,40 @@ TOKEN_MGR_DECLS : {
706706
return ((SimpleCharStream)input_stream).getAbsoluteTokenBegin();
707707
return -1;
708708
}
709+
710+
private static boolean endsWithDelimiter(Deque<Character> windowQueue, String delimiter) {
711+
if (windowQueue.size() != delimiter.length()) {
712+
return false;
713+
}
714+
715+
int i = 0;
716+
for (char ch : windowQueue) {
717+
if (ch != delimiter.charAt(i++)) {
718+
return false;
719+
}
720+
}
721+
return true;
722+
}
723+
724+
public void consumeDollarQuotedString(String closingQuote) {
725+
Deque<Character> windowQueue = new ArrayDeque<Character>();
726+
int delimiterLength = closingQuote.length();
727+
728+
try {
729+
while (true) {
730+
char ch = input_stream.readChar();
731+
windowQueue.addLast(ch);
732+
if (windowQueue.size() > delimiterLength) {
733+
windowQueue.removeFirst();
734+
}
735+
if (endsWithDelimiter(windowQueue, closingQuote)) {
736+
return;
737+
}
738+
}
739+
} catch (java.io.IOException e) {
740+
reportError(Math.max(closingQuote.length(), input_stream.GetImage().length()));
741+
}
742+
}
709743
}
710744

711745
SKIP:
@@ -1338,10 +1372,18 @@ TOKEN:
13381372
{
13391373
<S_PARAMETER: ["$"] (["0"-"9"])+ >
13401374
|
1341-
<S_IDENTIFIER: <LETTER> (<PART_LETTER>)*>
1375+
<S_DOLLAR_QUOTED_STRING: "$$">
1376+
{
1377+
consumeDollarQuotedString(matchedToken.image);
1378+
matchedToken.image = input_stream.GetImage();
1379+
matchedToken.kind = charLiteralIndex;
1380+
}
1381+
|
1382+
<S_IDENTIFIER: (<LETTER> (<PART_LETTER>)*) | "$" | ("$" <PART_LETTER_NO_DOLLAR> (<PART_LETTER>)*)>
13421383
| <#LETTER: <UnicodeIdentifierStart>
1343-
| <Nd> | [ "$" , "#", "_" ] // Not SQL:2016 compliant!
1384+
| <Nd> | [ "#", "_" ] // Not SQL:2016 compliant!
13441385
>
1386+
| <#PART_LETTER_NO_DOLLAR: <UnicodeIdentifierStart> | <UnicodeIdentifierExtend> | [ "#", "_" , "@" ] >
13451387
| <#PART_LETTER: <UnicodeIdentifierStart> | <UnicodeIdentifierExtend> | [ "$" , "#", "_" , "@" ] >
13461388
| <S_AT_IDENTIFIER: <K_AT_SIGN> (<K_AT_SIGN>)? <S_IDENTIFIER> >
13471389

@@ -1375,7 +1417,7 @@ TOKEN:
13751417
| < S_CHAR_LITERAL: (
13761418
(["U","E","N","R","B"]|"RB"|"_utf8")?
13771419
(
1378-
("'" ( <ESC> | <SPECIAL_ESC> | ~["'", "\\"] )* "'") | ("'" ("''" | ~["'"])* "'" | "$$" (~["$"])* "$$")
1420+
("'" ( <ESC> | <SPECIAL_ESC> | ~["'", "\\"] )* "'") | ("'" ("''" | ~["'"])* "'")
13791421
// Alternative Oracle Escape Modes
13801422
| ("q'{" (~[])* "}'")
13811423
| ("q'(" (~[])* ")'")
@@ -9041,22 +9083,14 @@ Execute Execute(): {
90419083

90429084
FullTextSearch FullTextSearch() : {
90439085
Token searchModifier;
9044-
Token againstValue;
9045-
JdbcParameter jdbcParameter;
9046-
JdbcNamedParameter jdbcNamedParameter;
9086+
Expression againstValue;
90479087
FullTextSearch fs = new FullTextSearch();
90489088
ExpressionList<Column> matchedColumns;
90499089
}
90509090
{
90519091
<K_MATCH> "(" matchedColumns=ColumnList() ")" <K_AGAINST>
90529092
"("
9053-
(
9054-
againstValue=<S_CHAR_LITERAL> { fs.setAgainstValue(new StringValue(againstValue.image)); }
9055-
|
9056-
jdbcParameter=JdbcParameter() { fs.setAgainstValue( jdbcParameter ); }
9057-
|
9058-
jdbcNamedParameter=JdbcNamedParameter() { fs.setAgainstValue( jdbcNamedParameter ); }
9059-
)
9093+
againstValue=SimpleExpression() { fs.setAgainstValue(againstValue); }
90609094
[
90619095
(
90629096
searchModifier="IN NATURAL LANGUAGE MODE"

src/test/java/net/sf/jsqlparser/expression/StringValueTest.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
*/
1010
package net.sf.jsqlparser.expression;
1111

12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
1214
import net.sf.jsqlparser.JSQLParserException;
1315
import net.sf.jsqlparser.statement.select.PlainSelect;
1416
import net.sf.jsqlparser.test.TestUtils;
1517
import org.junit.jupiter.api.Assertions;
1618
import org.junit.jupiter.api.Test;
1719

18-
import static org.junit.jupiter.api.Assertions.assertEquals;
19-
2020
/**
2121
*
2222
* @author toben
@@ -103,4 +103,12 @@ void testDollarQuotesIssue2267() throws JSQLParserException {
103103

104104
Assertions.assertInstanceOf(StringValue.class, select.getSelectItem(0).getExpression());
105105
}
106+
107+
@Test
108+
void testDollarQuotesWithDollarSignsInside() throws JSQLParserException {
109+
String sqlStr = "SELECT $$this references $1 and costs $5$$ FROM tbl;";
110+
PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
111+
112+
Assertions.assertInstanceOf(StringValue.class, select.getSelectItem(0).getExpression());
113+
}
106114
}

src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,30 @@ public void testIssue2090LockExclusive() throws JSQLParserException {
11881188
assertEquals("EXCLUSIVE", lockExp.getLockOption());
11891189
}
11901190

1191+
@Test
1192+
public void testIssue2091ModifyColumnCharacterSet() throws JSQLParserException {
1193+
String sql = "ALTER TABLE `jobs`.`runs` MODIFY COLUMN triggerInfo text "
1194+
+ "CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL";
1195+
1196+
Statement stmt = CCJSqlParserUtil.parse(sql);
1197+
assertTrue(stmt instanceof Alter);
1198+
1199+
Alter alter = (Alter) stmt;
1200+
assertEquals("`jobs`.`runs`", alter.getTable().getFullyQualifiedName());
1201+
1202+
List<AlterExpression> alterExpressions = alter.getAlterExpressions();
1203+
assertNotNull(alterExpressions);
1204+
assertEquals(1, alterExpressions.size());
1205+
1206+
ColumnDataType column = alterExpressions.get(0).getColDataTypeList().get(0);
1207+
assertEquals("triggerInfo", column.getColumnName());
1208+
assertEquals("text CHARACTER SET utf8mb4", column.getColDataType().toString());
1209+
assertEquals(Arrays.asList("COLLATE", "utf8mb4_unicode_ci", "NOT", "NULL"),
1210+
column.getColumnSpecs());
1211+
1212+
assertSqlCanBeParsedAndDeparsed(sql);
1213+
}
1214+
11911215
@ParameterizedTest
11921216
@MethodSource("provideMySQLConvertTestCases")
11931217
public void testIssue2089(String sql, String expectedCharacterSet, String expectedCollation)

src/test/java/net/sf/jsqlparser/statement/create/CreateFunctionalStatementTest.java

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
*/
1010
package net.sf.jsqlparser.statement.create;
1111

12+
import static net.sf.jsqlparser.test.TestUtils.assertDeparse;
13+
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
1216
import java.util.Arrays;
1317
import net.sf.jsqlparser.JSQLParserException;
1418
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
19+
import net.sf.jsqlparser.statement.Statements;
1520
import net.sf.jsqlparser.statement.create.function.CreateFunction;
1621
import net.sf.jsqlparser.statement.create.procedure.CreateProcedure;
17-
import static net.sf.jsqlparser.test.TestUtils.assertDeparse;
18-
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
19-
import static org.assertj.core.api.Assertions.assertThat;
2022
import org.junit.jupiter.api.Test;
2123

2224
/**
@@ -86,4 +88,66 @@ public void createOrReplaceFunctionMinimal() throws JSQLParserException {
8688
func.setOrReplace(true);
8789
assertDeparse(func, statement);
8890
}
91+
92+
@Test
93+
public void createFunctionWithPositionalParametersAcrossStatementsIssue2322()
94+
throws JSQLParserException {
95+
String sql = "create table if not exists test_table (\n"
96+
+ " id bigint not null\n"
97+
+ ");\n"
98+
+ "\n"
99+
+ "create or replace function test_fn_1(\n"
100+
+ " target text,\n"
101+
+ " characters text\n"
102+
+ ") returns boolean as $$\n"
103+
+ " select trim($2 from $1) <> $1\n"
104+
+ "$$ language sql immutable;\n"
105+
+ "\n"
106+
+ "create or replace function test_fn_2(\n"
107+
+ " target text,\n"
108+
+ " characters text\n"
109+
+ ") returns boolean as $$\n"
110+
+ " select position(repeat(first_char, 2) in translate(\n"
111+
+ " $1, $2, repeat(first_char, length($2))\n"
112+
+ " )) > 0\n"
113+
+ " from (values (left($2, 1))) params(first_char)\n"
114+
+ "$$ language sql immutable;\n"
115+
+ "\n"
116+
+ "create table if not exists test_table_2 (\n"
117+
+ " id bigint not null\n"
118+
+ ");";
119+
120+
Statements statements = CCJSqlParserUtil.parseStatements(sql);
121+
122+
assertThat(statements.getStatements()).hasSize(4);
123+
assertThat(statements.getStatements().get(1)).isInstanceOf(CreateFunction.class);
124+
assertThat(statements.getStatements().get(2)).isInstanceOf(CreateFunction.class);
125+
126+
CreateFunction function1 = (CreateFunction) statements.getStatements().get(1);
127+
CreateFunction function2 = (CreateFunction) statements.getStatements().get(2);
128+
129+
assertThat(function1.getFunctionDeclarationParts()).anySatisfy(
130+
token -> assertThat(token).startsWith("$$").endsWith("$$"));
131+
assertThat(function1.getFunctionDeclarationParts()).containsSequence("language", "sql",
132+
"immutable", ";");
133+
assertThat(String.join(" ", function1.getFunctionDeclarationParts()))
134+
.contains("test_fn_1")
135+
.contains("$2")
136+
.contains("$1")
137+
.doesNotContain("create or replace function test_fn_2");
138+
139+
assertThat(function2.getFunctionDeclarationParts()).anySatisfy(
140+
token -> assertThat(token).startsWith("$$").endsWith("$$"));
141+
assertThat(function2.getFunctionDeclarationParts()).containsSequence("language", "sql",
142+
"immutable", ";");
143+
assertThat(String.join(" ", function2.getFunctionDeclarationParts()))
144+
.contains("test_fn_2")
145+
.contains("params")
146+
.doesNotContain("create table if not exists test_table_2");
147+
148+
assertThat(function1.formatDeclaration()).contains("test_fn_1");
149+
assertThat(function1.formatDeclaration()).doesNotContain("test_fn_2");
150+
assertThat(function2.formatDeclaration()).contains("test_fn_2");
151+
assertThat(function2.formatDeclaration()).doesNotContain("test_table_2");
152+
}
89153
}

src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
5656
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
5757
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
58+
import net.sf.jsqlparser.expression.operators.relational.FullTextSearch;
5859
import net.sf.jsqlparser.expression.operators.relational.GreaterThan;
5960
import net.sf.jsqlparser.expression.operators.relational.InExpression;
6061
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
@@ -2279,6 +2280,18 @@ public void testFullTextSearchInDefaultMode() throws JSQLParserException {
22792280
assertSqlCanBeParsedAndDeparsed(statement);
22802281
}
22812282

2283+
@Test
2284+
public void testFullTextSearchAgainstFunctionInBooleanMode() throws JSQLParserException {
2285+
String statement =
2286+
"SELECT MATCH (name) AGAINST (concat('',?,'') IN BOOLEAN MODE) AS full_text FROM commodity";
2287+
Select select = (Select) assertSqlCanBeParsedAndDeparsed(statement);
2288+
FullTextSearch fullTextSearch = assertInstanceOf(FullTextSearch.class,
2289+
select.getPlainSelect().getSelectItem(0).getExpression());
2290+
2291+
assertInstanceOf(Function.class, fullTextSearch.getAgainstValue());
2292+
assertEquals("IN BOOLEAN MODE", fullTextSearch.getSearchModifier());
2293+
}
2294+
22822295
@Test
22832296
public void testIsTrue() throws JSQLParserException {
22842297
String statement = "SELECT col FROM tbl WHERE col IS TRUE";

src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package net.sf.jsqlparser.util.validation.validator;
1111

1212
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.parser.feature.Feature;
1314
import net.sf.jsqlparser.util.validation.ValidationTestAsserts;
1415
import net.sf.jsqlparser.util.validation.feature.DatabaseType;
1516
import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
@@ -216,6 +217,16 @@ public void testOneColumnFullTextSearchMySQL() throws JSQLParserException {
216217
EXPRESSIONS);
217218
}
218219

220+
@Test
221+
public void testFullTextSearchAgainstFunctionRequiresJdbcFeature() throws JSQLParserException {
222+
validateNotAllowed(
223+
"SELECT * FROM commodity WHERE MATCH (name) AGAINST (concat('',?,'') IN BOOLEAN MODE)",
224+
1,
225+
1,
226+
EXPRESSIONS,
227+
Feature.jdbcParameter);
228+
}
229+
219230
@Test
220231
public void testAnalyticFunctionFilter() throws JSQLParserException {
221232
validateNoErrors(

0 commit comments

Comments
 (0)