Skip to content

Commit 1cba73c

Browse files
kouCommitfest Bot
authored and
Commitfest Bot
committed
Add support for implementing custom COPY handler as extension
* TO: Add CopyToStateData::opaque that can be used to keep data for custom COPY TO handler implementation * TO: Export CopySendEndOfRow() to send end of row data as CopyToStateFlush() * FROM: Add CopyFromStateData::opaque that can be used to keep data for custom COPY FROM handler implementation * FROM: Export CopyGetData() to get the next data as CopyFromStateGetData() * FROM: Add CopyFromSkipErrorRow() for "ON_ERROR stop" and "LOG_VERBOSITY verbose" COPY FROM extensions must call CopyFromSkipErrorRow() when CopyFromOneRow callback reports an error by errsave(). CopyFromSkipErrorRow() handles "ON_ERROR stop" and "LOG_VERBOSITY verbose" cases.
1 parent 0255f99 commit 1cba73c

File tree

8 files changed

+231
-37
lines changed

8 files changed

+231
-37
lines changed

src/backend/commands/copyfromparse.c

+57-36
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,17 @@ CopyReadBinaryData(CopyFromState cstate, char *dest, int nbytes)
739739
return copied_bytes;
740740
}
741741

742+
/*
743+
* Export CopyGetData() for extensions. We want to keep CopyGetData() as a
744+
* static function for optimization. CopyGetData() calls in this file may be
745+
* optimized by a compiler.
746+
*/
747+
int
748+
CopyFromStateGetData(CopyFromState cstate, void *dest, int minread, int maxread)
749+
{
750+
return CopyGetData(cstate, dest, minread, maxread);
751+
}
752+
742753
/*
743754
* This function is exposed for use by extensions that read raw fields in the
744755
* next line. See NextCopyFromRawFieldsInternal() for details.
@@ -927,6 +938,51 @@ CopyFromCSVOneRow(CopyFromState cstate, ExprContext *econtext, Datum *values,
927938
return CopyFromTextLikeOneRow(cstate, econtext, values, nulls, true);
928939
}
929940

941+
/*
942+
* Call this when you report an error by errsave() in your CopyFromOneRow
943+
* callback. This handles "ON_ERROR stop" and "LOG_VERBOSITY verbose" cases
944+
* for you.
945+
*/
946+
void
947+
CopyFromSkipErrorRow(CopyFromState cstate)
948+
{
949+
Assert(cstate->opts.on_error != COPY_ON_ERROR_STOP);
950+
951+
cstate->num_errors++;
952+
953+
if (cstate->opts.log_verbosity == COPY_LOG_VERBOSITY_VERBOSE)
954+
{
955+
/*
956+
* Since we emit line number and column info in the below notice
957+
* message, we suppress error context information other than the
958+
* relation name.
959+
*/
960+
Assert(!cstate->relname_only);
961+
cstate->relname_only = true;
962+
963+
if (cstate->cur_attval)
964+
{
965+
char *attval;
966+
967+
attval = CopyLimitPrintoutLength(cstate->cur_attval);
968+
ereport(NOTICE,
969+
errmsg("skipping row due to data type incompatibility at line %llu for column \"%s\": \"%s\"",
970+
(unsigned long long) cstate->cur_lineno,
971+
cstate->cur_attname,
972+
attval));
973+
pfree(attval);
974+
}
975+
else
976+
ereport(NOTICE,
977+
errmsg("skipping row due to data type incompatibility at line %llu for column \"%s\": null input",
978+
(unsigned long long) cstate->cur_lineno,
979+
cstate->cur_attname));
980+
981+
/* reset relname_only */
982+
cstate->relname_only = false;
983+
}
984+
}
985+
930986
/*
931987
* Workhorse for CopyFromTextOneRow() and CopyFromCSVOneRow().
932988
*
@@ -1033,42 +1089,7 @@ CopyFromTextLikeOneRow(CopyFromState cstate, ExprContext *econtext,
10331089
(Node *) cstate->escontext,
10341090
&values[m]))
10351091
{
1036-
Assert(cstate->opts.on_error != COPY_ON_ERROR_STOP);
1037-
1038-
cstate->num_errors++;
1039-
1040-
if (cstate->opts.log_verbosity == COPY_LOG_VERBOSITY_VERBOSE)
1041-
{
1042-
/*
1043-
* Since we emit line number and column info in the below
1044-
* notice message, we suppress error context information other
1045-
* than the relation name.
1046-
*/
1047-
Assert(!cstate->relname_only);
1048-
cstate->relname_only = true;
1049-
1050-
if (cstate->cur_attval)
1051-
{
1052-
char *attval;
1053-
1054-
attval = CopyLimitPrintoutLength(cstate->cur_attval);
1055-
ereport(NOTICE,
1056-
errmsg("skipping row due to data type incompatibility at line %llu for column \"%s\": \"%s\"",
1057-
(unsigned long long) cstate->cur_lineno,
1058-
cstate->cur_attname,
1059-
attval));
1060-
pfree(attval);
1061-
}
1062-
else
1063-
ereport(NOTICE,
1064-
errmsg("skipping row due to data type incompatibility at line %llu for column \"%s\": null input",
1065-
(unsigned long long) cstate->cur_lineno,
1066-
cstate->cur_attname));
1067-
1068-
/* reset relname_only */
1069-
cstate->relname_only = false;
1070-
}
1071-
1092+
CopyFromSkipErrorRow(cstate);
10721093
return true;
10731094
}
10741095

