282 lines
9.3 KiB
Diff
282 lines
9.3 KiB
Diff
|
Subject: [PATCH] Prevent aliases of window functions expressions from being
|
||
|
used as arguments to aggregate or other window functions.
|
||
|
|
||
|
---
|
||
|
src/resolve.c | 21 ++++++---
|
||
|
src/sqliteInt.h | 2 +
|
||
|
test/windowerr.tcl | 59 ++++++++++++++++++++++++++
|
||
|
test/windowerr.test | 99 ++++++++++++++++++++++++++++++++++++++++++
|
||
|
4 files changed, 176 insertions(+), 5 deletions(-)
|
||
|
create mode 100644 test/windowerr.tcl
|
||
|
create mode 100644 test/windowerr.test
|
||
|
|
||
|
diff --git a/src/resolve.c b/src/resolve.c
|
||
|
index 0c7dfc0..cdcf4d9 100644
|
||
|
--- a/src/resolve.c
|
||
|
+++ b/src/resolve.c
|
||
|
@@ -436,6 +436,10 @@ static int lookupName(
|
||
|
sqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs);
|
||
|
return WRC_Abort;
|
||
|
}
|
||
|
+ if( (pNC->ncFlags&NC_AllowWin)==0 && ExprHasProperty(pOrig, EP_Win) ){
|
||
|
+ sqlite3ErrorMsg(pParse, "misuse of aliased window function %s",zAs);
|
||
|
+ return WRC_Abort;
|
||
|
+ }
|
||
|
if( sqlite3ExprVectorSize(pOrig)!=1 ){
|
||
|
sqlite3ErrorMsg(pParse, "row value misused");
|
||
|
return WRC_Abort;
|
||
|
@@ -707,6 +711,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||
|
const char *zId; /* The function name. */
|
||
|
FuncDef *pDef; /* Information about the function */
|
||
|
u8 enc = ENC(pParse->db); /* The database encoding */
|
||
|
+ int savedAllowFlags = (pNC->ncFlags & (NC_AllowAgg | NC_AllowWin));
|
||
|
|
||
|
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
|
||
|
zId = pExpr->u.zToken;
|
||
|
@@ -828,8 +833,11 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||
|
pNC->nErr++;
|
||
|
}
|
||
|
if( is_agg ){
|
||
|
+ /* Window functions may not be arguments of aggregate functions.
|
||
|
+ ** Or arguments of other window functions. But aggregate functions
|
||
|
+ ** may be arguments for window functions. */
|
||
|
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||
|
- pNC->ncFlags &= ~(pExpr->y.pWin ? NC_AllowWin : NC_AllowAgg);
|
||
|
+ pNC->ncFlags &= ~(NC_AllowWin | (!pExpr->y.pWin ? NC_AllowAgg : 0));
|
||
|
#else
|
||
|
pNC->ncFlags &= ~NC_AllowAgg;
|
||
|
#endif
|
||
|
@@ -850,7 +858,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||
|
pExpr->y.pWin->pNextWin = pSel->pWin;
|
||
|
pSel->pWin = pExpr->y.pWin;
|
||
|
}
|
||
|
- pNC->ncFlags |= NC_AllowWin;
|
||
|
+ pNC->ncFlags |= NC_HasWin;
|
||
|
}else
|
||
|
#endif /* SQLITE_OMIT_WINDOWFUNC */
|
||
|
{
|
||
|
@@ -868,8 +876,8 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||
|
pNC2->ncFlags |= NC_HasAgg | (pDef->funcFlags & SQLITE_FUNC_MINMAX);
|
||
|
|
||
|
}
|
||
|
- pNC->ncFlags |= NC_AllowAgg;
|
||
|
}
|
||
|
+ pNC->ncFlags |= savedAllowFlags;
|
||
|
}
|
||
|
/* FIX ME: Compute pExpr->affinity based on the expected return
|
||
|
** type of the function
|
||
|
@@ -1573,8 +1581,8 @@ int sqlite3ResolveExprNames(
|
||
|
Walker w;
|
||
|
|
||
|
if( pExpr==0 ) return SQLITE_OK;
|
||
|
- savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg);
|
||
|
- pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg);
|
||
|
+ savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin);
|
||
|
+ pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin);
|
||
|
w.pParse = pNC->pParse;
|
||
|
w.xExprCallback = resolveExprStep;
|
||
|
w.xSelectCallback = resolveSelectStep;
|
||
|
@@ -1593,6 +1601,9 @@ int sqlite3ResolveExprNames(
|
||
|
if( pNC->ncFlags & NC_HasAgg ){
|
||
|
ExprSetProperty(pExpr, EP_Agg);
|
||
|
}
|
||
|
+ if( pNC->ncFlags & NC_HasWin ){
|
||
|
+ ExprSetProperty(pExpr, EP_Win);
|
||
|
+ }
|
||
|
pNC->ncFlags |= savedHasAgg;
|
||
|
return pNC->nErr>0 || w.pParse->nErr>0;
|
||
|
}
|
||
|
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
|
||
|
index 5f5f3cc..b7d3571 100644
|
||
|
--- a/src/sqliteInt.h
|
||
|
+++ b/src/sqliteInt.h
|
||
|
@@ -2517,6 +2517,7 @@ struct Expr {
|
||
|
#define EP_Alias 0x400000 /* Is an alias for a result set column */
|
||
|
#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */
|
||
|
#define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */
|
||
|
+#define EP_Win 0x8000000 /* Contains window functions */
|
||
|
|
||
|
/*
|
||
|
** The EP_Propagate mask is a set of properties that automatically propagate
|
||
|
@@ -2773,6 +2774,7 @@ struct NameContext {
|
||
|
#define NC_MinMaxAgg 0x1000 /* min/max aggregates seen. See note above */
|
||
|
#define NC_Complex 0x2000 /* True if a function or subquery seen */
|
||
|
#define NC_AllowWin 0x4000 /* Window functions are allowed here */
|
||
|
+#define NC_HasWin 0x8000 /* One or more window functions seen */
|
||
|
|
||
|
/*
|
||
|
** An instance of the following object describes a single ON CONFLICT
|
||
|
diff --git a/test/windowerr.tcl b/test/windowerr.tcl
|
||
|
new file mode 100644
|
||
|
index 0000000..80f464d
|
||
|
--- /dev/null
|
||
|
+++ b/test/windowerr.tcl
|
||
|
@@ -0,0 +1,59 @@
|
||
|
+# 2018 May 19
|
||
|
+#
|
||
|
+# The author disclaims copyright to this source code. In place of
|
||
|
+# a legal notice, here is a blessing:
|
||
|
+#
|
||
|
+# May you do good and not evil.
|
||
|
+# May you find forgiveness for yourself and forgive others.
|
||
|
+# May you share freely, never taking more than you give.
|
||
|
+#
|
||
|
+#***********************************************************************
|
||
|
+#
|
||
|
+
|
||
|
+source [file join [file dirname $argv0] pg_common.tcl]
|
||
|
+
|
||
|
+#=========================================================================
|
||
|
+
|
||
|
+start_test windowerr "2019 March 01"
|
||
|
+ifcapable !windowfunc
|
||
|
+
|
||
|
+execsql_test 1.0 {
|
||
|
+ DROP TABLE IF EXISTS t1;
|
||
|
+ CREATE TABLE t1(a INTEGER, b INTEGER);
|
||
|
+ INSERT INTO t1 VALUES(1, 1);
|
||
|
+ INSERT INTO t1 VALUES(2, 2);
|
||
|
+ INSERT INTO t1 VALUES(3, 3);
|
||
|
+ INSERT INTO t1 VALUES(4, 4);
|
||
|
+ INSERT INTO t1 VALUES(5, 5);
|
||
|
+}
|
||
|
+
|
||
|
+foreach {tn frame} {
|
||
|
+ 1 "ORDER BY a ROWS BETWEEN -1 PRECEDING AND 1 FOLLOWING"
|
||
|
+ 2 "ORDER BY a ROWS BETWEEN 1 PRECEDING AND -1 FOLLOWING"
|
||
|
+
|
||
|
+ 3 "ORDER BY a RANGE BETWEEN -1 PRECEDING AND 1 FOLLOWING"
|
||
|
+ 4 "ORDER BY a RANGE BETWEEN 1 PRECEDING AND -1 FOLLOWING"
|
||
|
+
|
||
|
+ 5 "ORDER BY a GROUPS BETWEEN -1 PRECEDING AND 1 FOLLOWING"
|
||
|
+ 6 "ORDER BY a GROUPS BETWEEN 1 PRECEDING AND -1 FOLLOWING"
|
||
|
+
|
||
|
+ 7 "ORDER BY a,b RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING"
|
||
|
+
|
||
|
+ 8 "PARTITION BY a RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING"
|
||
|
+} {
|
||
|
+ errorsql_test 1.$tn "
|
||
|
+ SELECT a, sum(b) OVER (
|
||
|
+ $frame
|
||
|
+ ) FROM t1 ORDER BY 1
|
||
|
+ "
|
||
|
+}
|
||
|
+errorsql_test 2.1 {
|
||
|
+ SELECT sum( sum(a) OVER () ) FROM t1;
|
||
|
+}
|
||
|
+
|
||
|
+errorsql_test 2.2 {
|
||
|
+ SELECT sum(a) OVER () AS xyz FROM t1 ORDER BY sum(xyz);
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+finish_test
|
||
|
diff --git a/test/windowerr.test b/test/windowerr.test
|
||
|
new file mode 100644
|
||
|
index 0000000..97dae64
|
||
|
--- /dev/null
|
||
|
+++ b/test/windowerr.test
|
||
|
@@ -0,0 +1,99 @@
|
||
|
+# 2019 March 01
|
||
|
+#
|
||
|
+# The author disclaims copyright to this source code. In place of
|
||
|
+# a legal notice, here is a blessing:
|
||
|
+#
|
||
|
+# May you do good and not evil.
|
||
|
+# May you find forgiveness for yourself and forgive others.
|
||
|
+# May you share freely, never taking more than you give.
|
||
|
+#
|
||
|
+#***********************************************************************
|
||
|
+# This file implements regression tests for SQLite library.
|
||
|
+#
|
||
|
+
|
||
|
+####################################################
|
||
|
+# DO NOT EDIT! THIS FILE IS AUTOMATICALLY GENERATED!
|
||
|
+####################################################
|
||
|
+
|
||
|
+set testdir [file dirname $argv0]
|
||
|
+source $testdir/tester.tcl
|
||
|
+set testprefix windowerr
|
||
|
+
|
||
|
+ifcapable !windowfunc { finish_test ; return }
|
||
|
+do_execsql_test 1.0 {
|
||
|
+ DROP TABLE IF EXISTS t1;
|
||
|
+ CREATE TABLE t1(a INTEGER, b INTEGER);
|
||
|
+ INSERT INTO t1 VALUES(1, 1);
|
||
|
+ INSERT INTO t1 VALUES(2, 2);
|
||
|
+ INSERT INTO t1 VALUES(3, 3);
|
||
|
+ INSERT INTO t1 VALUES(4, 4);
|
||
|
+ INSERT INTO t1 VALUES(5, 5);
|
||
|
+} {}
|
||
|
+
|
||
|
+# PG says ERROR: frame starting offset must not be negative
|
||
|
+do_test 1.1 { catch { execsql {
|
||
|
+ SELECT a, sum(b) OVER (
|
||
|
+ ORDER BY a ROWS BETWEEN -1 PRECEDING AND 1 FOLLOWING
|
||
|
+ ) FROM t1 ORDER BY 1
|
||
|
+} } } 1
|
||
|
+
|
||
|
+# PG says ERROR: frame ending offset must not be negative
|
||
|
+do_test 1.2 { catch { execsql {
|
||
|
+ SELECT a, sum(b) OVER (
|
||
|
+ ORDER BY a ROWS BETWEEN 1 PRECEDING AND -1 FOLLOWING
|
||
|
+ ) FROM t1 ORDER BY 1
|
||
|
+} } } 1
|
||
|
+
|
||
|
+# PG says ERROR: invalid preceding or following size in window function
|
||
|
+do_test 1.3 { catch { execsql {
|
||
|
+ SELECT a, sum(b) OVER (
|
||
|
+ ORDER BY a RANGE BETWEEN -1 PRECEDING AND 1 FOLLOWING
|
||
|
+ ) FROM t1 ORDER BY 1
|
||
|
+} } } 1
|
||
|
+
|
||
|
+# PG says ERROR: invalid preceding or following size in window function
|
||
|
+do_test 1.4 { catch { execsql {
|
||
|
+ SELECT a, sum(b) OVER (
|
||
|
+ ORDER BY a RANGE BETWEEN 1 PRECEDING AND -1 FOLLOWING
|
||
|
+ ) FROM t1 ORDER BY 1
|
||
|
+} } } 1
|
||
|
+
|
||
|
+# PG says ERROR: frame starting offset must not be negative
|
||
|
+do_test 1.5 { catch { execsql {
|
||
|
+ SELECT a, sum(b) OVER (
|
||
|
+ ORDER BY a GROUPS BETWEEN -1 PRECEDING AND 1 FOLLOWING
|
||
|
+ ) FROM t1 ORDER BY 1
|
||
|
+} } } 1
|
||
|
+
|
||
|
+# PG says ERROR: frame ending offset must not be negative
|
||
|
+do_test 1.6 { catch { execsql {
|
||
|
+ SELECT a, sum(b) OVER (
|
||
|
+ ORDER BY a GROUPS BETWEEN 1 PRECEDING AND -1 FOLLOWING
|
||
|
+ ) FROM t1 ORDER BY 1
|
||
|
+} } } 1
|
||
|
+
|
||
|
+# PG says ERROR: RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column
|
||
|
+do_test 1.7 { catch { execsql {
|
||
|
+ SELECT a, sum(b) OVER (
|
||
|
+ ORDER BY a,b RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING
|
||
|
+ ) FROM t1 ORDER BY 1
|
||
|
+} } } 1
|
||
|
+
|
||
|
+# PG says ERROR: RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column
|
||
|
+do_test 1.8 { catch { execsql {
|
||
|
+ SELECT a, sum(b) OVER (
|
||
|
+ PARTITION BY a RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING
|
||
|
+ ) FROM t1 ORDER BY 1
|
||
|
+} } } 1
|
||
|
+
|
||
|
+# PG says ERROR: aggregate function calls cannot contain window function calls
|
||
|
+do_test 2.1 { catch { execsql {
|
||
|
+ SELECT sum( sum(a) OVER () ) FROM t1;
|
||
|
+} } } 1
|
||
|
+
|
||
|
+# PG says ERROR: column "xyz" does not exist
|
||
|
+do_test 2.2 { catch { execsql {
|
||
|
+ SELECT sum(a) OVER () AS xyz FROM t1 ORDER BY sum(xyz);
|
||
|
+} } } 1
|
||
|
+
|
||
|
+finish_test
|
||
|
--
|
||
|
2.24.1
|
||
|
|