Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ require (
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.10.1
github.com/stackql/any-sdk v0.5.3-alpha10
github.com/stackql/any-sdk v0.5.3-alpha11
github.com/stackql/go-suffix-map v0.0.1-alpha01
github.com/stackql/psql-wire v0.1.2-beta01
github.com/stackql/stackql-parser v0.0.16-alpha02
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -467,8 +467,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
github.com/stackql/any-sdk v0.5.3-alpha10 h1:FlumRS0hICuUqztUsvRCdHljSLA+aTqrU0Y4Z7DF8WQ=
github.com/stackql/any-sdk v0.5.3-alpha10/go.mod h1:yGAr3eEtY6UpFMq8fF5BTvZhdqYGpyS90LP6KY5AKZs=
github.com/stackql/any-sdk v0.5.3-alpha11 h1:peFWQndLyduUOEUXTTnrVWcdiw7wT4D+IulTG8MCL4A=
github.com/stackql/any-sdk v0.5.3-alpha11/go.mod h1:yGAr3eEtY6UpFMq8fF5BTvZhdqYGpyS90LP6KY5AKZs=
github.com/stackql/go-suffix-map v0.0.1-alpha01 h1:TDUDS8bySu41Oo9p0eniUeCm43mnRM6zFEd6j6VUaz8=
github.com/stackql/go-suffix-map v0.0.1-alpha01/go.mod h1:QAi+SKukOyf4dBtWy8UMy+hsXXV+yyEE4vmBkji2V7g=
github.com/stackql/psql-wire v0.1.2-beta01 h1:+0xeLLam3nbI1iv84pWJYGYw5VO5S/+eICRbdu6+dj8=
Expand Down
20 changes: 20 additions & 0 deletions internal/stackql/primitivegenerator/statement_analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,26 @@ func (pb *standardPrimitiveGenerator) expandTable(
uid := fmt.Sprintf("%s.%s", tbl.GetUniqueID(), colName)
pb.PrimitiveComposer.SetSymbol(uid, colEntry) //nolint:errcheck // TODO: review
}
// Register the display column names alongside the raw property keys.
// Under config.snake_case_aliases the tabulation's display names are the
// snake aliases (any-sdk owns that translation), so WHERE / projection
// symbols resolve for either form; without aliasing this re-registers
// the same names and is a no-op.
if tabulation := responseSchema.Tabulate(false, ""); tabulation != nil {
for _, col := range tabulation.GetColumns() {
colSchema := col.GetSchema()
if colSchema == nil {
continue
}
colEntry := symtab.NewSymTabEntry(
pb.PrimitiveComposer.GetDRMConfig().GetRelationalType(colSchema.GetType()),
colSchema,
"",
)
uid := fmt.Sprintf("%s.%s", tbl.GetUniqueID(), col.GetName())
pb.PrimitiveComposer.SetSymbol(uid, colEntry) //nolint:errcheck // duplicate registration is benign
}
}
return nil
}

Expand Down
3 changes: 2 additions & 1 deletion test/python/stackql_test_tooling/StackQLInterfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .psycopg_client import PsycoPGClient
from .psycopg2_client import PsycoPG2Client
from .sqlalchemy_client import SQLAlchemyClient
from .sqlite_exe_locate import resolve_sqlite_exe

SQL_BACKEND_CANONICAL_SQLITE_EMBEDDED :str = 'sqlite_embedded'
SQL_BACKEND_POSTGRES_TCP :str = 'postgres_tcp'
Expand All @@ -25,7 +26,7 @@
_TEST_APP_CACHE_ROOT = os.path.join("test", ".stackql")

PSQL_EXE :str = os.environ.get('PSQL_EXE', 'psql')
SQLITE_EXE :str = os.environ.get('SQLITE_EXE', 'sqlite3')
SQLITE_EXE :str = resolve_sqlite_exe(os.environ.get('SQLITE_EXE', 'sqlite3'))


@library(scope='SUITE', version='0.1.0', doc_format='reST')
Expand Down
54 changes: 54 additions & 0 deletions test/python/stackql_test_tooling/flask/native_test/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ def casing_echo_post():
}
)

@app.get("/casing/echo_strict")
def casing_echo_strict_get():
# Same echo semantics as /casing/echo; the spec-side difference is that
# VpcId is a REQUIRED wire parameter, so reaching this endpoint at all
# proves the router satisfied the requirement (via the snake alias when
# the SQL used vpc_id).
return jsonify(
{
"VpcId": request.args.get("VpcId"),
"SubnetId": request.args.get("SubnetId"),
"echoed_query": request.query_string.decode("utf-8"),
}
)

# ---- OData push-down target --------------------------------------------

@app.get("/odata/people")
Expand Down Expand Up @@ -127,6 +141,46 @@ def xml_ec2_volumes():
)
return Response(body, mimetype="text/xml")

@app.get("/xml/ec2/volumes_alias")
def xml_ec2_volumes_alias():
# Wire elements use EC2 locationName casing (volumeId, attachmentSet);
# the spec's schema keys are the botocore member names (VolumeId,
# Attachments) with xml: name overrides - the AWS provider shape. The
# nested <attachmentSet> content exercises JSON stringification of a
# complex value under a string-typed column.
body = (
"<DescribeVolumesResponse>"
"<requestId>req-ec2-alias-1</requestId>"
"<volumeSet>"
"<item><volumeId>vol-a1</volumeId><size>8</size><state>available</state>"
"<attachmentSet><item><instanceId>i-1</instanceId>"
"<device>/dev/sda1</device></item></attachmentSet></item>"
"<item><volumeId>vol-a2</volumeId><size>16</size><state>in-use</state>"
"<attachmentSet/></item>"
"</volumeSet>"
"</DescribeVolumesResponse>"
)
return Response(body, mimetype="text/xml")