src/backend/commands/copyto.c

+12
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,18 @@ CopySendEndOfRow(CopyToState cstate)
456456
resetStringInfo(fe_msgbuf);
457457
}
458458

459+
/*
460+
* Export CopySendEndOfRow() for extensions. We want to keep
461+
* CopySendEndOfRow() as a static function for
462+
* optimization. CopySendEndOfRow() calls in this file may be optimized by a
463+
* compiler.
464+
*/
465+
void
466+
CopyToStateFlush(CopyToState cstate)
467+
{
468+
CopySendEndOfRow(cstate);
469+
}
470+
459471
/*
460472
* Wrapper function of CopySendEndOfRow for text and CSV formats. Sends the
461473
* line termination and do common appropriate things for the end of row.

src/include/commands/copyapi.h

+6
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ typedef struct CopyToRoutine
5656
void (*CopyToEnd) (CopyToState cstate);
5757
} CopyToRoutine;
5858

59+
extern void CopyToStateFlush(CopyToState cstate);
60+
5961
/*
6062
* API structure for a COPY FROM format implementation. Note this must be
6163
* allocated in a server-lifetime manner, typically as a static const struct.
@@ -106,4 +108,8 @@ typedef struct CopyFromRoutine
106108
void (*CopyFromEnd) (CopyFromState cstate);
107109
} CopyFromRoutine;
108110

111+
extern int CopyFromStateGetData(CopyFromState cstate, void *dest, int minread, int maxread);
112+
113+
extern void CopyFromSkipErrorRow(CopyFromState cstate);
114+
109115
#endif /* COPYAPI_H */

src/include/commands/copyfrom_internal.h

+3
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ typedef struct CopyFromStateData
181181
#define RAW_BUF_BYTES(cstate) ((cstate)->raw_buf_len - (cstate)->raw_buf_index)
182182

183183
uint64 bytes_processed; /* number of bytes processed so far */
184+
185+
/* For custom format implementation */
186+
void *opaque; /* private space */
184187
} CopyFromStateData;
185188

186189
extern void ReceiveCopyBegin(CopyFromState cstate);

src/include/commands/copyto_internal.h

+3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ typedef struct CopyToStateData
7878
FmgrInfo *out_functions; /* lookup info for output functions */
7979
MemoryContext rowcontext; /* per-row evaluation context */
8080
uint64 bytes_processed; /* number of bytes processed so far */
81+
82+
/* For custom format implementation */
83+
void *opaque; /* private space */
8184
} CopyToStateData;
8285

8386
#endif /* COPYTO_INTERNAL_H */

src/test/modules/test_copy_format/expected/no_schema.out

