int64 *num_notnull_info; /* track size (number of tuples in
* partition) of the notnull_info array
* for each func args */
+ bool *notnull_info_cacheable; /* can we cache notnull_info? */
/*
* Null treatment options. One of: NO_NULLTREATMENT, PARSER_IGNORE_NULLS,
if (winobj->ignore_nulls == PARSER_IGNORE_NULLS)
{
+ int argno = 0;
+ ListCell *lc;
+
winobj->notnull_info = palloc0_array(uint8 *, numargs);
winobj->num_notnull_info = palloc0_array(int64, numargs);
+ winobj->notnull_info_cacheable = palloc_array(bool, numargs);
+
+ foreach(lc, perfuncstate->wfunc->args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+
+ winobj->notnull_info_cacheable[argno] =
+ !contain_volatile_functions(arg) &&
+ !contain_subplans(arg);
+
+ argno++;
+ }
}
}
uint8 mb;
int64 bpos;
+ if (!winobj->notnull_info_cacheable[argno])
+ return NN_UNKNOWN;
+
grow_notnull_info(winobj, pos, argno);
bpos = NN_POS_TO_BYTES(pos);
mbp = winobj->notnull_info[argno];
uint8 val = isnull ? NN_NULL : NN_NOTNULL;
int shift;
+ if (!winobj->notnull_info_cacheable[argno])
+ return;
+
grow_notnull_info(winobj, pos, argno);
bpos = NN_POS_TO_BYTES(pos);
mbp = winobj->notnull_info[argno];
int notnull_relpos;
int forward;
bool myisout;
+ bool got_datum;
Assert(WindowObjectIsValid(winobj));
winstate = winobj->winstate;
notnull_relpos = abs(relpos);
forward = relpos > 0 ? 1 : -1;
myisout = false;
+ got_datum = false;
datum = 0;
/*
{
/*
* NOT NULL info does not exist yet. Get tuple and evaluate func
- * arg in partition. We ignore the return value from
- * gettuple_eval_partition because we are just interested in
- * whether we are inside or outside of partition, NULL or NOT
- * NULL.
+ * arg in partition. Keep the return value in case this row is the
+ * target; re-evaluating a volatile argument could give a
+ * different nullness status.
*/
- (void) gettuple_eval_partition(winobj, argno,
- abs_pos, isnull, &myisout);
+ datum = gettuple_eval_partition(winobj, argno,
+ abs_pos, isnull, &myisout);
if (myisout) /* out of partition? */
break;
if (!*isnull)
+ {
notnull_offset++;
+ if (notnull_offset >= notnull_relpos)
+ got_datum = true;
+ }
/* record the row status */
put_notnull_info(winobj, abs_pos, argno, *isnull);
}
} while (notnull_offset < notnull_relpos);
/* get tuple and evaluate func arg in partition */
- datum = gettuple_eval_partition(winobj, argno,
- abs_pos, isnull, &myisout);
+ if (!got_datum)
+ datum = gettuple_eval_partition(winobj, argno,
+ abs_pos, isnull, &myisout);
if (!myisout && set_mark)
WinSetMarkPosition(winobj, mark_pos);
if (isout)
5 | 4
(5 rows)
+-- volatile arguments cannot use the IGNORE NULLS nullness cache
+CREATE TEMPORARY SEQUENCE null_treatment_seq;
+CREATE FUNCTION pg_temp.volatile_null(i int) RETURNS int
+LANGUAGE sql VOLATILE AS
+$$
+ SELECT CASE WHEN nextval('null_treatment_seq') % 2 = 0 THEN i ELSE NULL END;
+$$;
+SELECT x,
+ first_value(pg_temp.volatile_null(x)) IGNORE NULLS
+ OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
+FROM generate_series(1,5) g(x);
+ x | first_value
+---+-------------
+ 1 |
+ 2 | 1
+ 3 | 2
+ 4 | 2
+ 5 | 2
+(5 rows)
+
+SELECT last_value FROM null_treatment_seq;
+ last_value
+------------
+ 8
+(1 row)
+
+ALTER SEQUENCE null_treatment_seq RESTART WITH 1;
+SELECT x,
+ lead(pg_temp.volatile_null(x), 1) IGNORE NULLS OVER (ORDER BY x)
+FROM generate_series(1,5) g(x);
+ x | lead
+---+------
+ 1 | 3
+ 2 | 4
+ 3 | 5
+ 4 |
+ 5 |
+(5 rows)
+
+SELECT last_value FROM null_treatment_seq;
+ last_value
+------------
+ 7
+(1 row)
+
+ALTER SEQUENCE null_treatment_seq RESTART WITH 1;
+SELECT x,
+ first_value((SELECT CASE WHEN nextval('null_treatment_seq') % 2 = 0
+ THEN x ELSE NULL END)) IGNORE NULLS
+ OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
+FROM generate_series(1,5) g(x);
+ x | first_value
+---+-------------
+ 1 |
+ 2 | 1
+ 3 | 2
+ 4 | 2
+ 5 | 2
+(5 rows)
+
+SELECT last_value FROM null_treatment_seq;
+ last_value
+------------
+ 8
+(1 row)
+
--cleanup
DROP TABLE planets CASCADE;
NOTICE: drop cascades to view planets_view
FROM generate_series(1,5) g(x)
WINDOW w AS (ORDER BY x ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING);
+-- volatile arguments cannot use the IGNORE NULLS nullness cache
+CREATE TEMPORARY SEQUENCE null_treatment_seq;
+CREATE FUNCTION pg_temp.volatile_null(i int) RETURNS int
+LANGUAGE sql VOLATILE AS
+$$
+ SELECT CASE WHEN nextval('null_treatment_seq') % 2 = 0 THEN i ELSE NULL END;
+$$;
+
+SELECT x,
+ first_value(pg_temp.volatile_null(x)) IGNORE NULLS
+ OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
+FROM generate_series(1,5) g(x);
+SELECT last_value FROM null_treatment_seq;
+
+ALTER SEQUENCE null_treatment_seq RESTART WITH 1;
+SELECT x,
+ lead(pg_temp.volatile_null(x), 1) IGNORE NULLS OVER (ORDER BY x)
+FROM generate_series(1,5) g(x);
+SELECT last_value FROM null_treatment_seq;
+
+ALTER SEQUENCE null_treatment_seq RESTART WITH 1;
+SELECT x,
+ first_value((SELECT CASE WHEN nextval('null_treatment_seq') % 2 = 0
+ THEN x ELSE NULL END)) IGNORE NULLS
+ OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
+FROM generate_series(1,5) g(x);
+SELECT last_value FROM null_treatment_seq;
+
--cleanup
DROP TABLE planets CASCADE;