diff --git a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java index eea0c7c63..e999caecd 100644 --- a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java +++ b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java @@ -429,10 +429,11 @@ public static Statements parseStatements(String sqls, Consumer con } ExecutorService executorService = Executors.newSingleThreadExecutor(); - final Statements statements = parseStatements(sqls, executorService, consumer); - executorService.shutdown(); - - return statements; + try { + return parseStatements(sqls, executorService, consumer); + } finally { + executorService.shutdown(); + } } /** @@ -447,7 +448,6 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe return null; } - Statements statements = null; CCJSqlParser parser = newParser(sqls); if (consumer != null) { consumer.accept(parser); @@ -457,7 +457,7 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe // first, try to parse fast and simple try { - statements = parseStatements(parser.withAllowComplexParsing(false), executorService); + return parseStatements(parser.withAllowComplexParsing(false), executorService); } catch (JSQLParserException ex) { // when fast simple parsing fails, try complex parsing but only if it has a chance to // succeed @@ -468,10 +468,10 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe if (consumer != null) { consumer.accept(parser); } - statements = parseStatements(parser.withAllowComplexParsing(true), executorService); + return parseStatements(parser.withAllowComplexParsing(true), executorService); } + throw ex; } - return statements; } /** diff --git a/src/test/java/net/sf/jsqlparser/parser/ParseStatementsFailureTest.java b/src/test/java/net/sf/jsqlparser/parser/ParseStatementsFailureTest.java new file mode 100644 index 000000000..ee45377af --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/ParseStatementsFailureTest.java @@ -0,0 +1,81 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.statement.UnsupportedStatement; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.*; + +class ParseStatementsFailureTest { + @Test + void reportsFailureWhenComplexParsingIsDisabled() { + JSQLParserException exception = assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parseStatements("SELECT FROM", + parser -> parser.withAllowComplexParsing(false))); + assertNotNull(exception.getCause()); + } + + @Test + void reportsFailureWhenNestingPreventsRetry() { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parseStatements("SELECT (( FROM", + parser -> parser.withAllowComplexParsing(true).withAllowedNestingDepth(0))); + } + + @Test + void leavesCallerExecutorUsableAfterFailure() throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parseStatements("SELECT FROM", executor, + parser -> parser.withAllowComplexParsing(false))); + assertFalse(executor.isShutdown()); + assertEquals(2, CCJSqlParserUtil.parseStatements("SELECT 1; SELECT 2", executor, + parser -> parser.withAllowComplexParsing(false)).size()); + } finally { + executor.shutdownNow(); + } + } + + @Test + void reportsTimeoutWhenComplexParsingIsDisabled() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + CountDownLatch release = new CountDownLatch(1); + executor.submit(() -> release.await(30, TimeUnit.SECONDS)); + try { + JSQLParserException exception = assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parseStatements("SELECT 1", executor, + parser -> parser.withAllowComplexParsing(false).withTimeOut(50))); + assertInstanceOf(TimeoutException.class, exception.getCause()); + } finally { + release.countDown(); + executor.shutdownNow(); + } + } + + @Test + void preservesEmptyInputAndUnsupportedStatementContracts() throws Exception { + assertNull(CCJSqlParserUtil.parseStatements((String) null)); + assertNull(CCJSqlParserUtil.parseStatements("")); + assertInstanceOf(UnsupportedStatement.class, + CCJSqlParserUtil.parseStatements("SELECT 1; WHATEVER !", + parser -> parser.withAllowComplexParsing(false) + .withUnsupportedStatements(true)) + .get(1)); + } +}