+47
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,59 @@
11
CREATE EXTENSION test_copy_format;
22
CREATE TABLE public.test (a smallint, b integer, c bigint);
33
INSERT INTO public.test VALUES (1, 2, 3), (12, 34, 56), (123, 456, 789);
4+
-- 987 is accepted.
5+
-- 654 is a hard error because ON_ERROR is stop by default.
46
COPY public.test FROM stdin WITH (FORMAT 'test_copy_format');
57
NOTICE: test_copy_format: is_from=true
68
NOTICE: CopyFromInFunc: attribute: smallint
79
NOTICE: CopyFromInFunc: attribute: integer
810
NOTICE: CopyFromInFunc: attribute: bigint
911
NOTICE: CopyFromStart: the number of attributes: 3
1012
NOTICE: CopyFromOneRow
13+
NOTICE: CopyFromOneRow
14+
ERROR: invalid value: "6"
15+
CONTEXT: COPY test, line 2, column a: "6"
16+
-- 987 is accepted.
17+
-- 654 is a soft error because ON_ERROR is ignore.
18+
COPY public.test FROM stdin WITH (FORMAT 'test_copy_format', ON_ERROR ignore);
19+
NOTICE: test_copy_format: is_from=true
20+
NOTICE: CopyFromInFunc: attribute: smallint
21+
NOTICE: CopyFromInFunc: attribute: integer
22+
NOTICE: CopyFromInFunc: attribute: bigint
23+
NOTICE: CopyFromStart: the number of attributes: 3
24+
NOTICE: CopyFromOneRow
25+
NOTICE: CopyFromOneRow
26+
NOTICE: CopyFromOneRow
27+
NOTICE: 1 row was skipped due to data type incompatibility
28+
NOTICE: CopyFromEnd
29+
-- 987 is accepted.
30+
-- 654 is a soft error because ON_ERROR is ignore.
31+
COPY public.test FROM stdin WITH (FORMAT 'test_copy_format', ON_ERROR ignore, LOG_VERBOSITY verbose);
32+
NOTICE: test_copy_format: is_from=true
33+
NOTICE: CopyFromInFunc: attribute: smallint
34+
NOTICE: CopyFromInFunc: attribute: integer
35+
NOTICE: CopyFromInFunc: attribute: bigint
36+
NOTICE: CopyFromStart: the number of attributes: 3
37+
NOTICE: CopyFromOneRow
38+
NOTICE: CopyFromOneRow
39+
NOTICE: skipping row due to data type incompatibility at line 2 for column "a": "6"
40+
NOTICE: CopyFromOneRow
41+
NOTICE: 1 row was skipped due to data type incompatibility
1142
NOTICE: CopyFromEnd
43+
-- 987 is accepted.
44+
-- 654 is a soft error because ON_ERROR is ignore.
45+
-- 321 is a hard error.
46+
COPY public.test FROM stdin WITH (FORMAT 'test_copy_format', ON_ERROR ignore);
47+
NOTICE: test_copy_format: is_from=true
48+
NOTICE: CopyFromInFunc: attribute: smallint
49+
NOTICE: CopyFromInFunc: attribute: integer
50+
NOTICE: CopyFromInFunc: attribute: bigint
51+
NOTICE: CopyFromStart: the number of attributes: 3
52+
NOTICE: CopyFromOneRow
53+
NOTICE: CopyFromOneRow
54+
NOTICE: CopyFromOneRow
55+
ERROR: too much lines: 3
56+
CONTEXT: COPY test, line 3
1257
COPY public.test TO stdout WITH (FORMAT 'test_copy_format');
1358
NOTICE: test_copy_format: is_from=false
1459
NOTICE: CopyToOutFunc: attribute: smallint
@@ -18,6 +63,8 @@ NOTICE: CopyToStart: the number of attributes: 3
1863
NOTICE: CopyToOneRow: the number of valid values: 3
1964
NOTICE: CopyToOneRow: the number of valid values: 3
2065
NOTICE: CopyToOneRow: the number of valid values: 3
66+
NOTICE: CopyToOneRow: the number of valid values: 3
67+
NOTICE: CopyToOneRow: the number of valid values: 3
2168
NOTICE: CopyToEnd
2269
DROP TABLE public.test;
2370
DROP EXTENSION test_copy_format;

src/test/modules/test_copy_format/sql/no_schema.sql

