mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	psql: Update \d sequence display
For \d sequencename, the psql code just did SELECT * FROM sequencename
to get the information to display, but this does not contain much
interesting information anymore in PostgreSQL 10, because the metadata
has been moved to a separate system catalog.
This patch creates a newly designed sequence display that is not merely
an extension of the general relation/table display as it was previously.
Example:
PostgreSQL 9.6:
=> \d foobar
           Sequence "public.foobar"
    Column     |  Type   |        Value
---------------+---------+---------------------
 sequence_name | name    | foobar
 last_value    | bigint  | 1
 start_value   | bigint  | 1
 increment_by  | bigint  | 1
 max_value     | bigint  | 9223372036854775807
 min_value     | bigint  | 1
 cache_value   | bigint  | 1
 log_cnt       | bigint  | 0
 is_cycled     | boolean | f
 is_called     | boolean | f
PostgreSQL 10 before this change:
=> \d foobar
   Sequence "public.foobar"
   Column   |  Type   | Value
------------+---------+-------
 last_value | bigint  | 1
 log_cnt    | bigint  | 0
 is_called  | boolean | f
New:
=> \d foobar
                           Sequence "public.foobar"
  Type  | Start | Minimum |       Maximum       | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
 bigint |     1 |       1 | 9223372036854775807 |         1 | no      |     1
