Commits

Anonymous committed c138979

Add transform functions for various temporal typmod coercisions.

This enables ALTER TABLE to skip table and index rebuilds in some cases.

Noah Misch, with trivial changes by me.

Comments (0)

Files changed (8)

src/backend/utils/adt/date.c

 }
 
 
+/* time_transform()
+ * Flatten calls to time_scale() and timetz_scale() that solely represent
+ * increases in allowed precision.
+ */
+Datum
+time_transform(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(TemporalTransform(MAX_TIME_PRECISION,
+										(Node *) PG_GETARG_POINTER(0)));
+}
+
 /* time_scale()
  * Adjust time type for specified scale factor.
  * Used by PostgreSQL type system to stuff columns.

src/backend/utils/adt/datetime.c

 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_clause.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
 #include "utils/datetime.h"
 }
 
 /*
+ * Helper for temporal protransform functions.  Types time, timetz, timestamp
+ * and timestamptz each have a range of allowed precisions.  An unspecified
+ * precision is rigorously equivalent to the highest specifiable precision.
+ */
+Node *
+TemporalTransform(int32 max_precis, Node *node)
+{
+	FuncExpr   *expr = (FuncExpr *) node;
+	Node	   *typmod;
+	Node	   *ret = NULL;
+
+	if (!IsA(expr, FuncExpr))
+		return ret;
+
+	Assert(list_length(expr->args) == 2);
+	typmod = lsecond(expr->args);
+
+	if (IsA(typmod, Const))
+	{
+		Node	   *source = linitial(expr->args);
+		int32		old_precis = exprTypmod(source);
+		int32		new_precis = DatumGetInt32(((Const *) typmod)->constvalue);
+
+		if (new_precis == -1 ||
+			new_precis == max_precis ||
+			(old_precis != -1 && new_precis >= old_precis))
+			ret = relabel_to_typmod(source, new_precis);
+	}
+
+	return ret;
+}
+
+/*
  * This function gets called during timezone config file load or reload
  * to create the final array of timezone tokens.  The argument array
  * is already sorted in name order.  The data is converted to datetkn

src/backend/utils/adt/timestamp.c

 #include "funcapi.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_clause.h"
 #include "parser/scansup.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 }
 
 
+/* timestamp_transform()
+ * Flatten calls to timestamp_scale() and timestamptz_scale() that solely
+ * represent increases in allowed precision.
+ */
+Datum
+timestamp_transform(PG_FUNCTION_ARGS)
+{
+	/*
+	 * timestamp_scale throws an error when the typmod is out of range, but we
+	 * can't get there from a cast: our typmodin will have caught it already.
+	 */
+	PG_RETURN_POINTER(TemporalTransform(MAX_TIMESTAMP_PRECISION,
+										(Node *) PG_GETARG_POINTER(0)));
+}
+
 /* timestamp_scale()
  * Adjust time type for specified scale factor.
  * Used by PostgreSQL type system to stuff columns.
 	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }
 
+/*
+ * The interval typmod stores a "range" in its high 16 bits and a "precision"
+ * in its low 16 bits.  Both contribute to defining the resolution of the
+ * type.  Range addresses resolution granules larger than one second, and
+ * precision specifies resolution below one second.  This representation can
+ * express all SQL standard resolutions, but we implement them all in terms of
+ * truncating rightward from some position.  Range is a bitmap of permitted
+ * fields, but only the temporally-smallest such field is significant to our
+ * calculations.  Precision is a count of sub-second decimal places to retain.
+ * Setting all bits (INTERVAL_FULL_PRECISION) gives the same truncation
+ * semantics as choosing MAX_INTERVAL_PRECISION.
+ */
 Datum
 intervaltypmodin(PG_FUNCTION_ARGS)
 {
 }
 
 
