I prepared a patch for "Have COPY return the number of rows loaded/unloaded?" TODO. (Sorry for disturbing list with such a simple topic, but per warning from Bruce Momjian, I send my proposal to -hackers first.)
I used the "appending related information to commandTag" method which is used for INSERT/UPDATE/DELETE/FETCH commands too. Furthermore, I edited libpq to make PQcmdTuples() interpret affected rows from cmdStatus value for COPY command. (Changes don't cause any compatibility problems for API and seems like work with triggers too.) One of the problems related with the used concept is trying to encapsulate processed number of rows within an uint32 variable. This causes an internal limit for counting COPY when we think it can process billions of rows. I couldn't find a solution for this. (Maybe, two uint32 can be used to store row count.) But other processed row counters (like INSERT/UPDATE) uses uint32 too. What's your suggestions and comments? Regards.
Index: src/backend/commands/copy.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/commands/copy.c,v retrieving revision 1.255 diff -u -r1.255 copy.c --- src/backend/commands/copy.c 22 Nov 2005 18:17:08 -0000 1.255 +++ src/backend/commands/copy.c 12 Dec 2005 17:18:44 -0000 @@ -102,6 +102,7 @@ int client_encoding; /* remote side's character encoding */ bool need_transcoding; /* client encoding diff from server? */ bool client_only_encoding; /* encoding not valid on server? */ + uint32 processed; /* # of tuples processed */ /* parameters from the COPY command */ Relation rel; /* relation to copy to or from */ @@ -646,7 +647,7 @@ * Do not allow the copy if user doesn't have proper permission to access * the table. */ -void +uint32 DoCopy(const CopyStmt *stmt) { CopyState cstate; @@ -660,6 +661,7 @@ AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT); AclResult aclresult; ListCell *option; + uint32 processed; /* Allocate workspace and zero all fields */ cstate = (CopyStateData *) palloc0(sizeof(CopyStateData)); @@ -935,7 +937,7 @@ initStringInfo(&cstate->line_buf); cstate->line_buf_converted = false; cstate->raw_buf = (char *) palloc(RAW_BUF_SIZE + 1); - cstate->raw_buf_index = cstate->raw_buf_len = 0; + cstate->raw_buf_index = cstate->raw_buf_len = cstate->processed = 0; /* Set up encoding conversion info */ cstate->client_encoding = pg_get_client_encoding(); @@ -1080,7 +1082,10 @@ pfree(cstate->attribute_buf.data); pfree(cstate->line_buf.data); pfree(cstate->raw_buf); + + processed = cstate->processed; pfree(cstate); + return processed; } @@ -1310,6 +1315,8 @@ VARSIZE(outputbytes) - VARHDRSZ); } } + + cstate->processed++; } CopySendEndOfRow(cstate); @@ -1916,6 +1923,8 @@ /* AFTER ROW INSERT Triggers */ ExecARInsertTriggers(estate, resultRelInfo, tuple); + + cstate->processed++; } } Index: src/backend/tcop/utility.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/tcop/utility.c,v retrieving revision 1.250 diff -u -r1.250 utility.c --- src/backend/tcop/utility.c 29 Nov 2005 01:25:49 -0000 1.250 +++ src/backend/tcop/utility.c 12 Dec 2005 17:18:45 -0000 @@ -640,7 +640,12 @@ break; case T_CopyStmt: - DoCopy((CopyStmt *) parsetree); + { + uint32 processed = DoCopy((CopyStmt *) parsetree); + + snprintf(completionTag, COMPLETION_TAG_BUFSIZE, + "COPY %u", processed); + } break; case T_PrepareStmt: Index: src/include/commands/copy.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/commands/copy.h,v retrieving revision 1.25 diff -u -r1.25 copy.h --- src/include/commands/copy.h 31 Dec 2004 22:03:28 -0000 1.25 +++ src/include/commands/copy.h 12 Dec 2005 17:19:07 -0000 @@ -17,6 +17,6 @@ #include "nodes/parsenodes.h" -extern void DoCopy(const CopyStmt *stmt); +extern uint32 DoCopy(const CopyStmt *stmt); #endif /* COPY_H */ Index: src/interfaces/libpq/fe-exec.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v retrieving revision 1.177 diff -u -r1.177 fe-exec.c --- src/interfaces/libpq/fe-exec.c 22 Nov 2005 18:17:32 -0000 1.177 +++ src/interfaces/libpq/fe-exec.c 12 Dec 2005 17:18:48 -0000 @@ -2177,7 +2177,7 @@ char * PQcmdTuples(PGresult *res) { - char *p; + char *p, *c; if (!res) return ""; @@ -2195,7 +2195,8 @@ p = res->cmdStatus + 6; else if (strncmp(res->cmdStatus, "FETCH ", 6) == 0) p = res->cmdStatus + 5; - else if (strncmp(res->cmdStatus, "MOVE ", 5) == 0) + else if (strncmp(res->cmdStatus, "MOVE ", 5) == 0 || + strncmp(res->cmdStatus, "COPY ", 5) == 0) p = res->cmdStatus + 4; else return ""; @@ -2203,14 +2204,19 @@ p++; if (*p == 0) - { - pqInternalNotice(&res->noticeHooks, - "could not interpret result from server: %s", - res->cmdStatus); - return ""; - } + goto error; - return p; + /* check if we have an int */ + for (c = p; isdigit((int) *c); ++c) + ; + if (*c == 0) + return p; + +error: + pqInternalNotice(&res->noticeHooks, + "could not interpret result from server: %s", + res->cmdStatus); + return ""; } /*
---------------------------(end of broadcast)--------------------------- TIP 9: In versions below 8.0, the planner will ignore your desire to choose an index scan if your joining column's datatypes do not match