Reviewed-by: Fabien COELHO <coelho@cri.ensmp.fr>
			
			
This commit is contained in:
		@@ -1380,8 +1380,6 @@ describeOneTableDetails(const char *schemaname,
 | 
			
		||||
	int			i;
 | 
			
		||||
	char	   *view_def = NULL;
 | 
			
		||||
	char	   *headers[11];
 | 
			
		||||
	char	  **seq_values = NULL;
 | 
			
		||||
	char	  **ptr;
 | 
			
		||||
	PQExpBufferData title;
 | 
			
		||||
	PQExpBufferData tmpbuf;
 | 
			
		||||
	int			cols;
 | 
			
		||||
@@ -1563,27 +1561,125 @@ describeOneTableDetails(const char *schemaname,
 | 
			
		||||
	res = NULL;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * If it's a sequence, fetch its values and store into an array that will
 | 
			
		||||
	 * be used later.
 | 
			
		||||
	 * If it's a sequence, deal with it here separately.
 | 
			
		||||
	 */
 | 
			
		||||
	if (tableinfo.relkind == RELKIND_SEQUENCE)
 | 
			
		||||
	{
 | 
			
		||||
		printfPQExpBuffer(&buf, "SELECT * FROM %s", fmtId(schemaname));
 | 
			
		||||
		/* must be separate because fmtId isn't reentrant */
 | 
			
		||||
		appendPQExpBuffer(&buf, ".%s;", fmtId(relationname));
 | 
			
		||||
		PGresult   *result = NULL;
 | 
			
		||||
		printQueryOpt myopt = pset.popt;
 | 
			
		||||
		char	   *footers[2] = {NULL, NULL};
 | 
			
		||||
 | 
			
		||||
		if (pset.sversion >= 100000)
 | 
			
		||||
		{
 | 
			
		||||
			printfPQExpBuffer(&buf,
 | 
			
		||||
							  "SELECT pg_catalog.format_type(seqtypid, NULL) AS \"%s\",\n"
 | 
			
		||||
							  "       seqstart AS \"%s\",\n"
 | 
			
		||||
							  "       seqmin AS \"%s\",\n"
 | 
			
		||||
							  "       seqmax AS \"%s\",\n"
 | 
			
		||||
							  "       seqincrement AS \"%s\",\n"
 | 
			
		||||
							  "       CASE WHEN seqcycle THEN '%s' ELSE '%s' END AS \"%s\",\n"
 | 
			
		||||
							  "       seqcache AS \"%s\"\n",
 | 
			
		||||
							  gettext_noop("Type"),
 | 
			
		||||
							  gettext_noop("Start"),
 | 
			
		||||
							  gettext_noop("Minimum"),
 | 
			
		||||
							  gettext_noop("Maximum"),
 | 
			
		||||
							  gettext_noop("Increment"),
 | 
			
		||||
							  gettext_noop("yes"),
 | 
			
		||||
							  gettext_noop("no"),
 | 
			
		||||
							  gettext_noop("Cycles?"),
 | 
			
		||||
							  gettext_noop("Cache"));
 | 
			
		||||
			appendPQExpBuffer(&buf,
 | 
			
		||||
							  "FROM pg_catalog.pg_sequence\n"
 | 
			
		||||
							  "WHERE seqrelid = '%s';",
 | 
			
		||||
							  oid);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			printfPQExpBuffer(&buf,
 | 
			
		||||
							  "SELECT pg_catalog.format_type('bigint'::regtype, NULL) AS \"%s\",\n"
 | 
			
		||||
							  "       start_value AS \"%s\",\n"
 | 
			
		||||
							  "       min_value AS \"%s\",\n"
 | 
			
		||||
							  "       max_value AS \"%s\",\n"
 | 
			
		||||
							  "       increment_by AS \"%s\",\n"
 | 
			
		||||
							  "       CASE WHEN is_cycled THEN '%s' ELSE '%s' END AS \"%s\",\n"
 | 
			
		||||
							  "       cache_value AS \"%s\"\n",
 | 
			
		||||
							  gettext_noop("Type"),
 | 
			
		||||
							  gettext_noop("Start"),
 | 
			
		||||
							  gettext_noop("Minimum"),
 | 
			
		||||
							  gettext_noop("Maximum"),
 | 
			
		||||
							  gettext_noop("Increment"),
 | 
			
		||||
							  gettext_noop("yes"),
 | 
			
		||||
							  gettext_noop("no"),
 | 
			
		||||
							  gettext_noop("Cycles?"),
 | 
			
		||||
							  gettext_noop("Cache"));
 | 
			
		||||
			appendPQExpBuffer(&buf, "FROM %s", fmtId(schemaname));
 | 
			
		||||
			/* must be separate because fmtId isn't reentrant */
 | 
			
		||||
			appendPQExpBuffer(&buf, ".%s;", fmtId(relationname));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		res = PSQLexec(buf.data);
 | 
			
		||||
		if (!res)
 | 
			
		||||
			goto error_return;
 | 
			
		||||
 | 
			
		||||
		seq_values = pg_malloc((PQnfields(res) + 1) * sizeof(*seq_values));
 | 
			
		||||
		/* Footer information about a sequence */
 | 
			
		||||
 | 
			
		||||
		for (i = 0; i < PQnfields(res); i++)
 | 
			
		||||
			seq_values[i] = pg_strdup(PQgetvalue(res, 0, i));
 | 
			
		||||
		seq_values[i] = NULL;
 | 
			
		||||
		/* Get the column that owns this sequence */
 | 
			
		||||
		printfPQExpBuffer(&buf, "SELECT pg_catalog.quote_ident(nspname) || '.' ||"
 | 
			
		||||
						  "\n   pg_catalog.quote_ident(relname) || '.' ||"
 | 
			
		||||
						  "\n   pg_catalog.quote_ident(attname),"
 | 
			
		||||
						  "\n   d.deptype"
 | 
			
		||||
						  "\nFROM pg_catalog.pg_class c"
 | 
			
		||||
						  "\nINNER JOIN pg_catalog.pg_depend d ON c.oid=d.refobjid"
 | 
			
		||||
						  "\nINNER JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace"
 | 
			
		||||
						  "\nINNER JOIN pg_catalog.pg_attribute a ON ("
 | 
			
		||||
						  "\n a.attrelid=c.oid AND"
 | 
			
		||||
						  "\n a.attnum=d.refobjsubid)"
 | 
			
		||||
						  "\nWHERE d.classid='pg_catalog.pg_class'::pg_catalog.regclass"
 | 
			
		||||
						  "\n AND d.refclassid='pg_catalog.pg_class'::pg_catalog.regclass"
 | 
			
		||||
						  "\n AND d.objid='%s'"
 | 
			
		||||
						  "\n AND d.deptype IN ('a', 'i')",
 | 
			
		||||
						  oid);
 | 
			
		||||
 | 
			
		||||
		PQclear(res);
 | 
			
		||||
		res = NULL;
 | 
			
		||||
		result = PSQLexec(buf.data);
 | 
			
		||||
 | 
			
		||||
		/*
 | 
			
		||||
		 * If we get no rows back, don't show anything (obviously). We should
 | 
			
		||||
		 * never get more than one row back, but if we do, just ignore it and
 | 
			
		||||
		 * don't print anything.
 | 
			
		||||
		 */
 | 
			
		||||
		if (!result)
 | 
			
		||||
			goto error_return;
 | 
			
		||||
		else if (PQntuples(result) == 1)
 | 
			
		||||
		{
 | 
			
		||||
			switch (PQgetvalue(result, 0, 1)[0])
 | 
			
		||||
			{
 | 
			
		||||
				case 'a':
 | 
			
		||||
					footers[0] = psprintf(_("Owned by: %s"),
 | 
			
		||||
										  PQgetvalue(result, 0, 0));
 | 
			
		||||
					break;
 | 
			
		||||
				case 'i':
 | 
			
		||||
					footers[0] = psprintf(_("Sequence for identity column: %s"),
 | 
			
		||||
										  PQgetvalue(result, 0, 0));
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		PQclear(result);
 | 
			
		||||
 | 
			
		||||
		printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
 | 
			
		||||
						  schemaname, relationname);
 | 
			
		||||
 | 
			
		||||
		myopt.footers = footers;
 | 
			
		||||
		myopt.topt.default_footer = false;
 | 
			
		||||
		myopt.title = title.data;
 | 
			
		||||
		myopt.translate_header = true;
 | 
			
		||||
 | 
			
		||||
		printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
 | 
			
		||||
 | 
			
		||||
		if (footers[0])
 | 
			
		||||
			free(footers[0]);
 | 
			
		||||
 | 
			
		||||
		retval = true;
 | 
			
		||||
		goto error_return;		/* not an error, just return early */
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
@@ -1667,10 +1763,6 @@ describeOneTableDetails(const char *schemaname,
 | 
			
		||||
				printfPQExpBuffer(&title, _("Materialized view \"%s.%s\""),
 | 
			
		||||
								  schemaname, relationname);
 | 
			
		||||
			break;
 | 
			
		||||
		case RELKIND_SEQUENCE:
 | 
			
		||||
			printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
 | 
			
		||||
							  schemaname, relationname);
 | 
			
		||||
			break;
 | 
			
		||||
		case RELKIND_INDEX:
 | 
			
		||||
			if (tableinfo.relpersistence == 'u')
 | 
			
		||||
				printfPQExpBuffer(&title, _("Unlogged index \"%s.%s\""),
 | 
			
		||||
@@ -1729,9 +1821,6 @@ describeOneTableDetails(const char *schemaname,
 | 
			
		||||
		show_column_details = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (tableinfo.relkind == RELKIND_SEQUENCE)
 | 
			
		||||
		headers[cols++] = gettext_noop("Value");
 | 
			
		||||
 | 
			
		||||
	if (tableinfo.relkind == RELKIND_INDEX)
 | 
			
		||||
		headers[cols++] = gettext_noop("Definition");
 | 
			
		||||
 | 
			
		||||
@@ -1813,10 +1902,6 @@ describeOneTableDetails(const char *schemaname,
 | 
			
		||||
			printTableAddCell(&cont, default_str, false, false);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Value: for sequences only */
 | 
			
		||||
		if (tableinfo.relkind == RELKIND_SEQUENCE)
 | 
			
		||||
			printTableAddCell(&cont, seq_values[i], false, false);
 | 
			
		||||
 | 
			
		||||
		/* Expression for index column */
 | 
			
		||||
		if (tableinfo.relkind == RELKIND_INDEX)
 | 
			
		||||
			printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
 | 
			
		||||
@@ -2027,55 +2112,6 @@ describeOneTableDetails(const char *schemaname,
 | 
			
		||||
 | 
			
		||||
		PQclear(result);
 | 
			
		||||
	}
 | 
			
		||||
	else if (tableinfo.relkind == RELKIND_SEQUENCE)
 | 
			
		||||
	{
 | 
			
		||||
		/* Footer information about a sequence */
 | 
			
		||||
		PGresult   *result = NULL;
 | 
			
		||||
 | 
			
		||||
		/* Get the column that owns this sequence */
 | 
			
		||||
		printfPQExpBuffer(&buf, "SELECT pg_catalog.quote_ident(nspname) || '.' ||"
 | 
			
		||||
						  "\n   pg_catalog.quote_ident(relname) || '.' ||"
 | 
			
		||||
						  "\n   pg_catalog.quote_ident(attname),"
 | 
			
		||||
						  "\n   d.deptype"
 | 
			
		||||
						  "\nFROM pg_catalog.pg_class c"
 | 
			
		||||
						  "\nINNER JOIN pg_catalog.pg_depend d ON c.oid=d.refobjid"
 | 
			
		||||
						  "\nINNER JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace"
 | 
			
		||||
						  "\nINNER JOIN pg_catalog.pg_attribute a ON ("
 | 
			
		||||
						  "\n a.attrelid=c.oid AND"
 | 
			
		||||
						  "\n a.attnum=d.refobjsubid)"
 | 
			
		||||
						  "\nWHERE d.classid='pg_catalog.pg_class'::pg_catalog.regclass"
 | 
			
		||||
						  "\n AND d.refclassid='pg_catalog.pg_class'::pg_catalog.regclass"
 | 
			
		||||
						  "\n AND d.objid='%s'"
 | 
			
		||||
						  "\n AND d.deptype IN ('a', 'i')",
 | 
			
		||||
						  oid);
 | 
			
		||||
 | 
			
		||||
		result = PSQLexec(buf.data);
 | 
			
		||||
		if (!result)
 | 
			
		||||
			goto error_return;
 | 
			
		||||
		else if (PQntuples(result) == 1)
 | 
			
		||||
		{
 | 
			
		||||
			switch (PQgetvalue(result, 0, 1)[0])
 | 
			
		||||
			{
 | 
			
		||||
				case 'a':
 | 
			
		||||
					printfPQExpBuffer(&buf, _("Owned by: %s"),
 | 
			
		||||
									  PQgetvalue(result, 0, 0));
 | 
			
		||||
					printTableAddFooter(&cont, buf.data);
 | 
			
		||||
					break;
 | 
			
		||||
				case 'i':
 | 
			
		||||
					printfPQExpBuffer(&buf, _("Sequence for identity column: %s"),
 | 
			
		||||
									  PQgetvalue(result, 0, 0));
 | 
			
		||||
					printTableAddFooter(&cont, buf.data);
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*
 | 
			
		||||
		 * If we get no rows back, don't show anything (obviously). We should
 | 
			
		||||
		 * never get more than one row back, but if we do, just ignore it and
 | 
			
		||||
		 * don't print anything.
 | 
			
		||||
		 */
 | 
			
		||||
		PQclear(result);
 | 
			
		||||
	}
 | 
			
		||||
	else if (tableinfo.relkind == RELKIND_RELATION ||
 | 
			
		||||
			 tableinfo.relkind == RELKIND_MATVIEW ||
 | 
			
		||||
			 tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
 | 
			
		||||
@@ -2960,13 +2996,6 @@ error_return:
 | 
			
		||||
	termPQExpBuffer(&title);
 | 
			
		||||
	termPQExpBuffer(&tmpbuf);
 | 
			
		||||
 | 
			
		||||
	if (seq_values)
 | 
			
		||||
	{
 | 
			
		||||
		for (ptr = seq_values; *ptr; ptr++)
 | 
			
		||||
			free(*ptr);
 | 
			
		||||
		free(seq_values);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (view_def)
 | 
			
		||||
		free(view_def);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,13 @@ SELECT pg_get_serial_sequence('itest1', 'a');
 | 
			
		||||
 public.itest1_a_seq
 | 
			
		||||
(1 row)
 | 
			
		||||
 | 
			
		||||
\d itest1_a_seq
 | 
			
		||||
                    Sequence "public.itest1_a_seq"
 | 
			
		||||
  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache 
 | 
			
		||||
---------+-------+---------+------------+-----------+---------+-------
 | 
			
		||||
 integer |     1 |       1 | 2147483647 |         1 | no      |     1
 | 
			
		||||
Sequence for identity column: public.itest1.a
 | 
			
		||||
 | 
			
		||||
CREATE TABLE itest4 (a int, b text);
 | 
			
		||||
ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error, requires NOT NULL
 | 
			
		||||
ERROR:  column "a" of relation "itest4" must be declared NOT NULL before identity can be added
 | 
			
		||||
 
 | 
			
		||||
@@ -535,6 +535,19 @@ SELECT * FROM pg_sequence_parameters('sequence_test4'::regclass);
 | 
			
		||||
          -1 | -9223372036854775808 |            -1 |        -1 | f            |          1 |        20
 | 
			
		||||
(1 row)
 | 
			
		||||
 | 
			
		||||
\d sequence_test4
 | 
			
		||||
                       Sequence "public.sequence_test4"
 | 
			
		||||
  Type  | Start |       Minimum        | Maximum | Increment | Cycles? | Cache 
 | 
			
		||||
--------+-------+----------------------+---------+-----------+---------+-------
 | 
			
		||||
 bigint |    -1 | -9223372036854775808 |      -1 |        -1 | no      |     1
 | 
			
		||||
 | 
			
		||||
\d serialtest2_f2_seq
 | 
			
		||||
                 Sequence "public.serialtest2_f2_seq"
 | 
			
		||||
  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache 
 | 
			
		||||
---------+-------+---------+------------+-----------+---------+-------
 | 
			
		||||
 integer |     1 |       1 | 2147483647 |         1 | no      |     1
 | 
			
		||||
Owned by: public.serialtest2.f2
 | 
			
		||||
 | 
			
		||||
-- Test comments
 | 
			
		||||
COMMENT ON SEQUENCE asdf IS 'won''t work';
 | 
			
		||||
ERROR:  relation "asdf" does not exist
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,8 @@ SELECT sequence_name FROM information_schema.sequences WHERE sequence_name LIKE
 | 
			
		||||
 | 
			
		||||
SELECT pg_get_serial_sequence('itest1', 'a');
 | 
			
		||||
 | 
			
		||||
\d itest1_a_seq
 | 
			
		||||
 | 
			
		||||
CREATE TABLE itest4 (a int, b text);
 | 
			
		||||
ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error, requires NOT NULL
 | 
			
		||||
ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL;
 | 
			
		||||
 
 | 
			
		||||
@@ -246,6 +246,10 @@ WHERE sequencename ~ ANY(ARRAY['sequence_test', 'serialtest'])
 | 
			
		||||
SELECT * FROM pg_sequence_parameters('sequence_test4'::regclass);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\d sequence_test4
 | 
			
		||||
\d serialtest2_f2_seq
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- Test comments
 | 
			
		||||
COMMENT ON SEQUENCE asdf IS 'won''t work';
 | 
			
		||||
COMMENT ON SEQUENCE sequence_test2 IS 'will work';
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user