@@ -306,3 +306,106 @@ async fn extended_query_oid_mismatch_text_for_int_errors_or_coerces() {
306306 }
307307 }
308308}
309+
310+ // ── Cross-engine: issue #216 SQLSTATEs through the extended protocol ────────
311+
312+ /// Undefined-function rejection (issue #216, SQLSTATE `42883`) through the
313+ /// extended-query path. `sql_undefined_function.rs` proves this fires at
314+ /// PLAN time over simple-query (where Parse+Bind+Execute collapse into one
315+ /// round trip). Naively, `pgwire_database_authorization.rs`'s
316+ /// `prepared_parse_denies_*` tests suggest Parse/Describe is where this
317+ /// server plans a statement (authorization and undefined-table checks both
318+ /// reject there) — but confirmed empirically here, `.prepare()` for this
319+ /// statement actually *succeeds*: the function-registry existence gate is
320+ /// not consulted during Parse/Describe (no columns/params to resolve for
321+ /// this shape rules it out early), so the error is only raised once
322+ /// `.query()` drives Bind+Execute and the plan is actually built. Asserting
323+ /// on the real surfacing point, not the naive assumption.
324+ #[ tokio:: test]
325+ async fn extended_query_unknown_function_errors_42883 ( ) {
326+ let srv = TestServer :: start ( ) . await ;
327+
328+ let stmt = srv
329+ . client
330+ . prepare ( "SELECT some_function_that_does_not_exist_216(1, 2)" )
331+ . await
332+ . expect (
333+ "Parse/Describe succeeds for this statement shape — the \
334+ undefined-function gate is not consulted until Bind/Execute",
335+ ) ;
336+
337+ let err = srv
338+ . client
339+ . query ( & stmt, & [ ] )
340+ . await
341+ . expect_err ( "Execute must reject a call to an unregistered scalar function" ) ;
342+ let db_err = err
343+ . as_db_error ( )
344+ . expect ( "server must return a typed SQLSTATE for the unknown function" ) ;
345+ assert_eq ! (
346+ db_err. code( ) . code( ) ,
347+ "42883" ,
348+ "unknown-function rejection must carry SQLSTATE 42883, got {}" ,
349+ db_err. code( ) . code( )
350+ ) ;
351+ }
352+
353+ /// Division-by-zero rejection (issue #216, SQLSTATE `22012`) through the
354+ /// extended-query path. Unlike the undefined-function gate above, `10 / d`
355+ /// is a well-typed expression regardless of `d`'s runtime value, so
356+ /// Parse/Describe must succeed — the zero divisor can only be discovered
357+ /// once a bound row is actually evaluated, i.e. at Bind/Execute
358+ /// (`.query()`) time. Mirrors `sql_division_by_zero.rs`'s simple-query
359+ /// coverage of the same fix, through Parse/Bind/Execute instead.
360+ #[ tokio:: test]
361+ async fn extended_query_division_by_zero_errors_22012_at_execute ( ) {
362+ let srv = TestServer :: start ( ) . await ;
363+ srv. exec (
364+ "CREATE COLLECTION ext_divzero_216 (id STRING PRIMARY KEY, d INT) \
365+ WITH (engine='document_strict')",
366+ )
367+ . await
368+ . expect ( "CREATE ext_divzero_216" ) ;
369+ srv. exec ( "INSERT INTO ext_divzero_216 (id, d) VALUES ('nonzero', 2)" )
370+ . await
371+ . expect ( "INSERT nonzero row" ) ;
372+ srv. exec ( "INSERT INTO ext_divzero_216 (id, d) VALUES ('zero', 0)" )
373+ . await
374+ . expect ( "INSERT zero-divisor row" ) ;
375+
376+ let stmt = srv
377+ . client
378+ . prepare_typed (
379+ "SELECT 10 / d AS ratio FROM ext_divzero_216 WHERE id = $1" ,
380+ & [ Type :: TEXT ] ,
381+ )
382+ . await
383+ . expect (
384+ "Parse must succeed — `10 / d` is well-typed independent of d's runtime value" ,
385+ ) ;
386+
387+ let err = srv
388+ . client
389+ . query ( & stmt, & [ & "zero" ] )
390+ . await
391+ . expect_err ( "Execute over the zero-divisor row must fail the statement" ) ;
392+ let db_err = err
393+ . as_db_error ( )
394+ . expect ( "server must return a typed SQLSTATE for division by zero" ) ;
395+ assert_eq ! (
396+ db_err. code( ) . code( ) ,
397+ "22012" ,
398+ "zero-divisor Execute-time rejection must carry SQLSTATE 22012, got {}" ,
399+ db_err. code( ) . code( )
400+ ) ;
401+
402+ // Control: the identical prepared statement, bound against the
403+ // non-zero row, must still succeed — the fix must not turn division
404+ // itself into a blanket error on this path.
405+ let rows = srv
406+ . client
407+ . query ( & stmt, & [ & "nonzero" ] )
408+ . await
409+ . expect ( "Execute over the non-zero-divisor row must still succeed" ) ;
410+ assert_eq ! ( rows. len( ) , 1 , "expected exactly 1 row for the nonzero id" ) ;
411+ }
0 commit comments