Avoid some table rewrites for ALTER TABLE .. SET DATA TYPE timestamp.

When the timezone is UTC, timestamptz and timestamp are binary coercible
in both directions.  See b8a18ad485 and
c22ecc6562 for the previous attempt in
this problem space.  Skip the table rewrite; for now, continue to
needlessly rewrite any index on an affected column.

Reviewed by Simon Riggs and Tom Lane.

Discussion: https://postgr.es/m/20190226061450.GA1665944@rfd.leadboat.com
This commit is contained in:
Noah Misch 2019-03-08 20:16:27 -08:00
parent 82a5649fb9
commit 3c5926301a
5 changed files with 65 additions and 7 deletions

View File

@ -96,6 +96,7 @@
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "utils/typcache.h"
@ -9678,11 +9679,15 @@ ATPrepAlterColumnType(List **wqueue,
* When the data type of a column is changed, a rewrite might not be required
* if the new type is sufficiently identical to the old one, and the USING
* clause isn't trying to insert some other value. It's safe to skip the
* rewrite if the old type is binary coercible to the new type, or if the
* new type is an unconstrained domain over the old type. In the case of a
* constrained domain, we could get by with scanning the table and checking
* the constraint rather than actually rewriting it, but we don't currently
* try to do that.
* rewrite in these cases:
*
* - the old type is binary coercible to the new type
* - the new type is an unconstrained domain over the old type
* - {NEW,OLD} or {OLD,NEW} is {timestamptz,timestamp} and the timezone is UTC
*
* In the case of a constrained domain, we could get by with scanning the
* table and checking the constraint rather than actually rewriting it, but we
* don't currently try to do that.
*/
static bool
ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
@ -9704,6 +9709,23 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
return true;
expr = (Node *) d->arg;
}
else if (IsA(expr, FuncExpr))
{
FuncExpr *f = (FuncExpr *) expr;
switch (f->funcid)
{
case F_TIMESTAMPTZ_TIMESTAMP:
case F_TIMESTAMP_TIMESTAMPTZ:
if (TimestampTimestampTzRequiresRewrite())
return true;
else
expr = linitial(f->args);
break;
default:
return true;
}
}
else
return true;
}

View File

@ -5168,6 +5168,23 @@ timestamp_izone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMPTZ(result);
} /* timestamp_izone() */
/* TimestampTimestampTzRequiresRewrite()
*
* Returns false if the TimeZone GUC setting causes timestamp_timestamptz and
* timestamptz_timestamp to be no-ops, where the return value has the same
* bits as the argument. Since project convention is to assume a GUC changes
* no more often than STABLE functions change, the answer is valid that long.
*/
bool
TimestampTimestampTzRequiresRewrite(void)
{
long offset;
if (pg_get_timezone_offset(session_timezone, &offset) && offset == 0)
PG_RETURN_BOOL(false);
PG_RETURN_BOOL(true);
}
/* timestamp_timestamptz()
* Convert local timestamp to timestamp at GMT
*/

View File

@ -104,4 +104,6 @@ extern int date2isoweek(int year, int mon, int mday);
extern int date2isoyear(int year, int mon, int mday);
extern int date2isoyearday(int year, int mon, int mday);
extern bool TimestampTimestampTzRequiresRewrite(void);
#endif /* TIMESTAMP_H */

View File

@ -435,7 +435,7 @@ END;
$$;
create event trigger no_rewrite_allowed on table_rewrite
execute procedure test_evtrig_no_rewrite();
create table rewriteme (id serial primary key, foo float);
create table rewriteme (id serial primary key, foo float, bar timestamptz);
insert into rewriteme
select x * 1.001 from generate_series(1, 500) as t(x);
alter table rewriteme alter column foo type numeric;
@ -458,6 +458,15 @@ alter table rewriteme
NOTICE: Table 'rewriteme' is being rewritten (reason = 4)
-- shouldn't trigger a table_rewrite event
alter table rewriteme alter column foo type numeric(12,4);
begin;
set timezone to 'UTC';
alter table rewriteme alter column bar type timestamp;
set timezone to '0';
alter table rewriteme alter column bar type timestamptz;
set timezone to 'Europe/London';
alter table rewriteme alter column bar type timestamp; -- does rewrite
NOTICE: Table 'rewriteme' is being rewritten (reason = 4)
rollback;
-- typed tables are rewritten when their type changes. Don't emit table
-- name, because firing order is not stable.
CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger

View File

@ -329,7 +329,7 @@ $$;
create event trigger no_rewrite_allowed on table_rewrite
execute procedure test_evtrig_no_rewrite();
create table rewriteme (id serial primary key, foo float);
create table rewriteme (id serial primary key, foo float, bar timestamptz);
insert into rewriteme
select x * 1.001 from generate_series(1, 500) as t(x);
alter table rewriteme alter column foo type numeric;
@ -352,6 +352,14 @@ alter table rewriteme
-- shouldn't trigger a table_rewrite event
alter table rewriteme alter column foo type numeric(12,4);
begin;
set timezone to 'UTC';
alter table rewriteme alter column bar type timestamp;
set timezone to '0';
alter table rewriteme alter column bar type timestamptz;
set timezone to 'Europe/London';
alter table rewriteme alter column bar type timestamp; -- does rewrite
rollback;
-- typed tables are rewritten when their type changes. Don't emit table
-- name, because firing order is not stable.