├── docker-compose.yml ├── .gitignore ├── Makefile ├── .travis.yml ├── sql └── plantuner.sql ├── Dockerfile.tmpl ├── run_tests.sh ├── COPYRIGHT ├── expected └── plantuner.out ├── README.md └── plantuner.c /docker-compose.yml: -------------------------------------------------------------------------------- 1 | tests: 2 | build: . 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated subdirectories 2 | /log/ 3 | /results/ 4 | /tmp_check/ 5 | *.o 6 | *.so 7 | .deps 8 | Dockerfile 9 | *.log 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MODULE_big = plantuner 2 | DOCS = README.md 3 | REGRESS = plantuner 4 | OBJS=plantuner.o 5 | 6 | ifdef USE_PGXS 7 | PGXS = $(shell pg_config --pgxs) 8 | include $(PGXS) 9 | else 10 | subdir = contrib/plantuner 11 | top_builddir = ../.. 12 | include $(top_builddir)/src/Makefile.global 13 | 14 | include $(top_srcdir)/contrib/contrib-global.mk 15 | endif 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | 4 | sudo: required 5 | dist: trusty 6 | 7 | language: c 8 | 9 | services: 10 | - docker 11 | 12 | install: 13 | - sed -e 's/${CHECK_CODE}/'${CHECK_CODE}/g -e 's/${PG_VERSION}/'${PG_VERSION}/g Dockerfile.tmpl > Dockerfile 14 | - docker-compose build 15 | 16 | script: 17 | - docker-compose run tests 18 | 19 | env: 20 | - PG_VERSION=9.5 CHECK_CODE=clang 21 | - PG_VERSION=9.5 CHECK_CODE=false 22 | - PG_VERSION=9.6 CHECK_CODE=clang 23 | - PG_VERSION=9.6 CHECK_CODE=false 24 | - PG_VERSION=10 CHECK_CODE=clang 25 | - PG_VERSION=10 CHECK_CODE=false 26 | -------------------------------------------------------------------------------- /sql/plantuner.sql: -------------------------------------------------------------------------------- 1 | LOAD 'plantuner'; 2 | SET enable_seqscan=off; 3 | 4 | SHOW plantuner.disable_index; 5 | 6 | CREATE TABLE wow (i int, j int); 7 | INSERT INTO wow SELECT a, a+ 1 FROM generate_series(1, 1000) a; 8 | CREATE INDEX i_idx ON wow (i); 9 | CREATE INDEX j_idx ON wow (j); 10 | 11 | EXPLAIN (COSTS OFF) SELECT count(*) FROM wow; 12 | SELECT count(*) FROM wow; 13 | 14 | SET plantuner.disable_index="i_idx, j_idx"; 15 | 16 | EXPLAIN (COSTS OFF) SELECT count(*) FROM wow; 17 | SELECT count(*) FROM wow; 18 | 19 | SHOW plantuner.disable_index; 20 | 21 | SET plantuner.disable_index="i_idx, nonexistent, public.j_idx, wow"; 22 | 23 | SHOW plantuner.disable_index; 24 | 25 | SET plantuner.enable_index="i_idx"; 26 | 27 | SHOW plantuner.enable_index; 28 | 29 | EXPLAIN (COSTS OFF) SELECT count(*) FROM wow; 30 | SELECT count(*) FROM wow; 31 | 32 | DROP INDEX j_idx; 33 | SELECT pg_reload_conf(); 34 | 35 | EXPLAIN (COSTS OFF) SELECT count(*) FROM wow; 36 | SELECT count(*) FROM wow; 37 | -------------------------------------------------------------------------------- /Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM postgres:${PG_VERSION}-alpine 2 | 3 | ENV LANG=C.UTF-8 PGDATA=/pg/data 4 | 5 | RUN if [ "${CHECK_CODE}" = "clang" ] ; then \ 6 | echo 'http://dl-3.alpinelinux.org/alpine/edge/main' > /etc/apk/repositories; \ 7 | apk --no-cache add clang-analyzer make musl-dev gcc; \ 8 | fi 9 | 10 | RUN if [ "${CHECK_CODE}" = "false" ] ; then \ 11 | echo 'http://dl-3.alpinelinux.org/alpine/edge/main' > /etc/apk/repositories; \ 12 | apk --no-cache add curl python3 gcc make musl-dev;\ 13 | fi 14 | 15 | RUN mkdir -p ${PGDATA} && \ 16 | mkdir /pg/src && \ 17 | chown postgres:postgres ${PGDATA} && \ 18 | chmod a+rwx /usr/local/lib/postgresql && \ 19 | chmod a+rwx /usr/local/share/postgresql/extension && \ 20 | mkdir -p /usr/local/share/doc/postgresql/contrib && \ 21 | chmod a+rwx /usr/local/share/doc/postgresql/contrib 22 | 23 | ADD . /pg/src 24 | WORKDIR /pg/src 25 | RUN chmod -R go+rwX /pg/src 26 | USER postgres 27 | ENTRYPOINT PGDATA=${PGDATA} CHECK_CODE=${CHECK_CODE} bash run_tests.sh 28 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This is a main testing script for: 4 | # * regression tests 5 | # * testgres-based tests 6 | # * cmocka-based tests 7 | # Copyright (c) 2017, Postgres Professional 8 | 9 | set -eux 10 | 11 | echo CHECK_CODE=$CHECK_CODE 12 | 13 | status=0 14 | 15 | # perform code analysis if necessary 16 | if [ "$CHECK_CODE" = "clang" ]; then 17 | scan-build --status-bugs make USE_PGXS=1 || status=$? 18 | exit $status 19 | fi 20 | 21 | # don't forget to "make clean" 22 | make USE_PGXS=1 clean 23 | 24 | # initialize database 25 | initdb 26 | 27 | # build extension 28 | make USE_PGXS=1 install 29 | 30 | # check build 31 | status=$? 32 | if [ $status -ne 0 ]; then exit $status; fi 33 | 34 | # add pg_pathman to shared_preload_libraries and restart cluster 'test' 35 | echo "shared_preload_libraries = 'plantuner'" >> $PGDATA/postgresql.conf 36 | echo "port = 55435" >> $PGDATA/postgresql.conf 37 | pg_ctl start -l /tmp/postgres.log -w 38 | 39 | # check startup 40 | status=$? 41 | if [ $status -ne 0 ]; then cat /tmp/postgres.log; fi 42 | 43 | # run regression tests 44 | export PG_REGRESS_DIFF_OPTS="-w -U3" # for alpine's diff (BusyBox) 45 | PGPORT=55435 make USE_PGXS=1 installcheck || status=$? 46 | 47 | # show diff if it exists 48 | if test -f regression.diffs; then cat regression.diffs; fi 49 | 50 | exit $status 51 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Teodor Sigaev 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. Neither the name of the author nor the names of any co-contributors 14 | * may be used to endorse or promote products derived from this software 15 | * without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS 18 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY 21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 23 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 25 | * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | -------------------------------------------------------------------------------- /expected/plantuner.out: -------------------------------------------------------------------------------- 1 | LOAD 'plantuner'; 2 | SET enable_seqscan=off; 3 | SHOW plantuner.disable_index; 4 | plantuner.disable_index 5 | ------------------------- 6 | 7 | (1 row) 8 | 9 | CREATE TABLE wow (i int, j int); 10 | INSERT INTO wow SELECT a, a+ 1 FROM generate_series(1, 1000) a; 11 | CREATE INDEX i_idx ON wow (i); 12 | CREATE INDEX j_idx ON wow (j); 13 | EXPLAIN (COSTS OFF) SELECT count(*) FROM wow; 14 | QUERY PLAN 15 | ---------------------------------------- 16 | Aggregate 17 | -> Bitmap Heap Scan on wow 18 | -> Bitmap Index Scan on j_idx 19 | (3 rows) 20 | 21 | SELECT count(*) FROM wow; 22 | count 23 | ------- 24 | 1000 25 | (1 row) 26 | 27 | SET plantuner.disable_index="i_idx, j_idx"; 28 | EXPLAIN (COSTS OFF) SELECT count(*) FROM wow; 29 | QUERY PLAN 30 | ----------------------- 31 | Aggregate 32 | -> Seq Scan on wow 33 | (2 rows) 34 | 35 | SELECT count(*) FROM wow; 36 | count 37 | ------- 38 | 1000 39 | (1 row) 40 | 41 | SHOW plantuner.disable_index; 42 | plantuner.disable_index 43 | ---------------------------- 44 | public.i_idx, public.j_idx 45 | (1 row) 46 | 47 | SET plantuner.disable_index="i_idx, nonexistent, public.j_idx, wow"; 48 | WARNING: 'nonexistent' does not exist 49 | WARNING: 'wow' is not an index 50 | SHOW plantuner.disable_index; 51 | plantuner.disable_index 52 | ---------------------------- 53 | public.i_idx, public.j_idx 54 | (1 row) 55 | 56 | SET plantuner.enable_index="i_idx"; 57 | SHOW plantuner.enable_index; 58 | plantuner.enable_index 59 | ------------------------ 60 | public.i_idx 61 | (1 row) 62 | 63 | EXPLAIN (COSTS OFF) SELECT count(*) FROM wow; 64 | QUERY PLAN 65 | ---------------------------------------- 66 | Aggregate 67 | -> Bitmap Heap Scan on wow 68 | -> Bitmap Index Scan on i_idx 69 | (3 rows) 70 | 71 | SELECT count(*) FROM wow; 72 | count 73 | ------- 74 | 1000 75 | (1 row) 76 | 77 | DROP INDEX j_idx; 78 | SELECT pg_reload_conf(); 79 | pg_reload_conf 80 | ---------------- 81 | t 82 | (1 row) 83 | 84 | EXPLAIN (COSTS OFF) SELECT count(*) FROM wow; 85 | QUERY PLAN 86 | ---------------------------------------- 87 | Aggregate 88 | -> Bitmap Heap Scan on wow 89 | -> Bitmap Index Scan on i_idx 90 | (3 rows) 91 | 92 | SELECT count(*) FROM wow; 93 | count 94 | ------- 95 | 1000 96 | (1 row) 97 | 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/postgrespro/plantuner.svg?branch=master)](https://travis-ci.org/postgrespro/plantuner) 2 | [![GitHub license](https://img.shields.io/badge/license-PostgreSQL-blue.svg)](https://raw.githubusercontent.com/postgrespro/jsquery/master/LICENSE) 3 | 4 | # Plantuner - enable planner hints 5 | 6 | `plantuner` is a contribution module for PostgreSQL 9.5+, which 7 | enable planner hints. 8 | 9 | All work was done by Teodor Sigaev (teodor@sigaev.ru) and Oleg Bartunov 10 | (oleg@sai.msu.su). 11 | 12 | Sponsor: Nomao project (http://www.nomao.com) 13 | 14 | ## Motivation 15 | 16 | Whether somebody think it's bad or not, but sometime it's very 17 | interesting to be able to control planner (provide hints, which tells 18 | optimizer to ignore its algorithm in part), which is currently 19 | impossible in POstgreSQL. Oracle, for example, has over 120 hints, SQL 20 | Server also provides hints. 21 | 22 | This first version of plantuner provides a possibility to hide 23 | specified indexes from PostgreSQL planner, so it will not use them. 24 | 25 | There are many situation, when developer want to temporarily disable 26 | specific index(es), without dropping them, or to instruct planner to 27 | use specific index. 28 | 29 | Next, for some workload PostgreSQL could be too pessimistic for 30 | newly created tables and assumes much more rows in table than 31 | it actually has. If plantuner.fix_empty_table GUC variable is set 32 | to true then module will set to zero number of pages/tuples of 33 | table which hasn't blocks in file. 34 | 35 | ## Installation 36 | 37 | Get the latest source code. 38 | 39 | export USE_PGXS=1 40 | gmake 41 | gmake install 42 | gmake installcheck 43 | 44 | ## Syntax 45 | 46 | * `plantuner.forbid_index` (deprecated) 47 | * `plantuner.disable_index` 48 | 49 | List of indexes invisible to planner 50 | * `plantuner.enable_index` 51 | 52 | List of indexes visible to planner even they are hided by plantuner.disable_index. 53 | 54 | ## Usage 55 | 56 | To enable the module you can either load shared library `plantuner` in 57 | psql session or specify `shared_preload_libraries` option in 58 | `postgresql.conf`. 59 | 60 | ``` 61 | =# LOAD 'plantuner'; 62 | =# create table test(id int); 63 | =# create index id_idx on test(id); 64 | =# create index id_idx2 on test(id); 65 | =# \d test 66 | Table "public.test" 67 | Column | Type | Modifiers 68 | --------+---------+----------- 69 | id | integer | 70 | Indexes: 71 | "id_idx" btree (id) 72 | "id_idx2" btree (id) 73 | =# explain select id from test where id=1; 74 | QUERY PLAN 75 | ----------------------------------------------------------------------- 76 | Bitmap Heap Scan on test (cost=4.34..15.03 rows=12 width=4) 77 | Recheck Cond: (id = 1) 78 | -> Bitmap Index Scan on id_idx2 (cost=0.00..4.34 rows=12 width=0) 79 | Index Cond: (id = 1) 80 | (4 rows) 81 | =# set enable_seqscan=off; 82 | =# set plantuner.disable_index='id_idx2'; 83 | =# explain select id from test where id=1; 84 | QUERY PLAN 85 | ---------------------------------------------------------------------- 86 | Bitmap Heap Scan on test (cost=4.34..15.03 rows=12 width=4) 87 | Recheck Cond: (id = 1) 88 | -> Bitmap Index Scan on id_idx (cost=0.00..4.34 rows=12 width=0) 89 | Index Cond: (id = 1) 90 | (4 rows) 91 | =# set plantuner.disable_index='id_idx2,id_idx'; 92 | =# explain select id from test where id=1; 93 | QUERY PLAN 94 | ------------------------------------------------------------------------- 95 | Seq Scan on test (cost=10000000000.00..10000000040.00 rows=12 width=4) 96 | Filter: (id = 1) 97 | (2 rows) 98 | =# set plantuner.enable_index='id_idx'; 99 | =# explain select id from test where id=1; 100 | QUERY PLAN 101 | ----------------------------------------------------------------------- 102 | Bitmap Heap Scan on test (cost=4.34..15.03 rows=12 width=4) 103 | Recheck Cond: (id = 1) 104 | -> Bitmap Index Scan on id_idx (cost=0.00..4.34 rows=12 width=0) 105 | Index Cond: (id = 1) 106 | (4 rows) 107 | ``` 108 | 109 | -------------------------------------------------------------------------------- /plantuner.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Teodor Sigaev 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. Neither the name of the author nor the names of any co-contributors 14 | * may be used to endorse or promote products derived from this software 15 | * without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS 18 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY 21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 23 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 25 | * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #if PG_VERSION_NUM >= 100000 45 | #include 46 | #include 47 | #endif 48 | 49 | PG_MODULE_MAGIC; 50 | 51 | void _PG_init(void); 52 | 53 | static int nDisabledIndexes = 0; 54 | static Oid *disabledIndexes = NULL; 55 | static char *disableIndexesOutStr = ""; 56 | 57 | static int nEnabledIndexes = 0; 58 | static Oid *enabledIndexes = NULL; 59 | static char *enableIndexesOutStr = ""; 60 | 61 | get_relation_info_hook_type prevHook = NULL; 62 | static bool fix_empty_table = false; 63 | 64 | 65 | static const char * 66 | indexesAssign(const char *newval, bool doit, bool isDisable) 67 | { 68 | char *rawname; 69 | List *namelist; 70 | ListCell *l; 71 | Oid *newOids = NULL; 72 | int nOids = 0, 73 | i = 0; 74 | 75 | rawname = pstrdup(newval); 76 | 77 | if (!SplitIdentifierString(rawname, ',', &namelist)) 78 | goto cleanup; 79 | 80 | if (doit) 81 | { 82 | MemoryContext oldcxt; 83 | 84 | nOids = list_length(namelist); 85 | oldcxt = MemoryContextSwitchTo(TopMemoryContext); 86 | newOids = palloc0(sizeof(Oid) * nOids); 87 | MemoryContextSwitchTo(oldcxt); 88 | 89 | if (isDisable) 90 | { 91 | nDisabledIndexes = nOids; 92 | if (disabledIndexes != NULL) 93 | pfree(disabledIndexes); 94 | 95 | disabledIndexes = newOids; 96 | } 97 | else 98 | { 99 | nEnabledIndexes = nOids; 100 | if (enabledIndexes != NULL) 101 | pfree(enabledIndexes); 102 | 103 | enabledIndexes = newOids; 104 | } 105 | } 106 | 107 | foreach(l, namelist) 108 | { 109 | char *curname = (char *) lfirst(l); 110 | Oid indexOid = RangeVarGetRelid(makeRangeVarFromNameList(stringToQualifiedNameList(curname)), 111 | NoLock, true); 112 | 113 | if (indexOid == InvalidOid) 114 | { 115 | if (doit == false) 116 | elog(WARNING, "'%s' does not exist", curname); 117 | continue; 118 | } 119 | else if (get_rel_relkind(indexOid) != RELKIND_INDEX) 120 | { 121 | if (doit == false) 122 | elog(WARNING, "'%s' is not an index", curname); 123 | continue; 124 | } 125 | else if (doit) 126 | newOids[i++] = indexOid; 127 | } 128 | 129 | pfree(rawname); 130 | list_free(namelist); 131 | 132 | return newval; 133 | 134 | cleanup: 135 | if (newOids) 136 | free(newOids); 137 | 138 | pfree(rawname); 139 | list_free(namelist); 140 | return NULL; 141 | } 142 | 143 | static bool 144 | checkDisabledIndexes(char **newval, void **extra, GucSource source) 145 | { 146 | char *val; 147 | 148 | val = (char *) indexesAssign(*newval, false, true); 149 | 150 | if (val) 151 | { 152 | *newval = val; 153 | return true; 154 | } 155 | 156 | return false; 157 | } 158 | 159 | static bool 160 | checkEnabledIndexes(char **newval, void **extra, GucSource source) 161 | { 162 | char *val; 163 | 164 | val = (char *) indexesAssign(*newval, false, false); 165 | 166 | if (val) 167 | { 168 | *newval = val; 169 | return true; 170 | } 171 | 172 | return false; 173 | } 174 | 175 | static void 176 | assignDisabledIndexes(const char *newval, void *extra) 177 | { 178 | indexesAssign(newval, true, true); 179 | } 180 | 181 | static void 182 | assignEnabledIndexes(const char *newval, void *extra) 183 | { 184 | indexesAssign(newval, true, false); 185 | } 186 | 187 | static void 188 | indexFilter(PlannerInfo *root, Oid relationObjectId, bool inhparent, RelOptInfo *rel) 189 | { 190 | int i; 191 | 192 | for (i = 0; i < nDisabledIndexes; i++) 193 | { 194 | ListCell *l; 195 | Oid disabledOid; 196 | 197 | disabledOid = disabledIndexes[i]; 198 | if (disabledOid == InvalidOid) 199 | continue; 200 | 201 | foreach(l, rel->indexlist) 202 | { 203 | IndexOptInfo *info = (IndexOptInfo *) lfirst(l); 204 | 205 | if (disabledOid == info->indexoid) 206 | { 207 | int j; 208 | 209 | for (j = 0; j < nEnabledIndexes; j++) 210 | if (enabledIndexes[j] == info->indexoid) 211 | break; 212 | 213 | if (j >= nEnabledIndexes) 214 | rel->indexlist = list_delete_ptr(rel->indexlist, info); 215 | 216 | break; 217 | } 218 | } 219 | } 220 | } 221 | 222 | static void 223 | execPlantuner(PlannerInfo *root, Oid relationObjectId, bool inhparent, RelOptInfo *rel) 224 | { 225 | Relation relation; 226 | 227 | relation = heap_open(relationObjectId, NoLock); 228 | if (relation->rd_rel->relkind == RELKIND_RELATION) 229 | { 230 | if (fix_empty_table && RelationGetNumberOfBlocks(relation) == 0) 231 | { 232 | /* 233 | * estimate_rel_size() could be too pessimistic for particular 234 | * workload 235 | */ 236 | rel->pages = 0.0; 237 | rel->tuples = 0.0; 238 | } 239 | 240 | indexFilter(root, relationObjectId, inhparent, rel); 241 | } 242 | heap_close(relation, NoLock); 243 | 244 | /* 245 | * Call next hook if it exists 246 | */ 247 | if (prevHook) 248 | prevHook(root, relationObjectId, inhparent, rel); 249 | } 250 | 251 | /* 252 | * generate_qualified_relation_name 253 | * Compute the name to display for a relation specified by OID 254 | * 255 | * As above, but unconditionally schema-qualify the name. 256 | */ 257 | static char * 258 | generate_qualified_relation_name(Oid relid) 259 | { 260 | HeapTuple tp; 261 | Form_pg_class reltup; 262 | char *relname; 263 | char *nspname; 264 | char *result = NULL; 265 | 266 | tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); 267 | if (!HeapTupleIsValid(tp)) 268 | return NULL; 269 | 270 | reltup = (Form_pg_class) GETSTRUCT(tp); 271 | relname = NameStr(reltup->relname); 272 | 273 | nspname = get_namespace_name(reltup->relnamespace); 274 | if (nspname) 275 | result = quote_qualified_identifier(nspname, relname); 276 | 277 | ReleaseSysCache(tp); 278 | 279 | return result; 280 | } 281 | 282 | static const char * 283 | IndexFilterShow(Oid *indexes, int nIndexes) 284 | { 285 | int i; 286 | StringInfoData buf; 287 | 288 | initStringInfo(&buf); 289 | 290 | for (i = 0; i < nIndexes && indexes[i] != InvalidOid; i++) 291 | { 292 | char *s = generate_qualified_relation_name(indexes[i]); 293 | 294 | if (s == NULL) 295 | continue; 296 | 297 | if (i != 0) 298 | appendStringInfoString(&buf, ", "); 299 | 300 | appendStringInfoString(&buf, s); 301 | pfree(s); 302 | } 303 | 304 | return buf.data; 305 | } 306 | 307 | static const char * 308 | disabledIndexFilterShow(void) 309 | { 310 | return IndexFilterShow(disabledIndexes, nDisabledIndexes); 311 | } 312 | 313 | static const char * 314 | enabledIndexFilterShow(void) 315 | { 316 | return IndexFilterShow(enabledIndexes, nEnabledIndexes); 317 | } 318 | 319 | void 320 | _PG_init(void) 321 | { 322 | DefineCustomStringVariable( 323 | "plantuner.forbid_index", 324 | "List of forbidden indexes (deprecated)", 325 | "Listed indexes will not be used in queries (deprecated, use plantuner.disable_index)", 326 | &disableIndexesOutStr, 327 | "", 328 | PGC_USERSET, 329 | 0, 330 | checkDisabledIndexes, 331 | assignDisabledIndexes, 332 | disabledIndexFilterShow 333 | ); 334 | 335 | DefineCustomStringVariable( 336 | "plantuner.disable_index", 337 | "List of disabled indexes", 338 | "Listed indexes will not be used in queries", 339 | &disableIndexesOutStr, 340 | "", 341 | PGC_USERSET, 342 | 0, 343 | checkDisabledIndexes, 344 | assignDisabledIndexes, 345 | disabledIndexFilterShow 346 | ); 347 | 348 | DefineCustomStringVariable( 349 | "plantuner.enable_index", 350 | "List of enabled indexes (overload plantuner.disable_index)", 351 | "Listed indexes which could be used in queries even they are listed in plantuner.disable_index", 352 | &enableIndexesOutStr, 353 | "", 354 | PGC_USERSET, 355 | 0, 356 | checkEnabledIndexes, 357 | assignEnabledIndexes, 358 | enabledIndexFilterShow 359 | ); 360 | 361 | DefineCustomBoolVariable( 362 | "plantuner.fix_empty_table", 363 | "Sets to zero estimations for empty tables", 364 | "Sets to zero estimations for empty or newly created tables", 365 | &fix_empty_table, 366 | fix_empty_table, 367 | PGC_USERSET, 368 | GUC_NOT_IN_SAMPLE, 369 | NULL, 370 | NULL, 371 | NULL 372 | ); 373 | 374 | if (get_relation_info_hook != execPlantuner) 375 | { 376 | prevHook = get_relation_info_hook; 377 | get_relation_info_hook = execPlantuner; 378 | } 379 | } 380 | --------------------------------------------------------------------------------