Added LIMIT/OFFSET functionality including new regression test for it.
authorJan Wieck <JanWieck@Yahoo.com>
Mon, 8 Feb 1999 14:14:32 +0000 (14:14 +0000)
committerJan Wieck <JanWieck@Yahoo.com>
Mon, 8 Feb 1999 14:14:32 +0000 (14:14 +0000)
Removed CURRENT keyword for rule queries and changed rules regression
accordingly. CURRENT has beed announced to disappear in v6.5.

Jan

15 files changed:
src/backend/commands/command.c
src/backend/executor/execMain.c
src/backend/executor/functions.c
src/backend/executor/spi.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/keywords.c
src/backend/rewrite/rewriteDefine.c
src/backend/tcop/pquery.c
src/include/executor/executor.h
src/test/regress/expected/limit.out [new file with mode: 0644]
src/test/regress/expected/rules.out
src/test/regress/sql/limit.sql [new file with mode: 0644]
src/test/regress/sql/rules.sql
src/test/regress/sql/tests

index 1a65f4d4d0d829e90797d79c8bbbee2597ebdf07..310325d2f816a922641fb402a6bc7188e1d69061 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.36 1999/02/03 21:16:02 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.37 1999/02/08 14:14:08 wieck Exp $
  *
  * NOTES
  *   The PortalExecutorHeapMemory crap needs to be eliminated
@@ -40,6 +40,7 @@
 #include "utils/portal.h"
 #include "utils/syscache.h"
 #include "miscadmin.h"
+#include "string.h"
 
 /* ----------------
  *     PortalExecutorHeapMemory stuff
@@ -102,6 +103,7 @@ PerformPortalFetch(char *name,
    int         feature;
    QueryDesc  *queryDesc;
    MemoryContext context;
+   Const       limcount;
 
    /* ----------------
     *  sanity checks
@@ -113,6 +115,21 @@ PerformPortalFetch(char *name,
        return;
    }
 
+   /* ----------------
+    *  Create a const node from the given count value
+    * ----------------
+    */
+   memset(&limcount, 0, sizeof(limcount));
+   limcount.type       = T_Const;
+   limcount.consttype  = INT4OID;
+   limcount.constlen   = sizeof(int4);
+   limcount.constvalue = (Datum)count;
+   limcount.constisnull    = FALSE;
+   limcount.constbyval = TRUE;
+   limcount.constisset = FALSE;
+   limcount.constiscast    = FALSE;
+
+
    /* ----------------
     *  get the portal from the portal name
     * ----------------
@@ -176,7 +193,8 @@ PerformPortalFetch(char *name,
    PortalExecutorHeapMemory = (MemoryContext)
        PortalGetHeapMemory(portal);
 
-   ExecutorRun(queryDesc, PortalGetState(portal), feature, count);
+   ExecutorRun(queryDesc, PortalGetState(portal), feature, 
+               (Node *)NULL, (Node *)&limcount);
 
    if (dest == None)           /* MOVE */
        pfree(queryDesc);
index 5ce90902b4cabea63239cde7700c8e692e78f0c9..e11869ce783d9413ce1161abc4be3468764c4206 100644 (file)
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.75 1999/02/07 16:17:11 wieck Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.76 1999/02/08 14:14:09 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -60,21 +60,27 @@ void ExecCheckPerms(CmdType operation, int resultRelation, List *rangeTable,
 
 
 /* decls for local routines only used within this module */
-static TupleDesc InitPlan(CmdType operation, Query *parseTree,
-        Plan *plan, EState *estate);
-static void EndPlan(Plan *plan, EState *estate);
+static TupleDesc InitPlan(CmdType operation,
+                       Query *parseTree,
+                       Plan *plan,
+                       EState *estate);
+static void EndPlan(Plan *plan,
+                       EState *estate);
 static TupleTableSlot *ExecutePlan(EState *estate, Plan *plan,
-           CmdType operation, int numberTuples, ScanDirection direction,
-           DestReceiver *destfunc);
+                       CmdType operation, 
+                       int offsetTuples,
+                       int numberTuples,
+                       ScanDirection direction,
+                       DestReceiver *destfunc);
 static void ExecRetrieve(TupleTableSlot *slot,
-                        DestReceiver *destfunc,
-                        EState *estate);
+                       DestReceiver *destfunc,
+                       EState *estate);
 static void ExecAppend(TupleTableSlot *slot, ItemPointer tupleid,
-          EState *estate);
+                       EState *estate);
 static void ExecDelete(TupleTableSlot *slot, ItemPointer tupleid,
-          EState *estate);
+                       EState *estate);
 static void ExecReplace(TupleTableSlot *slot, ItemPointer tupleid,
-           EState *estate);
+                       EState *estate);
 
 TupleTableSlot *EvalPlanQual(EState *estate, Index rti, ItemPointer tid);
 static TupleTableSlot *EvalPlanQualNext(EState *estate);
