? src/backend/catalog/pg_tools_schema.sql Index: src/backend/catalog/Makefile =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/catalog/Makefile,v retrieving revision 1.45 diff -c -r1.45 Makefile *** src/backend/catalog/Makefile 2003/01/14 23:19:34 1.45 --- src/backend/catalog/Makefile 2003/02/28 03:08:38 *************** *** 16,21 **** --- 16,23 ---- BKIFILES = postgres.bki postgres.description + SCHEMAFILES = information_schema.sql pg_tools_schema.sql sql_features.txt + all: SUBSYS.o $(BKIFILES) SUBSYS.o: $(OBJS) *************** *** 46,51 **** --- 48,55 ---- $(INSTALL_DATA) postgres.bki $(DESTDIR)$(datadir)/postgres.bki $(INSTALL_DATA) postgres.description $(DESTDIR)$(datadir)/postgres.description $(INSTALL_DATA) $(srcdir)/information_schema.sql $(DESTDIR)$(datadir)/information_schema.sql + + $(INSTALL_DATA) $(srcdir)/pg_tools_schema.sql $(DESTDIR)$(datadir)/pg_tools_schema.sql $(INSTALL_DATA) $(srcdir)/sql_features.txt $(DESTDIR)$(datadir)/sql_features.txt installdirs: *************** *** 53,59 **** .PHONY: uninstall-data uninstall-data: ! rm -f $(addprefix $(DESTDIR)$(datadir)/, $(BKIFILES) information_schema.sql sql_features.txt) clean: --- 57,63 ---- .PHONY: uninstall-data uninstall-data: ! rm -f $(addprefix $(DESTDIR)$(datadir)/, $(BKIFILES) $(SCHEMAFILES)) clean: Index: src/backend/utils/adt/misc.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/adt/misc.c,v retrieving revision 1.28 diff -c -r1.28 misc.c *** src/backend/utils/adt/misc.c 2003/02/13 05:24:02 1.28 --- src/backend/utils/adt/misc.c 2003/02/28 03:08:43 *************** *** 16,23 **** --- 16,28 ---- #include #include + #include + #include "funcapi.h" #include "miscadmin.h" + #include "access/heapam.h" + #include "catalog/pg_type.h" + #include "mb/pg_wchar.h" #include "utils/builtins.h" *************** *** 57,60 **** --- 62,279 ---- namestrcpy(db, DatabaseName); PG_RETURN_NAME(db); + } + + /* + * pg_name_pattern + * + * Scan a wildcard-pattern option and segregate into a schema match regular + * expression, and object match regular expression. + */ + Datum + pg_name_pattern(PG_FUNCTION_ARGS) + { + FuncCallContext *funcctx; + char *pattern; + + char *schemabuf; + char *namebuf; + Datum values[2]; + char nulls[2]; + HeapTuple tuple; + Datum result; + char *cp; + bool inquotes = false; + bool firstcall = false; + + /* Grab the pattern */ + if (PG_ARGISNULL(0)) + elog(ERROR, "pg_name_pattern: Pattern is required"); + + pattern = DatumGetCString(DirectFunctionCall1(textout, PG_GETARG_DATUM(0))); + + /* First run through SRF? */ + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + MemoryContext oldcontext; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * switch to memory context appropriate for multiple function + * calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* build tupdesc for result tuples */ + /* this had better match pg_locks view in initdb.sh */ + tupdesc = CreateTemplateTupleDesc(2, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "schema_regexp", + TEXTOID, -1, 0, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "object_regexp", + TEXTOID, -1, 0, false); + + funcctx->slot = TupleDescGetSlot(tupdesc); + + MemoryContextSwitchTo(oldcontext); + + firstcall = true; + } + + funcctx = SRF_PERCALL_SETUP(); + + /* If it's not the first run through, we're done */ + if (!firstcall) + SRF_RETURN_DONE(funcctx); + + + /* Prepare the buffers */ + schemabuf = (char *) palloc(sizeof(char) * strlen(pattern) * 3 + 2); + namebuf = (char *) palloc(sizeof(char) * strlen(pattern) * 3 + 2); + strcpy(schemabuf, ""); + strcpy(namebuf, "^"); + + /* + * Parse the pattern, converting quotes and lower-casing unquoted + * letters; we assume this was NOT done by scan_option. Also, adjust + * shell-style wildcard characters into regexp notation. + */ + cp = pattern; + while (*cp) + { + if (*cp == '"') + { + if (inquotes && cp[1] == '"') + { + /* emit one quote */ + strcat(namebuf, "\""); + cp++; + } + inquotes = !inquotes; + cp++; + } + else if (!inquotes && *cp >= 'A' && *cp <= 'Z' && isupper((unsigned char) *cp)) + { + /* ASCII based conversion -- to match keywords.c conversion */ + *cp += 'a' - 'A'; + strncat(namebuf, cp, 1); + cp++; + } + else if (!inquotes && *cp == '*') + { + strcat(namebuf, ".*"); + cp++; + } + else if (!inquotes && *cp == '?') + { + strcat(namebuf, "."); + cp++; + } + else if (!inquotes && *cp == '.') + { + /* Found schema/name separator, move current pattern to schema */ + strcpy(schemabuf, namebuf); + namebuf[1] = '\0'; + cp++; + } + else + { + /* + * Ordinary data character, transfer to pattern + * + * Inside double quotes, or at all times if parsing an operator + * name, quote regexp special characters with a backslash to + * avoid regexp errors. Outside quotes, however, let them + * pass through as-is; this lets knowledgeable users build + * regexp expressions that are more powerful than shell-style + * patterns. + */ + if (inquotes && + strchr("|*+?()[]{}.^$\\", *cp)) + strcat(namebuf, "\\\\"); + + /* Ensure chars special to string literals are passed properly */ + if (*cp == '\'' || *cp == '\\') + strcat(namebuf, cp); + + strncat(namebuf, cp, pg_mbcharcliplen(cp, pg_mblen(cp), 1)); + cp++; + } + } + + /* + * Form tuple with appropriate data. + */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, ' ', sizeof(nulls)); + + if (strlen(schemabuf) - 1 > 1) + { + /* We have a schema pattern, so constrain the schemavar */ + + strcat(schemabuf, "$"); + /* Optimize away ".*$", and possibly the whole pattern */ + if (strlen(schemabuf) - 1 >= 3 && + strcmp(schemabuf+ (strlen(schemabuf) - 3), ".*$") == 0) + schemabuf[strlen(schemabuf) - 3] = '\0'; + + values[0] = DirectFunctionCall1(textin, + CStringGetDatum(schemabuf)); + } + else + nulls[0] = 'n'; + + if (strlen(namebuf) - 1 > 1) + { + /* We have a name pattern, so constrain the namevar(s) */ + + strcat(namebuf, "$"); + /* Optimize away ".*$", and possibly the whole pattern */ + if (strlen(namebuf) - 1 >= 3 && + strcmp(namebuf + (strlen(namebuf) - 3), ".*$") == 0) + namebuf[strlen(namebuf) - 3] = '\0'; + + values[1] = DirectFunctionCall1(textin, + CStringGetDatum(namebuf)); + } + else + nulls[1] = 'n'; + + /* Finish off the tuple and return it */ + tuple = heap_formtuple(funcctx->slot->ttc_tupleDescriptor, + values, nulls); + result = TupleGetDatum(funcctx->slot, tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + + /* + * pg_gettext() + * + * Enable external function calls to use gettext(). Intended for use + * in internal views. + */ + Datum + pg_gettext(PG_FUNCTION_ARGS) + { + char *str; + char *newstr; + text *result_text; + + /* Grab the text to translate */ + if (PG_ARGISNULL(0)) + elog(ERROR, "pg_gettext: Cannot translate null strings"); + + str = DatumGetCString(DirectFunctionCall1(textout, PG_GETARG_DATUM(0))); + + /* Convert text */ + newstr = gettext(str); + + /* Convert return string to text */ + result_text = DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(newstr))); + + /* return it */ + PG_RETURN_TEXT_P(result_text); } Index: src/bin/initdb/initdb.sh =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/initdb/initdb.sh,v retrieving revision 1.184 diff -c -r1.184 initdb.sh *** src/bin/initdb/initdb.sh 2003/02/19 23:41:15 1.184 --- src/bin/initdb/initdb.sh 2003/02/28 03:09:02 *************** *** 1059,1064 **** --- 1059,1068 ---- | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely echo "ok" + $ECHO_N "creating pg_tools schema... "$ECHO_C + "$PGPATH"/postgres $PGSQL_OPT -N template1 > /dev/null < "$datadir"/pg_tools_schema.sql || exit_nicely + echo "ok" + $ECHO_N "vacuuming database template1... "$ECHO_C "$PGPATH"/postgres $PGSQL_OPT template1 >/dev/null < nargs) + args[++nargs] = arg; + + /* Build an array of the options */ + success = dbPrint(args, nargs); + + if (success) + status = CMD_SKIP_LINE; + } + + /* If we've not succeeded, then it was an error */ if (!success) status = CMD_ERROR; *************** *** 890,897 **** return status; } - - /* * scan_option() * --- 860,865 ---- *************** *** 1455,1460 **** --- 1423,1431 ---- pset.issuper = test_superuser(PQuser(pset.db)); + /* Clean out the cache */ + removeDescCache(); + return success; } *************** *** 1673,1680 **** return !error; } - - /* * process_file --- 1644,1649 ---- Index: src/bin/psql/command.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/command.h,v retrieving revision 1.14 diff -c -r1.14 command.h *** src/bin/psql/command.h 2002/03/27 19:16:13 1.14 --- src/bin/psql/command.h 2003/02/28 03:09:11 *************** *** 13,19 **** #include "settings.h" #include "print.h" - typedef enum _backslashResult { CMD_UNKNOWN = 0, /* not done parsing yet (internal only) */ --- 13,18 ---- *************** *** 31,41 **** const char **end_of_cmd, volatile int *paren_level); ! int ! process_file(char *filename); ! bool ! test_superuser(const char *username); bool do_pset(const char *param, const char *value, --- 30,38 ---- const char **end_of_cmd, volatile int *paren_level); ! int process_file(char *filename); ! bool test_superuser(const char *username); bool do_pset(const char *param, const char *value, Index: src/bin/psql/describe.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/describe.c,v retrieving revision 1.75 diff -c -r1.75 describe.c *** src/bin/psql/describe.c 2003/02/24 03:54:06 1.75 --- src/bin/psql/describe.c 2003/02/28 03:09:18 *************** *** 14,1722 **** #include "common.h" #include "settings.h" #include "print.h" - #include "variables.h" #include ! #define _(x) gettext((x)) ! static bool describeOneTableDetails(const char *schemaname, ! const char *relationname, ! const char *oid, ! bool verbose); ! static void processNamePattern(PQExpBuffer buf, const char *pattern, ! bool have_where, bool force_escape, ! const char *schemavar, const char *namevar, ! const char *altnamevar, const char *visibilityrule); ! ! static void * xmalloc(size_t size) { ! void *tmp; ! ! tmp = malloc(size); ! if (!tmp) ! { ! psql_error("out of memory\n"); ! exit(EXIT_FAILURE); ! } ! return tmp; } static void * xmalloczero(size_t size) { ! void *tmp; ! ! tmp = xmalloc(size); ! memset(tmp, 0, size); ! return tmp; } ! ! /*---------------- ! * Handlers for various slash commands displaying some sort of list ! * of things in the database. * ! * If you add something here, try to format the query to look nice in -E output. ! *---------------- ! */ ! ! ! /* \da ! * Takes an optional regexp to select particular aggregates ! */ ! bool ! describeAggregates(const char *pattern, bool verbose) ! { ! PQExpBufferData buf; ! PGresult *res; ! printQueryOpt myopt = pset.popt; ! ! initPQExpBuffer(&buf); ! ! /* ! * There are two kinds of aggregates: ones that work on particular ! * types and ones that work on all (denoted by input type = "any") ! */ ! printfPQExpBuffer(&buf, ! "SELECT n.nspname as \"%s\",\n" ! " p.proname AS \"%s\",\n" ! " CASE p.proargtypes[0]\n" ! " WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype\n" ! " THEN CAST('%s' AS pg_catalog.text)\n" ! " ELSE pg_catalog.format_type(p.proargtypes[0], NULL)\n" ! " END AS \"%s\",\n" ! " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n" ! "FROM pg_catalog.pg_proc p\n" ! " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n" ! "WHERE p.proisagg\n", ! _("Schema"), _("Name"), _("(all types)"), ! _("Data type"), _("Description")); ! ! processNamePattern(&buf, pattern, true, false, ! "n.nspname", "p.proname", NULL, ! "pg_catalog.pg_function_is_visible(p.oid)"); ! ! appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3;"); ! ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! ! myopt.nullPrint = NULL; ! myopt.title = _("List of aggregate functions"); ! ! printQuery(res, &myopt, pset.queryFout); ! ! PQclear(res); ! return true; ! } ! ! ! /* \df ! * Takes an optional regexp to select particular functions ! */ ! bool ! describeFunctions(const char *pattern, bool verbose) ! { ! PQExpBufferData buf; ! PGresult *res; ! printQueryOpt myopt = pset.popt; ! ! initPQExpBuffer(&buf); ! ! printfPQExpBuffer(&buf, ! "SELECT CASE WHEN p.proretset THEN 'setof ' ELSE '' END ||\n" ! " pg_catalog.format_type(p.prorettype, NULL) as \"%s\",\n" ! " n.nspname as \"%s\",\n" ! " p.proname as \"%s\",\n" ! " pg_catalog.oidvectortypes(p.proargtypes) as \"%s\"", ! _("Result data type"), _("Schema"), _("Name"), ! _("Argument data types")); ! ! if (verbose) ! appendPQExpBuffer(&buf, ! ",\n u.usename as \"%s\",\n" ! " l.lanname as \"%s\",\n" ! " p.prosrc as \"%s\",\n" ! " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"", ! _("Owner"), _("Language"), ! _("Source code"), _("Description")); ! ! if (!verbose) ! appendPQExpBuffer(&buf, ! "\nFROM pg_catalog.pg_proc p" ! "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n"); ! else ! appendPQExpBuffer(&buf, ! "\nFROM pg_catalog.pg_proc p" ! "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace" ! "\n LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang" ! "\n LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner\n"); ! ! /* ! * we skip in/out funcs by excluding functions that take or return ! * cstring ! */ ! appendPQExpBuffer(&buf, ! "WHERE p.prorettype <> 'pg_catalog.cstring'::pg_catalog.regtype\n" ! " AND p.proargtypes[0] <> 'pg_catalog.cstring'::pg_catalog.regtype\n" ! " AND NOT p.proisagg\n"); ! ! processNamePattern(&buf, pattern, true, false, ! "n.nspname", "p.proname", NULL, ! "pg_catalog.pg_function_is_visible(p.oid)"); ! ! appendPQExpBuffer(&buf, "ORDER BY 2, 3, 1, 4;"); ! ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! ! myopt.nullPrint = NULL; ! myopt.title = _("List of functions"); ! ! printQuery(res, &myopt, pset.queryFout); ! ! PQclear(res); ! return true; ! } ! ! ! /* ! * \dT ! * describe types */ bool ! describeTypes(const char *pattern, bool verbose) { ! PQExpBufferData buf; PGresult *res; ! printQueryOpt myopt = pset.popt; ! ! initPQExpBuffer(&buf); ! ! printfPQExpBuffer(&buf, ! "SELECT n.nspname as \"%s\",\n" ! " pg_catalog.format_type(t.oid, NULL) AS \"%s\",\n", ! _("Schema"), _("Name")); ! if (verbose) ! appendPQExpBuffer(&buf, ! " t.typname AS \"%s\",\n" ! " CASE WHEN t.typrelid != 0\n" ! " THEN CAST('tuple' AS pg_catalog.text)\n" ! " WHEN t.typlen < 0\n" ! " THEN CAST('var' AS pg_catalog.text)\n" ! " ELSE CAST(t.typlen AS pg_catalog.text)\n" ! " END AS \"%s\",\n", ! _("Internal name"), _("Size")); ! appendPQExpBuffer(&buf, ! " pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n", ! _("Description")); ! ! appendPQExpBuffer(&buf, "FROM pg_catalog.pg_type t\n" ! " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n"); ! ! /* ! * do not include array types (start with underscore); do not include ! * complex types (typrelid!=0) unless they are standalone composite ! * types ! */ ! appendPQExpBuffer(&buf, "WHERE (t.typrelid = 0 "); ! appendPQExpBuffer(&buf, "OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c " ! "WHERE c.oid = t.typrelid)) "); ! appendPQExpBuffer(&buf, "AND t.typname !~ '^_'\n"); ! ! /* Match name pattern against either internal or external name */ ! processNamePattern(&buf, pattern, true, false, ! "n.nspname", "t.typname", ! "pg_catalog.format_type(t.oid, NULL)", ! "pg_catalog.pg_type_is_visible(t.oid)"); ! ! appendPQExpBuffer(&buf, "ORDER BY 1, 2;"); ! ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! ! myopt.nullPrint = NULL; ! myopt.title = _("List of data types"); ! ! printQuery(res, &myopt, pset.queryFout); ! ! PQclear(res); ! return true; ! } ! ! /* \do ! */ ! bool ! describeOperators(const char *pattern) ! { ! PQExpBufferData buf; ! PGresult *res; ! printQueryOpt myopt = pset.popt; ! ! initPQExpBuffer(&buf); ! printfPQExpBuffer(&buf, ! "SELECT n.nspname as \"%s\",\n" ! " o.oprname AS \"%s\",\n" ! " CASE WHEN o.oprkind='l' THEN NULL ELSE pg_catalog.format_type(o.oprleft, NULL) END AS \"%s\",\n" ! " CASE WHEN o.oprkind='r' THEN NULL ELSE pg_catalog.format_type(o.oprright, NULL) END AS \"%s\",\n" ! " pg_catalog.format_type(o.oprresult, NULL) AS \"%s\",\n" ! " coalesce(pg_catalog.obj_description(o.oid, 'pg_operator'),\n" ! " pg_catalog.obj_description(o.oprcode, 'pg_proc')) AS \"%s\"\n" ! "FROM pg_catalog.pg_operator o\n" ! " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace\n", ! _("Schema"), _("Name"), ! _("Left arg type"), _("Right arg type"), ! _("Result type"), _("Description")); ! ! processNamePattern(&buf, pattern, false, true, ! "n.nspname", "o.oprname", NULL, ! "pg_catalog.pg_operator_is_visible(o.oid)"); ! appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3, 4;"); ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! myopt.nullPrint = NULL; ! myopt.title = _("List of operators"); ! printQuery(res, &myopt, pset.queryFout); ! PQclear(res); ! return true; } - /* ! * listAllDbs * ! * for \l, \list, and -l switch */ ! bool ! listAllDbs(bool verbose) { PGresult *res; ! PQExpBufferData buf; printQueryOpt myopt = pset.popt; ! initPQExpBuffer(&buf); ! ! printfPQExpBuffer(&buf, ! "SELECT d.datname as \"%s\",\n" ! " u.usename as \"%s\"", ! _("Name"), _("Owner")); ! appendPQExpBuffer(&buf, ! ",\n pg_catalog.pg_encoding_to_char(d.encoding) as \"%s\"", ! _("Encoding")); ! if (verbose) ! appendPQExpBuffer(&buf, ! ",\n pg_catalog.obj_description(d.oid, 'pg_database') as \"%s\"", ! _("Description")); ! appendPQExpBuffer(&buf, ! "\nFROM pg_catalog.pg_database d" ! "\n LEFT JOIN pg_catalog.pg_user u ON d.datdba = u.usesysid\n" ! "ORDER BY 1;"); ! ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! myopt.nullPrint = NULL; ! myopt.title = _("List of databases"); ! printQuery(res, &myopt, pset.queryFout); ! ! PQclear(res); ! return true; ! } ! ! ! /* ! * List Tables Grant/Revoke Permissions ! * \z (now also \dp -- perhaps more mnemonic) ! */ ! bool ! permissionsList(const char *pattern) ! { ! PQExpBufferData buf; ! PGresult *res; ! printQueryOpt myopt = pset.popt; ! ! initPQExpBuffer(&buf); ! /* ! * we ignore indexes and toast tables since they have no meaningful ! * rights ! */ ! printfPQExpBuffer(&buf, ! "SELECT n.nspname as \"%s\",\n" ! " c.relname as \"%s\",\n" ! " c.relacl as \"%s\"\n" ! "FROM pg_catalog.pg_class c\n" ! " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" ! "WHERE c.relkind IN ('r', 'v', 'S')\n", ! _("Schema"), _("Table"), _("Access privileges")); ! ! /* ! * Unless a schema pattern is specified, we suppress system and temp ! * tables, since they normally aren't very interesting from a ! * permissions point of view. You can see 'em by explicit request ! * though, eg with \z pg_catalog.* ! */ ! processNamePattern(&buf, pattern, true, false, ! "n.nspname", "c.relname", NULL, ! "pg_catalog.pg_table_is_visible(c.oid) AND n.nspname !~ '^pg_'"); ! appendPQExpBuffer(&buf, "ORDER BY 1, 2;"); ! res = PSQLexec(buf.data, false); ! if (!res) ! { ! termPQExpBuffer(&buf); ! return false; } - - myopt.nullPrint = NULL; - printfPQExpBuffer(&buf, _("Access privileges for database \"%s\""), PQdb(pset.db)); - myopt.title = buf.data; ! printQuery(res, &myopt, pset.queryFout); ! ! termPQExpBuffer(&buf); ! PQclear(res); ! return true; } - - /* ! * Get object comments ! * ! * \dd [foo] * ! * Note: This only lists things that actually have a description. For complete ! * lists of things, there are other \d? commands. */ bool ! objectDescription(const char *pattern) { ! PQExpBufferData buf; PGresult *res; printQueryOpt myopt = pset.popt; - - initPQExpBuffer(&buf); - - appendPQExpBuffer(&buf, - "SELECT DISTINCT tt.nspname AS \"%s\", tt.name AS \"%s\", tt.object AS \"%s\", d.description AS \"%s\"\n" - "FROM (\n", - _("Schema"), _("Name"), _("Object"), _("Description")); - - /* Aggregate descriptions */ - appendPQExpBuffer(&buf, - " SELECT p.oid as oid, p.tableoid as tableoid,\n" - " n.nspname as nspname,\n" - " CAST(p.proname AS pg_catalog.text) as name," - " CAST('%s' AS pg_catalog.text) as object\n" - " FROM pg_catalog.pg_proc p\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n" - " WHERE p.proisagg\n", - _("aggregate")); - processNamePattern(&buf, pattern, true, false, - "n.nspname", "p.proname", NULL, - "pg_catalog.pg_function_is_visible(p.oid)"); - - /* Function descriptions (except in/outs for datatypes) */ - appendPQExpBuffer(&buf, - "UNION ALL\n" - " SELECT p.oid as oid, p.tableoid as tableoid,\n" - " n.nspname as nspname,\n" - " CAST(p.proname AS pg_catalog.text) as name," - " CAST('%s' AS pg_catalog.text) as object\n" - " FROM pg_catalog.pg_proc p\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n" - - " WHERE p.prorettype <> 'pg_catalog.cstring'::pg_catalog.regtype\n" - " AND p.proargtypes[0] <> 'pg_catalog.cstring'::pg_catalog.regtype\n" - " AND NOT p.proisagg\n", - _("function")); - processNamePattern(&buf, pattern, true, false, - "n.nspname", "p.proname", NULL, - "pg_catalog.pg_function_is_visible(p.oid)"); - - /* Operator descriptions (only if operator has its own comment) */ - appendPQExpBuffer(&buf, - "UNION ALL\n" - " SELECT o.oid as oid, o.tableoid as tableoid,\n" - " n.nspname as nspname,\n" - " CAST(o.oprname AS pg_catalog.text) as name," - " CAST('%s' AS pg_catalog.text) as object\n" - " FROM pg_catalog.pg_operator o\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace\n", - _("operator")); - processNamePattern(&buf, pattern, false, false, - "n.nspname", "o.oprname", NULL, - "pg_catalog.pg_operator_is_visible(o.oid)"); - - /* Type description */ - appendPQExpBuffer(&buf, - "UNION ALL\n" - " SELECT t.oid as oid, t.tableoid as tableoid,\n" - " n.nspname as nspname,\n" - " pg_catalog.format_type(t.oid, NULL) as name," - " CAST('%s' AS pg_catalog.text) as object\n" - " FROM pg_catalog.pg_type t\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n", - _("data type")); - processNamePattern(&buf, pattern, false, false, - "n.nspname", "pg_catalog.format_type(t.oid, NULL)", NULL, - "pg_catalog.pg_type_is_visible(t.oid)"); - - /* Relation (tables, views, indexes, sequences) descriptions */ - appendPQExpBuffer(&buf, - "UNION ALL\n" - " SELECT c.oid as oid, c.tableoid as tableoid,\n" - " n.nspname as nspname,\n" - " CAST(c.relname AS pg_catalog.text) as name,\n" - " CAST(\n" - " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' END" - " AS pg_catalog.text) as object\n" - " FROM pg_catalog.pg_class c\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" - " WHERE c.relkind IN ('r', 'v', 'i', 'S')\n", - _("table"), _("view"), _("index"), _("sequence")); - processNamePattern(&buf, pattern, true, false, - "n.nspname", "c.relname", NULL, - "pg_catalog.pg_table_is_visible(c.oid)"); - - /* Rule description (ignore rules for views) */ - appendPQExpBuffer(&buf, - "UNION ALL\n" - " SELECT r.oid as oid, r.tableoid as tableoid,\n" - " n.nspname as nspname,\n" - " CAST(r.rulename AS pg_catalog.text) as name," - " CAST('%s' AS pg_catalog.text) as object\n" - " FROM pg_catalog.pg_rewrite r\n" - " JOIN pg_catalog.pg_class c ON c.oid = r.ev_class\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" - " WHERE r.rulename != '_RETURN'\n", - _("rule")); - /* XXX not sure what to do about visibility rule here? */ - processNamePattern(&buf, pattern, true, false, - "n.nspname", "r.rulename", NULL, - "pg_catalog.pg_table_is_visible(c.oid)"); - - /* Trigger description */ - appendPQExpBuffer(&buf, - "UNION ALL\n" - " SELECT t.oid as oid, t.tableoid as tableoid,\n" - " n.nspname as nspname,\n" - " CAST(t.tgname AS pg_catalog.text) as name," - " CAST('%s' AS pg_catalog.text) as object\n" - " FROM pg_catalog.pg_trigger t\n" - " JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n", - _("trigger")); - /* XXX not sure what to do about visibility rule here? */ - processNamePattern(&buf, pattern, false, false, - "n.nspname", "t.tgname", NULL, - "pg_catalog.pg_table_is_visible(c.oid)"); - - appendPQExpBuffer(&buf, - ") AS tt\n" - " JOIN pg_catalog.pg_description d ON (tt.oid = d.objoid and tt.tableoid = d.classoid and d.objsubid = 0)\n"); ! appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3;"); ! ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! ! myopt.nullPrint = NULL; ! myopt.title = _("Object descriptions"); ! ! printQuery(res, &myopt, pset.queryFout); ! ! PQclear(res); ! return true; ! } ! ! ! ! /* ! * describeTableDetails (for \d) ! * ! * This routine finds the tables to be displayed, and calls ! * describeOneTableDetails for each one. ! */ ! bool ! describeTableDetails(const char *pattern, bool verbose) ! { ! PQExpBufferData buf; ! PGresult *res; ! int i; ! ! initPQExpBuffer(&buf); ! ! printfPQExpBuffer(&buf, ! "SELECT c.oid,\n" ! " n.nspname,\n" ! " c.relname\n" ! "FROM pg_catalog.pg_class c\n" ! " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"); ! ! processNamePattern(&buf, pattern, false, false, ! "n.nspname", "c.relname", NULL, ! "pg_catalog.pg_table_is_visible(c.oid)"); ! ! appendPQExpBuffer(&buf, "ORDER BY 2, 3;"); ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; - if (PQntuples(res) == 0) - { - if (!QUIET()) - fprintf(stderr, _("Did not find any relation named \"%s\".\n"), - pattern); PQclear(res); - return false; - } ! for (i = 0; i < PQntuples(res); i++) ! { ! const char *oid; ! const char *nspname; ! const char *relname; ! ! oid = PQgetvalue(res, i, 0); ! nspname = PQgetvalue(res, i, 1); ! relname = PQgetvalue(res, i, 2); ! ! if (!describeOneTableDetails(nspname, relname, oid, verbose)) ! { ! PQclear(res); ! return false; ! } } ! PQclear(res); ! return true; } /* ! * describeOneTableDetails (for \d) * ! * Unfortunately, the information presented here is so complicated that it ! * cannot be done in a single query. So we have to assemble the printed table ! * by hand and pass it to the underlying printTable() function. */ static bool ! describeOneTableDetails(const char *schemaname, ! const char *relationname, ! const char *oid, ! bool verbose) { PQExpBufferData buf; ! PGresult *res = NULL; ! printTableOpt myopt = pset.popt.topt; int i; ! char *view_def = NULL; ! const char *headers[5]; ! char **cells = NULL; ! char **footers = NULL; ! char **ptr; ! PQExpBufferData title; ! PQExpBufferData tmpbuf; ! int cols = 0; ! int numrows = 0; ! struct ! { ! bool hasindex; ! char relkind; ! int16 checks; ! int16 triggers; ! bool hasrules; ! } tableinfo; ! bool show_modifiers = false; ! bool retval; ! ! retval = false; ! ! initPQExpBuffer(&buf); ! initPQExpBuffer(&title); ! initPQExpBuffer(&tmpbuf); ! ! /* Get general table info */ ! printfPQExpBuffer(&buf, ! "SELECT relhasindex, relkind, relchecks, reltriggers, relhasrules\n" ! "FROM pg_catalog.pg_class WHERE oid = '%s'", ! oid); ! res = PSQLexec(buf.data, false); ! if (!res) ! goto error_return; ! ! /* Did we get anything? */ ! if (PQntuples(res) == 0) ! { ! if (!QUIET()) ! fprintf(stderr, _("Did not find any relation with oid %s.\n"), ! oid); ! goto error_return; ! } ! ! /* FIXME: check for null pointers here? */ ! tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 0), "t") == 0; ! tableinfo.relkind = *(PQgetvalue(res, 0, 1)); ! tableinfo.checks = atoi(PQgetvalue(res, 0, 2)); ! tableinfo.triggers = atoi(PQgetvalue(res, 0, 3)); ! tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 4), "t") == 0; ! PQclear(res); ! ! headers[0] = _("Column"); ! headers[1] = _("Type"); ! cols = 2; ! ! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v') ! { ! show_modifiers = true; ! cols++; ! headers[cols - 1] = _("Modifiers"); ! } ! ! if (verbose) ! { ! cols++; ! headers[cols - 1] = _("Description"); ! } ! ! headers[cols] = NULL; ! ! /* Get column info (index requires additional checks) */ ! if (tableinfo.relkind == 'i') ! printfPQExpBuffer(&buf, "SELECT\n CASE i.indproc WHEN ('-'::pg_catalog.regproc) THEN a.attname\n ELSE SUBSTR(pg_catalog.pg_get_indexdef(attrelid),\n POSITION('(' in pg_catalog.pg_get_indexdef(attrelid)))\n END,"); ! else ! printfPQExpBuffer(&buf, "SELECT a.attname,"); ! appendPQExpBuffer(&buf, "\n pg_catalog.format_type(a.atttypid, a.atttypmod)," ! "\n (SELECT substring(d.adsrc for 128) FROM pg_catalog.pg_attrdef d" ! "\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)," ! "\n a.attnotnull, a.attnum"); ! if (verbose) ! appendPQExpBuffer(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)"); ! appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_attribute a"); ! if (tableinfo.relkind == 'i') ! appendPQExpBuffer(&buf, ", pg_catalog.pg_index i"); ! appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid); ! if (tableinfo.relkind == 'i') ! appendPQExpBuffer(&buf, " AND a.attrelid = i.indexrelid"); ! appendPQExpBuffer(&buf, "\nORDER BY a.attnum"); ! ! res = PSQLexec(buf.data, false); ! if (!res) ! goto error_return; ! numrows = PQntuples(res); ! ! /* Check if table is a view */ ! if (tableinfo.relkind == 'v') ! { ! PGresult *result; ! ! printfPQExpBuffer(&buf, "SELECT pg_catalog.pg_get_viewdef('%s'::pg_catalog.oid)", oid); ! result = PSQLexec(buf.data, false); ! if (!result) ! goto error_return; ! ! if (PQntuples(result) > 0) ! view_def = xstrdup(PQgetvalue(result, 0, 0)); ! ! PQclear(result); ! } ! ! /* Generate table cells to be printed */ ! /* note: initialize all cells[] to NULL in case of error exit */ ! cells = xmalloczero((numrows * cols + 1) * sizeof(*cells)); ! ! for (i = 0; i < numrows; i++) ! { ! /* Name */ ! cells[i * cols + 0] = PQgetvalue(res, i, 0); /* don't free this ! * afterwards */ ! /* Type */ ! cells[i * cols + 1] = PQgetvalue(res, i, 1); /* don't free this ! * either */ ! ! /* Extra: not null and default */ ! if (show_modifiers) ! { ! resetPQExpBuffer(&tmpbuf); ! if (strcmp(PQgetvalue(res, i, 3), "t") == 0) ! appendPQExpBufferStr(&tmpbuf, "not null"); ! ! /* handle "default" here */ ! /* (note: above we cut off the 'default' string at 128) */ ! if (strlen(PQgetvalue(res, i, 2)) != 0) ! { ! if (tmpbuf.len > 0) ! appendPQExpBufferStr(&tmpbuf, " "); ! appendPQExpBuffer(&tmpbuf, "default %s", ! PQgetvalue(res, i, 2)); ! } ! cells[i * cols + 2] = xstrdup(tmpbuf.data); ! } ! /* Description */ ! if (verbose) ! cells[i * cols + cols - 1] = PQgetvalue(res, i, 5); ! } ! /* Make title */ ! switch (tableinfo.relkind) ! { ! case 'r': ! printfPQExpBuffer(&title, _("Table \"%s.%s\""), ! schemaname, relationname); ! break; ! case 'v': ! printfPQExpBuffer(&title, _("View \"%s.%s\""), ! schemaname, relationname); ! break; ! case 'S': ! printfPQExpBuffer(&title, _("Sequence \"%s.%s\""), ! schemaname, relationname); ! break; ! case 'i': ! printfPQExpBuffer(&title, _("Index \"%s.%s\""), ! schemaname, relationname); ! break; ! case 's': ! printfPQExpBuffer(&title, _("Special relation \"%s.%s\""), ! schemaname, relationname); ! break; ! case 't': ! printfPQExpBuffer(&title, _("TOAST table \"%s.%s\""), ! schemaname, relationname); ! break; ! case 'c': ! printfPQExpBuffer(&title, _("Composite type \"%s.%s\""), ! schemaname, relationname); ! break; ! default: ! printfPQExpBuffer(&title, _("?%c? \"%s.%s\""), ! tableinfo.relkind, schemaname, relationname); ! break; ! } ! /* Make footers */ ! if (tableinfo.relkind == 'i') { ! /* Footer information about an index */ ! PGresult *result; printfPQExpBuffer(&buf, ! "SELECT i.indisunique, i.indisprimary, a.amname, c2.relname,\n" ! " pg_catalog.pg_get_expr(i.indpred, i.indrelid)\n" ! "FROM pg_catalog.pg_index i, pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_am a\n" ! "WHERE i.indexrelid = c.oid AND c.oid = '%s' AND c.relam = a.oid\n" ! "AND i.indrelid = c2.oid", ! oid); ! ! result = PSQLexec(buf.data, false); ! if (!result) ! goto error_return; ! else if (PQntuples(result) != 1) ! { ! PQclear(result); ! goto error_return; ! } ! else ! { ! char *indisunique = PQgetvalue(result, 0, 0); ! char *indisprimary = PQgetvalue(result, 0, 1); ! char *indamname = PQgetvalue(result, 0, 2); ! char *indtable = PQgetvalue(result, 0, 3); ! char *indpred = PQgetvalue(result, 0, 4); ! ! if (strcmp(indisprimary, "t") == 0) ! printfPQExpBuffer(&tmpbuf, _("primary key, ")); ! else if (strcmp(indisunique, "t") == 0) ! printfPQExpBuffer(&tmpbuf, _("unique, ")); ! else ! resetPQExpBuffer(&tmpbuf); ! appendPQExpBuffer(&tmpbuf, "%s, ", indamname); ! ! /* we assume here that index and table are in same schema */ ! appendPQExpBuffer(&tmpbuf, _("for table \"%s.%s\""), ! schemaname, indtable); ! ! if (strlen(indpred)) ! appendPQExpBuffer(&tmpbuf, ", predicate %s", indpred); ! ! footers = xmalloczero(2 * sizeof(*footers)); ! footers[0] = xstrdup(tmpbuf.data); ! footers[1] = NULL; ! } ! ! PQclear(result); ! } ! else if (view_def) ! { ! PGresult *result = NULL; ! int rule_count = 0; ! int count_footers = 0; ! ! /* count rules other than the view rule */ ! if (tableinfo.hasrules) ! { ! printfPQExpBuffer(&buf, ! "SELECT r.rulename\n" ! "FROM pg_catalog.pg_rewrite r\n" ! "WHERE r.ev_class = '%s' AND r.rulename != '_RETURN'", ! oid); ! result = PSQLexec(buf.data, false); ! if (!result) ! goto error_return; ! else ! rule_count = PQntuples(result); ! } ! ! /* Footer information about a view */ ! footers = xmalloczero((rule_count + 2) * sizeof(*footers)); ! footers[count_footers] = xmalloc(64 + strlen(view_def)); ! snprintf(footers[count_footers], 64 + strlen(view_def), ! _("View definition: %s"), view_def); ! count_footers++; ! ! /* print rules */ ! for (i = 0; i < rule_count; i++) ! { ! char *s = _("Rules"); ! ! if (i == 0) ! printfPQExpBuffer(&buf, "%s: %s", s, PQgetvalue(result, i, 0)); ! else ! printfPQExpBuffer(&buf, "%*s %s", (int) strlen(s), "", PQgetvalue(result, i, 0)); ! if (i < rule_count - 1) ! appendPQExpBuffer(&buf, ","); ! ! footers[count_footers++] = xstrdup(buf.data); ! } ! PQclear(result); ! footers[count_footers] = NULL; ! } ! else if (tableinfo.relkind == 'r') ! { ! /* Footer information about a table */ ! PGresult *result1 = NULL, ! *result2 = NULL, ! *result3 = NULL, ! *result4 = NULL, ! *result5 = NULL; ! int check_count = 0, ! index_count = 0, ! foreignkey_count = 0, ! rule_count = 0, ! trigger_count = 0; ! int count_footers = 0; ! /* count indexes */ ! if (tableinfo.hasindex) ! { ! printfPQExpBuffer(&buf, ! "SELECT c2.relname, i.indisprimary, i.indisunique, " ! "pg_catalog.pg_get_indexdef(i.indexrelid)\n" ! "FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i\n" ! "WHERE c.oid = '%s' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n" ! "ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname", ! oid); ! result1 = PSQLexec(buf.data, false); ! if (!result1) ! goto error_return; ! else ! index_count = PQntuples(result1); ! } ! /* count table (and column) check constraints */ ! if (tableinfo.checks) ! { ! printfPQExpBuffer(&buf, ! "SELECT consrc, conname\n" ! "FROM pg_catalog.pg_constraint r\n" ! "WHERE r.conrelid = '%s' AND r.contype = 'c'", ! oid); ! result2 = PSQLexec(buf.data, false); ! if (!result2) ! goto error_return; ! else ! check_count = PQntuples(result2); ! } ! /* count rules */ ! if (tableinfo.hasrules) { ! printfPQExpBuffer(&buf, ! "SELECT r.rulename\n" ! "FROM pg_catalog.pg_rewrite r\n" ! "WHERE r.ev_class = '%s'", ! oid); ! result3 = PSQLexec(buf.data, false); ! if (!result3) ! goto error_return; ! else ! rule_count = PQntuples(result3); ! } ! /* count triggers (but ignore foreign-key triggers) */ ! if (tableinfo.triggers) ! { ! printfPQExpBuffer(&buf, ! "SELECT t.tgname\n" ! "FROM pg_catalog.pg_trigger t\n" ! "WHERE t.tgrelid = '%s' " ! "and (not tgisconstraint " ! " OR NOT EXISTS" ! " (SELECT 1 FROM pg_catalog.pg_depend d " ! " JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) " ! " WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))", ! oid); ! result4 = PSQLexec(buf.data, false); ! if (!result4) ! goto error_return; ! else ! trigger_count = PQntuples(result4); } ! /* count foreign-key constraints (there are none if no triggers) */ ! if (tableinfo.triggers) ! { ! printfPQExpBuffer(&buf, ! "SELECT conname,\n" ! " pg_catalog.pg_get_constraintdef(oid) as condef\n" ! "FROM pg_catalog.pg_constraint r\n" ! "WHERE r.conrelid = '%s' AND r.contype = 'f'", ! oid); ! result5 = PSQLexec(buf.data, false); ! if (!result5) ! goto error_return; ! else ! foreignkey_count = PQntuples(result5); ! } ! footers = xmalloczero((index_count + check_count + rule_count + trigger_count + foreignkey_count + 1) ! * sizeof(*footers)); ! /* print indexes */ ! for (i = 0; i < index_count; i++) { ! char *s = _("Indexes"); ! const char *indexdef; ! const char *usingpos; ! ! if (i == 0) ! printfPQExpBuffer(&buf, "%s: %s", s, ! PQgetvalue(result1, i, 0)); ! else ! printfPQExpBuffer(&buf, "%*s %s", (int) strlen(s), "", ! PQgetvalue(result1, i, 0)); ! ! /* Label as primary key or unique (but not both) */ ! appendPQExpBuffer(&buf, ! strcmp(PQgetvalue(result1, i, 1), "t") == 0 ! ? _(" primary key") : ! (strcmp(PQgetvalue(result1, i, 2), "t") == 0 ! ? _(" unique") ! : "")); ! ! /* Everything after "USING" is echoed verbatim */ ! indexdef = PQgetvalue(result1, i, 3); ! usingpos = strstr(indexdef, " USING "); ! if (usingpos) ! indexdef = usingpos + 7; ! ! appendPQExpBuffer(&buf, " %s", indexdef); ! if (i < index_count - 1) ! appendPQExpBuffer(&buf, ","); ! footers[count_footers++] = xstrdup(buf.data); ! } ! /* print check constraints */ ! for (i = 0; i < check_count; i++) ! { ! char *s = _("Check constraints"); ! ! if (i == 0) ! printfPQExpBuffer(&buf, _("%s: \"%s\" %s"), ! s, ! PQgetvalue(result2, i, 1), ! PQgetvalue(result2, i, 0)); ! else ! printfPQExpBuffer(&buf, _("%*s \"%s\" %s"), ! (int) strlen(s), "", ! PQgetvalue(result2, i, 1), ! PQgetvalue(result2, i, 0)); ! footers[count_footers++] = xstrdup(buf.data); } ! /* print foreign key constraints */ ! for (i = 0; i < foreignkey_count; i++) { ! char *s = _("Foreign Key constraints"); ! if (i == 0) ! printfPQExpBuffer(&buf, _("%s: %s %s"), ! s, ! PQgetvalue(result5, i, 0), ! PQgetvalue(result5, i, 1)); ! else ! printfPQExpBuffer(&buf, _("%*s %s %s"), ! (int) strlen(s), "", ! PQgetvalue(result5, i, 0), ! PQgetvalue(result5, i, 1)); ! if (i < foreignkey_count - 1) ! appendPQExpBuffer(&buf, ","); ! footers[count_footers++] = xstrdup(buf.data); ! } ! /* print rules */ ! for (i = 0; i < rule_count; i++) ! { ! char *s = _("Rules"); ! if (i == 0) ! printfPQExpBuffer(&buf, "%s: %s", s, PQgetvalue(result3, i, 0)); ! else ! printfPQExpBuffer(&buf, "%*s %s", (int) strlen(s), "", PQgetvalue(result3, i, 0)); ! if (i < rule_count - 1) ! appendPQExpBuffer(&buf, ","); ! footers[count_footers++] = xstrdup(buf.data); } ! /* print triggers */ ! for (i = 0; i < trigger_count; i++) ! { ! char *s = _("Triggers"); ! if (i == 0) ! printfPQExpBuffer(&buf, "%s: %s", s, PQgetvalue(result4, i, 0)); ! else ! printfPQExpBuffer(&buf, "%*s %s", (int) strlen(s), "", PQgetvalue(result4, i, 0)); ! if (i < trigger_count - 1) ! appendPQExpBuffer(&buf, ","); ! footers[count_footers++] = xstrdup(buf.data); ! } ! /* end of list marker */ ! footers[count_footers] = NULL; ! PQclear(result1); ! PQclear(result2); ! PQclear(result3); ! PQclear(result4); ! PQclear(result5); } ! printTable(title.data, headers, ! (const char **) cells, (const char **) footers, ! "llll", &myopt, pset.queryFout); ! retval = true; ! error_return: ! /* clean up */ ! termPQExpBuffer(&buf); ! termPQExpBuffer(&title); ! termPQExpBuffer(&tmpbuf); ! if (cells) ! { ! for (i = 0; i < numrows; i++) ! { ! if (show_modifiers) ! free(cells[i * cols + 2]); } ! free(cells); ! } ! ! if (footers) ! { ! for (ptr = footers; *ptr; ptr++) ! free(*ptr); ! free(footers); } ! if (view_def) ! free(view_def); ! ! if (res) ! PQclear(res); ! return retval; } - /* ! * \du * ! * Describes users. Any schema portion of the pattern is ignored. ! */ ! bool ! describeUsers(const char *pattern) { ! PQExpBufferData buf; ! PGresult *res; ! printQueryOpt myopt = pset.popt; ! initPQExpBuffer(&buf); ! printfPQExpBuffer(&buf, ! "SELECT u.usename AS \"%s\",\n" ! " u.usesysid AS \"%s\",\n" ! " CASE WHEN u.usesuper AND u.usecreatedb THEN CAST('%s' AS pg_catalog.text)\n" ! " WHEN u.usesuper THEN CAST('%s' AS pg_catalog.text)\n" ! " WHEN u.usecreatedb THEN CAST('%s' AS pg_catalog.text)\n" ! " ELSE CAST('' AS pg_catalog.text)\n" ! " END AS \"%s\"\n" ! "FROM pg_catalog.pg_user u\n", ! _("User name"), _("User ID"), ! _("superuser, create database"), ! _("superuser"), _("create database"), ! _("Attributes")); ! processNamePattern(&buf, pattern, false, false, ! NULL, "u.usename", NULL, NULL); ! appendPQExpBuffer(&buf, "ORDER BY 1;"); ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! ! myopt.nullPrint = NULL; ! myopt.title = _("List of database users"); ! printQuery(res, &myopt, pset.queryFout); ! PQclear(res); ! return true; } /* ! * listTables() * ! * handler for \d, \dt, etc. ! * ! * tabtypes is an array of characters, specifying what info is desired: ! * t - tables ! * i - indexes ! * v - views ! * s - sequences ! * S - system tables (pg_catalog) ! * (any order of the above is fine) */ ! bool ! listTables(const char *tabtypes, const char *pattern, bool verbose) { ! bool showTables = strchr(tabtypes, 't') != NULL; ! bool showIndexes = strchr(tabtypes, 'i') != NULL; ! bool showViews = strchr(tabtypes, 'v') != NULL; ! bool showSeq = strchr(tabtypes, 's') != NULL; ! bool showSystem = strchr(tabtypes, 'S') != NULL; ! PQExpBufferData buf; ! PGresult *res; ! printQueryOpt myopt = pset.popt; ! ! if (!(showTables || showIndexes || showViews || showSeq)) ! showTables = showViews = showSeq = true; ! ! initPQExpBuffer(&buf); ! ! printfPQExpBuffer(&buf, ! "SELECT n.nspname as \"%s\",\n" ! " c.relname as \"%s\",\n" ! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' WHEN 's' THEN '%s' END as \"%s\",\n" ! " u.usename as \"%s\"", ! _("Schema"), _("Name"), ! _("table"), _("view"), _("index"), _("sequence"), ! _("special"), _("Type"), _("Owner")); ! ! if (verbose) ! appendPQExpBuffer(&buf, ! ",\n pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"", ! _("Description")); ! ! if (showIndexes) ! appendPQExpBuffer(&buf, ! ",\n c2.relname as \"%s\"" ! "\nFROM pg_catalog.pg_class c" ! "\n JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid" ! "\n JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid" ! "\n LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner" ! "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n", ! _("Table")); ! else ! appendPQExpBuffer(&buf, ! "\nFROM pg_catalog.pg_class c" ! "\n LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner" ! "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"); ! ! appendPQExpBuffer(&buf, "WHERE c.relkind IN ("); ! if (showTables) ! appendPQExpBuffer(&buf, "'r',"); ! if (showViews) ! appendPQExpBuffer(&buf, "'v',"); ! if (showIndexes) ! appendPQExpBuffer(&buf, "'i',"); ! if (showSeq) ! appendPQExpBuffer(&buf, "'S',"); ! if (showSystem && showTables) ! appendPQExpBuffer(&buf, "'s',"); ! appendPQExpBuffer(&buf, "''"); /* dummy */ ! appendPQExpBuffer(&buf, ")\n"); ! ! /* ! * If showSystem is specified, show only system objects (those in ! * pg_catalog). Otherwise, suppress system objects, including ! * those in pg_catalog and pg_toast. (We don't want to hide temp ! * tables though.) ! */ ! if (showSystem) ! appendPQExpBuffer(&buf, " AND n.nspname = 'pg_catalog'\n"); ! else ! appendPQExpBuffer(&buf, " AND n.nspname NOT IN ('pg_catalog', 'pg_toast')\n"); ! ! processNamePattern(&buf, pattern, true, false, ! "n.nspname", "c.relname", NULL, ! "pg_catalog.pg_table_is_visible(c.oid)"); ! appendPQExpBuffer(&buf, "ORDER BY 1,2;"); ! ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! ! if (PQntuples(res) == 0 && !QUIET()) { ! if (pattern) ! fprintf(pset.queryFout, _("No matching relations found.\n")); ! else ! fprintf(pset.queryFout, _("No relations found.\n")); } - else - { - myopt.nullPrint = NULL; - myopt.title = _("List of relations"); ! printQuery(res, &myopt, pset.queryFout); ! } ! ! PQclear(res); ! return true; } ! ! /* ! * \dD ! * ! * Describes domains. ! */ ! bool ! listDomains(const char *pattern) { ! PQExpBufferData buf; ! PGresult *res; ! printQueryOpt myopt = pset.popt; ! initPQExpBuffer(&buf); ! printfPQExpBuffer(&buf, ! "SELECT n.nspname as \"%s\",\n" ! " t.typname as \"%s\",\n" ! " pg_catalog.format_type(t.typbasetype, t.typtypmod) as \"%s\",\n" ! " CASE WHEN t.typnotnull AND t.typdefault IS NOT NULL THEN 'not null default '||t.typdefault\n" ! " WHEN t.typnotnull AND t.typdefault IS NULL THEN 'not null'\n" ! " WHEN NOT t.typnotnull AND t.typdefault IS NOT NULL THEN 'default '||t.typdefault\n" ! " ELSE ''\n" ! " END as \"%s\"\n" ! "FROM pg_catalog.pg_type t\n" ! " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n" ! "WHERE t.typtype = 'd'\n", ! _("Schema"), ! _("Name"), ! _("Type"), ! _("Modifier")); ! ! processNamePattern(&buf, pattern, true, false, ! "n.nspname", "t.typname", NULL, ! "pg_catalog.pg_type_is_visible(t.oid)"); ! appendPQExpBuffer(&buf, "ORDER BY 1, 2;"); ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) return false; - - myopt.nullPrint = NULL; - myopt.title = _("List of domains"); - - printQuery(res, &myopt, pset.queryFout); - - PQclear(res); - return true; - } - - /* - * \dc - * - * Describes conversions. - */ - bool - listConversions(const char *pattern) - { - PQExpBufferData buf; - PGresult *res; - printQueryOpt myopt = pset.popt; - - initPQExpBuffer(&buf); - - printfPQExpBuffer(&buf, - "SELECT n.nspname AS \"%s\",\n" - " c.conname AS \"%s\",\n" - " pg_catalog.pg_encoding_to_char(c.conforencoding) AS \"%s\",\n" - " pg_catalog.pg_encoding_to_char(c.contoencoding) AS \"%s\",\n" - " CASE WHEN c.condefault THEN '%s'\n" - " ELSE '%s' END AS \"%s\"\n" - "FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n\n" - "WHERE n.oid = c.connamespace\n", - _("Schema"), - _("Name"), - _("Source"), - _("Destination"), - _("yes"), - _("no"), - _("Default?")); - - processNamePattern(&buf, pattern, true, false, - "n.nspname", "c.conname", NULL, - "pg_catalog.pg_conversion_is_visible(c.oid)"); - - appendPQExpBuffer(&buf, "ORDER BY 1, 2;"); ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) return false; ! myopt.nullPrint = NULL; ! myopt.title = _("List of conversions"); ! printQuery(res, &myopt, pset.queryFout); ! PQclear(res); return true; } ! /* ! * \dC ! * ! * Describes casts. ! */ ! bool ! listCasts(const char *pattern) { ! PQExpBufferData buf; ! PGresult *res; ! printQueryOpt myopt = pset.popt; ! initPQExpBuffer(&buf); ! /* NEED LEFT JOIN FOR BINARY CASTS */ ! printfPQExpBuffer(&buf, ! "SELECT pg_catalog.format_type(castsource, NULL) AS \"%s\",\n" ! " pg_catalog.format_type(casttarget, NULL) AS \"%s\",\n" ! " CASE WHEN castfunc = 0 THEN '%s'\n" ! " ELSE p.proname\n" ! " END as \"%s\",\n" ! " CASE WHEN c.castcontext = 'e' THEN '%s'\n" ! " WHEN c.castcontext = 'a' THEN '%s'\n" ! " ELSE '%s'\n" ! " END as \"%s\"\n" ! "FROM pg_catalog.pg_cast c LEFT JOIN pg_catalog.pg_proc p\n" ! " ON c.castfunc = p.oid\n" ! "ORDER BY 1, 2", ! _("Source"), ! _("Target"), ! _("BINARY"), ! _("Function"), ! _("no"), ! _("in assignment"), ! _("yes"), ! _("Implicit?")); ! ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! ! myopt.nullPrint = NULL; ! myopt.title = _("List of casts"); ! ! printQuery(res, &myopt, pset.queryFout); ! ! PQclear(res); ! return true; } ! /* ! * \dn ! * ! * Describes schemas (namespaces) ! */ ! bool ! listSchemas(const char *pattern) { ! PQExpBufferData buf; ! PGresult *res; ! printQueryOpt myopt = pset.popt; ! initPQExpBuffer(&buf); ! printfPQExpBuffer(&buf, ! "SELECT n.nspname AS \"%s\",\n" ! " u.usename AS \"%s\"\n" ! "FROM pg_catalog.pg_namespace n LEFT JOIN pg_catalog.pg_user u\n" ! " ON n.nspowner=u.usesysid\n", ! _("Name"), ! _("Owner")); ! ! processNamePattern(&buf, pattern, false, false, ! NULL, "n.nspname", NULL, ! NULL); ! ! appendPQExpBuffer(&buf, "ORDER BY 1;"); ! ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! ! myopt.nullPrint = NULL; ! myopt.title = _("List of schemas"); ! ! printQuery(res, &myopt, pset.queryFout); ! ! PQclear(res); ! return true; } - - /* - * processNamePattern - * - * Scan a wildcard-pattern option and generate appropriate WHERE clauses - * to limit the set of objects returned. The WHERE clauses are appended - * to buf. - * - * pattern: user-specified pattern option to a \d command, or NULL if none. - * have_where: true if caller already emitted WHERE. - * force_escape: always quote regexp special characters, even outside quotes. - * schemavar: name of WHERE variable to match against a schema-name pattern. - * Can be NULL if no schema. - * namevar: name of WHERE variable to match against an object-name pattern. - * altnamevar: NULL, or name of an alternate variable to match against name. - * visibilityrule: clause to use if we want to restrict to visible objects - * (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL. - */ static void ! processNamePattern(PQExpBuffer buf, const char *pattern, ! bool have_where, bool force_escape, ! const char *schemavar, const char *namevar, ! const char *altnamevar, const char *visibilityrule) { ! PQExpBufferData schemabuf; ! PQExpBufferData namebuf; ! bool inquotes; ! const char *cp; ! int i; ! #define WHEREAND() \ ! (appendPQExpBuffer(buf, have_where ? " AND " : "WHERE "), have_where = true) ! ! if (pattern == NULL) ! { ! /* Default: select all visible objects */ ! if (visibilityrule) ! { ! WHEREAND(); ! appendPQExpBuffer(buf, "%s\n", visibilityrule); ! } return; - } - - initPQExpBuffer(&schemabuf); - initPQExpBuffer(&namebuf); - - /* - * Parse the pattern, converting quotes and lower-casing unquoted - * letters; we assume this was NOT done by scan_option. Also, adjust - * shell-style wildcard characters into regexp notation. - */ - inquotes = false; - cp = pattern; - - while (*cp) - { - if (*cp == '"') - { - if (inquotes && cp[1] == '"') - { - /* emit one quote */ - appendPQExpBufferChar(&namebuf, '"'); - cp++; - } - inquotes = !inquotes; - cp++; - } - else if (!inquotes && isupper((unsigned char) *cp)) - { - appendPQExpBufferChar(&namebuf, - tolower((unsigned char) *cp)); - cp++; - } - else if (!inquotes && *cp == '*') - { - appendPQExpBuffer(&namebuf, ".*"); - cp++; - } - else if (!inquotes && *cp == '?') - { - appendPQExpBufferChar(&namebuf, '.'); - cp++; - } - else if (!inquotes && *cp == '.') - { - /* Found schema/name separator, move current pattern to schema */ - resetPQExpBuffer(&schemabuf); - appendPQExpBufferStr(&schemabuf, namebuf.data); - resetPQExpBuffer(&namebuf); - cp++; - } - else - { - /* - * Ordinary data character, transfer to pattern - * - * Inside double quotes, or at all times if parsing an operator - * name, quote regexp special characters with a backslash to - * avoid regexp errors. Outside quotes, however, let them - * pass through as-is; this lets knowledgeable users build - * regexp expressions that are more powerful than shell-style - * patterns. - */ - if ((inquotes || force_escape) && - strchr("|*+?()[]{}.^$\\", *cp)) - appendPQExpBuffer(&namebuf, "\\\\"); - - /* Ensure chars special to string literals are passed properly */ - if (*cp == '\'' || *cp == '\\') - appendPQExpBufferChar(&namebuf, *cp); ! i = PQmblen(cp, pset.encoding); ! while (i--) ! { ! appendPQExpBufferChar(&namebuf, *cp); ! cp++; ! } ! } ! } ! ! /* ! * Now decide what we need to emit. ! */ ! if (schemabuf.len > 0) ! { ! /* We have a schema pattern, so constrain the schemavar */ ! ! appendPQExpBufferChar(&schemabuf, '$'); ! /* Optimize away ".*$", and possibly the whole pattern */ ! if (schemabuf.len >= 3 && ! strcmp(schemabuf.data + (schemabuf.len - 3), ".*$") == 0) ! schemabuf.data[schemabuf.len - 3] = '\0'; ! ! if (schemabuf.data[0] && schemavar) ! { ! WHEREAND(); ! appendPQExpBuffer(buf, "%s ~ '^%s'\n", ! schemavar, schemabuf.data); ! } ! } ! else ! { ! /* No schema pattern given, so select only visible objects */ ! if (visibilityrule) ! { ! WHEREAND(); ! appendPQExpBuffer(buf, "%s\n", visibilityrule); ! } ! } ! ! if (namebuf.len > 0) ! { ! /* We have a name pattern, so constrain the namevar(s) */ ! ! appendPQExpBufferChar(&namebuf, '$'); ! /* Optimize away ".*$", and possibly the whole pattern */ ! if (namebuf.len >= 3 && ! strcmp(namebuf.data + (namebuf.len - 3), ".*$") == 0) ! namebuf.data[namebuf.len - 3] = '\0'; ! ! if (namebuf.data[0]) ! { ! WHEREAND(); ! if (altnamevar) ! appendPQExpBuffer(buf, ! "(%s ~ '^%s'\n" ! " OR %s ~ '^%s')\n", ! namevar, namebuf.data, ! altnamevar, namebuf.data); ! else ! appendPQExpBuffer(buf, ! "%s ~ '^%s'\n", ! namevar, namebuf.data); ! } } - - termPQExpBuffer(&schemabuf); - termPQExpBuffer(&namebuf); ! #undef WHEREAND } --- 14,549 ---- #include "common.h" #include "settings.h" #include "print.h" #include + #include ! static bool describeOneTableDetails(char *title, ! char *oid, ! bool verbose); ! static bool dbSearch(char **args, int nargs, PGresult **res, ! PGresult **extraRes, char **title); ! static void initDescCache(void); ! static descCache *getDescCached(const char *cmd, int nargs); ! static bool addToDescCache(const char *cmd, int nargs, ! const char *title, bool hasinfoqry); ! static void removeDescCacheItem(descCache *current); ! static char **dbBuildFooters(PGresult *res); ! /* Simplify malloc */ static void * xmalloc(size_t size) { ! void *tmp; ! ! tmp = malloc(size); ! if (!tmp) ! { ! psql_error("out of memory\n"); ! exit(EXIT_FAILURE); ! } ! return tmp; } static void * xmalloczero(size_t size) { ! void *tmp; ! ! tmp = xmalloc(size); ! memset(tmp, 0, size); ! return tmp; } ! /* ! * describeTableDetails (for \d) * ! * This routine finds the tables to be displayed, and calls ! * describeOneTableDetails for each one. ! * Finds tables to be displayed based upon a hardcoded ! * lookup command 'INT-tables'. It is anticipated that ! * this query will maintain it's structure in the future. */ bool ! describeTableDetails(char *pattern, bool verbose) { ! char *title; PGresult *res; ! PGresult *extraRes; ! char *args[2]; ! bool ret; + /* Fetch the information that we've been requested to display */ + args[0] = "PSQL-tablelist"; + args[1] = pattern; ! ret = dbSearch(args, 1, &res, &extraRes, &title); ! if (ret) ! { ! int i; ! if (PQntuples(res) == 0) ! { ! if (!QUIET()) ! fprintf(stderr, gettext("Did not find any relation named \"%s\".\n"), ! pattern); ! PQclear(res); ! return false; ! } ! for (i = 0; i < PQntuples(res); i++) ! { ! char *oid; ! char *title; ! oid = PQgetvalue(res, i, 0); ! title = PQgetvalue(res, i, 1); ! if (!describeOneTableDetails(title, oid, verbose)) ! { ! if (extraRes) ! PQclear(extraRes); ! PQclear(res); ! return false; ! } ! } ! PQclear(res); ! if (extraRes) ! PQclear(extraRes); ! return true; ! } ! return false; } /* ! * describeOneTableDetails * ! * Use dbSearch and some hardcoded command strings to fetch and ! * format the results. Very similar to dbPrint, but here we ! * receive a title from the caller rather than fetching it ! * from the database. */ ! static bool ! describeOneTableDetails(char *title, char *oid, bool verbose) { + char *junktitle; PGresult *res; ! PGresult *extraRes = NULL; ! char *args[2]; ! bool ret; printQueryOpt myopt = pset.popt; ! /* Fetch the information that we've been requested to display */ ! args[0] = "PSQL-onerelation"; ! args[1] = oid; ! ret = dbSearch(args, 1, &res, &extraRes, &junktitle); ! if (ret) ! { ! myopt.nullPrint = NULL; ! myopt.title = title; ! if (extraRes) ! myopt.footers = dbBuildFooters(extraRes); ! printQuery(res, &myopt, pset.queryFout); ! /* Cleanup */ ! PQclear(res); ! if (extraRes) ! PQclear(extraRes); } ! return ret; } /* ! * dbPrint * ! * Uses dbSearch to match a backend specific command, and prints it out. */ bool ! dbPrint(char **args, int nargs) { ! char *title; ! bool ret; PGresult *res; + PGresult *extraRes; printQueryOpt myopt = pset.popt; ! /* Fetch the information that we've been requested to display */ ! ret = dbSearch(args, nargs, &res, &extraRes, &title); ! if (ret) ! { ! myopt.nullPrint = NULL; ! myopt.title = gettext(title); ! if (extraRes) ! myopt.footers = dbBuildFooters(extraRes); ! printQuery(res, &myopt, pset.queryFout); PQclear(res); ! if (extraRes) ! PQclear(extraRes); } ! return ret; } /* ! * dbSearch ! * ! * Scan the database to see if there is a backend specific query for ! * the command requested (args). Since all commands must have a \cmd ! * nargs is the number of arguments following the \cmd portion. Used ! * when a hardcoded command has not been found. * ! * retRes will return a freeform information block. ! * ! * retExtraRes will return a (key text, value text) style tuple for use ! * in building a footer list */ static bool ! dbSearch(char **args, int nargs, ! PGresult **retRes, PGresult **retExtraRes, char **title) { PQExpBufferData buf; ! char *cmd = args[0]; ! char esccmd[strlen(cmd) * 2 + 1]; ! char key[strlen(cmd) * 2 + 10]; int i; ! descCache *cached; ! PGresult *res; ! PGresult *extraRes = NULL; ! /* Clean up the input cmd */ ! PQescapeString(esccmd, cmd, strlen(cmd)); ! /* Setup the key for queries */ ! sprintf(key, "%s%s%d", "pg_psql_", esccmd, nargs); ! initPQExpBuffer(&buf); ! /* Query the DB to see if there is a command matching the request */ ! cached = getDescCached(cmd, nargs); ! if (!cached) { ! PGresult *resprep; ! char *fTitle; ! char *fTabQuery; ! char *fInfoQuery = NULL; ! bool hasInfoQuery; printfPQExpBuffer(&buf, ! " SELECT table_title, table_query, info_query \n" ! " FROM pg_tools.psql_commands \n" ! " WHERE nargs = '%d' AND '%s' ~ cmd_expression \n" ! "ORDER BY match_order \n" ! " LIMIT 1", ! nargs, esccmd ! ); ! res = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!res) ! return false; ! if (!PQntuples(res)) ! return false; ! /* Pull out the needed values */ ! fTitle = PQgetvalue(res, 0, 0); ! fTabQuery = PQgetvalue(res, 0, 1); ! hasInfoQuery = ! PQgetisnull(res, 0, 2); ! if (hasInfoQuery) ! fInfoQuery = PQgetvalue(res, 0, 2); ! /* ! * Prepare query (based on above cache entry) ! */ ! printfPQExpBuffer(&buf, "PREPARE \"%s\"(", key); ! /* i <= nargs because cmd is sent, but isn't counted as an arg */ ! for (i = 0; i <= nargs; i++) { ! appendPQExpBuffer(&buf, "text"); ! /* If last arg append ')', otherwise append ',' */ ! appendPQExpBuffer(&buf, "%s", i == nargs ? ")" : ","); } ! appendPQExpBuffer(&buf, " AS \n%s", fTabQuery); ! resprep = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!resprep) ! return false; ! if (hasInfoQuery) { ! /* ! * Prepare Information query (based on above cache entry) ! */ ! printfPQExpBuffer(&buf, "PREPARE \"%sinfo\"(", key); ! /* i <= nargs because cmd is sent, but isn't counted as an arg */ ! for (i = 0; i <= nargs; i++) ! { ! appendPQExpBuffer(&buf, "text"); ! /* If last arg append ')', otherwise append ',' */ ! appendPQExpBuffer(&buf, "%s", i == nargs ? ")" : ","); ! } + appendPQExpBuffer(&buf, " AS \n%s", fInfoQuery); ! resprep = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!resprep) ! return false; } ! /* Build the cache or give up */ ! if (addToDescCache(cmd, nargs, fTitle, hasInfoQuery)) ! cached = getDescCached(cmd, nargs); ! else { ! PQclear(res); ! PQclear(resprep); ! /* Clean-up our prepared quer(ies) */ ! printfPQExpBuffer(&buf, "DEALLOCATE \"%s\"", key, key); ! resprep = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (hasInfoQuery) ! { ! printfPQExpBuffer(&buf, "DEALLOCATE \"%sinfo\"", key, key); ! resprep = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! } ! return false; } + PQclear(res); + PQclear(resprep); + } ! /* Run queries, with arguments (cmd / pattern) */ ! printfPQExpBuffer(&buf, "EXECUTE \"%s\"(", key); ! /* Deal with args */ ! for (i = 0; i <= nargs; i++) ! { ! char esc[strlen(args[i]) * 2 + 1]; ! PQescapeString(esc, args[i], strlen(args[i])); ! appendPQExpBuffer(&buf, "'%s'", esc); ! appendPQExpBuffer(&buf, "%s", i == nargs ? ")" : ","); } + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; ! /* Deal with the extraRes portion */ ! if (cached->hasinfoqry) ! { ! /* Run queries, with arguments (cmd / pattern) */ ! printfPQExpBuffer(&buf, "EXECUTE \"%sinfo\"(", key); ! /* Deal with args */ ! for (i = 0; i <= nargs; i++) ! { ! char esc[strlen(args[i]) * 2 + 1]; ! PQescapeString(esc, args[i], strlen(args[i])); ! appendPQExpBuffer(&buf, "'%s'", esc); ! appendPQExpBuffer(&buf, "%s", i == nargs ? ")" : ","); } ! extraRes = PSQLexec(buf.data, false); ! termPQExpBuffer(&buf); ! if (!extraRes) ! return false; } ! /* Return values */ ! *title = cached->title; ! *retRes = res; ! *retExtraRes = extraRes; ! return true; } /* ! * dbBuildFooters * ! * Build a NULL terminated array of strings that are to be displayed ! * by the print mechanism. It is expected that PGresult will be ! * sorted in display order, and the tuples will be in the form of ! * (identifier text, value text). ! */ ! static char** ! dbBuildFooters(PGresult *res) { ! char **footers; ! int i; ! char *lastkey = NULL; ! if (!res) ! psql_error("dbBuildFooters: Expecting an extra result set."); ! footers = xmalloczero((PQntuples(res) + 1) * sizeof(*footers)); ! for (i = 0; i < PQntuples(res); i++) ! { ! char tmp[PQgetlength(res, i, 0) + PQgetlength(res, i, 1) + 5]; ! char *key; ! const char *value; ! ! /* Skip malformed footers */ ! if (PQgetisnull(res, i, 0) || PQgetisnull(res, i, 1)) ! continue; ! key = PQgetvalue(res, i, 0); ! value = PQgetvalue(res, i, 1); ! if (!lastkey || strcmp(key, lastkey) != 0) ! sprintf(tmp, "%s: %s", key, value); ! else ! sprintf(tmp, "%*s %s", (int) strlen(key), "", value); ! footers[i] = xstrdup(tmp); ! lastkey = key; ! } ! footers[PQntuples(res)] = NULL; ! return footers; } /* ! * addToDescCache ! * getDescCached ! * initDescCache ! * removeDescCache ! * removeDescCacheItem * ! * Cache handling functions. The cache consists of a prepared query list ! * as pulled and prepared by dbSearch, as well as what information is ! * available for a given command. */ ! static descCache * ! getDescCached(const char *cmd, int nargs) { ! descCache *current; ! #ifdef USE_ASSERT_CHECKING ! assert(nargs >= 0); ! assert(cmd); ! #endif ! for (current = pset.dcache; current; current = current->next) { ! if (strcmp(current->cmd, cmd) == 0 && current->nargs == nargs) ! return current; } ! return NULL; } ! static bool ! addToDescCache(const char *cmd, int nargs, const char *title, bool hasInfoQuery) { ! descCache *current; ! descCache *previous; ! if (!pset.dcache) ! initDescCache(); ! #ifdef USE_ASSERT_CHECKING ! assert(nargs >= 0); ! assert(cmd); ! #endif ! for (current = pset.dcache, previous = NULL; current; previous = current, current = current->next) ! { ! #ifdef USE_ASSERT_CHECKING ! assert(strcmp(current->cmd, cmd) != 0 || current->nargs != nargs); ! #endif ! } ! previous->next = calloc(1, sizeof *(previous->next)); ! if (!previous->next) return false; ! previous->next->cmd = strdup(cmd); ! if (!previous->next->cmd) return false; ! previous->next->nargs = nargs; ! previous->next->title = strdup(title); ! if (!previous->next->title) ! return false; ! previous->next->hasinfoqry = hasInfoQuery; ! return true; } ! void ! removeDescCache() { ! if (pset.dcache) ! removeDescCacheItem(pset.dcache); ! pset.dcache = NULL; } ! static void ! removeDescCacheItem(descCache *current) { ! if (current->next) ! removeDescCacheItem(current->next); ! free(current->cmd); ! free(current->title); ! free(current); } static void ! initDescCache() { ! descCache *cache; ! cache = calloc(1, sizeof *cache); ! if (!cache) return; ! cache->cmd = strdup("__@__"); ! cache->nargs = 0; ! cache->title = strdup("@"); ! cache->hasinfoqry = false; ! if (!cache->cmd || !cache->title) ! { ! free(cache->cmd); ! free(cache->title); ! free(cache); ! return; } ! /* Setup global reference */ ! pset.dcache = cache; } Index: src/bin/psql/describe.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/describe.h,v retrieving revision 1.20 diff -c -r1.20 describe.h *** src/bin/psql/describe.h 2003/01/07 20:56:07 1.20 --- src/bin/psql/describe.h 2003/02/28 03:09:18 *************** *** 8,56 **** #ifndef DESCRIBE_H #define DESCRIBE_H - #include "settings.h" - - /* \da */ - bool describeAggregates(const char *pattern, bool verbose); - - /* \df */ - bool describeFunctions(const char *pattern, bool verbose); - - /* \dT */ - bool describeTypes(const char *pattern, bool verbose); - - /* \do */ - bool describeOperators(const char *pattern); - - /* \du */ - bool describeUsers(const char *pattern); - - /* \z (or \dp) */ - bool permissionsList(const char *pattern); - - /* \dd */ - bool objectDescription(const char *pattern); - /* \d foo */ ! bool describeTableDetails(const char *pattern, bool verbose); ! ! /* \l */ ! bool listAllDbs(bool verbose); ! ! /* \dt, \di, \ds, \dS, etc. */ ! bool listTables(const char *tabtypes, const char *pattern, bool verbose); ! ! /* \dD */ ! bool listDomains(const char *pattern); ! ! /* \dc */ ! bool listConversions(const char *pattern); ! /* \dC */ ! bool listCasts(const char *pattern); ! /* \dn */ ! bool listSchemas(const char *pattern); #endif /* DESCRIBE_H */ --- 8,28 ---- #ifndef DESCRIBE_H #define DESCRIBE_H /* \d foo */ ! bool describeTableDetails(char *pattern, bool verbose); ! /* Try all unmatched commands against a generic search mechanism */ ! bool dbPrint(char **args, int nargs); ! typedef struct descCache ! { ! char *cmd; ! int nargs; ! char *title; ! bool hasinfoqry; ! struct descCache *next; ! } descCache; + void removeDescCache(void); #endif /* DESCRIBE_H */ Index: src/bin/psql/settings.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/settings.h,v retrieving revision 1.13 diff -c -r1.13 settings.h *** src/bin/psql/settings.h 2002/03/05 00:01:02 1.13 --- src/bin/psql/settings.h 2003/02/28 03:09:22 *************** *** 10,17 **** #include "libpq-fe.h" ! #include "variables.h" #include "print.h" #define DEFAULT_FIELD_SEP "|" #define DEFAULT_RECORD_SEP "\n" --- 10,19 ---- #include "libpq-fe.h" ! #include "command.h" ! #include "describe.h" #include "print.h" + #include "variables.h" #define DEFAULT_FIELD_SEP "|" #define DEFAULT_RECORD_SEP "\n" *************** *** 21,27 **** #define DEFAULT_PROMPT2 "%/%R%# " #define DEFAULT_PROMPT3 ">> " - typedef struct _psqlSettings { PGconn *db; /* connection to backend */ --- 23,28 ---- *************** *** 31,36 **** --- 32,38 ---- printQueryOpt popt; VariableSpace vars; /* "shell variable" repository */ + descCache *dcache; /* DB Cache */ char *gfname; /* one-shot file output argument for \g */ Index: src/bin/psql/startup.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/startup.c,v retrieving revision 1.70 diff -c -r1.70 startup.c *** src/bin/psql/startup.c 2003/01/06 18:53:25 1.70 --- src/bin/psql/startup.c 2003/02/28 03:09:26 *************** *** 128,134 **** exit(EXIT_SUCCESS); } } ! pset.cur_cmd_source = stdin; pset.cur_cmd_interactive = false; pset.encoding = PQenv2encoding(); --- 128,134 ---- exit(EXIT_SUCCESS); } } ! pset.cur_cmd_source = stdin; pset.cur_cmd_interactive = false; pset.encoding = PQenv2encoding(); *************** *** 219,225 **** if (options.action == ACT_LIST_DB) { ! int success = listAllDbs(false); PQfinish(pset.db); exit(success ? EXIT_SUCCESS : EXIT_FAILURE); --- 219,231 ---- if (options.action == ACT_LIST_DB) { ! int success; ! char *args[1]; ! ! args[0] = "PSQL-databaselist"; ! ! /* dl is the standard command used for Database Lists */ ! success = dbPrint(args, 0); PQfinish(pset.db); exit(success ? EXIT_SUCCESS : EXIT_FAILURE); Index: src/include/catalog/pg_namespace.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/include/catalog/pg_namespace.h,v retrieving revision 1.8 diff -c -r1.8 pg_namespace.h *** src/include/catalog/pg_namespace.h 2002/09/04 20:31:37 1.8 --- src/include/catalog/pg_namespace.h 2003/02/28 03:09:33 *************** *** 69,74 **** --- 69,77 ---- DATA(insert OID = 11 ( "pg_catalog" PGUID "{=U}" )); DESCR("System catalog namespace"); #define PG_CATALOG_NAMESPACE 11 + DATA(insert OID = 90 ( "pg_tools" PGUID "{=}" )); + DESCR("Reserved namespace for postgresql Tools information"); + #define PG_TOOL_NAMESPACE 90 DATA(insert OID = 99 ( "pg_toast" PGUID "{=}" )); DESCR("Reserved namespace for TOAST tables"); #define PG_TOAST_NAMESPACE 99 Index: src/include/catalog/pg_proc.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/include/catalog/pg_proc.h,v retrieving revision 1.284 diff -c -r1.284 pg_proc.h *** src/include/catalog/pg_proc.h 2003/02/22 00:45:05 1.284 --- src/include/catalog/pg_proc.h 2003/02/28 03:11:05 *************** *** 2916,2922 **** DESCR("SET X as a function"); DATA(insert OID = 2084 ( pg_show_all_settings PGNSP PGUID 12 f f t t s 0 2249 "" show_all_settings - _null_ )); DESCR("SHOW ALL as a function"); ! DATA(insert OID = 1371 ( pg_lock_status PGNSP PGUID 12 f f f t v 0 2249 "" pg_lock_status - _null_ )); DESCR("view system lock information"); DATA(insert OID = 2079 ( pg_table_is_visible PGNSP PGUID 12 f f t f s 1 16 "26" pg_table_is_visible - _null_ )); --- 2916,2925 ---- DESCR("SET X as a function"); DATA(insert OID = 2084 ( pg_show_all_settings PGNSP PGUID 12 f f t t s 0 2249 "" show_all_settings - _null_ )); DESCR("SHOW ALL as a function"); ! DATA(insert OID = 1362 ( pg_name_pattern PGNSP PGUID 12 f f f t v 1 2249 "25" pg_name_pattern - _null_ )); ! DESCR("Process wildcard pattern into schema match expressions"); ! DATA(insert OID = 1365 ( pg_gettext PGNSP PGUID 12 f f t f s 1 25 "25" pg_gettext - _null_ )); ! DATA(insert OID = 1371 ( pg_lock_status PGNSP PGUID 12 f f f t v 0 2249 "" pg_lock_status - _null_ )); DESCR("view system lock information"); DATA(insert OID = 2079 ( pg_table_is_visible PGNSP PGUID 12 f f t f s 1 16 "26" pg_table_is_visible - _null_ )); Index: src/include/utils/builtins.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/include/utils/builtins.h,v retrieving revision 1.208 diff -c -r1.208 builtins.h *** src/include/utils/builtins.h 2003/02/13 05:24:04 1.208 --- src/include/utils/builtins.h 2003/02/28 03:11:22 *************** *** 316,321 **** --- 316,323 ---- extern Datum nullvalue(PG_FUNCTION_ARGS); extern Datum nonnullvalue(PG_FUNCTION_ARGS); extern Datum current_database(PG_FUNCTION_ARGS); + extern Datum pg_name_pattern(PG_FUNCTION_ARGS); + extern Datum pg_gettext(PG_FUNCTION_ARGS); /* not_in.c */ extern Datum int4notin(PG_FUNCTION_ARGS); Index: src/test/regress/expected/sanity_check.out =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/test/regress/expected/sanity_check.out,v retrieving revision 1.22 diff -c -r1.22 sanity_check.out *** src/test/regress/expected/sanity_check.out 2002/08/19 19:33:36 1.22 --- src/test/regress/expected/sanity_check.out 2003/02/28 03:11:44 *************** *** 58,68 **** pg_statistic | t pg_trigger | t pg_type | t road | t shighway | t tenk1 | t tenk2 | t ! (52 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 58,69 ---- pg_statistic | t pg_trigger | t pg_type | t + psql_commands | t road | t shighway | t tenk1 | t tenk2 | t ! (53 rows) -- -- another sanity check: every system catalog that has OIDs should have