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 @@ -21,7 +21,7 @@ require (
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.10.1
github.com/stackql-labs/omnisdk v0.1.4-alpha05
github.com/stackql/any-sdk v0.6.0-alpha01
github.com/stackql/any-sdk v0.6.1-alpha02
github.com/stackql/go-suffix-map v0.0.1-alpha01
github.com/stackql/psql-wire v0.1.3-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 @@ -370,8 +370,8 @@ github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcG
github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
github.com/stackql-labs/omnisdk v0.1.4-alpha05 h1:Z22XGi99mjKPJEAAYv3qkW1J0iqqFRd9OANK+9cw4Xs=
github.com/stackql-labs/omnisdk v0.1.4-alpha05/go.mod h1:2P2vFi+c/wEeQ3kFgIt0QuIlefpuzIoTm6bPJAiLAvQ=
github.com/stackql/any-sdk v0.6.0-alpha01 h1:mqy0bmZ1wghr7mUgLyaexoRU6rsUZCWXU9Mf60fC8dg=
github.com/stackql/any-sdk v0.6.0-alpha01/go.mod h1:ahkRgwHHEn7RTmSfKrshPVk6i4X1BdHknSeBflSyRac=
github.com/stackql/any-sdk v0.6.1-alpha02 h1:kXnL1SOORkwh47foKVX4GGDmD0gdOx2K4JGgvz+sxcw=
github.com/stackql/any-sdk v0.6.1-alpha02/go.mod h1:ahkRgwHHEn7RTmSfKrshPVk6i4X1BdHknSeBflSyRac=
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.3-beta01 h1:xDpR3e09xt74GBSkxYKIhfTMPnKY/Utgu2rl1ctEuF8=
Expand Down
41 changes: 41 additions & 0 deletions internal/stackql/dependencyplanner/dependencyplanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -857,10 +857,22 @@ func (dp *standardDependencyPlanner) getStreamFromEdge(
if err != nil {
return nil, err
}
projection = sourceResponseProjection(projection, e.GetSource().GetAnnotation().GetSchema().Tabulate(false, ""))
method, err := toAc.GetTableMeta().GetMethod()
if err != nil {
return nil, err
}
requestParameters := method.GetParametersIncludingNativeCasing()
projection = destinationRequestProjection(projection, requestParameters)
incomingCols := make(map[string]struct{})
for _, v := range projection {
incomingCols[v] = struct{}{}
}
for alias, parameter := range requestParameters {
if _, dynamic := incomingCols[parameter.GetName()]; dynamic {
incomingCols[alias] = struct{}{}
}
}
params := toAc.GetParameters()
staticParams := make(map[string]interface{})
for k, v := range params {
Expand All @@ -879,6 +891,35 @@ func (dp *standardDependencyPlanner) getStreamFromEdge(
return streaming.NewSimpleProjectionMapStream(projection, staticParams), nil
}

func sourceResponseProjection(projection map[string]string, tab formulation.Tabulation) map[string]string {
wireNames := make(map[string]string)
for _, column := range tab.GetColumns() {
wireNames[column.GetName()] = column.GetWireName()
}
translated := make(map[string]string, len(projection))
for source, destination := range projection {
if wireName := wireNames[source]; wireName != "" {
source = wireName
}
translated[source] = destination
}
return translated
}

func destinationRequestProjection(
projection map[string]string,
parameters map[string]formulation.Addressable,
) map[string]string {
translated := make(map[string]string, len(projection))
for source, destination := range projection {
if parameter, ok := parameters[destination]; ok {
destination = parameter.GetName()
}
translated[source] = destination
}
return translated
}

// This is very naive but safe because there is only ever one source table.
func (dp *standardDependencyPlanner) harvestFilter(sourceAnnotation taxonomy.AnnotationCtx) (string, bool) {
var valz []string
Expand Down
86 changes: 86 additions & 0 deletions internal/stackql/dependencyplanner/dependencyplanner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package dependencyplanner //nolint:testpackage // tests internal projection mapping

import (
"io"
"reflect"
"testing"

"github.com/stackql/any-sdk/pkg/streaming"
"github.com/stackql/any-sdk/public/formulation"
)

type projectionColumn struct {
formulation.ColumnDescriptor
name string
wireName string
}

func (column projectionColumn) GetName() string { return column.name }
func (column projectionColumn) GetWireName() string { return column.wireName }

type projectionTabulation struct {
formulation.Tabulation
columns []formulation.ColumnDescriptor
}

func (tab projectionTabulation) GetColumns() []formulation.ColumnDescriptor { return tab.columns }

type projectionParameter struct {
formulation.Addressable
name string
}

func (parameter projectionParameter) GetName() string { return parameter.name }

func TestDestinationRequestProjection(t *testing.T) {
projection := map[string]string{"AccessKeyId": "access_key_id", "other": "unchanged"}
parameters := map[string]formulation.Addressable{
"access_key_id": projectionParameter{name: "AccessKeyId"},
}
translated := destinationRequestProjection(projection, parameters)
want := map[string]string{"AccessKeyId": "AccessKeyId", "other": "unchanged"}
if !reflect.DeepEqual(translated, want) {
t.Fatalf("projection = %v, want %v", translated, want)
}
if projection["AccessKeyId"] != "access_key_id" {
t.Fatal("source projection was mutated")
}
}

func TestSourceResponseProjection(t *testing.T) {
projection := map[string]string{
"access_key_id": "access_key_id",
"unchanged": "destination",
"unknown": "unknown_destination",
"empty_wire": "empty_destination",
}
tab := projectionTabulation{columns: []formulation.ColumnDescriptor{
projectionColumn{name: "access_key_id", wireName: "AccessKeyId"},
projectionColumn{name: "unchanged", wireName: "unchanged"},
projectionColumn{name: "empty_wire"},
}}
translated := sourceResponseProjection(projection, tab)
want := map[string]string{
"AccessKeyId": "access_key_id", "unchanged": "destination",
"unknown": "unknown_destination", "empty_wire": "empty_destination",
}
if !reflect.DeepEqual(translated, want) {
t.Fatalf("projection = %v, want %v", translated, want)
}
if projection["access_key_id"] != "access_key_id" {
t.Fatal("source projection was mutated")
}
stream := streaming.NewSimpleProjectionMapStream(translated, nil)
if err := stream.Write([]map[string]interface{}{{
"AccessKeyId": "test-key", "unchanged": "value", "unknown": "other", "empty_wire": "empty",
}}); err != nil {
t.Fatal(err)
}
rows, err := stream.Read()
if err != io.EOF {
t.Fatalf("stream read: %v", err)
}
if len(rows) != 1 || rows[0]["access_key_id"] != "test-key" {
t.Fatalf("projected rows = %v", rows)
}
}
4 changes: 4 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 @@ -132,6 +132,10 @@ def casing_echo_strict_get():
}
)

@app.get("/casing/echo_filter")
def casing_echo_filter_get():
return jsonify({"echoed_query": request.query_string.decode("utf-8")})

# ---- OCI request signing (oci_signing_v1) target -----------------------

@app.get("/oci/buckets")
Expand Down
72 changes: 72 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 @@ -62,6 +62,11 @@ components:
type: string
echoed_query:
type: string
echo_query_only_response:
type: object
properties:
echoed_query:
type: string
bodyless_echo_response:
type: object
properties:
Expand Down Expand Up @@ -153,6 +158,52 @@ components:
insert: []
delete: []
update: []
echo_request_only:
id: stackql_native_test.casing.echo_request_only
name: echo_request_only
title: Echo with a required parameter absent from the response schema
methods:
select:
operation:
$ref: '#/paths/~1casing~1echo_strict/get'
request:
nativeCasing: pascal
response:
mediaType: application/json
openAPIDocKey: '200'
overrideMediaType: application/json
objectKey: $
schema_override:
$ref: '#/components/schemas/echo_query_only_response'
sqlVerbs:
select:
- $ref: '#/components/x-stackQL-resources/echo_request_only/methods/select'
insert: []
delete: []
update: []
echo_filter:
id: stackql_native_test.casing.echo_filter
name: echo_filter
title: Echo a single-word native-cased required parameter
methods:
select:
operation:
$ref: '#/paths/~1casing~1echo_filter/get'
request:
nativeCasing: pascal
response:
mediaType: application/json
openAPIDocKey: '200'
overrideMediaType: application/json
objectKey: $
schema_override:
$ref: '#/components/schemas/echo_query_only_response'
sqlVerbs:
select:
- $ref: '#/components/x-stackQL-resources/echo_filter/methods/select'
insert: []
delete: []
update: []
echo_header:
id: stackql_native_test.casing.echo_header
name: echo_header
Expand Down Expand Up @@ -286,6 +337,27 @@ paths:
servers:
- url: http://localhost:1070
summary: Echo received query parameters, VpcId required
/casing/echo_filter:
get:
operationId: casing/echo_filter/get
parameters:
- in: query
name: Filter
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
echoed_query:
type: string
servers:
- url: http://localhost:1070
/casing/echo_header:
get:
operationId: casing/echo_header/get
Expand Down
36 changes: 36 additions & 0 deletions test/robot/functional/stackql_casing.robot
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,42 @@ Documentation Functional coverage for the any-sdk casing engine, exercised t
... backward-compatibility block.

*** Test Cases ***
Snake Case Join Source Projects Native Response Field
Should StackQL Exec Inline Contain
... ${STACKQL_EXE}
... ${OKTA_SECRET_STR}
... ${GITHUB_SECRET_STR}
... ${K8S_SECRET_STR}
... ${REGISTRY_NO_VERIFY_CFG_STR}
... ${AUTH_CFG_STR}
... ${SQL_BACKEND_CFG_STR_CANONICAL}
... select target.echoed_query from stackql_native_test.casing.echo source inner join stackql_native_test.casing.echo_strict target on target.vpc_id \= source.vpc_id where source.vpc_id \= 'vpc-join';
... VpcId\=vpc-join

Snake Case Join Retains Request Only Destination Column
Should StackQL Exec Inline Contain
... ${STACKQL_EXE}
... ${OKTA_SECRET_STR}
... ${GITHUB_SECRET_STR}
... ${K8S_SECRET_STR}
... ${REGISTRY_NO_VERIFY_CFG_STR}
... ${AUTH_CFG_STR}
... ${SQL_BACKEND_CFG_STR_CANONICAL}
... select target.vpc_id, target.echoed_query from stackql_native_test.casing.echo source inner join stackql_native_test.casing.echo_request_only target on target.vpc_id \= source.vpc_id where source.vpc_id \= 'vpc-request-only';
... VpcId\=vpc-request-only

Single Word Native Parameter Has One SQL Column
Should StackQL Exec Inline Contain
... ${STACKQL_EXE}
... ${OKTA_SECRET_STR}
... ${GITHUB_SECRET_STR}
... ${K8S_SECRET_STR}
... ${REGISTRY_NO_VERIFY_CFG_STR}
... ${AUTH_CFG_STR}
... ${SQL_BACKEND_CFG_STR_CANONICAL}
... select echoed_query from stackql_native_test.casing.echo_filter where filter \= 'SERVICE_CONTROL_POLICY';
... Filter\=SERVICE_CONTROL_POLICY

Snake Case Aliases Multi Word Response Column Projects Value
[Documentation] Wire field VpcId is exposed as snake_case column vpc_id and its VALUE
... (echoed back by the mock) now projects via GetWireName (issue 108) instead of NULL.
Expand Down
Loading