@@ -187,13 +193,16 @@ ExecutorStart(QueryDesc *queryDesc, EState *estate)
  * ----------------------------------------------------------------
  */
 TupleTableSlot *
-ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
+ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, 
+                   Node *limoffset, Node *limcount)
 {
    CmdType         operation;
    Plan           *plan;
    TupleTableSlot *result;
    CommandDest     dest;
    DestReceiver   *destfunc;
+   int             offset = 0;
+   int             count = 0;
 
    /******************
     *  sanity checks
@@ -222,6 +231,96 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
     */
    (*destfunc->setup) (destfunc, (TupleDesc) NULL);
 
+    /******************
+     *  if given get the offset of the LIMIT clause
+     ******************
+     */
+    if (limoffset != NULL)
+    {
+        Const       *coffset;
+        Param       *poffset;
+        ParamListInfo   paramLI;
+       int  i;
+       switch (nodeTag(limoffset))
+       {
+           case T_Const:
+               coffset = (Const *)limoffset;
+               offset = (int)(coffset->constvalue);
+               break;
+           case T_Param:
+               poffset = (Param *)limoffset;
+               paramLI = estate->es_param_list_info;
+               if (paramLI == NULL)
+                   elog(ERROR, "parameter for limit offset not in executor state");
+               for (i = 0; paramLI[i].kind != PARAM_INVALID; i++)
+               {
+                   if (paramLI[i].kind == PARAM_NUM && paramLI[i].id == poffset->paramid)
+                       break;
+               }
+               if (paramLI[i].kind == PARAM_INVALID)
+                   elog(ERROR, "parameter for limit offset not in executor state");
+               if (paramLI[i].isnull)
+                   elog(ERROR, "limit offset cannot be NULL value");
+               offset = (int)(paramLI[i].value);
+               break;
+           default:
+               elog(ERROR, "unexpected node type %d as limit offset", nodeTag(limoffset));
+       }
+       if (offset < 0)
+           elog(ERROR, "limit offset cannot be negative");
+   }
+   /******************
+    *  if given get the count of the LIMIT clause
+    ******************
+    */
+   if (limcount != NULL)
+   {
+       Const       *ccount;
+       Param       *pcount;
+       ParamListInfo   paramLI;
+       int     i;
+       switch (nodeTag(limcount))
+       {
+           case T_Const:
+               ccount = (Const *)limcount;
+               count = (int)(ccount->constvalue);
+               break;
+           case T_Param:
+               pcount = (Param *)limcount;
+               paramLI = estate->es_param_list_info;
+               if (paramLI == NULL)
+                   elog(ERROR, "parameter for limit count not in executor state");
+               for (i = 0; paramLI[i].kind != PARAM_INVALID; i++)
+               {
+                   if (paramLI[i].kind == PARAM_NUM && paramLI[i].id == pcount->paramid)
+                       break;
+               }
+               if (paramLI[i].kind == PARAM_INVALID)
+                   elog(ERROR, "parameter for limit count not in executor state");
+               if (paramLI[i].isnull)
+                   elog(ERROR, "limit count cannot be NULL value");
+               count = (int)(paramLI[i].value);
+               break;
+           default:
+               elog(ERROR, "unexpected node type %d as limit count", nodeTag(limcount));
+       }
+       if (count < 0)
+           elog(ERROR, "limit count cannot be negative");
+   }
+
    switch (feature)
    {
 
@@ -229,7 +328,8 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
            result = ExecutePlan(estate,
                                 plan,
                                 operation,
-                                ALL_TUPLES,
+                                offset,
+                                count,
                                 ForwardScanDirection,
                                 destfunc);
            break;
@@ -237,6 +337,7 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
            result = ExecutePlan(estate,
                                 plan,
                                 operation,
+                                offset,
                                 count,
                                 ForwardScanDirection,
                                 destfunc);
@@ -250,6 +351,7 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
            result = ExecutePlan(estate,
                                 plan,
                                 operation,
+                                offset,
                                 count,
                                 BackwardScanDirection,
                                 destfunc);
@@ -264,6 +366,7 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count)
            result = ExecutePlan(estate,
                                 plan,
                                 operation,
+                                0,
                                 ONE_TUPLE,
                                 ForwardScanDirection,
                                 destfunc);
@@ -784,6 +887,7 @@ static TupleTableSlot *
 ExecutePlan(EState *estate,
            Plan *plan,
            CmdType operation,
+           int offsetTuples,
            int numberTuples,
            ScanDirection direction,
            DestReceiver* destfunc)
@@ -845,6 +949,20 @@ lnext:;
            break;
        }
 
