16
16
import java .util .List ;
17
17
import java .util .concurrent .ExecutorService ;
18
18
import java .util .concurrent .Executors ;
19
+ import java .util .concurrent .TimeUnit ;
19
20
import java .util .concurrent .TimeoutException ;
20
21
import java .util .logging .Level ;
21
22
33
34
import static org .junit .jupiter .api .Assertions .assertThrows ;
34
35
import static org .junit .jupiter .api .Assertions .assertTrue ;
35
36
37
+ import net .sf .jsqlparser .test .MemoryLeakVerifier ;
36
38
import org .junit .jupiter .api .Assertions ;
37
39
import org .junit .jupiter .api .Disabled ;
38
40
import org .junit .jupiter .api .Test ;
@@ -296,6 +298,96 @@ public void testCondExpressionIssue1482_2() throws JSQLParserException {
296
298
assertEquals ("test_table_enum.f1_enum IN ('TEST2'::test.\" test_enum\" )" , expr .toString ());
297
299
}
298
300
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
+
299
391
@ Test
300
392
public void testTimeOutIssue1582 () throws InterruptedException {
301
393
// This statement is INVALID on purpose
@@ -304,15 +396,15 @@ public void testTimeOutIssue1582() throws InterruptedException {
304
396
305
397
String sqlStr = ""
306
398
+ "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" ;
316
408
317
409
// With DEFAULT TIMEOUT 6 Seconds, we expect the statement to timeout normally
318
410
// A TimeoutException wrapped into a Parser Exception should be thrown
@@ -359,7 +451,7 @@ public void execute() throws Throwable {
359
451
360
452
@ Test
361
453
@ Timeout (2000 )
362
- void testIssue1792 () throws JSQLParserException {
454
+ void testIssue1792 () throws JSQLParserException , InterruptedException {
363
455
String sqlStr =
364
456
"SELECT ('{\" obj\" :{\" field\" : \" value\" }}'::JSON -> 'obj'::TEXT ->> 'field'::TEXT)" ;
365
457
@@ -378,11 +470,13 @@ public void execute() throws Throwable {
378
470
}
379
471
});
380
472
executorService .shutdown ();
473
+ executorService .awaitTermination (1 , TimeUnit .MINUTES );
474
+ CCJSqlParserUtil .LOGGER .setLevel (Level .OFF );
381
475
}
382
476
383
477
// Supposed to time out
384
478
@ Test
385
- void testComplexIssue1792 () throws JSQLParserException {
479
+ void testComplexIssue1792 () throws JSQLParserException , InterruptedException {
386
480
ExecutorService executorService = Executors .newCachedThreadPool ();
387
481
388
482
String sqlStr =
@@ -422,5 +516,7 @@ void testComplexIssue1792() throws JSQLParserException {
422
516
assertTrue (ex .getCause () instanceof TimeoutException );
423
517
}
424
518
executorService .shutdown ();
519
+ executorService .awaitTermination (1 , TimeUnit .MINUTES );
520
+ CCJSqlParserUtil .LOGGER .setLevel (Level .OFF );
425
521
}
426
522
}
0 commit comments