+24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
11
CREATE EXTENSION test_copy_format;
22
CREATE TABLE public.test (a smallint, b integer, c bigint);
33
INSERT INTO public.test VALUES (1, 2, 3), (12, 34, 56), (123, 456, 789);
4+
-- 987 is accepted.
5+
-- 654 is a hard error because ON_ERROR is stop by default.
46
COPY public.test FROM stdin WITH (FORMAT 'test_copy_format');
7+
987
8+
654
9+
\.
10+
-- 987 is accepted.
11+
-- 654 is a soft error because ON_ERROR is ignore.
12+
COPY public.test FROM stdin WITH (FORMAT 'test_copy_format', ON_ERROR ignore);
13+
987
14+
654
15+
\.
16+
-- 987 is accepted.
17+
-- 654 is a soft error because ON_ERROR is ignore.
18+
COPY public.test FROM stdin WITH (FORMAT 'test_copy_format', ON_ERROR ignore, LOG_VERBOSITY verbose);
19+
987
20+
654
21+
\.
22+
-- 987 is accepted.
23+
-- 654 is a soft error because ON_ERROR is ignore.
24+
-- 321 is a hard error.
25+
COPY public.test FROM stdin WITH (FORMAT 'test_copy_format', ON_ERROR ignore);
26+
987
27+
654
28+
321
529
\.
630
COPY public.test TO stdout WITH (FORMAT 'test_copy_format');
731
DROP TABLE public.test;

src/test/modules/test_copy_format/test_copy_format.c

+79-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "postgres.h"
1515

1616
#include "commands/copyapi.h"
17+
#include "commands/copyfrom_internal.h"
1718
#include "commands/defrem.h"
1819
#include "utils/builtins.h"
1920

@@ -35,8 +36,85 @@ TestCopyFromStart(CopyFromState cstate, TupleDesc tupDesc)
3536
static bool
3637
TestCopyFromOneRow(CopyFromState cstate, ExprContext *econtext, Datum *values, bool *nulls)
3738
{
39+
int n_attributes = list_length(cstate->attnumlist);
40+
char *line;
41+
int line_size = n_attributes + 1; /* +1 is for new line */
42+
int read_bytes;
43+
3844
ereport(NOTICE, (errmsg("CopyFromOneRow")));
39-
return false;
45+
46+
cstate->cur_lineno++;
47+
line = palloc(line_size);
48+
read_bytes = CopyFromStateGetData(cstate, line, line_size, line_size);
49+
if (read_bytes == 0)
50+
return false;
51+
if (read_bytes != line_size)
52+
ereport(ERROR,
53+
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
54+
errmsg("one line must be %d bytes: %d",
55+
line_size, read_bytes)));
56+
57+
if (cstate->cur_lineno == 1)
58+
{
59+
/* Success */
60+
TupleDesc tupDesc = RelationGetDescr(cstate->rel);
61+
ListCell *cur;
62+
int i = 0;
63+
64+
foreach(cur, cstate->attnumlist)
65+
{
66+
int attnum = lfirst_int(cur);
67+
int m = attnum - 1;
68+
Form_pg_attribute att = TupleDescAttr(tupDesc, m);
69+
70+
if (att->atttypid == INT2OID)
71+
{
72+
values[i] = Int16GetDatum(line[i] - '0');
73+
}
74+
else if (att->atttypid == INT4OID)
75+
{
76+
values[i] = Int32GetDatum(line[i] - '0');
77+
}
78+
else if (att->atttypid == INT8OID)
79+
{
80+
values[i] = Int64GetDatum(line[i] - '0');
81+
}
82+
nulls[i] = false;
83+
i++;
84+
}
85+
}
86+
else if (cstate->cur_lineno == 2)
87+
{
88+
/* Soft error */
89+
TupleDesc tupDesc = RelationGetDescr(cstate->rel);
90+
int attnum = lfirst_int(list_head(cstate->attnumlist));
91+
int m = attnum - 1;
92+
Form_pg_attribute att = TupleDescAttr(tupDesc, m);
93+
char value[2];
94+
95+
cstate->cur_attname = NameStr(att->attname);
96+
value[0] = line[0];
97+
value[1] = '\0';
98+
cstate->cur_attval = value;
99+
errsave((Node *) cstate->escontext,
100+
(
101+
errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
102+
errmsg("invalid value: \"%c\"", line[0])));
103+
CopyFromSkipErrorRow(cstate);
104+
cstate->cur_attname = NULL;
105+
cstate->cur_attval = NULL;
106+
return true;
107+
}
108+
else
109+
{
110+
/* Hard error */
111+
ereport(ERROR,
112+
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
113+
errmsg("too much lines: %llu",
114+
(unsigned long long) cstate->cur_lineno)));
115+
}
116+
117+
return true;
40118
}
41119

42120
static void

0 commit comments

Comments
 (0)