diff --git a/src/main/java/net/sf/jsqlparser/expression/StringValue.java b/src/main/java/net/sf/jsqlparser/expression/StringValue.java index 46bf58810..e959ad292 100644 --- a/src/main/java/net/sf/jsqlparser/expression/StringValue.java +++ b/src/main/java/net/sf/jsqlparser/expression/StringValue.java @@ -123,14 +123,14 @@ public String getNotExcapedValue() { if (quoteStr != null && quoteStr.startsWith("$")) { return value; } - StringBuilder buffer = new StringBuilder(value); - int index = 0; - int deletesNum = 0; - while ((index = value.indexOf("''", index)) != -1) { - buffer.deleteCharAt(index - deletesNum); - index += 2; - deletesNum++; + StringBuilder buffer = new StringBuilder(value.length()); + int from = 0; + int index; + while ((index = value.indexOf("''", from)) != -1) { + buffer.append(value, from, index + 1); + from = index + 2; } + buffer.append(value, from, value.length()); return buffer.toString(); } diff --git a/src/test/java/net/sf/jsqlparser/expression/StringValueTest.java b/src/test/java/net/sf/jsqlparser/expression/StringValueTest.java index 3dcb03a3d..7ea053d62 100644 --- a/src/test/java/net/sf/jsqlparser/expression/StringValueTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/StringValueTest.java @@ -10,7 +10,9 @@ package net.sf.jsqlparser.expression; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import java.time.Duration; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.test.TestUtils; @@ -50,6 +52,28 @@ public void testGetNotExcapedValue() { assertEquals(expResult, result); } + @Test + public void testGetNotExcapedValueLargeInputIsLinear() { + // a string literal made of many escaped single quotes used to be unescaped with a + // deleteCharAt() per pair, which is quadratic and lets a modest literal burn CPU + int pairs = 1_000_000; + StringBuilder sb = new StringBuilder(2 * pairs + 2); + sb.append('\''); + for (int i = 0; i < pairs; i++) { + sb.append("''"); + } + sb.append('\''); + StringValue instance = new StringValue(sb.toString()); + + String result = assertTimeoutPreemptively(Duration.ofSeconds(2), + instance::getNotExcapedValue); + + assertEquals(pairs, result.length()); + for (int i = 0; i < result.length(); i++) { + assertEquals('\'', result.charAt(i)); + } + } + @Test public void testPrefixes() { checkStringValue("E'test'", "test", "E");