From d2cfd7898fa6def5e6b6275439e17f6f1cbf8b53 Mon Sep 17 00:00:00 2001 From: Julien Rouhaud Date: Wed, 16 Nov 2016 23:49:27 +0100 Subject: [PATCH] Add a hypopg_get_indexdef(oid) function --- TODO.md | 2 +- expected/hypopg.out | 7 ++ hypopg--1.1.0dev.sql | 6 ++ hypopg.c | 151 +++++++++++++++++++++++++++++++++++++++++++ hypopg_import.c | 44 +++++++++++++ hypopg_import.h | 1 + test/sql/hypopg.sql | 3 + 7 files changed, 213 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 115b216..851bccd 100644 --- a/TODO.md +++ b/TODO.md @@ -17,7 +17,7 @@ Important - Add some more (or enhance) function. Following are interesting: - [X] estimated index size - [ ] estimated number of lines -- [ ] add hypopg_get_indexdef(oid) (based on src/backend/utils/adt/ruleutils.c/pg_get_indexdef_worker()) +- [X] add hypopg_get_indexdef(oid) Less important -------------- diff --git a/expected/hypopg.out b/expected/hypopg.out index f4aba0b..baa3b34 100644 --- a/expected/hypopg.out +++ b/expected/hypopg.out @@ -155,3 +155,10 @@ WHERE e ~ 'Index.*<\d+>btree_hypo.*'; 1 (1 row) +-- Deparse an index DDL, with almost every possible pathcode +SELECT hypopg_get_indexdef(indexrelid) FROM hypopg_create_index('create index on hypo using btree(id desc nulls first, cast(md5(val) as bpchar) bpchar_pattern_ops) with (fillfactor = 10) WHERE id < 1000 AND id +1 %2 = 3'); + hypopg_get_indexdef +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE INDEX ON public.hypo USING btree (id DESC, ((md5(hypo.val))::bpchar) bpchar_pattern_ops) WITH (fillfactor = 10) WHERE ((id < 1000) AND ((id + (1 % 2)) = 3)) +(1 row) + diff --git a/hypopg--1.1.0dev.sql b/hypopg--1.1.0dev.sql index 668a149..a4567d1 100644 --- a/hypopg--1.1.0dev.sql +++ b/hypopg--1.1.0dev.sql @@ -52,3 +52,9 @@ hypopg_relation_size(IN indexid oid) RETURNS bigint LANGUAGE c COST 100 AS '$libdir/hypopg', 'hypopg_relation_size'; + +CREATE FUNCTION +hypopg_get_indexdef(IN indexid oid) + RETURNS text +LANGUAGE C STRICT VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_get_indexdef'; diff --git a/hypopg.c b/hypopg.c index 9b56233..66cc512 100644 --- a/hypopg.c +++ b/hypopg.c @@ -59,6 +59,7 @@ #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "utils/ruleutils.h" #include "utils/syscache.h" #include "hypopg_import.h" @@ -162,12 +163,14 @@ Datum hypopg(PG_FUNCTION_ARGS); Datum hypopg_create_index(PG_FUNCTION_ARGS); Datum hypopg_drop_index(PG_FUNCTION_ARGS); Datum hypopg_relation_size(PG_FUNCTION_ARGS); +Datum hypopg_get_indexdef(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(hypopg_reset); PG_FUNCTION_INFO_V1(hypopg); PG_FUNCTION_INFO_V1(hypopg_create_index); PG_FUNCTION_INFO_V1(hypopg_drop_index); PG_FUNCTION_INFO_V1(hypopg_relation_size); +PG_FUNCTION_INFO_V1(hypopg_get_indexdef); static hypoEntry *hypo_newEntry(Oid relid, char *accessMethod, int ncolumns, List *options); @@ -1507,6 +1510,154 @@ hypopg_relation_size(PG_FUNCTION_ARGS) PG_RETURN_INT64(pages * BLCKSZ); } +/* + * Deparse an hypoEntry, indentified by its indexid to the actual CREATE INDEX + * command. + * + * Heavilty inspired on pg_get_indexdef_worker() + */ + +Datum +hypopg_get_indexdef(PG_FUNCTION_ARGS) +{ + Oid indexid = PG_GETARG_OID(0); + ListCell *indexpr_item; + StringInfoData buf; + hypoEntry *entry; + ListCell *lc; + List *context; + int keyno, cpt; + + foreach(lc, entries) + { + entry = (hypoEntry *) lfirst(lc); + + if (entry->oid == indexid) + break; + } + + if (!entry || entry->oid != indexid) + PG_RETURN_NULL(); + + initStringInfo(&buf); + appendStringInfo(&buf, "CREATE %s ON %s.%s USING %s (", + (entry->unique ? "UNIQUE INDEX" : "INDEX"), + quote_identifier(get_namespace_name(get_rel_namespace(entry->relid))), + quote_identifier(get_rel_name(entry->relid)), + get_am_name(entry->relam)); + + indexpr_item = list_head(entry->indexprs); + + context = deparse_context_for(get_rel_name(entry->relid), entry->relid); + + for (keyno=0; keynoncolumns; keyno++) + { + Oid indcoll; + Oid keycoltype; + Oid keycolcollation; + char *str; + + if (keyno != 0) + appendStringInfo(&buf, ", "); + + if (entry->indexkeys[keyno] != 0) + { + int32 keycoltypmod; + appendStringInfo(&buf, "%s", get_attname(entry->relid, + entry->indexkeys[keyno])); + + get_atttypetypmodcoll(entry->relid, entry->indexkeys[keyno], + &keycoltype, &keycoltypmod, + &keycolcollation); + } + else + { + /* expressional index */ + Node *indexkey; + + if (indexpr_item == NULL) + elog(ERROR, "too few entries in indexprs list"); + indexkey = (Node *) lfirst(indexpr_item); + indexpr_item = lnext(indexpr_item); + + /* Deparse */ + str = deparse_expression(indexkey, context, false, false); + + /* Need parens if it's not a bare function call */ + if (indexkey && IsA(indexkey, FuncExpr) && + ((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL) + appendStringInfoString(&buf, str); + else + appendStringInfo(&buf, "(%s)", str); + + keycoltype = exprType(indexkey); + keycolcollation = exprCollation(indexkey); + + cpt++; + } + + /* Add collation, if not default for column */ + indcoll = entry->indexcollations[keyno]; + if (OidIsValid(indcoll) && indcoll != keycolcollation) + appendStringInfo(&buf, " COLLATE %s", + generate_collation_name((indcoll))); + + /* Add the operator class name, if not default */ + get_opclass_name(entry->opclass[keyno], entry->opcintype[keyno], &buf); + + /* Add options if relevant */ + if (entry->amcanorder) + { + /* if it supports sort ordering, report DESC and NULLS opts */ + if (entry->reverse_sort[keyno]) + { + appendStringInfoString(&buf, " DESC"); + /* NULLS FIRST is the default in this case */ + if (!(entry->nulls_first[keyno])) + appendStringInfoString(&buf, " NULLS LAST"); + } + else + { + if (entry->nulls_first[keyno]) + appendStringInfoString(&buf, " NULLS FIRST"); + } + } + } + + appendStringInfo(&buf, ")"); + + if (entry->options) + { + appendStringInfo(&buf, " WITH ("); + + foreach(lc, entry->options) + { + DefElem *elem = (DefElem *) lfirst(lc); + + appendStringInfo(&buf, "%s = ", elem->defname); + + if (strcmp(elem->defname, "fillfactor") == 0) + appendStringInfo(&buf, "%d", (int32) intVal(elem->arg)); + else if (strcmp(elem->defname, "pages_per_range") == 0) + appendStringInfo(&buf, "%d", (int32) intVal(elem->arg)); + else if (strcmp(elem->defname, "length") == 0) + appendStringInfo(&buf, "%d", (int32) intVal(elem->arg)); + else + elog(WARNING," hypopg: option %s unhandled, please report the bug", + elem->defname); + } + appendStringInfo(&buf, ")"); + } + + if (entry->indpred) + { + appendStringInfo(&buf, " WHERE %s", deparse_expression((Node *) + make_ands_explicit(entry->indpred), context, false, false)); + } + + PG_RETURN_TEXT_P(cstring_to_text(buf.data)); +} + /* Simple function to set the indexname, dealing with max name length, and the * ending \0 diff --git a/hypopg_import.c b/hypopg_import.c index 4d24a1a..570db3d 100644 --- a/hypopg_import.c +++ b/hypopg_import.c @@ -23,6 +23,7 @@ #include "optimizer/planner.h" #include "parser/parse_coerce.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" @@ -263,3 +264,46 @@ CheckMutability(Expr *expr) /* Now we can search for non-immutable functions */ return contain_mutable_functions((Node *) expr); } + +/* + * Copied from /git/postgresql/src/backend/utils/adt/ruleutils.c, not exported. + * + * get_opclass_name - fetch name of an index operator class + * + * The opclass name is appended (after a space) to buf. + * + * Output is suppressed if the opclass is the default for the given + * actual_datatype. (If you don't want this behavior, just pass + * InvalidOid for actual_datatype.) + */ +void +get_opclass_name(Oid opclass, Oid actual_datatype, + StringInfo buf) +{ + HeapTuple ht_opc; + Form_pg_opclass opcrec; + char *opcname; + char *nspname; + + ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); + if (!HeapTupleIsValid(ht_opc)) + elog(ERROR, "cache lookup failed for opclass %u", opclass); + opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc); + + if (!OidIsValid(actual_datatype) || + GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass) + { + /* Okay, we need the opclass name. Do we need to qualify it? */ + opcname = NameStr(opcrec->opcname); + if (OpclassIsVisible(opclass)) + appendStringInfo(buf, " %s", quote_identifier(opcname)); + else + { + nspname = get_namespace_name(opcrec->opcnamespace); + appendStringInfo(buf, " %s.%s", + quote_identifier(nspname), + quote_identifier(opcname)); + } + } + ReleaseSysCache(ht_opc); +} diff --git a/hypopg_import.h b/hypopg_import.h index d69d328..20142fb 100644 --- a/hypopg_import.h +++ b/hypopg_import.h @@ -22,3 +22,4 @@ extern Oid GetIndexOpClass(List *opclass, Oid attrType, extern void CheckPredicate(Expr *predicate); extern bool CheckMutability(Expr *expr); +extern void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf); diff --git a/test/sql/hypopg.sql b/test/sql/hypopg.sql index f767a37..0b56ace 100644 --- a/test/sql/hypopg.sql +++ b/test/sql/hypopg.sql @@ -97,3 +97,6 @@ FROM public.hypopg_create_index('CREATE INDEX ON hypo (md5(val))'); -- Should use hypothetical index SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE md5(val) = md5(''line 1'')') e WHERE e ~ 'Index.*<\d+>btree_hypo.*'; + +-- Deparse an index DDL, with almost every possible pathcode +SELECT hypopg_get_indexdef(indexrelid) FROM hypopg_create_index('create index on hypo using btree(id desc nulls first, cast(md5(val) as bpchar) bpchar_pattern_ops) with (fillfactor = 10) WHERE id < 1000 AND id +1 %2 = 3');