mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Fix interval_transform so it doesn't throw away non-no-op casts.
interval_transform() contained two separate bugs that caused it to sometimes mistakenly decide that a cast from interval to restricted interval is a no-op and throw it away. First, it was wrong to rely on dt.h's field type macros to have an ordering consistent with the field's significance; in one case they do not. This led to mistakenly treating YEAR as less significant than MONTH, so that a cast from INTERVAL MONTH to INTERVAL YEAR was incorrectly discarded. Second, fls(1<<k) produces k+1 not k, so comparing its output directly to SECOND was wrong. This led to supposing that a cast to INTERVAL MINUTE was really a cast to INTERVAL SECOND and so could be discarded. To fix, get rid of the use of fls(), and make a function based on intervaltypmodout to produce a field ID code adapted to the need here. Per bug #14479 from Piotr Stefaniak. Back-patch to 9.2 where transform functions were introduced, because this code was born broken. Discussion: https://postgr.es/m/20161227172307.10135.7747@wrigleys.postgresql.org
This commit is contained in:
		@@ -922,6 +922,59 @@ intervaltypmodout(PG_FUNCTION_ARGS)
 | 
				
			|||||||
	PG_RETURN_CSTRING(res);
 | 
						PG_RETURN_CSTRING(res);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Given an interval typmod value, return a code for the least-significant
 | 
				
			||||||
 | 
					 * field that the typmod allows to be nonzero, for instance given
 | 
				
			||||||
 | 
					 * INTERVAL DAY TO HOUR we want to identify "hour".
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The results should be ordered by field significance, which means
 | 
				
			||||||
 | 
					 * we can't use the dt.h macros YEAR etc, because for some odd reason
 | 
				
			||||||
 | 
					 * they aren't ordered that way.  Instead, arbitrarily represent
 | 
				
			||||||
 | 
					 * SECOND = 0, MINUTE = 1, HOUR = 2, DAY = 3, MONTH = 4, YEAR = 5.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static int
 | 
				
			||||||
 | 
					intervaltypmodleastfield(int32 typmod)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (typmod < 0)
 | 
				
			||||||
 | 
							return 0;				/* SECOND */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (INTERVAL_RANGE(typmod))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							case INTERVAL_MASK(YEAR):
 | 
				
			||||||
 | 
								return 5;			/* YEAR */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(MONTH):
 | 
				
			||||||
 | 
								return 4;			/* MONTH */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(DAY):
 | 
				
			||||||
 | 
								return 3;			/* DAY */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(HOUR):
 | 
				
			||||||
 | 
								return 2;			/* HOUR */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(MINUTE):
 | 
				
			||||||
 | 
								return 1;			/* MINUTE */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(SECOND):
 | 
				
			||||||
 | 
								return 0;			/* SECOND */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
 | 
				
			||||||
 | 
								return 4;			/* MONTH */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
 | 
				
			||||||
 | 
								return 2;			/* HOUR */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
 | 
				
			||||||
 | 
								return 1;			/* MINUTE */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
 | 
				
			||||||
 | 
								return 0;			/* SECOND */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
 | 
				
			||||||
 | 
								return 1;			/* MINUTE */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
 | 
				
			||||||
 | 
								return 0;			/* SECOND */
 | 
				
			||||||
 | 
							case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
 | 
				
			||||||
 | 
								return 0;			/* SECOND */
 | 
				
			||||||
 | 
							case INTERVAL_FULL_RANGE:
 | 
				
			||||||
 | 
								return 0;			/* SECOND */
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								elog(ERROR, "invalid INTERVAL typmod: 0x%x", typmod);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0;					/* can't get here, but keep compiler quiet */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* interval_transform()
 | 
					/* interval_transform()
 | 
				
			||||||
 * Flatten superfluous calls to interval_scale().  The interval typmod is
 | 
					 * Flatten superfluous calls to interval_scale().  The interval typmod is
 | 
				
			||||||
@@ -943,39 +996,39 @@ interval_transform(PG_FUNCTION_ARGS)
 | 
				
			|||||||
	if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
 | 
						if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		Node	   *source = (Node *) linitial(expr->args);
 | 
							Node	   *source = (Node *) linitial(expr->args);
 | 
				
			||||||
		int32		old_typmod = exprTypmod(source);
 | 
					 | 
				
			||||||
		int32		new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
 | 
							int32		new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
 | 
				
			||||||
		int			old_range;
 | 
							bool		noop;
 | 
				
			||||||
		int			old_precis;
 | 
					 | 
				
			||||||
		int			new_range = INTERVAL_RANGE(new_typmod);
 | 
					 | 
				
			||||||
		int			new_precis = INTERVAL_PRECISION(new_typmod);
 | 
					 | 
				
			||||||
		int			new_range_fls;
 | 
					 | 
				
			||||||
		int			old_range_fls;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (old_typmod < 0)
 | 
							if (new_typmod < 0)
 | 
				
			||||||
		{
 | 
								noop = true;
 | 
				
			||||||
			old_range = INTERVAL_FULL_RANGE;
 | 
					 | 
				
			||||||
			old_precis = INTERVAL_FULL_PRECISION;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			old_range = INTERVAL_RANGE(old_typmod);
 | 
								int32		old_typmod = exprTypmod(source);
 | 
				
			||||||
 | 
								int			old_least_field;
 | 
				
			||||||
 | 
								int			new_least_field;
 | 
				
			||||||
 | 
								int			old_precis;
 | 
				
			||||||
 | 
								int			new_precis;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								old_least_field = intervaltypmodleastfield(old_typmod);
 | 
				
			||||||
 | 
								new_least_field = intervaltypmodleastfield(new_typmod);
 | 
				
			||||||
 | 
								if (old_typmod < 0)
 | 
				
			||||||
 | 
									old_precis = INTERVAL_FULL_PRECISION;
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
				old_precis = INTERVAL_PRECISION(old_typmod);
 | 
									old_precis = INTERVAL_PRECISION(old_typmod);
 | 
				
			||||||
		}
 | 
								new_precis = INTERVAL_PRECISION(new_typmod);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			/*
 | 
								/*
 | 
				
			||||||
		 * Temporally-smaller fields occupy higher positions in the range
 | 
								 * Cast is a no-op if least field stays the same or decreases
 | 
				
			||||||
		 * bitmap.  Since only the temporally-smallest bit matters for length
 | 
								 * while precision stays the same or increases.  But precision,
 | 
				
			||||||
		 * coercion purposes, we compare the last-set bits in the ranges.
 | 
								 * which is to say, sub-second precision, only affects ranges that
 | 
				
			||||||
		 * Precision, which is to say, sub-second precision, only affects
 | 
								 * include SECOND.
 | 
				
			||||||
		 * ranges that include SECOND.
 | 
					 | 
				
			||||||
			 */
 | 
								 */
 | 
				
			||||||
		new_range_fls = fls(new_range);
 | 
								noop = (new_least_field <= old_least_field) &&
 | 
				
			||||||
		old_range_fls = fls(old_range);
 | 
									(old_least_field > 0 /* SECOND */ ||
 | 
				
			||||||
		if (new_typmod < 0 ||
 | 
									 new_precis >= MAX_INTERVAL_PRECISION ||
 | 
				
			||||||
			((new_range_fls >= SECOND || new_range_fls >= old_range_fls) &&
 | 
									 new_precis >= old_precis);
 | 
				
			||||||
		   (old_range_fls < SECOND || new_precis >= MAX_INTERVAL_PRECISION ||
 | 
							}
 | 
				
			||||||
			new_precis >= old_precis)))
 | 
							if (noop)
 | 
				
			||||||
			ret = relabel_to_typmod(source, new_typmod);
 | 
								ret = relabel_to_typmod(source, new_typmod);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -692,6 +692,24 @@ SELECT interval '1 2:03:04.5678' minute to second(2);
 | 
				
			|||||||
 1 day 02:03:04.57
 | 
					 1 day 02:03:04.57
 | 
				
			||||||
