From 140d4ebcb46e17cdb1be43892ed797e5e060c8ef Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 21 Aug 2007 01:11:32 +0000 Subject: Tsearch2 functionality migrates to core. The bulk of this work is by Oleg Bartunov and Teodor Sigaev, but I did a lot of editorializing, so anything that's broken is probably my fault. Documentation is nonexistent as yet, but let's land the patch so we can get some portability testing done. --- src/bin/initdb/initdb.c | 188 ++++++++- src/bin/pg_dump/common.c | 26 +- src/bin/pg_dump/pg_backup_archiver.c | 10 +- src/bin/pg_dump/pg_dump.c | 722 ++++++++++++++++++++++++++++++++++- src/bin/pg_dump/pg_dump.h | 41 +- src/bin/pg_dump/pg_dump_sort.c | 30 +- src/bin/psql/command.c | 23 +- src/bin/psql/describe.c | 520 ++++++++++++++++++++++++- src/bin/psql/describe.h | 14 +- src/bin/psql/help.c | 6 +- 10 files changed, 1565 insertions(+), 15 deletions(-) (limited to 'src/bin') diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 4560e77a636..15ac4b7cb2d 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -42,7 +42,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * Portions taken from FreeBSD. * - * $PostgreSQL: pgsql/src/bin/initdb/initdb.c,v 1.139 2007/08/04 21:01:09 neilc Exp $ + * $PostgreSQL: pgsql/src/bin/initdb/initdb.c,v 1.140 2007/08/21 01:11:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -88,6 +88,7 @@ static char *lc_monetary = ""; static char *lc_numeric = ""; static char *lc_time = ""; static char *lc_messages = ""; +static const char *default_text_search_config = ""; static char *username = ""; static bool pwprompt = false; static char *pwfilename = NULL; @@ -108,6 +109,7 @@ static char *hba_file; static char *ident_file; static char *conf_file; static char *conversion_file; +static char *dictionary_file; static char *info_schema_file; static char *features_file; static char *system_views_file; @@ -181,6 +183,7 @@ static void setup_depend(void); static void setup_sysviews(void); static void setup_description(void); static void setup_conversion(void); +static void setup_dictionary(void); static void setup_privileges(void); static void set_info_version(void); static void setup_schema(void); @@ -729,7 +732,7 @@ struct encoding_match const char *system_enc_name; }; -struct encoding_match encoding_match_list[] = { +static const struct encoding_match encoding_match_list[] = { {PG_EUC_JP, "EUC-JP"}, {PG_EUC_JP, "eucJP"}, {PG_EUC_JP, "IBM-eucJP"}, @@ -907,6 +910,94 @@ find_matching_encoding(const char *ctype) } #endif /* HAVE_LANGINFO_H && CODESET */ + +/* + * Support for determining the best default text search configuration. + * We key this off LC_CTYPE, after stripping its encoding indicator if any. + */ +struct tsearch_config_match +{ + const char *tsconfname; + const char *langname; +}; + +static const struct tsearch_config_match tsearch_config_languages[] = +{ + {"danish", "da_DK"}, + {"danish", "Danish_Danmark"}, + {"dutch", "nl_NL"}, + {"dutch", "Dutch_Netherlands"}, + {"english", "C"}, + {"english", "POSIX"}, + {"english", "en_US"}, + {"english", "English_America"}, + {"english", "en_UK"}, + {"english", "English_Britain"}, + {"finnish", "fi_FI"}, + {"finnish", "Finnish_Finland"}, + {"french", "fr_FR"}, + {"french", "French_France"}, + {"german", "de_DE"}, + {"german", "German_Germany"}, + {"hungarian", "hu_HU"}, + {"hungarian", "Hungarian_Hungary"}, + {"italian", "it_IT"}, + {"italian", "Italian_Italy"}, + {"norwegian", "no_NO"}, + {"norwegian", "Norwegian_Norway"}, + {"portuguese", "pt_PT"}, + {"portuguese", "Portuguese_Portugal"}, + {"romanian", "ro_RO"}, + {"russian", "ru_RU"}, + {"russian", "Russian_Russia"}, + {"spanish", "es_ES"}, + {"spanish", "Spanish_Spain"}, + {"swedish", "sv_SE"}, + {"swedish", "Swedish_Sweden"}, + {"turkish", "tr_TR"}, + {"turkish", "Turkish_Turkey"}, + {NULL, NULL} /* end marker */ +}; + +/* + * Look for a text search configuration matching lc_ctype, and return its + * name; return NULL if no match. + */ +static const char * +find_matching_ts_config(const char *lc_type) +{ + int i; + char *langname, + *ptr; + + /* + * Convert lc_ctype to a language name by stripping ".utf8" or + * what-have-you + */ + if (lc_type == NULL) + langname = xstrdup(""); + else + { + ptr = langname = xstrdup(lc_type); + while (*ptr && *ptr != '.') + ptr++; + *ptr = '\0'; + } + + for (i = 0; tsearch_config_languages[i].tsconfname; i++) + { + if (pg_strcasecmp(tsearch_config_languages[i].langname, langname) == 0) + { + free(langname); + return tsearch_config_languages[i].tsconfname; + } + } + + free(langname); + return NULL; +} + + /* * get short version of VERSION */ @@ -1305,6 +1396,13 @@ setup_config(void) } conflines = replace_token(conflines, "#datestyle = 'iso, mdy'", repltok); + snprintf(repltok, sizeof(repltok), + "default_text_search_config = 'pg_catalog.%s'", + escape_quotes(default_text_search_config)); + conflines = replace_token(conflines, + "#default_text_search_config = 'pg_catalog.simple'", + repltok); + snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data); writefile(path, conflines); @@ -1679,6 +1777,14 @@ setup_depend(void) " FROM pg_namespace " " WHERE nspname LIKE 'pg%';\n", + "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' " + " FROM pg_ts_parser;\n", + "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' " + " FROM pg_ts_dict;\n", + "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' " + " FROM pg_ts_template;\n", + "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' " + " FROM pg_ts_config;\n", "INSERT INTO pg_shdepend SELECT 0, 0, 0, tableoid, oid, 'p' " " FROM pg_authid;\n", NULL @@ -1825,6 +1931,43 @@ setup_conversion(void) check_ok(); } +/* + * load extra dictionaries (Snowball stemmers) + */ +static void +setup_dictionary(void) +{ + PG_CMD_DECL; + char **line; + char **conv_lines; + + fputs(_("creating dictionaries ... "), stdout); + fflush(stdout); + + /* + * We use -j here to avoid backslashing stuff + */ + snprintf(cmd, sizeof(cmd), + "\"%s\" %s -j template1 >%s", + backend_exec, backend_options, + DEVNULL); + + PG_CMD_OPEN; + + conv_lines = readfile(dictionary_file); + for (line = conv_lines; *line != NULL; line++) + { + PG_CMD_PUTS(*line); + free(*line); + } + + free(conv_lines); + + PG_CMD_CLOSE; + + check_ok(); +} + /* * Set up privileges * @@ -2403,6 +2546,8 @@ usage(const char *progname) " in the respective category (default taken from\n" " environment)\n")); printf(_(" --no-locale equivalent to --locale=C\n")); + printf(_(" -T, --text-search-config=CFG\n")); + printf(_(" set default text search configuration\n")); printf(_(" -X, --xlogdir=XLOGDIR location for the transaction log directory\n")); printf(_(" -A, --auth=METHOD default authentication method for local connections\n")); printf(_(" -U, --username=NAME database superuser name\n")); @@ -2438,6 +2583,7 @@ main(int argc, char *argv[]) {"lc-time", required_argument, NULL, 6}, {"lc-messages", required_argument, NULL, 7}, {"no-locale", no_argument, NULL, 8}, + {"text-search-config", required_argument, NULL, 'T'}, {"auth", required_argument, NULL, 'A'}, {"pwprompt", no_argument, NULL, 'W'}, {"pwfile", required_argument, NULL, 9}, @@ -2498,7 +2644,7 @@ main(int argc, char *argv[]) /* process command-line options */ - while ((c = getopt_long(argc, argv, "dD:E:L:nU:WA:sX:", long_options, &option_index)) != -1) + while ((c = getopt_long(argc, argv, "dD:E:L:nU:WA:sT:X:", long_options, &option_index)) != -1) { switch (c) { @@ -2558,6 +2704,9 @@ main(int argc, char *argv[]) case 's': show_setting = true; break; + case 'T': + default_text_search_config = xstrdup(optarg); + break; case 'X': xlog_dir = xstrdup(optarg); break; @@ -2771,6 +2920,7 @@ main(int argc, char *argv[]) set_input(&ident_file, "pg_ident.conf.sample"); set_input(&conf_file, "postgresql.conf.sample"); set_input(&conversion_file, "conversion_create.sql"); + set_input(&dictionary_file, "snowball_create.sql"); set_input(&info_schema_file, "information_schema.sql"); set_input(&features_file, "sql_features.txt"); set_input(&system_views_file, "system_views.sql"); @@ -2803,6 +2953,7 @@ main(int argc, char *argv[]) check_input(ident_file); check_input(conf_file); check_input(conversion_file); + check_input(dictionary_file); check_input(info_schema_file); check_input(features_file); check_input(system_views_file); @@ -2864,6 +3015,35 @@ main(int argc, char *argv[]) } #endif /* HAVE_LANGINFO_H && CODESET */ + if (strlen(default_text_search_config) == 0) + { + default_text_search_config = find_matching_ts_config(lc_ctype); + if (default_text_search_config == NULL) + { + printf(_("%s: could not find suitable text search configuration for locale \"%s\"\n"), + progname, lc_ctype); + default_text_search_config = "simple"; + } + } + else + { + const char *checkmatch = find_matching_ts_config(lc_ctype); + + if (checkmatch == NULL) + { + printf(_("%s: warning: suitable text search configuration for locale \"%s\" is unknown\n"), + progname, lc_ctype); + } + else if (strcmp(checkmatch, default_text_search_config) != 0) + { + printf(_("%s: warning: specified text search configuration \"%s\" may not match locale \"%s\"\n"), + progname, default_text_search_config, lc_ctype); + } + } + + printf(_("The default text search configuration will be set to \"%s\".\n"), + default_text_search_config); + printf("\n"); umask(077); @@ -3062,6 +3242,8 @@ main(int argc, char *argv[]) setup_conversion(); + setup_dictionary(); + setup_privileges(); setup_schema(); diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 212c952d2c6..53d15ac970d 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/common.c,v 1.96 2007/01/23 17:54:50 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/common.c,v 1.97 2007/08/21 01:11:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -79,6 +79,10 @@ getSchemaData(int *numTablesPtr) OpclassInfo *opcinfo; OpfamilyInfo *opfinfo; ConvInfo *convinfo; + TSParserInfo *prsinfo; + TSTemplateInfo *tmplinfo; + TSDictInfo *dictinfo; + TSConfigInfo *cfginfo; int numNamespaces; int numAggregates; int numInherits; @@ -88,6 +92,10 @@ getSchemaData(int *numTablesPtr) int numOpclasses; int numOpfamilies; int numConversions; + int numTSParsers; + int numTSTemplates; + int numTSDicts; + int numTSConfigs; if (g_verbose) write_msg(NULL, "reading schemas\n"); @@ -119,6 +127,22 @@ getSchemaData(int *numTablesPtr) write_msg(NULL, "reading user-defined operator classes\n"); opcinfo = getOpclasses(&numOpclasses); + if (g_verbose) + write_msg(NULL, "reading user-defined text search parsers\n"); + prsinfo = getTSParsers(&numTSParsers); + + if (g_verbose) + write_msg(NULL, "reading user-defined text search templates\n"); + tmplinfo = getTSTemplates(&numTSTemplates); + + if (g_verbose) + write_msg(NULL, "reading user-defined text search dictionaries\n"); + dictinfo = getTSDictionaries(&numTSDicts); + + if (g_verbose) + write_msg(NULL, "reading user-defined text search configurations\n"); + cfginfo = getTSConfigurations(&numTSConfigs); + if (g_verbose) write_msg(NULL, "reading user-defined operator families\n"); opfinfo = getOpfamilies(&numOpfamilies); diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index c0e192e859b..01439a18073 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.145 2007/08/06 01:38:14 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.146 2007/08/21 01:11:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2419,7 +2419,9 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH) if (strcmp(type, "CONVERSION") == 0 || strcmp(type, "DOMAIN") == 0 || strcmp(type, "TABLE") == 0 || - strcmp(type, "TYPE") == 0) + strcmp(type, "TYPE") == 0 || + strcmp(type, "TEXT SEARCH DICTIONARY") == 0 || + strcmp(type, "TEXT SEARCH CONFIGURATION") == 0) { appendPQExpBuffer(buf, "%s ", type); if (te->namespace && te->namespace[0]) /* is null pre-7.3 */ @@ -2591,7 +2593,9 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat strcmp(te->desc, "TABLE") == 0 || strcmp(te->desc, "TYPE") == 0 || strcmp(te->desc, "VIEW") == 0 || - strcmp(te->desc, "SEQUENCE") == 0) + strcmp(te->desc, "SEQUENCE") == 0 || + strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 || + strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0) { PQExpBuffer temp = createPQExpBuffer(); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index ee7b6bca790..4d4d7f7986e 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * by PostgreSQL * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.469 2007/07/12 23:25:26 neilc Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.470 2007/08/21 01:11:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -158,6 +158,10 @@ static void dumpSequence(Archive *fout, TableInfo *tbinfo); static void dumpIndex(Archive *fout, IndxInfo *indxinfo); static void dumpConstraint(Archive *fout, ConstraintInfo *coninfo); static void dumpTableConstraintComment(Archive *fout, ConstraintInfo *coninfo); +static void dumpTSParser(Archive *fout, TSParserInfo *prsinfo); +static void dumpTSDictionary(Archive *fout, TSDictInfo *dictinfo); +static void dumpTSTemplate(Archive *fout, TSTemplateInfo *tmplinfo); +static void dumpTSConfig(Archive *fout, TSConfigInfo *cfginfo); static void dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId, const char *type, const char *name, @@ -174,6 +178,7 @@ static char *format_function_arguments(FuncInfo *finfo, int nallargs, static char *format_function_signature(FuncInfo *finfo, bool honor_quotes); static const char *convertRegProcReference(const char *proc); static const char *convertOperatorReference(const char *opr); +static const char *convertTSFunction(Oid funcOid); static Oid findLastBuiltinOid_V71(const char *); static Oid findLastBuiltinOid_V70(void); static void selectSourceSchema(const char *schemaName); @@ -4677,6 +4682,327 @@ getTableAttrs(TableInfo *tblinfo, int numTables) } +/* + * getTSParsers: + * read all text search parsers in the system catalogs and return them + * in the TSParserInfo* structure + * + * numTSParsers is set to the number of parsers read in + */ +TSParserInfo * +getTSParsers(int *numTSParsers) +{ + PGresult *res; + int ntups; + int i; + PQExpBuffer query = createPQExpBuffer(); + TSParserInfo *prsinfo; + int i_tableoid; + int i_oid; + int i_prsname; + int i_prsnamespace; + int i_prsstart; + int i_prstoken; + int i_prsend; + int i_prsheadline; + int i_prslextype; + + /* Before 8.3, there is no built-in text search support */ + if (g_fout->remoteVersion < 80300) + { + *numTSParsers = 0; + return NULL; + } + + /* + * find all text search objects, including builtin ones; we filter out + * system-defined objects at dump-out time. + */ + + /* Make sure we are in proper schema */ + selectSourceSchema("pg_catalog"); + + appendPQExpBuffer(query, "SELECT tableoid, oid, prsname, prsnamespace, " + "prsstart::oid, prstoken::oid, " + "prsend::oid, prsheadline::oid, prslextype::oid " + "FROM pg_ts_parser"); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + *numTSParsers = ntups; + + prsinfo = (TSParserInfo *) malloc(ntups * sizeof(TSParserInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_prsname = PQfnumber(res, "prsname"); + i_prsnamespace = PQfnumber(res, "prsnamespace"); + i_prsstart = PQfnumber(res, "prsstart"); + i_prstoken = PQfnumber(res, "prstoken"); + i_prsend = PQfnumber(res, "prsend"); + i_prsheadline = PQfnumber(res, "prsheadline"); + i_prslextype = PQfnumber(res, "prslextype"); + + for (i = 0; i < ntups; i++) + { + prsinfo[i].dobj.objType = DO_TSPARSER; + prsinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + prsinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&prsinfo[i].dobj); + prsinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_prsname)); + prsinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_prsnamespace)), + prsinfo[i].dobj.catId.oid); + prsinfo[i].prsstart = atooid(PQgetvalue(res, i, i_prsstart)); + prsinfo[i].prstoken = atooid(PQgetvalue(res, i, i_prstoken)); + prsinfo[i].prsend = atooid(PQgetvalue(res, i, i_prsend)); + prsinfo[i].prsheadline = atooid(PQgetvalue(res, i, i_prsheadline)); + prsinfo[i].prslextype = atooid(PQgetvalue(res, i, i_prslextype)); + + /* Decide whether we want to dump it */ + selectDumpableObject(&(prsinfo[i].dobj)); + } + + PQclear(res); + + destroyPQExpBuffer(query); + + return prsinfo; +} + +/* + * getTSDictionaries: + * read all text search dictionaries in the system catalogs and return them + * in the TSDictInfo* structure + * + * numTSDicts is set to the number of dictionaries read in + */ +TSDictInfo * +getTSDictionaries(int *numTSDicts) +{ + PGresult *res; + int ntups; + int i; + PQExpBuffer query = createPQExpBuffer(); + TSDictInfo *dictinfo; + int i_tableoid; + int i_oid; + int i_dictname; + int i_dictnamespace; + int i_rolname; + int i_dicttemplate; + int i_dictinitoption; + + /* Before 8.3, there is no built-in text search support */ + if (g_fout->remoteVersion < 80300) + { + *numTSDicts = 0; + return NULL; + } + + /* Make sure we are in proper schema */ + selectSourceSchema("pg_catalog"); + + appendPQExpBuffer(query, "SELECT tableoid, oid, dictname, " + "dictnamespace, (%s dictowner) as rolname, " + "dicttemplate, dictinitoption " + "FROM pg_ts_dict", + username_subquery); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + *numTSDicts = ntups; + + dictinfo = (TSDictInfo *) malloc(ntups * sizeof(TSDictInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_dictname = PQfnumber(res, "dictname"); + i_dictnamespace = PQfnumber(res, "dictnamespace"); + i_rolname = PQfnumber(res, "rolname"); + i_dictinitoption = PQfnumber(res, "dictinitoption"); + i_dicttemplate = PQfnumber(res, "dicttemplate"); + + for (i = 0; i < ntups; i++) + { + dictinfo[i].dobj.objType = DO_TSDICT; + dictinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + dictinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&dictinfo[i].dobj); + dictinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_dictname)); + dictinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_dictnamespace)), + dictinfo[i].dobj.catId.oid); + dictinfo[i].rolname = strdup(PQgetvalue(res, i, i_rolname)); + dictinfo[i].dicttemplate = atooid(PQgetvalue(res, i, i_dicttemplate)); + if (PQgetisnull(res, i, i_dictinitoption)) + dictinfo[i].dictinitoption = NULL; + else + dictinfo[i].dictinitoption = strdup(PQgetvalue(res, i, i_dictinitoption)); + + /* Decide whether we want to dump it */ + selectDumpableObject(&(dictinfo[i].dobj)); + } + + PQclear(res); + + destroyPQExpBuffer(query); + + return dictinfo; +} + +/* + * getTSTemplates: + * read all text search templates in the system catalogs and return them + * in the TSTemplateInfo* structure + * + * numTSTemplates is set to the number of templates read in + */ +TSTemplateInfo * +getTSTemplates(int *numTSTemplates) +{ + PGresult *res; + int ntups; + int i; + PQExpBuffer query = createPQExpBuffer(); + TSTemplateInfo *tmplinfo; + int i_tableoid; + int i_oid; + int i_tmplname; + int i_tmplnamespace; + int i_tmplinit; + int i_tmpllexize; + + /* Before 8.3, there is no built-in text search support */ + if (g_fout->remoteVersion < 80300) + { + *numTSTemplates = 0; + return NULL; + } + + /* Make sure we are in proper schema */ + selectSourceSchema("pg_catalog"); + + appendPQExpBuffer(query, "SELECT tableoid, oid, tmplname, " + "tmplnamespace, tmplinit::oid, tmpllexize::oid " + "FROM pg_ts_template"); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + *numTSTemplates = ntups; + + tmplinfo = (TSTemplateInfo *) malloc(ntups * sizeof(TSTemplateInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_tmplname = PQfnumber(res, "tmplname"); + i_tmplnamespace = PQfnumber(res, "tmplnamespace"); + i_tmplinit = PQfnumber(res, "tmplinit"); + i_tmpllexize = PQfnumber(res, "tmpllexize"); + + for (i = 0; i < ntups; i++) + { + tmplinfo[i].dobj.objType = DO_TSTEMPLATE; + tmplinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + tmplinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&tmplinfo[i].dobj); + tmplinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_tmplname)); + tmplinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_tmplnamespace)), + tmplinfo[i].dobj.catId.oid); + tmplinfo[i].tmplinit = atooid(PQgetvalue(res, i, i_tmplinit)); + tmplinfo[i].tmpllexize = atooid(PQgetvalue(res, i, i_tmpllexize)); + + /* Decide whether we want to dump it */ + selectDumpableObject(&(tmplinfo[i].dobj)); + } + + PQclear(res); + + destroyPQExpBuffer(query); + + return tmplinfo; +} + +/* + * getTSConfigurations: + * read all text search configurations in the system catalogs and return + * them in the TSConfigInfo* structure + * + * numTSConfigs is set to the number of configurations read in + */ +TSConfigInfo * +getTSConfigurations(int *numTSConfigs) +{ + PGresult *res; + int ntups; + int i; + PQExpBuffer query = createPQExpBuffer(); + TSConfigInfo *cfginfo; + int i_tableoid; + int i_oid; + int i_cfgname; + int i_cfgnamespace; + int i_rolname; + int i_cfgparser; + + /* Before 8.3, there is no built-in text search support */ + if (g_fout->remoteVersion < 80300) + { + *numTSConfigs = 0; + return NULL; + } + + /* Make sure we are in proper schema */ + selectSourceSchema("pg_catalog"); + + appendPQExpBuffer(query, "SELECT tableoid, oid, cfgname, " + "cfgnamespace, (%s cfgowner) as rolname, cfgparser " + "FROM pg_ts_config", + username_subquery); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + *numTSConfigs = ntups; + + cfginfo = (TSConfigInfo *) malloc(ntups * sizeof(TSConfigInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_cfgname = PQfnumber(res, "cfgname"); + i_cfgnamespace = PQfnumber(res, "cfgnamespace"); + i_rolname = PQfnumber(res, "rolname"); + i_cfgparser = PQfnumber(res, "cfgparser"); + + for (i = 0; i < ntups; i++) + { + cfginfo[i].dobj.objType = DO_TSCONFIG; + cfginfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + cfginfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&cfginfo[i].dobj); + cfginfo[i].dobj.name = strdup(PQgetvalue(res, i, i_cfgname)); + cfginfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_cfgnamespace)), + cfginfo[i].dobj.catId.oid); + cfginfo[i].rolname = strdup(PQgetvalue(res, i, i_rolname)); + cfginfo[i].cfgparser = atooid(PQgetvalue(res, i, i_cfgparser)); + + /* Decide whether we want to dump it */ + selectDumpableObject(&(cfginfo[i].dobj)); + } + + PQclear(res); + + destroyPQExpBuffer(query); + + return cfginfo; +} + + /* * dumpComment -- * @@ -5066,6 +5392,18 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj) case DO_TABLE_TYPE: /* table rowtypes are never dumped separately */ break; + case DO_TSPARSER: + dumpTSParser(fout, (TSParserInfo *) dobj); + break; + case DO_TSDICT: + dumpTSDictionary(fout, (TSDictInfo *) dobj); + break; + case DO_TSTEMPLATE: + dumpTSTemplate(fout, (TSTemplateInfo *) dobj); + break; + case DO_TSCONFIG: + dumpTSConfig(fout, (TSConfigInfo *) dobj); + break; case DO_BLOBS: ArchiveEntry(fout, dobj->catId, dobj->dumpId, dobj->name, NULL, NULL, "", @@ -6874,6 +7212,43 @@ convertOperatorReference(const char *opr) return oprInfo->dobj.name; } +/* + * Convert a function OID obtained from pg_ts_parser or pg_ts_template + * + * It is sufficient to use REGPROC rather than REGPROCEDURE, since the + * argument lists of these functions are predetermined. Note that the + * caller should ensure we are in the proper schema, because the results + * are search path dependent! + */ +static const char * +convertTSFunction(Oid funcOid) +{ + char *result; + char query[128]; + PGresult *res; + int ntups; + + snprintf(query, sizeof(query), + "SELECT '%u'::pg_catalog.regproc", funcOid); + res = PQexec(g_conn, query); + check_sql_result(res, g_conn, query, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + if (ntups != 1) + { + write_msg(NULL, "Got %d rows instead of one from \"%s\"\n", + ntups, query); + exit_nicely(); + } + + result = strdup(PQgetvalue(res, 0, 0)); + + PQclear(res); + + return result; +} + + /* * dumpOpclass * write out a single operator class definition @@ -7795,6 +8170,351 @@ dumpAgg(Archive *fout, AggInfo *agginfo) destroyPQExpBuffer(details); } +/* + * dumpTSParser + * write out a single text search parser + */ +static void +dumpTSParser(Archive *fout, TSParserInfo * prsinfo) +{ + PQExpBuffer q; + PQExpBuffer delq; + + /* Skip if not to be dumped */ + if (!prsinfo->dobj.dump || dataOnly) + return; + + q = createPQExpBuffer(); + delq = createPQExpBuffer(); + + /* Make sure we are in proper schema */ + selectSourceSchema(prsinfo->dobj.namespace->dobj.name); + + appendPQExpBuffer(q, "CREATE TEXT SEARCH PARSER %s (\n", + fmtId(prsinfo->dobj.name)); + + appendPQExpBuffer(q, " START = %s,\n", + convertTSFunction(prsinfo->prsstart)); + appendPQExpBuffer(q, " GETTOKEN = %s,\n", + convertTSFunction(prsinfo->prstoken)); + appendPQExpBuffer(q, " END = %s,\n", + convertTSFunction(prsinfo->prsend)); + if (prsinfo->prsheadline != InvalidOid) + appendPQExpBuffer(q, " HEADLINE = %s,\n", + convertTSFunction(prsinfo->prsheadline)); + appendPQExpBuffer(q, " LEXTYPES = %s );\n", + convertTSFunction(prsinfo->prslextype)); + + /* + * DROP must be fully qualified in case same name appears in pg_catalog + */ + appendPQExpBuffer(delq, "DROP TEXT SEARCH PARSER %s", + fmtId(prsinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delq, ".%s;\n", + fmtId(prsinfo->dobj.name)); + + ArchiveEntry(fout, prsinfo->dobj.catId, prsinfo->dobj.dumpId, + prsinfo->dobj.name, + prsinfo->dobj.namespace->dobj.name, + NULL, + "", + false, "TEXT SEARCH PARSER", q->data, delq->data, NULL, + prsinfo->dobj.dependencies, prsinfo->dobj.nDeps, + NULL, NULL); + + /* Dump Parser Comments */ + resetPQExpBuffer(q); + appendPQExpBuffer(q, "TEXT SEARCH PARSER %s", + fmtId(prsinfo->dobj.name)); + dumpComment(fout, q->data, + NULL, "", + prsinfo->dobj.catId, 0, prsinfo->dobj.dumpId); + + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); +} + +/* + * dumpTSDictionary + * write out a single text search dictionary + */ +static void +dumpTSDictionary(Archive *fout, TSDictInfo * dictinfo) +{ + PQExpBuffer q; + PQExpBuffer delq; + PQExpBuffer query; + PGresult *res; + int ntups; + char *nspname; + char *tmplname; + + /* Skip if not to be dumped */ + if (!dictinfo->dobj.dump || dataOnly) + return; + + q = createPQExpBuffer(); + delq = createPQExpBuffer(); + query = createPQExpBuffer(); + + /* Fetch name and namespace of the dictionary's template */ + selectSourceSchema("pg_catalog"); + appendPQExpBuffer(query, "SELECT nspname, tmplname " + "FROM pg_ts_template p, pg_namespace n " + "WHERE p.oid = '%u' AND n.oid = tmplnamespace", + dictinfo->dicttemplate); + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + ntups = PQntuples(res); + if (ntups != 1) + { + write_msg(NULL, "Got %d rows instead of one from \"%s\"\n", + ntups, query->data); + exit_nicely(); + } + nspname = PQgetvalue(res, 0, 0); + tmplname = PQgetvalue(res, 0, 1); + + /* Make sure we are in proper schema */ + selectSourceSchema(dictinfo->dobj.namespace->dobj.name); + + appendPQExpBuffer(q, "CREATE TEXT SEARCH DICTIONARY %s (\n", + fmtId(dictinfo->dobj.name)); + + appendPQExpBuffer(q, " TEMPLATE = "); + if (strcmp(nspname, dictinfo->dobj.namespace->dobj.name) != 0) + appendPQExpBuffer(q, "%s.", fmtId(nspname)); + appendPQExpBuffer(q, "%s", fmtId(tmplname)); + + PQclear(res); + + if (dictinfo->dictinitoption) + { + appendPQExpBuffer(q, ",\n OPTION = "); + appendStringLiteralConn(q, dictinfo->dictinitoption, g_conn); + } + + appendPQExpBuffer(q, " );\n"); + + /* + * DROP must be fully qualified in case same name appears in pg_catalog + */ + appendPQExpBuffer(delq, "DROP TEXT SEARCH DICTIONARY %s", + fmtId(dictinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delq, ".%s;\n", + fmtId(dictinfo->dobj.name)); + + ArchiveEntry(fout, dictinfo->dobj.catId, dictinfo->dobj.dumpId, + dictinfo->dobj.name, + dictinfo->dobj.namespace->dobj.name, + NULL, + dictinfo->rolname, + false, "TEXT SEARCH DICTIONARY", q->data, delq->data, NULL, + dictinfo->dobj.dependencies, dictinfo->dobj.nDeps, + NULL, NULL); + + /* Dump Dictionary Comments */ + resetPQExpBuffer(q); + appendPQExpBuffer(q, "TEXT SEARCH DICTIONARY %s", + fmtId(dictinfo->dobj.name)); + dumpComment(fout, q->data, + NULL, dictinfo->rolname, + dictinfo->dobj.catId, 0, dictinfo->dobj.dumpId); + + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); + destroyPQExpBuffer(query); +} + +/* + * dumpTSTemplate + * write out a single text search template + */ +static void +dumpTSTemplate(Archive *fout, TSTemplateInfo * tmplinfo) +{ + PQExpBuffer q; + PQExpBuffer delq; + + /* Skip if not to be dumped */ + if (!tmplinfo->dobj.dump || dataOnly) + return; + + q = createPQExpBuffer(); + delq = createPQExpBuffer(); + + /* Make sure we are in proper schema */ + selectSourceSchema(tmplinfo->dobj.namespace->dobj.name); + + appendPQExpBuffer(q, "CREATE TEXT SEARCH TEMPLATE %s (\n", + fmtId(tmplinfo->dobj.name)); + + if (tmplinfo->tmplinit != InvalidOid) + appendPQExpBuffer(q, " INIT = %s,\n", + convertTSFunction(tmplinfo->tmplinit)); + appendPQExpBuffer(q, " LEXIZE = %s );\n", + convertTSFunction(tmplinfo->tmpllexize)); + + /* + * DROP must be fully qualified in case same name appears in pg_catalog + */ + appendPQExpBuffer(delq, "DROP TEXT SEARCH TEMPLATE %s", + fmtId(tmplinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delq, ".%s;\n", + fmtId(tmplinfo->dobj.name)); + + ArchiveEntry(fout, tmplinfo->dobj.catId, tmplinfo->dobj.dumpId, + tmplinfo->dobj.name, + tmplinfo->dobj.namespace->dobj.name, + NULL, + "", + false, "TEXT SEARCH TEMPLATE", q->data, delq->data, NULL, + tmplinfo->dobj.dependencies, tmplinfo->dobj.nDeps, + NULL, NULL); + + /* Dump Template Comments */ + resetPQExpBuffer(q); + appendPQExpBuffer(q, "TEXT SEARCH TEMPLATE %s", + fmtId(tmplinfo->dobj.name)); + dumpComment(fout, q->data, + NULL, "", + tmplinfo->dobj.catId, 0, tmplinfo->dobj.dumpId); + + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); +} + +/* + * dumpTSConfig + * write out a single text search configuration + */ +static void +dumpTSConfig(Archive *fout, TSConfigInfo * cfginfo) +{ + PQExpBuffer q; + PQExpBuffer delq; + PQExpBuffer query; + PGresult *res; + char *nspname; + char *prsname; + int ntups, + i; + int i_tokenname; + int i_dictname; + + /* Skip if not to be dumped */ + if (!cfginfo->dobj.dump || dataOnly) + return; + + q = createPQExpBuffer(); + delq = createPQExpBuffer(); + query = createPQExpBuffer(); + + /* Fetch name and namespace of the config's parser */ + selectSourceSchema("pg_catalog"); + appendPQExpBuffer(query, "SELECT nspname, prsname " + "FROM pg_ts_parser p, pg_namespace n " + "WHERE p.oid = '%u' AND n.oid = prsnamespace", + cfginfo->cfgparser); + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + ntups = PQntuples(res); + if (ntups != 1) + { + write_msg(NULL, "Got %d rows instead of one from \"%s\"\n", + ntups, query->data); + exit_nicely(); + } + nspname = PQgetvalue(res, 0, 0); + prsname = PQgetvalue(res, 0, 1); + + /* Make sure we are in proper schema */ + selectSourceSchema(cfginfo->dobj.namespace->dobj.name); + + appendPQExpBuffer(q, "CREATE TEXT SEARCH CONFIGURATION %s (\n", + fmtId(cfginfo->dobj.name)); + + appendPQExpBuffer(q, " PARSER = "); + if (strcmp(nspname, cfginfo->dobj.namespace->dobj.name) != 0) + appendPQExpBuffer(q, "%s.", fmtId(nspname)); + appendPQExpBuffer(q, "%s );\n", fmtId(prsname)); + + PQclear(res); + + resetPQExpBuffer(query); + appendPQExpBuffer(query, + "SELECT \n" + " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t \n" + " WHERE t.tokid = m.maptokentype ) AS tokenname, \n" + " m.mapdict::pg_catalog.regdictionary AS dictname \n" + "FROM pg_catalog.pg_ts_config_map AS m \n" + "WHERE m.mapcfg = '%u' \n" + "ORDER BY m.mapcfg, m.maptokentype, m.mapseqno", + cfginfo->cfgparser, cfginfo->dobj.catId.oid); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + ntups = PQntuples(res); + + i_tokenname = PQfnumber(res, "tokenname"); + i_dictname = PQfnumber(res, "dictname"); + + for (i = 0; i < ntups; i++) + { + char *tokenname = PQgetvalue(res, i, i_tokenname); + char *dictname = PQgetvalue(res, i, i_dictname); + + if (i == 0 || + strcmp(tokenname, PQgetvalue(res, i-1, i_tokenname)) != 0) + { + /* starting a new token type, so start a new command */ + if (i > 0) + appendPQExpBuffer(q, ";\n"); + appendPQExpBuffer(q, "\nALTER TEXT SEARCH CONFIGURATION %s\n", + fmtId(cfginfo->dobj.name)); + /* tokenname needs quoting, dictname does NOT */ + appendPQExpBuffer(q, " ADD MAPPING FOR %s WITH %s", + fmtId(tokenname), dictname); + } + else + appendPQExpBuffer(q, ", %s", dictname); + } + + if (ntups > 0) + appendPQExpBuffer(q, ";\n"); + + PQclear(res); + + /* + * DROP must be fully qualified in case same name appears in pg_catalog + */ + appendPQExpBuffer(delq, "DROP TEXT SEARCH CONFIGURATION %s", + fmtId(cfginfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delq, ".%s;\n", + fmtId(cfginfo->dobj.name)); + + ArchiveEntry(fout, cfginfo->dobj.catId, cfginfo->dobj.dumpId, + cfginfo->dobj.name, + cfginfo->dobj.namespace->dobj.name, + NULL, + cfginfo->rolname, + false, "TEXT SEARCH CONFIGURATION", q->data, delq->data, NULL, + cfginfo->dobj.dependencies, cfginfo->dobj.nDeps, + NULL, NULL); + + /* Dump Configuration Comments */ + resetPQExpBuffer(q); + appendPQExpBuffer(q, "TEXT SEARCH CONFIGURATION %s", + fmtId(cfginfo->dobj.name)); + dumpComment(fout, q->data, + NULL, cfginfo->rolname, + cfginfo->dobj.catId, 0, cfginfo->dobj.dumpId); + + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); + destroyPQExpBuffer(query); +} + /*---------- * Write out grant/revoke information diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 9575cd5b19d..0383b0d296d 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.135 2007/05/11 17:57:13 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.136 2007/08/21 01:11:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -127,6 +127,10 @@ typedef enum DO_CAST, DO_TABLE_DATA, DO_TABLE_TYPE, + DO_TSPARSER, + DO_TSDICT, + DO_TSTEMPLATE, + DO_TSCONFIG, DO_BLOBS, DO_BLOB_COMMENTS } DumpableObjectType; @@ -379,6 +383,37 @@ typedef struct _inhInfo Oid inhparent; /* OID of its parent */ } InhInfo; +typedef struct _prsInfo +{ + DumpableObject dobj; + Oid prsstart; + Oid prstoken; + Oid prsend; + Oid prsheadline; + Oid prslextype; +} TSParserInfo; + +typedef struct _dictInfo +{ + DumpableObject dobj; + char *rolname; + Oid dicttemplate; + char *dictinitoption; +} TSDictInfo; + +typedef struct _tmplInfo +{ + DumpableObject dobj; + Oid tmplinit; + Oid tmpllexize; +} TSTemplateInfo; + +typedef struct _cfgInfo +{ + DumpableObject dobj; + char *rolname; + Oid cfgparser; +} TSConfigInfo; /* global decls */ extern bool force_quotes; /* double-quotes for identifiers flag */ @@ -458,5 +493,9 @@ extern void getTriggers(TableInfo tblinfo[], int numTables); extern ProcLangInfo *getProcLangs(int *numProcLangs); extern CastInfo *getCasts(int *numCasts); extern void getTableAttrs(TableInfo *tbinfo, int numTables); +extern TSParserInfo *getTSParsers(int *numTSParsers); +extern TSDictInfo *getTSDictionaries(int *numTSDicts); +extern TSTemplateInfo *getTSTemplates(int *numTSTemplates); +extern TSConfigInfo *getTSConfigurations(int *numTSConfigs); #endif /* PG_DUMP_H */ diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index a74857fb68e..d74a5facf8e 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump_sort.c,v 1.18 2007/03/18 16:50:44 neilc Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump_sort.c,v 1.19 2007/08/21 01:11:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -46,6 +46,10 @@ static const int oldObjectTypePriority[] = 2, /* DO_CAST */ 9, /* DO_TABLE_DATA */ 7, /* DO_TABLE_TYPE */ + 3, /* DO_TSPARSER */ + 4, /* DO_TSDICT */ + 3, /* DO_TSTEMPLATE */ + 5, /* DO_TSCONFIG */ 10, /* DO_BLOBS */ 11 /* DO_BLOB_COMMENTS */ }; @@ -76,6 +80,10 @@ static const int newObjectTypePriority[] = 8, /* DO_CAST */ 13, /* DO_TABLE_DATA */ 11, /* DO_TABLE_TYPE */ + 5, /* DO_TSPARSER */ + 6, /* DO_TSDICT */ + 5, /* DO_TSTEMPLATE */ + 7, /* DO_TSCONFIG */ 14, /* DO_BLOBS */ 15 /* DO_BLOB_COMMENTS */ }; @@ -1067,6 +1075,26 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) "TABLE TYPE %s (ID %d OID %u)", obj->name, obj->dumpId, obj->catId.oid); return; + case DO_TSPARSER: + snprintf(buf, bufsize, + "TEXT SEARCH PARSER %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; + case DO_TSDICT: + snprintf(buf, bufsize, + "TEXT SEARCH DICTIONARY %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; + case DO_TSTEMPLATE: + snprintf(buf, bufsize, + "TEXT SEARCH TEMPLATE %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; + case DO_TSCONFIG: + snprintf(buf, bufsize, + "TEXT SEARCH CONFIGURATION %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; case DO_BLOBS: snprintf(buf, bufsize, "BLOBS (ID %d)", diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 936c56b2031..04e5cce6361 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2007, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.180 2007/07/08 19:07:38 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.181 2007/08/21 01:11:22 tgl Exp $ */ #include "postgres_fe.h" #include "command.h" @@ -405,6 +405,27 @@ exec_command(const char *cmd, case 'u': success = describeRoles(pattern, show_verbose); break; + case 'F': /* text search subsystem */ + switch (cmd[2]) + { + case '\0': + case '+': + success = listTSConfigs(pattern, show_verbose); + break; + case 'p': + success = listTSParsers(pattern, show_verbose); + break; + case 'd': + success = listTSDictionaries(pattern, show_verbose); + break; + case 't': + success = listTSTemplates(pattern, show_verbose); + break; + default: + status = PSQL_CMD_UNKNOWN; + break; + } + break; default: status = PSQL_CMD_UNKNOWN; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 6dd156af4af..f258e16ce1a 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2007, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.157 2007/07/25 22:16:18 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.158 2007/08/21 01:11:22 tgl Exp $ */ #include "postgres_fe.h" #include "describe.h" @@ -33,6 +33,14 @@ static bool describeOneTableDetails(const char *schemaname, bool verbose); static bool add_tablespace_footer(char relkind, Oid tablespace, char **footers, int *count, PQExpBufferData buf, bool newline); +static bool listTSParsersVerbose(const char *pattern); +static bool describeOneTSParser(const char *oid, const char *nspname, + const char *prsname); +static bool listTSConfigsVerbose(const char *pattern); +static bool describeOneTSConfig(const char *oid, const char *nspname, + const char *cfgname, + const char *pnspname, const char *prsname); + /*---------------- * Handlers for various slash commands displaying some sort of list @@ -325,7 +333,6 @@ describeTypes(const char *pattern, bool verbose) } - /* \do */ bool @@ -1930,3 +1937,512 @@ listSchemas(const char *pattern, bool verbose) PQclear(res); return true; } + + +/* + * \dFp + * list text search parsers + */ +bool +listTSParsers(const char *pattern, bool verbose) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + if (verbose) + return listTSParsersVerbose(pattern); + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT \n" + " n.nspname as \"%s\",\n" + " p.prsname as \"%s\",\n" + " pg_catalog.obj_description(p.oid, 'pg_ts_parser') as \"%s\"\n" + "FROM pg_catalog.pg_ts_parser p \n" + "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n", + _("Schema"), + _("Name"), + _("Description") + ); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + "n.nspname", "p.prsname", NULL, + "pg_catalog.pg_ts_parser_is_visible(p.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 text search parsers"); + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; +} + +/* + * full description of parsers + */ +static bool +listTSParsersVerbose(const char *pattern) +{ + PQExpBufferData buf; + PGresult *res; + int i; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT p.oid, \n" + " n.nspname, \n" + " p.prsname \n" + "FROM pg_catalog.pg_ts_parser p\n" + "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n" + ); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + "n.nspname", "p.prsname", NULL, + "pg_catalog.pg_ts_parser_is_visible(p.oid)"); + + appendPQExpBuffer(&buf, "ORDER BY 1, 2;"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + if (PQntuples(res) == 0) + { + if (!pset.quiet) + fprintf(stderr, _("Did not find any text search parser named \"%s\".\n"), + pattern); + PQclear(res); + return false; + } + + for (i = 0; i < PQntuples(res); i++) + { + const char *oid; + const char *nspname = NULL; + const char *prsname; + + oid = PQgetvalue(res, i, 0); + if (!PQgetisnull(res, i, 1)) + nspname = PQgetvalue(res, i, 1); + prsname = PQgetvalue(res, i, 2); + + if (!describeOneTSParser(oid, nspname, prsname)) + { + PQclear(res); + return false; + } + + if (cancel_pressed) + { + PQclear(res); + return false; + } + } + + PQclear(res); + return true; +} + +static bool +describeOneTSParser(const char *oid, const char *nspname, const char *prsname) +{ + PQExpBufferData buf; + PGresult *res; + char title[1024]; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT '%s' AS \"%s\", \n" + " p.prsstart::pg_catalog.regproc AS \"%s\", \n" + " pg_catalog.obj_description(p.prsstart, 'pg_proc') as \"%s\" \n" + " FROM pg_catalog.pg_ts_parser p \n" + " WHERE p.oid = '%s' \n" + "UNION ALL \n" + "SELECT '%s', \n" + " p.prstoken::pg_catalog.regproc, \n" + " pg_catalog.obj_description(p.prstoken, 'pg_proc') \n" + " FROM pg_catalog.pg_ts_parser p \n" + " WHERE p.oid = '%s' \n" + "UNION ALL \n" + "SELECT '%s', \n" + " p.prsend::pg_catalog.regproc, \n" + " pg_catalog.obj_description(p.prsend, 'pg_proc') \n" + " FROM pg_catalog.pg_ts_parser p \n" + " WHERE p.oid = '%s' \n" + "UNION ALL \n" + "SELECT '%s', \n" + " p.prsheadline::pg_catalog.regproc, \n" + " pg_catalog.obj_description(p.prsheadline, 'pg_proc') \n" + " FROM pg_catalog.pg_ts_parser p \n" + " WHERE p.oid = '%s' \n" + "UNION ALL \n" + "SELECT '%s', \n" + " p.prslextype::pg_catalog.regproc, \n" + " pg_catalog.obj_description(p.prslextype, 'pg_proc') \n" + " FROM pg_catalog.pg_ts_parser p \n" + " WHERE p.oid = '%s' \n", + _("Start parse"), + _("Method"), _("Function"), _("Description"), + oid, + _("Get next token"), oid, + _("End parse"), oid, + _("Get headline"), oid, + _("Get lexeme types"), oid + ); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + if (nspname) + sprintf(title, _("Text search parser \"%s.%s\""), nspname, prsname); + else + sprintf(title, _("Text search parser \"%s\""), prsname); + myopt.title = title; + myopt.footers = NULL; + myopt.default_footer = false; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT t.alias as \"%s\", \n" + " t.description as \"%s\" \n" + "FROM pg_catalog.ts_token_type( '%s'::pg_catalog.oid ) as t \n" + "ORDER BY 1;", + _("Token name"), + _("Description"), + oid); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + if (nspname) + sprintf(title, _("Token types for parser \"%s.%s\""), nspname, prsname); + else + sprintf(title, _("Token types for parser \"%s\""), prsname); + myopt.title = title; + myopt.footers = NULL; + myopt.default_footer = true; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; +} + + +/* + * \dFd + * list text search dictionaries + */ +bool +listTSDictionaries(const char *pattern, bool verbose) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT \n" + " n.nspname as \"%s\",\n" + " d.dictname as \"%s\",\n", + _("Schema"), + _("Name")); + + if (verbose) + { + appendPQExpBuffer(&buf, + " ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM \n" + " pg_catalog.pg_ts_template t \n" + " LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace \n" + " WHERE d.dicttemplate = t.oid ) AS \"%s\", \n" + " d.dictinitoption as \"%s\", \n", + _("Template"), + _("Init options")); + } + + appendPQExpBuffer(&buf, + " pg_catalog.obj_description(d.oid, 'pg_ts_dict') as \"%s\"\n", + _("Description")); + + appendPQExpBuffer(&buf, "FROM pg_catalog.pg_ts_dict d\n" + "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n"); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + "n.nspname", "d.dictname", NULL, + "pg_catalog.pg_ts_dict_is_visible(d.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 text search dictionaries"); + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; +} + + +/* + * \dFt + * list text search templates + */ +bool +listTSTemplates(const char *pattern, bool verbose) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT \n" + " n.nspname AS \"%s\",\n" + " t.tmplname AS \"%s\",\n" + " t.tmplinit::pg_catalog.regproc AS \"%s\",\n" + " t.tmpllexize::pg_catalog.regproc AS \"%s\",\n" + " pg_catalog.obj_description(t.oid, 'pg_ts_template') AS \"%s\"\n", + _("Schema"), + _("Name"), + _("Init"), + _("Lexize"), + _("Description")); + + appendPQExpBuffer(&buf, "FROM pg_catalog.pg_ts_template t\n" + "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n"); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + "n.nspname", "t.tmplname", NULL, + "pg_catalog.pg_ts_template_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 text search templates"); + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; +} + + +/* + * \dF + * list text search configurations + */ +bool +listTSConfigs(const char *pattern, bool verbose) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + if (verbose) + return listTSConfigsVerbose(pattern); + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT \n" + " n.nspname as \"%s\",\n" + " c.cfgname as \"%s\",\n" + " pg_catalog.obj_description(c.oid, 'pg_ts_config') as \"%s\"\n" + "FROM pg_catalog.pg_ts_config c\n" + "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace \n", + _("Schema"), + _("Name"), + _("Description") + ); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + "n.nspname", "c.cfgname", NULL, + "pg_catalog.pg_ts_config_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 text search configurations"); + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; +} + +static bool +listTSConfigsVerbose(const char *pattern) +{ + PQExpBufferData buf; + PGresult *res; + int i; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT c.oid, c.cfgname,\n" + " n.nspname, \n" + " p.prsname, \n" + " np.nspname as pnspname \n" + "FROM pg_catalog.pg_ts_config c \n" + " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace, \n" + " pg_catalog.pg_ts_parser p \n" + " LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.prsnamespace \n" + "WHERE p.oid = c.cfgparser\n" + ); + + processSQLNamePattern(pset.db, &buf, pattern, true, false, + "n.nspname", "c.cfgname", NULL, + "pg_catalog.pg_ts_config_is_visible(c.oid)"); + + appendPQExpBuffer(&buf, "ORDER BY 3, 2;"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + if (PQntuples(res) == 0) + { + if (!pset.quiet) + fprintf(stderr, _("Did not find any text search configuration named \"%s\".\n"), + pattern); + PQclear(res); + return false; + } + + for (i = 0; i < PQntuples(res); i++) + { + const char *oid; + const char *cfgname; + const char *nspname = NULL; + const char *prsname; + const char *pnspname = NULL; + + oid = PQgetvalue(res, i, 0); + cfgname = PQgetvalue(res, i, 1); + if (!PQgetisnull(res, i, 2)) + nspname = PQgetvalue(res, i, 2); + prsname = PQgetvalue(res, i, 3); + if (!PQgetisnull(res, i, 4)) + pnspname = PQgetvalue(res, i, 4); + + if (!describeOneTSConfig(oid, nspname, cfgname, pnspname, prsname)) + { + PQclear(res); + return false; + } + + if (cancel_pressed) + { + PQclear(res); + return false; + } + } + + PQclear(res); + return true; +} + +static bool +describeOneTSConfig(const char *oid, const char *nspname, const char *cfgname, + const char *pnspname, const char *prsname) +{ + PQExpBufferData buf, + title; + PGresult *res; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT \n" + " ( SELECT t.alias FROM \n" + " pg_catalog.ts_token_type(c.cfgparser) AS t \n" + " WHERE t.tokid = m.maptokentype ) AS \"%s\", \n" + " pg_catalog.btrim( \n" + " ARRAY( SELECT mm.mapdict::pg_catalog.regdictionary \n" + " FROM pg_catalog.pg_ts_config_map AS mm \n" + " WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype \n" + " ORDER BY mapcfg, maptokentype, mapseqno \n" + " ) :: pg_catalog.text , \n" + " '{}') AS \"%s\" \n" + "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m \n" + "WHERE c.oid = '%s' AND m.mapcfg = c.oid \n" + "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser \n" + "ORDER BY 1", + _("Token"), + _("Dictionaries"), + oid); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + initPQExpBuffer(&title); + + if (nspname) + appendPQExpBuffer(&title, _("Text search configuration \"%s.%s\""), nspname, cfgname); + else + appendPQExpBuffer(&title, _("Text search configuration \"%s\""), cfgname); + + if (pnspname) + appendPQExpBuffer(&title, _("\nParser: \"%s.%s\""), pnspname, prsname); + else + appendPQExpBuffer(&title, _("\nParser: \"%s\""), prsname); + + myopt.nullPrint = NULL; + myopt.title = title.data; + myopt.footers = NULL; + myopt.default_footer = false; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + termPQExpBuffer(&title); + + PQclear(res); + return true; +} diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index 88d355f57b2..1ba3b54fc14 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2007, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/describe.h,v 1.33 2007/01/05 22:19:49 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/psql/describe.h,v 1.34 2007/08/21 01:11:22 tgl Exp $ */ #ifndef DESCRIBE_H #define DESCRIBE_H @@ -36,6 +36,18 @@ extern bool objectDescription(const char *pattern); /* \d foo */ extern bool describeTableDetails(const char *pattern, bool verbose); +/* \dF */ +extern bool listTSConfigs(const char *pattern, bool verbose); + +/* \dFp */ +extern bool listTSParsers(const char *pattern, bool verbose); + +/* \dFd */ +extern bool listTSDictionaries(const char *pattern, bool verbose); + +/* \dFt */ +extern bool listTSTemplates(const char *pattern, bool verbose); + /* \l */ extern bool listAllDbs(bool verbose); diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 027b5cb0207..b220339a498 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2007, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.117 2007/02/23 18:20:59 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.118 2007/08/21 01:11:22 tgl Exp $ */ #include "postgres_fe.h" @@ -219,6 +219,10 @@ slashUsage(unsigned short int pager) fprintf(output, _(" \\dd [PATTERN] show comment for object\n")); fprintf(output, _(" \\dD [PATTERN] list domains\n")); fprintf(output, _(" \\df [PATTERN] list functions (add \"+\" for more detail)\n")); + fprintf(output, _(" \\dF [PATTERN] list text search configurations (add \"+\" for more detail)\n")); + fprintf(output, _(" \\dFd [PATTERN] list text search dictionaries (add \"+\" for more detail)\n")); + fprintf(output, _(" \\dFt [PATTERN] list text search templates\n")); + fprintf(output, _(" \\dFp [PATTERN] list text search parsers (add \"+\" for more detail)\n")); fprintf(output, _(" \\dg [PATTERN] list groups\n")); fprintf(output, _(" \\dn [PATTERN] list schemas (add \"+\" for more detail)\n")); fprintf(output, _(" \\do [NAME] list operators\n")); -- cgit v1.2.3