mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Resolve untyped parameters in FOR PORTION OF FROM/TO bounds
FOR PORTION OF valid_at FROM $1 TO $2 fails with "could not determine data type of parameter $1", so using PREPARE requires users to say $1::date etc. But transformForPortionOfClause() already coerces the bounds, since it builds a constructor for the targeted range. We just need to store the coerced expressions in targetFrom and targetTo. This also improves pg_get_ruledef(), which now renders an untyped NULL bound as NULL::date instead of NULL::unknown. Reported-by: Noah Misch <noah@leadboat.com> Author: Paul A. Jungwirth <pj@illuminatedcomputing.com> Discussion: https://www.postgresql.org/message-id/flat/CA%2BrenyW1uJp1dw%3DiK7MsWfbbZf-dKP_4JLoQ65wj%3Dfry35FjCw%40mail.gmail.com
This commit is contained in:
@@ -1465,8 +1465,7 @@ transformForPortionOfClause(ParseState *pstate,
|
||||
EXPR_KIND_FOR_PORTION);
|
||||
actual_arg_types[0] = exprType(result->targetFrom);
|
||||
actual_arg_types[1] = exprType(result->targetTo);
|
||||
args = list_make2(copyObject(result->targetFrom),
|
||||
copyObject(result->targetTo));
|
||||
args = list_make2(result->targetFrom, result->targetTo);
|
||||
|
||||
/*
|
||||
* Check the bound types separately, for better error message and
|
||||
@@ -1490,6 +1489,15 @@ transformForPortionOfClause(ParseState *pstate,
|
||||
parser_errposition(pstate, exprLocation(forPortionOf->target_end))));
|
||||
|
||||
make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
|
||||
|
||||
/*
|
||||
* Keep the *coerced* bounds. This lets prepared statements use
|
||||
* parameters without explicit casts, and it improves deparsing when
|
||||
* FOR PORTION OF appears in a function or RULE.
|
||||
*/
|
||||
result->targetFrom = copyObject((Node *) linitial(args));
|
||||
result->targetTo = copyObject((Node *) lsecond(args));
|
||||
|
||||
result->targetRange = (Node *) makeFuncExpr(get_range_constructor2(attbasetype),
|
||||
attbasetype,
|
||||
args,
|
||||
|
||||
@@ -652,7 +652,7 @@ END;
|
||||
RETURNS text
|
||||
LANGUAGE sql
|
||||
1 BEGIN ATOMIC
|
||||
2 UPDATE for_portion_of_test FOR PORTION OF valid_at FROM '2018-01-15' TO '2019-01-01' SET name = 'one^1'::text
|
||||
2 UPDATE for_portion_of_test FOR PORTION OF valid_at FROM '2018-01-15'::date TO '2019-01-01'::date SET name = 'one^1'::text
|
||||
3 RETURNING for_portion_of_test.name;
|
||||
4 END
|
||||
CREATE OR REPLACE function fpo_update()
|
||||
@@ -1029,7 +1029,7 @@ END;
|
||||
RETURNS text
|
||||
LANGUAGE sql
|
||||
1 BEGIN ATOMIC
|
||||
2 DELETE FROM for_portion_of_test FOR PORTION OF valid_at FROM '2018-01-15' TO '2019-01-01'
|
||||
2 DELETE FROM for_portion_of_test FOR PORTION OF valid_at FROM '2018-01-15'::date TO '2019-01-01'::date
|
||||
3 RETURNING for_portion_of_test.name;
|
||||
4 END
|
||||
CREATE OR REPLACE function fpo_delete()
|
||||
@@ -2793,4 +2793,106 @@ SELECT * FROM fpo_rls ORDER BY valid_at;
|
||||
|
||||
DROP TABLE fpo_rls;
|
||||
DROP ROLE regress_fpo_rls;
|
||||
--
|
||||
-- Parameters in the FOR PORTION OF bounds
|
||||
--
|
||||
CREATE TABLE fpo_param (
|
||||
id int4range,
|
||||
valid_at daterange,
|
||||
name text
|
||||
);
|
||||
INSERT INTO fpo_param (id, valid_at, name) VALUES
|
||||
('[1,2)', daterange('2000-01-01', '2010-01-01'), 'one');
|
||||
-- A parameter of unspecified type in an ordinary expression gets its type
|
||||
-- resolved from context. This is the control case for the FROM/TO bounds
|
||||
-- below: it builds exactly the same daterange the FROM/TO form does.
|
||||
PREPARE fpo_param_control AS
|
||||
UPDATE fpo_param SET name = 'ctl' WHERE valid_at && daterange($1, $2);
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_control';
|
||||
parameter_types
|
||||
-----------------
|
||||
{date,date}
|
||||
(1 row)
|
||||
|
||||
-- The (portion) form resolves the parameter type from the range column.
|
||||
PREPARE fpo_param_portion AS
|
||||
UPDATE fpo_param FOR PORTION OF valid_at ($1) SET name = 'portion';
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_portion';
|
||||
parameter_types
|
||||
-----------------
|
||||
{daterange}
|
||||
(1 row)
|
||||
|
||||
-- The FROM/TO form should likewise resolve its bounds to the range's
|
||||
-- subtype, so that clients need not spell out the parameter types.
|
||||
PREPARE fpo_param_update AS
|
||||
UPDATE fpo_param FOR PORTION OF valid_at FROM $1 TO $2 SET name = 'upd';
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_update';
|
||||
parameter_types
|
||||
-----------------
|
||||
{date,date}
|
||||
(1 row)
|
||||
|
||||
PREPARE fpo_param_delete AS
|
||||
DELETE FROM fpo_param FOR PORTION OF valid_at FROM $1 TO $2;
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_delete';
|
||||
parameter_types
|
||||
-----------------
|
||||
{date,date}
|
||||
(1 row)
|
||||
|
||||
-- Only one bound parameterized.
|
||||
PREPARE fpo_param_one AS
|
||||
UPDATE fpo_param FOR PORTION OF valid_at FROM $1 TO '2003-01-01'
|
||||
SET name = 'one-bound';
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_one';
|
||||
parameter_types
|
||||
-----------------
|
||||
{date}
|
||||
(1 row)
|
||||
|
||||
-- A parameter used as both bounds must still resolve to one type.
|
||||
PREPARE fpo_param_same AS
|
||||
UPDATE fpo_param FOR PORTION OF valid_at FROM $1 TO $1 SET name = 'same';
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_same';
|
||||
parameter_types
|
||||
-----------------
|
||||
{date}
|
||||
(1 row)
|
||||
|
||||
EXECUTE fpo_param_update('2002-01-01', '2003-01-01');
|
||||
SELECT * FROM fpo_param ORDER BY valid_at;
|
||||
id | valid_at | name
|
||||
-------+-------------------------+------
|
||||
[1,2) | [2000-01-01,2002-01-01) | one
|
||||
[1,2) | [2002-01-01,2003-01-01) | upd
|
||||
[1,2) | [2003-01-01,2010-01-01) | one
|
||||
(3 rows)
|
||||
|
||||
DEALLOCATE fpo_param_control;
|
||||
DEALLOCATE fpo_param_portion;
|
||||
DEALLOCATE fpo_param_update;
|
||||
DEALLOCATE fpo_param_delete;
|
||||
DEALLOCATE fpo_param_one;
|
||||
DEALLOCATE fpo_param_same;
|
||||
-- The bounds we keep for deparsing are the coerced ones, so an untyped NULL
|
||||
-- bound renders with the range's subtype instead of "unknown".
|
||||
CREATE TABLE fpo_param_src (id int);
|
||||
CREATE RULE fpo_param_r AS ON DELETE TO fpo_param_src DO INSTEAD
|
||||
DELETE FROM fpo_param FOR PORTION OF valid_at FROM NULL TO '2001-01-01';
|
||||
SELECT definition FROM pg_rules WHERE rulename = 'fpo_param_r';
|
||||
definition
|
||||
----------------------------------------------------------------------------------------------------------------------------------------
|
||||
CREATE RULE fpo_param_r AS +
|
||||
ON DELETE TO public.fpo_param_src DO INSTEAD DELETE FROM fpo_param FOR PORTION OF valid_at FROM NULL::date TO '2001-01-01'::date;
|
||||
(1 row)
|
||||
|
||||
DROP TABLE fpo_param_src;
|
||||
DROP TABLE fpo_param;
|
||||
RESET datestyle;
|
||||
|
||||
@@ -1849,4 +1849,75 @@ SELECT * FROM fpo_rls ORDER BY valid_at;
|
||||
DROP TABLE fpo_rls;
|
||||
DROP ROLE regress_fpo_rls;
|
||||
|
||||
--
|
||||
-- Parameters in the FOR PORTION OF bounds
|
||||
--
|
||||
|
||||
CREATE TABLE fpo_param (
|
||||
id int4range,
|
||||
valid_at daterange,
|
||||
name text
|
||||
);
|
||||
INSERT INTO fpo_param (id, valid_at, name) VALUES
|
||||
('[1,2)', daterange('2000-01-01', '2010-01-01'), 'one');
|
||||
|
||||
-- A parameter of unspecified type in an ordinary expression gets its type
|
||||
-- resolved from context. This is the control case for the FROM/TO bounds
|
||||
-- below: it builds exactly the same daterange the FROM/TO form does.
|
||||
PREPARE fpo_param_control AS
|
||||
UPDATE fpo_param SET name = 'ctl' WHERE valid_at && daterange($1, $2);
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_control';
|
||||
|
||||
-- The (portion) form resolves the parameter type from the range column.
|
||||
PREPARE fpo_param_portion AS
|
||||
UPDATE fpo_param FOR PORTION OF valid_at ($1) SET name = 'portion';
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_portion';
|
||||
|
||||
-- The FROM/TO form should likewise resolve its bounds to the range's
|
||||
-- subtype, so that clients need not spell out the parameter types.
|
||||
PREPARE fpo_param_update AS
|
||||
UPDATE fpo_param FOR PORTION OF valid_at FROM $1 TO $2 SET name = 'upd';
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_update';
|
||||
|
||||
PREPARE fpo_param_delete AS
|
||||
DELETE FROM fpo_param FOR PORTION OF valid_at FROM $1 TO $2;
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_delete';
|
||||
|
||||
-- Only one bound parameterized.
|
||||
PREPARE fpo_param_one AS
|
||||
UPDATE fpo_param FOR PORTION OF valid_at FROM $1 TO '2003-01-01'
|
||||
SET name = 'one-bound';
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_one';
|
||||
|
||||
-- A parameter used as both bounds must still resolve to one type.
|
||||
PREPARE fpo_param_same AS
|
||||
UPDATE fpo_param FOR PORTION OF valid_at FROM $1 TO $1 SET name = 'same';
|
||||
SELECT parameter_types FROM pg_prepared_statements
|
||||
WHERE name = 'fpo_param_same';
|
||||
|
||||
EXECUTE fpo_param_update('2002-01-01', '2003-01-01');
|
||||
SELECT * FROM fpo_param ORDER BY valid_at;
|
||||
|
||||
DEALLOCATE fpo_param_control;
|
||||
DEALLOCATE fpo_param_portion;
|
||||
DEALLOCATE fpo_param_update;
|
||||
DEALLOCATE fpo_param_delete;
|
||||
DEALLOCATE fpo_param_one;
|
||||
DEALLOCATE fpo_param_same;
|
||||
|
||||
-- The bounds we keep for deparsing are the coerced ones, so an untyped NULL
|
||||
-- bound renders with the range's subtype instead of "unknown".
|
||||
CREATE TABLE fpo_param_src (id int);
|
||||
CREATE RULE fpo_param_r AS ON DELETE TO fpo_param_src DO INSTEAD
|
||||
DELETE FROM fpo_param FOR PORTION OF valid_at FROM NULL TO '2001-01-01';
|
||||
SELECT definition FROM pg_rules WHERE rulename = 'fpo_param_r';
|
||||
|
||||
DROP TABLE fpo_param_src;
|
||||
DROP TABLE fpo_param;
|
||||
|
||||
RESET datestyle;
|
||||
|
||||
Reference in New Issue
Block a user