Major changes since last commit: - FiveSql2 SQL:1999 engine (10,458 LOC) — 43/43 ALL PASS - 21 compiler/runtime bugs fixed (short-circuit AND/OR, FOR LOOP, etc.) - @byref pass-by-reference via RefCell pattern - Mutable closure capture (EnsureLocalRef + RefCell sharing) - RTL: 400 → 479 functions (+79: file, string, datetime, hash, UTF-8) - DateTime/Timestamp fully working (hb_DateTime, hb_Hour/Min/Sec, display) - Reserved word guard (39 keywords blocked from function calls) - AEval arg order fix (element before index) - Closure capture redecl fix (unique _cap_ names per block) - Hash/string indexing in ArrayPush/ArrayPop - Harbour compat test suite: 51/51 - 4 docs: Porting Report, Implementation Plan, Optimization Plan, Commercialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
549 lines
19 KiB
Plaintext
549 lines
19 KiB
Plaintext
/*
|
|
* test_sql_standards.prg — SQL:2003-2023 Parser Feature Tests
|
|
*
|
|
* Parser-level tests: verifies that all new SQL standard constructs
|
|
* parse without error and produce the correct AST node types.
|
|
* No database setup required — tokenize + parse only.
|
|
*
|
|
* FiveSql — SQL Engine for Harbour DBF/NTX
|
|
*
|
|
* Copyright (c) 2025-2026 Charles KWON (Charles KWON OhJun)
|
|
* Email: charleskwonohjun@gmail.com
|
|
*
|
|
* All rights reserved.
|
|
*/
|
|
|
|
#include "FiveSqlDef.ch"
|
|
#include "hbclass.ch"
|
|
|
|
STATIC s_nPass := 0
|
|
STATIC s_nFail := 0
|
|
STATIC s_nTotal := 0
|
|
|
|
|
|
PROCEDURE Main()
|
|
|
|
ErrorBlock( {|e| QOut( "TRAP: " + e:description + " " + e:operation ), Break(e) } )
|
|
|
|
? "================================================================"
|
|
? " FiveSql SQL:2003-2023 Parser Feature Test Suite"
|
|
? "================================================================"
|
|
?
|
|
|
|
? "--- Section 1: InfixBP Operators (SQL:2003) ---"
|
|
TestInfixOperators()
|
|
|
|
? ""
|
|
? "--- Section 2: Primary Expressions (SQL:1992-2023) ---"
|
|
TestPrimaryExpressions()
|
|
|
|
? ""
|
|
? "--- Section 3: SELECT Clauses (SQL:2003-2008) ---"
|
|
TestSelectClauses()
|
|
|
|
? ""
|
|
? "--- Section 4: Window Frame Specification (SQL:2003-2011) ---"
|
|
TestWindowFrames()
|
|
|
|
? ""
|
|
? "--- Section 5: New Statement Types (SQL:2003-2008) ---"
|
|
TestNewStatements()
|
|
|
|
? ""
|
|
? "--- Section 6: JSON Functions (SQL:2016) ---"
|
|
TestJsonFunctions()
|
|
|
|
? ""
|
|
? "--- Section 7: XML Functions (SQL:2003) ---"
|
|
TestXmlFunctions()
|
|
|
|
? ""
|
|
? "--- Section 8: SQL:2023 Functions ---"
|
|
TestSql2023Functions()
|
|
|
|
? ""
|
|
? "================================================================"
|
|
? " RESULTS"
|
|
? " Pass: " + hb_ntos( s_nPass )
|
|
? " Fail: " + hb_ntos( s_nFail )
|
|
? " Total: " + hb_ntos( s_nTotal )
|
|
? " Rate: " + hb_ntos( Int( s_nPass * 100 / Max( s_nTotal, 1 ) ) ) + "%"
|
|
? "================================================================"
|
|
|
|
IF s_nFail > 0
|
|
? " *** FAILURES DETECTED ***"
|
|
ErrorLevel( 1 )
|
|
ELSE
|
|
? " All tests passed."
|
|
ENDIF
|
|
|
|
RETURN
|
|
|
|
|
|
/* ---- Helpers ---- */
|
|
|
|
STATIC FUNCTION ParseSQL( cSQL )
|
|
|
|
LOCAL oLexer, oParser, hResult
|
|
|
|
oLexer := TSqlLexer():New( cSQL )
|
|
oLexer:Tokenize()
|
|
oParser := TSqlParser2():New( oLexer:GetTokens(), {} )
|
|
|
|
BEGIN SEQUENCE
|
|
hResult := oParser:Parse()
|
|
RECOVER
|
|
hResult := NIL
|
|
END SEQUENCE
|
|
|
|
RETURN hResult
|
|
|
|
|
|
STATIC FUNCTION Assert( cLabel, lCond )
|
|
|
|
s_nTotal++
|
|
IF lCond
|
|
s_nPass++
|
|
? " PASS: " + cLabel
|
|
ELSE
|
|
s_nFail++
|
|
? " FAIL: " + cLabel
|
|
ENDIF
|
|
|
|
RETURN lCond
|
|
|
|
|
|
/* ---- Section 1: InfixBP Operators ---- */
|
|
|
|
STATIC PROCEDURE TestInfixOperators()
|
|
|
|
LOCAL h
|
|
|
|
/* 1.1 SIMILAR TO (SQL:2003) */
|
|
h := ParseSQL( "SELECT * FROM t WHERE name SIMILAR TO '%pattern%'" )
|
|
Assert( "1.1 SIMILAR TO", h != NIL .AND. h[ "type" ] == "SELECT" .AND. ;
|
|
h[ "where" ] != NIL .AND. h[ "where" ][ 2 ] == "SIMILAR TO" )
|
|
|
|
/* 1.2 SIMILAR TO with ESCAPE */
|
|
h := ParseSQL( "SELECT * FROM t WHERE name SIMILAR TO '%x%' ESCAPE '\'" )
|
|
Assert( "1.2 SIMILAR TO ESCAPE", h != NIL .AND. h[ "where" ] != NIL .AND. ;
|
|
h[ "where" ][ 2 ] == "SIMILAR TO" .AND. h[ "where" ][ 5 ] != NIL )
|
|
|
|
/* 1.3 NOT SIMILAR TO */
|
|
h := ParseSQL( "SELECT * FROM t WHERE name NOT SIMILAR TO '%bad%'" )
|
|
Assert( "1.3 NOT SIMILAR TO", h != NIL .AND. h[ "where" ] != NIL .AND. ;
|
|
h[ "where" ][ 1 ] == ND_UNI .AND. h[ "where" ][ 2 ] == "NOT" )
|
|
|
|
/* 1.4 IS DISTINCT FROM (SQL:2003) */
|
|
h := ParseSQL( "SELECT * FROM t WHERE a IS DISTINCT FROM b" )
|
|
Assert( "1.4 IS DISTINCT FROM", h != NIL .AND. h[ "where" ] != NIL .AND. ;
|
|
h[ "where" ][ 2 ] == "IS DISTINCT FROM" )
|
|
|
|
/* 1.5 IS NOT DISTINCT FROM (SQL:2003) */
|
|
h := ParseSQL( "SELECT * FROM t WHERE a IS NOT DISTINCT FROM b" )
|
|
Assert( "1.5 IS NOT DISTINCT FROM", h != NIL .AND. h[ "where" ] != NIL .AND. ;
|
|
h[ "where" ][ 2 ] == "IS NOT DISTINCT FROM" )
|
|
|
|
RETURN
|
|
|
|
|
|
/* ---- Section 2: Primary Expressions ---- */
|
|
|
|
STATIC PROCEDURE TestPrimaryExpressions()
|
|
|
|
LOCAL h, xCol
|
|
|
|
/* 2.1 CAST(expr AS type) */
|
|
h := ParseSQL( "SELECT CAST(salary AS VARCHAR(20)) FROM t" )
|
|
Assert( "2.1 CAST", h != NIL .AND. h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "CAST" )
|
|
|
|
/* 2.2 CAST with precision */
|
|
h := ParseSQL( "SELECT CAST(price AS DECIMAL(10,2)) FROM t" )
|
|
Assert( "2.2 CAST DECIMAL(10,2)", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "CAST" )
|
|
|
|
/* 2.3 EXTRACT(part FROM expr) */
|
|
h := ParseSQL( "SELECT EXTRACT(YEAR FROM hire_date) FROM t" )
|
|
Assert( "2.3 EXTRACT", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "EXTRACT" )
|
|
|
|
/* 2.4 TRIM with spec */
|
|
h := ParseSQL( "SELECT TRIM(LEADING ' ' FROM name) FROM t" )
|
|
Assert( "2.4 TRIM LEADING", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "TRIM" )
|
|
|
|
/* 2.5 TRIM(TRAILING) */
|
|
h := ParseSQL( "SELECT TRIM(TRAILING 'x' FROM name) FROM t" )
|
|
Assert( "2.5 TRIM TRAILING", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "TRIM" )
|
|
|
|
/* 2.6 TRIM(BOTH) */
|
|
h := ParseSQL( "SELECT TRIM(BOTH FROM name) FROM t" )
|
|
Assert( "2.6 TRIM BOTH", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "TRIM" )
|
|
|
|
/* 2.7 POSITION(str IN str) */
|
|
h := ParseSQL( "SELECT POSITION('x' IN name) FROM t" )
|
|
Assert( "2.7 POSITION", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "POSITION" )
|
|
|
|
/* 2.8 OVERLAY(str PLACING str FROM n FOR n) */
|
|
h := ParseSQL( "SELECT OVERLAY(name PLACING 'X' FROM 1 FOR 3) FROM t" )
|
|
Assert( "2.8 OVERLAY", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "OVERLAY" )
|
|
|
|
/* 2.9 ARRAY constructor */
|
|
h := ParseSQL( "SELECT ARRAY(1, 2, 3) FROM t" )
|
|
Assert( "2.9 ARRAY", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "ARRAY" )
|
|
|
|
/* 2.10 ROW value constructor */
|
|
h := ParseSQL( "SELECT ROW(1, 'a', 3.14) FROM t" )
|
|
Assert( "2.10 ROW", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "ROW" )
|
|
|
|
/* 2.11 COALESCE (handled as normal function) */
|
|
h := ParseSQL( "SELECT COALESCE(a, b, 0) FROM t" )
|
|
Assert( "2.11 COALESCE", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "COALESCE" )
|
|
|
|
/* 2.12 NULLIF (handled as normal function) */
|
|
h := ParseSQL( "SELECT NULLIF(a, 0) FROM t" )
|
|
Assert( "2.12 NULLIF", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "NULLIF" )
|
|
|
|
/* 2.13 LISTAGG with WITHIN GROUP */
|
|
h := ParseSQL( "SELECT LISTAGG(name, ',') WITHIN GROUP (ORDER BY name) FROM t" )
|
|
Assert( "2.13 LISTAGG", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "LISTAGG" )
|
|
|
|
RETURN
|
|
|
|
|
|
/* ---- Section 3: SELECT Clauses ---- */
|
|
|
|
STATIC PROCEDURE TestSelectClauses()
|
|
|
|
LOCAL h
|
|
|
|
/* 3.1 NULLS FIRST in ORDER BY */
|
|
h := ParseSQL( "SELECT * FROM t ORDER BY name ASC NULLS FIRST" )
|
|
Assert( "3.1 NULLS FIRST", h != NIL .AND. ;
|
|
Len( h[ "order_by" ] ) > 0 .AND. Len( h[ "order_by" ][ 1 ] ) >= 3 .AND. ;
|
|
h[ "order_by" ][ 1 ][ 3 ] == "FIRST" )
|
|
|
|
/* 3.2 NULLS LAST in ORDER BY */
|
|
h := ParseSQL( "SELECT * FROM t ORDER BY salary DESC NULLS LAST" )
|
|
Assert( "3.2 NULLS LAST", h != NIL .AND. ;
|
|
Len( h[ "order_by" ] ) > 0 .AND. Len( h[ "order_by" ][ 1 ] ) >= 3 .AND. ;
|
|
h[ "order_by" ][ 1 ][ 3 ] == "LAST" )
|
|
|
|
/* 3.3 FETCH FIRST n ROWS ONLY (SQL:2008) */
|
|
h := ParseSQL( "SELECT * FROM t ORDER BY id FETCH FIRST 10 ROWS ONLY" )
|
|
Assert( "3.3 FETCH FIRST ONLY", h != NIL .AND. ;
|
|
hb_HHasKey( h, "fetch" ) .AND. h[ "fetch" ] == 10 .AND. ;
|
|
h[ "fetch_ties" ] == "ONLY" )
|
|
|
|
/* 3.4 FETCH FIRST WITH TIES (SQL:2008) */
|
|
h := ParseSQL( "SELECT * FROM t ORDER BY id FETCH FIRST 5 ROWS WITH TIES" )
|
|
Assert( "3.4 FETCH WITH TIES", h != NIL .AND. ;
|
|
hb_HHasKey( h, "fetch" ) .AND. h[ "fetch" ] == 5 .AND. ;
|
|
h[ "fetch_ties" ] == "WITH TIES" )
|
|
|
|
/* 3.5 OFFSET n ROWS (SQL:2008) */
|
|
h := ParseSQL( "SELECT * FROM t ORDER BY id OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY" )
|
|
Assert( "3.5 OFFSET ROWS", h != NIL .AND. ;
|
|
h[ "offset" ] == 20 .AND. h[ "fetch" ] == 10 )
|
|
|
|
/* 3.6 GROUPING SETS (SQL:2003) */
|
|
h := ParseSQL( "SELECT dept, yr, SUM(amt) FROM t GROUP BY GROUPING SETS ((dept), (yr), ())" )
|
|
Assert( "3.6 GROUPING SETS", h != NIL .AND. ;
|
|
Len( h[ "group_by" ] ) > 0 .AND. ;
|
|
h[ "group_by" ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "group_by" ][ 1 ][ 2 ] == "GROUPING SETS" )
|
|
|
|
/* 3.7 ROLLUP (SQL:2003) */
|
|
h := ParseSQL( "SELECT dept, SUM(salary) FROM t GROUP BY ROLLUP(dept)" )
|
|
Assert( "3.7 ROLLUP", h != NIL .AND. ;
|
|
Len( h[ "group_by" ] ) > 0 .AND. ;
|
|
h[ "group_by" ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "group_by" ][ 1 ][ 2 ] == "ROLLUP" )
|
|
|
|
/* 3.8 CUBE (SQL:2003) */
|
|
h := ParseSQL( "SELECT dept, yr, SUM(amt) FROM t GROUP BY CUBE(dept, yr)" )
|
|
Assert( "3.8 CUBE", h != NIL .AND. ;
|
|
Len( h[ "group_by" ] ) > 0 .AND. ;
|
|
h[ "group_by" ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "group_by" ][ 1 ][ 2 ] == "CUBE" )
|
|
|
|
/* 3.9 LATERAL subquery (SQL:2003) */
|
|
h := ParseSQL( "SELECT * FROM t, LATERAL (SELECT * FROM s WHERE s.id = t.id) AS lat" )
|
|
Assert( "3.9 LATERAL subquery", h != NIL .AND. ;
|
|
Len( h[ "tables" ] ) >= 2 .AND. ;
|
|
h[ "tables" ][ 2 ][ 1 ] == "__LATERAL__" )
|
|
|
|
/* 3.10 FOR UPDATE (SQL:2003) */
|
|
h := ParseSQL( "SELECT * FROM t WHERE id = 1 FOR UPDATE" )
|
|
Assert( "3.10 FOR UPDATE", h != NIL .AND. ;
|
|
hb_HHasKey( h, "for_lock" ) .AND. h[ "for_lock" ] == "UPDATE" )
|
|
|
|
/* 3.11 FOR SHARE (SQL:2003) */
|
|
h := ParseSQL( "SELECT * FROM t FOR SHARE" )
|
|
Assert( "3.11 FOR SHARE", h != NIL .AND. ;
|
|
hb_HHasKey( h, "for_lock" ) .AND. h[ "for_lock" ] == "SHARE" )
|
|
|
|
/* 3.12 FOR UPDATE OF col (SQL:2003) */
|
|
h := ParseSQL( "SELECT * FROM t FOR UPDATE OF name, salary" )
|
|
Assert( "3.12 FOR UPDATE OF", h != NIL .AND. ;
|
|
hb_HHasKey( h, "for_lock_cols" ) .AND. ;
|
|
Len( h[ "for_lock_cols" ] ) == 2 )
|
|
|
|
/* 3.13 WINDOW clause (SQL:2003) */
|
|
h := ParseSQL( "SELECT name, SUM(salary) OVER w FROM t WINDOW w AS (ORDER BY id)" )
|
|
Assert( "3.13 WINDOW clause", h != NIL .AND. ;
|
|
hb_HHasKey( h, "window_defs" ) .AND. ;
|
|
Len( h[ "window_defs" ] ) == 1 .AND. ;
|
|
h[ "window_defs" ][ 1 ][ 1 ] == "W" )
|
|
|
|
RETURN
|
|
|
|
|
|
/* ---- Section 4: Window Frame Specification ---- */
|
|
|
|
STATIC PROCEDURE TestWindowFrames()
|
|
|
|
LOCAL h, xCol
|
|
|
|
/* 4.1 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW */
|
|
h := ParseSQL( "SELECT SUM(x) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t" )
|
|
Assert( "4.1 ROWS frame", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_WINDOW )
|
|
|
|
/* 4.2 RANGE BETWEEN n PRECEDING AND n FOLLOWING */
|
|
h := ParseSQL( "SELECT AVG(x) OVER (ORDER BY id RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING) FROM t" )
|
|
Assert( "4.2 RANGE frame", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_WINDOW )
|
|
|
|
/* 4.3 GROUPS frame (SQL:2011) */
|
|
h := ParseSQL( "SELECT SUM(x) OVER (ORDER BY id GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t" )
|
|
Assert( "4.3 GROUPS frame", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_WINDOW )
|
|
|
|
/* 4.4 Frame with EXCLUDE (SQL:2011) */
|
|
h := ParseSQL( "SELECT SUM(x) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT ROW) FROM t" )
|
|
Assert( "4.4 EXCLUDE CURRENT ROW", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_WINDOW )
|
|
|
|
/* 4.5 EXCLUDE NO OTHERS */
|
|
h := ParseSQL( "SELECT SUM(x) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE NO OTHERS) FROM t" )
|
|
Assert( "4.5 EXCLUDE NO OTHERS", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_WINDOW )
|
|
|
|
/* 4.6 EXCLUDE GROUP */
|
|
h := ParseSQL( "SELECT SUM(x) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP) FROM t" )
|
|
Assert( "4.6 EXCLUDE GROUP", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_WINDOW )
|
|
|
|
/* 4.7 EXCLUDE TIES */
|
|
h := ParseSQL( "SELECT SUM(x) OVER (ORDER BY id ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING EXCLUDE TIES) FROM t" )
|
|
Assert( "4.7 EXCLUDE TIES", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_WINDOW )
|
|
|
|
/* 4.8 PARTITION BY + ORDER BY + frame */
|
|
h := ParseSQL( "SELECT SUM(x) OVER (PARTITION BY dept ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t" )
|
|
Assert( "4.8 Partition + Order + Frame", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_WINDOW )
|
|
|
|
/* 4.9 NULLS FIRST in window ORDER BY */
|
|
h := ParseSQL( "SELECT ROW_NUMBER() OVER (ORDER BY name ASC NULLS FIRST) FROM t" )
|
|
Assert( "4.9 Window NULLS FIRST", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_WINDOW )
|
|
|
|
RETURN
|
|
|
|
|
|
/* ---- Section 5: New Statement Types ---- */
|
|
|
|
STATIC PROCEDURE TestNewStatements()
|
|
|
|
LOCAL h
|
|
|
|
/* 5.1 VALUES standalone (SQL:2003) */
|
|
h := ParseSQL( "VALUES (1, 'a'), (2, 'b'), (3, 'c')" )
|
|
Assert( "5.1 VALUES rows", h != NIL .AND. h[ "type" ] == "VALUES" .AND. ;
|
|
Len( h[ "rows" ] ) == 3 )
|
|
|
|
/* 5.2 TABLE shorthand (SQL:2003) */
|
|
h := ParseSQL( "TABLE employees" )
|
|
Assert( "5.2 TABLE shorthand", h != NIL .AND. h[ "type" ] == "TABLE" .AND. ;
|
|
h[ "table" ] == "EMPLOYEES" )
|
|
|
|
/* 5.3 CALL procedure (SQL:2003) */
|
|
h := ParseSQL( "CALL my_proc(1, 'hello')" )
|
|
Assert( "5.3 CALL procedure", h != NIL .AND. h[ "type" ] == "CALL" .AND. ;
|
|
h[ "procedure" ] == "MY_PROC" .AND. Len( h[ "args" ] ) == 2 )
|
|
|
|
/* 5.4 CALL with no args */
|
|
h := ParseSQL( "CALL refresh_cache()" )
|
|
Assert( "5.4 CALL no args", h != NIL .AND. h[ "type" ] == "CALL" .AND. ;
|
|
h[ "procedure" ] == "REFRESH_CACHE" .AND. Len( h[ "args" ] ) == 0 )
|
|
|
|
/* 5.5 MERGE with AND condition (SQL:2008 extended) */
|
|
h := ParseSQL( "MERGE INTO tgt USING src ON tgt.id = src.id " + ;
|
|
"WHEN MATCHED AND src.active = 1 THEN UPDATE SET tgt.name = src.name " + ;
|
|
"WHEN NOT MATCHED AND src.active = 1 THEN INSERT (id, name) VALUES (src.id, src.name)" )
|
|
Assert( "5.5 MERGE AND condition", h != NIL .AND. h[ "type" ] == "MERGE" .AND. ;
|
|
h[ "has_matched" ] .AND. h[ "has_not_matched" ] .AND. ;
|
|
h[ "match_condition" ] != NIL )
|
|
|
|
/* 5.6 MERGE WHEN MATCHED THEN DELETE (SQL:2008) */
|
|
h := ParseSQL( "MERGE INTO tgt USING src ON tgt.id = src.id " + ;
|
|
"WHEN MATCHED AND src.deleted = 1 THEN DELETE" )
|
|
Assert( "5.6 MERGE DELETE", h != NIL .AND. h[ "type" ] == "MERGE" .AND. ;
|
|
h[ "matched_delete" ] )
|
|
|
|
RETURN
|
|
|
|
|
|
/* ---- Section 6: JSON Functions (SQL:2016) ---- */
|
|
|
|
STATIC PROCEDURE TestJsonFunctions()
|
|
|
|
LOCAL h
|
|
|
|
/* 6.1 JSON_VALUE */
|
|
h := ParseSQL( "SELECT JSON_VALUE(data, '$.name') FROM t" )
|
|
Assert( "6.1 JSON_VALUE", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "JSON_VALUE" )
|
|
|
|
/* 6.2 JSON_QUERY */
|
|
h := ParseSQL( "SELECT JSON_QUERY(data, '$.items') FROM t" )
|
|
Assert( "6.2 JSON_QUERY", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "JSON_QUERY" )
|
|
|
|
/* 6.3 JSON_EXISTS */
|
|
h := ParseSQL( "SELECT * FROM t WHERE JSON_EXISTS(data, '$.name')" )
|
|
Assert( "6.3 JSON_EXISTS", h != NIL .AND. h[ "where" ] != NIL .AND. ;
|
|
h[ "where" ][ 1 ] == ND_FN .AND. h[ "where" ][ 2 ] == "JSON_EXISTS" )
|
|
|
|
/* 6.4 JSON_OBJECT */
|
|
h := ParseSQL( "SELECT JSON_OBJECT('name' VALUE name, 'id' VALUE id) FROM t" )
|
|
Assert( "6.4 JSON_OBJECT", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "JSON_OBJECT" )
|
|
|
|
/* 6.5 JSON_ARRAY (handled as normal function) */
|
|
h := ParseSQL( "SELECT JSON_ARRAY(1, 2, 3) FROM t" )
|
|
Assert( "6.5 JSON_ARRAY", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "JSON_ARRAY" )
|
|
|
|
/* 6.6 JSON_ARRAYAGG (handled as normal function) */
|
|
h := ParseSQL( "SELECT JSON_ARRAYAGG(name) FROM t" )
|
|
Assert( "6.6 JSON_ARRAYAGG", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "JSON_ARRAYAGG" )
|
|
|
|
/* 6.7 JSON_OBJECTAGG */
|
|
h := ParseSQL( "SELECT JSON_OBJECTAGG(name VALUE salary) FROM t" )
|
|
Assert( "6.7 JSON_OBJECTAGG", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "JSON_OBJECTAGG" )
|
|
|
|
/* 6.8 JSON_TABLE in expression context */
|
|
h := ParseSQL( "SELECT JSON_TABLE(data, '$.items' COLUMNS (name VARCHAR(50) PATH '$.name')) FROM t" )
|
|
Assert( "6.8 JSON_TABLE", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "JSON_TABLE" )
|
|
|
|
RETURN
|
|
|
|
|
|
/* ---- Section 7: XML Functions (SQL:2003) ---- */
|
|
|
|
STATIC PROCEDURE TestXmlFunctions()
|
|
|
|
LOCAL h
|
|
|
|
/* 7.1 XMLELEMENT */
|
|
h := ParseSQL( "SELECT XMLELEMENT(NAME employee, name) FROM t" )
|
|
Assert( "7.1 XMLELEMENT", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "XMLELEMENT" )
|
|
|
|
/* 7.2 XMLFOREST */
|
|
h := ParseSQL( "SELECT XMLFOREST(name AS empname, salary AS sal) FROM t" )
|
|
Assert( "7.2 XMLFOREST", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "XMLFOREST" )
|
|
|
|
/* 7.3 XMLAGG */
|
|
h := ParseSQL( "SELECT XMLAGG(XMLELEMENT(NAME item, name) ORDER BY name) FROM t" )
|
|
Assert( "7.3 XMLAGG", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "XMLAGG" )
|
|
|
|
RETURN
|
|
|
|
|
|
/* ---- Section 8: SQL:2023 Functions ---- */
|
|
|
|
STATIC PROCEDURE TestSql2023Functions()
|
|
|
|
LOCAL h
|
|
|
|
/* 8.1 ANY_VALUE */
|
|
h := ParseSQL( "SELECT dept, ANY_VALUE(name) FROM t GROUP BY dept" )
|
|
Assert( "8.1 ANY_VALUE", h != NIL .AND. ;
|
|
h[ "columns" ][ 2 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 2 ][ 1 ][ 2 ] == "ANY_VALUE" )
|
|
|
|
/* 8.2 GREATEST */
|
|
h := ParseSQL( "SELECT GREATEST(a, b, c) FROM t" )
|
|
Assert( "8.2 GREATEST", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "GREATEST" )
|
|
|
|
/* 8.3 LEAST */
|
|
h := ParseSQL( "SELECT LEAST(a, b, c) FROM t" )
|
|
Assert( "8.3 LEAST", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "LEAST" )
|
|
|
|
/* 8.4 LPAD */
|
|
h := ParseSQL( "SELECT LPAD(name, 20, ' ') FROM t" )
|
|
Assert( "8.4 LPAD", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "LPAD" )
|
|
|
|
/* 8.5 RPAD */
|
|
h := ParseSQL( "SELECT RPAD(name, 20, '.') FROM t" )
|
|
Assert( "8.5 RPAD", h != NIL .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 1 ][ 1 ][ 2 ] == "RPAD" )
|
|
|
|
/* 8.6 BOOL_AND */
|
|
h := ParseSQL( "SELECT dept, BOOL_AND(active) FROM t GROUP BY dept" )
|
|
Assert( "8.6 BOOL_AND", h != NIL .AND. ;
|
|
h[ "columns" ][ 2 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 2 ][ 1 ][ 2 ] == "BOOL_AND" )
|
|
|
|
/* 8.7 BOOL_OR */
|
|
h := ParseSQL( "SELECT dept, BOOL_OR(active) FROM t GROUP BY dept" )
|
|
Assert( "8.7 BOOL_OR", h != NIL .AND. ;
|
|
h[ "columns" ][ 2 ][ 1 ][ 1 ] == ND_FN .AND. ;
|
|
h[ "columns" ][ 2 ][ 1 ][ 2 ] == "BOOL_OR" )
|
|
|
|
RETURN
|