1616import java .util .List ;
1717import java .util .concurrent .ExecutorService ;
1818import java .util .concurrent .Executors ;
19+ import java .util .concurrent .TimeUnit ;
1920import java .util .concurrent .TimeoutException ;
2021import java .util .logging .Level ;
2122
3334import static org .junit .jupiter .api .Assertions .assertThrows ;
3435import static org .junit .jupiter .api .Assertions .assertTrue ;
3536
37+ import net .sf .jsqlparser .test .MemoryLeakVerifier ;
3638import org .junit .jupiter .api .Assertions ;
3739import org .junit .jupiter .api .Disabled ;
3840import org .junit .jupiter .api .Test ;
@@ -296,6 +298,96 @@ public void testCondExpressionIssue1482_2() throws JSQLParserException {
296298 assertEquals ("test_table_enum.f1_enum IN ('TEST2'::test.\" test_enum\" )" , expr .toString ());
297299 }
298300
301+
302+ /**
303+ * The purpose of the test is to run into a timeout and to stop the parser when this happens. We
304+ * provide an INVALID statement for this purpose, which will fail the SIMPLE parse and then hang
305+ * with COMPLEX parsing until the timeout occurs.
306+ * <p>
307+ * We repeat that test multiple times and want to see no stale references to the Parser after
308+ * timeout.
309+ *
310+ * @throws JSQLParserException
311+ */
312+ @ Test
313+ public void testParserInterruptedByTimeout () throws InterruptedException {
314+ String sqlStr = "SELECT * FROM TABLE_1 t1\n "
315+ + "WHERE\n "
316+ + "(((t1.COL1 = 'VALUE2' )\n "
317+ + "AND (t1.CAL2 = 'VALUE2' ))\n "
318+ + "AND (((1 = 1 )\n "
319+ + "AND ((((((t1.id IN (940550 ,940600 ,940650 ,940700 ,940750 ,940800 ,940850 ,940900 ,940950 ,941000 ,941050 ,941100 ,941150 ,941200 ,941250 ,941300 ,941350 ,941400 ,941450 ,941500 ,941550 ,941600 ,941650 ,941700 ,941750 ,941800 ,941850 ,941900 ,941950 ,942000 ,942050 ,942100 ,942150 ,942200 ,942250 ,942300 ,942350 ,942400 ,942450 ,942500 ,942550 ,942600 ,942650 ,942700 ,942750 ,942800 ,942850 ,942900 ,942950 ,943000 ,943050 ,943100 ,943150 ,943200 ,943250 ,943300 ,943350 ,943400 ,943450 ,943500 ,943550 ,943600 ,943650 ,943700 ,943750 ,943800 ,943850 ,943900 ,943950 ,944000 ,944050 ,944100 ,944150 ,944200 ,944250 ,944300 ,944350 ,944400 ,944450 ,944500 ,944550 ,944600 ,944650 ,944700 ,944750 ,944800 ,944850 ,944900 ,944950 ,945000 ,945050 ,945100 ,945150 ,945200 ,945250 ,945300 ))\n "
320+ + "OR (t1.id IN (945350 ,945400 ,945450 ,945500 ,945550 ,945600 ,945650 ,945700 ,945750 ,945800 ,945850 ,945900 ,945950 ,946000 ,946050 ,946100 ,946150 ,946200 ,946250 ,946300 ,946350 ,946400 ,946450 ,946500 ,946550 ,946600 ,946650 ,946700 ,946750 ,946800 ,946850 ,946900 ,946950 ,947000 ,947050 ,947100 ,947150 ,947200 ,947250 ,947300 ,947350 ,947400 ,947450 ,947500 ,947550 ,947600 ,947650 ,947700 ,947750 ,947800 ,947850 ,947900 ,947950 ,948000 ,948050 ,948100 ,948150 ,948200 ,948250 ,948300 ,948350 ,948400 ,948450 ,948500 ,948550 ,948600 ,948650 ,948700 ,948750 ,948800 ,948850 ,948900 ,948950 ,949000 ,949050 ,949100 ,949150 ,949200 ,949250 ,949300 ,949350 ,949400 ,949450 ,949500 ,949550 ,949600 ,949650 ,949700 ,949750 ,949800 ,949850 ,949900 ,949950 ,950000 ,950050 ,950100 )))\n "
321+ + "OR (t1.id IN (950150 ,950200 ,950250 ,950300 ,950350 ,950400 ,950450 ,950500 ,950550 ,950600 ,950650 ,950700 ,950750 ,950800 ,950850 ,950900 ,950950 ,951000 ,951050 ,951100 ,951150 ,951200 ,951250 ,951300 ,951350 ,951400 ,951450 ,951500 ,951550 ,951600 ,951650 ,951700 ,951750 ,951800 ,951850 ,951900 ,951950 ,952000 ,952050 ,952100 ,952150 ,952200 ,952250 ,952300 ,952350 ,952400 ,952450 ,952500 ,952550 ,952600 ,952650 ,952700 ,952750 ,952800 ,952850 ,952900 ,952950 ,953000 ,953050 ,953100 ,953150 ,953200 ,953250 ,953300 ,953350 ,953400 ,953450 ,953500 ,953550 ,953600 ,953650 ,953700 )))\n "
322+ + "OR (t1.id IN (953750 ,953800 ,953850 ,953900 ,953950 ,954000 ,954050 ,954100 ,954150 ,954200 ,954250 ,954300 ,954350 ,954400 ,954450 ,954500 ,954550 ,954600 ,954650 ,954700 ,954750 ,954800 ,954850 ,954900 ,954950 ,955000 ,955050 ,955100 ,955150 ,955200 ,955250 ,955300 ,955350 ,955400 ,955450 ,955500 ,955550 ,955600 ,955650 ,955700 ,955750 ,955800 ,955850 ,955900 ,955950 ,956000 ,956050 ,956100 ,956150 ,956200 ,956250 ,956300 ,956350 ,956400 ,956450 ,956500 ,956550 ,956600 ,956650 ,956700 ,956750 ,956800 ,956850 ,956900 ,956950 ,957000 ,957050 ,957100 ,957150 ,957200 ,957250 ,957300 )))\n "
323+ + "OR (t1.id IN (944100, 944150, 944200, 944250, 944300, 944350, 944400, 944450, 944500, 944550, 944600, 944650, 944700, 944750, 944800, 944850, 944900, 944950, 945000 )))\n "
324+ + "OR (t1.id IN (957350 ,957400 ,957450 ,957500 ,957550 ,957600 ,957650 ,957700 ,957750 ,957800 ,957850 ,957900 ,957950 ,958000 ,958050 ,958100 ,958150 ,958200 ,958250 ,958300 ,958350 ,958400 ,958450 ,958500 ,958550 ,958600 ,958650 ,958700 ,958750 ,958800 ,958850 ,958900 ,958950 ,959000 ,959050 ,959100 ,959150 ,959200 ,959250 ,959300 ,959350 ,959400 ,959450 ,959500 ,959550 ,959600 ,959650 ,959700 ,959750 ,959800 ,959850 ,959900 ,959950 ,960000 ,960050 ,960100 ,960150 ,960200 ,960250 ,960300 ,960350 ,960400 ,960450 ,960500 ,960550 ,960600 ,960650 ,960700 ,960750 ,960800 ,960850 ,960900 ,960950 ,961000 ,961050 ,961100 ,961150 ,961200 ,961250 ,961300 ,961350 ,961400 ,961450 ,961500 ,961550 ,961600 ,961650 ,961700 ,961750 ,961800 ,961850 ,961900 ,961950 ,962000 ,962050 ,962100 ))))\n "
325+ + "OR (t1.id IN (962150 ,962200 ,962250 ,962300 ,962350 ,962400 ,962450 ,962500 ,962550 ,962600 ,962650 ,962700 ,962750 ,962800 ,962850 ,962900 ,962950 ,963000 ,963050 ,963100 ,963150 ,963200 ,963250 ,963300 ,963350 ,963400 ,963450 ,963500 ,963550 ,963600 ,963650 ,963700 ,963750 ,963800 ,963850 ,963900 ,963950 ,964000 ,964050 ,964100 ,964150 ,964200 ,964250 ,964300 ,964350 ,964400 ,964450 ,964500 ,964550 ,964600 ,964650 ,964700 ,964750 ,964800 ,964850 ,964900 ,964950 ,965000 ,965050 ,965100 ,965150 ,965200 ,965250 ,965300 ,965350 ,965400 ,965450 ,965500 ))))\n "
326+ + "AND t1.COL3 IN (\n "
327+ + " SELECT\n "
328+ + " t2.COL3\n "
329+ + " FROM\n "
330+ + " TABLE_6 t6,\n "
331+ + " TABLE_1 t5,\n "
332+ + " TABLE_4 t4,\n "
333+ + " TABLE_3 t3,\n "
334+ + " TABLE_1 t2\n "
335+ + " WHERE\n "
336+ + " (((((((t5.CAL3 = T6.id)\n "
337+ + " AND (t5.CAL5 = t6.CAL5))\n "
338+ + " AND (t5.CAL1 = t6.CAL1))\n "
339+ + " AND (t3.CAL1 IN (108500)))\n "
340+ + " AND (t5.id = t2.id))\n "
341+ + " AND NOT ((t6.CAL6 IN ('VALUE'))))\n "
342+ + " AND ((t2.id = t3.CAL2)\n "
343+ + " AND (t4.id = t3.CAL3))))\n " +
344+ // add two redundant unmatched brackets in order to make the Simple Parser fail
345+ // and get the complex parser stuck
346+ " )) \n "
347+ + "ORDER BY\n "
348+ + "t1.id ASC" ;
349+
350+ MemoryLeakVerifier verifier = new MemoryLeakVerifier ();
351+
352+ int parallelThreads = Runtime .getRuntime ().availableProcessors () + 1 ;
353+ ExecutorService executorService = Executors .newFixedThreadPool (parallelThreads );
354+ ExecutorService timeOutService = Executors .newSingleThreadExecutor ();
355+ for (int i = 0 ; i < parallelThreads ; i ++) {
356+ executorService .submit (new Runnable () {
357+ @ Override
358+ public void run () {
359+
360+ try {
361+ CCJSqlParser parser =
362+ CCJSqlParserUtil .newParser (sqlStr ).withAllowComplexParsing (true );
363+ verifier .addObject (parser );
364+
365+ Statement statement =
366+ CCJSqlParserUtil .parseStatement (parser , timeOutService );
367+ } catch (JSQLParserException ignore ) {
368+ // We expected that to happen.
369+ }
370+ }
371+ });
372+ }
373+
374+ executorService .shutdown ();
375+ timeOutService .shutdown ();
376+
377+ // we should not run in any timeout here (because we expect that the Parser has timed out by
378+ // itself)
379+ Assertions .assertDoesNotThrow (new Executable () {
380+ @ Override
381+ public void execute () throws Throwable {
382+ executorService .awaitTermination (10 , TimeUnit .SECONDS );
383+ timeOutService .awaitTermination (10 , TimeUnit .SECONDS );
384+ }
385+ });
386+
387+ // we should not have any Objects left in the weak reference map
388+ verifier .assertGarbageCollected ();
389+ }
390+
299391 @ Test
300392 public void testTimeOutIssue1582 () throws InterruptedException {
301393 // This statement is INVALID on purpose
@@ -304,15 +396,15 @@ public void testTimeOutIssue1582() throws InterruptedException {
304396
305397 String sqlStr = ""
306398 + "select\n "
307- + " t0.operatienr\n "
308- + " , case\n "
309- + " when\n "
310- + " case when (t0.vc_begintijd_operatie is null or lpad((extract('hours' into t0.vc_begintijd_operatie::timestamp))::text,2,'0') ||':'|| lpad(extract('minutes' from t0.vc_begintijd_operatie::timestamp)::text,2,'0') = '00:00') then null\n "
311- + " else (greatest(((extract('hours' into (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp))*60 + extract('minutes' from (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp)))/60)::numeric(12,2),0))*60\n "
312- + " end = 0 then null\n "
313- + " else '25. Meer dan 4 uur'\n "
314- + " end \n "
315- + " as snijtijd_interval" ;
399+ + " t0.operatienr\n "
400+ + " , case\n "
401+ + " when\n "
402+ + " case when (t0.vc_begintijd_operatie is null or lpad((extract('hours' into t0.vc_begintijd_operatie::timestamp))::text,2,'0') ||':'|| lpad(extract('minutes' from t0.vc_begintijd_operatie::timestamp)::text,2,'0') = '00:00') then null\n "
403+ + " else (greatest(((extract('hours' into (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp))*60 + extract('minutes' from (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp)))/60)::numeric(12,2),0))*60\n "
404+ + " end = 0 then null\n "
405+ + " else '25. Meer dan 4 uur'\n "
406+ + " end \n "
407+ + " as snijtijd_interval" ;
316408
317409 // With DEFAULT TIMEOUT 6 Seconds, we expect the statement to timeout normally
318410 // A TimeoutException wrapped into a Parser Exception should be thrown
@@ -359,7 +451,7 @@ public void execute() throws Throwable {
359451
360452 @ Test
361453 @ Timeout (2000 )
362- void testIssue1792 () throws JSQLParserException {
454+ void testIssue1792 () throws JSQLParserException , InterruptedException {
363455 String sqlStr =
364456 "SELECT ('{\" obj\" :{\" field\" : \" value\" }}'::JSON -> 'obj'::TEXT ->> 'field'::TEXT)" ;
365457
@@ -378,11 +470,13 @@ public void execute() throws Throwable {
378470 }
379471 });
380472 executorService .shutdown ();
473+ executorService .awaitTermination (1 , TimeUnit .MINUTES );
474+ CCJSqlParserUtil .LOGGER .setLevel (Level .OFF );
381475 }
382476
383477 // Supposed to time out
384478 @ Test
385- void testComplexIssue1792 () throws JSQLParserException {
479+ void testComplexIssue1792 () throws JSQLParserException , InterruptedException {
386480 ExecutorService executorService = Executors .newCachedThreadPool ();
387481
388482 String sqlStr =
@@ -422,5 +516,7 @@ void testComplexIssue1792() throws JSQLParserException {
422516 assertTrue (ex .getCause () instanceof TimeoutException );
423517 }
424518 executorService .shutdown ();
519+ executorService .awaitTermination (1 , TimeUnit .MINUTES );
520+ CCJSqlParserUtil .LOGGER .setLevel (Level .OFF );
425521 }
426522}
0 commit comments