*** a/contrib/file_fdw/file_fdw.c --- b/contrib/file_fdw/file_fdw.c *************** *** 58,67 **** static struct FileFdwOption valid_options[] = { { "quote", ForeignTableRelationId }, { "escape", ForeignTableRelationId }, { "null", ForeignTableRelationId }, /* FIXME: implement force_not_null option */ ! /* Centinel */ { NULL, InvalidOid } }; --- 58,68 ---- { "quote", ForeignTableRelationId }, { "escape", ForeignTableRelationId }, { "null", ForeignTableRelationId }, + { "textarray", ForeignTableRelationId }, /* FIXME: implement force_not_null option */ ! /* Sentinel */ { NULL, InvalidOid } }; *************** *** 134,139 **** file_fdw_validator(PG_FUNCTION_ARGS) --- 135,141 ---- char *escape = NULL; char *null = NULL; bool header; + bool textarray; /* Only superuser can change generic options of the foreign table */ if (catalog == ForeignTableRelationId && !superuser()) *************** *** 220,225 **** file_fdw_validator(PG_FUNCTION_ARGS) --- 222,231 ---- errmsg("null representation cannot use newline or carriage return"))); null = strVal(def->arg); } + else if (strcmp(def->defname, "textarray") == 0) + { + textarray = defGetBoolean(def); + } } /* Check options which depend on the file format. */ *** a/src/backend/commands/copy.c --- b/src/backend/commands/copy.c *************** *** 117,122 **** typedef struct CopyStateData --- 117,125 ---- bool *force_quote_flags; /* per-column CSV FQ flags */ bool *force_notnull_flags; /* per-column CSV FNN flags */ + /* param from FDW */ + bool text_array; /* scan to a single text array field */ + /* these are just for error messages, see CopyFromErrorCallback */ const char *cur_relname; /* table name for error messages */ int cur_lineno; /* line number for error messages */ *************** *** 970,975 **** BeginCopy(bool is_from, --- 973,986 ---- errmsg("argument to option \"%s\" must be a list of column names", defel->defname))); } + else if (strcmp(defel->defname, "textarray") == 0) + { + if (cstate->text_array) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + cstate->text_array = defGetBoolean(defel); + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), *************** *** 1109,1114 **** BeginCopy(bool is_from, --- 1120,1131 ---- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("CSV quote character must not appear in the NULL specification"))); + /* check textarray */ + if (cstate->text_array && !is_from) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("textarray only available in read mode"))); + if (rel) { Assert(!raw_query); *************** *** 1201,1206 **** BeginCopy(bool is_from, --- 1218,1236 ---- num_phys_attrs = tupDesc->natts; + /* make sure rel has the right shape for textarray */ + if (cstate->text_array) + { + if (num_phys_attrs != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("too many columns for textarray"))); + if (tupDesc->attrs[0]->atttypid != TEXTARRAYOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("target column must be of type text[] for textarray"))); + } + /* Convert FORCE QUOTE name list to per-column flags, check validity */ cstate->force_quote_flags = (bool *) palloc0(num_phys_attrs * sizeof(bool)); if (force_quote_all) *************** *** 2283,2289 **** NextCopyFrom(CopyState cstate) fldct = CopyReadAttributesText(cstate); /* check for overflowing fields */ ! if (nfields > 0 && fldct > nfields) ereport(ERROR, (errcode(ERRCODE_BAD_COPY_FILE_FORMAT), errmsg("extra data after last expected column"))); --- 2313,2319 ---- fldct = CopyReadAttributesText(cstate); /* check for overflowing fields */ ! if (nfields > 0 && fldct > nfields && !cstate->text_array) ereport(ERROR, (errcode(ERRCODE_BAD_COPY_FILE_FORMAT), errmsg("extra data after last expected column"))); *************** *** 2319,2324 **** NextCopyFrom(CopyState cstate) --- 2349,2401 ---- } } + if (cstate->text_array) + { + Datum *textvals; + bool *nulls; + int dims[1]; + int lbs[1]; + int fld; + + textvals = palloc(fldct * sizeof(Datum)); + nulls = palloc(fldct * sizeof(bool)); + + /* Treat an empty line as having no fields */ + if (fldct == 1 && field_strings[fld] == NULL && strlen(cstate->nulltext) == 0) + fldct = 0; + + dims[0] = fldct; + lbs[0] = 1; /* sql arrays typically start at 1 */ + + + for (fld=0; fld < fldct; fld++) + { + if (field_strings[fld] == NULL) + { + nulls[fld] = true; + } + else + { + nulls[fld] = false; + textvals[fld] = DirectFunctionCall1(textin, PointerGetDatum(field_strings[fld])); + } + } + + cstate->values[0] = construct_md_array(textvals, + nulls, + 1, + dims, + lbs, + TEXTOID,- + 1, + false, + 'i'); + cstate->nulls[0] = false; + cstate->cur_attname = NULL; + cstate->cur_attval = NULL; + } + else + { /* Loop to read the user attributes on the line. */ foreach(cur, cstate->attnumlist) { *************** *** 2350,2357 **** NextCopyFrom(CopyState cstate) cstate->cur_attname = NULL; cstate->cur_attval = NULL; } ! Assert(fieldno == nfields); } else { --- 2427,2435 ---- cstate->cur_attname = NULL; cstate->cur_attval = NULL; } + } ! Assert(cstate->text_array || fieldno == nfields); } else {