@app.get("/xml/ec2/vpc")
def xml_ec2_vpc():
# CreateVpc-style singleton: the row lives under a named wrapper member
# one level below the response root (walker singleton-unwrap regime).
body = (
"<CreateVpcResponse>"
"<requestId>req-ec2-vpc-1</requestId>"
"<vpc><vpcId>vpc-fixture-1</vpcId>"
"<cidrBlock>10.99.0.0/16</cidrBlock><state>pending</state></vpc>"
"</CreateVpcResponse>"
)
return Response(body, mimetype="text/xml")

@app.get("/xml/ec2/volumes_empty_body")
def xml_ec2_volumes_empty_body():
# S3 CreateBucket-style: 200 with an empty body. The walker must yield
# zero rows rather than an mxj EOF error.
return Response("", mimetype="text/xml")

@app.get("/xml/query/stacks")
def xml_query_stacks():
# `id`/`region` are single-word (value projects); `stackName` is multi-word
Expand Down
40 changes: 40 additions & 0 deletions test/python/stackql_test_tooling/sqlite_exe_locate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Defensive resolution of the sqlite3 CLI executable for export tests.

The Windows CI runner installs sqlite with `choco install --force sqlite` and
points SQLITE_EXE at the historical layout
(C:\\ProgramData\\chocolatey\\lib\\SQLite\\tools\\sqlite3.exe). Newer versions
of the chocolatey package nest the binary one level deeper (a
sqlite-tools-win-* folder), which broke the hardcoded path with
FileNotFoundError. Resolve in order:

1. The configured value, when it is an existing file or a bare command name
resolvable on PATH.
2. `sqlite3` on PATH.
3. A recursive search of the chocolatey SQLite lib tree (newest hit wins).

Falls back to the configured value so a genuine miss still surfaces the
original, informative error at call time.
"""

import glob
import os
import shutil

_CHOCO_SQLITE_TOOLS_GLOB = r"C:\ProgramData\chocolatey\lib\SQLite\tools\**\sqlite3.exe"


def resolve_sqlite_exe(configured: str) -> str:
if not configured:
configured = "sqlite3"
if os.path.isfile(configured):
return configured
if os.path.sep not in configured and shutil.which(configured):
return configured
path_hit = shutil.which("sqlite3")
if path_hit:
return path_hit
if os.name == "nt":
hits = sorted(glob.glob(_CHOCO_SQLITE_TOOLS_GLOB, recursive=True))
if hits:
return hits[-1]
return configured
3 changes: 2 additions & 1 deletion test/python/stackql_test_tooling/stackql_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
## cannot use relative imports due to robot usage pattern
from typed_python_responses import SELECT_AWS_CLOUD_CONTROL_EVENTS_MINIMAL_EXPECTED
from registry_cfg import RegistryCfg
from sqlite_exe_locate import resolve_sqlite_exe

_exe_name = 'stackql'
_mcp_client_exe_name = 'stackql_mcp_client'
Expand Down Expand Up @@ -597,7 +598,7 @@ def get_object_count_dict(count :int) -> dict:
PG_SRV_PORT_DOCKER_UNENCRYPTED = 5577

PSQL_EXE :str = os.environ.get('PSQL_EXE', 'psql')
SQLITE_EXE :str = os.environ.get('SQLITE_EXE', 'sqlite3')
SQLITE_EXE :str = resolve_sqlite_exe(os.environ.get('SQLITE_EXE', 'sqlite3'))

PSQL_CLIENT_HOST :str = "127.0.0.1"

Expand Down
62 changes: 62 additions & 0 deletions test/registry/src/stackql_native_test/v0.1.0/services/casing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,48 @@ components:
- $ref: '#/components/x-stackQL-resources/echo/methods/insert'
delete: []
update: []
echo_strict:
id: stackql_native_test.casing.echo_strict
name: echo_strict
title: Echo with a REQUIRED PascalCase wire parameter (routing reverse-casing)
methods:
select:
operation:
$ref: '#/paths/~1casing~1echo_strict/get'
request:
nativeCasing: pascal
response:
mediaType: application/json
openAPIDocKey: '200'
objectKey: $
sqlVerbs:
select:
- $ref: '#/components/x-stackQL-resources/echo_strict/methods/select'
insert: []
delete: []
update: []
echo_post:
id: stackql_native_test.casing.echo_post
name: echo_post
title: POST echo exercising the request.base fallback body (aws-json '{}' pattern)
methods:
select:
operation:
$ref: '#/paths/~1casing~1echo/post'
request:
mediaType: application/json
base: '{}'
nativeCasing: pascal
response:
mediaType: application/json
openAPIDocKey: '200'
objectKey: $
sqlVerbs:
select:
- $ref: '#/components/x-stackQL-resources/echo_post/methods/select'
insert: []
delete: []
update: []
paths:
/casing/echo:
get:
Expand Down Expand Up @@ -113,3 +155,23 @@ paths:
servers:
- url: http://localhost:1070
summary: Echo received request body
/casing/echo_strict:
get:
operationId: casing/echo_strict/get
parameters:
- in: query
name: VpcId
required: true
schema:
type: string
- $ref: '#/components/parameters/SubnetId'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/echo_response'
servers:
- url: http://localhost:1070
summary: Echo received query parameters, VpcId required
Loading
Loading