Index: doc/src/sgml/ref/copy.sgml =================================================================== RCS file: /cvsroot/pgsql-server/doc/src/sgml/ref/copy.sgml,v retrieving revision 1.56 diff -c -c -r1.56 copy.sgml *** doc/src/sgml/ref/copy.sgml 19 Apr 2004 17:22:30 -0000 1.56 --- doc/src/sgml/ref/copy.sgml 21 Apr 2004 00:17:51 -0000 *************** *** 29,35 **** [ NULL [ AS ] 'null string' ] [ CSV [ QUOTE [ AS ] 'quote' ] [ ESCAPE [ AS ] 'escape' ] ! [ LITERAL column [, ...] ] COPY tablename [ ( column [, ...] ) ] TO { 'filename' | STDOUT } --- 29,35 ---- [ NULL [ AS ] 'null string' ] [ CSV [ QUOTE [ AS ] 'quote' ] [ ESCAPE [ AS ] 'escape' ] ! [ FORCE NOT NULL column [, ...] ] COPY tablename [ ( column [, ...] ) ] TO { 'filename' | STDOUT } *************** *** 40,46 **** [ NULL [ AS ] 'null string' ] [ CSV [ QUOTE [ AS ] 'quote' ] [ ESCAPE [ AS ] 'escape' ] ! [ FORCE column [, ...] ] --- 40,46 ---- [ NULL [ AS ] 'null string' ] [ CSV [ QUOTE [ AS ] 'quote' ] [ ESCAPE [ AS ] 'escape' ] ! [ FORCE QUOTE column [, ...] ] *************** *** 185,194 **** CSV ! Enables Comma Separated Variable (CSV) mode. (Also called ! Comma Separated Value). It sets the default DELIMITER to ! comma, and QUOTE and ESCAPE values to ! double-quote. --- 185,194 ---- CSV ! Enables Comma Separated Variable (CSV) mode. (Also ! called Comma Separated Value). It sets the default ! DELIMITER to comma, and QUOTE and ! ESCAPE values to double-quote. *************** *** 207,244 **** escape ! Specifies the character that should appear before a QUOTE ! data character value in CSV mode. The default is the ! QUOTE value (usually double-quote). ! FORCE ! In CSV COPY TO mode, forces quoting ! to be used for all non-NULL values in each specified ! column. NULL output is never quoted. ! LITERAL ! In CSV COPY FROM mode, for each column specified, ! do not do a null string comparison; instead load the value ! literally. QUOTE and ESCAPE processing are still ! performed. ! ! ! If the null string is '' (the default ! in CSV mode), a missing input value (delimiter, ! delimiter), will load as a zero-length string. Delimiter, quote, ! quote, delimiter is always treated as a zero-length string on input. --- 207,239 ---- escape ! Specifies the character that should appear before a ! QUOTE data character value in CSV mode. ! The default is the QUOTE value (usually double-quote). ! FORCE QUOTE ! In CSV COPY TO mode, forces quoting to be ! used for all non-NULL values in each specified column. ! NULL output is never quoted. ! FORCE NOT NULL ! In CSV COPY FROM mode, process each ! specified column as though it was quoted and not a ! NULL value. For the default null string in ! CSV mode (''), this causes a missing ! values to be input as a zero-length strings. *************** *** 483,489 **** suffixed by the QUOTE character, and any occurrence within the value of a QUOTE character or the ESCAPE character is preceded by the escape character. ! You can also use FORCE to force quotes when outputting non-NULL values in specific columns. --- 478,484 ---- suffixed by the QUOTE character, and any occurrence within the value of a QUOTE character or the ESCAPE character is preceded by the escape character. ! You can also use FORCE QUOTE to force quotes when outputting non-NULL values in specific columns. *************** *** 496,502 **** is quoted. Therefore, using the default settings, a NULL is written as an unquoted empty string, while an empty string is written with double quotes (""). Reading values follows ! similar rules. You can use LITERAL to prevent NULL input comparisons for specific columns. --- 491,497 ---- is quoted. Therefore, using the default settings, a NULL is written as an unquoted empty string, while an empty string is written with double quotes (""). Reading values follows ! similar rules. You can use FORCE NOT NULL to prevent NULL input comparisons for specific columns. Index: doc/src/sgml/ref/psql-ref.sgml =================================================================== RCS file: /cvsroot/pgsql-server/doc/src/sgml/ref/psql-ref.sgml,v retrieving revision 1.111 diff -c -c -r1.111 psql-ref.sgml *** doc/src/sgml/ref/psql-ref.sgml 19 Apr 2004 17:22:30 -0000 1.111 --- doc/src/sgml/ref/psql-ref.sgml 21 Apr 2004 00:17:53 -0000 *************** *** 713,720 **** [ null [as] 'string' ] [ csv [ quote [as] 'character' ] [ escape [as] 'character' ] ! [ force column_list ] ! [ literal column_list ] ] --- 713,720 ---- [ null [as] 'string' ] [ csv [ quote [as] 'character' ] [ escape [as] 'character' ] ! [ force quote column_list ] ! [ force not null column_list ] ] Index: src/backend/commands/copy.c =================================================================== RCS file: /cvsroot/pgsql-server/src/backend/commands/copy.c,v retrieving revision 1.222 diff -c -c -r1.222 copy.c *** src/backend/commands/copy.c 19 Apr 2004 21:58:02 -0000 1.222 --- src/backend/commands/copy.c 21 Apr 2004 00:17:55 -0000 *************** *** 132,141 **** /* non-export function prototypes */ static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, char *escape, ! List *force_atts); static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, char *escape, ! List *literal_atts); static bool CopyReadLine(void); static char *CopyReadAttribute(const char *delim, const char *null_print, CopyReadResult *result, bool *isnull); --- 132,141 ---- /* non-export function prototypes */ static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, char *escape, ! List *force_quote_atts); static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, char *escape, ! List *force_notnull_atts); static bool CopyReadLine(void); static char *CopyReadAttribute(const char *delim, const char *null_print, CopyReadResult *result, bool *isnull); *************** *** 695,704 **** char *quote = NULL; char *escape = NULL; char *null_print = NULL; ! List *force = NIL; ! List *literal = NIL; ! List *force_atts = NIL; ! List *literal_atts = NIL; Relation rel; AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT); AclResult aclresult; --- 695,704 ---- char *quote = NULL; char *escape = NULL; char *null_print = NULL; ! List *force_quote = NIL; ! List *force_notnull = NIL; ! List *force_quote_atts = NIL; ! List *force_notnull_atts = NIL; Relation rel; AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT); AclResult aclresult; *************** *** 764,784 **** errmsg("conflicting or redundant options"))); escape = strVal(defel->arg); } ! else if (strcmp(defel->defname, "force") == 0) { ! if (force) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); ! force = (List *)defel->arg; } ! else if (strcmp(defel->defname, "literal") == 0) { ! if (literal) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); ! literal = (List *)defel->arg; } else elog(ERROR, "option \"%s\" not recognized", --- 764,784 ---- errmsg("conflicting or redundant options"))); escape = strVal(defel->arg); } ! else if (strcmp(defel->defname, "force_quote") == 0) { ! if (force_quote) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); ! force_quote = (List *)defel->arg; } ! else if (strcmp(defel->defname, "force_notnull") == 0) { ! if (force_notnull) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); ! force_notnull = (List *)defel->arg; } else elog(ERROR, "option \"%s\" not recognized", *************** *** 850,877 **** errmsg("COPY escape must be a single character"))); /* ! * Check force */ ! if (!csv_mode && force != NIL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("COPY force available only in CSV mode"))); ! if (force != NIL && is_from) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("COPY force only available using COPY TO"))); /* ! * Check literal */ ! if (!csv_mode && literal != NIL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("COPY literal available only in CSV mode"))); ! if (literal != NIL && !is_from) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("COPY literal only available using COPY FROM"))); /* * Don't allow the delimiter to appear in the null string. --- 850,877 ---- errmsg("COPY escape must be a single character"))); /* ! * Check force_quote */ ! if (!csv_mode && force_quote != NIL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("COPY force quote available only in CSV mode"))); ! if (force_quote != NIL && is_from) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("COPY force quote only available using COPY TO"))); /* ! * Check force_notnull */ ! if (!csv_mode && force_notnull != NIL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("COPY force not null available only in CSV mode"))); ! if (force_notnull != NIL && !is_from) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("COPY force not null only available using COPY FROM"))); /* * Don't allow the delimiter to appear in the null string. *************** *** 928,974 **** attnumlist = CopyGetAttnums(rel, attnamelist); /* ! * Check that FORCE references valid COPY columns */ ! if (force) { TupleDesc tupDesc = RelationGetDescr(rel); Form_pg_attribute *attr = tupDesc->attrs; List *cur; ! force_atts = CopyGetAttnums(rel, force); ! foreach(cur, force_atts) { int attnum = lfirsti(cur); if (!intMember(attnum, attnumlist)) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), ! errmsg("FORCE column \"%s\" not referenced by COPY", NameStr(attr[attnum - 1]->attname)))); } } /* ! * Check that LITERAL references valid COPY columns */ ! if (literal) { List *cur; TupleDesc tupDesc = RelationGetDescr(rel); Form_pg_attribute *attr = tupDesc->attrs; ! literal_atts = CopyGetAttnums(rel, literal); ! foreach(cur, literal_atts) { int attnum = lfirsti(cur); if (!intMember(attnum, attnumlist)) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), ! errmsg("LITERAL column \"%s\" not referenced by COPY", NameStr(attr[attnum - 1]->attname)))); } } --- 928,974 ---- attnumlist = CopyGetAttnums(rel, attnamelist); /* ! * Check that FORCE QUOTE references valid COPY columns */ ! if (force_quote) { TupleDesc tupDesc = RelationGetDescr(rel); Form_pg_attribute *attr = tupDesc->attrs; List *cur; ! force_quote_atts = CopyGetAttnums(rel, force_quote); ! foreach(cur, force_quote_atts) { int attnum = lfirsti(cur); if (!intMember(attnum, attnumlist)) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), ! errmsg("FORCE QUOTE column \"%s\" not referenced by COPY", NameStr(attr[attnum - 1]->attname)))); } } /* ! * Check that FORCE NOT NULL references valid COPY columns */ ! if (force_notnull) { List *cur; TupleDesc tupDesc = RelationGetDescr(rel); Form_pg_attribute *attr = tupDesc->attrs; ! force_notnull_atts = CopyGetAttnums(rel, force_notnull); ! foreach(cur, force_notnull_atts) { int attnum = lfirsti(cur); if (!intMember(attnum, attnumlist)) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), ! errmsg("FORCE NOT NULL column \"%s\" not referenced by COPY", NameStr(attr[attnum - 1]->attname)))); } } *************** *** 1037,1043 **** } } CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode, ! quote, escape, literal_atts); } else { /* copy from database to file */ --- 1037,1043 ---- } } CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode, ! quote, escape, force_notnull_atts); } else { /* copy from database to file */ *************** *** 1100,1106 **** } } CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode, ! quote, escape, force_atts); } if (!pipe) --- 1100,1106 ---- } } CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode, ! quote, escape, force_quote_atts); } if (!pipe) *************** *** 1133,1139 **** static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, ! char *escape, List *force_atts) { HeapTuple tuple; TupleDesc tupDesc; --- 1133,1139 ---- static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, ! char *escape, List *force_quote_atts) { HeapTuple tuple; TupleDesc tupDesc; *************** *** 1180,1186 **** &isvarlena[attnum - 1]); fmgr_info(out_func_oid, &out_functions[attnum - 1]); ! if (intMember(attnum, force_atts)) force_quote[attnum - 1] = true; else force_quote[attnum - 1] = false; --- 1180,1186 ---- &isvarlena[attnum - 1]); fmgr_info(out_func_oid, &out_functions[attnum - 1]); ! if (intMember(attnum, force_quote_atts)) force_quote[attnum - 1] = true; else force_quote[attnum - 1] = false; *************** *** 1434,1440 **** static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, ! char *escape, List *literal_atts) { HeapTuple tuple; TupleDesc tupDesc; --- 1434,1440 ---- static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, char *delim, char *null_print, bool csv_mode, char *quote, ! char *escape, List *force_notnull_atts) { HeapTuple tuple; TupleDesc tupDesc; *************** *** 1447,1453 **** Oid *elements; Oid oid_in_element; ExprState **constraintexprs; ! bool *literal_nullstr; bool hasConstraints = false; int attnum; int i; --- 1447,1453 ---- Oid *elements; Oid oid_in_element; ExprState **constraintexprs; ! bool *force_notnull; bool hasConstraints = false; int attnum; int i; *************** *** 1509,1515 **** defmap = (int *) palloc((num_phys_attrs + 1) * sizeof(int)); defexprs = (ExprState **) palloc((num_phys_attrs + 1) * sizeof(ExprState *)); constraintexprs = (ExprState **) palloc0((num_phys_attrs + 1) * sizeof(ExprState *)); ! literal_nullstr = (bool *) palloc((num_phys_attrs + 1) * sizeof(bool)); for (attnum = 1; attnum <= num_phys_attrs; attnum++) { --- 1509,1515 ---- defmap = (int *) palloc((num_phys_attrs + 1) * sizeof(int)); defexprs = (ExprState **) palloc((num_phys_attrs + 1) * sizeof(ExprState *)); constraintexprs = (ExprState **) palloc0((num_phys_attrs + 1) * sizeof(ExprState *)); ! force_notnull = (bool *) palloc((num_phys_attrs + 1) * sizeof(bool)); for (attnum = 1; attnum <= num_phys_attrs; attnum++) { *************** *** 1526,1535 **** &in_func_oid, &elements[attnum - 1]); fmgr_info(in_func_oid, &in_functions[attnum - 1]); ! if (intMember(attnum, literal_atts)) ! literal_nullstr[attnum - 1] = true; else ! literal_nullstr[attnum - 1] = false; /* Get default info if needed */ if (!intMember(attnum, attnumlist)) --- 1526,1535 ---- &in_func_oid, &elements[attnum - 1]); fmgr_info(in_func_oid, &in_functions[attnum - 1]); ! if (intMember(attnum, force_notnull_atts)) ! force_notnull[attnum - 1] = true; else ! force_notnull[attnum - 1] = false; /* Get default info if needed */ if (!intMember(attnum, attnumlist)) *************** *** 1748,1754 **** string = CopyReadAttribute(delim, null_print, &result, &isnull); ! if (csv_mode && isnull && literal_nullstr[m]) { string = null_print; /* set to NULL string */ isnull = false; --- 1748,1754 ---- string = CopyReadAttribute(delim, null_print, &result, &isnull); ! if (csv_mode && isnull && force_notnull[m]) { string = null_print; /* set to NULL string */ isnull = false; *************** *** 1947,1953 **** pfree(defmap); pfree(defexprs); pfree(constraintexprs); ! pfree(literal_nullstr); ExecDropTupleTable(tupleTable, true); --- 1947,1953 ---- pfree(defmap); pfree(defexprs); pfree(constraintexprs); ! pfree(force_notnull); ExecDropTupleTable(tupleTable, true); *************** *** 2558,2571 **** */ static void CopyAttributeOutCSV(char *server_string, char *delim, char *quote, ! char *escape, bool force_quote) { char *string; char c; char delimc = delim[0]; char quotec = quote[0]; char escapec = escape[0]; - bool need_quote = force_quote; char *test_string; bool same_encoding; int mblen; --- 2558,2570 ---- */ static void CopyAttributeOutCSV(char *server_string, char *delim, char *quote, ! char *escape, bool use_quote) { char *string; char c; char delimc = delim[0]; char quotec = quote[0]; char escapec = escape[0]; char *test_string; bool same_encoding; int mblen; *************** *** 2583,2605 **** */ for(test_string = string; ! !need_quote && (c = *test_string) != '\0'; test_string += mblen) { if (c == delimc || c == quotec || c == '\n' || c == '\r') ! need_quote = true; if (!same_encoding) mblen = pg_encoding_mblen(client_encoding, test_string); else mblen = 1; } ! if (need_quote) CopySendChar(quotec); for (; (c = *string) != '\0'; string += mblen) { ! if (need_quote && (c == quotec || c == escapec)) CopySendChar(escapec); CopySendChar(c); --- 2582,2604 ---- */ for(test_string = string; ! !use_quote && (c = *test_string) != '\0'; test_string += mblen) { if (c == delimc || c == quotec || c == '\n' || c == '\r') ! use_quote = true; if (!same_encoding) mblen = pg_encoding_mblen(client_encoding, test_string); else mblen = 1; } ! if (use_quote) CopySendChar(quotec); for (; (c = *string) != '\0'; string += mblen) { ! if (use_quote && (c == quotec || c == escapec)) CopySendChar(escapec); CopySendChar(c); *************** *** 2615,2621 **** mblen = 1; } ! if (need_quote) CopySendChar(quotec); } --- 2614,2620 ---- mblen = 1; } ! if (use_quote) CopySendChar(quotec); } Index: src/backend/parser/gram.y =================================================================== RCS file: /cvsroot/pgsql-server/src/backend/parser/gram.y,v retrieving revision 2.451 diff -c -c -r2.451 gram.y *** src/backend/parser/gram.y 19 Apr 2004 17:22:30 -0000 2.451 --- src/backend/parser/gram.y 21 Apr 2004 00:18:00 -0000 *************** *** 370,376 **** KEY LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEFT LEVEL LIKE LIMIT ! LISTEN LITERAL LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE --- 370,376 ---- KEY LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEFT LEVEL LIKE LIMIT ! LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE *************** *** 1374,1386 **** { $$ = makeDefElem("escape", (Node *)makeString($3)); } ! | FORCE columnList { ! $$ = makeDefElem("force", (Node *)$2); } ! | LITERAL columnList { ! $$ = makeDefElem("literal", (Node *)$2); } ; --- 1374,1386 ---- { $$ = makeDefElem("escape", (Node *)makeString($3)); } ! | FORCE QUOTE columnList { ! $$ = makeDefElem("force_quote", (Node *)$3); } ! | FORCE NOT NULL_P columnList { ! $$ = makeDefElem("force_notnull", (Node *)$4); } ; *************** *** 7496,7502 **** | LAST_P | LEVEL | LISTEN - | LITERAL | LOAD | LOCAL | LOCATION --- 7496,7501 ---- Index: src/backend/parser/keywords.c =================================================================== RCS file: /cvsroot/pgsql-server/src/backend/parser/keywords.c,v retrieving revision 1.148 diff -c -c -r1.148 keywords.c *** src/backend/parser/keywords.c 19 Apr 2004 17:22:31 -0000 1.148 --- src/backend/parser/keywords.c 21 Apr 2004 00:18:01 -0000 *************** *** 187,193 **** {"like", LIKE}, {"limit", LIMIT}, {"listen", LISTEN}, - {"literal", LITERAL}, {"load", LOAD}, {"local", LOCAL}, {"localtime", LOCALTIME}, --- 187,192 ---- Index: src/bin/psql/copy.c =================================================================== RCS file: /cvsroot/pgsql-server/src/bin/psql/copy.c,v retrieving revision 1.45 diff -c -c -r1.45 copy.c *** src/bin/psql/copy.c 19 Apr 2004 17:42:58 -0000 1.45 --- src/bin/psql/copy.c 21 Apr 2004 00:18:03 -0000 *************** *** 71,78 **** char *null; char *quote; char *escape; ! char *force_list; ! char *literal_list; }; --- 71,78 ---- char *null; char *quote; char *escape; ! char *force_quote_list; ! char *force_notnull_list; }; *************** *** 88,95 **** free(ptr->null); free(ptr->quote); free(ptr->escape); ! free(ptr->force_list); ! free(ptr->literal_list); free(ptr); } --- 88,95 ---- free(ptr->null); free(ptr->quote); free(ptr->escape); ! free(ptr->force_quote_list); ! free(ptr->force_notnull_list); free(ptr); } *************** *** 344,388 **** } else if (strcasecmp(token, "force") == 0) { ! /* handle column list */ ! fetch_next = false; ! for (;;) { ! token = strtokx(NULL, whitespace, ",", "\"", ! 0, false, pset.encoding); ! if (!token || strchr(",", token[0])) ! goto error; ! if (!result->force_list) ! result->force_list = pg_strdup(token); ! else ! xstrcat(&result->force_list, token); ! token = strtokx(NULL, whitespace, ",", "\"", ! 0, false, pset.encoding); ! if (!token || token[0] != ',') ! break; ! xstrcat(&result->force_list, token); } ! } ! else if (strcasecmp(token, "literal") == 0) ! { ! /* handle column list */ ! fetch_next = false; ! for (;;) { token = strtokx(NULL, whitespace, ",", "\"", 0, false, pset.encoding); ! if (!token || strchr(",", token[0])) goto error; ! if (!result->literal_list) ! result->literal_list = pg_strdup(token); ! else ! xstrcat(&result->literal_list, token); ! token = strtokx(NULL, whitespace, ",", "\"", ! 0, false, pset.encoding); ! if (!token || token[0] != ',') ! break; ! xstrcat(&result->literal_list, token); } } else goto error; --- 344,399 ---- } else if (strcasecmp(token, "force") == 0) { ! token = strtokx(NULL, whitespace, ",", "\"", ! 0, false, pset.encoding); ! if (strcasecmp(token, "quote") == 0) { ! /* handle column list */ ! fetch_next = false; ! for (;;) ! { ! token = strtokx(NULL, whitespace, ",", "\"", ! 0, false, pset.encoding); ! if (!token || strchr(",", token[0])) ! goto error; ! if (!result->force_quote_list) ! result->force_quote_list = pg_strdup(token); ! else ! xstrcat(&result->force_quote_list, token); ! token = strtokx(NULL, whitespace, ",", "\"", ! 0, false, pset.encoding); ! if (!token || token[0] != ',') ! break; ! xstrcat(&result->force_quote_list, token); ! } } ! else if (strcasecmp(token, "not") == 0) { token = strtokx(NULL, whitespace, ",", "\"", 0, false, pset.encoding); ! if (strcasecmp(token, "null") != 0) goto error; ! /* handle column list */ ! fetch_next = false; ! for (;;) ! { ! token = strtokx(NULL, whitespace, ",", "\"", ! 0, false, pset.encoding); ! if (!token || strchr(",", token[0])) ! goto error; ! if (!result->force_notnull_list) ! result->force_notnull_list = pg_strdup(token); ! else ! xstrcat(&result->force_notnull_list, token); ! token = strtokx(NULL, whitespace, ",", "\"", ! 0, false, pset.encoding); ! if (!token || token[0] != ',') ! break; ! xstrcat(&result->force_notnull_list, token); ! } } + else + goto error; } else goto error; *************** *** 493,506 **** appendPQExpBuffer(&query, " ESCAPE AS '%s'", options->escape); } ! if (options->force_list) { ! appendPQExpBuffer(&query, " FORCE %s", options->force_list); } ! if (options->literal_list) { ! appendPQExpBuffer(&query, " LITERAL %s", options->literal_list); } if (options->from) --- 504,517 ---- appendPQExpBuffer(&query, " ESCAPE AS '%s'", options->escape); } ! if (options->force_quote_list) { ! appendPQExpBuffer(&query, " FORCE QUOTE %s", options->force_quote_list); } ! if (options->force_notnull_list) { ! appendPQExpBuffer(&query, " FORCE NOT NULL %s", options->force_notnull_list); } if (options->from)