+       /******************
+        *  For now we completely execute the plan and skip
+        *  result tuples if requested by LIMIT offset.
+        *  Finally we should try to do it in deeper levels
+        *  if possible (during index scan)
+        *  - Jan
+        ******************
+        */
+       if (offsetTuples > 0)
+       {
+           --offsetTuples;
+           continue;
+       }
+
        /******************
         *      if we have a junk filter, then project a new
         *      tuple with the junk removed.
index 51ea6655e9dddfcfd7a4925377b93cecc44cadda..7108a1cab5287bffc207d1e083692cb2153a0d80 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.22 1999/02/03 21:16:12 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.23 1999/02/08 14:14:09 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -130,6 +130,9 @@ init_execution_state(FunctionCachePtr fcache,
                                     None);
        estate = CreateExecutorState();
 
+       if (queryTree->limitOffset != NULL || queryTree->limitCount != NULL)
+           elog(ERROR, "LIMIT clause from SQL functions not yet implemented");
+
        if (nargs > 0)
        {
            int         i;
@@ -199,7 +202,7 @@ postquel_getnext(execution_state *es)
 
    feature = (LAST_POSTQUEL_COMMAND(es)) ? EXEC_RETONE : EXEC_RUN;
 
-   return ExecutorRun(es->qd, es->estate, feature, 0);
+   return ExecutorRun(es->qd, es->estate, feature, (Node *)NULL, (Node *)NULL);
 }
 
 static void
index 7ca2a6c21e507972d953bbe821171e7d5c9dbfe6..397f68a4055ea32fdcc80f6589fb7122d4e2f9ea 100644 (file)
@@ -3,7 +3,7 @@
  * spi.c--
  *             Server Programming Interface
  *
- * $Id: spi.c,v 1.32 1999/01/27 16:15:20 wieck Exp $
+ * $Id: spi.c,v 1.33 1999/02/08 14:14:10 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -779,6 +779,8 @@ _SPI_pquery(QueryDesc *queryDesc, EState *state, int tcount)
    bool        isRetrieveIntoRelation = false;
    char       *intoName = NULL;
    int         res;
+   Const       tcount_const;
+   Node        *count = NULL;
 
    switch (operation)
    {
@@ -813,6 +815,39 @@ _SPI_pquery(QueryDesc *queryDesc, EState *state, int tcount)
            return SPI_ERROR_OPUNKNOWN;
    }
 
+   /* ----------------
+    * Get the query LIMIT tuple count
+    * ----------------
+    */
+   if (parseTree->limitCount != NULL)
+   {
+       /* ----------------
+        * A limit clause in the parsetree overrides the
+        * tcount parameter
+        * ----------------
+        */
+       count = parseTree->limitCount;
+   }
+   else
+   {
+       /* ----------------
+        * No LIMIT clause in parsetree. Use a local Const node
+        * to put tcount into it
+        * ----------------
+        */
+       memset(&tcount_const, 0, sizeof(tcount_const));
+       tcount_const.type          = T_Const;
+       tcount_const.consttype    = INT4OID;
+       tcount_const.constlen      = sizeof(int4);
+       tcount_const.constvalue  = (Datum)tcount;
+       tcount_const.constisnull    = FALSE;
+       tcount_const.constbyval  = TRUE;
+       tcount_const.constisset  = FALSE;
+       tcount_const.constiscast    = FALSE;
+       count = (Node *)&tcount_const;
+   }
    if (state == NULL)          /* plan preparation */
        return res;
 #ifdef SPI_EXECUTOR_STATS
@@ -833,7 +868,7 @@ _SPI_pquery(QueryDesc *queryDesc, EState *state, int tcount)
        return SPI_OK_CURSOR;
    }
 
-   ExecutorRun(queryDesc, state, EXEC_FOR, tcount);
+   ExecutorRun(queryDesc, state, EXEC_FOR, parseTree->limitOffset, count);
 
    _SPI_current->processed = state->es_processed;
    if (operation == CMD_SELECT && queryDesc->dest == SPI)
index 6232a2d067d51e0a8839725e7cee066420150812..ada18bb71d3cc240e5098c97c1db6278ac1ff1b0 100644 (file)
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- *  $Id: analyze.c,v 1.97 1999/02/02 03:44:32 momjian Exp $
+ *  $Id: analyze.c,v 1.98 1999/02/08 14:14:11 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -205,7 +205,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 
        case T_SelectStmt:
            if (!((SelectStmt *) parseTree)->portalname)
+           {
                result = transformSelectStmt(pstate, (SelectStmt *) parseTree);
+               result->limitOffset = ((SelectStmt *)parseTree)->limitOffset;
+               result->limitCount = ((SelectStmt *)parseTree)->limitCount;
+           }
            else
                result = transformCursorStmt(pstate, (SelectStmt *) parseTree);
            break;
index e88b9073084cc8dc06468c7fc2553490da650f4e..b649ceba36cf748e1f26f6bf6252fc66271923b0 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.53 1999/02/07 19:02:19 wieck Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.54 1999/02/08 14:14:12 wieck Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -48,6 +48,7 @@
 #include "storage/lmgr.h"
 #include "utils/numeric.h"
 #include "parser/analyze.h"