+/* interval_transform()
+ * Flatten superfluous calls to interval_scale().  The interval typmod is
+ * complex to permit accepting and regurgitating all SQL standard variations.
+ * For truncation purposes, it boils down to a single, simple granularity.
+ */
+Datum
+interval_transform(PG_FUNCTION_ARGS)
+{
+	FuncExpr   *expr = (FuncExpr *) PG_GETARG_POINTER(0);
+	Node	   *typmod;
+	Node	   *ret = NULL;
+
+	if (!IsA(expr, FuncExpr))
+		PG_RETURN_POINTER(ret);
+
+	Assert(list_length(expr->args) == 2);
+	typmod = lsecond(expr->args);
+
+	if (IsA(typmod, Const))
+	{
+		Node	   *source = linitial(expr->args);
+		int32		old_typmod = exprTypmod(source);
+		int32		new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
+		int			old_range;
+		int			old_precis;
+		int			new_range = INTERVAL_RANGE(new_typmod);
+		int			new_precis = INTERVAL_PRECISION(new_typmod);
+		int			new_range_fls;
+
+		if (old_typmod == -1)
+		{
+			old_range = INTERVAL_FULL_RANGE;
+			old_precis = INTERVAL_FULL_PRECISION;
+		}
+		else
+		{
+			old_range = INTERVAL_RANGE(old_typmod);
+			old_precis = INTERVAL_PRECISION(old_typmod);
+		}
+
+		/*
+		 * Temporally-smaller fields occupy higher positions in the range
+		 * bitmap.  Since only the temporally-smallest bit matters for length
+		 * coercion purposes, we compare the last-set bits in the ranges.
+		 */
+		new_range_fls = fls(new_range);
+		if (new_typmod == -1 ||
+			((new_range_fls >= SECOND ||
+			  new_range_fls >= fls(old_range)) &&
+			 (new_precis >= MAX_INTERVAL_PRECISION ||
+			  new_precis >= old_precis)))
+			ret = relabel_to_typmod(source, new_typmod);
+	}
+
+	PG_RETURN_POINTER(ret);
+}
+
 /* interval_scale()
  * Adjust interval type for specified fields.
  * Used by PostgreSQL type system to stuff columns.

src/include/catalog/catversion.h

  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201202072
+#define CATALOG_VERSION_NO	201202081
 
 #endif

src/include/catalog/pg_proc.h

 
 /* OIDS 1200 - 1299 */
 
-DATA(insert OID = 1200 (  interval			PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 1186 "1186 23" _null_ _null_ _null_ _null_ interval_scale _null_ _null_ _null_ ));
+DATA(insert OID = 3918 (  interval_transform PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ interval_transform _null_ _null_ _null_ ));
+DESCR("transform an interval length coercion");
+DATA(insert OID = 1200 (  interval			PGNSP PGUID 12 1 0 0 3918 f f f t f i 2 0 1186 "1186 23" _null_ _null_ _null_ _null_ interval_scale _null_ _null_ _null_ ));
 DESCR("adjust interval precision");
 
 DATA(insert OID = 1215 (  obj_description	PGNSP PGUID 14 100 0 0 0 f f f t f s 2 0 25 "26 19" _null_ _null_ _null_ _null_ "select description from pg_catalog.pg_description where objoid = $1 and classoid = (select oid from pg_catalog.pg_class where relname = $2 and relnamespace = PGNSP) and objsubid = 0" _null_ _null_ _null_ ));
 DATA(insert OID = 1954 (  byteacmp		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "17 17" _null_ _null_ _null_ _null_ byteacmp _null_ _null_ _null_ ));
 DESCR("less-equal-greater");
 
-DATA(insert OID = 1961 (  timestamp		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 1114 "1114 23" _null_ _null_ _null_ _null_ timestamp_scale _null_ _null_ _null_ ));
+DATA(insert OID = 3917 (  timestamp_transform PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ timestamp_transform _null_ _null_ _null_ ));
+DESCR("transform a timestamp length coercion");
+DATA(insert OID = 1961 (  timestamp		   PGNSP PGUID 12 1 0 0 3917 f f f t f i 2 0 1114 "1114 23" _null_ _null_ _null_ _null_ timestamp_scale _null_ _null_ _null_ ));
 DESCR("adjust timestamp precision");
 
 DATA(insert OID = 1965 (  oidlarger		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidlarger _null_ _null_ _null_ ));
 DATA(insert OID = 1966 (  oidsmaller	   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 26 "26 26" _null_ _null_ _null_ _null_ oidsmaller _null_ _null_ _null_ ));
 DESCR("smaller of two");
 
-DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
+DATA(insert OID = 1967 (  timestamptz	   PGNSP PGUID 12 1 0 0 3917 f f f t f i 2 0 1184 "1184 23" _null_ _null_ _null_ _null_ timestamptz_scale _null_ _null_ _null_ ));
 DESCR("adjust timestamptz precision");
-DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
+DATA(insert OID = 3944 (  time_transform   PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ time_transform _null_ _null_ _null_ ));
+DESCR("transform a time length coercion");
+DATA(insert OID = 1968 (  time			   PGNSP PGUID 12 1 0 0 3944 f f f t f i 2 0 1083 "1083 23" _null_ _null_ _null_ _null_ time_scale _null_ _null_ _null_ ));
 DESCR("adjust time precision");
-DATA(insert OID = 1969 (  timetz		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 1266 "1266 23" _null_ _null_ _null_ _null_ timetz_scale _null_ _null_ _null_ ));
+DATA(insert OID = 1969 (  timetz		   PGNSP PGUID 12 1 0 0 3944 f f f t f i 2 0 1266 "1266 23" _null_ _null_ _null_ _null_ timetz_scale _null_ _null_ _null_ ));
 DESCR("adjust time with time zone precision");
 
 DATA(insert OID = 2003 (  textanycat	   PGNSP PGUID 14 1 0 0 0 f f f t f v 2 0 25 "25 2776" _null_ _null_ _null_ _null_ "select $1 || $2::pg_catalog.text" _null_ _null_ _null_ ));

src/include/utils/date.h

 extern Datum time_send(PG_FUNCTION_ARGS);
 extern Datum timetypmodin(PG_FUNCTION_ARGS);
 extern Datum timetypmodout(PG_FUNCTION_ARGS);
+extern Datum time_transform(PG_FUNCTION_ARGS);
 extern Datum time_scale(PG_FUNCTION_ARGS);
 extern Datum time_eq(PG_FUNCTION_ARGS);
 extern Datum time_ne(PG_FUNCTION_ARGS);

src/include/utils/datetime.h

 #ifndef DATETIME_H
 #define DATETIME_H
 
+#include "nodes/nodes.h"
 #include "utils/timestamp.h"
 
 /* this struct is declared in utils/tzparser.h: */
 
 extern int	j2day(int jd);
 
+extern Node *TemporalTransform(int32 max_precis, Node *node);
+
 extern bool CheckDateTokenTables(void);
 
 extern void ConvertTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl,

src/include/utils/timestamp.h

 extern Datum timestamp_send(PG_FUNCTION_ARGS);
 extern Datum timestamptypmodin(PG_FUNCTION_ARGS);
 extern Datum timestamptypmodout(PG_FUNCTION_ARGS);
+extern Datum timestamp_transform(PG_FUNCTION_ARGS);
 extern Datum timestamp_scale(PG_FUNCTION_ARGS);
 extern Datum timestamp_eq(PG_FUNCTION_ARGS);
 extern Datum timestamp_ne(PG_FUNCTION_ARGS);
 extern Datum interval_send(PG_FUNCTION_ARGS);
 extern Datum intervaltypmodin(PG_FUNCTION_ARGS);
 extern Datum intervaltypmodout(PG_FUNCTION_ARGS);
+extern Datum interval_transform(PG_FUNCTION_ARGS);
 extern Datum interval_scale(PG_FUNCTION_ARGS);
 extern Datum interval_eq(PG_FUNCTION_ARGS);
 extern Datum interval_ne(PG_FUNCTION_ARGS);