diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..535a8dc Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/backend/.DS_Store b/src/backend/.DS_Store new file mode 100644 index 0000000..327792b Binary files /dev/null and b/src/backend/.DS_Store differ diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 3bc42ba..eb42901 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -1986,6 +1986,40 @@ WinGetPartitionLocalMemory(WindowObject winobj, Size sz) } /* + * WinGetResultDatumCopy + * Gets a copy of the given datum. + * + * This uses the window's per-function ByVal and TypeLen information + * when copying the datum. + */ +Datum +WinGetResultDatumCopy(WindowObject winobj, Datum datum) +{ + WindowStatePerFunc perfunc; + + Assert(WindowObjectIsValid(winobj)); + perfunc = winobj->winstate->perfunc; + return datumCopy(datum, perfunc->resulttypeByVal, perfunc->resulttypeLen); +} + +/* + * WinFreeResultDatumCopy + * Frees a Datum previously created by WinGetResultDatumCopy. + * + * This uses the window's per-function ByVal and TypeLen information + * when copying the datum. + */ +void +WinFreeResultDatumCopy(WindowObject winobj, Datum datum) +{ + WindowStatePerFunc perfunc; + + Assert(WindowObjectIsValid(winobj)); + perfunc = winobj->winstate->perfunc; + datumFree(datum, perfunc->resulttypeByVal, perfunc->resulttypeLen); +} + +/* * WinGetCurrentPosition * Return the current row's position (counting from 0) within the current * partition. @@ -1996,6 +2030,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/.DS_Store b/src/backend/utils/.DS_Store new file mode 100644 index 0000000..cbbea58 Binary files /dev/null and b/src/backend/utils/.DS_Store differ diff --git a/src/backend/utils/adt/datum.c b/src/backend/utils/adt/datum.c index 612b7ef..e72f5a9 100644 --- a/src/backend/utils/adt/datum.c +++ b/src/backend/utils/adt/datum.c @@ -129,7 +129,7 @@ datumCopy(Datum value, bool typByVal, int typLen) realSize = datumGetSize(value, typByVal, typLen); s = (char *) palloc(realSize); - memcpy(s, DatumGetPointer(value), realSize); + memcpy(s, (value), realSize); res = PointerGetDatum(s); } return res; @@ -144,7 +144,6 @@ datumCopy(Datum value, bool typByVal, int typLen) * ONLY datums created by "datumCopy" can be freed! *------------------------------------------------------------------------- */ -#ifdef NOT_USED void datumFree(Datum value, bool typByVal, int typLen) { @@ -155,7 +154,6 @@ datumFree(Datum value, bool typByVal, int typLen) pfree(s); } } -#endif /*------------------------------------------------------------------------- * datumIsEqual diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c index 2f171ac..fea772f 100644 --- a/src/backend/utils/adt/windowfuncs.c +++ b/src/backend/utils/adt/windowfuncs.c @@ -25,6 +25,15 @@ typedef struct rank_context } rank_context; /* + * structure for IGNORE NULLS / RESPECT NULLS semantics + */ +typedef struct leadlag_context +{ + Datum last; /* last non-null result */ + bool last_isnull; +} leadlag_context; + +/* * ntile process information */ typedef struct @@ -292,6 +301,8 @@ leadlag_common(FunctionCallInfo fcinfo, Datum result; bool isnull; bool isout; + bool ignore_nulls; + leadlag_context* context; if (withoffset) { @@ -322,8 +333,47 @@ 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. + */ + context = (leadlag_context *) + WinGetPartitionLocalMemory(winobj, sizeof(leadlag_context)); if(isnull) + { + if(context->last != NULL) + { + /* restore the stashed copy */ + result = context->last; + isnull = context->last_isnull; + } + } + else + { + if(context->last != NULL) + { + /* + * This step is not strictly necessary as the Datum copies are + * allocated in a context that'll be discarded after this query. + * However, we'd like to avoid a large memory spike during the + * query (as we'd get if we kept a copy of all the non-null + * results for the duration of the query) so we'll free the + * Datum copies as we go along: + */ + WinFreeResultDatumCopy(winobj, context->last); + } + context->last = WinGetResultDatumCopy(winobj, result); + context->last_isnull = isnull; + } + } + + if (isnull) + { PG_RETURN_NULL(); + } PG_RETURN_DATUM(result); } diff --git a/src/include/.DS_Store b/src/include/.DS_Store new file mode 100644 index 0000000..d5711fe Binary files /dev/null and b/src/include/.DS_Store differ 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..73524ca 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); @@ -61,4 +63,7 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno, extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull); +extern Datum WinGetResultDatumCopy(WindowObject winobj, Datum datum); +extern void WinFreeResultDatumCopy(WindowObject winobj, Datum datum); + #endif /* WINDOWAPI_H */ diff --git a/src/interfaces/.DS_Store b/src/interfaces/.DS_Store new file mode 100644 index 0000000..c8d03e0 Binary files /dev/null and b/src/interfaces/.DS_Store differ diff --git a/src/interfaces/ecpg/.DS_Store b/src/interfaces/ecpg/.DS_Store new file mode 100644 index 0000000..b3e3f35 Binary files /dev/null and b/src/interfaces/ecpg/.DS_Store differ 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/.DS_Store b/src/test/.DS_Store new file mode 100644 index 0000000..7c9864e Binary files /dev/null and b/src/test/.DS_Store differ diff --git a/src/test/regress/.DS_Store b/src/test/regress/.DS_Store new file mode 100644 index 0000000..a623e62 Binary files /dev/null and b/src/test/regress/.DS_Store differ diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 752c7b4..41d76a6 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -5,19 +5,21 @@ CREATE TEMPORARY TABLE empsalary ( depname varchar, empno bigint, salary int, - enroll_date date + enroll_date date, + term_date date, + favourite_animal text ); 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, null), +('sales', 1, 5000, '2006-10-01', null, 'frog'), +('personnel', 5, 3500, '2007-12-10', null, null), +('sales', 4, 4800, '2007-08-08', '2010-09-22', 'chicken'), +('personnel', 2, 3900, '2006-12-23', null, null), +('develop', 7, 4200, '2008-01-01', null, null), +('develop', 9, 4500, '2008-01-01', null, 'gorilla'), +('sales', 3, 4800, '2007-08-01', '2009-03-05', null), +('develop', 8, 6000, '2006-10-01', '2009-11-17', 'tiger'), +('develop', 11, 5200, '2007-08-15', null, null); SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary; depname | empno | salary | sum -----------+-------+--------+------- @@ -1020,5 +1022,114 @@ 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: (1) lags +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) + +-- a numeric (date) column +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) + +-- a text column +SELECT lag(favourite_animal) IGNORE NULLS OVER (ORDER BY empno) FROM empsalary; + lag +--------- + + frog + frog + frog + chicken + chicken + chicken + tiger + gorilla + gorilla +(10 rows) + +-- (2) leads +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..67e9a9a 100644 --- a/src/test/regress/sql/window.sql +++ b/src/test/regress/sql/window.sql @@ -6,20 +6,22 @@ CREATE TEMPORARY TABLE empsalary ( depname varchar, empno bigint, salary int, - enroll_date date + enroll_date date, + term_date date, + favourite_animal text ); 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, null), +('sales', 1, 5000, '2006-10-01', null, 'frog'), +('personnel', 5, 3500, '2007-12-10', null, null), +('sales', 4, 4800, '2007-08-08', '2010-09-22', 'chicken'), +('personnel', 2, 3900, '2006-12-23', null, null), +('develop', 7, 4200, '2008-01-01', null, null), +('develop', 9, 4500, '2008-01-01', null, 'gorilla'), +('sales', 3, 4800, '2007-08-01', '2009-03-05', null), +('develop', 8, 6000, '2006-10-01', '2009-11-17', 'tiger'), +('develop', 11, 5200, '2007-08-15', null, null); SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary; @@ -264,5 +266,25 @@ 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: (1) lags + +SELECT lag(term_date) OVER (ORDER BY empno) FROM empsalary; + +SELECT lag(term_date) RESPECT NULLS OVER (ORDER BY empno) FROM empsalary; + +-- a numeric (date) column +SELECT lag(term_date) IGNORE NULLS OVER (ORDER BY empno) FROM empsalary; + +-- a text column +SELECT lag(favourite_animal) IGNORE NULLS OVER (ORDER BY empno) FROM empsalary; + +-- (2) leads + +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;