From f7286436ca31ae0e6369276573aaf6c46708fd0c Mon Sep 17 00:00:00 2001 From: General Kroll Date: Tue, 24 Feb 2026 09:34:09 +1100 Subject: [PATCH 1/2] left-outer-join-lhs-inline Summary: - Support for `left outer join` with inline lhs. - Added robot test `Left Outer Join Negative LHS Inline`. - Added robot test `Left Outer Join Positive LHS Inline`. --- .vscode/launch.json | 3 +- .../astanalysis/earlyanalysis/ast_expand.go | 4 -- internal/stackql/dbmsinternal/dbmsinternal.go | 4 +- internal/stackql/sql_system/postgres.go | 3 + internal/stackql/sql_system/sqlite.go | 3 + .../stackql_mocked_from_cmd_line.robot | 58 +++++++++++++++++++ 6 files changed, 68 insertions(+), 7 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index b2c16ecb..90928539 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -192,6 +192,7 @@ "replace /*+ AWAIT */ google.compute.firewalls set data__disabled = 'true' where project = 'mutable-project' and firewall = 'replacable-firewall' returning *;", "select split_part(rings.name, '/', -1) from google.cloudkms.key_rings rings inner join google.cloudkms.crypto_keys keys on keys.keyRingsId = split_part(rings.name, '/', -1) and keys.projectsId = 'testing-project' where rings.projectsId = 'testing-project' and rings.locationsId = 'global' and keys.locationsId = 'global';", "select * from local_openssl.keys.x509 where cert_file = '${workspaceFolder}/test/assets/input/manual_cert.pem';", + "select t1.id as t1_id, t2.id as t2_id from (select 'my-id' as id) t1 left outer join (select a.vpcId id from aws.ec2_native.vpcs a where region = 'ap-southeast-2') t2 on t1.id = t2.id;", ], "default": "show providers;" }, @@ -214,7 +215,7 @@ "{ \"url\": \"file://${workspaceFolder}/test/registry-mocked\", \"localDocRoot\": \"${workspaceFolder}/test/registry-mocked\", \"verifyConfig\": { \"nopVerify\": true } }", "{ \"url\": \"file://${workspaceFolder}/test/registry-mocked-native\", \"localDocRoot\": \"${workspaceFolder}/test/registry-mocked-native\", \"verifyConfig\": { \"nopVerify\": true } }", "{ \"url\": \"file://${workspaceFolder}/test/registry-advanced\", \"localDocRoot\": \"${workspaceFolder}/test/registry-advanced\", \"verifyConfig\": { \"nopVerify\": true } }", - "{ \"url\": \"file://${workspaceFolder}/build/.stackql\", \"localDocRoot\": \"${workspaceFolder}/build/.stackql\", \"verifyConfig\": { \"nopVerify\": true } }", + "{ \"url\": \"file://${workspaceFolder}/.stackql\", \"localDocRoot\": \"${workspaceFolder}/.stackql\", \"verifyConfig\": { \"nopVerify\": true } }", "{ \"url\": \"file://${workspaceFolder}/docs/examples/empty-registry\", \"localDocRoot\": \"${workspaceFolder}/docs/examples/empty-registry\" }", "{ \"url\": \"https://cdn.statically.io/gh/stackql/stackql-provider-registry/main/providers\", \"localDocRoot\": \"${workspaceFolder}/test/registry\" }", "{ \"url\": \"https://cdn.statically.io/gh/stackql/stackql-provider-registry/dev/providers\" }", diff --git a/internal/stackql/astanalysis/earlyanalysis/ast_expand.go b/internal/stackql/astanalysis/earlyanalysis/ast_expand.go index b38180c6..c0db2ae1 100644 --- a/internal/stackql/astanalysis/earlyanalysis/ast_expand.go +++ b/internal/stackql/astanalysis/earlyanalysis/ast_expand.go @@ -204,10 +204,6 @@ func (v *indirectExpandAstVisitor) processIndirect(node sqlparser.SQLNode, indir if createBuilderExists { v.createBuilder = append(v.createBuilder, createBuilder...) } - // createBuilder, createBuilderExists := childAnalyzer.GetIndirectCreateTail() - // if createBuilderExists { - // v.createBuilder = createBuilder - // } return nil } diff --git a/internal/stackql/dbmsinternal/dbmsinternal.go b/internal/stackql/dbmsinternal/dbmsinternal.go index 3c27dfe9..0b455d4f 100644 --- a/internal/stackql/dbmsinternal/dbmsinternal.go +++ b/internal/stackql/dbmsinternal/dbmsinternal.go @@ -213,8 +213,8 @@ func (pgr *standardDBMSInternalRouter) analyzeTableExprAllRDBMS(node sqlparser.T } case *sqlparser.JoinTableExpr: lhs := pgr.analyzeTableExprAllRDBMS(node.LeftExpr) - if lhs { - return true + if !lhs { + return false } rhs := pgr.analyzeTableExprAllRDBMS(node.RightExpr) if rhs { diff --git a/internal/stackql/sql_system/postgres.go b/internal/stackql/sql_system/postgres.go index 19cec2e4..a2fd4ab0 100644 --- a/internal/stackql/sql_system/postgres.go +++ b/internal/stackql/sql_system/postgres.go @@ -1052,6 +1052,9 @@ func (eng *postgresSystem) render(alias string, controls = append(controls, fmt.Sprintf(`%s = $%d AND %s = $%d AND %s = $%d AND %s = $%d`, gIDcn, j+1, sIDcn, j+2, tIDcn, j+3, iIDcn, j+4)) //nolint:mnd // the magic numbers are offsets j += constants.ControlColumnCount } + if len(controls) == 0 { + return "1 = 1" + } return fmt.Sprintf(`( %s )`, strings.Join(controls, " OR ")) } diff --git a/internal/stackql/sql_system/sqlite.go b/internal/stackql/sql_system/sqlite.go index 28e517d1..cda9c612 100644 --- a/internal/stackql/sql_system/sqlite.go +++ b/internal/stackql/sql_system/sqlite.go @@ -1178,6 +1178,9 @@ func (eng *sqLiteSystem) render(alias string, aliasToCountersMap map[string][]in iIDcn := fmt.Sprintf(`"%s"`, insIDColName) controls = append(controls, fmt.Sprintf(`( %s = ? AND %s = ? AND %s = ? AND %s = ? )`, gIDcn, sIDcn, tIDcn, iIDcn)) } + if len(controls) == 0 { + return "1 = 1" + } return fmt.Sprintf(`( %s )`, strings.Join(controls, " OR ")) } diff --git a/test/robot/functional/stackql_mocked_from_cmd_line.robot b/test/robot/functional/stackql_mocked_from_cmd_line.robot index 53f76c24..71c23662 100644 --- a/test/robot/functional/stackql_mocked_from_cmd_line.robot +++ b/test/robot/functional/stackql_mocked_from_cmd_line.robot @@ -9278,3 +9278,61 @@ Materialized View of Filtered Multi Level Table Valued Function In Subquery Retu ... ${outputStr} ... stdout=${CURDIR}/tmp/Materialized-View-of-Filtered-Multi-Level-Table-Valued-Function-In-Subquery-Returns-Expected-Results.tmp ... stderr=${CURDIR}/tmp/Materialized-View-of-Filtered-Multi-Level-Table-Valued-Function-In-Subquery-Returns-Expected-Results-stderr.tmp + +Left Outer Join Negative LHS Inline + ${inputStr} = Catenate + ... select lhs.id, lhs.secondary_field, rhs.volume_id + ... from + ... (select 'my-id' as id, 'some other field' as secondary_field) lhs + ... left outer join + ... (select volume_id from aws.ec2.volumes_presented where region = 'ap-southeast-2') rhs + ... on lhs.id = rhs.volume_id + ... where volume_id is null + ... ; + ${outputStr} = Catenate SEPARATOR=\n + ... |-------|------------------|-----------| + ... |${SPACE}${SPACE}id${SPACE}${SPACE}${SPACE}|${SPACE}secondary_field${SPACE}${SPACE}|${SPACE}volume_id${SPACE}| + ... |-------|------------------|-----------| + ... |${SPACE}my-id${SPACE}|${SPACE}some${SPACE}other${SPACE}field${SPACE}|${SPACE}null${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}| + ... |-------|------------------|-----------| + Should Stackql Exec Inline Equal + ... ${STACKQL_EXE} + ... ${OKTA_SECRET_STR} + ... ${GITHUB_SECRET_STR} + ... ${K8S_SECRET_STR} + ... ${REGISTRY_NO_VERIFY_CFG_STR} + ... ${AUTH_CFG_STR} + ... ${SQL_BACKEND_CFG_STR_CANONICAL} + ... ${inputStr} + ... ${outputStr} + ... stdout=${CURDIR}/tmp/Left-Outer-Join-Negative-LHS-Inline.tmp + ... stderr=${CURDIR}/tmp/Left-Outer-Join-Negative-LHS-Inline-stderr.tmp + +Left Outer Join Positive LHS Inline + ${inputStr} = Catenate + ... select lhs.id, lhs.secondary_field, rhs.volume_id + ... from + ... (select 'vol-00200000000000000' as id, 'some other field' as secondary_field) lhs + ... left outer join + ... (select volume_id from aws.ec2.volumes_presented where region = 'ap-southeast-2') rhs + ... on lhs.id = rhs.volume_id + ... where volume_id is not null + ... ; + ${outputStr} = Catenate SEPARATOR=\n + ... |-----------------------|------------------|-----------------------| + ... |${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}id${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}|${SPACE}secondary_field${SPACE}${SPACE}|${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}volume_id${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}| + ... |-----------------------|------------------|-----------------------| + ... |${SPACE}vol-00200000000000000${SPACE}|${SPACE}some${SPACE}other${SPACE}field${SPACE}|${SPACE}vol-00200000000000000${SPACE}| + ... |-----------------------|------------------|-----------------------| + Should Stackql Exec Inline Equal + ... ${STACKQL_EXE} + ... ${OKTA_SECRET_STR} + ... ${GITHUB_SECRET_STR} + ... ${K8S_SECRET_STR} + ... ${REGISTRY_NO_VERIFY_CFG_STR} + ... ${AUTH_CFG_STR} + ... ${SQL_BACKEND_CFG_STR_CANONICAL} + ... ${inputStr} + ... ${outputStr} + ... stdout=${CURDIR}/tmp/Left-Outer-Join-Positive-LHS-Inline.tmp + ... stderr=${CURDIR}/tmp/Left-Outer-Join-Positive-LHS-Inline-stderr.tmp From e0e40f222ec9788093c3564e8d881c822cddf9e4 Mon Sep 17 00:00:00 2001 From: General Kroll Date: Tue, 24 Feb 2026 11:01:56 +1100 Subject: [PATCH 2/2] - Postgres fix. --- internal/stackql/dbmsinternal/dbmsinternal.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/stackql/dbmsinternal/dbmsinternal.go b/internal/stackql/dbmsinternal/dbmsinternal.go index 0b455d4f..242f8b74 100644 --- a/internal/stackql/dbmsinternal/dbmsinternal.go +++ b/internal/stackql/dbmsinternal/dbmsinternal.go @@ -190,8 +190,8 @@ func (pgr *standardDBMSInternalRouter) analyzeTableExpr(node sqlparser.TableExpr } case *sqlparser.JoinTableExpr: lhs := pgr.analyzeTableExpr(node.LeftExpr) - if lhs { - return true + if !lhs { + return false } rhs := pgr.analyzeTableExpr(node.RightExpr) if rhs {