mirror of
https://github.com/HypoPG/hypopg
synced 2026-05-24 01:28:51 +00:00
Add a hypopg_get_indexdef(oid) function
This commit is contained in:
parent
b6ce86d59d
commit
d2cfd7898f
7 changed files with 213 additions and 1 deletions
2
TODO.md
2
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
|
||||
--------------
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
151
hypopg.c
151
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; keyno<entry->ncolumns; 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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Reference in a new issue