diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 896c08c..75cb36e 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -12003,6 +12003,7 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab; lag(value any [, offset integer [, default any ]]) + [respect nulls]|[ignore nulls] @@ -12017,7 +12018,10 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab; default are evaluated with respect to the current row. If omitted, offset defaults to 1 and - default to null + default to null. If + IGNORE NULLS is specified and a previous evalution in the + current window has returned a non-null value then that value will be + returned instead. @@ -12030,6 +12034,7 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab; lead(value any [, offset integer [, default any ]]) + [respect nulls]|[ignore nulls] @@ -12044,7 +12049,9 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab; default are evaluated with respect to the current row. If omitted, offset defaults to 1 and - default to null + IGNORE NULLS is specified and a previous evalution in the + current window has returned a non-null value then that value will be + returned instead. @@ -12138,11 +12145,10 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab; The SQL standard defines a RESPECT NULLS or - IGNORE NULLS option for lead, lag, - first_value, last_value, and - nth_value. This is not implemented in - PostgreSQL: the behavior is always the - same as the standard's default, namely RESPECT NULLS. + IGNORE NULLS option for first_value, + last_value, and nth_value. This is not + implemented in PostgreSQL: the behavior is + always the same as the standard's default, namely RESPECT NULLS. Likewise, the standard's FROM FIRST or FROM LAST option for nth_value is not implemented: only the default FROM FIRST behavior is supported. (You can achieve diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 3bc42ba..548c506 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -1996,6 +1996,16 @@ WinGetCurrentPosition(WindowObject winobj) Assert(WindowObjectIsValid(winobj)); return winobj->winstate->currentpos; } +/* + * WinGetFrameOptions + * Returns the frame option flags + */ +int +WinGetFrameOptions(WindowObject winobj) +{ + Assert(WindowObjectIsValid(winobj)); + return winobj->winstate->frameOptions; +} /* * WinGetPartitionRowCount diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9d07f30..6dda644 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -496,6 +496,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type window_clause window_definition_list opt_partition_clause %type window_definition over_clause window_specification opt_frame_clause frame_extent frame_bound + over_specification %type opt_existing_window_name %type opt_if_not_exists @@ -551,7 +552,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); HANDLER HAVING HEADER_P HOLD HOUR_P - IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P + IDENTITY_P IF_P IGNORE ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION @@ -581,7 +582,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA - RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK + RESET RESPECT RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES @@ -615,6 +616,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * creates these tokens when required. */ %token NULLS_FIRST NULLS_LAST WITH_TIME +%token RESPECT_NULLS IGNORE_NULLS /* Precedence: lowest to highest */ @@ -11785,7 +11787,8 @@ window_definition: } ; -over_clause: OVER window_specification +over_specification: + OVER window_specification { $$ = $2; } | OVER ColId { @@ -11800,6 +11803,18 @@ over_clause: OVER window_specification n->location = @2; $$ = n; } + ; + +over_clause: over_specification + { $$ = $1; } + | RESPECT_NULLS over_specification + { $$ = $2; } + | IGNORE_NULLS over_specification + { + if($2) + $2->frameOptions |= FRAMEOPTION_IGNORE_NULLS; + $$ = $2; + } | /*EMPTY*/ { $$ = NULL; } ; @@ -12765,6 +12780,7 @@ unreserved_keyword: | HOUR_P | IDENTITY_P | IF_P + | IGNORE | IMMEDIATE | IMMUTABLE | IMPLICIT_P @@ -12852,6 +12868,7 @@ unreserved_keyword: | REPLACE | REPLICA | RESET + | RESPECT | RESTART | RESTRICT | RETURNS diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index b8ec790..25d09e0 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -156,6 +156,33 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) } break; + /* + * Window functions can use RESPECT NULLS or IGNORE NULLS to + * modify their behaviour + */ + case RESPECT: + cur_yylval = lvalp->core_yystype; + cur_yylloc = *llocp; + next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner); + switch (next_token) + { + case NULLS_P: + cur_token = RESPECT_NULLS; + break; + } + break; + case IGNORE: + cur_yylval = lvalp->core_yystype; + cur_yylloc = *llocp; + next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner); + switch (next_token) + { + case NULLS_P: + cur_token = IGNORE_NULLS; + break; + } + break; + default: break; } diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c index 2f171ac..3144fd7 100644 --- a/src/backend/utils/adt/windowfuncs.c +++ b/src/backend/utils/adt/windowfuncs.c @@ -292,6 +292,7 @@ leadlag_common(FunctionCallInfo fcinfo, Datum result; bool isnull; bool isout; + bool ignore_nulls; if (withoffset) { @@ -322,8 +323,29 @@ leadlag_common(FunctionCallInfo fcinfo, result = WinGetFuncArgCurrent(winobj, 2, &isnull); } + ignore_nulls = (WinGetFrameOptions(winobj) & FRAMEOPTION_IGNORE_NULLS) != 0; + if(ignore_nulls) + { + /* + * We'll keep the last non-null value we've seen in our per-partition chunk + * of memory, so it gets cleaned up for us. + */ + Datum* stash = (Datum*) WinGetPartitionLocalMemory(winobj, sizeof(Datum)); + if(isnull) + { + result = *stash; + isnull = result == 0; + } + else + { + *stash = result; + } + } + if (isnull) + { PG_RETURN_NULL(); + } PG_RETURN_DATUM(result); } diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 2229ef0..a13c58b 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -435,6 +435,7 @@ typedef struct WindowDef #define FRAMEOPTION_END_VALUE_PRECEDING 0x00800 /* end is V. P. */ #define FRAMEOPTION_START_VALUE_FOLLOWING 0x01000 /* start is V. F. */ #define FRAMEOPTION_END_VALUE_FOLLOWING 0x02000 /* end is V. F. */ +#define FRAMEOPTION_IGNORE_NULLS 0x04000 /* lead/lag/nth */ #define FRAMEOPTION_START_VALUE \ (FRAMEOPTION_START_VALUE_PRECEDING | FRAMEOPTION_START_VALUE_FOLLOWING) diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 68a13b7..2acf073 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -179,6 +179,7 @@ PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD) PG_KEYWORD("hour", HOUR_P, UNRESERVED_KEYWORD) PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD) PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD) +PG_KEYWORD("ignore", IGNORE, UNRESERVED_KEYWORD) PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD) PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD) @@ -312,6 +313,7 @@ PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD) PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD) PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD) PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD) +PG_KEYWORD("respect", RESPECT, UNRESERVED_KEYWORD) PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD) PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD) PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD) diff --git a/src/include/windowapi.h b/src/include/windowapi.h index 5bbf1fa..81f5ba0 100644 --- a/src/include/windowapi.h +++ b/src/include/windowapi.h @@ -46,6 +46,8 @@ extern void *WinGetPartitionLocalMemory(WindowObject winobj, Size sz); extern int64 WinGetCurrentPosition(WindowObject winobj); extern int64 WinGetPartitionRowCount(WindowObject winobj); +extern int WinGetFrameOptions(WindowObject winobj); + extern void WinSetMarkPosition(WindowObject winobj, int64 markpos); extern bool WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2); diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl index f4b51d6..fe5dcb3 100644 --- a/src/interfaces/ecpg/preproc/parse.pl +++ b/src/interfaces/ecpg/preproc/parse.pl @@ -45,6 +45,8 @@ my %replace_string = ( 'WITH_TIME' => 'with time', 'NULLS_FIRST' => 'nulls first', 'NULLS_LAST' => 'nulls last', + 'RESPECT_NULLS' => 'respect nulls', + 'IGNORE_NULLS' => 'ignore nulls', 'TYPECAST' => '::', 'DOT_DOT' => '..', 'COLON_EQUALS' => ':=',); diff --git a/src/interfaces/ecpg/preproc/parser.c b/src/interfaces/ecpg/preproc/parser.c index 2ce9dd9..53f4167 100644 --- a/src/interfaces/ecpg/preproc/parser.c +++ b/src/interfaces/ecpg/preproc/parser.c @@ -121,6 +121,53 @@ filtered_base_yylex(void) } break; + /* + * Window functions can use RESPECT NULLS or IGNORE NULLS to + * modify their behaviour + */ + case RESPECT: + cur_yylval = base_yylval; + cur_yylloc = base_yylloc; + next_token = base_yylex(); + switch (next_token) + { + case NULLS_P: + cur_token = RESPECT_NULLS; + break; + default: + /* save the lookahead token for next time */ + lookahead_token = next_token; + lookahead_yylval = base_yylval; + lookahead_yylloc = base_yylloc; + have_lookahead = true; + /* and back up the output info to cur_token */ + base_yylval = cur_yylval; + base_yylloc = cur_yylloc; + break; + } + break; + case IGNORE: + cur_yylval = base_yylval; + cur_yylloc = base_yylloc; + next_token = base_yylex(); + switch (next_token) + { + case NULLS_P: + cur_token = IGNORE_NULLS; + break; + default: + /* save the lookahead token for next time */ + lookahead_token = next_token; + lookahead_yylval = base_yylval; + lookahead_yylloc = base_yylloc; + have_lookahead = true; + /* and back up the output info to cur_token */ + base_yylval = cur_yylval; + base_yylloc = cur_yylloc; + break; + } + break; + default: break; } diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 752c7b4..bcc9140 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -5,19 +5,20 @@ CREATE TEMPORARY TABLE empsalary ( depname varchar, empno bigint, salary int, - enroll_date date + enroll_date date, + term_date date ); INSERT INTO empsalary VALUES -('develop', 10, 5200, '2007-08-01'), -('sales', 1, 5000, '2006-10-01'), -('personnel', 5, 3500, '2007-12-10'), -('sales', 4, 4800, '2007-08-08'), -('personnel', 2, 3900, '2006-12-23'), -('develop', 7, 4200, '2008-01-01'), -('develop', 9, 4500, '2008-01-01'), -('sales', 3, 4800, '2007-08-01'), -('develop', 8, 6000, '2006-10-01'), -('develop', 11, 5200, '2007-08-15'); +('develop', 10, 5200, '2007-08-01', null), +('sales', 1, 5000, '2006-10-01', null), +('personnel', 5, 3500, '2007-12-10', null), +('sales', 4, 4800, '2007-08-08', '2010-09-22'), +('personnel', 2, 3900, '2006-12-23', null), +('develop', 7, 4200, '2008-01-01', null), +('develop', 9, 4500, '2008-01-01', null), +('sales', 3, 4800, '2007-08-01', '2009-03-05'), +('develop', 8, 6000, '2006-10-01', '2009-11-17'), +('develop', 11, 5200, '2007-08-15', null); SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary; depname | empno | salary | sum -----------+-------+--------+------- @@ -1020,5 +1021,96 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1; ERROR: argument of ntile must be greater than zero SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1; ERROR: argument of nth_value must be greater than zero +-- test null behaviour +SELECT lag(term_date) OVER (ORDER BY empno) FROM empsalary; + lag +------------ + + + + 03-05-2009 + 09-22-2010 + + + 11-17-2009 + + +(10 rows) + +SELECT lag(term_date) RESPECT NULLS OVER (ORDER BY empno) FROM empsalary; + lag +------------ + + + + 03-05-2009 + 09-22-2010 + + + 11-17-2009 + + +(10 rows) + +SELECT lag(term_date) IGNORE NULLS OVER (ORDER BY empno) FROM empsalary; + lag +------------ + + + + 03-05-2009 + 09-22-2010 + 09-22-2010 + 09-22-2010 + 11-17-2009 + 11-17-2009 + 11-17-2009 +(10 rows) + +SELECT lead(term_date) OVER (ORDER BY empno) FROM empsalary; + lead +------------ + + 03-05-2009 + 09-22-2010 + + + 11-17-2009 + + + + +(10 rows) + +SELECT lead(term_date) RESPECT NULLS OVER (ORDER BY empno) FROM empsalary; + lead +------------ + + 03-05-2009 + 09-22-2010 + + + 11-17-2009 + + + + +(10 rows) + +SELECT lead(term_date) IGNORE NULLS OVER (ORDER BY empno) FROM empsalary; + lead +------------ + + 03-05-2009 + 09-22-2010 + 09-22-2010 + 09-22-2010 + 11-17-2009 + 11-17-2009 + 11-17-2009 + 11-17-2009 + 11-17-2009 +(10 rows) + -- cleanup DROP TABLE empsalary; diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql index 769be0f..cc9b583 100644 --- a/src/test/regress/sql/window.sql +++ b/src/test/regress/sql/window.sql @@ -6,20 +6,21 @@ CREATE TEMPORARY TABLE empsalary ( depname varchar, empno bigint, salary int, - enroll_date date + enroll_date date, + term_date date ); INSERT INTO empsalary VALUES -('develop', 10, 5200, '2007-08-01'), -('sales', 1, 5000, '2006-10-01'), -('personnel', 5, 3500, '2007-12-10'), -('sales', 4, 4800, '2007-08-08'), -('personnel', 2, 3900, '2006-12-23'), -('develop', 7, 4200, '2008-01-01'), -('develop', 9, 4500, '2008-01-01'), -('sales', 3, 4800, '2007-08-01'), -('develop', 8, 6000, '2006-10-01'), -('develop', 11, 5200, '2007-08-15'); +('develop', 10, 5200, '2007-08-01', null), +('sales', 1, 5000, '2006-10-01', null), +('personnel', 5, 3500, '2007-12-10', null), +('sales', 4, 4800, '2007-08-08', '2010-09-22'), +('personnel', 2, 3900, '2006-12-23', null), +('develop', 7, 4200, '2008-01-01', null), +('develop', 9, 4500, '2008-01-01', null), +('sales', 3, 4800, '2007-08-01', '2009-03-05'), +('develop', 8, 6000, '2006-10-01', '2009-11-17'), +('develop', 11, 5200, '2007-08-15', null); SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary; @@ -264,5 +265,18 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1; SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1; +-- test null behaviour +SELECT lag(term_date) OVER (ORDER BY empno) FROM empsalary; + +SELECT lag(term_date) RESPECT NULLS OVER (ORDER BY empno) FROM empsalary; + +SELECT lag(term_date) IGNORE NULLS OVER (ORDER BY empno) FROM empsalary; + +SELECT lead(term_date) OVER (ORDER BY empno) FROM empsalary; + +SELECT lead(term_date) RESPECT NULLS OVER (ORDER BY empno) FROM empsalary; + +SELECT lead(term_date) IGNORE NULLS OVER (ORDER BY empno) FROM empsalary; + -- cleanup DROP TABLE empsalary;