+#include "catalog/pg_type.h"
 
 #ifdef MULTIBYTE
 #include "mb/pg_wchar.h"
@@ -168,7 +169,8 @@ Oid param_type(int t); /* used in parse_expr.c */
        sort_clause, sortby_list, index_params, index_list, name_list,
        from_clause, from_list, opt_array_bounds, nest_array_bounds,
        expr_list, attrs, res_target_list, res_target_list2,
-       def_list, opt_indirection, group_clause, TriggerFuncArgs
+       def_list, opt_indirection, group_clause, TriggerFuncArgs,
+       opt_select_limit
 
 %type <node>   func_return
 %type <boolean>    set_opt
@@ -197,6 +199,8 @@ Oid param_type(int t); /* used in parse_expr.c */
 
 %type <ival>   fetch_how_many
 
+%type <node>   select_limit_value, select_offset_value
+
 %type <list>   OptSeqList
 %type <defelt> OptSeqElem
 
@@ -306,9 +310,10 @@ Oid    param_type(int t); /* used in parse_expr.c */
        DATABASE, DELIMITERS, DO, EACH, ENCODING, EXPLAIN, EXTEND,
        FORWARD, FUNCTION, HANDLER,
        INCREMENT, INDEX, INHERITS, INSTEAD, ISNULL,
-       LANCOMPILER, LISTEN, LOAD, LOCATION, LOCK_P, MAXVALUE, MINVALUE, MOVE,
+       LANCOMPILER, LIMIT, LISTEN, LOAD, LOCATION, LOCK_P,
+       MAXVALUE, MINVALUE, MOVE,
        NEW, NOCREATEDB, NOCREATEUSER, NONE, NOTHING, NOTIFY, NOTNULL,
-       OIDS, OPERATOR, PASSWORD, PROCEDURAL,
+       OFFSET, OIDS, OPERATOR, PASSWORD, PROCEDURAL,
        RECIPE, RENAME, RESET, RETURNS, ROW, RULE,
        SEQUENCE, SERIAL, SETOF, SHOW, START, STATEMENT, STDIN, STDOUT, TRUSTED, 
        UNLISTEN, UNTIL, VACUUM, VALID, VERBOSE, VERSION
@@ -2731,7 +2736,7 @@ opt_of:  OF columnList
  * 
  * The rule returns a SelectStmt Node having the set operations attached to 
  * unionClause and intersectClause (NIL if no set operations were present) */ 
-SelectStmt:      select_w_o_sort sort_clause for_update_clause
+SelectStmt:      select_w_o_sort sort_clause for_update_clause opt_select_limit
            {
                /* There were no set operations, so just attach the sortClause */
                if IsA($1, SelectStmt)
@@ -2739,6 +2744,8 @@ SelectStmt:     select_w_o_sort sort_clause for_update_clause
                  SelectStmt *n = (SelectStmt *)$1;
                  n->sortClause = $2;
                  n->forUpdate = $3;
+                 n->limitOffset = nth(0, $4);
+                 n->limitCount = nth(1, $4);
                  $$ = (Node *)n;
                 }
                /* There were set operations: The root of the operator tree
@@ -2920,6 +2927,84 @@ OptUseOp:  USING Op                              { $$ = $2; }
        | /*EMPTY*/                             { $$ = "<"; /*default*/ }
        ;
 
