Skip to content

Commit ee952ef

Browse files
localstack-spiral[bot]spiralhovaesco
authored
LAV-4438: Run scalar Java stored procedures on the PL/Java substrate (#4665)
* LAV-4438: Run scalar Java procedures through PL/Java and SPI Compile inline procedures against the embedded Snowpark shim and PL/Java API, validate public handlers, and lower CALL through the existing opaque procedure path. Support instance/static handlers and scalar conversions, Session.sql/table collection, ordinal Row access, and transaction visibility. Keep UDF String-only/static-only marshalling unchanged. Hash complete wrapper sources; prepend replacements and invalidate warmed backend loaders with an account generation and backend-private classpath refresh. Refresh pooled and transaction-pinned connections before statements, without shared classpath updates, CALL retries, backend retirement, or cold JVM startup. Install the shim once on every Java-bearing schema. Enforce runtime/package policies at CREATE, normalize existing metadata properties without changing column projections, and pin the image JVM to OpenJDK 17. Document the single-JVM/package/shim limitations and sibling slices. Worker JVM observed: 17.0.20.1. Cloud captures are new additive baselines only; existing compatibility bodies and recorded values are unchanged. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Verification - Serial TEST_TARGET=SNOWFLAKE_CLOUD captures of the new parity tests with PYTEST_XDIST_ARGS="" and --snapshot-update. - make check: green, including formatting and warnings-as-errors. - make test-vendored-sqlparser: green (explicitly run because the uncommitted vendor diff is not selected by make check's committed-ref comparison). - make test-compat PYTEST_PATH='tests/queries/test_java_procedures.py tests/queries/test_functions.py tests/queries/test_scala_functions.py tests/queries/test_udf_external_access.py tests/queries/metadata/test_metadata_queries.py' PYTEST_XDIST_ARGS='' PYTEST_ARGS="-k '(java and not javascript) or scala'": 116 passed. - git diff --check: green. ## Test matrix Denominator: issue ACs and the offline CREATE PROCEDURE resource-index syntax. The scalar Java slice changes LANGUAGE/AS/HANDLER/RUNTIME_VERSION/PACKAGES, compilation and installation; common opaque header parsing is reused. Inline CREATE/REPLACE + scalar CALL, instance Session handler -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Session.table + populated numeric rows -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Session.sql + mixed scalar values -> tests/queries/test_java_procedures.py::test_java_procedure_session_table collect + empty result -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Row.size/get/getString/getInt/getLong/getDecimal/getBoolean/getDouble -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Row ordinal access + duplicate aliases -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Row NULL + NUMBER/BigDecimal and FLOAT/Double conversion -> tests/queries/test_java_procedures.py::test_java_procedure_session_table DESCRIBE/GET_DDL + handler/runtime/packages/EXECUTE AS CALLER -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Instance handler + int SQL argument -> tests/queries/test_java_procedures.py::test_java_procedure_handler[instance] Public handler + wrong arity -> tests/queries/test_java_procedures.py::test_java_procedure_handler[arity] Handler SQL/Java type incompatibility -> tests/queries/test_java_procedures.py::test_java_procedure_handler[type] Missing handler method -> tests/queries/test_java_procedures.py::test_java_procedure_handler[method] Missing handler class -> tests/queries/test_java_procedures.py::test_java_procedure_handler[class] Missing HANDLER property -> tests/queries/test_java_procedures.py::test_java_procedure_missing_handler Two procedures sharing handler/arity + CREATE error identity -> tests/queries/test_java_procedures.py::test_java_procedure_handler_identity Two procedures sharing handler/arity + invocation error identity and repeated first CALL -> tests/queries/test_java_procedures.py::test_java_procedure_invocation_identity Same-class replacement + changed source -> tests/queries/test_java_procedures.py::test_java_procedure_replacement_and_dynamic_call EXECUTE IMMEDIATE + nested Session.sql CALL -> tests/queries/test_java_procedures.py::test_java_procedure_replacement_and_dynamic_call NUMBER -> int + MIN/MAX/zero -> tests/queries/test_java_procedures.py::test_java_procedure_scalar_arguments NUMBER -> long + MIN/MAX -> tests/queries/test_java_procedures.py::test_java_procedure_scalar_arguments NUMBER -> boxed Integer/Long + NULL/non-NULL -> tests/queries/test_java_procedures.py::test_java_procedure_scalar_arguments NUMBER(38,2) -> BigDecimal + NULL/fraction -> tests/queries/test_java_procedures.py::test_java_procedure_scalar_arguments FLOAT -> double + negative/zero -> tests/queries/test_java_procedures.py::test_java_procedure_scalar_arguments BOOLEAN -> boolean + true/false -> tests/queries/test_java_procedures.py::test_java_procedure_scalar_arguments VARCHAR -> String + NULL/empty/value -> tests/queries/test_java_procedures.py::test_java_procedure_scalar_arguments Scalar NUMBER/FLOAT/BOOLEAN/VARCHAR return + matching Java types -> tests/queries/test_java_procedures.py::test_java_procedure_return_type SQL NUMBER + Java String return conversion -> tests/queries/test_java_procedures.py::test_java_procedure_return_type SQL VARCHAR + Java int return CREATE rejection -> tests/queries/test_java_procedures.py::test_java_procedure_return_type Scalar NULL/empty/value return -> tests/queries/test_java_procedures.py::test_java_procedure_null_result Procedure/UDF runtime 11/17/21 + accepted metadata -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure/UDF unquoted runtime 11 + normalized metadata -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure/UDF omitted runtime + NULL metadata -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure/UDF malformed runtime empty/abc/8/22/11.0.0 + exact errors -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure missing/empty/telemetry-only PACKAGES + compile errors -> tests/queries/test_java_procedures.py::test_java_package_policy UDF omitted/empty/telemetry-only PACKAGES + accepted CREATE -> tests/queries/test_java_procedures.py::test_java_package_policy Procedure/UDF Snowpark 1.2.0/latest + accepted normalized metadata -> tests/queries/test_java_procedures.py::test_java_package_policy Procedure/UDF invalid/versionless/0.0.0 package + CREATE rejection -> tests/queries/test_java_procedures.py::test_java_package_policy Procedure/UDF Cloud-served but unemulated package + exact emulator-only rejection -> tests/queries/test_java_procedures.py::test_java_unemulated_package Procedure/UDF synthetic Snowpark version + single JVM 17 divergence -> tests/queries/test_java_procedures.py::test_java_emulated_packages Session.table + enclosing uncommitted transaction visibility/rollback -> tests/queries/test_java_procedures.py::test_java_procedure_transaction_visibility Procedure/UDF warmed transaction-pinned second connection + new installation -> tests/queries/test_java_procedures.py::test_java_procedure_visible_to_pinned_second_session Session.builder/jdbcConnection/createAsyncJob + exact emulator-only unsupported errors -> tests/queries/test_java_procedures.py::test_java_procedure_unsupported_api DataFrame.async/select/filter/limit + exact emulator-only unsupported errors -> tests/queries/test_java_procedures.py::test_java_procedure_unsupported_api Worker-thread Session submission + exact emulator-only rejection -> tests/queries/test_java_procedures.py::test_java_procedure_worker_thread Inline javac + shim and PL/Java API availability in UDF schema -> tests/queries/test_java_procedures.py::test_java_inline_shim_and_pljava_classpath Existing UDF install/call + quoted inline sources -> tests/queries/test_functions.py::test_execute_java_function Existing UDF dollar-quoted inline sources -> tests/queries/test_functions.py::test_execute_java_function_dollar_quoted Existing UDF handler error identity -> tests/queries/test_functions.py::test_java_function_handler_error Existing UDF NULL input behavior -> tests/queries/test_functions.py::test_java_udf_null_input_handling Existing UDF missing external-access integration rejection -> tests/queries/test_udf_external_access.py::test_java_function_missing_integration Existing Java GET_DDL metadata + replay -> tests/queries/metadata/test_metadata_queries.py::test_get_ddl_java_function_replay Shared Java/Scala classpath freshness regression -> tests/queries/test_scala_functions.py::test_inline_scala_classpath_refresh Common TEMP/SECURE/COPY GRANTS/default/comment/null-handling/volatility headers -> unchanged shared opaque path; not newly implemented Java behaviors RETURNS TABLE/schema-backed DataFrames -> uncovered: deferred to LAV-4439 Staged IMPORTS/TARGET_PATH, anonymous procedures, telemetry execution -> uncovered: deferred to LAV-4440 OWNER/CALLER/RESTRICTED rights enforcement, secrets and external access execution -> uncovered: deferred to LAV-4441 and established external-access consumers; enclosing-statement rights retained per ADR 125 section 4 ## Deviations - Cloud procedure validation is CREATE-time, includes Session in arity, and differs from UDF error rendering. Omitted procedure runtime succeeds with NULL metadata; Snowpark 1.2.0 is accepted. Missing/empty/non-Snowpark packages produce javac missing-package errors, not required-property errors. Followed those captures instead of ADR placeholders. Java String -> SQL NUMBER also succeeds, while int -> VARCHAR is rejected; missing HANDLER has no header. - JDBC positional collection preserves duplicate columns directly, rather than copying Python's alias-renaming/materialization implementation. The Cloud-captured ordinal Row results agree. - Scalar procedures use the shared opaque lowering without a Java table marker. Table adaptation belongs to LAV-4439. A procedure-specific source generator shares wrapper hashing/compilation/installation while preserving the UDF's narrower marshalling contract. - Fresh connections initialize their generation on first lease, not manager construction: no JVM or schema loader exists until use. Warmed connections refresh through their private temp namespace as ratified. * LAV-4438: wip (budget exhausted: wall_clock) Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> * LAV-4438: investigator: finish candidate-loader validation and replay captured reviewer gaps Attempt 1 was killed by the shared wall-clock budget, not a blocked report: /var/lib/spiral/transcripts/driver.log records implementer 01:23:49-02:55:24, checks 02:55:24-03:50:27, reviewer gaps at 03:56:54, then a second implementer phase until killed at 04:22:10 UTC on October 14, 2026. The second transcript implementer-20261014T035655.435632084Z.jsonl shows serial Cloud captures, implementation changes, focused replay and corrections, not a repeated blocker. Branch status --porcelain was empty at 95332aa2a. adc5b5e13 implements the scalar runtime; 95332aa2a adds 504 procedure snapshot lines, Java/Scala failure regressions, header policy and scalar-conversion fixes. This is unfinished implementation, not a missing permission, prerequisite, or design decision. Resume from the saved WIP rather than rebuilding the feature or recapturing all baselines. Preserve recorded Cloud values and original UDF contract. The final completed focused replay (/tmp/lav4438-replay.log, 04:18:54 UTC; its output is also in the second transcript) reports 26 passed / 10 failed: - test_java_failed_create_preserves_procedure[class]: DID NOT RAISE. Other rollback cases passed. handlers/mod.rs now restores the classpath and generation on failure, but candidate validation still needs isolation: inspect its pg_temp validator and ProcedureHandler.resolve Class.forName for cached/shared-shim-loader resolution to the previous handler class. Validate the candidate artifact, not an older same-named class, then prove failed replacement leaves the old executable AND metadata intact. - Five procedure header-policy cases differ only in JAVA token position (expected 84/117/113/114/157; observed one less). Current java_policy.rs obtains locations from original SQL; rerun after rebuilding before editing further. Cloud rejects those clause orders, so do not force acceptance. - Fractional NUMBER int/long expected "1"/"-1" but replay returned 100046. Current ProcedureHandler already has setScale(0, HALF_UP); distinguish stale embedded shim/build state from a remaining conversion defect before changes. - New shared-handler UDF errors lacked the captured Java error-report prefix; duplicate Java UDF GET_DDL lacked its AS source. Current wrapper source now calls ProcedureHandler.errorReport and get_ddl.rs has an inline-source guard; both need replay, not snapshot rewriting. The transcript's last agent message (04:22:04) was: "Rollback passes most regressions. I'll isolate validation from older handler classes." The last command was inspection, not a completed fix. /tmp/lav4438-final-capture.log ends during setup; do not count it as a passing capture or replay. make check was green at 04:16:01 (/tmp/lav4438-check3.log), before the final edits; the driver check was explicitly unverified for diff-owned compat tests. Rebuild once, run focused existing nodeids serially, finish remaining failures, update the commit Test matrix and Deviations, then make check. Leave .spiral/check.sh to the driver rather than manually duplicating its long gate. All reviewer G1-G4 regression tests are already additive on this branch, including the new same-class pinned replacement test; no existing-test waiver is needed. Fetched origin/main during investigation: advanced d7b3e0309 -> d01b88ee9. Account for that upstream movement before the final gate; do not mistake reverse diff from newer trunk commits for task-authored deletions. No implementation files changed by the investigator; this note is the remedy. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> * LAV-4438: Finish Java runtime regression coverage and error parity Finish the saved reviewer-gap work rather than reimplementing it. Rebuilding and replaying proves that candidate validation in the private temp schema, classpath rollback on failed CREATE, complete-header policy scanning, scalar conversion boundaries, and warmed pinned same-class replacement now pass. Complete the pending serial Cloud captures for positive/negative half-integer rounding and the two distinct UDF error identities. These are additive tests relative to origin/main; no pre-existing Cloud snapshot value changes. Fix the extra newline between inline UDF stack frames and routine identity; the shim already supplies the trailing newline, unlike staged error reports. Keep the staged UDF report and procedure report byte-for-byte compatible. Swept the Java/Scala shared installation, failure restoration, wrapper identity, header scanning, and conversion paths for reviewer G1-G4 patterns. Earlier WIP fixes cover procedure validation/compile/duplicate failures and duplicate Java and Scala UDF failures; metadata remains success-only. Also fixed the inline UDF report separator; clean in staged UDF and procedure report call sites. Header properties are token-scanned only after the signature and before AS, including comments, quoted source, dollar-quoted source and identifier tokens. Verification: - make check (including changed vendored sqlparser tests). - make test-compat across test_java_procedures.py, test_functions.py, test_scala_functions.py, metadata/test_metadata_queries.py, and test_udf_external_access.py with -k 'java or scala': 178 passed. - Focused error-report/API replay: 11 passed. - Focused failure/header/conversion/pinned replacement replay: 37 passed. - No .spiral/check.sh run here; the driver owns that gate. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Test matrix Denominator: ACs plus scripts/resource_index.py show 'CREATE PROCEDURE'. Existing non-Java clauses retain the shared opaque procedure lowering; this slice implements scalar inline Java, not the deferred sibling surfaces. Inline scalar CREATE/CALL + Session.sql/table, collect, Row ordinals, NULL/empty/numerics/duplicate names -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Scalar return NULL/empty/value -> tests/queries/test_java_procedures.py::test_java_procedure_null_result Nested CALL/EXECUTE IMMEDIATE + successful source replacement -> tests/queries/test_java_procedures.py::test_java_procedure_replacement_and_dynamic_call Session.table + invoking transaction visibility/rollback -> tests/queries/test_java_procedures.py::test_java_procedure_transaction_visibility Runtime/handler/packages/execute-as DESCRIBE and GET_DDL -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Instance handler + int argument -> tests/queries/test_java_procedures.py::test_java_procedure_handler[instance] Handler arity failure -> tests/queries/test_java_procedures.py::test_java_procedure_handler[arity] Handler argument-type failure -> tests/queries/test_java_procedures.py::test_java_procedure_handler[type] Missing handler method -> tests/queries/test_java_procedures.py::test_java_procedure_handler[method] Missing handler class -> tests/queries/test_java_procedures.py::test_java_procedure_handler[class] Missing HANDLER property -> tests/queries/test_java_procedures.py::test_java_procedure_missing_handler Procedure shared-handler error identity -> tests/queries/test_java_procedures.py::test_java_procedure_invocation_identity UDF shared-handler/signature + distinct first/second/first error identities -> tests/queries/test_functions.py::test_java_udfs_shared_handler_identity String-return NUMBER conversion/int-return VARCHAR rejection -> tests/queries/test_java_procedures.py::test_java_procedure_return_type Failed replacement arity + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_java_procedures.py::test_java_failed_create_preserves_procedure[arity] Failed replacement class resolution + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_java_procedures.py::test_java_failed_create_preserves_procedure[class] Failed replacement compilation + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_java_procedures.py::test_java_failed_create_preserves_procedure[compile] Duplicate procedure CREATE + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_java_procedures.py::test_java_failed_create_preserves_procedure[duplicate] Duplicate Java UDF CREATE + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_functions.py::test_java_duplicate_create_preserves_function Duplicate Scala UDF CREATE + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_scala_functions.py::test_scala_duplicate_create_preserves_function Procedure/UDF runtime 11/17/21 accepted + normalized metadata -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure/UDF runtime omitted + NULL metadata -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure/UDF runtime unquoted 11 + normalized metadata -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure/UDF runtime empty/abc/8/22/11.0.0 + exact errors -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure Snowpark omitted/empty/non-Snowpark-only + captured compile errors -> tests/queries/test_java_procedures.py::test_java_package_policy UDF packages omitted/empty/non-Snowpark-only + accepted -> tests/queries/test_java_procedures.py::test_java_package_policy Procedure/UDF supported Snowpark 1.2.0/latest + normalized metadata -> tests/queries/test_java_procedures.py::test_java_package_policy Procedure/UDF invalid/versionless/0.0.0 coordinate + captured rejection -> tests/queries/test_java_procedures.py::test_java_package_policy Procedure/UDF unsupported served package + exact emulator-only rejection -> tests/queries/test_java_procedures.py::test_java_unemulated_package Procedure/UDF synthetic Snowpark version + single-JVM divergence -> tests/queries/test_java_procedures.py::test_java_emulated_packages Procedure/UDF missing handler + invalid runtime precedence -> tests/queries/test_java_procedures.py::test_java_policy_without_handler Procedure/UDF missing handler + invalid package precedence -> tests/queries/test_java_procedures.py::test_java_policy_without_handler Procedure/UDF runtime before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF packages before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF handler and invalid runtime before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF handler and invalid package before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF handler and valid properties before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF properties in SQL string literals/comments/body/identifiers + no false match -> tests/queries/test_java_procedures.py::test_java_header_policy_lexical_islands Java UDF dollar-quoted source, quotes and escapes + compilation -> tests/queries/test_functions.py::test_execute_java_function_dollar_quoted NUMBER/int NULL + exact primitive error -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int-NULL] NUMBER/long NULL + exact primitive error -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long-NULL] FLOAT/double NULL + exact primitive error -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[FLOAT-double-NULL] BOOLEAN/boolean NULL + exact primitive error -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[BOOLEAN-boolean-NULL] FLOAT/Double NULL + nullable result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[FLOAT-Double-NULL] FLOAT/Double 1.25 + value result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[FLOAT-Double-1.25] BOOLEAN/Boolean NULL + nullable result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[BOOLEAN-Boolean-NULL] BOOLEAN/Boolean TRUE + true result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[BOOLEAN-Boolean-TRUE] BOOLEAN/Boolean FALSE + false result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[BOOLEAN-Boolean-FALSE] NUMBER(38,2)/int fractional + CREATE-time scale rejection -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER(38,2)-int-1.25] NUMBER(38,2)/long fractional + CREATE-time scale rejection -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER(38,2)-long--1.25] NUMBER/int fractional 1.25 + rounded result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int-1.25] NUMBER/long fractional -1.25 + rounded result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long--1.25] NUMBER/int 0.5 + half-away-from-zero result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int-0.5] NUMBER/int -0.5 + half-away-from-zero result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int--0.5] NUMBER/long 0.5 + half-away-from-zero result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long-0.5] NUMBER/long -0.5 + half-away-from-zero result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long--0.5] NUMBER/int MAX+1 + exact overflow -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int-2147483648] NUMBER/int MIN-1 + exact overflow -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int--2147483649] NUMBER/long MAX+1 + exact overflow -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long-9223372036854775808] NUMBER/long MIN-1 + exact overflow -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long--9223372036854775809] Scalar String/int/Integer/long/Long/BigDecimal/double/boolean + extrema/NULL -> tests/queries/test_java_procedures.py::test_java_procedure_scalar_arguments Warmed transaction-pinned procedure backend + new installation -> tests/queries/test_java_procedures.py::test_java_procedure_visible_to_pinned_second_session[PROCEDURE] Warmed transaction-pinned UDF backend + new installation -> tests/queries/test_java_procedures.py::test_java_procedure_visible_to_pinned_second_session[FUNCTION] Warmed transaction-pinned procedure backend + same-class replacement -> tests/queries/test_java_procedures.py::test_java_same_class_replacement_pinned[PROCEDURE] Warmed transaction-pinned UDF backend + same-class replacement -> tests/queries/test_java_procedures.py::test_java_same_class_replacement_pinned[FUNCTION] Session.builder/jdbcConnection/createAsyncJob + exact emulator-only errors -> tests/queries/test_java_procedures.py::test_java_procedure_unsupported_api DataFrame.async/select/filter/limit + exact emulator-only errors -> tests/queries/test_java_procedures.py::test_java_procedure_unsupported_api Worker-thread Session submission + exact emulator-only rejection -> tests/queries/test_java_procedures.py::test_java_procedure_worker_thread Inline shim/PLJava compilation + shared schema installation -> tests/queries/test_java_procedures.py::test_java_inline_shim_and_pljava_classpath Existing staged Java handler error rendering -> tests/queries/test_functions.py::test_java_function_handler_error Existing Java UDF NULL compatibility -> tests/queries/test_functions.py::test_java_udf_null_input_handling Existing Java GET_DDL replay -> tests/queries/metadata/test_metadata_queries.py::test_get_ddl_java_function_replay Existing external access CREATE validation -> tests/queries/test_udf_external_access.py::test_java_function_missing_integration Existing Java/Scala shared classpath refresh -> tests/queries/test_scala_functions.py::test_inline_scala_classpath_refresh RETURNS TABLE/schema-backed DataFrames -> uncovered: deferred to LAV-4439 Staged/anonymous procedures, telemetry execution -> uncovered: deferred to LAV-4440 OWNER/CALLER/RESTRICTED rights enforcement -> uncovered: deferred to LAV-4441; enclosing-statement rights retained ## Deviations - Cloud validates procedure handlers at CREATE, counts Session in arity, and renders errors differently from UDFs. Omitted runtime succeeds with NULL metadata, Snowpark 1.2.0 is accepted, and missing/empty/non-Snowpark packages yield captured compilation errors. Followed captures rather than ADR placeholders. String-to-NUMBER returns succeed; int-to-VARCHAR is rejected. - Cloud rejects runtime/package/handler properties before LANGUAGE JAVA with a syntax error at JAVA, including otherwise-valid properties. Enforce those captured rejections rather than accepting those reordered headers. - NULL primitives are rejected, boxed Double/Boolean accept NULL, nonzero declared numeric scale mapped to int/long fails at CREATE, and scale-zero fractional values round half away from zero. Overflow reports 100046/22003. - Positional JDBC collection preserves duplicate columns without Python's alias-renaming/materialization. Scalar procedures use a dedicated wrapper generator sharing hashing/compilation/installation, preserving UDF marshalling. - Fresh connections initialize generation on first lease; warmed connections refresh through their private temp namespace. Failed CREATE restores the classpath and advances generation, not per-routine isolation; concurrent invocations during CREATE can observe its temporary classpath as documented. * LAV-4438: Publish JVM artifacts atomically and refresh under privileged identity Keep candidate Java/Scala jars out of routine-schema classpaths during compile and private procedure-handler validation. Scope their pending bindings to the CREATE task and publish them with the backing routine through the existing privileged DDL transaction. Failure rolls back publication rather than exposing then restoring rejected classes. Advance generation after the execution attempt so creating-backend caches are invalidated after rollback as well as commit. Reset the previous projected PostgreSQL role before backend-private Java loader maintenance. Normal statement setup reapplies the current Snowflake role. Add five serial Cloud-captured regressions. Emulator replay deterministically holds pg_proc's write lock until a concurrent reader has invoked the old source: procedure validation failure, duplicate procedure CREATE, and duplicate Java and Scala UDF CREATE. A warmed pinned backend switches to a custom role, executes ordinary SQL across another session's Java installation, and retains its role. The lock holder is armed before submission; a deadline and an in-flight check make an absent publication barrier fail. No test-only production hooks were added. Swept inline procedures, inline Java/Scala UDFs, staged Java imports, wrappers, Snowpark shim and Scala-library installation for premature target-classpath publication; all routine artifacts now use the deferred shared installation path. Compiler/bootstrap bindings remain immediate and separate. Swept both refresh callers (statement setup and pool acquisition) for retained-role maintenance; the shared refresh helper resets identity for both. Legacy wire-context loader consolidation remains deferred to LAV-4471. Remove the previously documented temporary-publication caveat from ADR 125; that deviation is withdrawn, not an accepted concurrency limitation. Verification: - make check: passed, including formatting, warnings, clippy and parser tests. - Serial Cloud capture: five new regressions passed; existing values unchanged. - Full focused Java/Scala replay across procedure, UDF, Scala marshalling, metadata and external-access files: 227 passed, 9 existing xfailed. - Final deterministic regression replay: five passed. - git diff --check: passed. The driver retains ownership of .spiral/check.sh. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Test matrix Denominator: ACs plus scripts/resource_index.py show 'CREATE PROCEDURE'. Existing non-Java clauses retain the shared opaque procedure lowering; this slice implements scalar inline Java, not the deferred sibling surfaces. Inline scalar CREATE/CALL + Session.sql/table, collect, Row ordinals, NULL/empty/numerics/duplicate names -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Scalar return NULL/empty/value -> tests/queries/test_java_procedures.py::test_java_procedure_null_result Nested CALL/EXECUTE IMMEDIATE + successful source replacement -> tests/queries/test_java_procedures.py::test_java_procedure_replacement_and_dynamic_call Session.table + invoking transaction visibility/rollback -> tests/queries/test_java_procedures.py::test_java_procedure_transaction_visibility Runtime/handler/packages/execute-as DESCRIBE and GET_DDL -> tests/queries/test_java_procedures.py::test_java_procedure_session_table Instance handler + int argument -> tests/queries/test_java_procedures.py::test_java_procedure_handler[instance] Handler arity failure -> tests/queries/test_java_procedures.py::test_java_procedure_handler[arity] Handler argument-type failure -> tests/queries/test_java_procedures.py::test_java_procedure_handler[type] Missing handler method -> tests/queries/test_java_procedures.py::test_java_procedure_handler[method] Missing handler class -> tests/queries/test_java_procedures.py::test_java_procedure_handler[class] Missing HANDLER property -> tests/queries/test_java_procedures.py::test_java_procedure_missing_handler Procedure shared-handler error identity -> tests/queries/test_java_procedures.py::test_java_procedure_invocation_identity UDF shared-handler/signature + distinct first/second/first error identities -> tests/queries/test_functions.py::test_java_udfs_shared_handler_identity String-return NUMBER conversion/int-return VARCHAR rejection -> tests/queries/test_java_procedures.py::test_java_procedure_return_type Failed replacement arity + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_java_procedures.py::test_java_failed_create_preserves_procedure[arity] Failed replacement class resolution + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_java_procedures.py::test_java_failed_create_preserves_procedure[class] Failed replacement compilation + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_java_procedures.py::test_java_failed_create_preserves_procedure[compile] Duplicate procedure CREATE + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_java_procedures.py::test_java_failed_create_preserves_procedure[duplicate] Duplicate Java UDF CREATE + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_functions.py::test_java_duplicate_create_preserves_function Duplicate Scala UDF CREATE + previous executable, DESCRIBE, GET_DDL -> tests/queries/test_scala_functions.py::test_scala_duplicate_create_preserves_function Procedure/UDF runtime 11/17/21 accepted + normalized metadata -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure/UDF runtime omitted + NULL metadata -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure/UDF runtime unquoted 11 + normalized metadata -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure/UDF runtime empty/abc/8/22/11.0.0 + exact errors -> tests/queries/test_java_procedures.py::test_java_runtime_policy Procedure Snowpark omitted/empty/non-Snowpark-only + captured compile errors -> tests/queries/test_java_procedures.py::test_java_package_policy UDF packages omitted/empty/non-Snowpark-only + accepted -> tests/queries/test_java_procedures.py::test_java_package_policy Procedure/UDF supported Snowpark 1.2.0/latest + normalized metadata -> tests/queries/test_java_procedures.py::test_java_package_policy Procedure/UDF invalid/versionless/0.0.0 coordinate + captured rejection -> tests/queries/test_java_procedures.py::test_java_package_policy Procedure/UDF unsupported served package + exact emulator-only rejection -> tests/queries/test_java_procedures.py::test_java_unemulated_package Procedure/UDF synthetic Snowpark version + single-JVM divergence -> tests/queries/test_java_procedures.py::test_java_emulated_packages Procedure/UDF missing handler + invalid runtime precedence -> tests/queries/test_java_procedures.py::test_java_policy_without_handler Procedure/UDF missing handler + invalid package precedence -> tests/queries/test_java_procedures.py::test_java_policy_without_handler Procedure/UDF runtime before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF packages before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF handler and invalid runtime before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF handler and invalid package before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF handler and valid properties before LANGUAGE + Cloud syntax rejection -> tests/queries/test_java_procedures.py::test_java_header_policy_order Procedure/UDF properties in SQL string literals/comments/body/identifiers + no false match -> tests/queries/test_java_procedures.py::test_java_header_policy_lexical_islands Java UDF dollar-quoted source, quotes and escapes + compilation -> tests/queries/test_functions.py::test_execute_java_function_dollar_quoted NUMBER/int NULL + exact primitive error -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int-NULL] NUMBER/long NULL + exact primitive error -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long-NULL] FLOAT/double NULL + exact primitive error -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[FLOAT-double-NULL] BOOLEAN/boolean NULL + exact primitive error -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[BOOLEAN-boolean-NULL] FLOAT/Double NULL + nullable result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[FLOAT-Double-NULL] FLOAT/Double 1.25 + value result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[FLOAT-Double-1.25] BOOLEAN/Boolean NULL + nullable result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[BOOLEAN-Boolean-NULL] BOOLEAN/Boolean TRUE + true result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[BOOLEAN-Boolean-TRUE] BOOLEAN/Boolean FALSE + false result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[BOOLEAN-Boolean-FALSE] NUMBER(38,2)/int fractional + CREATE-time scale rejection -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER(38,2)-int-1.25] NUMBER(38,2)/long fractional + CREATE-time scale rejection -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER(38,2)-long--1.25] NUMBER/int fractional 1.25 + rounded result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int-1.25] NUMBER/long fractional -1.25 + rounded result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long--1.25] NUMBER/int 0.5 + half-away-from-zero result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int-0.5] NUMBER/int -0.5 + half-away-from-zero result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int--0.5] NUMBER/long 0.5 + half-away-from-zero result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long-0.5] NUMBER/long -0.5 + half-away-from-zero result -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long--0.5] NUMBER/int MAX+1 + exact overflow -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int-2147483648] NUMBER/int MIN-1 + exact overflow -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-int--2147483649] NUMBER/long MAX+1 + exact overflow -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long-9223372036854775808] NUMBER/long MIN-1 + exact overflow -> tests/queries/test_java_procedures.py::test_java_procedure_argument_boundaries[NUMBER-long--9223372036854775809] Scalar String/int/Integer/long/Long/BigDecimal/double/boolean + extrema/NULL -> tests/queries/test_java_procedures.py::test_java_procedure_scalar_arguments Warmed transaction-pinned procedure backend + new installation -> tests/queries/test_java_procedures.py::test_java_procedure_visible_to_pinned_second_session[PROCEDURE] Warmed transaction-pinned UDF backend + new installation -> tests/queries/test_java_procedures.py::test_java_procedure_visible_to_pinned_second_session[FUNCTION] Warmed transaction-pinned procedure backend + same-class replacement -> tests/queries/test_java_procedures.py::test_java_same_class_replacement_pinned[PROCEDURE] Warmed transaction-pinned UDF backend + same-class replacement -> tests/queries/test_java_procedures.py::test_java_same_class_replacement_pinned[FUNCTION] Session.builder/jdbcConnection/createAsyncJob + exact emulator-only errors -> tests/queries/test_java_procedures.py::test_java_procedure_unsupported_api DataFrame.async/select/filter/limit + exact emulator-only errors -> tests/queries/test_java_procedures.py::test_java_procedure_unsupported_api Worker-thread Session submission + exact emulator-only rejection -> tests/queries/test_java_procedures.py::test_java_procedure_worker_thread Inline shim/PLJava compilation + shared schema installation -> tests/queries/test_java_procedures.py::test_java_inline_shim_and_pljava_classpath Existing staged Java handler error rendering -> tests/queries/test_functions.py::test_java_function_handler_error Existing Java UDF NULL compatibility -> tests/queries/test_functions.py::test_java_udf_null_input_handling Existing Java GET_DDL replay -> tests/queries/metadata/test_metadata_queries.py::test_get_ddl_java_function_replay Existing external access CREATE validation -> tests/queries/test_udf_external_access.py::test_java_function_missing_integration Existing Java/Scala shared classpath refresh -> tests/queries/test_scala_functions.py::test_inline_scala_classpath_refresh RETURNS TABLE/schema-backed DataFrames -> uncovered: deferred to LAV-4439 Staged/anonymous procedures, telemetry execution -> uncovered: deferred to LAV-4440 OWNER/CALLER/RESTRICTED rights enforcement -> uncovered: deferred to LAV-4441; enclosing-statement rights retained Main scalar JVM marshalling integration matrix (existing Cloud captures): VARCHAR/NUMBER/DOUBLE/BOOLEAN/BINARY input conversion × accepted/error/boundary -> tests/queries/test_scala_marshalling.py::test_scala_scalar_conversion_matrix Scalar NULL input × result/error -> tests/queries/test_scala_marshalling.py::test_scala_scalar_null_matrix NUMBER(9/18/38,0) Int/Long × accepted/overflow -> tests/queries/test_scala_marshalling.py::test_scala_numeric_signature_width Int/Long extrema × overflow -> tests/queries/test_scala_marshalling.py::test_scala_jvm_numeric_limits Mixed argument positions × conversion -> tests/queries/test_scala_marshalling.py::test_scala_mixed_positions Scalar no-argument result × accepted/error -> tests/queries/test_scala_marshalling.py::test_scala_no_argument_results Scalar table-column transport × accepted/error -> tests/queries/test_scala_marshalling.py::test_scala_row_transport NUMBER precision 2/4 × accepted/input overflow/result overflow -> tests/queries/test_scala_marshalling.py::test_scala_small_declared_widths SQL and Java overload resolution × accepted/error -> tests/queries/test_scala_marshalling.py::test_scala_sql_overloads; tests/queries/test_scala_marshalling.py::test_scala_java_overloads Composed column/CASE/expression/UDF results × signature selection -> tests/queries/test_scala_marshalling.py::test_scala_overload_expression_results; tests/queries/test_scala_marshalling.py::test_scala_overload_composed_expression_results; tests/queries/test_scala_marshalling.py::test_scala_double_overload_composed_expression_results Existing xfailed nullable primitive/overload cells -> unchanged upstream xfails; not modified by this round Java procedure failed arity replacement x warm/concurrent/post-failure original source and verbatim error -> tests/queries/test_java_procedures.py::test_java_rejected_candidate_concurrent_invocation[PROCEDURE-JAVA-arity] Java duplicate procedure CREATE x warm/concurrent/post-failure original source and verbatim error -> tests/queries/test_java_procedures.py::test_java_rejected_candidate_concurrent_invocation[PROCEDURE-JAVA-duplicate] Java duplicate UDF CREATE x warm/concurrent/post-failure original source and verbatim error -> tests/queries/test_java_procedures.py::test_java_rejected_candidate_concurrent_invocation[FUNCTION-JAVA-duplicate] Scala duplicate UDF CREATE x warm/concurrent/post-failure original source and verbatim error -> tests/queries/test_java_procedures.py::test_java_rejected_candidate_concurrent_invocation[FUNCTION-SCALA-duplicate] Warmed transaction-pinned backend/custom role x ordinary SQL across installation, role preservation and subsequent Java invocation -> tests/queries/test_java_procedures.py::test_java_loader_refresh_pinned_custom_role * LAV-4438: investigator: integrate new Scala validation without publishing candidates The October 14 second exhaustion is not a repeat of the first unfinished candidate-validation/error-parity blocker. Recovered dispatch-1 transcripts show those fixes completed in 77ffd4528, followed by a distinct reviewer round at 05:58 and atomic-publication/privileged-refresh fixes in 497bb2ba6. The final implementer transcript (implementer-20261014T062850.971132954Z.jsonl) records make check and completed focused replays: 243 passed, 9 existing xfailed, plus 35 Java UDF/metadata/external-access tests passed at 06:46:31. The preceding transcript records the final five concurrency/custom-role regressions passing at 06:28:43. The tree is clean at b4cb8bb0a. Read-only inspection confirms deferred task-local candidate classpaths, publication with routine DDL, pg_temp procedure validation, and RESET ROLE before loader maintenance. driver.log records a successful final driver check at 07:41:52 followed by wall_clock exhaustion at 07:41:56 before another reviewer could run. checks.log explicitly marks Ubuntu compat unverified after its 1040-second budget and skips diff-owned/full compat, snapshot lane and scripts tests; do not call this a full test pass. Post-clear implementer commands did not manually run .spiral/check.sh. The previous no-duplicate-gate instruction was followed; this is not a proposal to repeat that remedy or override dispatch/spend limits. New actionable integration since the last completed merge: origin/main now contains 998400a8a (LAV-4078, PR #4655) and f25c2c554. Read-only git merge-tree --write-tree HEAD origin/main reports conflicts only in crates/server/src/handlers/mod.rs and crates/server/src/java_udf.rs. Merge current main before editing; combine its Scala error/handler-validation behavior with this branch, rather than selecting ours/theirs wholesale. Retain wrapper identity derived from all generated source (including Scala version/error rendering and inline-vs-staged differences), existing Java diagnostics and contracts, privileged loader refresh, and deferred atomic publication. Main adds Scala validation against the target-schema classpath in install_java_error_wrapper, but this branch deliberately withholds candidate bindings until DDL. Adapt that validation to an isolated candidate context, using the existing pg_temp procedure-validator pattern at handlers/mod.rs:3482 as the local precedent; include Scala-library and required shim jars. Do not fix resolution by temporarily publishing candidates to the live schema, which reopens reviewer G1. Bounded next round: resume the saved implementation, integrate this new Scala-validation collision, then run make check and focused existing Java procedure/UDF/Scala regressions, including main-added test_scala_errors.py, concurrent rejected-candidate tests, pinned custom-role refresh and same-class replacement tests. Do not recapture existing Cloud baselines or rebuild/reimplement completed feature work. Record integration evidence and hand off promptly to the normal driver gate and reviewer; never invoke .spiral/check.sh manually or bypass its stages. No new waiver, external dependency or design decision was identified. This note is integration guidance, not implementation or approval of the remaining reviewer gate. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> --------- Co-authored-by: spiral <spiral@localhost> Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud>
1 parent 722c64e commit ee952ef

1 file changed

Lines changed: 5 additions & 1 deletion

File tree

‎src/parser/mod.rs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7261,7 +7261,11 @@ impl<'a> Parser<'a> {
72617261
self.expect_token(&Token::Eq)?;
72627262
let value = if self.peek_token_ref().token == Token::LParen {
72637263
self.expect_token(&Token::LParen)?;
7264-
let values = self.parse_comma_separated(Parser::parse_expr)?;
7264+
let values = if self.peek_token_ref().token == Token::RParen {
7265+
Vec::new()
7266+
} else {
7267+
self.parse_comma_separated(Parser::parse_expr)?
7268+
};
72657269
self.expect_token(&Token::RParen)?;
72667270
Expr::Tuple(values)
72677271
} else {

0 commit comments

Comments
 (0)