diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 10078f24532..7ca890eac6f 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -27,6 +27,7 @@ #include "fe_utils/cancel.h" #include "fe_utils/mbprint.h" #include "fe_utils/string_utils.h" +#include "mainloop.h" #include "portability/instr_time.h" #include "settings.h" @@ -34,6 +35,7 @@ static bool DescribeQuery(const char *query, double *elapsed_msec); static int ExecQueryAndProcessResults(const char *query, double *elapsed_msec, bool *svpt_gone_p, + int num_copy_from_stdin, bool is_watch, int min_rows, const printQueryOpt *opt, @@ -723,7 +725,11 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout, SetCancelConn(pset.db); - res = ExecQueryAndProcessResults(query, &elapsed_msec, NULL, true, min_rows, opt, printQueryFout); + res = ExecQueryAndProcessResults(query, + &elapsed_msec, NULL, + -1, + true, min_rows, + opt, printQueryFout); ResetCancelConn(); @@ -895,7 +901,7 @@ ExecQueryTuples(const PGresult *result) fflush(stdout); } - if (!SendQuery(query)) + if (!SendQuery(query, -1)) { /* Error - abandon execution if ON_ERROR_STOP */ success = false; @@ -926,8 +932,7 @@ loop_exit: * once and report any error. Return whether all was ok. * * For COPY OUT, direct the output to copystream, or discard if that's NULL. - * For COPY IN, use pset.copyStream as data source if it's set, - * otherwise cur_cmd_source. + * For COPY IN, read from copystream (which mustn't be NULL). * * Update *resultp if further processing is necessary; set to NULL otherwise. * Return a result when queryFout can safely output a result status: on COPY @@ -968,8 +973,7 @@ HandleCopyResult(PGresult **resultp, FILE *copystream) else { /* COPY IN */ - /* Ignore the copystream argument passed to the function */ - copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source; + Assert(copystream); success = handleCopyIn(pset.db, copystream, PQbinaryTuples(*resultp), @@ -1114,10 +1118,14 @@ PrintQueryResult(PGresult *result, bool last, * To send "back door" queries (generated by slash commands, etc.) in a * controlled way, use PSQLexec(). * + * In the most common case, the caller can determine whether the query + * includes any COPY FROM STDIN command(s); if so, pass the count of them. + * Otherwise pass num_copy_from_stdin == -1 and we'll compute it locally. + * * Returns true if the query executed successfully, false otherwise. */ bool -SendQuery(const char *query) +SendQuery(const char *query, int num_copy_from_stdin) { bool timing = pset.timing; PGTransactionStatusType transaction_status; @@ -1211,7 +1219,11 @@ SendQuery(const char *query) else { /* Default fetch-and-print mode */ - OK = (ExecQueryAndProcessResults(query, &elapsed_msec, &svpt_gone, false, 0, NULL, NULL) > 0); + OK = (ExecQueryAndProcessResults(query, + &elapsed_msec, &svpt_gone, + num_copy_from_stdin, + false, 0, + NULL, NULL) > 0); } if (!OK && pset.echo == PSQL_ECHO_ERRORS) @@ -1570,6 +1582,7 @@ discardAbortedPipelineResults(void) static int ExecQueryAndProcessResults(const char *query, double *elapsed_msec, bool *svpt_gone_p, + int num_copy_from_stdin, bool is_watch, int min_rows, const printQueryOpt *opt, FILE *printQueryFout) { @@ -1774,6 +1787,29 @@ ExecQueryAndProcessResults(const char *query, return 0; } + /* + * If the caller didn't count the number of COPY FROM STDIN command(s) in + * the query string, we must do so now. + */ + if (num_copy_from_stdin < 0) + { + PsqlScanState scan_state; + PQExpBuffer query_buf; + promptStatus_t prompt_tmp; + + scan_state = psql_scan_create(&psqlscan_callbacks); + psql_scan_setup(scan_state, query, strlen(query), + pset.encoding, standard_strings()); + query_buf = createPQExpBuffer(); + + (void) psql_scan(scan_state, query_buf, &prompt_tmp); + + num_copy_from_stdin = psql_scan_count_copy_from_stdin(scan_state); + + destroyPQExpBuffer(query_buf); + psql_scan_destroy(scan_state); + } + /* first result */ result = PQgetResult(pset.db); if (min_rows > 0 && PQntuples(result) < min_rows) @@ -1909,14 +1945,15 @@ ExecQueryAndProcessResults(const char *query, exit(EXIT_BADCONN); } - /* - * For COPY OUT, direct the output to the default place (probably - * a pager pipe) for \watch, or to pset.copyStream for \copy, - * otherwise to pset.gfname if that's set, otherwise to - * pset.queryFout. - */ + /* Select data source or sink. */ if (result_status == PGRES_COPY_OUT) { + /* + * For COPY OUT, direct the output to the default place + * (probably a pager pipe) for \watch, or to pset.copyStream + * for \copy, otherwise to pset.gfname if that's set, + * otherwise to pset.queryFout. + */ if (is_watch) { /* invoked by \watch */ @@ -1940,6 +1977,32 @@ ExecQueryAndProcessResults(const char *query, copy_stream = pset.queryFout; } } + else + { + if (num_copy_from_stdin <= 0) + { + /* + * We didn't send a COPY FROM STDIN command. The server + * is broken or possibly malicious. Report and quit. (We + * could instead send an empty COPY response, but it's not + * clear that that's a better behavior; it would make it + * harder to diagnose any such problem.) + */ + pg_log_info("unexpected COPY_IN result, aborting connection"); + exit(EXIT_BADCONN); + } + /* Keep track of the number of remaining copy operations */ + num_copy_from_stdin--; + + /* + * For COPY IN, read from pset.copyStream if \copy set that, + * otherwise from the current command source. + */ + if (pset.copyStream) + copy_stream = pset.copyStream; + else + copy_stream = pset.cur_cmd_source; + } /* * Even if the output stream could not be opened, we call @@ -2229,6 +2292,24 @@ ExecQueryAndProcessResults(const char *query, if (!CheckConnection()) return -1; + /* + * If we were expecting COPY ... FROM STDIN, and we didn't get + * PGRES_COPY_IN (presumably because the COPY failed server-side), and the + * data is supposed to come from the current command source, we must eat + * up the data in order to stay in sync with the command file's contents. + * Repeat for the number of unfulfilled COPY commands. + */ + while (num_copy_from_stdin > 0) + { + if (pset.copyStream == NULL || + pset.copyStream == pset.cur_cmd_source) + (void) handleCopyIn(NULL, /* no connection */ + pset.cur_cmd_source, + false, /* XXX assume not binary */ + NULL); /* no result */ + num_copy_from_stdin--; + } + if (cancel_pressed || return_early) return 0; diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h index b8a5a2ab6a1..d4b99b42331 100644 --- a/src/bin/psql/common.h +++ b/src/bin/psql/common.h @@ -34,7 +34,7 @@ extern void SetShellResultVariables(int wait_result); extern PGresult *PSQLexec(const char *query); extern int PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout, int min_rows); -extern bool SendQuery(const char *query); +extern bool SendQuery(const char *query, int num_copy_from_stdin); extern bool is_superuser(void); extern bool standard_strings(void); diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c index 6a8a9792e7d..1b8d6c7687f 100644 --- a/src/bin/psql/copy.c +++ b/src/bin/psql/copy.c @@ -367,7 +367,7 @@ do_copy(const char *args) /* run it like a user command, but with copystream as data source/sink */ pset.copyStream = copystream; - success = SendQuery(query.data); + success = SendQuery(query.data, options->from ? 1 : 0); pset.copyStream = NULL; termPQExpBuffer(&query); @@ -495,11 +495,13 @@ handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res) * sends data to complete a COPY ... FROM STDIN command * * conn should be a database connection that you just issued COPY FROM on - * and got back a PGRES_COPY_IN result. + * and got back a PGRES_COPY_IN result. Alternatively, if conn is NULL, + * we read and discard the appropriate amount of data from copystream. * copystream is the file stream to read the data from. * isbinary can be set from PQbinaryTuples(). - * The final status for the COPY is returned into *res (but note - * we already reported the error, if it's not a success result). + * The final status for the COPY is returned into *res; but note + * we already reported the error, if it's not a success result. + * Also, if conn is NULL then *res is not touched. * * result is true if successful, false if not. */ @@ -514,6 +516,12 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res) char buf[COPYBUFSIZ]; bool showprompt; + /* We want to prompt if interactive input ... */ + showprompt = isatty(fileno(copystream)); + /* ... but if we're just discarding data, don't bother the user at all */ + if (showprompt && !conn) + return true; + /* * Establish longjmp destination for exiting from wait-for-input. (This is * only effective while sigint_interrupt_enabled is TRUE.) @@ -523,24 +531,19 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res) /* got here with longjmp */ /* Terminate data transfer */ - PQputCopyEnd(conn, - (PQprotocolVersion(conn) < 3) ? NULL : - _("canceled by user")); + if (conn) + PQputCopyEnd(conn, + (PQprotocolVersion(conn) < 3) ? NULL : + _("canceled by user")); OK = false; goto copyin_cleanup; } - /* Prompt if interactive input */ - if (isatty(fileno(copystream))) - { - showprompt = true; - if (!pset.quiet) - puts(_("Enter data to be copied followed by a newline.\n" - "End with a backslash and a period on a line by itself, or an EOF signal.")); - } - else - showprompt = false; + /* Issue initial prompt if interactive input */ + if (showprompt && !pset.quiet) + puts(_("Enter data to be copied followed by a newline.\n" + "End with a backslash and a period on a line by itself, or an EOF signal.")); OK = true; @@ -569,7 +572,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res) if (buflen <= 0) break; - if (PQputCopyData(conn, buf, buflen) <= 0) + if (conn && PQputCopyData(conn, buf, buflen) <= 0) { OK = false; break; @@ -667,7 +670,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res) */ if (buflen >= COPYBUFSIZ - 5 || (copydone && buflen > 0)) { - if (PQputCopyData(conn, buf, buflen) <= 0) + if (conn && PQputCopyData(conn, buf, buflen) <= 0) { OK = false; break; @@ -688,7 +691,8 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res) * keep the version checks just in case you're using a pre-v14 libpq.so at * runtime) */ - if (PQputCopyEnd(conn, + if (conn && + PQputCopyEnd(conn, (OK || PQprotocolVersion(conn) < 3) ? NULL : _("aborted because of read failure")) <= 0) OK = false; @@ -705,6 +709,10 @@ copyin_cleanup: */ clearerr(copystream); + /* Done if we don't have a connection to clean up */ + if (!conn) + return OK; + /* * Check command status and return to normal libpq state. * diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c index e9abda07161..fa9e0904cab 100644 --- a/src/bin/psql/mainloop.c +++ b/src/bin/psql/mainloop.c @@ -436,7 +436,9 @@ MainLoop(FILE *source) /* execute query unless we're in an inactive \if branch */ if (conditional_active(cond_stack)) { - success = SendQuery(query_buf->data); + /* count_copy_from_stdin should be reliable here */ + success = SendQuery(query_buf->data, + psql_scan_count_copy_from_stdin(scan_state)); slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; pset.stmt_lineno = 1; @@ -448,9 +450,10 @@ MainLoop(FILE *source) query_buf = swap_buf; } resetPQExpBuffer(query_buf); + /* reset parsing state, too */ + psql_scan_reset(scan_state); added_nl_pos = -1; - /* we need not do psql_scan_reset() here */ } else { @@ -512,7 +515,7 @@ MainLoop(FILE *source) /* should not see this in inactive branch */ Assert(conditional_active(cond_stack)); - success = SendQuery(query_buf->data); + success = SendQuery(query_buf->data, -1); /* transfer query to previous_buf by pointer-swapping */ { @@ -522,8 +525,7 @@ MainLoop(FILE *source) query_buf = swap_buf; } resetPQExpBuffer(query_buf); - - /* flush any paren nesting info after forced send */ + /* reset parsing state, too */ psql_scan_reset(scan_state); } else if (slashCmdStatus == PSQL_CMD_NEWEDIT) @@ -610,7 +612,8 @@ MainLoop(FILE *source) /* execute query unless we're in an inactive \if branch */ if (conditional_active(cond_stack)) { - success = SendQuery(query_buf->data); + success = SendQuery(query_buf->data, + psql_scan_count_copy_from_stdin(scan_state)); } else { diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l index 837158ae0d3..e3ec1775e62 100644 --- a/src/bin/psql/psqlscanslash.l +++ b/src/bin/psql/psqlscanslash.l @@ -721,6 +721,7 @@ psql_scan_get_lex_state(PsqlScanState state) lex_state->paren_depth = state->paren_depth; lex_state->begin_depth = state->begin_depth; + lex_state->copy_stdin_count = state->copy_stdin_count; lex_state->init_idents_count = state->init_idents_count; memcpy(lex_state->init_idents, state->init_idents, sizeof(lex_state->init_idents)); @@ -739,6 +740,7 @@ psql_scan_set_lex_state(PsqlScanState state, { state->paren_depth = lex_state->paren_depth; state->begin_depth = lex_state->begin_depth; + state->copy_stdin_count = lex_state->copy_stdin_count; state->init_idents_count = lex_state->init_idents_count; memcpy(state->init_idents, lex_state->init_idents, sizeof(state->init_idents)); diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 7665f0a3124..454ce3c939e 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -390,7 +390,7 @@ main(int argc, char *argv[]) if (pset.echo == PSQL_ECHO_ALL) puts(cell->val); - successResult = SendQuery(cell->val) + successResult = SendQuery(cell->val, -1) ? EXIT_SUCCESS : EXIT_FAILURE; } else if (cell->action == ACT_SINGLE_SLASH) diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l index bbfafbc5223..97055f5ad3c 100644 --- a/src/fe_utils/psqlscan.l +++ b/src/fe_utils/psqlscan.l @@ -61,6 +61,7 @@ typedef int YYSTYPE; #define ECHO psqlscan_emit(cur_state, yytext, yyleng) +static bool psqlscan_is_copy_from_stdin(PsqlScanState state); static void psqlscan_track_identifier(PsqlScanState state, const char *identifier); @@ -683,6 +684,9 @@ other . if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0) { + /* Remember if this subcommand was COPY FROM STDIN */ + if (psqlscan_is_copy_from_stdin(cur_state)) + cur_state->copy_stdin_count++; /* Terminate lexing temporarily */ cur_state->start_state = YY_START; cur_state->init_idents_count = 0; @@ -698,11 +702,16 @@ other . "\\"[;:] { /* Force a semi-colon or colon into the query buffer */ psqlscan_emit(cur_state, yytext + 1, 1); - /* Reset BEGIN/END tracking if semi at outer level */ + /* Reset BEGIN/END/COPY tracking if semi at outer level */ if (yytext[1] == ';' && cur_state->paren_depth == 0 && cur_state->begin_depth == 0) + { + /* Remember if this subcommand was COPY FROM STDIN */ + if (psqlscan_is_copy_from_stdin(cur_state)) + cur_state->copy_stdin_count++; cur_state->init_idents_count = 0; + } } "\\" { @@ -975,7 +984,11 @@ psqlscan_record_initial_keyword(const char *identifier, /* * What we need to recognize is CREATE [OR REPLACE] FUNCTION/PROCEDURE * and CREATE SCHEMA. Checking for SCHEMA is useless but not harmful - * in the CREATE SCHEMA sub-statement case. + * in the CREATE SCHEMA sub-statement case. We record these keywords + * in lower case. We also need to recognize COPY ... FROM STDIN. + * (Note: the backend grammar doesn't distinguish STDIN from STDOUT, + * so we should not do so here either.) We record these keywords in + * upper case, to avoid conflicting with the first set. */ if (pg_strcasecmp(identifier, "create") == 0 || pg_strcasecmp(identifier, "function") == 0 || @@ -984,6 +997,11 @@ psqlscan_record_initial_keyword(const char *identifier, pg_strcasecmp(identifier, "replace") == 0 || pg_strcasecmp(identifier, "schema") == 0) idents[*idents_count] = pg_tolower((unsigned char) identifier[0]); + else if (pg_strcasecmp(identifier, "copy") == 0 || + pg_strcasecmp(identifier, "from") == 0 || + pg_strcasecmp(identifier, "stdin") == 0 || + pg_strcasecmp(identifier, "stdout") == 0) + idents[*idents_count] = pg_toupper((unsigned char) identifier[0]); /* For other keywords or identifiers, leave '\0' in the array entry */ (*idents_count)++; } @@ -1002,7 +1020,40 @@ psqlscan_is_create_routine(const char *idents) } /* - * Track whether we are inside a BEGIN .. END block in a function definition, + * Does the current input match COPY ... FROM STDIN? + */ +static bool +psqlscan_is_copy_from_stdin(PsqlScanState state) +{ + const char *idents = state->init_idents; + + /* + * The first word must be COPY, but after that there could be up to four + * identifiers (BINARY database.schema.table) before FROM. Since some of + * the words we track are not reserved words, don't assume the intervening + * array entries are '\0'. Life is simplified here by the fact that + * psqlscan_track_identifier ignores everything within parens: we won't + * see column lists nor the query in COPY (query). + */ + if (idents[0] != 'C') + return false; + for (int i = 1; i < lengthof(state->init_idents) - 1; i++) + { + /* Scan to find FROM; if not seen within range, it's not valid COPY */ + if (idents[i] != 'F') + continue; + /* It's COPY FROM STDIN only if the next word is STDIN */ + return (idents[i + 1] == 'S'); + } + return false; +} + +/* + * This function is called each time the lexer recognizes an unquoted + * identifier (which could also be a keyword, and indeed keywords are the + * only case we really care about here). It presently has two tasks: + * + * 1. Track whether we are inside BEGIN .. END in a function definition, * so that semicolons contained therein don't terminate the whole statement. * Short of writing a full parser here, the following heuristic should work. * @@ -1011,6 +1062,9 @@ psqlscan_is_create_routine(const char *idents) * after recognizing an embedded CREATE [OR REPLACE] {FUNCTION|PROCEDURE} * subcommand. Once one of these conditions holds, count BEGIN and END * pairs. We also have to account for CASE ... END. + * + * 2. Record enough information for psqlscan_is_copy_from_stdin() to recognize + * COPY FROM STDIN commands. */ static void psqlscan_track_identifier(PsqlScanState state, const char *identifier) @@ -1357,10 +1411,9 @@ psql_scan_finish(PsqlScanState state) /* * Reset lexer scanning state to start conditions. This is appropriate * for executing \r psql commands (or any other time that we discard the - * prior contents of query_buf). It is not, however, necessary to do this - * when we execute and clear the buffer after getting a PSCAN_SEMICOLON or - * PSCAN_EOL scan result, because the scan state must be INITIAL when those - * conditions are returned. + * prior contents of query_buf). Do not call this between psql_scan() + * calls that are scanning successive chunks of a single query string; + * do call it when preparing to process a new query string. * * Note that this is unrelated to flushing unread input; that task is * done by psql_scan_finish(). @@ -1375,6 +1428,7 @@ psql_scan_reset(PsqlScanState state) free(state->dolqstart); state->dolqstart = NULL; state->begin_depth = 0; + state->copy_stdin_count = 0; state->init_idents_count = 0; } @@ -1399,6 +1453,26 @@ psql_scan_reselect_sql_lexer(PsqlScanState state) state->start_state = INITIAL; } +/* + * Return the number of COPY ... FROM STDIN commands in the input string. + * + * This should be called only after we've finished parsing a complete + * string and are ready to send it to the backend. + */ +int +psql_scan_count_copy_from_stdin(PsqlScanState state) +{ + if (state->init_idents_count > 0) + { + /* Count any COPY FROM STDIN following the last semicolon */ + if (psqlscan_is_copy_from_stdin(state)) + state->copy_stdin_count++; + /* ... but do so only once */ + state->init_idents_count = 0; + } + return state->copy_stdin_count; +} + /* * Return true if lexer is currently in an "inside quotes" state. * diff --git a/src/include/fe_utils/psqlscan.h b/src/include/fe_utils/psqlscan.h index f3e972e8892..61f34eaa140 100644 --- a/src/include/fe_utils/psqlscan.h +++ b/src/include/fe_utils/psqlscan.h @@ -88,6 +88,8 @@ extern void psql_scan_reset(PsqlScanState state); extern void psql_scan_reselect_sql_lexer(PsqlScanState state); +extern int psql_scan_count_copy_from_stdin(PsqlScanState state); + extern bool psql_scan_in_quote(PsqlScanState state); extern void psql_scan_get_location(PsqlScanState state, diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h index ada1089f70d..b1661afd4af 100644 --- a/src/include/fe_utils/psqlscan_int.h +++ b/src/include/fe_utils/psqlscan_int.h @@ -114,12 +114,14 @@ typedef struct PsqlScanStateData char *dolqstart; /* current $foo$ quote start string */ /* - * State to track boundaries of BEGIN ... END blocks in function - * definitions, so that semicolons do not send query too early. + * State used to track boundaries of BEGIN ... END blocks in function + * definitions, so that semicolons do not send query too early. We also + * use this state to detect and count COPY FROM STDIN commands. */ int begin_depth; /* depth of begin/end pairs */ + int copy_stdin_count; /* number of COPY FROM STDIN commands */ int init_idents_count; /* # identifiers since start of statement */ - char init_idents[4]; /* records the first few identifiers */ + char init_idents[8]; /* records the first few identifiers */ int sub_idents_count; /* # identifiers since start of a CREATE * SCHEMA element */ char sub_idents[4]; /* records the first few of those identifiers */ @@ -145,8 +147,9 @@ typedef struct PsqlScanStateSave { int paren_depth; /* depth of nesting in parentheses */ int begin_depth; /* depth of begin/end pairs */ + int copy_stdin_count; /* number of COPY FROM STDIN commands */ int init_idents_count; /* # identifiers since start of statement */ - char init_idents[4]; /* records the first few identifiers */ + char init_idents[8]; /* records the first few identifiers */ int sub_idents_count; /* # identifiers since start of a CREATE * SCHEMA element */ char sub_idents[4]; /* records the first few of those identifiers */ diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out index ace38225623..0af0b646921 100644 --- a/src/test/regress/expected/copy.out +++ b/src/test/regress/expected/copy.out @@ -518,13 +518,9 @@ copy oversized_column_default from stdin; -- error if the column is excluded copy oversized_column_default (col2) from stdin; ERROR: value too long for type character varying(5) -\. -invalid command \. -- error if the DEFAULT option is given copy oversized_column_default from stdin (default ''); ERROR: value too long for type character varying(5) -\. -invalid command \. drop table oversized_column_default; -- -- Create partitioned table that does not allow bulk insertions, to test bugs diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 7e67f795f25..e8605dd041f 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5949,6 +5949,11 @@ SELECT line FROM reload_output ORDER BY lineno; (2 rows) DROP TABLE reload_output; +-- \copy must skip in-line data, even if the issued COPY command fails. +\copy no_such_table from stdin +ERROR: relation "no_such_table" does not exist +\echo this should get output +this should get output -- -- AUTOCOMMIT and combined queries -- diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index f5f13bbd3e7..9f6c2a4bb08 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1157,7 +1157,9 @@ copy attest from stdin; \. select * from attest; copy attest(a) from stdin; +\. copy attest("........pg.dropped.1........") from stdin; +\. copy attest(b,c) from stdin; 31 32 \. diff --git a/src/test/regress/sql/copy.sql b/src/test/regress/sql/copy.sql index 507b822946f..14da3c5ec37 100644 --- a/src/test/regress/sql/copy.sql +++ b/src/test/regress/sql/copy.sql @@ -115,6 +115,7 @@ copy copytest to stdout (format json, force_null *); copy copytest to stdout (format json, on_error ignore); copy copytest to stdout (format json, reject_limit 1); copy copytest from stdin(format json); +\. -- all of the above should yield error -- column list with json format @@ -400,6 +401,7 @@ alter table header_copytest drop column c; alter table header_copytest add column c text; copy header_copytest to stdout with (header match); copy header_copytest from stdin with (header wrong_choice); +\. -- works copy header_copytest from stdin with (header match); a b c diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql index f853499021d..696ceeeaf82 100644 --- a/src/test/regress/sql/copy2.sql +++ b/src/test/regress/sql/copy2.sql @@ -52,57 +52,94 @@ COPY x (a, b, c, d, e) from stdin; -- non-existent column in column list: should fail COPY x (xyz) from stdin; +\. -- redundant options COPY x from stdin (format CSV, FORMAT CSV); +\. COPY x from stdin (freeze off, freeze on); +\. COPY x from stdin (delimiter ',', delimiter ','); +\. COPY x from stdin (null ' ', null ' '); +\. COPY x from stdin (header off, header on); +\. COPY x from stdin (quote ':', quote ':'); +\. COPY x from stdin (escape ':', escape ':'); +\. COPY x from stdin (force_quote (a), force_quote *); +\. COPY x from stdin (force_not_null (a), force_not_null (b)); +\. COPY x from stdin (force_null (a), force_null (b)); +\. COPY x from stdin (convert_selectively (a), convert_selectively (b)); +\. COPY x from stdin (encoding 'sql_ascii', encoding 'sql_ascii'); +\. COPY x from stdin (on_error ignore, on_error ignore); +\. COPY x from stdin (on_error set_null, on_error set_null); +\. COPY x from stdin (log_verbosity default, log_verbosity verbose); +\. -- incorrect options COPY x from stdin (format BINARY, delimiter ','); +\. COPY x from stdin (format BINARY, null 'x'); +\. COPY x from stdin (format BINARY, on_error ignore); +\. COPY x from stdin (format BINARY, on_error set_null); +\. COPY x from stdin (on_error set_null, reject_limit 2); +\. COPY x from stdin (on_error unsupported); +\. COPY x from stdin (format TEXT, force_quote(a)); +\. COPY x from stdin (format TEXT, force_quote *); +\. COPY x from stdin (format CSV, force_quote(a)); +\. COPY x from stdin (format CSV, force_quote *); +\. COPY x from stdin (format TEXT, force_not_null(a)); +\. COPY x from stdin (format TEXT, force_not_null *); +\. COPY x to stdout (format CSV, force_not_null(a)); COPY x to stdout (format CSV, force_not_null *); COPY x from stdin (format TEXT, force_null(a)); +\. COPY x from stdin (format TEXT, force_null *); +\. COPY x to stdout (format CSV, force_null(a)); COPY x to stdout (format CSV, force_null *); COPY x to stdout (format BINARY, on_error unsupported); COPY x to stdout (on_error set_null); COPY x from stdin (log_verbosity unsupported); +\. COPY x from stdin with (reject_limit 1); +\. COPY x from stdin with (on_error ignore, reject_limit 0); +\. COPY x from stdin with (header -1); +\. COPY x from stdin with (header 2.5); +\. COPY x to stdout with (header 2); COPY x to stdout with (header '-1'); COPY x from stdin with (header '2.5'); +\. COPY x to stdout with (header '2'); -- too many columns in column list: should fail COPY x (a, b, c, d, e, d, c) from stdin; +\. -- missing data: should fail COPY x from stdin; @@ -159,16 +196,22 @@ COPY x from stdin WHERE a > 60003; \. COPY x from stdin WHERE f > 60003; +\. COPY x from stdin WHERE a = max(x.b); +\. COPY x from stdin WHERE a IN (SELECT 1 FROM x); +\. COPY x from stdin WHERE a IN (generate_series(1,5)); +\. COPY x from stdin WHERE a = row_number() over(b); +\. COPY x from stdin WHERE tableoid = 'x'::regclass; +\. -- check results of copy in @@ -365,10 +408,12 @@ ROLLBACK; -- should fail with "not referenced by COPY" error BEGIN; COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b)); +\. ROLLBACK; -- should fail with "not referenced by COPY" error BEGIN; COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b)); +\. ROLLBACK; -- should succeed with no effect ("b" remains an empty string, "c" remains NULL) BEGIN; @@ -394,10 +439,12 @@ SELECT b, c FROM forcetest WHERE a = 6; -- should fail with "conflicting or redundant options" error BEGIN; COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL *, FORCE_NOT_NULL(b)); +\. ROLLBACK; -- should fail with "conflicting or redundant options" error BEGIN; COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NULL *, FORCE_NULL(b)); +\. ROLLBACK; \pset null '' @@ -693,19 +740,25 @@ truncate copy_default; -- DEFAULT cannot be used in binary mode copy copy_default from stdin with (format binary, default '\D'); +\. -- DEFAULT cannot be new line nor carriage return copy copy_default from stdin with (default E'\n'); +\. copy copy_default from stdin with (default E'\r'); +\. -- DELIMITER cannot appear in DEFAULT spec copy copy_default from stdin with (delimiter ';', default 'test;test'); +\. -- CSV quote cannot appear in DEFAULT spec copy copy_default from stdin with (format csv, quote '"', default 'test"test'); +\. -- NULL and DEFAULT spec must be different copy copy_default from stdin with (default '\N'); +\. -- cannot use DEFAULT marker in column that has no DEFAULT value copy copy_default from stdin with (default '\D'); diff --git a/src/test/regress/sql/copyselect.sql b/src/test/regress/sql/copyselect.sql index e32a4f8e38e..273a5f90a56 100644 --- a/src/test/regress/sql/copyselect.sql +++ b/src/test/regress/sql/copyselect.sql @@ -42,6 +42,7 @@ copy (select t into temp test3 from test1 where id=3) to stdout; -- This should fail -- copy (select * from test1) from stdin; +\. -- -- This should fail -- diff --git a/src/test/regress/sql/generated_stored.sql b/src/test/regress/sql/generated_stored.sql index 9eecd13dd9e..235bc28db81 100644 --- a/src/test/regress/sql/generated_stored.sql +++ b/src/test/regress/sql/generated_stored.sql @@ -216,10 +216,13 @@ COPY gtest1 FROM stdin; \. COPY gtest1 (a, b) FROM stdin; +\. COPY gtest1 FROM stdin WHERE b <> 10; +\. COPY gtest1 FROM stdin WHERE gtest1 IS NULL; +\. SELECT * FROM gtest1 ORDER BY a; @@ -236,6 +239,7 @@ COPY gtest3 FROM stdin; \. COPY gtest3 (a, b) FROM stdin; +\. SELECT * FROM gtest3 ORDER BY a; diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index ed9d50fe784..e4ea63bb3a1 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -216,10 +216,13 @@ COPY gtest1 FROM stdin; \. COPY gtest1 (a, b) FROM stdin; +\. COPY gtest1 FROM stdin WHERE b <> 10; +\. COPY gtest1 FROM stdin WHERE gtest1 IS NULL; +\. SELECT * FROM gtest1 ORDER BY a; @@ -236,6 +239,7 @@ COPY gtest3 FROM stdin; \. COPY gtest3 (a, b) FROM stdin; +\. SELECT * FROM gtest3 ORDER BY a; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 8a5f9ff98ad..ac91511f451 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -253,6 +253,7 @@ BEGIN; LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- fail COMMIT; COPY atest2 FROM stdin; -- fail +\. GRANT ALL ON atest1 TO PUBLIC; -- fail -- checks in subquery, both ok @@ -296,6 +297,7 @@ BEGIN; LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- ok COMMIT; COPY atest2 FROM stdin; -- fail +\. -- checks in subquery, both fail SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) ); @@ -543,6 +545,7 @@ SELECT one, two FROM atest5 NATURAL JOIN atest6; -- ok now -- test column-level privileges for INSERT and UPDATE INSERT INTO atest5 (two) VALUES (3); -- ok COPY atest5 FROM stdin; -- fail +\. COPY atest5 (two) FROM stdin; -- ok 1 \. diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 42eca3b8a6b..cd685b43e0f 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -1583,6 +1583,15 @@ SELECT line FROM reload_output ORDER BY lineno; DROP TABLE reload_output; +-- \copy must skip in-line data, even if the issued COPY command fails. +\copy no_such_table from stdin +foo +\echo this should not get output +bar +\echo this should not get output +\. +\echo this should get output + -- -- AUTOCOMMIT and combined queries -- diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql index 0bd24d1028b..ed70b1b229e 100644 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -1882,8 +1882,10 @@ COPY copy_t FROM STDIN; --ok SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TO OFF; COPY copy_t FROM STDIN; --fail - would be affected by RLS. +\. SET row_security TO ON; COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS. +\. -- Check COPY FROM as user with permissions and BYPASSRLS SET SESSION AUTHORIZATION regress_rls_exempt_user; @@ -1899,8 +1901,10 @@ COPY copy_t FROM STDIN; --ok SET SESSION AUTHORIZATION regress_rls_carol; SET row_security TO OFF; COPY copy_t FROM STDIN; --fail - permission denied. +\. SET row_security TO ON; COPY copy_t FROM STDIN; --fail - permission denied. +\. RESET SESSION AUTHORIZATION; DROP TABLE copy_t;