/* * bench_parser.prg — Parser performance benchmark * Compares TSqlParser (original) vs TSqlParser2 (Pratt) * * Copyright (c) 2025-2026 Charles KWON (Charles KWON OhJun) * All rights reserved. */ #include "FiveSqlDef.ch" #include "hbclass.ch" #define ITERS 10000 PROCEDURE Main() LOCAL aSQL, i, j, t0, t1, oLex, aTokens, oP, h LOCAL nT1, nT2, aAllTokens aSQL := { ; "SELECT name, salary FROM employees WHERE salary > 5000", ; "SELECT e.name, o.product FROM employees e JOIN orders o ON e.id = o.emp_id WHERE o.amount > 100", ; "SELECT dept, COUNT(*) AS cnt, AVG(salary) AS avg_sal FROM employees GROUP BY dept HAVING AVG(salary) > 5000 ORDER BY avg_sal DESC", ; "SELECT name FROM employees WHERE id IN (SELECT emp_id FROM orders WHERE amount > 500)", ; "SELECT name, salary, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn FROM employees", ; "SELECT name, CASE WHEN salary > 7000 THEN 'High' WHEN salary > 5000 THEN 'Mid' ELSE 'Low' END AS tier FROM employees WHERE dept = 'Engineering' OR (salary > 6000 AND mgr_id IS NOT NULL)", ; "INSERT INTO employees (id, name, dept, salary) VALUES (99, 'Test', 'QA', 5000)", ; "UPDATE employees SET salary = salary * 1.1 + 500 WHERE dept = 'Sales' AND salary BETWEEN 4000 AND 6000" ; } ? "================================================================" ? " Parser Benchmark: TSqlParser vs TSqlParser2 (Pratt)" ? " Queries: " + hb_ntos( Len( aSQL ) ) + " Iterations: " + hb_ntos( ITERS ) ? "================================================================" ? /* Pre-tokenize all queries */ aAllTokens := {} FOR i := 1 TO Len( aSQL ) oLex := TSqlLexer():New( aSQL[ i ] ) oLex:Tokenize() AAdd( aAllTokens, oLex:GetTokens() ) NEXT /* Benchmark TSqlParser (original) */ t0 := hb_MilliSeconds() FOR j := 1 TO ITERS FOR i := 1 TO Len( aSQL ) oP := TSqlParser():New( aAllTokens[ i ], {} ) h := oP:Parse() NEXT NEXT t1 := hb_MilliSeconds() nT1 := t1 - t0 ? " TSqlParser (original) : " + Str( nT1, 8 ) + " ms (" + ; Str( nT1 * 1000 / ( ITERS * Len( aSQL ) ), 6, 1 ) + " us/parse)" /* Benchmark TSqlParser2 (Pratt) */ t0 := hb_MilliSeconds() FOR j := 1 TO ITERS FOR i := 1 TO Len( aSQL ) oP := TSqlParser2():New( aAllTokens[ i ], {} ) h := oP:Parse() NEXT NEXT t1 := hb_MilliSeconds() nT2 := t1 - t0 ? " TSqlParser2 (Pratt) : " + Str( nT2, 8 ) + " ms (" + ; Str( nT2 * 1000 / ( ITERS * Len( aSQL ) ), 6, 1 ) + " us/parse)" ? IF nT2 > 0 .AND. nT1 > 0 IF nT1 > nT2 ? " Pratt is " + Str( nT1 / nT2, 5, 2 ) + "x faster" ELSEIF nT2 > nT1 ? " Original is " + Str( nT2 / nT1, 5, 2 ) + "x faster" ELSE ? " Same speed" ENDIF ENDIF ? "================================================================" RETURN