summaryrefslogtreecommitdiff
path: root/src/backend/pgxc/copy/remotecopy.c
blob: 0a88eaa67d21e0fa492d00e290994149ab942723 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/*-------------------------------------------------------------------------
 *
 * remotecopy.c
 *		Implements an extension of COPY command for remote management
 *
 * Portions Copyright (c) 2012-2014, TransLattice, Inc.
 * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
 * Portions Copyright (c) 2010-2012, Postgres-XC Development Group
 *
 *
 * IDENTIFICATION
 *		src/backend/pgxc/copy/remotecopy.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"
#include "miscadmin.h"
#include "lib/stringinfo.h"
#include "nodes/pg_list.h"
#include "optimizer/pgxcship.h"
#include "optimizer/planner.h"
#include "pgxc/pgxcnode.h"
#include "pgxc/remotecopy.h"
#include "rewrite/rewriteHandler.h"
#include "utils/builtins.h"
#include "utils/rel.h"
#ifdef PGXC
#include "utils/lsyscache.h"
#endif

static void RemoteCopy_QuoteStr(StringInfo query_buf, char *value);

/*
 * RemoteCopy_GetRelationLoc
 * Get relation node list based on COPY data involved. An empty list is
 * returned to caller if relation involved has no locator information
 * as it is the case of a system relation.
 */
void
RemoteCopy_GetRelationLoc(RemoteCopyData *state,
						  Relation rel,
                          List *attnums)
{
	/*
	 * If target table does not exists on nodes (e.g. system table)
	 * the location info returned is NULL. This is the criteria, when
	 * we need to run COPY on Coordinator
	 */
	state->rel_loc = GetRelationLocInfo(RelationGetRelid(rel));

	if (state->rel_loc &&
			AttributeNumberIsValid(state->rel_loc->partAttrNum))
	{
		TupleDesc tdesc;
		Form_pg_attribute pattr;
		/* determine distribution column data type */
		tdesc = RelationGetDescr(rel);

		pattr = tdesc->attrs[state->rel_loc->partAttrNum - 1];
		state->dist_type = pattr->atttypid;
	}
	else
		state->dist_type = InvalidOid;

	state->locator = NULL;
}

/*
 * RemoteCopy_BuildStatement
 * Build a COPY query for remote management
 */
void
RemoteCopy_BuildStatement(RemoteCopyData *state,
						  Relation rel,
						  RemoteCopyOptions *options,
						  List *attnamelist,
						  List *attnums)
{
	int			attnum;
	TupleDesc	tupDesc = RelationGetDescr(rel);

	/*
	 * Build up query string for the Datanodes, it should match
	 * to original string, but should have STDIN/STDOUT instead
	 * of filename.
	 */
	initStringInfo(&state->query_buf);
	appendStringInfoString(&state->query_buf, "COPY ");

	/*
	 * The table name should be qualified, unless the table is a temporary table
	 */
	if (rel->rd_backend == MyBackendId)
		appendStringInfo(&state->query_buf, "%s",
						 quote_identifier(RelationGetRelationName(rel)));
	else
		appendStringInfo(&state->query_buf, "%s",
						 quote_qualified_identifier(
								get_namespace_name(RelationGetNamespace(rel)),
								RelationGetRelationName(rel)));

	if (attnamelist)
	{
		ListCell *cell;
		ListCell *prev = NULL;
		appendStringInfoString(&state->query_buf, " (");
		foreach (cell, attnamelist)
		{
			if (prev)
				appendStringInfoString(&state->query_buf, ", ");
			appendStringInfoString(&state->query_buf,
								   quote_identifier(strVal(lfirst(cell))));
			prev = cell;
		}

		/*
		 * For COPY FROM, we need to append unspecified attributes that have
		 * default expressions associated.
		 */
		if (state->is_from)
		{
			for (attnum = 1; attnum <= tupDesc->natts; attnum++)
			{
				/* Don't let dropped attributes go into the column list */
				if (tupDesc->attrs[attnum - 1]->attisdropped)
					continue;

				if (!list_member_int(attnums, attnum))
				{
					/* Append only if the default expression is not shippable. */
					Expr *defexpr = (Expr*) build_column_default(rel, attnum);
					if (defexpr &&
						!pgxc_is_expr_shippable(expression_planner(defexpr), NULL))
						{
							appendStringInfoString(&state->query_buf, ", ");
							appendStringInfoString(&state->query_buf,
							   quote_identifier(NameStr(tupDesc->attrs[attnum - 1]->attname)));
						}
				}
			}
		}

		appendStringInfoChar(&state->query_buf, ')');
	}

	if (state->is_from)
		appendStringInfoString(&state->query_buf, " FROM STDIN");
	else
		appendStringInfoString(&state->query_buf, " TO STDOUT");

	if (options->rco_binary)
		appendStringInfoString(&state->query_buf, " BINARY");

	if (options->rco_oids)
		appendStringInfoString(&state->query_buf, " OIDS");

	if (options->rco_delim)
	{
		if ((!options->rco_csv_mode && options->rco_delim[0] != '\t')
			|| (options->rco_csv_mode && options->rco_delim[0] != ','))
		{
			appendStringInfoString(&state->query_buf, " DELIMITER AS ");
			RemoteCopy_QuoteStr(&state->query_buf, options->rco_delim);
		}
	}

	if (options->rco_null_print)
	{
		if ((!options->rco_csv_mode && strcmp(options->rco_null_print, "\\N"))
			|| (options->rco_csv_mode && strcmp(options->rco_null_print, "")))
		{
			appendStringInfoString(&state->query_buf, " NULL AS ");
			RemoteCopy_QuoteStr(&state->query_buf, options->rco_null_print);
		}
	}

	if (options->rco_csv_mode)
		appendStringInfoString(&state->query_buf, " CSV");

	/*
	 * It is not necessary to send the HEADER part to Datanodes.
	 * Sending data is sufficient.
	 */
	if (options->rco_quote && options->rco_quote[0] != '"')
	{
		appendStringInfoString(&state->query_buf, " QUOTE AS ");
		RemoteCopy_QuoteStr(&state->query_buf, options->rco_quote);
	}

	if (options->rco_escape && options->rco_quote && options->rco_escape[0] != options->rco_quote[0])
	{
		appendStringInfoString(&state->query_buf, " ESCAPE AS ");
		RemoteCopy_QuoteStr(&state->query_buf, options->rco_escape);
	}

	if (options->rco_force_quote)
	{
		ListCell *cell;
		ListCell *prev = NULL;
		appendStringInfoString(&state->query_buf, " FORCE QUOTE ");
		foreach (cell, options->rco_force_quote)
		{
			if (prev)
				appendStringInfoString(&state->query_buf, ", ");
			appendStringInfoString(&state->query_buf,
								   quote_identifier(strVal(lfirst(cell))));
			prev = cell;
		}
	}

	if (options->rco_force_notnull)
	{
		ListCell *cell;
		ListCell *prev = NULL;
		appendStringInfoString(&state->query_buf, " FORCE NOT NULL ");
		foreach (cell, options->rco_force_notnull)
		{
			if (prev)
				appendStringInfoString(&state->query_buf, ", ");
			appendStringInfoString(&state->query_buf,
								   quote_identifier(strVal(lfirst(cell))));
			prev = cell;
		}
	}
}