+
+opt_select_limit:  LIMIT select_limit_value ',' select_offset_value
+           { $$ = lappend(lappend(NIL, $4), $2); }
+       | LIMIT select_limit_value OFFSET select_offset_value
+           { $$ = lappend(lappend(NIL, $4), $2); }
+       | LIMIT select_limit_value
+           { $$ = lappend(lappend(NIL, NULL), $2); }
+       | OFFSET select_offset_value LIMIT select_limit_value
+           { $$ = lappend(lappend(NIL, $2), $4); }
+       | OFFSET select_offset_value
+           { $$ = lappend(lappend(NIL, $2), NULL); }
+       | /* EMPTY */
+           { $$ = lappend(lappend(NIL, NULL), NULL); }
+       ;
+
+select_limit_value:        Iconst
+           {
+               Const   *n = makeNode(Const);
+
+               if ($1 < 1)
+                   elog(ERROR, "selection limit must be ALL or a positive integer value > 0");
+
+               n->consttype    = INT4OID;
+               n->constlen     = sizeof(int4);
+               n->constvalue   = (Datum)$1;
+               n->constisnull  = FALSE;
+               n->constbyval   = TRUE;
+               n->constisset   = FALSE;
+               n->constiscast  = FALSE;
+               $$ = (Node *)n;
+           }
+       | ALL
+           {
+               Const   *n = makeNode(Const);
+
+               n->consttype    = INT4OID;
+               n->constlen     = sizeof(int4);
+               n->constvalue   = (Datum)0;
+               n->constisnull  = FALSE;
+               n->constbyval   = TRUE;
+               n->constisset   = FALSE;
+               n->constiscast  = FALSE;
+               $$ = (Node *)n;
+           }
+       | PARAM
+           {
+               Param   *n = makeNode(Param);
+
+               n->paramkind    = PARAM_NUM;
+               n->paramid      = $1;
+               n->paramtype    = INT4OID;
+               $$ = (Node *)n;
+           }
+       ;
+
+select_offset_value:   Iconst
+           {
+               Const   *n = makeNode(Const);
+
+               n->consttype    = INT4OID;
+               n->constlen     = sizeof(int4);
+               n->constvalue   = (Datum)$1;
+               n->constisnull  = FALSE;
+               n->constbyval   = TRUE;
+               n->constisset   = FALSE;
+               n->constiscast  = FALSE;
+               $$ = (Node *)n;
+           }
+       | PARAM
+           {
+               Param   *n = makeNode(Param);
+
+               n->paramkind    = PARAM_NUM;
+               n->paramid      = $1;
+               n->paramtype    = INT4OID;
+               $$ = (Node *)n;
+           }
+       ;
 /*
  * jimmy bell-style recursive queries aren't supported in the
  * current system.
index f1e59781ff38f1590bafe3ddbc5ea4e757ba2dad..b9af71dc46bdd52fbe453b5edebd0eed341c8233 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.52 1999/02/02 03:44:42 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.53 1999/02/08 14:14:13 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,9 +68,6 @@ static ScanKeyword ScanKeywords[] = {
    {"createdb", CREATEDB},
    {"createuser", CREATEUSER},
    {"cross", CROSS},
-   {"current", CURRENT},       /* 6.4 to 6.5 is migration time! CURRENT
-                                * will be removed in 6.5! Use OLD keyword
-                                * in rules. Jan */
    {"current_date", CURRENT_DATE},
    {"current_time", CURRENT_TIME},
    {"current_timestamp", CURRENT_TIMESTAMP},
