SPI_cursor_open failed to enforce that only read-only queries could be
authorTom Lane <[email protected]>
Sat, 17 Mar 2007 03:16:03 +0000 (03:16 +0000)
committerTom Lane <[email protected]>
Sat, 17 Mar 2007 03:16:03 +0000 (03:16 +0000)
executed in read_only mode.  This could lead to various relatively-subtle
failures, such as an allegedly stable function returning non-stable results.
Bug goes all the way back to the introduction of read-only mode in 8.0.
Per report from Gaetano Mendola.

src/backend/executor/spi.c

index 634d1fdb285184fa43167ae2505143b33954adf7..9b3bad74fcd8b7c30a99e6d35f6e63e249754323 100644 (file)
@@ -832,7 +832,7 @@ SPI_cursor_open(const char *name, void *plan,
        Portal          portal;
        int                     k;
 
-       /* Ensure that the plan contains only one regular SELECT query */
+       /* Ensure that the plan contains only one query */
        if (list_length(ptlist) != 1 || list_length(qtlist) != 1)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
@@ -840,14 +840,37 @@ SPI_cursor_open(const char *name, void *plan,
        queryTree = (Query *) linitial((List *) linitial(qtlist));
        planTree = (Plan *) linitial(ptlist);
 
-       if (queryTree->commandType != CMD_SELECT)
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-                                errmsg("cannot open non-SELECT query as cursor")));
-       if (queryTree->into != NULL)
+       /* Must be a query that returns tuples */
+       switch (queryTree->commandType)
+       {
+               case CMD_SELECT:
+                       if (queryTree->into != NULL)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+                                                errmsg("cannot open SELECT INTO query as cursor")));
+                       break;
+               case CMD_UTILITY:
+                       if (!UtilityReturnsTuples(queryTree->utilityStmt))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+                                                errmsg("cannot open non-SELECT query as cursor")));
+                       break;
+               default:
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+                                        errmsg("cannot open non-SELECT query as cursor")));
+                       break;
+       }
+
+       /*
+        * If told to be read-only, we'd better check for read-only queries.
+        */
+       if (read_only && !QueryIsReadOnly(queryTree))
                ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-                                errmsg("cannot open SELECT INTO query as cursor")));
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                /* translator: %s is a SQL statement name */
+                                errmsg("%s is not allowed in a non-volatile function",
+                                               CreateQueryTag(queryTree))));
 
        /* Reset SPI result */
        SPI_processed = 0;
@@ -911,7 +934,7 @@ SPI_cursor_open(const char *name, void *plan,
         */
        PortalDefineQuery(portal,
                                          NULL,         /* unfortunately don't have sourceText */
-                                         "SELECT", /* cursor's query is always a SELECT */
+                                         "SELECT", /* nor the raw parse tree... */
                                          list_make1(queryTree),
                                          list_make1(planTree),
                                          PortalGetHeapMemory(portal));
@@ -922,7 +945,7 @@ SPI_cursor_open(const char *name, void *plan,
         * Set up options for portal.
         */
        portal->cursorOptions &= ~(CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL);
-       if (ExecSupportsBackwardScan(plan))
+       if (planTree == NULL || ExecSupportsBackwardScan(planTree))
                portal->cursorOptions |= CURSOR_OPT_SCROLL;
        else
                portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
@@ -944,7 +967,8 @@ SPI_cursor_open(const char *name, void *plan,
         */
        PortalStart(portal, paramLI, snapshot);
 
-       Assert(portal->strategy == PORTAL_ONE_SELECT);
+       Assert(portal->strategy == PORTAL_ONE_SELECT ||
+                  portal->strategy == PORTAL_UTIL_SELECT);
 
        /* Return the created portal */
        return portal;