Anna Akenteva wrote 2020-08-11 13:37:
About the patch:1) There is some code duplication for the exec_command_[sm|si|st|sr] functions. Plus, it seems weird to separate sm (show matview) from sv (show view). Perhaps it would be more convenient to combine some of the code? Maybe by editing the already-existing exec_command_sf_sv() function.
I've combined most of the functions into one, as the code was mostly duplicated. Had to change the argument from is_func to object type, because the number of values has increased. I've attached a patch with those changes.
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 560eacc7f0c..3faac9e25a6 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -49,7 +49,11 @@ typedef enum EditableObjectType { EditableFunction, - EditableView + EditableView, + EditableMatview, + EditableIndex, + EditableTrigger, + EditableTable } EditableObjectType; /* local function declarations */ @@ -117,8 +121,10 @@ static backslashResult exec_command_s(PsqlScanState scan_state, bool active_bran static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch, const char *cmd); -static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_branch, - const char *cmd, bool is_func); +static backslashResult exec_command_sf_sv_sm_si_sr(PsqlScanState scan_state, bool active_branch, + const char *cmd, EditableObjectType type); +static backslashResult exec_command_st(PsqlScanState scan_state, bool active_branch, + const char *cmd); static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch); @@ -154,6 +160,8 @@ static bool do_shell(const char *command); static bool do_watch(PQExpBuffer query_buf, double sleep); static bool lookup_object_oid(EditableObjectType obj_type, const char *desc, Oid *obj_oid); +static bool lookup_trigger_oid(const char *table_name, const char *trigger_name, + Oid *obj_oid); static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid, PQExpBuffer buf); static int strip_lineno_from_objdesc(char *obj); @@ -381,9 +389,17 @@ exec_command(const char *cmd, else if (strcmp(cmd, "setenv") == 0) status = exec_command_setenv(scan_state, active_branch, cmd); else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0) - status = exec_command_sf_sv(scan_state, active_branch, cmd, true); + status = exec_command_sf_sv_sm_si_sr(scan_state, active_branch, cmd, EditableFunction); else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0) - status = exec_command_sf_sv(scan_state, active_branch, cmd, false); + status = exec_command_sf_sv_sm_si_sr(scan_state, active_branch, cmd, EditableView); + else if (strcmp(cmd, "sm") == 0 || strcmp(cmd, "sm+") == 0) + status = exec_command_sf_sv_sm_si_sr(scan_state, active_branch, cmd, EditableMatview); + else if (strcmp(cmd, "si") == 0 || strcmp(cmd, "si+") == 0) + status = exec_command_sf_sv_sm_si_sr(scan_state, active_branch, cmd, EditableIndex); + else if (strcmp(cmd, "st") == 0 || strcmp(cmd, "st+") == 0) + status = exec_command_st(scan_state, active_branch, cmd); + else if (strcmp(cmd, "sr") == 0 || strcmp(cmd, "sr+") == 0) + status = exec_command_sf_sv_sm_si_sr(scan_state, active_branch, cmd, EditableTable); else if (strcmp(cmd, "t") == 0) status = exec_command_t(scan_state, active_branch); else if (strcmp(cmd, "T") == 0) @@ -2318,11 +2334,11 @@ exec_command_setenv(PsqlScanState scan_state, bool active_branch, } /* - * \sf/\sv -- show a function/view's source code + * \sf/\sv/\sm/\si/\sr -- show a function/view's/matview's/index/table's source code */ static backslashResult -exec_command_sf_sv(PsqlScanState scan_state, bool active_branch, - const char *cmd, bool is_func) +exec_command_sf_sv_sm_si_sr(PsqlScanState scan_state, bool active_branch, + const char *cmd, EditableObjectType type) { backslashResult status = PSQL_CMD_SKIP_LINE; @@ -2332,39 +2348,60 @@ exec_command_sf_sv(PsqlScanState scan_state, bool active_branch, PQExpBuffer buf; char *obj_desc; Oid obj_oid = InvalidOid; - EditableObjectType eot = is_func ? EditableFunction : EditableView; buf = createPQExpBuffer(); obj_desc = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true); - if (pset.sversion < (is_func ? 80400 : 70400)) + if ((pset.sversion < 80400 && type == EditableFunction) || (pset.sversion < 70400 && type == EditableView)) { char sverbuf[32]; formatPGVersionNumber(pset.sversion, false, sverbuf, sizeof(sverbuf)); - if (is_func) - pg_log_error("The server (version %s) does not support showing function source.", - sverbuf); - else - pg_log_error("The server (version %s) does not support showing view definitions.", - sverbuf); + switch (type) + { + case EditableFunction: + pg_log_error("The server (version %s) does not support showing function source.", + sverbuf); + break; + case EditableView: + pg_log_error("The server (version %s) does not support showing view definitions.", + sverbuf); + default: + break; + } status = PSQL_CMD_ERROR; } else if (!obj_desc) { - if (is_func) - pg_log_error("function name is required"); - else - pg_log_error("view name is required"); + switch (type) + { + case EditableFunction: + pg_log_error("function name is required"); + break; + case EditableView: + pg_log_error("view name is required"); + break; + case EditableMatview: + pg_log_error("matview name is required"); + break; + case EditableIndex: + pg_log_error("index name is required"); + break; + case EditableTable: + pg_log_error("table name is required"); + break; + default: + break; + } status = PSQL_CMD_ERROR; } - else if (!lookup_object_oid(eot, obj_desc, &obj_oid)) + else if (!lookup_object_oid(type, obj_desc, &obj_oid)) { /* error already reported */ status = PSQL_CMD_ERROR; } - else if (!get_create_object_cmd(eot, obj_oid, buf)) + else if (!get_create_object_cmd(type, obj_oid, buf)) { /* error already reported */ status = PSQL_CMD_ERROR; @@ -2399,8 +2436,13 @@ exec_command_sf_sv(PsqlScanState scan_state, bool active_branch, * with "AS ", and that there can be no such line before the * real start of the function body. */ - print_with_linenumbers(output, buf->data, - is_func ? "AS " : NULL); + if (type == EditableFunction) { + print_with_linenumbers(output, buf->data, "AS "); + } + else + { + print_with_linenumbers(output, buf->data, NULL); + } } else { @@ -2422,6 +2464,106 @@ exec_command_sf_sv(PsqlScanState scan_state, bool active_branch, return status; } +/* + * \st -- show a trigger's source code + * the format of \st arguments is "table_name TRIGGER trigger_name", + * because different tables can have triggers with the same name + */ +static backslashResult +exec_command_st(PsqlScanState scan_state, bool active_branch, const char *cmd) +{ + backslashResult status = PSQL_CMD_SKIP_LINE; + + if (active_branch) + { + bool show_linenumbers = (strcmp(cmd, "st+") == 0); + PQExpBuffer trigger_buf; + char *table; + char *tg_string; + char *trigger; + Oid trigger_oid = InvalidOid; + + trigger_buf = createPQExpBuffer(); + table = psql_scan_slash_option(scan_state, + OT_NORMAL, NULL, true); + tg_string = psql_scan_slash_option(scan_state, + OT_NORMAL, NULL, true); + trigger = psql_scan_slash_option(scan_state, + OT_NORMAL, NULL, true); + + /* + * TO-DO add version check + */ + + if (!trigger) + { + pg_log_error("trigger name is required"); + status = PSQL_CMD_ERROR; + } + else if (strcmp(tg_string, "TRIGGER") != 0) { + pg_log_error("wrong command format"); + status = PSQL_CMD_ERROR; + } + else if (!table) { + pg_log_error("table name is required"); + status = PSQL_CMD_ERROR; + } + else if (!lookup_trigger_oid(table, trigger, &trigger_oid)) + { + /* error already reported */ + status = PSQL_CMD_ERROR; + } + else if (!get_create_object_cmd(EditableTrigger, trigger_oid, trigger_buf)) + { + /* error already reported */ + status = PSQL_CMD_ERROR; + } + else + { + FILE *output; + bool is_pager; + + /* Select output stream: stdout, pager, or file */ + if (pset.queryFout == stdout) + { + /* count lines in view to see if pager is needed */ + int lineno = count_lines_in_buf(trigger_buf); + + output = PageOutput(lineno, &(pset.popt.topt)); + is_pager = true; + } + else + { + /* use previously set output file, without pager */ + output = pset.queryFout; + is_pager = false; + } + + if (show_linenumbers) + { + /* add line numbers, numbering all lines */ + print_with_linenumbers(output, trigger_buf->data, NULL); + } + else + { + /* just send the view definition to output */ + fputs(trigger_buf->data, output); + } + + if (is_pager) + ClosePager(output); + } + + if (trigger) + free(trigger); + destroyPQExpBuffer(trigger_buf); + } + else + ignore_slash_whole_line(scan_state); + + return status; +} + /* * \t -- turn off table headers and row count */ @@ -4805,16 +4947,32 @@ lookup_object_oid(EditableObjectType obj_type, const char *desc, break; case EditableView: + case EditableMatview: + case EditableIndex: + case EditableTable: /* - * Convert view name (possibly schema-qualified) to OID. Note: - * this code doesn't check if the relation is actually a view. - * We'll detect that in get_create_object_cmd(). + * Convert name (possibly schema-qualified) to OID. Note: this + * code doesn't check if the relation is actually of the right + * type. We'll detect that in get_create_object_cmd(). */ appendPQExpBufferStr(query, "SELECT "); appendStringLiteralConn(query, desc, pset.db); appendPQExpBufferStr(query, "::pg_catalog.regclass::pg_catalog.oid"); break; + + case EditableTrigger: + + /* + * Note: triggers of different databases can have the same name, + * this function displays only the first result + */ + printfPQExpBuffer(query, + "SELECT t.oid FROM pg_trigger t " + "WHERE tgname = \'%s\' LIMIT 1", + desc); + break; + } if (!echo_hidden_command(query->data)) @@ -4827,7 +4985,61 @@ lookup_object_oid(EditableObjectType obj_type, const char *desc, *obj_oid = atooid(PQgetvalue(res, 0, 0)); else { - minimal_error_message(res); + if (obj_type == EditableTrigger) + { + /* + * Printing error message for triggers + */ + pg_log_error("trigger does not exist"); + } + else + { + minimal_error_message(res); + } + result = false; + } + + PQclear(res); + destroyPQExpBuffer(query); + + return result; +} + +/* + * Triggers use a separate lookup_oid function, because + * trigger names are not unique and multiple tables can have + * a trigger of the same name, so we must also look up table's oid + */ +static bool +lookup_trigger_oid(const char *table_name, const char *trigger_name, + Oid *obj_oid) +{ + bool result = true; + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + Oid table_oid = InvalidOid; + + if (!lookup_object_oid(EditableTable, table_name, &table_oid)) { + return false; + } + + printfPQExpBuffer(query, + "SELECT t.oid FROM pg_trigger t " + "LEFT JOIN pg_class c ON t.tgrelid = c.oid " + "WHERE t.tgname = \'%s\' AND c.oid = %u", + trigger_name, table_oid); + + if (!echo_hidden_command(query->data)) + { + destroyPQExpBuffer(query); + return false; + } + res = PQexec(pset.db, query->data); + if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) + *obj_oid = atooid(PQgetvalue(res, 0, 0)); + else + { + pg_log_error("trigger does not exist"); result = false; } @@ -4910,6 +5122,45 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid, oid); } break; + case EditableMatview: + printfPQExpBuffer(query, + "SELECT m.definition, relname, relkind, amname, nspname, reloptions " + "FROM pg_catalog.pg_class c " + "LEFT JOIN pg_matviews m ON c.relname = m.matviewname " + "LEFT JOIN pg_am a on c.relam = a.oid " + "LEFT JOIN pg_namespace s ON c.relnamespace = s.oid " + "WHERE c.oid = %u ", + oid); + break; + case EditableIndex: + printfPQExpBuffer(query, + "SELECT i.indexdef, relkind, relname " + "FROM pg_catalog.pg_class c " + "LEFT JOIN pg_indexes i ON c.relname = i.indexname " + "WHERE c.oid = %u ", + oid); + break; + + case EditableTrigger: + printfPQExpBuffer(query, + "SELECT pg_catalog.pg_get_triggerdef(%u)", + oid); + break; + + case EditableTable: + printfPQExpBuffer(query, + "SELECT relname, relkind, nspname, spcname, reloptions, " + "relnatts, relpersistence, reloftype, " + "relispartition, pg_get_partkeydef(c.oid), " + "pg_get_expr(c.relpartbound, c.oid, true), " + "amname, typname " + "FROM pg_class c LEFT JOIN pg_type t ON t.oid = c.reloftype " + "LEFT JOIN pg_namespace s ON c.relnamespace = s.oid " + "LEFT JOIN pg_am a on c.relam = a.oid " + "LEFT JOIN pg_tablespace tbs on c.reltablespace = tbs.oid " + "WHERE c.oid = %u", + oid); + break; } if (!echo_hidden_command(query->data)) @@ -4924,8 +5175,80 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid, switch (obj_type) { case EditableFunction: + case EditableTrigger: appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0)); break; + case EditableIndex: + { + char *indexdef = PQgetvalue(res, 0, 0); + char *relkind = PQgetvalue(res, 0, 1); + char *relname = PQgetvalue(res, 0, 2); + + /* + * Check if relation is an index + */ + if (relkind[0] != 'i' && relkind[0] != 'I') + { + pg_log_error("\"%s\" is not an index", + relname); + result = false; + break; + } + appendPQExpBufferStr(buf, indexdef); + break; + } + case EditableMatview: + { + char *viewdef = PQgetvalue(res, 0, 0); + char *relname = PQgetvalue(res, 0, 1); + char *relkind = PQgetvalue(res, 0, 2); + char *amname = PQgetvalue(res, 0, 3); + char *spcname = PQgetvalue(res, 0, 4); + char *reloptions = PQgetvalue(res, 0, 5); + + switch (relkind[0]) + { + case RELKIND_MATVIEW: + appendPQExpBufferStr(buf, "CREATE MATERIALIZED VIEW "); + break; + default: + pg_log_error("\"%s\" is not a matview", + relname); + result = false; + break; + } + appendPQExpBuffer(buf, "%s", relname); + + /* + * add access method + */ + if (!PQgetisnull(res, 0, 3)) + { + appendPQExpBuffer(buf, "\n USING %s", amname); + } + + /* reloptions, if not an empty array "{}" */ + if (reloptions != NULL && strlen(reloptions) > 2) + { + appendPQExpBufferStr(buf, "\n WITH ("); + if (!appendReloptionsArray(buf, reloptions, "", + pset.encoding, + standard_strings())) + { + pg_log_error("could not parse reloptions array"); + result = false; + } + appendPQExpBufferChar(buf, ')'); + } + + + if (!PQgetisnull(res, 0, 4)) + { + appendPQExpBuffer(buf, "\n TABLESPACE %s", spcname); + } + appendPQExpBuffer(buf, "\n AS\n %s", viewdef); + } + break; case EditableView: { @@ -4946,7 +5269,7 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid, { #ifdef NOT_USED case RELKIND_MATVIEW: - appendPQExpBufferStr(buf, "CREATE OR REPLACE MATERIALIZED VIEW "); + appendPQExpBufferStr(buf, "CREATE MATERIALIZED VIEW "); break; #endif case RELKIND_VIEW: @@ -4988,7 +5311,291 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid, checkoption); } break; + case EditableTable: + { + char *relname = PQgetvalue(res, 0, 0); + char *relkind = PQgetvalue(res, 0, 1); + char *nspname = PQgetvalue(res, 0, 2); + char *spcname = PQgetvalue(res, 0, 3); + char *reloptions = PQgetvalue(res, 0, 4); + int relnatts = atoi(PQgetvalue(res, 0, 5)); + char *relpersistence = PQgetvalue(res, 0, 6); + char *reloftype = PQgetvalue(res, 0, 7); + char *relispartition = PQgetvalue(res, 0, 8); + char *partkeydef = PQgetvalue(res, 0, 9); + char *relpartbound = PQgetvalue(res, 0, 10); + char *amname = PQgetvalue(res, 0, 11); + int numParents; + + PQExpBuffer column_info = createPQExpBuffer(); + PQExpBuffer constraint_info = createPQExpBuffer(); + PQExpBuffer parent_info = createPQExpBuffer(); + + PGresult *colres; + PGresult *conres; + PGresult *parres; + + /* + * Check if relation is a table + */ + if (relkind[0] != 'r' && relkind[0] != 't' && relkind[0] != 'p') + { + pg_log_error("\"%s\" is not a table", + relname); + result = false; + break; + } + + /* + * Constructing select statements for information about + * columns, constraints and parents of a table + */ + + printfPQExpBuffer(column_info, + "SELECT attname, pg_catalog.format_type(atttypid, NULL), collname, attnotnull, atthasdef, pg_get_expr(d.adbin, d.adrelid) " + "FROM pg_attribute a LEFT JOIN pg_type t on a.atttypid = t.oid " + "LEFT JOIN pg_attrdef d " + "ON d.adrelid = a.attrelid AND d.adnum = a.attnum " + "LEFT JOIN pg_collation c ON a.attcollation = c.oid " + "WHERE attrelid = %u AND attnum >= 1 " + "ORDER BY attnum ASC ", + oid); + if (!echo_hidden_command(column_info->data)) + { + result = false; + break; + } + + printfPQExpBuffer(constraint_info, + "SELECT con.conname, pg_get_constraintdef(con.oid) " + "FROM pg_catalog.pg_constraint con " + "INNER JOIN pg_catalog.pg_class rel " + "ON rel.oid = con.conrelid " + "WHERE rel.oid = %u ;", + oid); + if (!echo_hidden_command(constraint_info->data)) + { + result = false; + break; + } + + printfPQExpBuffer(parent_info, + "SELECT relname, nspname " + "FROM pg_class c LEFT JOIN pg_inherits i ON i.inhparent = c.oid " + "LEFT JOIN pg_namespace n ON c.relnamespace = n.oid " + "WHERE inhrelid = %u ORDER BY inhseqno ASC", + oid); + if (!echo_hidden_command(parent_info->data)) + { + result = false; + break; + } + + colres = PQexec(pset.db, column_info->data); + conres = PQexec(pset.db, constraint_info->data); + parres = PQexec(pset.db, parent_info->data); + + if (PQresultStatus(colres) != PGRES_TUPLES_OK) + { + minimal_error_message(colres); + result = false; + break; + } + if (PQresultStatus(conres) != PGRES_TUPLES_OK) + { + minimal_error_message(conres); + result = false; + break; + } + if (PQresultStatus(parres) != PGRES_TUPLES_OK) + { + minimal_error_message(parres); + result = false; + break; + } + + numParents = PQntuples(parres); + + appendPQExpBuffer(buf, "CREATE "); + switch (relpersistence[0]) + { + case 'u': + appendPQExpBuffer(buf, "UNLOGGED "); + break; + case 't': + appendPQExpBuffer(buf, "TEMPORARY "); + break; + default: + break; + } + appendPQExpBuffer(buf, "TABLE %s.%s", nspname, relname); + + /* + * if typed, add type name + */ + if (strcmp(reloftype, "0") != 0) + { + char *typname = PQgetvalue(res, 0, 13); + + appendPQExpBuffer(buf, "\n OF %s", typname); + } + + if (relispartition[0] == 't') + { + + /* + * Partition can only have one parent. + */ + if (numParents != 1) + { + pg_log_error("Invalid number of parents %d for table %s\n", numParents, relname); + result = false; + } + + appendPQExpBuffer(buf, " PARTITION OF %s", PQgetvalue(parres, 0, 0)); + } + + /* + * adding column info + */ + + for (int column = 0; column < relnatts; column++) + { + char *column_name = PQgetvalue(colres, column, 0); + char *data_type = PQgetvalue(colres, column, 1); + char *collation_name = PQgetvalue(colres, column, 2); + char *not_null = PQgetvalue(colres, column, 3); + char *has_default = PQgetvalue(colres, column, 4); + char *default_value = PQgetvalue(colres, column, 5); + + if (column == 0) + appendPQExpBuffer(buf, " (\n "); + else + appendPQExpBuffer(buf, ",\n "); + appendPQExpBuffer(buf, "%s", column_name); + if (strcmp(reloftype, "0") == 0) + appendPQExpBuffer(buf, " %s", data_type); + + /* + * check if not null + */ + if (strcmp(not_null, "t") == 0) + appendPQExpBuffer(buf, " NOT NULL"); + + /* + * check for default value + */ + if (strcmp(has_default, "t") == 0) + appendPQExpBuffer(buf, " DEFAULT %s", default_value); + if (strcmp(collation_name, "default") != 0 && !PQgetisnull(colres, column, 2)) + appendPQExpBuffer(buf, " COLLATE %s", collation_name); + } + + /* + * add constraints, if any + */ + if (PQntuples(conres)) + { + appendPQExpBufferStr(buf, ",\n"); + } + else + { + appendPQExpBufferStr(buf, "\n"); + } + for (int i = 0; i < PQntuples(conres); i++) + { + char *constraint_name = PQgetvalue(conres, i, 0); + char *constraint_def = PQgetvalue(conres, i, 1); + + appendPQExpBuffer(buf, " CONSTRAINT %s %s", constraint_name, constraint_def); + if (i != PQntuples(conres) - 1) + appendPQExpBuffer(buf, ","); + appendPQExpBuffer(buf, "\n"); + } + appendPQExpBufferStr(buf, " ) "); + + /* + * add patition bounds + */ + if (relispartition[0] == 't') + { + appendPQExpBuffer(buf, "%s", relpartbound); + } + appendPQExpBufferStr(buf, "\n"); + + /* + * add parent info + */ + if (numParents > 0 && relispartition[0] == 'f') + { + appendPQExpBuffer(buf, "INHERITS "); + for (int i = 0; i < numParents; i++) + { + char *parent_name = PQgetvalue(parres, i, 0); + char *parent_namespace = PQgetvalue(parres, i, 1); + + if (strcmp(nspname, parent_namespace) != 0) + { + appendPQExpBuffer(buf, "%s.", parent_namespace); + } + appendPQExpBuffer(buf, "%s", parent_name); + if (i != numParents - 1) + { + appendPQExpBuffer(buf, ", "); + } + } + appendPQExpBuffer(buf, "\n"); + } + + /* + * if partitioned, add definition + */ + if (relkind[0] == 'p') + { + appendPQExpBuffer(buf, "PARTITION BY %s\n", partkeydef); + } + + /* + * add access method + */ + if (!PQgetisnull(res, 0, 12)) + { + appendPQExpBuffer(buf, "USING %s\n", amname); + } + + /* + * reloptions, if not an empty array "{}" + */ + if (reloptions != NULL && strlen(reloptions) > 2) + { + appendPQExpBufferStr(buf, " WITH ("); + if (!appendReloptionsArray(buf, reloptions, "", + pset.encoding, + standard_strings())) + { + pg_log_error("could not parse reloptions array\n"); + result = false; + } + appendPQExpBufferStr(buf, ")\n"); + } + + if (!PQgetisnull(res, 0, 3)) + { + appendPQExpBuffer(buf, " TABLESPACE %s\n", spcname); + } + + PQclear(colres); + PQclear(conres); + PQclear(parres); + + destroyPQExpBuffer(column_info); + destroyPQExpBuffer(constraint_info); + destroyPQExpBuffer(parent_info); + + } + break; } + /* Make sure result ends with a newline */ if (buf->len > 0 && buf->data[buf->len - 1] != '\n') appendPQExpBufferChar(buf, '\n'); @@ -5005,6 +5612,7 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid, return result; } + /* * If the given argument of \ef or \ev ends with a line number, delete the line * number from the argument string and return it as an integer. (We need diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index eb018854a5c..186a1cf2efa 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1491,7 +1491,7 @@ psql_completion(const char *text, int start, int end) "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r", - "\\s", "\\set", "\\setenv", "\\sf", "\\sv", + "\\s", "\\set", "\\setenv", "\\sf", "\\sv", "\\si", "\\sm", "\\sr", "\\st", "\\t", "\\T", "\\timing", "\\unset", "\\x", @@ -3890,6 +3890,21 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL); else if (TailMatchesCS("\\sv*")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL); + else if (TailMatchesCS("\\si*")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); + else if (TailMatchesCS("\\sm*")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); + else if (TailMatchesCS("\\sr*")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + else if (TailMatchesCS("\\st*")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + else if (MatchesCS("\\st*", MatchAny)) + COMPLETE_WITH("TRIGGER"); + else if (MatchesCS("\\st*", MatchAny, "TRIGGER")) + { + completion_info_charp = prev2_wd; + COMPLETE_WITH_QUERY(Query_for_trigger_of_table); + } else if (TailMatchesCS("\\cd|\\e|\\edit|\\g|\\gx|\\i|\\include|" "\\ir|\\include_relative|\\o|\\out|" "\\s|\\w|\\write|\\lo_import")) diff --git a/src/test/regress/expected/si_st_sm_sr.out b/src/test/regress/expected/si_st_sm_sr.out new file mode 100644 index 00000000000..7934d273089 --- /dev/null +++ b/src/test/regress/expected/si_st_sm_sr.out @@ -0,0 +1,156 @@ +-- +-- SI_ST_SM_SR +-- Test cases for recreating CREATE commands +-- +CREATE TABLE smtest (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL); +CREATE VIEW smtestv AS SELECT type, sum(amt) AS totamt FROM smtest GROUP BY type; +CREATE MATERIALIZED VIEW smtestm AS SELECT type, sum(amt) AS totamt FROM smtest GROUP BY type WITH NO DATA; +CREATE MATERIALIZED VIEW smtestvm AS SELECT * FROM smtestv ORDER BY type; +\sm smtestm +CREATE MATERIALIZED VIEW smtestm + USING heap + TABLESPACE public + AS + SELECT smtest.type, + sum(smtest.amt) AS totamt + FROM smtest + GROUP BY smtest.type; +\sm smtestvm +CREATE MATERIALIZED VIEW smtestvm + USING heap + TABLESPACE public + AS + SELECT smtestv.type, + smtestv.totamt + FROM smtestv + ORDER BY smtestv.type; +DROP TABLE smtest CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to view smtestv +drop cascades to materialized view smtestvm +drop cascades to materialized view smtestm +create table pkeys (pkey1 int4 not null, pkey2 text not null); +create table fkeys (fkey1 int4, fkey2 text, fkey3 int); +create table fkeys2 (fkey21 int4, fkey22 text, pkey23 int not null); +create trigger check_fkeys_pkey_exist + before insert or update on fkeys + for each row + execute function + check_primary_key ('fkey1', 'fkey2', 'pkeys', 'pkey1', 'pkey2'); +create trigger check_fkeys2_pkey_exist + before insert or update on fkeys2 + for each row + execute procedure + check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2'); +create trigger check_fkeys2_pkey_exist + before insert or update on fkeys + for each row + execute procedure + check_primary_key ('fkey21', 'fkey22'); +\st fkeys TRIGGER check_fkeys_pkey_exist +CREATE TRIGGER check_fkeys_pkey_exist BEFORE INSERT OR UPDATE ON public.fkeys FOR EACH ROW EXECUTE FUNCTION check_primary_key('fkey1', 'fkey2', 'pkeys', 'pkey1', 'pkey2') +\st fkeys2 TRIGGER check_fkeys2_pkey_exist +CREATE TRIGGER check_fkeys2_pkey_exist BEFORE INSERT OR UPDATE ON public.fkeys2 FOR EACH ROW EXECUTE FUNCTION check_primary_key('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2') +\st fkeys TRIGGER check_fkeys2_pkey_exist +CREATE TRIGGER check_fkeys2_pkey_exist BEFORE INSERT OR UPDATE ON public.fkeys FOR EACH ROW EXECUTE FUNCTION check_primary_key('fkey21', 'fkey22') +DROP TABLE pkeys; +DROP TABLE fkeys; +DROP TABLE fkeys2; +create table idxtable (a int, b int, c text); +create index idx on idxtable using hash (a); +create index idx2 on idxtable (c COLLATE "POSIX"); +\si idx +CREATE INDEX idx ON public.idxtable USING hash (a) +\si idx2 +CREATE INDEX idx2 ON public.idxtable USING btree (c COLLATE "POSIX") +drop index idx; +drop index idx2; +drop table idxtable; +CREATE TABLE collate_test ( + a int, + b text COLLATE "C" NOT NULL +); +\sr collate_test +CREATE TABLE public.collate_test ( + a integer, + b text NOT NULL COLLATE C + ) +DROP TABLE collate_test; +CREATE TABLE ptif_test (a int, b int) PARTITION BY range (a); +CREATE TABLE ptif_test0 PARTITION OF ptif_test + FOR VALUES FROM (minvalue) TO (0) PARTITION BY list (b); +\sr ptif_test +CREATE TABLE public.ptif_test ( + a integer, + b integer + ) +PARTITION BY RANGE (a) +\sr ptif_test0 +CREATE TABLE public.ptif_test0 PARTITION OF ptif_test ( + a integer, + b integer + ) FOR VALUES FROM (MINVALUE) TO (0) +PARTITION BY LIST (b) +DROP TABLE ptif_test0; +DROP TABLE ptif_test; +CREATE TABLE srtest0 (aa TEXT); +CREATE TABLE srtest1 (bb TEXT) INHERITS (srtest0); +CREATE TABLE srtest2 (cc TEXT) INHERITS (srtest0); +CREATE TABLE srtest3 (dd TEXT) INHERITS (srtest1, srtest2, srtest0); +NOTICE: merging multiple inherited definitions of column "aa" +NOTICE: merging multiple inherited definitions of column "aa" +\sr srtest0 +CREATE TABLE public.srtest0 ( + aa text + ) +\sr srtest1 +CREATE TABLE public.srtest1 ( + aa text, + bb text + ) +INHERITS srtest0 +\sr srtest2 +CREATE TABLE public.srtest2 ( + aa text, + cc text + ) +INHERITS srtest0 +\sr srtest3 +CREATE TABLE public.srtest3 ( + aa text, + bb text, + cc text, + dd text + ) +INHERITS srtest1, srtest2, srtest0 +DROP TABLE srtest0 CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table srtest1 +drop cascades to table srtest2 +drop cascades to table srtest3 +CREATE TABLE srtest4 (id int, name text) WITH (fillfactor=10); +\sr srtest4 +CREATE TABLE public.srtest4 ( + id integer, + name text + ) + WITH (fillfactor='10') +DROP TABLE srtest4; +CREATE TABLE constraint_test( + ID INT PRIMARY KEY NOT NULL, + NAME TEXT NOT NULL, + AGE INT NOT NULL UNIQUE, + ADDRESS CHAR(50), + SALARY REAL DEFAULT 50000.00 +); +\sr constraint_test +CREATE TABLE public.constraint_test ( + id integer NOT NULL, + name text NOT NULL, + age integer NOT NULL, + address character, + salary real DEFAULT 50000.00, + CONSTRAINT constraint_test_age_key UNIQUE (age), + CONSTRAINT constraint_test_pkey PRIMARY KEY (id) + ) +DROP TABLE constraint_test; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 026ea880cde..3ffa4ead18d 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -121,3 +121,4 @@ test: fast_default # run stats by itself because its delay may be insufficient under heavy load test: stats +test: si_st_sm_sr \ No newline at end of file diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 979d9261197..11c21ffbdaf 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -201,3 +201,4 @@ test: explain test: event_trigger test: fast_default test: stats +test: si_st_sm_sr \ No newline at end of file diff --git a/src/test/regress/sql/si_st_sm_sr.sql b/src/test/regress/sql/si_st_sm_sr.sql new file mode 100644 index 00000000000..fa3272d4ebb --- /dev/null +++ b/src/test/regress/sql/si_st_sm_sr.sql @@ -0,0 +1,83 @@ +-- +-- SI_ST_SM_SR +-- Test cases for recreating CREATE commands +-- + +CREATE TABLE smtest (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL); +CREATE VIEW smtestv AS SELECT type, sum(amt) AS totamt FROM smtest GROUP BY type; +CREATE MATERIALIZED VIEW smtestm AS SELECT type, sum(amt) AS totamt FROM smtest GROUP BY type WITH NO DATA; +CREATE MATERIALIZED VIEW smtestvm AS SELECT * FROM smtestv ORDER BY type; +\sm smtestm +\sm smtestvm +DROP TABLE smtest CASCADE; + +create table pkeys (pkey1 int4 not null, pkey2 text not null); +create table fkeys (fkey1 int4, fkey2 text, fkey3 int); +create table fkeys2 (fkey21 int4, fkey22 text, pkey23 int not null); + +create trigger check_fkeys_pkey_exist + before insert or update on fkeys + for each row + execute function + check_primary_key ('fkey1', 'fkey2', 'pkeys', 'pkey1', 'pkey2'); +create trigger check_fkeys2_pkey_exist + before insert or update on fkeys2 + for each row + execute procedure + check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2'); +create trigger check_fkeys2_pkey_exist + before insert or update on fkeys + for each row + execute procedure + check_primary_key ('fkey21', 'fkey22'); +\st fkeys TRIGGER check_fkeys_pkey_exist +\st fkeys2 TRIGGER check_fkeys2_pkey_exist +\st fkeys TRIGGER check_fkeys2_pkey_exist +DROP TABLE pkeys; +DROP TABLE fkeys; +DROP TABLE fkeys2; +create table idxtable (a int, b int, c text); +create index idx on idxtable using hash (a); +create index idx2 on idxtable (c COLLATE "POSIX"); +\si idx +\si idx2 +drop index idx; +drop index idx2; +drop table idxtable; + +CREATE TABLE collate_test ( + a int, + b text COLLATE "C" NOT NULL +); +\sr collate_test +DROP TABLE collate_test; +CREATE TABLE ptif_test (a int, b int) PARTITION BY range (a); +CREATE TABLE ptif_test0 PARTITION OF ptif_test + FOR VALUES FROM (minvalue) TO (0) PARTITION BY list (b); +\sr ptif_test +\sr ptif_test0 +DROP TABLE ptif_test0; +DROP TABLE ptif_test; + +CREATE TABLE srtest0 (aa TEXT); +CREATE TABLE srtest1 (bb TEXT) INHERITS (srtest0); +CREATE TABLE srtest2 (cc TEXT) INHERITS (srtest0); +CREATE TABLE srtest3 (dd TEXT) INHERITS (srtest1, srtest2, srtest0); +\sr srtest0 +\sr srtest1 +\sr srtest2 +\sr srtest3 +DROP TABLE srtest0 CASCADE; +CREATE TABLE srtest4 (id int, name text) WITH (fillfactor=10); +\sr srtest4 +DROP TABLE srtest4; + +CREATE TABLE constraint_test( + ID INT PRIMARY KEY NOT NULL, + NAME TEXT NOT NULL, + AGE INT NOT NULL UNIQUE, + ADDRESS CHAR(50), + SALARY REAL DEFAULT 50000.00 +); +\sr constraint_test +DROP TABLE constraint_test;