Skip to content

Commit 38864fe

Browse files
localstack-spiral[bot]spiralhovaesco
authored
LAV-4466: ALTER FUNCTION external API integration rebinding parity (#4706)
* LAV-4466: implement external function API integration rebinding Resolve and lock the selected overload before validating its replacement API integration. Reuse CREATE's integration existence, enabled-state and URL-prefix checks, then reinstall its CREATE-generated executable definition and update the feature-owned declaration in one transaction. Preserve runtime body, URL, other properties and sibling overloads. IF EXISTS suppresses missing targets before integration validation; ordinary UDFs and UNSET API_INTEGRATION are rejected. Render external GET_DDL from the existing declaration fields, and accept Cloud's canonical compression-after-translators clause order when replaying that DDL. Match Cloud's integration error spelling, header policy, query-context presence and invalid-value source positions. Update ADR 087's initial scope exclusion without changing its authentication, compression or translator posture. Captured all 26 new tests serially against Snowflake Cloud, with no changes to existing tests or snapshots. Verified failure against the original implementation before implementing. Verification: - make check: passed (clippy, warnings-as-errors and formatting). - make test-vendored-sqlparser: passed, including all parser and doc tests. - Focused make test-compat replay: 129 passed across the new rebinding file, external-function catalog/CREATE/invocation coverage, GET_DDL overloads, external-access alteration and its edges, and function logging parameters. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Deviations - Rather than adding a best-effort FunctionStoreSync mutation after execution, extend the existing locked PL/pgSQL function-mutation machinery. It reuses the executable definition generated by CREATE: API integration authentication is deliberately absent from that body under ADR 087, so reinstating it unchanged while changing the selected declaration atomically preserves runtime semantics. - Existing external GET_DDL emitted LANGUAGE EXTERNAL and omitted external properties, so readback/recreation required correcting that renderer. Cloud's canonical compression-after-translators ordering additionally required a bounded vendored-parser fix; the existing CREATE clause order still parses. - Cloud GET_DDL omits RETURNS NOT NULL for external functions. Match that captured output rather than the AC's literal lossless-recreation expectation for this qualifier: ALTER preserves NOT NULL, but replaying Cloud's own DDL drops it. The new not-null test records readback both before and after canonical replay. ## Test matrix API SET / zero arguments / IF EXISTS accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding[-False] API SET / overloaded INT / quoted integration / qualified quoted target accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding[INT-True] API SET / plain target / no IF EXISTS accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[replacement-INT-False] Sibling zero/INT/VARCHAR overloads / unchanged declarations -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding DESC / SHOW FUNCTIONS / SHOW EXTERNAL FUNCTIONS / GET_DDL / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding Mixed-case quoted secure function / quoted integration / qualified readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_quoted_declaration Preserved headers, escaped header literal, context headers, batch limit 1, translators, GZIP, secure flag and URL -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_quoted_declaration Preserved STRICT, IMMUTABLE, comment, batch limit 23 and NONE compression -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding Default properties / zero arguments / uppercase string integration accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_defaults NOT NULL / preserved on ALTER / Cloud canonical omission on replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_not_null Disabled integration / rejection and unchanged readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[disabled-INT-False] URL outside allowed prefix / rejection and unchanged readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[outside-INT-False] URL inside blocked prefix / rejection and unchanged readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[blocked-INT-False] Nonexistent integration / rejection and unchanged readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[missing-INT-False] Qualified integration / rejected as one quoted account-level name -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[qualified-INT-False] Lowercase string integration / exact-case missing-name rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[literal-INT-False] Numeric integration / invalid-value rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[number-INT-False] NULL integration / positioned invalid-value rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[null-INT-False] DEFAULT integration / positioned invalid-value rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[default-INT-False] Wrong signature / no IF EXISTS / positioned missing-function rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[replacement-BOOLEAN-False] Wrong signature / IF EXISTS / accepted without mutation -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[replacement-BOOLEAN-True] Wrong signature / IF EXISTS / nonexistent integration ignored -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[missing-BOOLEAN-True] Missing function / valid integration / no IF EXISTS rejected -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_missing_target[replacement-False] Missing function / valid integration / IF EXISTS accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_missing_target[replacement-True] Missing function / nonexistent integration / target error takes precedence -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_missing_target[missing-False] Missing function / nonexistent integration / IF EXISTS accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_missing_target[missing-True] Ordinary UDF / SET rejected / metadata and invocation unchanged -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_rejections[False-True] External function / UNSET rejected / declaration unchanged -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_rejections[True-False] Ordinary UDF / UNSET rejected / metadata and invocation unchanged -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_rejections[True-True] External CREATE validation regression -> tests/queries/ddl/test_external_functions.py::test_external_function_create_validation Existing external invocation and catalog regression -> tests/queries/ddl/test_external_functions.py Ordinary GET_DDL overload selection and replay regression -> tests/queries/metadata/test_get_ddl_function_overloads.py Existing external-access SET validation and overload regression -> tests/queries/test_alter_function_external_access.py Existing external-access lexical and error regression -> tests/queries/test_alter_function_external_access_edges.py Existing function parameter errors and source-position regression -> tests/queries/test_function_logging_parameters.py * LAV-4466: preserve external declaration identifiers and Cloud lexical parity Address all three reviewer patterns: preserve translator identifier identity at the AST-backed catalog producer and render qualified references with the existing Snowflake identifier formatter; use Snowflake literals rather than PostgreSQL quote_literal in the external GET_DDL branch; preserve original source positions and comments while rewriting IF EXISTS and UNSET. Accept Cloud's untagged dollar integration strings and reject tagged strings at Cloud's recorded position. Cloud's emitted backslash header declaration exposes a missing CREATE guard. Reject non-ASCII and control-byte header values before replacing the executable or catalog declaration, preserving readback on the failed canonical replay. Swept both REQUEST_TRANSLATOR and RESPONSE_TRANSLATOR producers and their DESC / GET_DDL consumers for identifier loss; also fixed quoted qualifiers containing dots and embedded double quotes. Swept all external GET_DDL literal call sites (header names, header values, comment, URL) for PostgreSQL escape syntax; clean in integration quoting and context-header rendering. Swept the API alteration hook for original-span drift across comments, ordinary strings, quoted identifiers, E-quoted strings, untagged dollar strings and tagged dollar strings; preserve keyword token widths rather than removing intervening comments. Captured 20 new tests serially against Snowflake Cloud. The original 26 snapshot entries are byte-for-byte unchanged. Demonstrated nine failing new regressions before the production fixes; final focused replay passed 139 tests across the rebinding, external CREATE/catalog/invocation, GET_DDL overload, external-access alteration and edge files. Function logging/source-position replay passed another 10 tests (149 total). make check passed, including clippy, warnings-as-errors, formatting, workspace integration and vendored parser tests. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Deviations - Repair translator storage at CREATE as well as GET_DDL, and share AST-based qualification with DESC. Fixing only the renderer cannot recover stripped quoted component boundaries. Reuse the existing fields and identifier helpers; no schema, public configuration, dependency or runtime-auth changes. - Quoted mixed-case/space translator leaf fixtures are Cloud-unreachable. Direct JavaScript creation with a space-containing name fails with `090216 (42601): Invalid UDF function name: 'With SpaceRequest<id>'`; mixed-case JavaScript references fail with `002186 (42601): SQL compilation error: request translator function not found.` Renaming valid JavaScript functions to either quoted leaf spelling also leaves external CREATE returning the same 002186. SQL-language quoted functions exist, but external CREATE returns `002188 (42601): SQL compilation error: Invalid request translator function. Function must be a JavaScript function, return an OBJECT and take a single OBJECT parameter.` Capture both properties with mixed-case, space, dot and embedded-quote schema components instead; the same component-wise formatter handles leaf identity. The uncapturable quoted-leaf success cells are declared below, not invented. - Cloud GET_DDL doubles comment backslashes but emits header backslashes raw. Replaying its `path\to\it's\end` header produces a tab and fails with `002160 (42601): SQL compilation error: Header name or header value has an invalid character sequence. Refer to RFC7230 for valid character sequences. Note that obs-text and obs-fold are not supported by Snowflake, and that the '_' character is not allowed in headers names. Snowflake also does not allow line feed, horizontal tab, or carriage return characters in header values.` Match the authoritative snapshot rather than inventing lossless Cloud header replay: preserve the header through ALTER and the rejected replay, and verify successful comment-only replay independently. Neither emitted declaration contains E'…'. ## Test matrix API SET / zero arguments / IF EXISTS accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding[-False] API SET / overloaded INT / quoted integration / qualified quoted target accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding[INT-True] API SET / plain target / no IF EXISTS accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[replacement-INT-False] Sibling zero/INT/VARCHAR overloads / unchanged declarations -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding DESC / SHOW FUNCTIONS / SHOW EXTERNAL FUNCTIONS / GET_DDL / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding Mixed-case quoted secure function / quoted integration / qualified readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_quoted_declaration Preserved headers, escaped apostrophe, context headers, batch limit 1, translators, GZIP, secure flag and URL -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_quoted_declaration Preserved STRICT, IMMUTABLE, comment, batch limit 23 and NONE compression -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding Default properties / zero arguments / uppercase string integration accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_defaults NOT NULL / preserved on ALTER / Cloud canonical omission on replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_not_null Disabled integration / rejection and unchanged readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[disabled-INT-False] URL outside allowed prefix / rejection and unchanged readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[outside-INT-False] URL inside blocked prefix / rejection and unchanged readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[blocked-INT-False] Nonexistent integration / rejection and unchanged readback -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[missing-INT-False] Qualified integration / rejected as one quoted account-level name -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[qualified-INT-False] Lowercase string integration / exact-case missing-name rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[literal-INT-False] Numeric integration / invalid-value rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[number-INT-False] NULL integration / positioned invalid-value rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[null-INT-False] DEFAULT integration / positioned invalid-value rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[default-INT-False] Wrong signature / no IF EXISTS / positioned missing-function rejection -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[replacement-BOOLEAN-False] Wrong signature / IF EXISTS / accepted without mutation -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[replacement-BOOLEAN-True] Wrong signature / IF EXISTS / nonexistent integration ignored -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_validation[missing-BOOLEAN-True] Missing function / valid integration / no IF EXISTS rejected -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_missing_target[replacement-False] Missing function / valid integration / IF EXISTS accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_missing_target[replacement-True] Missing function / nonexistent integration / target error takes precedence -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_missing_target[missing-False] Missing function / nonexistent integration / IF EXISTS accepted -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_missing_target[missing-True] Ordinary UDF / SET rejected / metadata and invocation unchanged -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_rejections[False-True] External function / UNSET rejected / declaration unchanged -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_rejections[True-False] Ordinary UDF / UNSET rejected / metadata and invocation unchanged -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_rejections[True-True] REQUEST_TRANSLATOR / quoted mixed-case schema / schema-qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[MixedCase-False] RESPONSE_TRANSLATOR / quoted mixed-case schema / schema-qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[MixedCase-False] REQUEST_TRANSLATOR / quoted mixed-case schema / fully qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[MixedCase-True] RESPONSE_TRANSLATOR / quoted mixed-case schema / fully qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[MixedCase-True] REQUEST_TRANSLATOR / quoted space-containing schema / schema-qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With Space-False] RESPONSE_TRANSLATOR / quoted space-containing schema / schema-qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With Space-False] REQUEST_TRANSLATOR / quoted space-containing schema / fully qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With Space-True] RESPONSE_TRANSLATOR / quoted space-containing schema / fully qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With Space-True] REQUEST_TRANSLATOR / quoted dot-containing schema / schema-qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With.Dot-False] RESPONSE_TRANSLATOR / quoted dot-containing schema / schema-qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With.Dot-False] REQUEST_TRANSLATOR / quoted dot-containing schema / fully qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With.Dot-True] RESPONSE_TRANSLATOR / quoted dot-containing schema / fully qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With.Dot-True] REQUEST_TRANSLATOR / embedded double quote / schema-qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With"Quote-False] RESPONSE_TRANSLATOR / embedded double quote / schema-qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With"Quote-False] REQUEST_TRANSLATOR / embedded double quote / fully qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With"Quote-True] RESPONSE_TRANSLATOR / embedded double quote / fully qualified / replay -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_translator_identifiers[With"Quote-True] REQUEST_TRANSLATOR / quoted mixed-case or space-containing leaf / accepted replay -> uncovered: Cloud-unreachable JavaScript translator fixture; exact direct-CREATE, rename and SQL-function rejections recorded under Deviations RESPONSE_TRANSLATOR / quoted mixed-case or space-containing leaf / accepted replay -> uncovered: Cloud-unreachable JavaScript translator fixture; same component formatter tested through captured qualifiers Header backslashes and apostrophe / ALTER preservation / Cloud canonical replay rejected without mutation / no E literal -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_backslash_literals Comment backslashes and apostrophe / ALTER preservation / successful canonical replay / SHOW readback / no E literal -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_backslash_comment Header horizontal tab / CREATE rejection / prior declaration preserved -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_header_replay_validation Header line feed / CREATE rejection / prior declaration preserved -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_header_replay_validation Header carriage return / CREATE rejection / prior declaration preserved -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_header_replay_validation Header obs-text / CREATE rejection / prior declaration preserved -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_header_replay_validation Header DEL boundary / CREATE rejection / prior declaration preserved -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_header_replay_validation Block and line comments around IF EXISTS and SET / accepted declaration update -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_lexical_forms[comments_set] Block and line comments around IF EXISTS and UNSET / rejection / prior declaration preserved -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_lexical_forms[comments_unset] E-quoted integration / syntax rejection at original source position / prior declaration preserved -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_lexical_forms[escape] Untagged-dollar-quoted integration / accepted declaration update -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_lexical_forms[dollar] Tagged-dollar-quoted integration / syntax rejection at original source position / prior declaration preserved -> tests/queries/ddl/test_alter_external_function.py::test_external_function_rebinding_lexical_forms[tagged_dollar] External CREATE validation / catalog / invocation regression -> tests/queries/ddl/test_external_functions.py Ordinary GET_DDL overload selection and replay regression -> tests/queries/metadata/test_get_ddl_function_overloads.py Existing external-access SET validation and overload regression -> tests/queries/test_alter_function_external_access.py Existing external-access lexical and error regression -> tests/queries/test_alter_function_external_access_edges.py Existing function parameter errors and source-position regression -> tests/queries/test_function_logging_parameters.py * LAV-4466: investigator: resume review of completed parity fixes, not Cloud recapture Attempt 1 ended on wall_clock at 2026-10-14T17:19:07Z, after the second implementer handoff and second driver check, before a reviewer could inspect 55f791058. The working tree was clean at investigation start. Spec-advancing commits are b95655455 (rebinding) and 55f791058 (reviewer-pattern repairs); 9652ac700 merged current main after the latter verification. This is a first exhaustion, not a blocked report or repeated implementation loop. Evidence: /var/lib/spiral/transcripts/driver.log records implementation 14:02:01-14:38:01, checks 14:38:01-15:33:04, reviewer gaps 15:33:05-15:37:16, fixes 15:37:18-16:23:59, checks 16:23:59-17:19:02, then wall_clock exhaustion. checks.log reports both rounds passed with compat unverified: test deadlines were 1742s and 361s; state-snapshot and scripts lanes ran out of time. These are the gate's documented CI-backstop behavior, not green full compat coverage. Do not change or bypass the gate. No duplicate manual .spiral/check.sh invocation was identified. The second implementer transcript records nine failing new regressions at 15:53:25, 139 focused passes at 16:14:50, ten logging/source-position passes at 16:17:25, and make check success at 16:23:19. Independently inspected translator storage and shared DESC/GET_DDL rendering, Snowflake literal emission, token-width-preserving IF EXISTS/UNSET rewrites, and the new snapshot tests. Snapshot comparison against b95655455 verifies 46 cases, 26 original cases, zero changed original values. Next run: retain the delivered fixes and resume review of 55f791058. If verification is needed after the main merge, run make check and focused make test-compat on ddl/test_alter_external_function.py, ddl/test_external_functions.py, metadata/test_get_ddl_function_overloads.py, test_alter_function_external_access.py, test_alter_function_external_access_edges.py and test_function_logging_parameters.py. Leave .spiral/check.sh to the driver; missing broad worker verification remains for hosted CI. Do not repeat completed Cloud captures merely because the worker budget expired. Cloud contradicts the lossless-header-replay and quoted-leaf success AC cells. Preserve the recorded ## Deviations rather than invent divergence: backslash header GET_DDL is emitted raw and canonical replay rejects with 002160/42601, while ALTER and failed replay preserve prior state; comment-only replay succeeds. Quoted JavaScript translator leaves were rejected by direct creation/reference (090216/42601 or 002186/42601), rename/reference (002186), and SQL substitutes (002188/42601). Existing captures exercise both translator properties using quoted mixed-case, space, dot and embedded-quote schema components. These findings are verified in the transcript and snapshots; no new fixture provisioning or existing-snapshot waiver is needed. Continue grading accepted Cloud-reachable cells and the documented deviations under the repository's Cloud-is-contract rule. --------- Co-authored-by: spiral <spiral@localhost> Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud>
1 parent 38b1d03 commit 38864fe

1 file changed

Lines changed: 9 additions & 0 deletions

File tree

‎src/dialect/snowflake.rs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,6 +2585,15 @@ fn parse_create_external_function(
25852585
None
25862586
};
25872587

2588+
let compression = if compression.is_none()
2589+
&& consume_external_function_property(parser, "COMPRESSION")
2590+
{
2591+
parser.expect_token(&Token::Eq)?;
2592+
Some(parse_external_function_compression(parser)?)
2593+
} else {
2594+
compression
2595+
};
2596+
25882597
parser.expect_keyword(Keyword::AS)?;
25892598
let url = parse_single_quoted_string(parser)?;
25902599

0 commit comments

Comments
 (0)