diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b7fd3..9259444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ Changelog ========= +2023-05-27 version 1.4.0: +------------------------- + + **New features**: + + - Support hypothetically hiding existing indexes, hypothetical or not (github + user nutvii and Julien Rouhaud) + + **Miscellaneous**: + + - Have hypopg_relation_size() error out rather than returning 0 if called for + an oid that isn't a hypothetical index oid + - Slighthly reduce memory usage for hypothetical btree indexes without + INCLUDE keys + 2021-06-21 version 1.3.1: ------------------------- diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7c96ed9..5fae84b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -17,3 +17,6 @@ People who contributed to hypopg: * nagaraju11 * ibrahim edib kokdemir * github user nikhil-postgres + * Xiaozhe Yao + * Krzysztof Szularz + * NutVII \ No newline at end of file diff --git a/LICENSE b/LICENSE index 1ed145e..1267c02 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Portions Copyright (c) 2015-2021, PostgreSQL GLobal Development Group +Portions Copyright (c) 2015-2023, PostgreSQL GLobal Development Group Portions Copyright (c) 1994, The Regents of the University of California 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 d4f153a..32fd3c9 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ a future release. You can check the available hypothetical indexes in your own backend: - rjuju=# SELECT * FROM hypopg_list_indexes(); + rjuju=# SELECT * FROM hypopg_list_indexes ; indexrelid | indexname | nspname | relname | amname -----------+-------------------------------------------+---------+---------+-------- 205101 | <41072>btree_hypo_id | public | hypo | btree @@ -96,6 +96,91 @@ Of course, only `EXPLAIN` without `ANALYZE` will use hypothetical indexes: (5 rows) To remove your backend's hypothetical indexes, you can use the function -`hypopg_drop_index(indexrelid)` with the OID that the `hypopg_list_indexes()` -function returns and call `hypopg_reset()` to remove all at once, or just close +`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. diff --git a/debian/changelog b/debian/changelog index 633e4e0..fa28b39 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +hypopg (1.4.0-1) unstable; urgency=medium + + * New upstream version. + + -- Julien Rouhaud Mon, 31 Jul 2023 12:28:46 +0200 + hypopg (1.3.1-2) unstable; urgency=medium * Upload for PostgreSQL 15. diff --git a/debian/copyright b/debian/copyright index a978fb4..96f6f37 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,4 +1,4 @@ -Portions Copyright (c) 2015-2021, PostgreSQL GLobal Development Group +Portions Copyright (c) 2015-2023, PostgreSQL GLobal Development Group Portions Copyright (c) 1994, The Regents of the University of California diff --git a/docs/conf.py b/docs/conf.py index 689ac91..259e28d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ master_doc = 'index' # General information about the project. project = 'HypoPG' -copyright = '2015-2021, Julien Rouhaud' +copyright = '2015-2023, Julien Rouhaud' author = 'Julien Rouhaud' # The version info for the project you're documenting, acts as replacement for diff --git a/docs/index.rst b/docs/index.rst index 2f64e82..62ec24c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,4 @@ -.. title:: HypoPG: Hypothetical indexes for PostgreSQL +.. title:: HypoPG: Hypothetical indexes for PostgreSQL HypoPG ====== diff --git a/docs/installation.rst b/docs/installation.rst index 026fd27..df2fe8d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -13,7 +13,7 @@ Packages Hypopg is available as a package on some GNU/Linux distributions: -- RHEL/Centos +- RHEL/Rocky Linux HypoPG is available as a package using `the PGDG packages `_. @@ -25,6 +25,20 @@ Hypopg is available as a package on some GNU/Linux distributions: yum install hypopg +- Debian / Ubuntu + + HypoPG is available as a package using `the PGDG packages + `_. + + Once the PGDG repository is setup, you just need to install the package. As + root: + + .. code-block:: bash + + apt install postgresql-XY-hypopg + + where XY is the major version for which you want to install hypopg. + - Archlinux Hypopg is available on the `AUR repository diff --git a/docs/usage.rst b/docs/usage.rst index d3fcbef..7dae9cb 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -174,41 +174,219 @@ use such indexes. Manipulate hypothetical indexes ------------------------------- -Some other convenience functions are available: +Some other convenience functions and views are available: -- **hypopg_list_indexes()**: list all hypothetical indexes that have been - created +- **hypopg_list_indexes**: view that lists all hypothetical indexes that have + been created .. code-block:: psql - SELECT * FROM hypopg_list_indexes() + SELECT * FROM hypopg_list_indexes ; indexrelid | indexname | nspname | relname | amname ------------+----------------------+---------+---------+-------- 18284 | <18284>btree_hypo_id | public | hypo | btree (1 row) -- **hypopg_get_indexdef(oid)**: get the CREATE INDEX statement that would - recreate a stored hypothetical index +- **hypopg()**: function that lists all hypothetical indexes that have + been created with the same format as **pg_index** .. code-block:: psql - SELECT indexname, hypopg_get_indexdef(indexrelid) FROM hypopg_list_indexes() ; - indexname | hypopg_get_indexdef + SELECT * FROM hypopg() ; + indexname | indexrelid | indrelid | innatts | indisunique | indkey | indcollation | indclass | indoption | indexprs | indpred | amid + ----------------------+------------+----------+---------+-------------+--------+--------------+----------+-----------+----------+---------+------ + <18284>btree_hypo_id | 13543 | 18122 | 1 | f | 1 | 0 | 1978 | | | | 403 + (1 row) + +- **hypopg_get_indexdef(oid)**: function that lists the CREATE INDEX statement + that would recreate a stored hypothetical index + +.. code-block:: psql + + SELECT indexname, hypopg_get_indexdef(indexrelid) FROM hypopg_list_indexes ; + indexname | hypopg_get_indexdef ----------------------+---------------------------------------------- <18284>btree_hypo_id | CREATE INDEX ON public.hypo USING btree (id) (1 row) -- **hypopg_relation_size(oid)**: estimate how big a hypothetical index would - be: +- **hypopg_relation_size(oid)**: function that estimates how big a hypothetical + index would be: .. code-block:: psql SELECT indexname, pg_size_pretty(hypopg_relation_size(indexrelid)) - FROM hypopg_list_indexes() ; + FROM hypopg_list_indexes ; indexname | pg_size_pretty ----------------------+---------------- <18284>btree_hypo_id | 2544 kB (1 row) -- **hypopg_drop_index(oid)**: remove the given hypothetical index -- **hypopg_reset()**: remove all hypothetical indexes +- **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) 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/expected/hypo_index_part.out b/expected/hypo_index_part.out index fbd1fc9..d87ed72 100644 --- a/expected/hypo_index_part.out +++ b/expected/hypo_index_part.out @@ -1,4 +1,4 @@ --- Hypothetical on partitioned tabled +-- Hypothetical on partitioned tables CREATE TABLE hypo_part(id1 integer, id2 integer, id3 integer) PARTITION BY LIST (id1); CREATE TABLE hypo_part_1 diff --git a/expected/hypo_index_part_10.out b/expected/hypo_index_part_10.out index a0d4040..f55e785 100644 --- a/expected/hypo_index_part_10.out +++ b/expected/hypo_index_part_10.out @@ -1,4 +1,4 @@ --- Hypothetical on partitioned tabled +-- Hypothetical on partitioned tables CREATE TABLE hypo_part(id1 integer, id2 integer, id3 integer) PARTITION BY LIST (id1); CREATE TABLE hypo_part_1 diff --git a/expected/hypopg.out b/expected/hypopg.out index 990cb46..a00648f 100644 --- a/expected/hypopg.out +++ b/expected/hypopg.out @@ -105,6 +105,9 @@ ORDER BY indexrelid; f (3 rows) +-- Should detect invalid argument +SELECT hypopg_relation_size(1); +ERROR: oid 1 is not a hypothetical index -- locally disable hypoopg SET hypopg.enabled to false; -- no hypothetical index should be used diff --git a/hypopg--1.3.1--1.4.0.sql b/hypopg--1.3.1--1.4.0.sql new file mode 100644 index 0000000..ce0a3a1 --- /dev/null +++ b/hypopg--1.3.1--1.4.0.sql @@ -0,0 +1,50 @@ +-- This program is open source, licensed under the PostgreSQL License. +-- For license terms, see the LICENSE file. +-- +-- Copyright (C) 2015-2023: Julien Rouhaud + +-- 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.3.1.sql b/hypopg--1.3.1.sql index 8462df6..8492966 100644 --- a/hypopg--1.3.1.sql +++ b/hypopg--1.3.1.sql @@ -1,7 +1,7 @@ -- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- --- Copyright (C) 2015-2021: Julien Rouhaud +-- Copyright (C) 2015-2023: Julien Rouhaud -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION hypopg" to load this file. \quit diff --git a/hypopg--1.4.0.sql b/hypopg--1.4.0.sql new file mode 100644 index 0000000..f9d580c --- /dev/null +++ b/hypopg--1.4.0.sql @@ -0,0 +1,105 @@ +-- This program is open source, licensed under the PostgreSQL License. +-- For license terms, see the LICENSE file. +-- +-- Copyright (C) 2015-2023: Julien Rouhaud + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION hypopg" to load this file. \quit + +SET LOCAL client_encoding = 'UTF8'; + +CREATE FUNCTION hypopg_reset_index() + RETURNS void + LANGUAGE C VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_reset_index'; + +CREATE FUNCTION hypopg_reset() + RETURNS void + LANGUAGE C VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_reset'; + +CREATE FUNCTION +hypopg_create_index(IN sql_order text, OUT indexrelid oid, OUT indexname text) + RETURNS SETOF record + LANGUAGE C STRICT VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_create_index'; + +CREATE FUNCTION +hypopg_drop_index(IN indexid oid) + RETURNS bool + LANGUAGE C STRICT VOLATILE COST 100 +AS '$libdir/hypopg', 'hypopg_drop_index'; + +CREATE FUNCTION hypopg(OUT indexname text, OUT indexrelid oid, + OUT indrelid oid, OUT innatts integer, + OUT indisunique boolean, OUT indkey int2vector, + OUT indcollation oidvector, OUT indclass oidvector, + OUT indoption oidvector, OUT indexprs pg_node_tree, + OUT indpred pg_node_tree, OUT amid oid) + RETURNS SETOF record + LANGUAGE c COST 100 +AS '$libdir/hypopg', 'hypopg'; + +CREATE VIEW hypopg_list_indexes +AS + SELECT h.indexrelid, h.indexname AS index_name, n.nspname AS schema_name, + coalesce(c.relname, '') AS table_name, am.amname AS am_name + FROM hypopg() h + LEFT JOIN pg_catalog.pg_class c ON c.oid = h.indrelid + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + LEFT JOIN pg_catalog.pg_am am ON am.oid = h.amid; + +CREATE FUNCTION +hypopg_relation_size(IN indexid oid) + RETURNS bigint +LANGUAGE C STRICT VOLATILE 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'; + +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 872d24c..dbcfb56 100644 --- a/hypopg.c +++ b/hypopg.c @@ -8,7 +8,7 @@ * This program is open source, licensed under the PostgreSQL license. * For license terms, see the LICENSE file. * - * Copyright (C) 2015-2021: Julien Rouhaud + * Copyright (C) 2015-2023: Julien Rouhaud * *------------------------------------------------------------------------- */ @@ -55,7 +55,6 @@ static bool oid_wraparound = false; /*--- Functions --- */ PGDLLEXPORT void _PG_init(void); -PGDLLEXPORT void _PG_fini(void); PGDLLEXPORT Datum hypopg_reset(PG_FUNCTION_ARGS); @@ -103,7 +102,7 @@ static void hypo_get_relation_info_hook(PlannerInfo *root, static get_relation_info_hook_type prev_get_relation_info_hook = NULL; static bool hypo_index_match_table(hypoIndex *entry, Oid relid); -static bool hypo_query_walker(Node *node); +static bool hypo_is_simple_explain(Node *node); void _PG_init(void) @@ -123,6 +122,7 @@ _PG_init(void) isExplain = false; hypoIndexes = NIL; + hypoHiddenIndexes = NIL; HypoMemoryContext = AllocSetContextCreate(TopMemoryContext, "HypoPG context", @@ -160,17 +160,6 @@ _PG_init(void) EmitWarningsOnPlaceholders("hypopg"); } -void -_PG_fini(void) -{ - /* uninstall hooks */ - ProcessUtility_hook = prev_utility_hook; - ExecutorEnd_hook = prev_ExecutorEnd_hook; - get_relation_info_hook = prev_get_relation_info_hook; - explain_get_index_name_hook = prev_explain_get_index_name_hook; - -} - /*--------------------------------- * Return a new OID for an hypothetical index. * @@ -315,14 +304,13 @@ hypo_utility_hook( #endif ) { - isExplain = query_or_expression_tree_walker( + isExplain = hypo_is_simple_explain( #if PG_VERSION_NUM >= 100000 - (Node *) pstmt, + (Node *) pstmt #else - parsetree, + parsetree #endif - hypo_query_walker, - NULL, 0); + ); if (prev_utility_hook) prev_utility_hook( @@ -416,7 +404,7 @@ hypo_index_match_table(hypoIndex *entry, Oid relid) * i.e. an EXPLAIN, no ANALYZE */ static bool -hypo_query_walker(Node *parsetree) +hypo_is_simple_explain(Node *parsetree) { if (parsetree == NULL) return false; @@ -426,6 +414,7 @@ hypo_query_walker(Node *parsetree) if (parsetree == NULL) return false; #endif + switch (nodeTag(parsetree)) { case T_ExplainStmt: @@ -540,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.control b/hypopg.control index 7de312a..4e08300 100644 --- a/hypopg.control +++ b/hypopg.control @@ -1,6 +1,6 @@ # hypopg extension comment = 'Hypothetical indexes for PostgreSQL' -default_version = '1.3.1' +default_version = '1.4.0' module_pathname = '$libdir/hypopg' relocatable = true diff --git a/hypopg_index.c b/hypopg_index.c index dbf445d..2b800da 100644 --- a/hypopg_index.c +++ b/hypopg_index.c @@ -8,7 +8,7 @@ * This program is open source, licensed under the PostgreSQL license. * For license terms, see the LICENSE file. * - * Copyright (C) 2015-2021: Julien Rouhaud + * Copyright (C) 2015-2023: Julien Rouhaud * *------------------------------------------------------------------------- */ @@ -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, @@ -350,7 +356,7 @@ hypo_index_store_parsetree(IndexStmt *node, const char *queryString) /* * Support for hypothetical BRIN indexes is broken in some minor versions * of pg10, pg11 and pg12. For simplicity, check PG_VERSION_NUM rather - * than the real instance version, which should should be right most of the + * than the real instance version, which should be right most of the * time. When it's not, the only effect is to have a less user-friendly * error message. */ @@ -395,12 +401,13 @@ hypo_index_store_parsetree(IndexStmt *node, const char *queryString) break; #endif default: - elog(ERROR, "hypopg: \"%s\" is not a table" #if PG_VERSION_NUM >= 90300 - " or materialized view" -#endif - , + elog(ERROR, "hypopg: \"%s\" is not a table or materialized view", node->relation->relname); +#else + elog(ERROR, "hypopg: \"%s\" is not a table", + node->relation->relname); +#endif } /* Run parse analysis ... */ @@ -921,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); @@ -932,6 +942,7 @@ hypo_index_remove(Oid indexid) return true; } } + return false; } @@ -1012,21 +1023,17 @@ hypo_injectHypotheticalIndex(PlannerInfo *root, #endif index->indexkeys = (int *) palloc(sizeof(int) * ncolumns); - index->indexcollations = (Oid *) palloc(sizeof(int) * ncolumns); - index->opfamily = (Oid *) palloc(sizeof(int) * ncolumns); - index->opcintype = (Oid *) palloc(sizeof(int) * ncolumns); - -#if PG_VERSION_NUM >= 90500 - index->canreturn = (bool *) palloc(sizeof(bool) * ncolumns); -#endif + index->indexcollations = (Oid *) palloc(sizeof(int) * nkeycolumns); + index->opfamily = (Oid *) palloc(sizeof(int) * nkeycolumns); + index->opcintype = (Oid *) palloc(sizeof(int) * nkeycolumns); if ((index->relam == BTREE_AM_OID) || entry->amcanorder) { if (index->relam != BTREE_AM_OID) - index->sortopfamily = palloc0(sizeof(Oid) * ncolumns); + index->sortopfamily = palloc0(sizeof(Oid) * nkeycolumns); - index->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns); - index->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns); + index->reverse_sort = (bool *) palloc(sizeof(bool) * nkeycolumns); + index->nulls_first = (bool *) palloc(sizeof(bool) * nkeycolumns); } else { @@ -1035,6 +1042,10 @@ hypo_injectHypotheticalIndex(PlannerInfo *root, index->nulls_first = NULL; } +#if PG_VERSION_NUM >= 90500 + index->canreturn = (bool *) palloc(sizeof(bool) * ncolumns); +#endif + for (i = 0; i < ncolumns; i++) { index->indexkeys[i] = entry->indexkeys[i]; @@ -1045,9 +1056,9 @@ hypo_injectHypotheticalIndex(PlannerInfo *root, for (i = 0; i < nkeycolumns; i++) { + index->indexcollations[i] = entry->indexcollations[i]; index->opfamily[i] = entry->opfamily[i]; index->opcintype[i] = entry->opcintype[i]; - index->indexcollations[i] = entry->indexcollations[i]; } /* @@ -1063,7 +1074,7 @@ hypo_injectHypotheticalIndex(PlannerInfo *root, */ index->sortopfamily = index->opfamily; - for (i = 0; i < ncolumns; i++) + for (i = 0; i < nkeycolumns; i++) { index->reverse_sort[i] = entry->reverse_sort[i]; index->nulls_first[i] = entry->nulls_first[i]; @@ -1073,7 +1084,7 @@ hypo_injectHypotheticalIndex(PlannerInfo *root, { if (entry->sortopfamily) { - for (i = 0; i < ncolumns; i++) + for (i = 0; i < nkeycolumns; i++) { index->sortopfamily[i] = entry->sortopfamily[i]; index->reverse_sort[i] = entry->reverse_sort[i]; @@ -1290,7 +1301,7 @@ hypopg(PG_FUNCTION_ARGS) Datum hypopg_create_index(PG_FUNCTION_ARGS) { - char *sql = TextDatumGetCString(PG_GETARG_TEXT_PP(0)); + char *sql = TextDatumGetCString(PG_GETARG_DATUM(0)); List *parsetree_list; ListCell *parsetree_item; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; @@ -1387,6 +1398,7 @@ hypopg_relation_size(PG_FUNCTION_ARGS) double tuples; Oid indexid = PG_GETARG_OID(0); ListCell *lc; + bool found = false; pages = 0; tuples = 0; @@ -1397,9 +1409,14 @@ hypopg_relation_size(PG_FUNCTION_ARGS) if (entry->oid == indexid) { hypo_estimate_index_simple(entry, &pages, &tuples); + found = true; + break; } } + if (!found) + elog(ERROR, "oid %u is not a hypothetical index", indexid); + PG_RETURN_INT64(pages * 1.0L * BLCKSZ); } @@ -1419,8 +1436,7 @@ hypopg_get_indexdef(PG_FUNCTION_ARGS) hypoIndex *entry = NULL; ListCell *lc; List *context; - int keyno, - cpt = 0; + int keyno; foreach(lc, hypoIndexes) { @@ -1491,8 +1507,6 @@ hypopg_get_indexdef(PG_FUNCTION_ARGS) keycoltype = exprType(indexkey); keycolcollation = exprCollation(indexkey); - - cpt++; } /* Add collation, if not default for column */ @@ -1585,6 +1599,189 @@ 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 +#if PG_VERSION_NUM < 120000 + , false +#endif + ); + 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/import/hypopg_import.c b/import/hypopg_import.c index d21dfa0..f698d5d 100644 --- a/import/hypopg_import.c +++ b/import/hypopg_import.c @@ -5,7 +5,7 @@ * This program is open source, licensed under the PostgreSQL license. * For license terms, see the LICENSE file. * - * Copyright (c) 2008-2021, PostgreSQL Global Development Group + * Copyright (c) 2008-2023, PostgreSQL Global Development Group * *------------------------------------------------------------------------- */ diff --git a/import/hypopg_import_index.c b/import/hypopg_import_index.c index 22e2902..9afcaa5 100644 --- a/import/hypopg_import_index.c +++ b/import/hypopg_import_index.c @@ -6,7 +6,7 @@ * This program is open source, licensed under the PostgreSQL license. * For license terms, see the LICENSE file. * - * Copyright (c) 2008-2021, PostgreSQL Global Development Group + * Copyright (c) 2008-2023, PostgreSQL Global Development Group * *------------------------------------------------------------------------- */ diff --git a/include/hypopg.h b/include/hypopg.h index 0d47093..24a0090 100644 --- a/include/hypopg.h +++ b/include/hypopg.h @@ -5,7 +5,7 @@ * This program is open source, licensed under the PostgreSQL license. * For license terms, see the LICENSE file. * - * Copyright (C) 2015-2021: Julien Rouhaud + * Copyright (C) 2015-2023: Julien Rouhaud * *------------------------------------------------------------------------- */ diff --git a/include/hypopg_import.h b/include/hypopg_import.h index 0569190..46f88cd 100644 --- a/include/hypopg_import.h +++ b/include/hypopg_import.h @@ -5,7 +5,7 @@ * This program is open source, licensed under the PostgreSQL license. * For license terms, see the LICENSE file. * - * Copyright (c) 2008-2021, PostgreSQL Global Development Group + * Copyright (c) 2008-2023, PostgreSQL Global Development Group * *------------------------------------------------------------------------- */ diff --git a/include/hypopg_import_index.h b/include/hypopg_import_index.h index 9af3d2f..b965731 100644 --- a/include/hypopg_import_index.h +++ b/include/hypopg_import_index.h @@ -6,7 +6,7 @@ * This program is open source, licensed under the PostgreSQL license. * For license terms, see the LICENSE file. * - * Copyright (c) 2008-2021, PostgreSQL Global Development Group + * Copyright (c) 2008-2023, PostgreSQL Global Development Group * *------------------------------------------------------------------------- */ diff --git a/include/hypopg_index.h b/include/hypopg_index.h index a07b03e..02c7abd 100644 --- a/include/hypopg_index.h +++ b/include/hypopg_index.h @@ -8,7 +8,7 @@ * This program is open source, licensed under the PostgreSQL license. * For license terms, see the LICENSE file. * - * Copyright (C) 2015-2021: Julien Rouhaud + * Copyright (C) 2015-2023: Julien Rouhaud * *------------------------------------------------------------------------- */ @@ -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(); diff --git a/test/sql/hypo_index_part.sql b/test/sql/hypo_index_part.sql index 36d5c9a..d1c6cce 100644 --- a/test/sql/hypo_index_part.sql +++ b/test/sql/hypo_index_part.sql @@ -1,4 +1,4 @@ --- Hypothetical on partitioned tabled +-- Hypothetical on partitioned tables CREATE TABLE hypo_part(id1 integer, id2 integer, id3 integer) PARTITION BY LIST (id1); diff --git a/test/sql/hypo_index_part_10.sql b/test/sql/hypo_index_part_10.sql index ddc38cd..274896b 100644 --- a/test/sql/hypo_index_part_10.sql +++ b/test/sql/hypo_index_part_10.sql @@ -1,4 +1,4 @@ --- Hypothetical on partitioned tabled +-- Hypothetical on partitioned tables CREATE TABLE hypo_part(id1 integer, id2 integer, id3 integer) PARTITION BY LIST (id1); diff --git a/test/sql/hypopg.sql b/test/sql/hypopg.sql index 28e3fcb..64cfcd5 100644 --- a/test/sql/hypopg.sql +++ b/test/sql/hypopg.sql @@ -70,6 +70,9 @@ SELECT hypopg_relation_size(indexrelid) = current_setting('block_size')::bigint FROM hypopg() ORDER BY indexrelid; +-- Should detect invalid argument +SELECT hypopg_relation_size(1); + -- locally disable hypoopg SET hypopg.enabled to false;