Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * copyto.c
4 : * COPY <table> TO file/program/client
5 : *
6 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/commands/copyto.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include <ctype.h>
18 : #include <unistd.h>
19 : #include <sys/stat.h>
20 :
21 : #include "access/tableam.h"
22 : #include "commands/copy.h"
23 : #include "commands/progress.h"
24 : #include "executor/execdesc.h"
25 : #include "executor/executor.h"
26 : #include "executor/tuptable.h"
27 : #include "libpq/libpq.h"
28 : #include "libpq/pqformat.h"
29 : #include "mb/pg_wchar.h"
30 : #include "miscadmin.h"
31 : #include "pgstat.h"
32 : #include "storage/fd.h"
33 : #include "tcop/tcopprot.h"
34 : #include "utils/lsyscache.h"
35 : #include "utils/memutils.h"
36 : #include "utils/rel.h"
37 : #include "utils/snapmgr.h"
38 :
39 : /*
40 : * Represents the different dest cases we need to worry about at
41 : * the bottom level
42 : */
43 : typedef enum CopyDest
44 : {
45 : COPY_FILE, /* to file (or a piped program) */
46 : COPY_FRONTEND, /* to frontend */
47 : COPY_CALLBACK, /* to callback function */
48 : } CopyDest;
49 :
50 : /*
51 : * This struct contains all the state variables used throughout a COPY TO
52 : * operation.
53 : *
54 : * Multi-byte encodings: all supported client-side encodings encode multi-byte
55 : * characters by having the first byte's high bit set. Subsequent bytes of the
56 : * character can have the high bit not set. When scanning data in such an
57 : * encoding to look for a match to a single-byte (ie ASCII) character, we must
58 : * use the full pg_encoding_mblen() machinery to skip over multibyte
59 : * characters, else we might find a false match to a trailing byte. In
60 : * supported server encodings, there is no possibility of a false match, and
61 : * it's faster to make useless comparisons to trailing bytes than it is to
62 : * invoke pg_encoding_mblen() to skip over them. encoding_embeds_ascii is true
63 : * when we have to do it the hard way.
64 : */
65 : typedef struct CopyToStateData
66 : {
67 : /* low-level state data */
68 : CopyDest copy_dest; /* type of copy source/destination */
69 : FILE *copy_file; /* used if copy_dest == COPY_FILE */
70 : StringInfo fe_msgbuf; /* used for all dests during COPY TO */
71 :
72 : int file_encoding; /* file or remote side's character encoding */
73 : bool need_transcoding; /* file encoding diff from server? */
74 : bool encoding_embeds_ascii; /* ASCII can be non-first byte? */
75 :
76 : /* parameters from the COPY command */
77 : Relation rel; /* relation to copy to */
78 : QueryDesc *queryDesc; /* executable query to copy from */
79 : List *attnumlist; /* integer list of attnums to copy */
80 : char *filename; /* filename, or NULL for STDOUT */
81 : bool is_program; /* is 'filename' a program to popen? */
82 : copy_data_dest_cb data_dest_cb; /* function for writing data */
83 :
84 : CopyFormatOptions opts;
85 : Node *whereClause; /* WHERE condition (or NULL) */
86 :
87 : /*
88 : * Working state
89 : */
90 : MemoryContext copycontext; /* per-copy execution context */
91 :
92 : FmgrInfo *out_functions; /* lookup info for output functions */
93 : MemoryContext rowcontext; /* per-row evaluation context */
94 : uint64 bytes_processed; /* number of bytes processed so far */
95 : } CopyToStateData;
96 :
97 : /* DestReceiver for COPY (query) TO */
98 : typedef struct
99 : {
100 : DestReceiver pub; /* publicly-known function pointers */
101 : CopyToState cstate; /* CopyToStateData for the command */
102 : uint64 processed; /* # of tuples processed */
103 : } DR_copy;
104 :
105 : /* NOTE: there's a copy of this in copyfromparse.c */
106 : static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
107 :
108 :
109 : /* non-export function prototypes */
110 : static void EndCopy(CopyToState cstate);
111 : static void ClosePipeToProgram(CopyToState cstate);
112 : static void CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot);
113 : static void CopyAttributeOutText(CopyToState cstate, const char *string);
114 : static void CopyAttributeOutCSV(CopyToState cstate, const char *string,
115 : bool use_quote);
116 :
117 : /* Low-level communications functions */
118 : static void SendCopyBegin(CopyToState cstate);
119 : static void SendCopyEnd(CopyToState cstate);
120 : static void CopySendData(CopyToState cstate, const void *databuf, int datasize);
121 : static void CopySendString(CopyToState cstate, const char *str);
122 : static void CopySendChar(CopyToState cstate, char c);
123 : static void CopySendEndOfRow(CopyToState cstate);
124 : static void CopySendInt32(CopyToState cstate, int32 val);
125 : static void CopySendInt16(CopyToState cstate, int16 val);
126 :
127 :
128 : /*
129 : * Send copy start/stop messages for frontend copies. These have changed
130 : * in past protocol redesigns.
131 : */
132 : static void
133 8474 : SendCopyBegin(CopyToState cstate)
134 : {
135 : StringInfoData buf;
136 8474 : int natts = list_length(cstate->attnumlist);
137 8474 : int16 format = (cstate->opts.binary ? 1 : 0);
138 : int i;
139 :
140 8474 : pq_beginmessage(&buf, PqMsg_CopyOutResponse);
141 8474 : pq_sendbyte(&buf, format); /* overall format */
142 8474 : pq_sendint16(&buf, natts);
143 38874 : for (i = 0; i < natts; i++)
144 30400 : pq_sendint16(&buf, format); /* per-column formats */
145 8474 : pq_endmessage(&buf);
146 8474 : cstate->copy_dest = COPY_FRONTEND;
147 8474 : }
148 :
149 : static void
150 8472 : SendCopyEnd(CopyToState cstate)
151 : {
152 : /* Shouldn't have any unsent data */
153 : Assert(cstate->fe_msgbuf->len == 0);
154 : /* Send Copy Done message */
155 8472 : pq_putemptymessage(PqMsg_CopyDone);
156 8472 : }
157 :
158 : /*----------
159 : * CopySendData sends output data to the destination (file or frontend)
160 : * CopySendString does the same for null-terminated strings
161 : * CopySendChar does the same for single characters
162 : * CopySendEndOfRow does the appropriate thing at end of each data row
163 : * (data is not actually flushed except by CopySendEndOfRow)
164 : *
165 : * NB: no data conversion is applied by these functions
166 : *----------
167 : */
168 : static void
169 12649934 : CopySendData(CopyToState cstate, const void *databuf, int datasize)
170 : {
171 12649934 : appendBinaryStringInfo(cstate->fe_msgbuf, databuf, datasize);
172 12649934 : }
173 :
174 : static void
175 1171600 : CopySendString(CopyToState cstate, const char *str)
176 : {
177 1171600 : appendBinaryStringInfo(cstate->fe_msgbuf, str, strlen(str));
178 1171600 : }
179 :
180 : static void
181 14049126 : CopySendChar(CopyToState cstate, char c)
182 : {
183 14049126 : appendStringInfoCharMacro(cstate->fe_msgbuf, c);
184 14049126 : }
185 :
186 : static void
187 3648920 : CopySendEndOfRow(CopyToState cstate)
188 : {
189 3648920 : StringInfo fe_msgbuf = cstate->fe_msgbuf;
190 :
191 3648920 : switch (cstate->copy_dest)
192 : {
193 12282 : case COPY_FILE:
194 12282 : if (!cstate->opts.binary)
195 : {
196 : /* Default line termination depends on platform */
197 : #ifndef WIN32
198 12258 : CopySendChar(cstate, '\n');
199 : #else
200 : CopySendString(cstate, "\r\n");
201 : #endif
202 : }
203 :
204 12282 : if (fwrite(fe_msgbuf->data, fe_msgbuf->len, 1,
205 12282 : cstate->copy_file) != 1 ||
206 12282 : ferror(cstate->copy_file))
207 : {
208 0 : if (cstate->is_program)
209 : {
210 0 : if (errno == EPIPE)
211 : {
212 : /*
213 : * The pipe will be closed automatically on error at
214 : * the end of transaction, but we might get a better
215 : * error message from the subprocess' exit code than
216 : * just "Broken Pipe"
217 : */
218 0 : ClosePipeToProgram(cstate);
219 :
220 : /*
221 : * If ClosePipeToProgram() didn't throw an error, the
222 : * program terminated normally, but closed the pipe
223 : * first. Restore errno, and throw an error.
224 : */
225 0 : errno = EPIPE;
226 : }
227 0 : ereport(ERROR,
228 : (errcode_for_file_access(),
229 : errmsg("could not write to COPY program: %m")));
230 : }
231 : else
232 0 : ereport(ERROR,
233 : (errcode_for_file_access(),
234 : errmsg("could not write to COPY file: %m")));
235 : }
236 12282 : break;
237 3636632 : case COPY_FRONTEND:
238 : /* The FE/BE protocol uses \n as newline for all platforms */
239 3636632 : if (!cstate->opts.binary)
240 3636610 : CopySendChar(cstate, '\n');
241 :
242 : /* Dump the accumulated row as one CopyData message */
243 3636632 : (void) pq_putmessage(PqMsg_CopyData, fe_msgbuf->data, fe_msgbuf->len);
244 3636632 : break;
245 6 : case COPY_CALLBACK:
246 6 : cstate->data_dest_cb(fe_msgbuf->data, fe_msgbuf->len);
247 6 : break;
248 : }
249 :
250 : /* Update the progress */
251 3648920 : cstate->bytes_processed += fe_msgbuf->len;
252 3648920 : pgstat_progress_update_param(PROGRESS_COPY_BYTES_PROCESSED, cstate->bytes_processed);
253 :
254 3648920 : resetStringInfo(fe_msgbuf);
255 3648920 : }
256 :
257 : /*
258 : * These functions do apply some data conversion
259 : */
260 :
261 : /*
262 : * CopySendInt32 sends an int32 in network byte order
263 : */
264 : static inline void
265 188 : CopySendInt32(CopyToState cstate, int32 val)
266 : {
267 : uint32 buf;
268 :
269 188 : buf = pg_hton32((uint32) val);
270 188 : CopySendData(cstate, &buf, sizeof(buf));
271 188 : }
272 :
273 : /*
274 : * CopySendInt16 sends an int16 in network byte order
275 : */
276 : static inline void
277 46 : CopySendInt16(CopyToState cstate, int16 val)
278 : {
279 : uint16 buf;
280 :
281 46 : buf = pg_hton16((uint16) val);
282 46 : CopySendData(cstate, &buf, sizeof(buf));
283 46 : }
284 :
285 : /*
286 : * Closes the pipe to an external program, checking the pclose() return code.
287 : */
288 : static void
289 0 : ClosePipeToProgram(CopyToState cstate)
290 : {
291 : int pclose_rc;
292 :
293 : Assert(cstate->is_program);
294 :
295 0 : pclose_rc = ClosePipeStream(cstate->copy_file);
296 0 : if (pclose_rc == -1)
297 0 : ereport(ERROR,
298 : (errcode_for_file_access(),
299 : errmsg("could not close pipe to external command: %m")));
300 0 : else if (pclose_rc != 0)
301 : {
302 0 : ereport(ERROR,
303 : (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
304 : errmsg("program \"%s\" failed",
305 : cstate->filename),
306 : errdetail_internal("%s", wait_result_to_str(pclose_rc))));
307 : }
308 0 : }
309 :
310 : /*
311 : * Release resources allocated in a cstate for COPY TO/FROM.
312 : */
313 : static void
314 8536 : EndCopy(CopyToState cstate)
315 : {
316 8536 : if (cstate->is_program)
317 : {
318 0 : ClosePipeToProgram(cstate);
319 : }
320 : else
321 : {
322 8536 : if (cstate->filename != NULL && FreeFile(cstate->copy_file))
323 0 : ereport(ERROR,
324 : (errcode_for_file_access(),
325 : errmsg("could not close file \"%s\": %m",
326 : cstate->filename)));
327 : }
328 :
329 8536 : pgstat_progress_end_command();
330 :
331 8536 : MemoryContextDelete(cstate->copycontext);
332 8536 : pfree(cstate);
333 8536 : }
334 :
335 : /*
336 : * Setup CopyToState to read tuples from a table or a query for COPY TO.
337 : *
338 : * 'rel': Relation to be copied
339 : * 'raw_query': Query whose results are to be copied
340 : * 'queryRelId': OID of base relation to convert to a query (for RLS)
341 : * 'filename': Name of server-local file to write, NULL for STDOUT
342 : * 'is_program': true if 'filename' is program to execute
343 : * 'data_dest_cb': Callback that processes the output data
344 : * 'attnamelist': List of char *, columns to include. NIL selects all cols.
345 : * 'options': List of DefElem. See copy_opt_item in gram.y for selections.
346 : *
347 : * Returns a CopyToState, to be passed to DoCopyTo() and related functions.
348 : */
349 : CopyToState
350 8726 : BeginCopyTo(ParseState *pstate,
351 : Relation rel,
352 : RawStmt *raw_query,
353 : Oid queryRelId,
354 : const char *filename,
355 : bool is_program,
356 : copy_data_dest_cb data_dest_cb,
357 : List *attnamelist,
358 : List *options)
359 : {
360 : CopyToState cstate;
361 8726 : bool pipe = (filename == NULL && data_dest_cb == NULL);
362 : TupleDesc tupDesc;
363 : int num_phys_attrs;
364 : MemoryContext oldcontext;
365 8726 : const int progress_cols[] = {
366 : PROGRESS_COPY_COMMAND,
367 : PROGRESS_COPY_TYPE
368 : };
369 8726 : int64 progress_vals[] = {
370 : PROGRESS_COPY_COMMAND_TO,
371 : 0
372 : };
373 :
374 8726 : if (rel != NULL && rel->rd_rel->relkind != RELKIND_RELATION)
375 : {
376 12 : if (rel->rd_rel->relkind == RELKIND_VIEW)
377 12 : ereport(ERROR,
378 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
379 : errmsg("cannot copy from view \"%s\"",
380 : RelationGetRelationName(rel)),
381 : errhint("Try the COPY (SELECT ...) TO variant.")));
382 0 : else if (rel->rd_rel->relkind == RELKIND_MATVIEW)
383 0 : ereport(ERROR,
384 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
385 : errmsg("cannot copy from materialized view \"%s\"",
386 : RelationGetRelationName(rel)),
387 : errhint("Try the COPY (SELECT ...) TO variant.")));
388 0 : else if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
389 0 : ereport(ERROR,
390 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
391 : errmsg("cannot copy from foreign table \"%s\"",
392 : RelationGetRelationName(rel)),
393 : errhint("Try the COPY (SELECT ...) TO variant.")));
394 0 : else if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
395 0 : ereport(ERROR,
396 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
397 : errmsg("cannot copy from sequence \"%s\"",
398 : RelationGetRelationName(rel))));
399 0 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
400 0 : ereport(ERROR,
401 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
402 : errmsg("cannot copy from partitioned table \"%s\"",
403 : RelationGetRelationName(rel)),
404 : errhint("Try the COPY (SELECT ...) TO variant.")));
405 : else
406 0 : ereport(ERROR,
407 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
408 : errmsg("cannot copy from non-table relation \"%s\"",
409 : RelationGetRelationName(rel))));
410 : }
411 :
412 :
413 : /* Allocate workspace and zero all fields */
414 8714 : cstate = (CopyToStateData *) palloc0(sizeof(CopyToStateData));
415 :
416 : /*
417 : * We allocate everything used by a cstate in a new memory context. This
418 : * avoids memory leaks during repeated use of COPY in a query.
419 : */
420 8714 : cstate->copycontext = AllocSetContextCreate(CurrentMemoryContext,
421 : "COPY",
422 : ALLOCSET_DEFAULT_SIZES);
423 :
424 8714 : oldcontext = MemoryContextSwitchTo(cstate->copycontext);
425 :
426 : /* Extract options from the statement node tree */
427 8714 : ProcessCopyOptions(pstate, &cstate->opts, false /* is_from */ , options);
428 :
429 : /* Process the source/target relation or query */
430 8672 : if (rel)
431 : {
432 : Assert(!raw_query);
433 :
434 8166 : cstate->rel = rel;
435 :
436 8166 : tupDesc = RelationGetDescr(cstate->rel);
437 : }
438 : else
439 : {
440 : List *rewritten;
441 : Query *query;
442 : PlannedStmt *plan;
443 : DestReceiver *dest;
444 :
445 506 : cstate->rel = NULL;
446 :
447 : /*
448 : * Run parse analysis and rewrite. Note this also acquires sufficient
449 : * locks on the source table(s).
450 : */
451 506 : rewritten = pg_analyze_and_rewrite_fixedparams(raw_query,
452 : pstate->p_sourcetext, NULL, 0,
453 : NULL);
454 :
455 : /* check that we got back something we can work with */
456 494 : if (rewritten == NIL)
457 : {
458 18 : ereport(ERROR,
459 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
460 : errmsg("DO INSTEAD NOTHING rules are not supported for COPY")));
461 : }
462 476 : else if (list_length(rewritten) > 1)
463 : {
464 : ListCell *lc;
465 :
466 : /* examine queries to determine which error message to issue */
467 102 : foreach(lc, rewritten)
468 : {
469 84 : Query *q = lfirst_node(Query, lc);
470 :
471 84 : if (q->querySource == QSRC_QUAL_INSTEAD_RULE)
472 18 : ereport(ERROR,
473 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
474 : errmsg("conditional DO INSTEAD rules are not supported for COPY")));
475 66 : if (q->querySource == QSRC_NON_INSTEAD_RULE)
476 18 : ereport(ERROR,
477 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
478 : errmsg("DO ALSO rules are not supported for COPY")));
479 : }
480 :
481 18 : ereport(ERROR,
482 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
483 : errmsg("multi-statement DO INSTEAD rules are not supported for COPY")));
484 : }
485 :
486 422 : query = linitial_node(Query, rewritten);
487 :
488 : /* The grammar allows SELECT INTO, but we don't support that */
489 422 : if (query->utilityStmt != NULL &&
490 18 : IsA(query->utilityStmt, CreateTableAsStmt))
491 12 : ereport(ERROR,
492 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
493 : errmsg("COPY (SELECT INTO) is not supported")));
494 :
495 : /* The only other utility command we could see is NOTIFY */
496 410 : if (query->utilityStmt != NULL)
497 6 : ereport(ERROR,
498 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
499 : errmsg("COPY query must not be a utility command")));
500 :
501 : /*
502 : * Similarly the grammar doesn't enforce the presence of a RETURNING
503 : * clause, but this is required here.
504 : */
505 404 : if (query->commandType != CMD_SELECT &&
506 110 : query->returningList == NIL)
507 : {
508 : Assert(query->commandType == CMD_INSERT ||
509 : query->commandType == CMD_UPDATE ||
510 : query->commandType == CMD_DELETE ||
511 : query->commandType == CMD_MERGE);
512 :
513 24 : ereport(ERROR,
514 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
515 : errmsg("COPY query must have a RETURNING clause")));
516 : }
517 :
518 : /* plan the query */
519 380 : plan = pg_plan_query(query, pstate->p_sourcetext,
520 : CURSOR_OPT_PARALLEL_OK, NULL);
521 :
522 : /*
523 : * With row-level security and a user using "COPY relation TO", we
524 : * have to convert the "COPY relation TO" to a query-based COPY (eg:
525 : * "COPY (SELECT * FROM ONLY relation) TO"), to allow the rewriter to
526 : * add in any RLS clauses.
527 : *
528 : * When this happens, we are passed in the relid of the originally
529 : * found relation (which we have locked). As the planner will look up
530 : * the relation again, we double-check here to make sure it found the
531 : * same one that we have locked.
532 : */
533 378 : if (queryRelId != InvalidOid)
534 : {
535 : /*
536 : * Note that with RLS involved there may be multiple relations,
537 : * and while the one we need is almost certainly first, we don't
538 : * make any guarantees of that in the planner, so check the whole
539 : * list and make sure we find the original relation.
540 : */
541 54 : if (!list_member_oid(plan->relationOids, queryRelId))
542 0 : ereport(ERROR,
543 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
544 : errmsg("relation referenced by COPY statement has changed")));
545 : }
546 :
547 : /*
548 : * Use a snapshot with an updated command ID to ensure this query sees
549 : * results of any previously executed queries.
550 : */
551 378 : PushCopiedSnapshot(GetActiveSnapshot());
552 378 : UpdateActiveSnapshotCommandId();
553 :
554 : /* Create dest receiver for COPY OUT */
555 378 : dest = CreateDestReceiver(DestCopyOut);
556 378 : ((DR_copy *) dest)->cstate = cstate;
557 :
558 : /* Create a QueryDesc requesting no output */
559 378 : cstate->queryDesc = CreateQueryDesc(plan, NULL, pstate->p_sourcetext,
560 : GetActiveSnapshot(),
561 : InvalidSnapshot,
562 : dest, NULL, NULL, 0);
563 :
564 : /*
565 : * Call ExecutorStart to prepare the plan for execution.
566 : *
567 : * ExecutorStart computes a result tupdesc for us
568 : */
569 378 : if (!ExecutorStart(cstate->queryDesc, 0))
570 0 : elog(ERROR, "ExecutorStart() failed unexpectedly");
571 :
572 372 : tupDesc = cstate->queryDesc->tupDesc;
573 : }
574 :
575 : /* Generate or convert list of attributes to process */
576 8538 : cstate->attnumlist = CopyGetAttnums(tupDesc, cstate->rel, attnamelist);
577 :
578 8538 : num_phys_attrs = tupDesc->natts;
579 :
580 : /* Convert FORCE_QUOTE name list to per-column flags, check validity */
581 8538 : cstate->opts.force_quote_flags = (bool *) palloc0(num_phys_attrs * sizeof(bool));
582 8538 : if (cstate->opts.force_quote_all)
583 : {
584 18 : MemSet(cstate->opts.force_quote_flags, true, num_phys_attrs * sizeof(bool));
585 : }
586 8520 : else if (cstate->opts.force_quote)
587 : {
588 : List *attnums;
589 : ListCell *cur;
590 :
591 24 : attnums = CopyGetAttnums(tupDesc, cstate->rel, cstate->opts.force_quote);
592 :
593 48 : foreach(cur, attnums)
594 : {
595 24 : int attnum = lfirst_int(cur);
596 24 : Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
597 :
598 24 : if (!list_member_int(cstate->attnumlist, attnum))
599 0 : ereport(ERROR,
600 : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
601 : /*- translator: %s is the name of a COPY option, e.g. FORCE_NOT_NULL */
602 : errmsg("%s column \"%s\" not referenced by COPY",
603 : "FORCE_QUOTE", NameStr(attr->attname))));
604 24 : cstate->opts.force_quote_flags[attnum - 1] = true;
605 : }
606 : }
607 :
608 : /* Use client encoding when ENCODING option is not specified. */
609 8538 : if (cstate->opts.file_encoding < 0)
610 8520 : cstate->file_encoding = pg_get_client_encoding();
611 : else
612 18 : cstate->file_encoding = cstate->opts.file_encoding;
613 :
614 : /*
615 : * Set up encoding conversion info if the file and server encodings differ
616 : * (see also pg_server_to_any).
617 : */
618 8538 : if (cstate->file_encoding == GetDatabaseEncoding() ||
619 8 : cstate->file_encoding == PG_SQL_ASCII)
620 8536 : cstate->need_transcoding = false;
621 : else
622 2 : cstate->need_transcoding = true;
623 :
624 : /* See Multibyte encoding comment above */
625 8538 : cstate->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(cstate->file_encoding);
626 :
627 8538 : cstate->copy_dest = COPY_FILE; /* default */
628 :
629 8538 : if (data_dest_cb)
630 : {
631 2 : progress_vals[1] = PROGRESS_COPY_TYPE_CALLBACK;
632 2 : cstate->copy_dest = COPY_CALLBACK;
633 2 : cstate->data_dest_cb = data_dest_cb;
634 : }
635 8536 : else if (pipe)
636 : {
637 8474 : progress_vals[1] = PROGRESS_COPY_TYPE_PIPE;
638 :
639 : Assert(!is_program); /* the grammar does not allow this */
640 8474 : if (whereToSendOutput != DestRemote)
641 0 : cstate->copy_file = stdout;
642 : }
643 : else
644 : {
645 62 : cstate->filename = pstrdup(filename);
646 62 : cstate->is_program = is_program;
647 :
648 62 : if (is_program)
649 : {
650 0 : progress_vals[1] = PROGRESS_COPY_TYPE_PROGRAM;
651 0 : cstate->copy_file = OpenPipeStream(cstate->filename, PG_BINARY_W);
652 0 : if (cstate->copy_file == NULL)
653 0 : ereport(ERROR,
654 : (errcode_for_file_access(),
655 : errmsg("could not execute command \"%s\": %m",
656 : cstate->filename)));
657 : }
658 : else
659 : {
660 : mode_t oumask; /* Pre-existing umask value */
661 : struct stat st;
662 :
663 62 : progress_vals[1] = PROGRESS_COPY_TYPE_FILE;
664 :
665 : /*
666 : * Prevent write to relative path ... too easy to shoot oneself in
667 : * the foot by overwriting a database file ...
668 : */
669 62 : if (!is_absolute_path(filename))
670 0 : ereport(ERROR,
671 : (errcode(ERRCODE_INVALID_NAME),
672 : errmsg("relative path not allowed for COPY to file")));
673 :
674 62 : oumask = umask(S_IWGRP | S_IWOTH);
675 62 : PG_TRY();
676 : {
677 62 : cstate->copy_file = AllocateFile(cstate->filename, PG_BINARY_W);
678 : }
679 0 : PG_FINALLY();
680 : {
681 62 : umask(oumask);
682 : }
683 62 : PG_END_TRY();
684 62 : if (cstate->copy_file == NULL)
685 : {
686 : /* copy errno because ereport subfunctions might change it */
687 0 : int save_errno = errno;
688 :
689 0 : ereport(ERROR,
690 : (errcode_for_file_access(),
691 : errmsg("could not open file \"%s\" for writing: %m",
692 : cstate->filename),
693 : (save_errno == ENOENT || save_errno == EACCES) ?
694 : errhint("COPY TO instructs the PostgreSQL server process to write a file. "
695 : "You may want a client-side facility such as psql's \\copy.") : 0));
696 : }
697 :
698 62 : if (fstat(fileno(cstate->copy_file), &st))
699 0 : ereport(ERROR,
700 : (errcode_for_file_access(),
701 : errmsg("could not stat file \"%s\": %m",
702 : cstate->filename)));
703 :
704 62 : if (S_ISDIR(st.st_mode))
705 0 : ereport(ERROR,
706 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
707 : errmsg("\"%s\" is a directory", cstate->filename)));
708 : }
709 : }
710 :
711 : /* initialize progress */
712 8538 : pgstat_progress_start_command(PROGRESS_COMMAND_COPY,
713 8538 : cstate->rel ? RelationGetRelid(cstate->rel) : InvalidOid);
714 8538 : pgstat_progress_update_multi_param(2, progress_cols, progress_vals);
715 :
716 8538 : cstate->bytes_processed = 0;
717 :
718 8538 : MemoryContextSwitchTo(oldcontext);
719 :
720 8538 : return cstate;
721 : }
722 :
723 : /*
724 : * Clean up storage and release resources for COPY TO.
725 : */
726 : void
727 8536 : EndCopyTo(CopyToState cstate)
728 : {
729 8536 : if (cstate->queryDesc != NULL)
730 : {
731 : /* Close down the query and free resources. */
732 372 : ExecutorFinish(cstate->queryDesc);
733 372 : ExecutorEnd(cstate->queryDesc);
734 372 : FreeQueryDesc(cstate->queryDesc);
735 372 : PopActiveSnapshot();
736 : }
737 :
738 : /* Clean up storage */
739 8536 : EndCopy(cstate);
740 8536 : }
741 :
742 : /*
743 : * Copy from relation or query TO file.
744 : *
745 : * Returns the number of rows processed.
746 : */
747 : uint64
748 8538 : DoCopyTo(CopyToState cstate)
749 : {
750 8538 : bool pipe = (cstate->filename == NULL && cstate->data_dest_cb == NULL);
751 8538 : bool fe_copy = (pipe && whereToSendOutput == DestRemote);
752 : TupleDesc tupDesc;
753 : int num_phys_attrs;
754 : ListCell *cur;
755 : uint64 processed;
756 :
757 8538 : if (fe_copy)
758 8474 : SendCopyBegin(cstate);
759 :
760 8538 : if (cstate->rel)
761 8166 : tupDesc = RelationGetDescr(cstate->rel);
762 : else
763 372 : tupDesc = cstate->queryDesc->tupDesc;
764 8538 : num_phys_attrs = tupDesc->natts;
765 8538 : cstate->opts.null_print_client = cstate->opts.null_print; /* default */
766 :
767 : /* We use fe_msgbuf as a per-row buffer regardless of copy_dest */
768 8538 : cstate->fe_msgbuf = makeStringInfo();
769 :
770 : /* Get info about the columns we need to process. */
771 8538 : cstate->out_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
772 39168 : foreach(cur, cstate->attnumlist)
773 : {
774 30632 : int attnum = lfirst_int(cur);
775 : Oid out_func_oid;
776 : bool isvarlena;
777 30632 : Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
778 :
779 30632 : if (cstate->opts.binary)
780 62 : getTypeBinaryOutputInfo(attr->atttypid,
781 : &out_func_oid,
782 : &isvarlena);
783 : else
784 30570 : getTypeOutputInfo(attr->atttypid,
785 : &out_func_oid,
786 : &isvarlena);
787 30630 : fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
788 : }
789 :
790 : /*
791 : * Create a temporary memory context that we can reset once per row to
792 : * recover palloc'd memory. This avoids any problems with leaks inside
793 : * datatype output routines, and should be faster than retail pfree's
794 : * anyway. (We don't need a whole econtext as CopyFrom does.)
795 : */
796 8536 : cstate->rowcontext = AllocSetContextCreate(CurrentMemoryContext,
797 : "COPY TO",
798 : ALLOCSET_DEFAULT_SIZES);
799 :
800 8536 : if (cstate->opts.binary)
801 : {
802 : /* Generate header for a binary copy */
803 : int32 tmp;
804 :
805 : /* Signature */
806 14 : CopySendData(cstate, BinarySignature, 11);
807 : /* Flags field */
808 14 : tmp = 0;
809 14 : CopySendInt32(cstate, tmp);
810 : /* No header extension */
811 14 : tmp = 0;
812 14 : CopySendInt32(cstate, tmp);
813 : }
814 : else
815 : {
816 : /*
817 : * For non-binary copy, we need to convert null_print to file
818 : * encoding, because it will be sent directly with CopySendString.
819 : */
820 8522 : if (cstate->need_transcoding)
821 2 : cstate->opts.null_print_client = pg_server_to_any(cstate->opts.null_print,
822 : cstate->opts.null_print_len,
823 : cstate->file_encoding);
824 :
825 : /* if a header has been requested send the line */
826 8522 : if (cstate->opts.header_line)
827 : {
828 18 : bool hdr_delim = false;
829 :
830 54 : foreach(cur, cstate->attnumlist)
831 : {
832 36 : int attnum = lfirst_int(cur);
833 : char *colname;
834 :
835 36 : if (hdr_delim)
836 18 : CopySendChar(cstate, cstate->opts.delim[0]);
837 36 : hdr_delim = true;
838 :
839 36 : colname = NameStr(TupleDescAttr(tupDesc, attnum - 1)->attname);
840 :
841 36 : if (cstate->opts.csv_mode)
842 24 : CopyAttributeOutCSV(cstate, colname, false);
843 : else
844 12 : CopyAttributeOutText(cstate, colname);
845 : }
846 :
847 18 : CopySendEndOfRow(cstate);
848 : }
849 : }
850 :
851 8536 : if (cstate->rel)
852 : {
853 : TupleTableSlot *slot;
854 : TableScanDesc scandesc;
855 :
856 8164 : scandesc = table_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
857 8164 : slot = table_slot_create(cstate->rel, NULL);
858 :
859 8164 : processed = 0;
860 3650050 : while (table_scan_getnextslot(scandesc, ForwardScanDirection, slot))
861 : {
862 3641886 : CHECK_FOR_INTERRUPTS();
863 :
864 : /* Deconstruct the tuple ... */
865 3641886 : slot_getallattrs(slot);
866 :
867 : /* Format and send the data */
868 3641886 : CopyOneRowTo(cstate, slot);
869 :
870 : /*
871 : * Increment the number of processed tuples, and report the
872 : * progress.
873 : */
874 3641886 : pgstat_progress_update_param(PROGRESS_COPY_TUPLES_PROCESSED,
875 : ++processed);
876 : }
877 :
878 8164 : ExecDropSingleTupleTableSlot(slot);
879 8164 : table_endscan(scandesc);
880 : }
881 : else
882 : {
883 : /* run the plan --- the dest receiver will send tuples */
884 372 : ExecutorRun(cstate->queryDesc, ForwardScanDirection, 0);
885 372 : processed = ((DR_copy *) cstate->queryDesc->dest)->processed;
886 : }
887 :
888 8536 : if (cstate->opts.binary)
889 : {
890 : /* Generate trailer for a binary copy */
891 14 : CopySendInt16(cstate, -1);
892 : /* Need to flush out the trailer */
893 14 : CopySendEndOfRow(cstate);
894 : }
895 :
896 8536 : MemoryContextDelete(cstate->rowcontext);
897 :
898 8536 : if (fe_copy)
899 8472 : SendCopyEnd(cstate);
900 :
901 8536 : return processed;
902 : }
903 :
904 : /*
905 : * Emit one row during DoCopyTo().
906 : */
907 : static void
908 3648888 : CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
909 : {
910 3648888 : FmgrInfo *out_functions = cstate->out_functions;
911 : MemoryContext oldcontext;
912 :
913 3648888 : MemoryContextReset(cstate->rowcontext);
914 3648888 : oldcontext = MemoryContextSwitchTo(cstate->rowcontext);
915 :
916 3648888 : if (cstate->opts.binary)
917 : {
918 : /* Binary per-tuple header */
919 32 : CopySendInt16(cstate, list_length(cstate->attnumlist));
920 : }
921 :
922 : /* Make sure the tuple is fully deconstructed */
923 3648888 : slot_getallattrs(slot);
924 :
925 3648888 : if (!cstate->opts.binary)
926 : {
927 3648856 : bool need_delim = false;
928 :
929 21313962 : foreach_int(attnum, cstate->attnumlist)
930 : {
931 14016250 : Datum value = slot->tts_values[attnum - 1];
932 14016250 : bool isnull = slot->tts_isnull[attnum - 1];
933 : char *string;
934 :
935 14016250 : if (need_delim)
936 10367532 : CopySendChar(cstate, cstate->opts.delim[0]);
937 14016250 : need_delim = true;
938 :
939 14016250 : if (isnull)
940 1171306 : CopySendString(cstate, cstate->opts.null_print_client);
941 : else
942 : {
943 12844944 : string = OutputFunctionCall(&out_functions[attnum - 1],
944 : value);
945 12844944 : if (cstate->opts.csv_mode)
946 594 : CopyAttributeOutCSV(cstate, string,
947 594 : cstate->opts.force_quote_flags[attnum - 1]);
948 : else
949 12844350 : CopyAttributeOutText(cstate, string);
950 : }
951 : }
952 : }
953 : else
954 : {
955 224 : foreach_int(attnum, cstate->attnumlist)
956 : {
957 160 : Datum value = slot->tts_values[attnum - 1];
958 160 : bool isnull = slot->tts_isnull[attnum - 1];
959 : bytea *outputbytes;
960 :
961 160 : if (isnull)
962 30 : CopySendInt32(cstate, -1);
963 : else
964 : {
965 130 : outputbytes = SendFunctionCall(&out_functions[attnum - 1],
966 : value);
967 130 : CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ);
968 130 : CopySendData(cstate, VARDATA(outputbytes),
969 130 : VARSIZE(outputbytes) - VARHDRSZ);
970 : }
971 : }
972 : }
973 :
974 3648888 : CopySendEndOfRow(cstate);
975 :
976 3648888 : MemoryContextSwitchTo(oldcontext);
977 3648888 : }
978 :
979 : /*
980 : * Send text representation of one attribute, with conversion and escaping
981 : */
982 : #define DUMPSOFAR() \
983 : do { \
984 : if (ptr > start) \
985 : CopySendData(cstate, start, ptr - start); \
986 : } while (0)
987 :
988 : static void
989 12844362 : CopyAttributeOutText(CopyToState cstate, const char *string)
990 : {
991 : const char *ptr;
992 : const char *start;
993 : char c;
994 12844362 : char delimc = cstate->opts.delim[0];
995 :
996 12844362 : if (cstate->need_transcoding)
997 0 : ptr = pg_server_to_any(string, strlen(string), cstate->file_encoding);
998 : else
999 12844362 : ptr = string;
1000 :
1001 : /*
1002 : * We have to grovel through the string searching for control characters
1003 : * and instances of the delimiter character. In most cases, though, these
1004 : * are infrequent. To avoid overhead from calling CopySendData once per
1005 : * character, we dump out all characters between escaped characters in a
1006 : * single call. The loop invariant is that the data from "start" to "ptr"
1007 : * can be sent literally, but hasn't yet been.
1008 : *
1009 : * We can skip pg_encoding_mblen() overhead when encoding is safe, because
1010 : * in valid backend encodings, extra bytes of a multibyte character never
1011 : * look like ASCII. This loop is sufficiently performance-critical that
1012 : * it's worth making two copies of it to get the IS_HIGHBIT_SET() test out
1013 : * of the normal safe-encoding path.
1014 : */
1015 12844362 : if (cstate->encoding_embeds_ascii)
1016 : {
1017 0 : start = ptr;
1018 0 : while ((c = *ptr) != '\0')
1019 : {
1020 0 : if ((unsigned char) c < (unsigned char) 0x20)
1021 : {
1022 : /*
1023 : * \r and \n must be escaped, the others are traditional. We
1024 : * prefer to dump these using the C-like notation, rather than
1025 : * a backslash and the literal character, because it makes the
1026 : * dump file a bit more proof against Microsoftish data
1027 : * mangling.
1028 : */
1029 0 : switch (c)
1030 : {
1031 0 : case '\b':
1032 0 : c = 'b';
1033 0 : break;
1034 0 : case '\f':
1035 0 : c = 'f';
1036 0 : break;
1037 0 : case '\n':
1038 0 : c = 'n';
1039 0 : break;
1040 0 : case '\r':
1041 0 : c = 'r';
1042 0 : break;
1043 0 : case '\t':
1044 0 : c = 't';
1045 0 : break;
1046 0 : case '\v':
1047 0 : c = 'v';
1048 0 : break;
1049 0 : default:
1050 : /* If it's the delimiter, must backslash it */
1051 0 : if (c == delimc)
1052 0 : break;
1053 : /* All ASCII control chars are length 1 */
1054 0 : ptr++;
1055 0 : continue; /* fall to end of loop */
1056 : }
1057 : /* if we get here, we need to convert the control char */
1058 0 : DUMPSOFAR();
1059 0 : CopySendChar(cstate, '\\');
1060 0 : CopySendChar(cstate, c);
1061 0 : start = ++ptr; /* do not include char in next run */
1062 : }
1063 0 : else if (c == '\\' || c == delimc)
1064 : {
1065 0 : DUMPSOFAR();
1066 0 : CopySendChar(cstate, '\\');
1067 0 : start = ptr++; /* we include char in next run */
1068 : }
1069 0 : else if (IS_HIGHBIT_SET(c))
1070 0 : ptr += pg_encoding_mblen(cstate->file_encoding, ptr);
1071 : else
1072 0 : ptr++;
1073 : }
1074 : }
1075 : else
1076 : {
1077 12844362 : start = ptr;
1078 134950330 : while ((c = *ptr) != '\0')
1079 : {
1080 122105968 : if ((unsigned char) c < (unsigned char) 0x20)
1081 : {
1082 : /*
1083 : * \r and \n must be escaped, the others are traditional. We
1084 : * prefer to dump these using the C-like notation, rather than
1085 : * a backslash and the literal character, because it makes the
1086 : * dump file a bit more proof against Microsoftish data
1087 : * mangling.
1088 : */
1089 13774 : switch (c)
1090 : {
1091 0 : case '\b':
1092 0 : c = 'b';
1093 0 : break;
1094 0 : case '\f':
1095 0 : c = 'f';
1096 0 : break;
1097 11652 : case '\n':
1098 11652 : c = 'n';
1099 11652 : break;
1100 0 : case '\r':
1101 0 : c = 'r';
1102 0 : break;
1103 2122 : case '\t':
1104 2122 : c = 't';
1105 2122 : break;
1106 0 : case '\v':
1107 0 : c = 'v';
1108 0 : break;
1109 0 : default:
1110 : /* If it's the delimiter, must backslash it */
1111 0 : if (c == delimc)
1112 0 : break;
1113 : /* All ASCII control chars are length 1 */
1114 0 : ptr++;
1115 0 : continue; /* fall to end of loop */
1116 : }
1117 : /* if we get here, we need to convert the control char */
1118 13774 : DUMPSOFAR();
1119 13774 : CopySendChar(cstate, '\\');
1120 13774 : CopySendChar(cstate, c);
1121 13774 : start = ++ptr; /* do not include char in next run */
1122 : }
1123 122092194 : else if (c == '\\' || c == delimc)
1124 : {
1125 4356 : DUMPSOFAR();
1126 4356 : CopySendChar(cstate, '\\');
1127 4356 : start = ptr++; /* we include char in next run */
1128 : }
1129 : else
1130 122087838 : ptr++;
1131 : }
1132 : }
1133 :
1134 12844362 : DUMPSOFAR();
1135 12844362 : }
1136 :
1137 : /*
1138 : * Send text representation of one attribute, with conversion and
1139 : * CSV-style escaping
1140 : */
1141 : static void
1142 618 : CopyAttributeOutCSV(CopyToState cstate, const char *string,
1143 : bool use_quote)
1144 : {
1145 : const char *ptr;
1146 : const char *start;
1147 : char c;
1148 618 : char delimc = cstate->opts.delim[0];
1149 618 : char quotec = cstate->opts.quote[0];
1150 618 : char escapec = cstate->opts.escape[0];
1151 618 : bool single_attr = (list_length(cstate->attnumlist) == 1);
1152 :
1153 : /* force quoting if it matches null_print (before conversion!) */
1154 618 : if (!use_quote && strcmp(string, cstate->opts.null_print) == 0)
1155 54 : use_quote = true;
1156 :
1157 618 : if (cstate->need_transcoding)
1158 0 : ptr = pg_server_to_any(string, strlen(string), cstate->file_encoding);
1159 : else
1160 618 : ptr = string;
1161 :
1162 : /*
1163 : * Make a preliminary pass to discover if it needs quoting
1164 : */
1165 618 : if (!use_quote)
1166 : {
1167 : /*
1168 : * Quote '\.' if it appears alone on a line, so that it will not be
1169 : * interpreted as an end-of-data marker. (PG 18 and up will not
1170 : * interpret '\.' in CSV that way, except in embedded-in-SQL data; but
1171 : * we want the data to be loadable by older versions too. Also, this
1172 : * avoids breaking clients that are still using PQgetline().)
1173 : */
1174 432 : if (single_attr && strcmp(ptr, "\\.") == 0)
1175 6 : use_quote = true;
1176 : else
1177 : {
1178 426 : const char *tptr = ptr;
1179 :
1180 2208 : while ((c = *tptr) != '\0')
1181 : {
1182 1914 : if (c == delimc || c == quotec || c == '\n' || c == '\r')
1183 : {
1184 132 : use_quote = true;
1185 132 : break;
1186 : }
1187 1782 : if (IS_HIGHBIT_SET(c) && cstate->encoding_embeds_ascii)
1188 0 : tptr += pg_encoding_mblen(cstate->file_encoding, tptr);
1189 : else
1190 1782 : tptr++;
1191 : }
1192 : }
1193 : }
1194 :
1195 618 : if (use_quote)
1196 : {
1197 324 : CopySendChar(cstate, quotec);
1198 :
1199 : /*
1200 : * We adopt the same optimization strategy as in CopyAttributeOutText
1201 : */
1202 324 : start = ptr;
1203 2538 : while ((c = *ptr) != '\0')
1204 : {
1205 2214 : if (c == quotec || c == escapec)
1206 : {
1207 156 : DUMPSOFAR();
1208 156 : CopySendChar(cstate, escapec);
1209 156 : start = ptr; /* we include char in next run */
1210 : }
1211 2214 : if (IS_HIGHBIT_SET(c) && cstate->encoding_embeds_ascii)
1212 0 : ptr += pg_encoding_mblen(cstate->file_encoding, ptr);
1213 : else
1214 2214 : ptr++;
1215 : }
1216 324 : DUMPSOFAR();
1217 :
1218 324 : CopySendChar(cstate, quotec);
1219 : }
1220 : else
1221 : {
1222 : /* If it doesn't need quoting, we can just dump it as-is */
1223 294 : CopySendString(cstate, ptr);
1224 : }
1225 618 : }
1226 :
1227 : /*
1228 : * copy_dest_startup --- executor startup
1229 : */
1230 : static void
1231 372 : copy_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
1232 : {
1233 : /* no-op */
1234 372 : }
1235 :
1236 : /*
1237 : * copy_dest_receive --- receive one tuple
1238 : */
1239 : static bool
1240 7002 : copy_dest_receive(TupleTableSlot *slot, DestReceiver *self)
1241 : {
1242 7002 : DR_copy *myState = (DR_copy *) self;
1243 7002 : CopyToState cstate = myState->cstate;
1244 :
1245 : /* Send the data */
1246 7002 : CopyOneRowTo(cstate, slot);
1247 :
1248 : /* Increment the number of processed tuples, and report the progress */
1249 7002 : pgstat_progress_update_param(PROGRESS_COPY_TUPLES_PROCESSED,
1250 7002 : ++myState->processed);
1251 :
1252 7002 : return true;
1253 : }
1254 :
1255 : /*
1256 : * copy_dest_shutdown --- executor end
1257 : */
1258 : static void
1259 372 : copy_dest_shutdown(DestReceiver *self)
1260 : {
1261 : /* no-op */
1262 372 : }
1263 :
1264 : /*
1265 : * copy_dest_destroy --- release DestReceiver object
1266 : */
1267 : static void
1268 0 : copy_dest_destroy(DestReceiver *self)
1269 : {
1270 0 : pfree(self);
1271 0 : }
1272 :
1273 : /*
1274 : * CreateCopyDestReceiver -- create a suitable DestReceiver object
1275 : */
1276 : DestReceiver *
1277 378 : CreateCopyDestReceiver(void)
1278 : {
1279 378 : DR_copy *self = (DR_copy *) palloc(sizeof(DR_copy));
1280 :
1281 378 : self->pub.receiveSlot = copy_dest_receive;
1282 378 : self->pub.rStartup = copy_dest_startup;
1283 378 : self->pub.rShutdown = copy_dest_shutdown;
1284 378 : self->pub.rDestroy = copy_dest_destroy;
1285 378 : self->pub.mydest = DestCopyOut;
1286 :
1287 378 : self->cstate = NULL; /* will be set later */
1288 378 : self->processed = 0;
1289 :
1290 378 : return (DestReceiver *) self;
1291 : }
|