diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b61991b..5fae84b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -19,3 +19,4 @@ People who contributed to hypopg: * github user nikhil-postgres * Xiaozhe Yao * Krzysztof Szularz + * NutVII \ No newline at end of file diff --git a/Makefile b/Makefile index e245936..2ac6b7d 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,8 @@ ifneq ($(MAJORVERSION),$(filter $(MAJORVERSION), 9.2 9.3 9.4 9.5 9.6)) REGRESS += hypo_hash endif +REGRESS += hypo_hide_index + DEBUILD_ROOT = /tmp/$(EXTENSION) deb: release-zip diff --git a/README.md b/README.md index 9f28f94..f83549d 100644 --- a/README.md +++ b/README.md @@ -99,3 +99,88 @@ To remove your backend's hypothetical indexes, you can use the function `hypopg_drop_index(indexrelid)` with the OID that the `hypopg_list_indexes` view returns and call `hypopg_reset()` to remove all at once, or just close your current connection. + +Continuing with the above case, you can `hide existing indexes`, +but should be use `hypopg_reset()` to clear the previous effects of other indexes at first. + +Create two real indexes and run `EXPLAIN`: + + rjuju=# SELECT hypopg_reset(); + rjuju=# CREATE INDEX ON hypo(id); + rjuju=# CREATE INDEX ON hypo(id, val); + rjuju=# EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ---------------------------------------------------------------------------------- + Index Only Scan using hypo_id_val_idx on hypo (cost=0.29..8.30 rows=1 width=13) + Index Cond: (id = 1) + (2 rows) + +The query plan is using the `hypo_id_val_idx` index. Use `hypopg_hide_index(oid)` to hide one of the indexes: + + rjuju=# SELECT hypopg_hide_index('hypo_id_val_idx'::REGCLASS); + rjuju=# EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------------------------- + Index Scan using hypo_id_idx on hypo (cost=0.29..8.30 rows=1 width=13) + Index Cond: (id = 1) + (2 rows) + +The query plan is using the other index `hypo_id_idx` now. Use `hypopg_hide_index(oid)` to hide it: + + rjuju=# SELECT hypopg_hide_index('hypo_id_idx'::REGCLASS); + rjuju=# EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------- + Seq Scan on hypo (cost=0.00..180.00 rows=1 width=13) + Filter: (id = 1) + (2 rows) + +And now the query plan changes back to `Seq Scan`. Use `hypopg_unhide_index(oid)` to restore index: + + rjuju=# SELECT hypopg_unhide_index('hypo_id_idx'::regclass); + rjuju=# EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------------------------- + Index Scan using hypo_id_idx on hypo (cost=0.29..8.30 rows=1 width=13) + Index Cond: (id = 1) + (2 rows) + +Of course, you can also hide hypothetical indexes: + + rjuju=# SELECT hypopg_create_index('CREATE INDEX ON hypo(id)'); + rjuju=# EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------------------------------------ + Index Scan using "<12659>btree_hypo_id" on hypo (cost=0.04..8.05 rows=1 width=13) + Index Cond: (id = 1) + (2 rows) + + rjuju=# SELECT hypopg_hide_index(12659); + rjuju=# EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------- + Seq Scan on hypo (cost=0.00..180.00 rows=1 width=13) + Filter: (id = 1) + (2 rows) + +You can check which indexes are hidden using `hypopg_hidden_indexes()` or the `hypopg_hidden_indexes` view: + + rjuju=# SELECT * FROM hypopg_hidden_indexes(); + indexid + --------- + 526604 + 526603 + 12659 + (3 rows) + + rjuju=# SELECT * FROM hypopg_hidden_indexes; + indexrelid | index_name | schema_name | table_name | am_name | is_hypo + ------------+----------------------+-------------+------------+---------+--------- + 12659 | <12659>btree_hypo_id | public | hypo | btree | t + 526603 | hypo_id_idx | public | hypo | btree | f + 526604 | hypo_id_val_idx | public | hypo | btree | f + (3 rows) + +To restore all existing indexes, you can use the function `hypopg_unhide_all_indexes()`. +Note that the functionality to hide existing indexes only applies to the EXPLAIN command in the current session +and will not affect other sessions. \ No newline at end of file diff --git a/docs/usage.rst b/docs/usage.rst index 231c6d6..6d2cf27 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -224,3 +224,169 @@ Some other convenience functions and views are available: - **hypopg_drop_index(oid)**: function that removes the given hypothetical index - **hypopg_reset()**: function that removes all hypothetical indexes + +Hypothetically hide existing indexes +------------------------------------ + +You can hide both existing and hypothetical indexes hypothetically. +If you want to test it as described in the documentation, +you should first use **hypopg_reset()** to clear the effects of any other hypothetical indexes. + +As a simple case, let's consider two indexes: + +.. code-block:: psql + + SELECT hypopg_reset(); + CREATE INDEX ON hypo(id); + CREATE INDEX ON hypo(id, val); + +.. code-block:: psql + :emphasize-lines: 4 + + EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ---------------------------------------------------------------------------------- + Index Only Scan using hypo_id_val_idx on hypo (cost=0.29..8.30 rows=1 width=13) + Index Cond: (id = 1) + (2 rows) + +The query plan is using the **hypo_id_val_idx** index now. + +- **hypopg_hide_index(oid)**: function that allows you to hide an index in the EXPLAIN output by using its OID. + It returns `true` if the index was successfully hidden, and `false` otherwise. + +.. code-block:: psql + :emphasize-lines: 10 + + SELECT hypopg_hide_index('hypo_id_val_idx'::REGCLASS); + hypopg_hide_index + ------------------- + t + (1 row) + + EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------------------------- + Index Scan using hypo_id_idx on hypo (cost=0.29..8.30 rows=1 width=13) + Index Cond: (id = 1) + (2 rows) + +As an example, let's assume that the query plan is currently using the **hypo_id_val_idx** index. +To continue testing, use the **hypopg_hide_index(oid)** function to hide another index. + +.. code-block:: psql + :emphasize-lines: 10 + + SELECT hypopg_hide_index('hypo_id_idx'::REGCLASS); + hypopg_hide_index + ------------------- + t + (1 row) + + EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------- + Seq Scan on hypo (cost=0.00..180.00 rows=1 width=13) + Filter: (id = 1) + (2 rows) + +- **hypopg_unhide_index(oid)**: function that restore a previously hidden index in the EXPLAIN output by using its OID. + It returns `true` if the index was successfully restored, and `false` otherwise. + +.. code-block:: psql + :emphasize-lines: 10 + + SELECT hypopg_unhide_index('hypo_id_idx'::regclass); + hypopg_unhide_index + ------------------- + t + (1 row) + + EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------------------------- + Index Scan using hypo_id_idx on hypo (cost=0.29..8.30 rows=1 width=13) + Index Cond: (id = 1) + (2 rows) + +- **hypopg_unhide_all_index()**: function that restore all hidden indexes and returns void. + +- **hypopg_hidden_indexes()**: function that returns a list of OIDs for all hidden indexes. + +.. code-block:: psql + + SELECT * FROM hypopg_hidden_indexes(); + indexid + --------- + 526604 + (1 rows) + +- **hypopg_hidden_indexes**: view that returns a formatted list of all hidden indexes. + +.. code-block:: psql + + SELECT * FROM hypopg_hidden_indexes; + indexrelid | index_name | schema_name | table_name | am_name | is_hypo + -------------+----------------------+-------------+------------+---------+--------- + 526604 | hypo_id_val_idx | public | hypo | btree | f + (1 rows) + +.. note:: + + Hypothetical indexes can be hidden as well. + +.. code-block:: psql + :emphasize-lines: 10 + + SELECT hypopg_create_index('CREATE INDEX ON hypo(id)'); + hypopg_create_index + ------------------------------ + (12659,<12659>btree_hypo_id) + (1 row) + + EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------------------------------------ + Index Scan using "<12659>btree_hypo_id" on hypo (cost=0.04..8.05 rows=1 width=13) + Index Cond: (id = 1) + (2 rows) + +Now that the hypothetical index is being used, we can try hiding it to see the change: + +.. code-block:: psql + :emphasize-lines: 10 + + SELECT hypopg_hide_index(12659); + hypopg_hide_index + ------------------- + t + (1 row) + + EXPLAIN SELECT * FROM hypo WHERE id = 1; + QUERY PLAN + ------------------------------------------------------------------------- + Index Scan using hypo_id_idx on hypo (cost=0.29..8.30 rows=1 width=13) + Index Cond: (id = 1) + (2 rows) + + SELECT * FROM hypopg_hidden_indexes; + indexrelid | index_name | schema_name | table_name | am_name | is_hypo + -------------+----------------------+-------------+------------+---------+--------- + 12659 | <12659>btree_hypo_id | public | hypo | btree | t + 526604 | hypo_id_val_idx | public | hypo | btree | f + (2 rows) + +.. note:: + + If a hypothetical index has been hidden, it will be automatically unhidden + when it is deleted using **hypopg_drop_index(oid)** or **hypopg_reset()**. + +.. code-block:: psql + + SELECT hypopg_drop_index(12659); + + SELECT * FROM hypopg_hidden_indexes; + indexrelid | index_name | schema_name | table_name | am_name | is_hypo + -------------+----------------------+-------------+------------+---------+--------- + 526604 | hypo_id_val_idx | public | hypo | btree | f + (2 rows) \ No newline at end of file diff --git a/expected/hypo_hide_index.out b/expected/hypo_hide_index.out new file mode 100644 index 0000000..c63d47b --- /dev/null +++ b/expected/hypo_hide_index.out @@ -0,0 +1,268 @@ +-- Hypothetically hiding existing indexes tests +-- Remove all the hypothetical indexes if any +SELECT hypopg_reset(); + hypopg_reset +-------------- + +(1 row) + +-- The EXPLAIN initial state +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_idx'; + count +------- + 0 +(1 row) + +-- Create real index in hypo and use this index +CREATE INDEX hypo_id_idx ON hypo(id); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_idx'; + count +------- + 1 +(1 row) + +-- Should be zero +SELECT COUNT(*) FROM hypopg_hidden_indexes(); + count +------- + 0 +(1 row) + +-- The hypo_id_idx index should not be used +SELECT hypopg_hide_index('hypo_id_idx'::regclass); + hypopg_hide_index +------------------- + t +(1 row) + +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_idx'; + count +------- + 0 +(1 row) + +-- Should be only one record +SELECT COUNT(*) FROM hypopg_hidden_indexes(); + count +------- + 1 +(1 row) + +SELECT table_name,index_name FROM hypopg_hidden_indexes; + table_name | index_name +------------+------------- + hypo | hypo_id_idx +(1 row) + +-- Create the real index again and +-- EXPLAIN should use this index instead of the previous one +CREATE index hypo_id_val_idx ON hypo(id, val); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_val_idx'; + count +------- + 1 +(1 row) + +-- Shouldn't use any index +SELECT hypopg_hide_index('hypo_id_val_idx'::regclass); + hypopg_hide_index +------------------- + t +(1 row) + +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_val_idx'; + count +------- + 0 +(1 row) + +-- Should be two records +SELECT table_name,index_name FROM hypopg_hidden_indexes; + table_name | index_name +------------+----------------- + hypo | hypo_id_idx + hypo | hypo_id_val_idx +(2 rows) + +-- Try to add one repeatedly or add another wrong index oid +SELECT hypopg_hide_index('hypo_id_idx'::regclass); + hypopg_hide_index +------------------- + f +(1 row) + +SELECT hypopg_hide_index('hypo'::regclass); + hypopg_hide_index +------------------- + f +(1 row) + +SELECT hypopg_hide_index(0); + hypopg_hide_index +------------------- + f +(1 row) + +-- Also of course can be used to hide hypothetical indexes +SELECT COUNT(*) FROM hypopg_create_index('create index on hypo(id,val);'); + count +------- + 1 +(1 row) + +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'Index.*<\d+>btree_hypo.*'; + count +------- + 1 +(1 row) + +SELECT hypopg_hide_index((SELECT indexrelid FROM hypopg_list_indexes LIMIT 1)); + hypopg_hide_index +------------------- + t +(1 row) + +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'Index.*<\d+>btree_hypo.*'; + count +------- + 0 +(1 row) + +-- Should be only three records +SELECT COUNT(*) FROM hypopg_hidden_indexes; + count +------- + 3 +(1 row) + +-- Hypothetical indexes should be unhidden when deleting +SELECT hypopg_drop_index((SELECT indexrelid FROM hypopg_list_indexes LIMIT 1)); + hypopg_drop_index +------------------- + t +(1 row) + +-- Should become two records +SELECT COUNT(*) FROM hypopg_hidden_indexes; + count +------- + 2 +(1 row) + +-- Hypopg_reset can also unhidden the hidden indexes +-- due to the deletion of hypothetical indexes. +SELECT COUNT(*) FROM hypopg_create_index('create index on hypo(id,val);'); + count +------- + 1 +(1 row) + +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'Index.*<\d+>btree_hypo.*'; + count +------- + 1 +(1 row) + +SELECT hypopg_hide_index((SELECT indexrelid FROM hypopg_list_indexes LIMIT 1)); + hypopg_hide_index +------------------- + t +(1 row) + +-- Changed from three records to two records. +SELECT COUNT(*) FROM hypopg_hidden_indexes; + count +------- + 3 +(1 row) + +SELECT hypopg_reset(); + hypopg_reset +-------------- + +(1 row) + +SELECT COUNT(*) FROM hypopg_hidden_indexes; + count +------- + 2 +(1 row) + +-- Unhide an index +SELECT hypopg_unhide_index('hypo_id_idx'::regclass); + hypopg_unhide_index +--------------------- + t +(1 row) + +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_idx'; + count +------- + 1 +(1 row) + +-- Should become one record +SELECT table_name,index_name FROM hypopg_hidden_indexes; + table_name | index_name +------------+----------------- + hypo | hypo_id_val_idx +(1 row) + +-- Try to delete one repeatedly or delete another wrong index oid +SELECT hypopg_unhide_index('hypo_id_idx'::regclass); + hypopg_unhide_index +--------------------- + f +(1 row) + +SELECT hypopg_unhide_index('hypo'::regclass); + hypopg_unhide_index +--------------------- + f +(1 row) + +SELECT hypopg_unhide_index(0); + hypopg_unhide_index +--------------------- + f +(1 row) + +-- Should still have one record +SELECT table_name,index_name FROM hypopg_hidden_indexes; + table_name | index_name +------------+----------------- + hypo | hypo_id_val_idx +(1 row) + +-- Unhide all indexes +SELECT hypopg_unhide_all_indexes(); + hypopg_unhide_all_indexes +--------------------------- + +(1 row) + +-- Should change back to the original zero +SELECT COUNT(*) FROM hypopg_hidden_indexes(); + count +------- + 0 +(1 row) + +-- Clean real indexes and hypothetical indexes +DROP INDEX hypo_id_idx; +DROP INDEX hypo_id_val_idx; +SELECT hypopg_reset(); + hypopg_reset +-------------- + +(1 row) + diff --git a/hypopg--1.3.1--1.4.0.sql b/hypopg--1.3.1--1.4.0.sql index aafba06..ce0a3a1 100644 --- a/hypopg--1.3.1--1.4.0.sql +++ b/hypopg--1.3.1--1.4.0.sql @@ -5,3 +5,46 @@ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION hypopg" to load this file. \quit + +CREATE FUNCTION +hypopg_hide_index(IN indexid oid) + RETURNS bool + LANGUAGE C STRICT VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_hide_index'; + +CREATE FUNCTION +hypopg_unhide_index(IN indexid oid) + RETURNS bool + LANGUAGE C STRICT VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_unhide_index'; + +CREATE FUNCTION +hypopg_unhide_all_indexes() + RETURNS void + LANGUAGE C VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_unhide_all_indexes'; + +CREATE FUNCTION hypopg_hidden_indexes() + RETURNS TABLE (indexid oid) + LANGUAGE C STRICT VOLATILE +AS '$libdir/hypopg', 'hypopg_hidden_indexes'; + +CREATE VIEW hypopg_hidden_indexes +AS + SELECT h.indexid AS indexrelid, + i.relname AS index_name, + n.nspname AS schema_name, + t.relname AS table_name, + m.amname AS am_name, + false AS is_hypo + FROM hypopg_hidden_indexes() h + JOIN pg_index x ON x.indexrelid = h.indexid + JOIN pg_class i ON i.oid = h.indexid + JOIN pg_namespace n ON n.oid = i.relnamespace + JOIN pg_class t ON t.oid = x.indrelid + JOIN pg_am m ON m.oid = i.relam + UNION ALL + SELECT hl.*, true AS is_hypo + FROM hypopg_hidden_indexes() hi + JOIN hypopg_list_indexes hl on hl.indexrelid = hi.indexid + ORDER BY index_name; \ No newline at end of file diff --git a/hypopg--1.4.0.sql b/hypopg--1.4.0.sql index 8492966..f9d580c 100644 --- a/hypopg--1.4.0.sql +++ b/hypopg--1.4.0.sql @@ -60,3 +60,46 @@ hypopg_get_indexdef(IN indexid oid) RETURNS text LANGUAGE C STRICT VOLATILE COST 100 AS '$libdir/hypopg', 'hypopg_get_indexdef'; + +CREATE FUNCTION +hypopg_hide_index(IN indexid oid) + RETURNS bool + LANGUAGE C STRICT VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_hide_index'; + +CREATE FUNCTION +hypopg_unhide_index(IN indexid oid) + RETURNS bool + LANGUAGE C STRICT VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_unhide_index'; + +CREATE FUNCTION +hypopg_unhide_all_indexes() + RETURNS void + LANGUAGE C VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_unhide_all_indexes'; + +CREATE FUNCTION hypopg_hidden_indexes() + RETURNS TABLE (indexid oid) + LANGUAGE C STRICT VOLATILE +AS '$libdir/hypopg', 'hypopg_hidden_indexes'; + +CREATE VIEW hypopg_hidden_indexes +AS + SELECT h.indexid AS indexrelid, + i.relname AS index_name, + n.nspname AS schema_name, + t.relname AS table_name, + m.amname AS am_name, + false AS is_hypo + FROM hypopg_hidden_indexes() h + JOIN pg_index x ON x.indexrelid = h.indexid + JOIN pg_class i ON i.oid = h.indexid + JOIN pg_namespace n ON n.oid = i.relnamespace + JOIN pg_class t ON t.oid = x.indrelid + JOIN pg_am m ON m.oid = i.relam + UNION ALL + SELECT hl.*, true AS is_hypo + FROM hypopg_hidden_indexes() hi + JOIN hypopg_list_indexes hl on hl.indexrelid = hi.indexid + ORDER BY index_name; \ No newline at end of file diff --git a/hypopg.c b/hypopg.c index 5c042f2..dbcfb56 100644 --- a/hypopg.c +++ b/hypopg.c @@ -122,6 +122,7 @@ _PG_init(void) isExplain = false; hypoIndexes = NIL; + hypoHiddenIndexes = NIL; HypoMemoryContext = AllocSetContextCreate(TopMemoryContext, "HypoPG context", @@ -528,6 +529,8 @@ hypo_get_relation_info_hook(PlannerInfo *root, inhparent, rel, relation, entry); } } + + hypo_hideIndexes(rel); } /* Close the relation release the lock now */ diff --git a/hypopg_index.c b/hypopg_index.c index e7a0b7a..d77f584 100644 --- a/hypopg_index.c +++ b/hypopg_index.c @@ -81,6 +81,7 @@ static Oid BLOOM_AM_OID = InvalidOid; explain_get_index_name_hook_type prev_explain_get_index_name_hook; List *hypoIndexes; +List *hypoHiddenIndexes; /*--- Functions --- */ @@ -90,6 +91,10 @@ PG_FUNCTION_INFO_V1(hypopg_drop_index); PG_FUNCTION_INFO_V1(hypopg_relation_size); PG_FUNCTION_INFO_V1(hypopg_get_indexdef); PG_FUNCTION_INFO_V1(hypopg_reset_index); +PG_FUNCTION_INFO_V1(hypopg_hide_index); +PG_FUNCTION_INFO_V1(hypopg_unhide_index); +PG_FUNCTION_INFO_V1(hypopg_unhide_all_indexes); +PG_FUNCTION_INFO_V1(hypopg_hidden_indexes); static void hypo_addIndex(hypoIndex * entry); @@ -101,6 +106,7 @@ static void hypo_estimate_index(hypoIndex * entry, RelOptInfo *rel); static int hypo_estimate_index_colsize(hypoIndex * entry, int col); static void hypo_index_pfree(hypoIndex * entry); static bool hypo_index_remove(Oid indexid); +static bool hypo_index_unhide(Oid indexid); static const hypoIndex *hypo_index_store_parsetree(IndexStmt *node, const char *queryString); static hypoIndex * hypo_newIndex(Oid relid, char *accessMethod, int nkeycolumns, @@ -922,6 +928,9 @@ hypo_index_remove(Oid indexid) { ListCell *lc; + /* remove this index from the list of hidden indexes if present */ + hypo_index_unhide(indexid); + foreach(lc, hypoIndexes) { hypoIndex *entry = (hypoIndex *) lfirst(lc); @@ -933,6 +942,7 @@ hypo_index_remove(Oid indexid) return true; } } + return false; } @@ -1592,6 +1602,185 @@ hypopg_reset_index(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +/* + * Add the given oid for the list of hidden indexes + * if it's a valid index (hypothetical or real), and if not hidden already. + * Return true if the oid is added to the list, false otherwise. + */ +Datum +hypopg_hide_index(PG_FUNCTION_ARGS) +{ + Oid indexid = PG_GETARG_OID(0); + MemoryContext old_context; + bool is_hypo = false; + ListCell *lc; + + /* first check if it is in hypoIndexes */ + foreach(lc, hypoIndexes) + { + hypoIndex *entry = (hypoIndex *) lfirst(lc); + + if (entry->oid == indexid) + { + is_hypo = true; + break; + } + } + + if (!is_hypo) + { + HeapTuple index_tup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexid)); + + if (!HeapTupleIsValid(index_tup)) + return false; + + ReleaseSysCache(index_tup); + } + + if (list_member_oid(hypoHiddenIndexes, indexid)) + return false; + + old_context = MemoryContextSwitchTo(HypoMemoryContext); + hypoHiddenIndexes = lappend_oid(hypoHiddenIndexes, indexid); + MemoryContextSwitchTo(old_context); + + return true; +} + +/* + * Unhide the given index oid (hypothetical or not) to make it visible to + * the planner again. + */ +Datum +hypopg_unhide_index(PG_FUNCTION_ARGS) +{ + Oid indexid = PG_GETARG_OID(0); + + PG_RETURN_BOOL(hypo_index_unhide(indexid)); +} + +/* + * Restore all hidden index. + */ +Datum +hypopg_unhide_all_indexes(PG_FUNCTION_ARGS) +{ + list_free(hypoHiddenIndexes); + hypoHiddenIndexes = NIL; + PG_RETURN_VOID(); +} + +/* + * Get all hidden index oid. + */ +Datum +hypopg_hidden_indexes(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + MemoryContext oldcontext; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + ListCell *lc; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); + + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "indexid", OIDOID, -1, 0); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + foreach(lc, hypoHiddenIndexes) + { + Oid indexid = lfirst_oid(lc); + Datum values[HYPO_HIDDEN_INDEX_COLS]; + bool nulls[HYPO_HIDDEN_INDEX_COLS]; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[0] = ObjectIdGetDatum(indexid); + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} + +/* + * Remove the oid to restore this index on EXPLAIN. + */ +bool +hypo_index_unhide(Oid indexid) +{ + int prev_length = list_length(hypoHiddenIndexes); + + hypoHiddenIndexes = list_delete_oid(hypoHiddenIndexes, indexid); + return prev_length > list_length(hypoHiddenIndexes); +} + +/* + * Check rel and delete the same oid index as hypoHiddenIndexes + * in rel->indexlist. + */ +void +hypo_hideIndexes(RelOptInfo *rel) +{ + ListCell *cell = NULL; + + if (rel == NULL) + return; + + if (list_length(rel->indexlist) == 0 || list_length(hypoHiddenIndexes) == 0) + return; + + foreach(cell, hypoHiddenIndexes) + { + Oid oid = lfirst_oid(cell); + ListCell *lc = NULL; + +#if PG_VERSION_NUM >= 130000 + foreach(lc, rel->indexlist) + { + IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); + + if (index->indexoid == oid) + rel->indexlist = foreach_delete_current(rel->indexlist, lc); + } +#else + ListCell *next; + ListCell *prev = NULL; + + for (lc = list_head(rel->indexlist); lc != NULL; lc = next) + { + IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); + + next = lnext(lc); + if (index->indexoid == oid) + rel->indexlist = list_delete_cell(rel->indexlist, lc, prev); + else + prev = lc; + } +#endif + } +} /* Simple function to set the indexname, dealing with max name length, and the * ending \0 diff --git a/include/hypopg_index.h b/include/hypopg_index.h index 75cb178..02c7abd 100644 --- a/include/hypopg_index.h +++ b/include/hypopg_index.h @@ -24,6 +24,8 @@ #define HYPO_INDEX_NB_COLS 12 /* # of column hypopg() returns */ #define HYPO_INDEX_CREATE_COLS 2 /* # of column hypopg_create_index() * returns */ +#define HYPO_HIDDEN_INDEX_COLS 1 /* # of column hypopg_hidden_indexes() + * returns */ #if PG_VERSION_NUM >= 90600 /* hardcode some bloom values, bloom.h is not exported */ @@ -110,6 +112,9 @@ typedef struct hypoIndex /* List of hypothetic indexes for current backend */ extern List *hypoIndexes; +/* List of hypothetical hidden existing indexes for current backend */ +extern List *hypoHiddenIndexes; + /*--- Functions --- */ void hypo_index_reset(void); @@ -120,6 +125,10 @@ PGDLLEXPORT Datum hypopg_drop_index(PG_FUNCTION_ARGS); PGDLLEXPORT Datum hypopg_relation_size(PG_FUNCTION_ARGS); PGDLLEXPORT Datum hypopg_get_indexdef(PG_FUNCTION_ARGS); PGDLLEXPORT Datum hypopg_reset_index(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum hypopg_hide_index(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum hypopg_unhide_index(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum hypopg_unhide_all_indexes(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum hypopg_hidden_indexes(PG_FUNCTION_ARGS); extern explain_get_index_name_hook_type prev_explain_get_index_name_hook; hypoIndex *hypo_get_index(Oid indexId); @@ -131,5 +140,6 @@ void hypo_injectHypotheticalIndex(PlannerInfo *root, RelOptInfo *rel, Relation relation, hypoIndex * entry); +void hypo_hideIndexes(RelOptInfo *rel); #endif diff --git a/test/sql/hypo_hide_index.sql b/test/sql/hypo_hide_index.sql new file mode 100644 index 0000000..fa5875c --- /dev/null +++ b/test/sql/hypo_hide_index.sql @@ -0,0 +1,100 @@ +-- Hypothetically hiding existing indexes tests + +-- Remove all the hypothetical indexes if any +SELECT hypopg_reset(); + +-- The EXPLAIN initial state +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_idx'; + +-- Create real index in hypo and use this index +CREATE INDEX hypo_id_idx ON hypo(id); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_idx'; + +-- Should be zero +SELECT COUNT(*) FROM hypopg_hidden_indexes(); + +-- The hypo_id_idx index should not be used +SELECT hypopg_hide_index('hypo_id_idx'::regclass); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_idx'; + +-- Should be only one record +SELECT COUNT(*) FROM hypopg_hidden_indexes(); +SELECT table_name,index_name FROM hypopg_hidden_indexes; + +-- Create the real index again and +-- EXPLAIN should use this index instead of the previous one +CREATE index hypo_id_val_idx ON hypo(id, val); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_val_idx'; + +-- Shouldn't use any index +SELECT hypopg_hide_index('hypo_id_val_idx'::regclass); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_val_idx'; + +-- Should be two records +SELECT table_name,index_name FROM hypopg_hidden_indexes; + +-- Try to add one repeatedly or add another wrong index oid +SELECT hypopg_hide_index('hypo_id_idx'::regclass); +SELECT hypopg_hide_index('hypo'::regclass); +SELECT hypopg_hide_index(0); + +-- Also of course can be used to hide hypothetical indexes +SELECT COUNT(*) FROM hypopg_create_index('create index on hypo(id,val);'); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'Index.*<\d+>btree_hypo.*'; +SELECT hypopg_hide_index((SELECT indexrelid FROM hypopg_list_indexes LIMIT 1)); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'Index.*<\d+>btree_hypo.*'; + +-- Should be only three records +SELECT COUNT(*) FROM hypopg_hidden_indexes; + +-- Hypothetical indexes should be unhidden when deleting +SELECT hypopg_drop_index((SELECT indexrelid FROM hypopg_list_indexes LIMIT 1)); + +-- Should become two records +SELECT COUNT(*) FROM hypopg_hidden_indexes; + +-- Hypopg_reset can also unhidden the hidden indexes +-- due to the deletion of hypothetical indexes. +SELECT COUNT(*) FROM hypopg_create_index('create index on hypo(id,val);'); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'Index.*<\d+>btree_hypo.*'; +SELECT hypopg_hide_index((SELECT indexrelid FROM hypopg_list_indexes LIMIT 1)); + +-- Changed from three records to two records. +SELECT COUNT(*) FROM hypopg_hidden_indexes; +SELECT hypopg_reset(); +SELECT COUNT(*) FROM hypopg_hidden_indexes; + +-- Unhide an index +SELECT hypopg_unhide_index('hypo_id_idx'::regclass); +SELECT COUNT(*) FROM do_explain('SELECT * FROM hypo WHERE id = 1') e +WHERE e ~ 'hypo_id_idx'; + +-- Should become one record +SELECT table_name,index_name FROM hypopg_hidden_indexes; + +-- Try to delete one repeatedly or delete another wrong index oid +SELECT hypopg_unhide_index('hypo_id_idx'::regclass); +SELECT hypopg_unhide_index('hypo'::regclass); +SELECT hypopg_unhide_index(0); + +-- Should still have one record +SELECT table_name,index_name FROM hypopg_hidden_indexes; + +-- Unhide all indexes +SELECT hypopg_unhide_all_indexes(); + +-- Should change back to the original zero +SELECT COUNT(*) FROM hypopg_hidden_indexes(); + +-- Clean real indexes and hypothetical indexes +DROP INDEX hypo_id_idx; +DROP INDEX hypo_id_val_idx; +SELECT hypopg_reset();