diff -rc head_260207/src/backend/commands/portalcmds.c head_260207_jb/src/backend/commands/portalcmds.c *** head_260207/src/backend/commands/portalcmds.c 2007-02-26 17:12:37.000000000 +1100 --- head_260207_jb/src/backend/commands/portalcmds.c 2007-02-26 16:26:44.000000000 +1100 *************** *** 90,99 **** (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), errmsg("DECLARE CURSOR cannot specify INTO"))); if (query->rowMarks != NIL) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"), ! errdetail("Cursors must be READ ONLY."))); plan = planner(query, true, stmt->options, params); --- 90,100 ---- (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), errmsg("DECLARE CURSOR cannot specify INTO"))); if (query->rowMarks != NIL) ! if (query->forUpdate == FALSE) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("DECLARE CURSOR ... FOR SHARE is not supported"), ! errdetail("Cursors must not be FOR SHARE."))); plan = planner(query, true, stmt->options, params); /*------------------------------------------------- * * ctidListStore.c * Contains the processing relating to the storage of the list of ctids * required for Updatable Cursor. * * The details to be handled here include: * * use of shared memory to hold basic list * format and use of file for permanent storage of list * functions to access and search the shared memory, ie use the list * functions to access and build the list * functions to initialise and empty the list * functions to initialise and remove the file associated with the list * functions to populate shared memory from existing ctid files during * database startup. * * INTERFACE ROUTINES * * saveCtids(ItemPointer ctid) */ #include "backend/executor/ctidListStore.h" /* * Directory where CTID list files reside within PGDATA */ #define CTID_DIR "pg_ctids" static List *ctidList; /* list of ctids for updatable cursor */ static void RegisterCtidRecord(); static void ReadCtidFile(); static void ProcessCtidRecords(); int max_ctids_in_list = 1000; /* This is for performance reasons */ typedef struct CtidData { /* * Number of Ctid records in the list */ int CtidCount; ItemPointer ctidArray[1]; /* VARIABLE LENGTH ARRAY */ } CtidData; /* VARIABLE LENGTH STRUCT */ static void CtidShmemSize() { Size size; /* Need the fixed struct and the ctid */ size = offsetof(CtidData, ctidArray); size = add_size(size, sizeof(ItemPointer)); size = MAXALIGN(size); return size; } static void CtidShmemInit() { bool found; CtidState = ShmemInitStruct("Current Tuple Id Table", CtidShmemSize(), &found); } /************************************************************************/ /* State file support */ /************************************************************************/ #define CtidListFilePath(path, ) \ snprintf(path, MAXPGPATH, CTIDLIST_DIR "/%08X", xid) /* * CtidList state file format: * * 1. CtidListFileHeader * 5. CtidRecordOnDisk * 6. ... * 7. CtidRecordOnDisk (end sentinel, rmid == CTIDLIST_RM_END_ID) * 8. CRC32 * * Each segment except the final CRC32 is MAXALIGN'd. */ /* * Header for a CtidList file */ #define CTIDLIST_MAGIC ???????? /* format identifier HOW IS THIS DEFINED */ typedef struct CtidListFileHeader { uint32 magic; /* format identifier */ uint32 total_len; /* actual file length */ TransactionId xid; /* original transaction XID */ Oid database; /* OID of database it was in */ Oid owner; /* user running the transaction */ char gid[GIDSIZE]; /* GID for transaction */ } CtidListFileHeader; /* * Header for each record in a state file * * NOTE: len counts only the rmgr data, not the TwoPhaseRecordOnDisk header. * The rmgr data will be stored starting on a MAXALIGN boundary. */ typedef struct CtidRecordOnDisk { uint32 len; /* length of rmgr data */ CtidListRmgrId rmid; /* resource manager for this record */ uint16 info; /* flag bits for use by rmgr */ } CtidRecordOnDisk; /* WILL CTID LIST BE WRITTEN TO WAL??? * During prepare, the state file is assembled in memory before writing it * to WAL and the actual state file. We use a chain of XLogRecData blocks * so that we will be able to pass the state file contents directly to * XLogInsert. */ static struct xllist { XLogRecData *head; /* first data block in the chain */ XLogRecData *tail; /* last block in chain */ uint32 bytes_free; /* free bytes left in tail block */ uint32 total_len; /* total data bytes in chain */ } records; static void StartCtidPrepare() { } static void EndCtidPrepare() { } static void RemoveCtidFile() { } static void saveCtid(ItemPointer ctid) { if (ctidList != NIL) lappend(ctidList,ctid); } static ItemPointer findCtidInList(ItemPointer ctid) { if (ctidList == NILL) return; while( } /* * ctidListStore.h * * Header file to expose the interface for ctid shared memory and file * processing */ static void saveCtid(ItemPointer ctid); static ItemPointer findCtidInList(ItemPointer ctid); static void CtidShmemSize(); static void CtidShmemInit(); static void StartCtidPrepare(); static void EndCtidPrepare(); static void RemoveCtidFile(); diff -rc head_260207/src/backend/executor/execMain.c head_260207_jb/src/backend/executor/execMain.c *** head_260207/src/backend/executor/execMain.c 2007-02-26 17:21:03.000000000 +1100 --- head_260207_jb/src/backend/executor/execMain.c 2007-02-26 16:29:08.000000000 +1100 *************** *** 41,46 **** --- 41,47 ---- #include "catalog/toasting.h" #include "commands/tablespace.h" #include "commands/trigger.h" + #include "executor/ctidListStore.h" #include "executor/execdebug.h" #include "executor/instrument.h" #include "executor/nodeSubplan.h" *************** *** 70,76 **** List *rangeTable, CmdType operation, bool doInstrument); ! static TupleTableSlot *ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, long numberTuples, ScanDirection direction, --- 71,77 ---- List *rangeTable, CmdType operation, bool doInstrument); ! static TupleTableSlot *ExecutePlan(EState *estate, QueryDesc *queryDesc, CmdType operation, long numberTuples, ScanDirection direction, *************** *** 240,246 **** result = NULL; else result = ExecutePlan(estate, ! queryDesc->planstate, operation, count, direction, --- 241,247 ---- result = NULL; else result = ExecutePlan(estate, ! queryDesc, operation, count, direction, *************** *** 1009,1022 **** */ static TupleTableSlot * ExecutePlan(EState *estate, ! PlanState *planstate, CmdType operation, long numberTuples, ScanDirection direction, DestReceiver *dest) { JunkFilter *junkfilter; TupleTableSlot *planSlot; TupleTableSlot *slot; ItemPointer tupleid = NULL; ItemPointerData tuple_ctid; --- 1010,1025 ---- */ static TupleTableSlot * ExecutePlan(EState *estate, ! QueryDesc *queryDesc, CmdType operation, long numberTuples, ScanDirection direction, DestReceiver *dest) { JunkFilter *junkfilter; + PlanState *planstate = queryDesc->planstate; TupleTableSlot *planSlot; + TupleTaplanstatebleSlot *planSlot; TupleTableSlot *slot; ItemPointer tupleid = NULL; ItemPointerData tuple_ctid; *************** *** 1103,1110 **** --- 1106,1116 ---- /* * extract the 'ctid' junk attribute. + * The third condition here relates to updatable cursor, when + * the ctid must always be extracted. */ if (operation == CMD_UPDATE || operation == CMD_DELETE) + queryDesc->parsetree->portal != NULL)) { datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, &isNull); *************** *** 1115,1120 **** --- 1121,1154 ---- tupleid = (ItemPointer) DatumGetPointer(datum); tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */ tupleid = &tuple_ctid; + + /* + * Save the ctid in portal for updatable cursor during Fetch + * request + */ + if (((FetchStmt *)queryDesc->parsetree->utilityStmt)->type == T_FetchStmt) + { + /* + * Save the ctid and tuple in the portal for + * updatable cursor during a Fetch request. + */ + queryDesc->parsetree->portal->ctid = tupleid; + queryDesc->parsetree->portal->tuple = slot; + } + else if ((queryDesc->parsetree->commandType == CMD_UPDATE || + queryDesc->parsetree->commandType == CMD_DELETE) && + queryDesc->parsetree->portal != NULL && + NodeTag(planstate) == TidScanState && + ctidList == NULL) + { + /* + * Update or Delete statement with updatable cursor + * executing the TidScan node for the first time in + * this query so save the tupleid into a hash table + * and store to disk. + */ + saveCtid(tupleid); + } } /* diff -rc head_260207/src/backend/optimizer/path/tidpath.c head_260207_jb/src/backend/optimizer/path/tidpath.c *** head_260207/src/backend/optimizer/path/tidpath.c 2007-02-26 15:12:00.000000000 +1100 --- head_260207_jb/src/backend/optimizer/path/tidpath.c 2007-02-26 16:30:47.000000000 +1100 *************** *** 41,46 **** --- 41,47 ---- static bool IsTidEqualClause(OpExpr *node, int varno); static bool IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno); + /* static List *build_tidqual(); */ static List *TidQualFromExpr(Node *expr, int varno); static List *TidQualFromRestrictinfo(List *restrictinfo, int varno); *************** *** 140,145 **** --- 141,192 ---- return false; } + /* + * build_tid_qual + * + * Create a tid qual to represent CTID = ANY and insert it into the tidqual + * list to be returned. As this tid qual is required only to force a TidScan to + * occur, it is only necessary to populate those fields required to satisfy + * the conditions required to force a TidScan path to be created in the plan. + * CTID is not a string it is data stored in a tuple. Need to obtain it from + * somewhere for use here... + * + * This function has been commented out as it may not be needed to implement updatable + * cursor functionality. + * + + static List * + build_tidqual() + { + List *qualList; + ScalarArrayOpExpr *qual; + Var *arg1; + Node *arg2; + + qual->xpr = ; + qual->opno = TIDEqualOperator; + qual->useOr = TRUE; + + * Define arguments 1 and 2. arg1 is a Var, arg2 is a const. + * varattno is just assigned the value here to satisfy tidqual conditions + * It means nothing outside this area. + arg1->varattno = SelfItemPointerAttributeNumber; + arg1->vartype = TIDOID; + arg1->varlevelsup = 0; + arg1->xpr = + arg1->vartypmod = + arg1->varnoold = + arg1->varoattno = + + * Add first and second arguments into argument list + + qual->args = lappend(qual->args, arg1); + qual->args = lappend(qual->args, arg2); + + qualList = lappend(qualList, qual); + return qualList; + } + */ /* * Extract a set of CTID conditions from the given qual expression *************** *** 239,246 **** { List *tidquals; ! tidquals = TidQualFromRestrictinfo(rel->baserestrictinfo, rel->relid); ! if (tidquals) ! add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals)); } --- 286,307 ---- { List *tidquals; ! if (root->parse->portal == NULL) ! { ! tidquals = TidQualFromRestrictinfo(rel->baserestrictinfo, rel->relid); ! if (tidquals) ! add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals)); ! } ! else ! { ! /* ! * Updatable cursor case: According to the Executor, tid quals are ! * not required for a TidScan to be processed. So pass tidquals of NULL ! */ ! ! /* tidquals = build_tidqual(); may not be needed */ ! if (tidquals) ! add_path(rel, (Path *) create_tidscan_path(root, rel, NULL)); ! } } diff -rc head_260207/src/backend/parser/analyze.c head_260207_jb/src/backend/parser/analyze.c *** head_260207/src/backend/parser/analyze.c 2007-02-26 15:01:03.000000000 +1100 --- head_260207_jb/src/backend/parser/analyze.c 2007-02-26 16:36:20.000000000 +1100 *************** *** 508,514 **** */ transformFromClause(pstate, stmt->usingClause); ! qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); qry->returningList = transformReturningList(pstate, stmt->returningList); --- 508,536 ---- */ transformFromClause(pstate, stmt->usingClause); ! if (stmt->cursorName == NULL) ! { ! /* Only transform WHERE clause if not a WHERE CURRENT OF clause */ ! qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); ! } ! else ! { ! /* ! * In WHERE CURRENT OF clause obtain Portal from cursor name and save in ! * Query structure. The test condition below ensures that a combined WHERE ! * and WHERE CURRENT OF clause is not possible in a single query. ! */ ! if (stmt->whereClause != NULL) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("Cannot have WHERE and WHERE CURRENT OF in same query"), ! errdetail("Only WHERE or WHERE CURRENT OF may be used in query")); ! qry->portal = GetPortalByName(stmt->cursorName); ! } ! ! qry->returningList = transformReturningList(pstate, stmt->returningList); ! qry->portal = GetPortalByName(stmt->cursorName); ! } qry->returningList = transformReturningList(pstate, stmt->returningList); *************** *** 2836,2841 **** --- 2858,2864 ---- transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) { Query *qry = makeNode(Query); + Portal *portal; Node *qual; ListCell *origTargetList; ListCell *tl; *************** *** 2855,2862 **** transformFromClause(pstate, stmt->fromClause); qry->targetList = transformTargetList(pstate, stmt->targetList); ! qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); qry->returningList = transformReturningList(pstate, stmt->returningList); --- 2878,2907 ---- transformFromClause(pstate, stmt->fromClause); qry->targetList = transformTargetList(pstate, stmt->targetList); + + if (stmt->cursorName == NULL) + { + /* + * Only call if it is not a WHERE CURRENT OF clause + */ + qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); + } + else + { + /* + * In WHERE CURRENT OF cursor name, get Portal from cursor name + * and save in Query structure. The test condition below ensures that + * a combined WHERE and WHERE CURRENT OF clause is not possible + * in a single query. + */ ! if (stmt->whereClause != NULL) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("Cannot have WHERE and WHERE CURRENT OF in same query"), ! errdetail("Only WHERE or WHERE CURRENT OF may be used in query")); ! qry->portal = GetPortalByName(stmt->cursorName); ! } qry->returningList = transformReturningList(pstate, stmt->returningList); diff -rc head_260207/src/backend/parser/gram.y head_260207_jb/src/backend/parser/gram.y *** head_260207/src/backend/parser/gram.y 2007-02-26 15:05:24.000000000 +1100 --- head_260207_jb/src/backend/parser/gram.y 2007-02-26 16:47:59.000000000 +1100 *************** *** 249,255 **** prep_type_clause execute_param_clause using_clause returning_clause ! %type OptTempTableName %type into_clause create_as_target %type createfunc_opt_item common_func_opt_item --- 249,255 ---- prep_type_clause execute_param_clause using_clause returning_clause ! %type OptTempTableName %type into_clause create_as_target %type createfunc_opt_item common_func_opt_item *************** *** 375,382 **** CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT COMMITTED CONCURRENTLY CONNECTION CONSTRAINT CONSTRAINTS CONTENT_P CONVERSION_P CONVERT COPY COST CREATE CREATEDB - CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS --- 375,382 ---- CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT COMMITTED CONCURRENTLY CONNECTION CONSTRAINT CONSTRAINTS + CREATEROLE CREATEUSER CROSS CSV CURRENT CURRENT_DATE CURRENT_ROLE CURRENT_TIME CONTENT_P CONVERSION_P CONVERT COPY COST CREATE CREATEDB CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS *************** *** 5584,5590 **** DeleteStmt *n = makeNode(DeleteStmt); n->relation = $3; n->usingClause = $4; ! n->whereClause = $5; n->returningList = $6; $$ = (Node *)n; } --- 5584,5593 ---- DeleteStmt *n = makeNode(DeleteStmt); n->relation = $3; n->usingClause = $4; ! if ( (Node *)$5->type == T_A_Expr ) ! n->whereClause = $5; ! else ! n->cursorName = $5; n->returningList = $6; $$ = (Node *)n; } *************** *** 5642,5648 **** n->relation = $2; n->targetList = $4; n->fromClause = $5; ! n->whereClause = $6; n->returningList = $7; $$ = (Node *)n; } --- 5645,5654 ---- n->relation = $2; n->targetList = $4; n->fromClause = $5; ! if ( (Node *)$6->type == T_A_Expr ) ! n->whereClause = $6; ! else ! n->cursorName = $6; n->returningList = $7; $$ = (Node *)n; } *************** *** 5709,5715 **** | set_target_list ',' set_target { $$ = lappend($1,$3); } ; - /***************************************************************************** * * QUERY: --- 5715,5720 ---- *************** *** 6440,6445 **** --- 6445,6451 ---- where_clause: WHERE a_expr { $$ = $2; } + | WHERE CURRENT OF name {$$ = $4; } | /*EMPTY*/ { $$ = NULL; } ; *************** *** 8996,9001 **** --- 9002,9008 ---- | COLUMN | CONSTRAINT | CREATE + | CURRENT | CURRENT_DATE | CURRENT_ROLE | CURRENT_TIME diff -rc head_260207/src/backend/parser/keywords.c head_260207_jb/src/backend/parser/keywords.c *** head_260207/src/backend/parser/keywords.c 2007-02-26 15:05:35.000000000 +1100 --- head_260207_jb/src/backend/parser/keywords.c 2007-02-26 16:39:02.000000000 +1100 *************** *** 100,105 **** --- 100,106 ---- {"createuser", CREATEUSER}, {"cross", CROSS}, {"csv", CSV}, + {"current", CURRENT}, {"current_date", CURRENT_DATE}, {"current_role", CURRENT_ROLE}, {"current_time", CURRENT_TIME}, diff -rc head_260207/src/include/executor/execdesc.h head_260207_jb/src/include/executor/execdesc.h *** head_260207/src/include/executor/execdesc.h 2007-02-26 15:05:14.000000000 +1100 --- head_260207_jb/src/include/executor/execdesc.h 2007-02-26 17:18:30.000000000 +1100 *************** *** 47,52 **** --- 47,56 ---- TupleDesc tupDesc; /* descriptor for result tuples */ EState *estate; /* executor's query-wide state */ PlanState *planstate; /* tree of per-plan-node state */ + /* These fields are set by ExecutorRun ??? */ + TupleId tupleId; /* Tuple id retrieved on cursor FETCH */ + TupleTableSlot tuple; /* Tuple relating to tuple id */ + RelationId relationId; /* Relation Id retrieved on cursor FETCH */ } QueryDesc; /* in pquery.c */ diff -rc head_260207/src/include/nodes/parsenodes.h head_260207_jb/src/include/nodes/parsenodes.h *** head_260207/src/include/nodes/parsenodes.h 2007-02-26 15:06:09.000000000 +1100 --- head_260207_jb/src/include/nodes/parsenodes.h 2007-02-26 17:13:08.000000000 +1100 *************** *** 109,114 **** --- 109,115 ---- List *rtable; /* list of range table entries */ FromExpr *jointree; /* table join tree (FROM and WHERE clauses) */ + Portal *portal; /* Portal relating to updatable cursor */ List *targetList; /* target list (of TargetEntry) */ *************** *** 685,690 **** --- 686,692 ---- RangeVar *relation; /* relation to delete from */ List *usingClause; /* optional using clause for more tables */ Node *whereClause; /* qualifications */ + char *cursorName; /* Cursor name in WHERE CURRENT OF */ List *returningList; /* list of expressions to return */ } DeleteStmt; *************** *** 698,703 **** --- 700,706 ---- RangeVar *relation; /* relation to update */ List *targetList; /* the target list (of ResTarget) */ Node *whereClause; /* qualifications */ + char *cursorName; /* Cursor name in WHERE CURRENT OF */ List *fromClause; /* optional from clause for more tables */ List *returningList; /* list of expressions to return */ } UpdateStmt; diff -rc head_260207/src/include/utils/portal.h head_260207_jb/src/include/utils/portal.h *** head_260207/src/include/utils/portal.h 2007-02-26 15:06:20.000000000 +1100 --- head_260207_jb/src/include/utils/portal.h 2007-02-26 17:16:23.000000000 +1100 *************** *** 161,166 **** --- 161,172 ---- MemoryContext holdContext; /* memory containing holdStore */ /* + * Updatable Cursor change. Here the current tuple id (ctid) is + * stored. + */ + ItemPointer ctid; /* updatable cursor current tuple id */ + TupleTableSlot tuple; /* tuple related to ctid */ + /* * atStart, atEnd and portalPos indicate the current cursor position. * portalPos is zero before the first row, N after fetching N'th row of * query. After we run off the end, portalPos = # of rows in query, and