(1 row)
 | 
					(1 row)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- test casting to restricted precision (bug #14479)
 | 
				
			||||||
 | 
					SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
 | 
				
			||||||
 | 
					  (f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
 | 
				
			||||||
 | 
					  FROM interval_tbl;
 | 
				
			||||||
 | 
					       f1        |     minutes     |  years   
 | 
				
			||||||
 | 
					-----------------+-----------------+----------
 | 
				
			||||||
 | 
					 00:01:00        | 00:01:00        | 00:00:00
 | 
				
			||||||
 | 
					 05:00:00        | 05:00:00        | 00:00:00
 | 
				
			||||||
 | 
					 10 days         | 10 days         | 00:00:00
 | 
				
			||||||
 | 
					 34 years        | 34 years        | 34 years
 | 
				
			||||||
 | 
					 3 mons          | 3 mons          | 00:00:00
 | 
				
			||||||
 | 
					 -00:00:14       | 00:00:00        | 00:00:00
 | 
				
			||||||
 | 
					 1 day 02:03:04  | 1 day 02:03:00  | 00:00:00
 | 
				
			||||||
 | 
					 6 years         | 6 years         | 6 years
 | 
				
			||||||
 | 
					 5 mons          | 5 mons          | 00:00:00
 | 
				
			||||||
 | 
					 5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
 | 
				
			||||||
 | 
					(10 rows)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- test inputting and outputting SQL standard interval literals
 | 
					-- test inputting and outputting SQL standard interval literals
 | 
				
			||||||
SET IntervalStyle TO sql_standard;
 | 
					SET IntervalStyle TO sql_standard;
 | 
				
			||||||
SELECT  interval '0'                       AS "zero",
 | 
					SELECT  interval '0'                       AS "zero",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -198,6 +198,11 @@ SELECT interval '1 2.3456' minute to second(2);
 | 
				
			|||||||
SELECT interval '1 2:03.5678' minute to second(2);
 | 
					SELECT interval '1 2:03.5678' minute to second(2);
 | 
				
			||||||
SELECT interval '1 2:03:04.5678' minute to second(2);
 | 
					SELECT interval '1 2:03:04.5678' minute to second(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- test casting to restricted precision (bug #14479)
 | 
				
			||||||
 | 
					SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
 | 
				
			||||||
 | 
					  (f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
 | 
				
			||||||
 | 
					  FROM interval_tbl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- test inputting and outputting SQL standard interval literals
 | 
					-- test inputting and outputting SQL standard interval literals
 | 
				
			||||||
SET IntervalStyle TO sql_standard;
 | 
					SET IntervalStyle TO sql_standard;
 | 
				
			||||||
SELECT  interval '0'                       AS "zero",
 | 
					SELECT  interval '0'                       AS "zero",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user