Skip to content

Commit c5b85ab

Browse files
authored
Fix dollar-quoted CREATE FUNCTION statement splitting (#2410)
* Fix dollar-quoted CREATE FUNCTION statement splitting * Fix exception
1 parent 0cb209c commit c5b85ab

File tree

3 files changed

+122
-8
lines changed

3 files changed

+122
-8
lines changed

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

Lines changed: 45 additions & 3 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'(" (~[])* ")'")

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/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
}

0 commit comments

Comments
 (0)