/*
 * Build a default set for RemoteCopyOptions
 */
RemoteCopyOptions *
makeRemoteCopyOptions(void)
{
	RemoteCopyOptions *res = (RemoteCopyOptions *) palloc(sizeof(RemoteCopyOptions));
	res->rco_binary = false;
	res->rco_oids = false;
	res->rco_csv_mode = false;
	res->rco_delim = NULL;
	res->rco_null_print = NULL;
	res->rco_quote = NULL;
	res->rco_escape = NULL;
	res->rco_force_quote = NIL;
	res->rco_force_notnull = NIL;
	return res;
}


/*
 * FreeRemoteCopyOptions
 * Free remote COPY options structure
 */
void
FreeRemoteCopyOptions(RemoteCopyOptions *options)
{
	/* Leave if nothing */
	if (options == NULL)
		return;

	/* Free field by field */
	if (options->rco_delim)
		pfree(options->rco_delim);
	if (options->rco_null_print)
		pfree(options->rco_null_print);
	if (options->rco_quote)
		pfree(options->rco_quote);
	if (options->rco_escape)
		pfree(options->rco_escape);
	if (options->rco_force_quote)
		list_free(options->rco_force_quote);
	if (options->rco_force_notnull)
		list_free(options->rco_force_notnull);

	/* Then finish the work */
	pfree(options);
}


/*
 * FreeRemoteCopyData
 * Free remote COPY state data structure
 */
void
FreeRemoteCopyData(RemoteCopyData *state)
{
	/* Leave if nothing */
	if (state == NULL)
		return;
	if (state->locator)
		freeLocator(state->locator);
	if (state->query_buf.data)
		pfree(state->query_buf.data);
	FreeRelationLocInfo(state->rel_loc);
	pfree(state);
}

#define APPENDSOFAR(query_buf, start, current) \
	if (current > start) \
		appendBinaryStringInfo(query_buf, start, current - start)

/*
 * RemoteCopy_QuoteStr
 * Append quoted value to the query buffer. Value is escaped if needed
 * When rewriting query to be sent down to nodes we should escape special
 * characters, that may present in the value. The characters are backslash(\)
 * and single quote ('). These characters are escaped by doubling. We do not
 * have to escape characters like \t, \v, \b, etc. because Datanode interprets
 * them properly.
 * We use E'...' syntax for literals containing backslashes.
 */
static void
RemoteCopy_QuoteStr(StringInfo query_buf, char *value)
{
	char   *start = value;
	char   *current = value;
	char	c;
	bool	has_backslash = (strchr(value, '\\') != NULL);

	if (has_backslash)
		appendStringInfoChar(query_buf, 'E');

	appendStringInfoChar(query_buf, '\'');

	while ((c = *current) != '\0')
	{
		switch (c)
		{
			case '\\':
			case '\'':
				APPENDSOFAR(query_buf, start, current);
				/* Double current */
				appendStringInfoChar(query_buf, c);
				/* Second current will be appended next time */
				start = current;
				/* fallthru */
			default:
				current++;
		}
	}
	APPENDSOFAR(query_buf, start, current);
	appendStringInfoChar(query_buf, '\'');
}