Summary
The request-side casing mechanism (request.nativeCasing) is backward compatible: wire-cased keys always match first and snake_case is an additional alias. The response-side mechanism (config.snake_case_aliases) is not: it renames the column display/DDL surface to snake_case outright, so any existing query that projects, filters, orders by or references a wire-cased (camelCase / PascalCase) column name breaks the moment a provider ships with the flag enabled. To make snake-on-the-surface / native-on-the-wire adoptable for published providers with an existing user base (google, aws), the column surface needs an aliasing mode to match the parameter surface.
Four work items are tracked here. They were found while adopting nativeCasing on the google provider family and share one root concern: which spellings the engine accepts, and which spellings it presents, must agree - and accepting a new spelling must never remove an old one.
Target end state
| surface |
presented as |
accepted as |
tracked in |
response columns (DESCRIBE, SELECT *) |
snake |
snake and wire |
item 1 |
column refs in projection / ORDER BY / local WHERE |
- |
snake and wire |
item 1 |
parameters (SHOW METHODS, docs) |
snake |
snake and wire |
items 2, 4 |
top-level data__ body keys (SHOW INSERT, docs) |
snake |
snake and wire |
items 3, 4 |
| nested body/JSON blob contents |
wire |
wire |
unchanged |
| server variables |
wire |
wire |
unchanged |
Nothing in this issue removes a wire spelling from the accepted set, on any surface.
Current behaviour (verified against main)
Request side - dual-cased, wire-first, non-breaking:
GetParameter, parameterMatch and operation-param lookup (internal/anysdk/operation_store.go) all try the key as given first and only on a miss apply the snake -> wire retry, gated on request.nativeCasing being declared.
GetParametersIncludingNativeCasing augments the wire-keyed set with snake aliases and never clobbers a real wire parameter of the same name.
getschemaAttributeMatcher accepts both the wire property name and its ToSnake form and maps back to the wire key - but see item 3: it is only reachable from the naive* translator path, not the default data__ path.
Response side - renamed, wire names removed from the surface:
- With
StackQLConfig.SnakeCaseAliases enabled (internal/anysdk/config.go), getPropertiesColumns, ToDescriptionMap and GetAllColumns (internal/anysdk/schema.go) set the column display/DDL name to casing.ToSnake(wireKey). The wire name is retained only internally (GetWireName) for response payload extraction.
- The regression test contract is explicit: wire-cased keys must NOT appear in DESCRIBE output (
TestToDescriptionMapSnakeAliases).
- The RDBMS insertion table is therefore created with snake DDL columns only. There is no camel/Pascal fallback in column resolution, so a query like the one below either errors on the backend or degrades to projecting the identifier as a string literal (the failure mode noted in the
TestGetAllColumnsSnakeAliases comment):
-- works today against a provider without the flag; breaks when the flag ships
SELECT machineType, creationTimestamp
FROM google.compute.instances
WHERE project = 'p' AND zone = 'z'
ORDER BY creationTimestamp DESC;
Why this blocks adoption
For a provider with no installed base the rename is fine. For google/aws it means a hard flag day: every existing script, dashboard and stackql-deploy template that touches a response field by wire name breaks in a single provider release, while the parameter surface (correctly) keeps accepting both spellings. The asymmetry is the problem - inputs are dual-cased, outputs are single-cased.
Scale on google alone: 19,756 distinct camelCase schema property names across 70,274 properties, published as a single rolling v00.00.00000 with no earlier version for users to pin.
Item 1: response-column surface is a rename, not an alias
Make the response-column surface dual-cased under the flag, mirroring the parameter treatment:
- Column resolution fallback (preferred): keep snake as the display/DDL name, but when a projected/referenced identifier misses the column set and the provider has
snake_case_aliases enabled, retry with casing.ToSnake(identifier) before failing. This is the exact mirror of the GetParameter reverse-casing retry, applied at the column descriptor / select-items resolution layer, and requires no DDL change.
- Alternative: emit both names into the tabulation (wire name as a true alias column sharing the extraction key), with the snake name canonical in DESCRIBE/star expansion. Heavier: doubles the visible column count or needs display filtering.
- Alternative: make the rename opt-in per session/user rather than per provider doc. Punts the migration problem to every user individually; least preferred.
Option 1 acceptance criteria:
- With the flag enabled,
SELECT machine_type ... and SELECT machineType ... both resolve to the same column and value.
ORDER BY and locally-evaluated WHERE predicates accept both spellings.
- DESCRIBE /
SELECT * output remains snake-only (unchanged from current flag behaviour).
- With the flag absent, behaviour is byte-identical to today (existing
TestToDescriptionMapDisabledIsWireKeyed / TestGetAllColumnsDisabledIsWireKeyed still pass).
- A regression test asserting the camel -> snake column fallback, alongside the existing surface-parity tests in
casing_surface_regression_test.go.
Item 2: acronym round-trip asymmetry on the parameter surface
casing.ToSnake is a botocore xform_name port and intentionally lossy for acronyms (IPProtocol -> ip_protocol), but the routing-side retry uses FromSnake (ip_protocol -> ipProtocol != IPProtocol), so the snake spelling of acronym-headed wire names resolves on the ToSnake-derived alias paths (GetParametersIncludingNativeCasing, body attribute matcher) but can miss on the FromSnake retry paths (GetParameter, parameterMatch). Wire-cased input is unaffected.
Suggested fix: build a per-method snake -> wire map from ToSnake over the declared parameter names (the total, forward direction) and use it in the retry paths instead of FromSnake, which guarantees the alias surface is consistent everywhere.
Reproduces on google healthcare - BinaryId, ConsentId, GroupId, PatientId all round-trip to binaryId etc. and fail to resolve:
EXEC google.healthcare.fhir."binary-read" @binary_id = 'b', ...
-> required param not supplied for exec: could not find variable 'BinaryId'
Google compute (IPProtocol, IPAddress) hits the same class on the response side.
Item 3: nativeCasing snake aliases do not reach request bodies under the default data__ regime
Verified live (stackql v0.10.601, google storage, method with request: {nativeCasing: camel, mediaType: application/json}):
- Query parameter aliasing works and is wire-correct:
WHERE max_results = 2 and WHERE maxResults = 2 both produce ...?maxResults=2 on the wire.
- Body attribute aliasing does not:
INSERT ... (data__storage_class) VALUES ('NEARLINE') sends {"storage_class": "NEARLINE"} on the wire; the API silently ignores the unknown key and the bucket is created STANDARD. No error is surfaced.
Cause: the nativeCasing-aware body matcher (getRequestBodySchemaAttributeMatcher -> getschemaAttributeMatcher) is only wired into the requestBodyTranslate: naive* translator path in inferTranslator (internal/anysdk/operation_store.go). The default (data__ prefix) path uses getDefaultRequestBodyMatcher() / requestBodyBaseKeyFuzzyMatcher, which has no casing awareness, and the armoury body construction copies body-map keys verbatim.
Proposed fix: when the method declares request.nativeCasing, the default translator should rename post-prefix body keys via the schema attribute matcher (snake -> wire property name), preserving wire-cased keys verbatim (wire-first, mirroring parameter treatment). Unknown keys that match neither spelling should error rather than pass through, closing the silent-drop hazard generally.
Acceptance criteria:
-- both must produce {"storageClass": "NEARLINE"} on the wire
INSERT INTO google.storage.buckets(project, data__name, data__location, data__storageClass)
SELECT 'p', 'b', 'US', 'NEARLINE';
INSERT INTO google.storage.buckets(project, data__name, data__location, data__storage_class)
SELECT 'p', 'b', 'US', 'NEARLINE';
- Absent
request.nativeCasing, behaviour is byte-identical to today.
- A regression test covering the snake body key -> wire-cased JSON body rename, alongside the existing request-side tests in
casing_surface_regression_test.go.
Item 4: the parameter surface is dual-cased for resolution but wire-only for presentation
With config.snake_case_aliases enabled and a method declaring request.nativeCasing, the column surface renames to snake but the parameter surface does not: SHOW METHODS and SHOW INSERT still present wire names. A reader following generated docs (which render snake under provider-utils --snake-case-aliases=fields,params) sees one spelling, and SHOW METHODS on the same method reports another. Both spellings resolve, so nothing is broken at runtime - the two surfaces just disagree about what to call things.
Verified behaviour
Two registries identical except config.snake_case_aliases, stackql v0.10.601:
flag OFF flag ON
DESCRIBE google.compute.networks (column) autoCreateSubnetworks auto_create_subnetworks
SHOW METHODS google.storage.buckets (param) ifMetagenerationMatch ifMetagenerationMatch
SHOW METHODS google.accessapproval....settings (param) foldersId foldersId
SHOW INSERT google.storage.buckets (body key) data__storageClass data__storageClass
Only the column row moves. This is not an exec-only quirk - folders_delete_access_approval_settings is a DELETE method.
Cause
ToPresentationMap (internal/anysdk/operation_store.go) builds RequiredParams from getRequiredNonBodyParameters(), which is keyed by wire name. The snake alias set built by GetParametersIncludingNativeCasing is consulted only on the resolution paths (GetParameter, parameterMatch, IngestMap), never on the presentation path. The same applies to the request-body attributes rendered by SHOW INSERT.
Proposal
When the method declares request.nativeCasing and the provider enables snake_case_aliases, render the presentation surface through the same alias map already used for resolution:
SHOW METHODS required/optional parameter names
SHOW INSERT top-level data__ body keys (nested contents keep wire casing, as today)
Presentation only - no change to how inputs resolve, so no query can change behaviour. Server variables keep their wire spelling in both presentation and resolution.
Exec input resolution
Exec input resolution is currently split, and should be made uniform as part of this item:
-- required exec arg: snake does NOT resolve
EXEC google.run.locations.export_image @locationsId = 'us-central1', @locations_id_1 = 'x', @projectsId = 'p'
-> required param not supplied for exec: could not find variable 'locationsId1'
-- optional exec arg: snake DOES resolve, and is wire-correct
EXEC google.compute.addresses.move @address = 'a', @project = 'p', @request_id = 'r'
-> .../addresses/a/move?requestId=r
Aliasing both is preferred: it makes the surface uniform and lets docgen drop its exec carve-out. If exec stays wire-only, state that explicitly so tooling can rely on it - provider-utils currently renders exec methods wire-only for exactly this reason.
Acceptance criteria
- With the flag and
nativeCasing, SHOW METHODS and SHOW INSERT present snake names for parameters and top-level body keys; nested body contents and server variables keep wire casing.
- Required and optional exec arguments behave identically to each other, whichever way it is resolved.
- With
snake_case_aliases absent, presentation is byte-identical to today.
- Regression tests alongside the existing request-side tests in
casing_surface_regression_test.go.
Item 5: a metadata-only request block breaks body-less EXEC methods
Adopting request.nativeCasing means emitting a request block on methods that have no request body, purely to carry the casing declaration. That breaks every body-less method whose SQL verb is EXEC, which fail before dispatch with:
no request body for operation = compute.instances.stop
Casing is incidental: the failure is triggered by the presence of a request block that declares no body schema, whatever it contains. It surfaced during nativeCasing adoption only because that is the first reason to emit such a block.
The trigger is the SQL verb, not the HTTP verb. Body-less GET, DELETE and POST all fail when mapped to EXEC; the same HTTP verbs are unaffected when mapped to SELECT / DELETE / INSERT.
Verified reproduction (stackql v0.10.601, local file:// registry)
Two registries generated from the same discovery revision, differing only by a request: {nativeCasing: camel} block on body-less operations:
| operation |
HTTP -> SQL |
without the block |
with the block |
google.healthcare.fhir."binary-read" |
GET -> exec |
dispatched |
never dispatched |
google.apigee.debugsession_data...delete_data |
DELETE -> exec |
dispatched |
never dispatched |
google.compute.instances.stop |
POST -> exec |
Google 404, dispatched |
never dispatched |
SELECT ... google.compute.instances |
GET -> select |
rows |
rows |
DELETE ... google.compute.networks |
DELETE -> delete |
Google 404 |
Google 404 |
google.aiplatform.agents_iam_policies.get_iam_policy |
POST -> select |
Google 400 |
Google 400 |
google.sqladmin.instances_entra_id_certificate... |
POST -> insert |
Google 403 |
Google 403 |
Blast radius on the google family: 363 of 9678 methods are body-less EXEC.
Cause
getRequestBodySchema (internal/anysdk/operation_store.go) conflates "this method declares no body" with "error":
func (op *standardOpenAPIOperationStore) getRequestBodySchema() (Schema, error) {
if op.Request != nil && op.Request.Schema != nil {
return op.Request.Schema, nil
}
return nil, fmt.Errorf("no request body for operation = %s", op.GetName())
}
The EXEC analyzer in stackql core (internal/stackql/primitivegenerator/statement_analyzer.go) then treats that error as fatal whenever a request block exists at all:
requestSchema, err := method.GetRequestBodySchema()
req, reqExists := method.GetRequest()
if err != nil && reqExists {
return nil, err
}
With no request block, reqExists is false and the missing schema is tolerated. Adding a metadata-only block makes reqExists true while the schema is still absent, so the statement fails during analysis.
Candidate fixes
Option A - any-sdk local (no core change). Return (nil, nil) from getRequestBodySchema when the method declares no body schema, reserving the error for genuine failures. The stackql guard then never fires, and analyzeSchemaVsMap is only reached when an EXEC payload was supplied. Callers needing a nil guard: getRequestBodyStringifiedPaths (dereferences .getProperties()) and getRequestBodySchemaAttributeMatcher (dereferences .FindByPath()). getRequestBodyAttributes / ...NoRename already guard with if s != nil; introspection.go already checks bodySchema != nil; shims.go discards the error.
Option B - stackql core call site. Tighten the guard so a request block that declares no body is not taken to imply one, e.g. if err != nil && reqExists && req.GetBodyMediaType() != "". Correct but cannot be carried by this issue.
Option A is preferred if the semantics are agreed: "no body declared" is a normal state for a method, not an error condition.
Acceptance criteria
- With
request: {nativeCasing: camel} on a body-less EXEC method, EXEC dispatches and returns the API response (identical to the same method with no request block), for GET-, DELETE- and POST-backed exec methods alike.
- Body-less GET/DELETE/POST mapped to non-exec verbs, and POST-with-body, remain byte-identical.
- An
EXEC carrying a payload against a method with no body schema still errors.
- Regression test alongside the existing request-side tests in
casing_surface_regression_test.go.
Current workaround
The google provider generator omits the request block for body-less EXEC methods (isExecMethod in generateStackQLResources), so those 363 methods keep wire-cased parameters only. It can be removed once this is fixed.
Backward compatibility (hard requirement, all items)
Every one of these must keep working exactly as today, before and after, on any provider with either flag enabled:
-- wire-case column in a projection
SELECT machineType, creationTimestamp FROM google.compute.instances
WHERE project = 'p' AND zone = 'z';
-- wire-case column in a locally-evaluated predicate and ORDER BY
SELECT name FROM google.compute.networks
WHERE project = 'p' AND autoCreateSubnetworks = false
ORDER BY creationTimestamp DESC;
-- wire-case parameter in a WHERE clause
SELECT name FROM google.storage.buckets
WHERE project = 'p' AND maxResults = 2;
-- wire-case parameter in an INSERT column list, and wire-case body keys
INSERT INTO google.storage.buckets(project, requestId, data__name, data__storageClass)
SELECT 'p', 'r', 'b', 'NEARLINE';
-- wire-case EXEC arguments
EXEC google.compute.instances.stop
@instance = 'vm', @project = 'p', @zone = 'z', @requestId = 'r';
Resolution stays wire-first: the key as written is matched first, and a snake alias is never allowed to clobber a real wire name of the same spelling. Renaming a presentation surface must not remove the wire spelling from the resolvable set.
Fail-safe (must not regress, all items)
Unresolvable names error today rather than silently producing wrong results, and that must hold for both spellings:
SELECT ... WHERE max_resultz = 2 -> could not locate symbol max_resultz
SELECT auto_create_subnetworkz FROM ... -> no such column: auto_create_subnetworkz
A key that matches no wire name must not degrade into a silently-dropped parameter, a silently-dropped body key (item 3), or a client-side filter over a column that does not exist.
Out of scope
- Wire-first matching order on the request side: correct as is, and unchanged by every item here.
- Enabling either flag in any published provider (separate registry decisions per provider, gated on this issue).
- Nested/structured body attribute renames beyond the existing top-level treatment.
- Response column extraction changes:
GetWireName remains the extraction key throughout.
Summary
The request-side casing mechanism (
request.nativeCasing) is backward compatible: wire-cased keys always match first and snake_case is an additional alias. The response-side mechanism (config.snake_case_aliases) is not: it renames the column display/DDL surface to snake_case outright, so any existing query that projects, filters, orders by or references a wire-cased (camelCase / PascalCase) column name breaks the moment a provider ships with the flag enabled. To make snake-on-the-surface / native-on-the-wire adoptable for published providers with an existing user base (google, aws), the column surface needs an aliasing mode to match the parameter surface.Four work items are tracked here. They were found while adopting
nativeCasingon the google provider family and share one root concern: which spellings the engine accepts, and which spellings it presents, must agree - and accepting a new spelling must never remove an old one.Target end state
DESCRIBE,SELECT *)ORDER BY/ localWHERESHOW METHODS, docs)data__body keys (SHOW INSERT, docs)Nothing in this issue removes a wire spelling from the accepted set, on any surface.
Current behaviour (verified against main)
Request side - dual-cased, wire-first, non-breaking:
GetParameter,parameterMatchand operation-param lookup (internal/anysdk/operation_store.go) all try the key as given first and only on a miss apply the snake -> wire retry, gated onrequest.nativeCasingbeing declared.GetParametersIncludingNativeCasingaugments the wire-keyed set with snake aliases and never clobbers a real wire parameter of the same name.getschemaAttributeMatcheraccepts both the wire property name and itsToSnakeform and maps back to the wire key - but see item 3: it is only reachable from thenaive*translator path, not the defaultdata__path.Response side - renamed, wire names removed from the surface:
StackQLConfig.SnakeCaseAliasesenabled (internal/anysdk/config.go),getPropertiesColumns,ToDescriptionMapandGetAllColumns(internal/anysdk/schema.go) set the column display/DDL name tocasing.ToSnake(wireKey). The wire name is retained only internally (GetWireName) for response payload extraction.TestToDescriptionMapSnakeAliases).TestGetAllColumnsSnakeAliasescomment):Why this blocks adoption
For a provider with no installed base the rename is fine. For google/aws it means a hard flag day: every existing script, dashboard and stackql-deploy template that touches a response field by wire name breaks in a single provider release, while the parameter surface (correctly) keeps accepting both spellings. The asymmetry is the problem - inputs are dual-cased, outputs are single-cased.
Scale on google alone: 19,756 distinct camelCase schema property names across 70,274 properties, published as a single rolling
v00.00.00000with no earlier version for users to pin.Item 1: response-column surface is a rename, not an alias
Make the response-column surface dual-cased under the flag, mirroring the parameter treatment:
snake_case_aliasesenabled, retry withcasing.ToSnake(identifier)before failing. This is the exact mirror of theGetParameterreverse-casing retry, applied at the column descriptor / select-items resolution layer, and requires no DDL change.Option 1 acceptance criteria:
SELECT machine_type ...andSELECT machineType ...both resolve to the same column and value.ORDER BYand locally-evaluatedWHEREpredicates accept both spellings.SELECT *output remains snake-only (unchanged from current flag behaviour).TestToDescriptionMapDisabledIsWireKeyed/TestGetAllColumnsDisabledIsWireKeyedstill pass).casing_surface_regression_test.go.Item 2: acronym round-trip asymmetry on the parameter surface
casing.ToSnakeis a botocorexform_nameport and intentionally lossy for acronyms (IPProtocol->ip_protocol), but the routing-side retry usesFromSnake(ip_protocol->ipProtocol!=IPProtocol), so the snake spelling of acronym-headed wire names resolves on theToSnake-derived alias paths (GetParametersIncludingNativeCasing, body attribute matcher) but can miss on theFromSnakeretry paths (GetParameter,parameterMatch). Wire-cased input is unaffected.Suggested fix: build a per-method snake -> wire map from
ToSnakeover the declared parameter names (the total, forward direction) and use it in the retry paths instead ofFromSnake, which guarantees the alias surface is consistent everywhere.Reproduces on google healthcare -
BinaryId,ConsentId,GroupId,PatientIdall round-trip tobinaryIdetc. and fail to resolve:Google compute (
IPProtocol,IPAddress) hits the same class on the response side.Item 3:
nativeCasingsnake aliases do not reach request bodies under the defaultdata__regimeVerified live (stackql v0.10.601, google storage, method with
request: {nativeCasing: camel, mediaType: application/json}):WHERE max_results = 2andWHERE maxResults = 2both produce...?maxResults=2on the wire.INSERT ... (data__storage_class) VALUES ('NEARLINE')sends{"storage_class": "NEARLINE"}on the wire; the API silently ignores the unknown key and the bucket is created STANDARD. No error is surfaced.Cause: the
nativeCasing-aware body matcher (getRequestBodySchemaAttributeMatcher->getschemaAttributeMatcher) is only wired into therequestBodyTranslate: naive*translator path ininferTranslator(internal/anysdk/operation_store.go). The default (data__prefix) path usesgetDefaultRequestBodyMatcher()/requestBodyBaseKeyFuzzyMatcher, which has no casing awareness, and the armoury body construction copies body-map keys verbatim.Proposed fix: when the method declares
request.nativeCasing, the default translator should rename post-prefix body keys via the schema attribute matcher (snake -> wire property name), preserving wire-cased keys verbatim (wire-first, mirroring parameter treatment). Unknown keys that match neither spelling should error rather than pass through, closing the silent-drop hazard generally.Acceptance criteria:
request.nativeCasing, behaviour is byte-identical to today.casing_surface_regression_test.go.Item 4: the parameter surface is dual-cased for resolution but wire-only for presentation
With
config.snake_case_aliasesenabled and a method declaringrequest.nativeCasing, the column surface renames to snake but the parameter surface does not:SHOW METHODSandSHOW INSERTstill present wire names. A reader following generated docs (which render snake under provider-utils--snake-case-aliases=fields,params) sees one spelling, andSHOW METHODSon the same method reports another. Both spellings resolve, so nothing is broken at runtime - the two surfaces just disagree about what to call things.Verified behaviour
Two registries identical except
config.snake_case_aliases, stackql v0.10.601:Only the column row moves. This is not an exec-only quirk -
folders_delete_access_approval_settingsis aDELETEmethod.Cause
ToPresentationMap(internal/anysdk/operation_store.go) buildsRequiredParamsfromgetRequiredNonBodyParameters(), which is keyed by wire name. The snake alias set built byGetParametersIncludingNativeCasingis consulted only on the resolution paths (GetParameter,parameterMatch,IngestMap), never on the presentation path. The same applies to the request-body attributes rendered bySHOW INSERT.Proposal
When the method declares
request.nativeCasingand the provider enablessnake_case_aliases, render the presentation surface through the same alias map already used for resolution:SHOW METHODSrequired/optional parameter namesSHOW INSERTtop-leveldata__body keys (nested contents keep wire casing, as today)Presentation only - no change to how inputs resolve, so no query can change behaviour. Server variables keep their wire spelling in both presentation and resolution.
Exec input resolution
Exec input resolution is currently split, and should be made uniform as part of this item:
Aliasing both is preferred: it makes the surface uniform and lets docgen drop its exec carve-out. If exec stays wire-only, state that explicitly so tooling can rely on it - provider-utils currently renders exec methods wire-only for exactly this reason.
Acceptance criteria
nativeCasing,SHOW METHODSandSHOW INSERTpresent snake names for parameters and top-level body keys; nested body contents and server variables keep wire casing.snake_case_aliasesabsent, presentation is byte-identical to today.casing_surface_regression_test.go.Item 5: a metadata-only
requestblock breaks body-less EXEC methodsAdopting
request.nativeCasingmeans emitting arequestblock on methods that have no request body, purely to carry the casing declaration. That breaks every body-less method whose SQL verb isEXEC, which fail before dispatch with:Casing is incidental: the failure is triggered by the presence of a
requestblock that declares no body schema, whatever it contains. It surfaced duringnativeCasingadoption only because that is the first reason to emit such a block.The trigger is the SQL verb, not the HTTP verb. Body-less
GET,DELETEandPOSTall fail when mapped toEXEC; the same HTTP verbs are unaffected when mapped toSELECT/DELETE/INSERT.Verified reproduction (stackql v0.10.601, local
file://registry)Two registries generated from the same discovery revision, differing only by a
request: {nativeCasing: camel}block on body-less operations:google.healthcare.fhir."binary-read"google.apigee.debugsession_data...delete_datagoogle.compute.instances.stopSELECT ... google.compute.instancesDELETE ... google.compute.networksgoogle.aiplatform.agents_iam_policies.get_iam_policygoogle.sqladmin.instances_entra_id_certificate...Blast radius on the google family: 363 of 9678 methods are body-less EXEC.
Cause
getRequestBodySchema(internal/anysdk/operation_store.go) conflates "this method declares no body" with "error":The
EXECanalyzer in stackql core (internal/stackql/primitivegenerator/statement_analyzer.go) then treats that error as fatal whenever a request block exists at all:With no request block,
reqExistsis false and the missing schema is tolerated. Adding a metadata-only block makesreqExiststrue while the schema is still absent, so the statement fails during analysis.Candidate fixes
Option A - any-sdk local (no core change). Return
(nil, nil)fromgetRequestBodySchemawhen the method declares no body schema, reserving the error for genuine failures. The stackql guard then never fires, andanalyzeSchemaVsMapis only reached when anEXECpayload was supplied. Callers needing a nil guard:getRequestBodyStringifiedPaths(dereferences.getProperties()) andgetRequestBodySchemaAttributeMatcher(dereferences.FindByPath()).getRequestBodyAttributes/...NoRenamealready guard withif s != nil;introspection.goalready checksbodySchema != nil;shims.godiscards the error.Option B - stackql core call site. Tighten the guard so a request block that declares no body is not taken to imply one, e.g.
if err != nil && reqExists && req.GetBodyMediaType() != "". Correct but cannot be carried by this issue.Option A is preferred if the semantics are agreed: "no body declared" is a normal state for a method, not an error condition.
Acceptance criteria
request: {nativeCasing: camel}on a body-less EXEC method,EXECdispatches and returns the API response (identical to the same method with no request block), for GET-, DELETE- and POST-backed exec methods alike.EXECcarrying a payload against a method with no body schema still errors.casing_surface_regression_test.go.Current workaround
The google provider generator omits the
requestblock for body-less EXEC methods (isExecMethodingenerateStackQLResources), so those 363 methods keep wire-cased parameters only. It can be removed once this is fixed.Backward compatibility (hard requirement, all items)
Every one of these must keep working exactly as today, before and after, on any provider with either flag enabled:
Resolution stays wire-first: the key as written is matched first, and a snake alias is never allowed to clobber a real wire name of the same spelling. Renaming a presentation surface must not remove the wire spelling from the resolvable set.
Fail-safe (must not regress, all items)
Unresolvable names error today rather than silently producing wrong results, and that must hold for both spellings:
A key that matches no wire name must not degrade into a silently-dropped parameter, a silently-dropped body key (item 3), or a client-side filter over a column that does not exist.
Out of scope
GetWireNameremains the extraction key throughout.