@@ -139,6 +136,7 @@ static ScanKeyword ScanKeywords[] = {
    {"left", LEFT},
    {"level", LEVEL},
    {"like", LIKE},
+   {"limit", LIMIT},
    {"listen", LISTEN},
    {"load", LOAD},
    {"local", LOCAL},
@@ -168,6 +166,7 @@ static ScanKeyword ScanKeywords[] = {
    {"nullif", NULLIF},
    {"numeric", NUMERIC},
    {"of", OF},
+   {"offset", OFFSET},
    {"oids", OIDS},
    {"old", CURRENT},
    {"on", ON},
index b20b6f69d32ee8374f5bbbc190919bd29f12857a..a1145402d8695b3752beab70918e49258d825ed9 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.23 1998/10/06 22:14:14 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.24 1999/02/08 14:14:13 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -311,6 +311,12 @@ DefineQueryRewrite(RuleStmt *stmt)
 
        heap_close(event_relation);
 
+       /*
+        * LIMIT in view is not supported
+        */
+       if (query->limitOffset != NULL || query->limitCount != NULL)
+           elog(ERROR, "LIMIT clause not supported in views");
+
        /*
         * ... and finally the rule must be named _RETviewname.
         */
index 577a859077998fa57f2ebd0f9a552e21fe83152b..abe3c31a9cc557f042ca76a0cb695d14f60e6611 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.19 1998/09/02 23:05:34 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.20 1999/02/08 14:14:14 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,7 +40,8 @@
 #include "commands/command.h"
 
 static char *CreateOperationTag(int operationType);
-static void ProcessQueryDesc(QueryDesc *queryDesc);
+static void ProcessQueryDesc(QueryDesc *queryDesc, Node *limoffset,
+                                                  Node *limcount);
 
 
 /* ----------------------------------------------------------------
@@ -205,7 +206,7 @@ ProcessPortal(char *portalName,
  * ----------------------------------------------------------------
  */
 static void
-ProcessQueryDesc(QueryDesc *queryDesc)
+ProcessQueryDesc(QueryDesc *queryDesc, Node *limoffset, Node *limcount)
 {
    Query      *parseTree;
    Plan       *plan;
@@ -330,7 +331,7 @@ ProcessQueryDesc(QueryDesc *queryDesc)
     *   actually run the plan..
     * ----------------
     */
-   ExecutorRun(queryDesc, state, EXEC_RUN, 0);
+   ExecutorRun(queryDesc, state, EXEC_RUN, limoffset, limcount);
 
    /* save infos for EndCommand */
    UpdateCommandInfo(operation, state->es_lastoid, state->es_processed);
@@ -373,5 +374,6 @@ ProcessQuery(Query *parsetree,
        print_plan(plan, parsetree);
    }
    else
-       ProcessQueryDesc(queryDesc);
+       ProcessQueryDesc(queryDesc, parsetree->limitOffset,
+                                   parsetree->limitCount);
 }
index 68d89897d092ece35616ead1290bb44da64b8c61..2b015f4c8b6acf820b8b2bfca1a8ffdd3268a6e4 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: executor.h,v 1.29 1999/02/07 16:17:12 wieck Exp $
+ * $Id: executor.h,v 1.30 1999/02/08 14:14:18 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -83,7 +83,8 @@ extern HeapTuple ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot);
  * prototypes from functions in execMain.c
  */
 extern TupleDesc ExecutorStart(QueryDesc *queryDesc, EState *estate);
-extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count);
+extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc, EState *estate, 
+                           int feature, Node *limoffset, Node *limcount);
 extern void ExecutorEnd(QueryDesc *queryDesc, EState *estate);
 extern void ExecConstraints(char *caller, Relation rel, HeapTuple tuple, 
                                            EState *estate);
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
new file mode 100644 (file)
index 0000000..a46515d
--- /dev/null
@@ -0,0 +1,106 @@
+QUERY: SELECT ''::text AS two, unique1, unique2, stringu1
+       FROM onek WHERE unique1 > 50
+       ORDER BY unique1 LIMIT 2;
+two|unique1|unique2|stringu1
+---+-------+-------+--------
+   |     51|     76|ZBAAAA  
+   |     52|    985|ACAAAA  
+(2 rows)
+
+QUERY: SELECT ''::text AS five, unique1, unique2, stringu1
+       FROM onek WHERE unique1 > 60
+       ORDER BY unique1 LIMIT 5;
+five|unique1|unique2|stringu1
+----+-------+-------+--------
+    |     61|    560|JCAAAA  
+    |     62|    633|KCAAAA  
+    |     63|    296|LCAAAA  
+    |     64|    479|MCAAAA  
+    |     65|     64|NCAAAA  
+(5 rows)
+
+QUERY: SELECT ''::text AS two, unique1, unique2, stringu1
+       FROM onek WHERE unique1 > 60 AND unique1 < 63
+       ORDER BY unique1 LIMIT 5;
+two|unique1|unique2|stringu1
+---+-------+-------+--------
+   |     61|    560|JCAAAA  
+   |     62|    633|KCAAAA  
+(2 rows)
+
+QUERY: SELECT ''::text AS three, unique1, unique2, stringu1
+       FROM onek WHERE unique1 > 100
+       ORDER BY unique1 LIMIT 3 OFFSET 20;
+three|unique1|unique2|stringu1
+-----+-------+-------+--------
+     |    121|    700|REAAAA  
+     |    122|    519|SEAAAA  
+     |    123|    777|TEAAAA  
+(3 rows)
+
+QUERY: SELECT ''::text AS zero, unique1, unique2, stringu1
+       FROM onek WHERE unique1 < 50
+       ORDER BY unique1 DESC LIMIT 8 OFFSET 99;
+zero|unique1|unique2|stringu1
+----+-------+-------+--------
+(0 rows)
+
+QUERY: SELECT ''::text AS eleven, unique1, unique2, stringu1
+       FROM onek WHERE unique1 < 50
+       ORDER BY unique1 DESC LIMIT 20 OFFSET 39;
+eleven|unique1|unique2|stringu1
+------+-------+-------+--------
+      |     10|    520|KAAAAA  
+      |      9|     49|JAAAAA  
+      |      8|    653|IAAAAA  
+      |      7|    647|HAAAAA  
+      |      6|    978|GAAAAA  
+      |      5|    541|FAAAAA  
+      |      4|    833|EAAAAA  
+      |      3|    431|DAAAAA  
+      |      2|    326|CAAAAA  
+      |      1|    214|BAAAAA  
+      |      0|    998|AAAAAA  
+(11 rows)
+
+QUERY: SELECT ''::text AS ten, unique1, unique2, stringu1
+       FROM onek
+       ORDER BY unique1 OFFSET 990;
+ten|unique1|unique2|stringu1
+---+-------+-------+--------
+   |    990|    369|CMAAAA  
+   |    991|    426|DMAAAA  
+   |    992|    363|EMAAAA  
+   |    993|    661|FMAAAA  
+   |    994|    695|GMAAAA  
+   |    995|    144|HMAAAA  
+   |    996|    258|IMAAAA  
+   |    997|     21|JMAAAA  
+   |    998|    549|KMAAAA  
+   |    999|    152|LMAAAA  
+(10 rows)
+
+QUERY: SELECT ''::text AS five, unique1, unique2, stringu1
+       FROM onek
+       ORDER BY unique1 OFFSET 990 LIMIT 5;
+five|unique1|unique2|stringu1
+----+-------+-------+--------
+    |    990|    369|CMAAAA  
+    |    991|    426|DMAAAA  
+    |    992|    363|EMAAAA  
+    |    993|    661|FMAAAA  
+    |    994|    695|GMAAAA  
+(5 rows)
+
+QUERY: SELECT ''::text AS five, unique1, unique2, stringu1
+       FROM onek
+       ORDER BY unique1 LIMIT 5, 900;
+five|unique1|unique2|stringu1
+----+-------+-------+--------
+    |    900|    913|QIAAAA  
+    |    901|    931|RIAAAA  
+    |    902|    702|SIAAAA  
+    |    903|    641|TIAAAA  
+    |    904|    793|UIAAAA  
+(5 rows)
+
index 68dc16c86900dbfb30ede9e56ea6a4aefeac4f80..7fb9fd25aa64eb4bd7354c22ed9be720a7fe7181 100644 (file)
@@ -6,39 +6,39 @@ QUERY: create rule rtest_v1_ins as on insert to rtest_v1 do instead
    insert into rtest_t1 values (new.a, new.b);
 QUERY: create rule rtest_v1_upd as on update to rtest_v1 do instead
    update rtest_t1 set a = new.a, b = new.b
