Files
five/_FiveSql2/test/test_sql_standards.prg
Charles KWON OhJun 486e466592 feat: FiveSql2 43/43, @byref, mutable closure, RTL 479, DateTime fix
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>
2026-04-11 11:35:37 +09:00

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