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;