-   where a = current.a;
+   where a = old.a;
 QUERY: create rule rtest_v1_del as on delete to rtest_v1 do instead
-   delete from rtest_t1 where a = current.a;
+   delete from rtest_t1 where a = old.a;
 QUERY: create table rtest_system (sysname text, sysdesc text);
 QUERY: create table rtest_interface (sysname text, ifname text);
 QUERY: create table rtest_person (pname text, pdesc text);
 QUERY: create table rtest_admin (pname text, sysname text);
 QUERY: create rule rtest_sys_upd as on update to rtest_system do (
    update rtest_interface set sysname = new.sysname
-       where sysname = current.sysname;
+       where sysname = old.sysname;
    update rtest_admin set sysname = new.sysname
-       where sysname = current.sysname
+       where sysname = old.sysname
    );
 QUERY: create rule rtest_sys_del as on delete to rtest_system do (
-   delete from rtest_interface where sysname = current.sysname;
-   delete from rtest_admin where sysname = current.sysname;
+   delete from rtest_interface where sysname = old.sysname;
+   delete from rtest_admin where sysname = old.sysname;
    );
 QUERY: create rule rtest_pers_upd as on update to rtest_person do
-   update rtest_admin set pname = new.pname where pname = current.pname;
+   update rtest_admin set pname = new.pname where pname = old.pname;
 QUERY: create rule rtest_pers_del as on delete to rtest_person do
-   delete from rtest_admin where pname = current.pname;
+   delete from rtest_admin where pname = old.pname;
 QUERY: create table rtest_emp (ename char(20), salary money);
 QUERY: create table rtest_emplog (ename char(20), who name, action char(10), newsal money, oldsal money);
 QUERY: create table rtest_empmass (ename char(20), salary money);
 QUERY: create rule rtest_emp_ins as on insert to rtest_emp do
    insert into rtest_emplog values (new.ename, current_user,
            'hired', new.salary, '0.00');
-QUERY: create rule rtest_emp_upd as on update to rtest_emp where new.salary != current.salary do
+QUERY: create rule rtest_emp_upd as on update to rtest_emp where new.salary != old.salary do
    insert into rtest_emplog values (new.ename, current_user,
-           'honored', new.salary, current.salary);
+           'honored', new.salary, old.salary);
 QUERY: create rule rtest_emp_del as on delete to rtest_emp do
-   insert into rtest_emplog values (current.ename, current_user,
-           'fired', '0.00', current.salary);
+   insert into rtest_emplog values (old.ename, current_user,
+           'fired', '0.00', old.salary);
 QUERY: create table rtest_t4 (a int4, b text);
 QUERY: create table rtest_t5 (a int4, b text);
 QUERY: create table rtest_t6 (a int4, b text);
@@ -131,7 +131,7 @@ a| b
 QUERY: delete from rtest_v1 where b = 12;
 QUERY: select * from rtest_v1;
 ** Remember the delete rule on rtest_v1: It says
-** DO INSTEAD DELETE FROM rtest_t1 WHERE a = current.a
+** DO INSTEAD DELETE FROM rtest_t1 WHERE a = old.a
 ** So this time both rows with a = 2 must get deleted
 a| b
 -+--
diff --git a/src/test/regress/sql/limit.sql b/src/test/regress/sql/limit.sql
new file mode 100644 (file)
index 0000000..eb3bf0e
--- /dev/null
@@ -0,0 +1,31 @@
+--
+-- Check the LIMIT/OFFSET feature of SELECT
+--
+
+SELECT ''::text AS two, unique1, unique2, stringu1 
+       FROM onek WHERE unique1 > 50 
+       ORDER BY unique1 LIMIT 2;
+SELECT ''::text AS five, unique1, unique2, stringu1 
+       FROM onek WHERE unique1 > 60 
+       ORDER BY unique1 LIMIT 5;
+SELECT ''::text AS two, unique1, unique2, stringu1 
+       FROM onek WHERE unique1 > 60 AND unique1 < 63
+       ORDER BY unique1 LIMIT 5;
+SELECT ''::text AS three, unique1, unique2, stringu1 
+       FROM onek WHERE unique1 > 100 
+       ORDER BY unique1 LIMIT 3 OFFSET 20;
+SELECT ''::text AS zero, unique1, unique2, stringu1 
+       FROM onek WHERE unique1 < 50 
+       ORDER BY unique1 DESC LIMIT 8 OFFSET 99;
+SELECT ''::text AS eleven, unique1, unique2, stringu1 
+       FROM onek WHERE unique1 < 50 
+       ORDER BY unique1 DESC LIMIT 20 OFFSET 39;
+SELECT ''::text AS ten, unique1, unique2, stringu1 
+       FROM onek
+       ORDER BY unique1 OFFSET 990;
+SELECT ''::text AS five, unique1, unique2, stringu1 
+       FROM onek
+       ORDER BY unique1 OFFSET 990 LIMIT 5;
+SELECT ''::text AS five, unique1, unique2, stringu1 
+       FROM onek
+       ORDER BY unique1 LIMIT 5, 900;
index e148489d9a8127114ac7b3b4020f4493d6aaac06..319a20665226c5abb9b92ae40e47db26cfd55cfd 100644 (file)
@@ -16,9 +16,9 @@ create rule rtest_v1_ins as on insert to rtest_v1 do instead
    insert into rtest_t1 values (new.a, new.b);
 create rule rtest_v1_upd as on update to rtest_v1 do instead
    update rtest_t1 set a = new.a, b = new.b
-   where a = current.a;
+   where a = old.a;
 create rule rtest_v1_del as on delete to rtest_v1 do instead
-   delete from rtest_t1 where a = current.a;
+   delete from rtest_t1 where a = old.a;
 
 --
 -- Tables and rules for the constraint update/delete test
@@ -35,21 +35,21 @@ create table rtest_admin (pname text, sysname text);
 
 create rule rtest_sys_upd as on update to rtest_system do (
    update rtest_interface set sysname = new.sysname 
-       where sysname = current.sysname;
+       where sysname = old.sysname;
    update rtest_admin set sysname = new.sysname 
-       where sysname = current.sysname
+       where sysname = old.sysname
    );
 
 create rule rtest_sys_del as on delete to rtest_system do (
-   delete from rtest_interface where sysname = current.sysname;
-   delete from rtest_admin where sysname = current.sysname;
+   delete from rtest_interface where sysname = old.sysname;
+   delete from rtest_admin where sysname = old.sysname;
    );
 
 create rule rtest_pers_upd as on update to rtest_person do 
-   update rtest_admin set pname = new.pname where pname = current.pname;
+   update rtest_admin set pname = new.pname where pname = old.pname;
 
 create rule rtest_pers_del as on delete to rtest_person do 
-   delete from rtest_admin where pname = current.pname;
+   delete from rtest_admin where pname = old.pname;
 
 --
 -- Tables and rules for the logging test
@@ -62,13 +62,13 @@ create rule rtest_emp_ins as on insert to rtest_emp do
    insert into rtest_emplog values (new.ename, current_user,
            'hired', new.salary, '0.00');
 
-create rule rtest_emp_upd as on update to rtest_emp where new.salary != current.salary do
+create rule rtest_emp_upd as on update to rtest_emp where new.salary != old.salary do
    insert into rtest_emplog values (new.ename, current_user,
-           'honored', new.salary, current.salary);
+           'honored', new.salary, old.salary);
 
 create rule rtest_emp_del as on delete to rtest_emp do
-   insert into rtest_emplog values (current.ename, current_user,
-           'fired', '0.00', current.salary);
+   insert into rtest_emplog values (old.ename, current_user,
+           'fired', '0.00', old.salary);
 
 --
 -- Tables and rules for the multiple cascaded qualified instead
@@ -177,7 +177,7 @@ insert into rtest_v1 values (2, 12);
 insert into rtest_v1 values (2, 13);
 select * from rtest_v1;
 ** Remember the delete rule on rtest_v1: It says
-** DO INSTEAD DELETE FROM rtest_t1 WHERE a = current.a
+** DO INSTEAD DELETE FROM rtest_t1 WHERE a = old.a
 ** So this time both rows with a = 2 must get deleted
 \p
 \r
index ddc2365d2b428c80555c774632b75e89bb0b49c9..44529af2b27927edf6f86d7398a62078e13e8de7 100644 (file)
@@ -62,6 +62,7 @@ select_views
 alter_table
 portals_p2
 rules
+limit
 install_plpgsql
 plpgsql
 temp