├── .gitignore ├── Changes ├── META.json ├── Makefile ├── README.md ├── doc └── pg_idx_advisor.md ├── pg_idx_advisor.control ├── sql ├── pg_idx_advisor--0.1.1--0.1.2.sql └── pg_idx_advisor.sql ├── src ├── idx_adviser.c ├── idx_adviser.h ├── utils.c └── utils.h └── test ├── expected ├── base.out ├── json.out └── measurement.out └── sql ├── base.sql ├── json.sql └── measurement.sql /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.app 26 | *.i*86 27 | *.x86_64 28 | *.hex 29 | 30 | # Debug files 31 | *.dSYM/ 32 | 33 | # Regression output 34 | /regression.diffs 35 | /regression.out 36 | /results/ 37 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for PostgreSQL extension pg_idx_advisor. 2 | 3 | 0.1.2 2015-06-17 22:00:00 4 | - Currect SQL files for creating the extension. 5 | - Improve documentation 6 | 7 | 0.1.1 2015-06-17 21:00:00 8 | - BugFix: Statistics can now exist on the tables. 9 | - BugFix: memory leaks. 10 | - Initial work to comply with PG 9.5 11 | 12 | 0.1.0 2015-05-27 11:47:43 13 | - Initial version. 14 | - Implementation in C. 15 | - Included in [PGXN Manager](https://github.com/theory/pgxn-manager). 16 | - Not otherwise released. 17 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pg_idx_advisor", 3 | "abstract": "Index advice for PostgreSQL", 4 | "description": "pg_idx_advisor is a PostgreSQL extension that gives index tuning recommendations for queries.", 5 | "version": "0.1.2", 6 | "release_status": "stable", 7 | "maintainer": [ 8 | "Jony V. Cohen " 9 | ], 10 | "license": { 11 | "PostgreSQL": "http://www.postgresql.org/about/licence" 12 | }, 13 | "prereqs": { 14 | "runtime": { 15 | "requires": { 16 | "plpgsql": 0, 17 | "PostgreSQL": "9.2.0" 18 | }, 19 | "recommends": { 20 | "PostgreSQL": "9.4.0" 21 | } 22 | } 23 | }, 24 | "provides": { 25 | "idx_adv": { 26 | "file": "pg_idx_advisor.so", 27 | "docfile": "doc/README.md", 28 | "version": "0.1.2", 29 | "abstract": "Index advice for PostgreSQL" 30 | } 31 | }, 32 | "resources": { 33 | "homepage": "https://github.com/cohenjo/pg_idx_advisor", 34 | "bugtracker": { 35 | "web": "http://github.com/cohenjo/pg_idx_advisor/issues" 36 | }, 37 | "repository": { 38 | "url": "https://github.com/cohenjo/pg_idx_advisor.git", 39 | "web": "https://github.com/cohenjo/pg_idx_advisor", 40 | "type": "git" 41 | } 42 | }, 43 | "generated_by": "Jony V. cohen", 44 | "meta-spec": { 45 | "version": "1.0.0", 46 | "url": "http://pgxn.org/meta/spec.txt" 47 | }, 48 | "tags": [ 49 | "tuning", 50 | "performance", 51 | "index", 52 | "advisor", 53 | "query tuning" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Set PG_CONFIG properly 2 | PG_CONFIG ?= pg_config 3 | 4 | EXTENSION = $(shell grep -m 1 '"name":' META.json | \ 5 | sed -e 's/[[:space:]]*"name":[[:space:]]*"\([^"]*\)",/\1/') 6 | EXTVERSION = $(shell grep -m 1 '[[:space:]]\"version":' META.json | \ 7 | sed -e 's/[[:space:]]*"version":[[:space:]]*"\([^"]*\)",\{0,1\}/\1/') 8 | 9 | DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) 10 | DOCS = $(wildcard doc/*.md) 11 | TESTS = $(wildcard test/sql/*.sql) 12 | REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) 13 | REGRESS_OPTS = --inputdir=test --load-language=plpgsql --debug 14 | 15 | MODULE_big = pg_idx_advisor 16 | OBJS = src/utils.o src/idx_adviser.o 17 | # MODULES = $(patsubst %.c,%,$(wildcard src/*.c)) 18 | PG91 = $(shell $(PG_CONFIG) --version | grep -qE " 8\.| 9\.0" && echo no || echo yes) 19 | 20 | DATA = $(wildcard sql/*--*.sql) 21 | EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql 22 | 23 | PGXS := $(shell $(PG_CONFIG) --pgxs) 24 | include $(PGXS) 25 | 26 | all: sql/$(EXTENSION)--$(EXTVERSION).sql 27 | 28 | sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql 29 | cp $< $@ 30 | 31 | pkglibdir = $(shell $(PG_CONFIG) --pkglibdir) 32 | 33 | 34 | 35 | dist: 36 | git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ -o $(EXTENSION)-$(EXTVERSION).zip HEAD 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pg_idx_advisor 2 | ================= 3 | 4 | [![PGXN version](https://badge.fury.io/pg/pg_idx_advisor.svg)](https://badge.fury.io/pg/pg_idx_advisor) 5 | 6 | A PostgreSQL extension to analyze queries and give indexing advice. 7 | 8 | ## Note: This is no longer updated - please see: https://github.com/HypoPG/hypopg 9 | 10 | ## Introduction ## 11 | The index advisor uses the virtual index framework/support built into PG 12 | to provide a list of "candidates" for the query engine to choose from. 13 | 14 | The following features are enabled/supported (more to come :) ) 15 | Feature list: 16 | - Partial indexes 17 | - CTE 18 | - functional indexes 19 | - text_pattern_ops 20 | - inheritance tables 21 | - composite indexes 22 | 23 | ## Installation ## 24 | make 25 | make install 26 | 27 | If you encounter an error such as: 28 | 29 | make: pg_config: Command not found 30 | 31 | Be sure that you have `pg_config` installed and in your path. If you used a 32 | package management system such as RPM to install PostgreSQL, be sure that the 33 | `-devel` package is also installed. If necessary tell the build process where 34 | to find it: 35 | 36 | env PG_CONFIG=/path/to/pg_config make && make installcheck && make install 37 | 38 | 39 | 40 | ## Usage ## 41 | First Create the extension `create extension pg_idx_advisor;` it will create the requires table to store query recommendations. 42 | Then you must load the library using the 'LOAD' command: `Load 'pg_idx_advisor.so'` 43 | That's it - you are ready to be advised on your first query. 44 | simply run the query with the "explain" keyword - you will see both the original execution plan as well as the new plan with the suggested Virtual/Hypothetical indexes. 45 | 46 | Examples: 47 | 48 | ``` 49 | postgres=# explain select c21 from entities_113681518 where c11 = 200; 50 | INFO: 51 | ** Plan with original indexes ** 52 | 53 | QUERY PLAN 54 | ------------------------------------------------------------------------------------------ 55 | Seq Scan on entities_113681518 (cost=0.00..10.00 rows=1 width=8) 56 | Filter: (c11 = 200) 57 | 58 | ** Plan with hypothetical indexes ** 59 | read only, advice, index: create index on entities_113681518(c11) 60 | Index Scan using :49154 on entities_113681518 (cost=0.00..8.02 rows=1 width=8) 61 | Index Cond: (c11 = 200) 62 | (7 rows) 63 | ``` 64 | 65 | Works with CTE as well: 66 | 67 | ``` 68 | postgres=# explain with vals as (select c21 from entities_113681518 where c11 = 200 ) select * from vals; 69 | INFO: 70 | ** Plan with original indexes ** 71 | 72 | QUERY PLAN 73 | -------------------------------------------------------------------------------------------------- 74 | CTE Scan on vals (cost=10.00..10.02 rows=1 width=8) 75 | CTE vals 76 | -> Seq Scan on entities_113681518 (cost=0.00..10.00 rows=1 width=8) 77 | Filter: (c11 = 200) 78 | 79 | ** Plan with hypothetical indexes ** 80 | read only, advice, index: create index on entities_113681518(c11) 81 | CTE Scan on vals (cost=8.02..8.04 rows=1 width=8) 82 | CTE vals 83 | -> Index Scan using :49156 on entities_113681518 (cost=0.00..8.02 rows=1 width=8) 84 | Index Cond: (c11 = 200) 85 | (11 rows) 86 | 87 | ``` 88 | 89 | 90 | Dependencies 91 | ------------ 92 | The `pg_idx_advisor` extension has no dependencies other than PostgreSQL. 93 | 94 | Copyright and License 95 | --------------------- 96 | 97 | Copyright (c) 2010-2014 Jony Vesterman Cohen. 98 | 99 | This module is free software; you can redistribute it and/or modify it under 100 | the [PostgreSQL License](http://www.opensource.org/licenses/postgresql). 101 | 102 | Permission to use, copy, modify, and distribute this software and its 103 | documentation for any purpose, without fee, and without a written agreement is 104 | hereby granted, provided that the above copyright notice and this paragraph 105 | and the following two paragraphs appear in all copies. 106 | 107 | In no event shall Jony Vesterman Cohen be liable to any party for direct, indirect, 108 | special, incidental, or consequential damages, including lost profits, 109 | arising out of the use of this software and its documentation, 110 | even if Jony Vesterman Cohen has been advised of the possibility of such damage. 111 | 112 | Jony Vesterman Cohen specifically disclaim any warranties, 113 | including, but not limited to, the implied warranties of merchantability and 114 | fitness for a particular purpose. The software provided hereunder is on an "as 115 | is" basis, and Jony Vesterman Cohen have no obligations to provide 116 | maintenance, support, updates, enhancements, or modifications. 117 | -------------------------------------------------------------------------------- /doc/pg_idx_advisor.md: -------------------------------------------------------------------------------- 1 | pg_idx_advisor 0.1.2 2 | ============ 3 | 4 | Synopsis 5 | -------- 6 | ``` 7 | create extension pg_idx_advisor; 8 | CREATE EXTENSION 9 | 10 | load 'pg_idx_advisor.so'; 11 | NOTICE: IND ADV: plugin loaded 12 | LOAD 13 | 14 | explain select * from t where a = 100; 15 | INFO: 16 | ** Plan with original indexes ** 17 | 18 | QUERY PLAN 19 | -------------------------------------------------------------------------------- 20 | Seq Scan on t (cost=0.00..36.75 rows=11 width=8) 21 | Filter: (a = 100) 22 | 23 | ** Plan with hypothetical indexes ** 24 | read only, advice, index: create index on t(a) 25 | Bitmap Heap Scan on t (cost=4.12..14.79 rows=11 width=8) 26 | Recheck Cond: (a = 100) 27 | -> Bitmap Index Scan on :114699 (cost=0.00..4.11 rows=11 width=0) 28 | Index Cond: (a = 100) 29 | (9 rows) 30 | ``` 31 | Description 32 | ----------- 33 | 34 | The index advisor uses the virtual index framework/support built into PG 35 | to provide a list of "candidates" for the query engine to choose from. 36 | 37 | The following features are enabled/supported (more to come :) ) 38 | Feature list: 39 | - Partial indexes 40 | - CTE 41 | - functional indexes 42 | - text_pattern_ops 43 | - inheritance tables 44 | - composite indexes 45 | 46 | Usage 47 | ----- 48 | 49 | First you must load the library using the 'LOAD' command: 50 | `Load 'g_idx_advisor.so'` 51 | simply run the query with the "explain" - you will see both the original execution plan as well as the new plan with the suggested Virtual/Hypothetical indexes. 52 | 53 | Examples: 54 | 55 | ``` 56 | postgres=# explain select c21 from entities_113681518 where c11 = 200; 57 | INFO: 58 | ** Plan with original indexes ** 59 | 60 | QUERY PLAN 61 | ------------------------------------------------------------------------------------------ 62 | Seq Scan on entities_113681518 (cost=0.00..10.00 rows=1 width=8) 63 | Filter: (c11 = 200) 64 | 65 | ** Plan with hypothetical indexes ** 66 | read only, advice, index: create index on entities_113681518(c11) 67 | Index Scan using :49154 on entities_113681518 (cost=0.00..8.02 rows=1 width=8) 68 | Index Cond: (c11 = 200) 69 | (7 rows) 70 | ``` 71 | 72 | Works with CTE as well: 73 | 74 | ``` 75 | postgres=# explain with vals as (select c21 from entities_113681518 where c11 = 200 ) select * from vals; 76 | INFO: 77 | ** Plan with original indexes ** 78 | 79 | QUERY PLAN 80 | -------------------------------------------------------------------------------------------------- 81 | CTE Scan on vals (cost=10.00..10.02 rows=1 width=8) 82 | CTE vals 83 | -> Seq Scan on entities_113681518 (cost=0.00..10.00 rows=1 width=8) 84 | Filter: (c11 = 200) 85 | 86 | ** Plan with hypothetical indexes ** 87 | read only, advice, index: create index on entities_113681518(c11) 88 | CTE Scan on vals (cost=8.02..8.04 rows=1 width=8) 89 | CTE vals 90 | -> Index Scan using :49156 on entities_113681518 (cost=0.00..8.02 rows=1 width=8) 91 | Index Cond: (c11 = 200) 92 | (11 rows) 93 | 94 | ``` 95 | 96 | 97 | Support 98 | ------- 99 | 100 | This library is stored in an open [GitHub repository](https://github.com/cohenjo/pg_idx_advisor). Feel free to fork and contribute! 101 | Please file bug reports via [GitHub Issues](https://github.com/cohenjo/pg_idx_advisor/issues). 102 | 103 | Author 104 | ------ 105 | 106 | [Jony Vesterman Cphen]() 107 | 108 | Copyright and License 109 | --------------------- 110 | 111 | Copyright (c) 2010-2014 Jony Vesterman Cohen. 112 | 113 | This module is free software; you can redistribute it and/or modify it under 114 | the [PostgreSQL License](http://www.opensource.org/licenses/postgresql). 115 | 116 | Permission to use, copy, modify, and distribute this software and its 117 | documentation for any purpose, without fee, and without a written agreement is 118 | hereby granted, provided that the above copyright notice and this paragraph 119 | and the following two paragraphs appear in all copies. 120 | 121 | In no event shall Jony Vesterman Cohen be liable to any party for direct, indirect, 122 | special, incidental, or consequential damages, including lost profits, 123 | arising out of the use of this software and its documentation, 124 | even if Jony Vesterman Cohen has been advised of the possibility of such damage. 125 | 126 | Jony Vesterman Cohen specifically disclaim any warranties, 127 | including, but not limited to, the implied warranties of merchantability and 128 | fitness for a particular purpose. The software provided hereunder is on an "as 129 | is" basis, and Jony Vesterman Cohen have no obligations to provide 130 | maintenance, support, updates, enhancements, or modifications. 131 | -------------------------------------------------------------------------------- /pg_idx_advisor.control: -------------------------------------------------------------------------------- 1 | # pg_idx_advisor extension 2 | comment = 'Index advice for PostgreSQL' 3 | default_version = '0.1.2' 4 | module_pathname = '$pkglibdir/pg_idx_advisor' 5 | relocatable = true 6 | -------------------------------------------------------------------------------- /sql/pg_idx_advisor--0.1.1--0.1.2.sql: -------------------------------------------------------------------------------- 1 | create table index_advisory( reloid oid, 2 | attrs integer[], 3 | benefit real, 4 | index_size integer, 5 | backend_pid integer, 6 | timestamp timestamptz, 7 | indcollation int[], -- oidvector 8 | indclass int[], 9 | indoption int[], 10 | indexprs text, 11 | indpred text, 12 | query text, 13 | recommendation text); 14 | 15 | create index IA_reloid on index_advisory( reloid ); 16 | create index IA_backend_pid on index_advisory( backend_pid ); 17 | -------------------------------------------------------------------------------- /sql/pg_idx_advisor.sql: -------------------------------------------------------------------------------- 1 | create table index_advisory( reloid oid, 2 | attrs integer[], 3 | benefit real, 4 | index_size integer, 5 | backend_pid integer, 6 | timestamp timestamptz, 7 | indcollation int[], -- oidvector 8 | indclass int[], 9 | indoption int[], 10 | indexprs text, 11 | indpred text, 12 | query text, 13 | recommendation text); 14 | 15 | create index IA_reloid on index_advisory( reloid ); 16 | create index IA_backend_pid on index_advisory( backend_pid ); 17 | -------------------------------------------------------------------------------- /src/idx_adviser.c: -------------------------------------------------------------------------------- 1 | /*!------------------------------------------------------------------------- 2 | * 3 | * \file idx_adviser.c 4 | * \brief Plugin to analyze and give indexing advice. 5 | * 6 | * 7 | * 8 | * 9 | * Noted projects: 10 | * ================ 11 | * pg_adviser - 12 | * Hypothetical indexes in PostgreSQL 13 | * 14 | * Project History: 15 | * ================ 16 | * 10/06/2013 Jony Cohen Initial Version compile & run against PostgreSQL 9.2.4 17 | * 15/09/2013 Jony Cohen added functionality to support partial & functional indexes. 18 | * 15/02/2015 Jony Cohen Support for PostgreSQL 9.4 19 | * 22/02/2015 Jony Cohen Expression tree walker - Increase the code stability for newer versions. (Support CTE etc.) 20 | * 21 | *------------------------------------------------------------------------- 22 | */ 23 | 24 | /* ------------------------------------------------------------------------ 25 | * Includes 26 | * ------------------------------------------------------------------------ 27 | */ 28 | //#include 29 | 30 | #include "postgres.h" 31 | 32 | #include "access/genam.h" 33 | #include "access/heapam.h" 34 | #include "access/itup.h" 35 | #include "access/nbtree.h" 36 | #include "access/xact.h" 37 | #include "idx_adviser.h" 38 | #include "catalog/catalog.h" 39 | #include "catalog/index.h" 40 | #include "catalog/indexing.h" 41 | #include "catalog/namespace.h" 42 | #include "commands/defrem.h" 43 | #include "commands/explain.h" 44 | #include "executor/execdesc.h" 45 | #include "executor/spi.h" 46 | #include "fmgr.h" /* for PG_MODULE_MAGIC */ 47 | #include "miscadmin.h" 48 | #include "nodes/pg_list.h" 49 | #include "nodes/print.h" 50 | #include "optimizer/planner.h" 51 | #include "optimizer/plancat.h" 52 | #include "parser/parse_coerce.h" 53 | #include "parser/parsetree.h" 54 | #include "storage/lmgr.h" 55 | #include "storage/proc.h" 56 | #include "tcop/dest.h" 57 | #include "tcop/tcopprot.h" 58 | #include "utils.h" 59 | #include "utils/builtins.h" 60 | #include "utils/elog.h" 61 | #include "utils/lsyscache.h" 62 | #include "utils/relcache.h" 63 | #include "utils/syscache.h" 64 | #include "utils/selfuncs.h" 65 | 66 | /* mark this dynamic library to be compatible with PG as of PG 8.2 */ 67 | PG_MODULE_MAGIC; 68 | 69 | /* ***************************************************************************** 70 | * Function Declarations 71 | * ****************************************************************************/ 72 | 73 | static bool index_candidates_walker (Node *root, ScanContext *context); 74 | static List* scan_query( const Query* const query, 75 | OpnosContext* context, 76 | List* rangeTableStack ); 77 | 78 | static List* scan_generic_node( const Node* const root, 79 | OpnosContext *context, 80 | List* const rangeTableStack ); 81 | 82 | static List* scan_group_clause( List* const groupList, 83 | List* const targtList, 84 | OpnosContext* context, 85 | List* const rangeTblStack ); 86 | 87 | static List* scan_targetList( List* const targetList, 88 | OpnosContext* context, 89 | List* const rangeTableStack ); 90 | 91 | 92 | static List* build_composite_candidates( List* l1, List* l2 ); 93 | 94 | static List* remove_irrelevant_candidates( List* candidates ); 95 | static void tag_and_remove_candidates(Cost startupCostSaved, 96 | Cost totalCostSaved, 97 | PlannedStmt *new_plan, 98 | const Node* const head, 99 | List* const candidates); 100 | static void mark_used_candidates( const Node* const plan, 101 | List* const candidates ); 102 | 103 | static int compare_candidates( const IndexCandidate* c1, 104 | const IndexCandidate* c2 ); 105 | 106 | static List* merge_candidates( List* l1, List* l2 ); 107 | static List* expand_inherited_candidates(List* list1); 108 | static void expand_inherited_rel_clauses(); 109 | 110 | 111 | static List* create_virtual_indexes( List* candidates ); 112 | static void drop_virtual_indexes( List* candidates ); 113 | 114 | static List* get_rel_clauses(List* table_clauses, Oid reloid, char* erefAlias); 115 | static ListCell* get_rel_clausesCell(List* table_clauses, Oid reloid, char* erefAlias); 116 | static Expr* makePredicateClause(OpExpr* root,Const* constArg, Var* VarArg); 117 | static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index, 118 | Relation heapRelation); 119 | 120 | static void store_idx_advice( List* candidates, ExplainState * es ); 121 | 122 | static void log_candidates( const char* text, List* candidates ); 123 | 124 | /* function used for estimating the size of virtual indexes */ 125 | static BlockNumber estimate_index_pages(Oid rel_oid, Oid ind_oid ); 126 | 127 | static PlannedStmt* planner_callback( Query* query, 128 | int cursorOptions, 129 | ParamListInfo boundParams); 130 | 131 | static void ExplainOneQuery_callback( Query* query, IntoClause *into, 132 | ExplainState* stmt, 133 | const char* queryString, 134 | ParamListInfo params); 135 | 136 | static void get_relation_info_callback( PlannerInfo* root, 137 | Oid relationObjectId, 138 | bool inhparent, 139 | RelOptInfo* rel); 140 | 141 | static const char* explain_get_index_name_callback( Oid indexId ); 142 | 143 | static PlannedStmt* index_adviser( Query* query, 144 | int cursorOptions, 145 | ParamListInfo boundParams, 146 | PlannedStmt* actual_plan, 147 | ExplainState * es, 148 | bool doingExplain); 149 | 150 | static void resetSecondaryHooks(void); 151 | static bool is_virtual_index( Oid oid, IndexCandidate** cand_out ); 152 | 153 | /* ------------------------------------------------------------------------ 154 | * Global Parameters 155 | * ------------------------------------------------------------------------ 156 | */ 157 | 158 | /*! global list of index candidates. */ 159 | static List* index_candidates; 160 | /* Need this to store the predicate for the partial indexes. */ 161 | //static QueryContext* context; 162 | 163 | /*! Need this to store table clauses until they are filled in the candidates*/ 164 | static List* table_clauses; 165 | 166 | /*! Holds the advisor configuration */ 167 | static bool idxadv_read_only; 168 | static bool idxadv_text_pattern_ops; 169 | static char *idxadv_columns; 170 | static char *idxadv_schema; 171 | static int idxadv_composit_max_cols; 172 | 173 | 174 | /*! Global variable to hold a value across calls to mark_used_candidates() */ 175 | static PlannedStmt* plannedStmtGlobal; 176 | //static char *envVar; 177 | 178 | 179 | /** parameters to store the old hooks */ 180 | static planner_hook_type prev_planner = NULL; 181 | static ExplainOneQuery_hook_type prev_ExplainOneQuery = NULL; 182 | 183 | 184 | /* ------------------------------------------------------------------------ 185 | * implementations: index adviser 186 | * ------------------------------------------------------------------------ 187 | */ 188 | 189 | /* PG calls this func when loading the plugin */ 190 | void _PG_init(void) 191 | { 192 | elog(DEBUG1,"IND ADV: load parameters"); 193 | /* Read parameters */ 194 | DefineCustomStringVariable("index_adviser.cols", 195 | "comma separated list of column names to be used in partial indexes", 196 | NULL, 197 | &idxadv_columns, 198 | "entity_type_id,is_deleted", 199 | PGC_SUSET, 200 | 0, 201 | NULL, 202 | NULL, 203 | NULL); 204 | DefineCustomStringVariable("index_adviser.schema", 205 | "index advisory recommendation schema", 206 | NULL, 207 | &idxadv_schema, 208 | "public", 209 | PGC_SUSET, 210 | 0, 211 | NULL, 212 | NULL, 213 | NULL); 214 | elog(DEBUG1,"IND ADV: load parameters"); 215 | DefineCustomBoolVariable("index_adviser.read_only", 216 | "disables insertion of recommendations to the advisory table - only prints to screen", 217 | NULL, 218 | &idxadv_read_only, 219 | false, 220 | PGC_SUSET, 221 | 0, 222 | NULL, 223 | NULL, 224 | NULL); 225 | elog(DEBUG1,"IND ADV: load parameters"); 226 | DefineCustomBoolVariable("index_adviser.text_pattern_ops", 227 | "allows creation of text indexes with text_pattern_ops", 228 | NULL, 229 | &idxadv_text_pattern_ops, 230 | true, 231 | PGC_SUSET, 232 | 0, 233 | NULL, 234 | NULL, 235 | NULL); 236 | DefineCustomIntVariable("index_adviser.composit_max_cols", 237 | "max number of columns to use in composite indexes.", 238 | NULL, 239 | &idxadv_composit_max_cols, 240 | 3, 241 | 1, 242 | INT_MAX, 243 | PGC_SIGHUP, 244 | 0, 245 | NULL, 246 | NULL, 247 | NULL); 248 | 249 | elog(DEBUG1,"IND ADV: loaded parameters"); 250 | /* Install hookds. */ 251 | prev_ExplainOneQuery = ExplainOneQuery_hook; 252 | ExplainOneQuery_hook = ExplainOneQuery_callback; 253 | prev_planner = planner_hook; 254 | planner_hook = planner_callback; 255 | 256 | /* We dont need to reset the state here since the contrib module has just been 257 | * loaded; FIXME: consider removing this call. 258 | */ 259 | resetSecondaryHooks(); 260 | 261 | elog( NOTICE , "IND ADV: plugin loaded" ); 262 | } 263 | 264 | /* PG calls this func when un-loading the plugin (if ever) */ 265 | void _PG_fini(void) 266 | { 267 | /* Uninstall hooks. */ 268 | planner_hook = prev_planner; 269 | ExplainOneQuery_hook = prev_ExplainOneQuery; 270 | 271 | resetSecondaryHooks(); 272 | 273 | elog( NOTICE , "IND ADV: plugin unloaded." ); 274 | } 275 | 276 | /* Make sure that Cost datatype can represent negative values */ 277 | compile_assert( ((Cost)-1) < 0 ); 278 | 279 | /* As of now the order-by and group-by clauses use the same C-struct. 280 | * A rudimentary check to confirm this: 281 | */ 282 | compile_assert( sizeof(*((Query*)NULL)->groupClause) == sizeof(*((Query*)NULL)->sortClause) ); 283 | 284 | /** 285 | * index_adviser 286 | * Takes a query and the actual plan generated by the standard planner for 287 | * that query. It then creates virtual indexes, using the columns used in the 288 | * query, and asks the standard planner to generate a new plan for the query. 289 | * 290 | * If the new plan appears to be cheaper than the actual plan, it 291 | * saves the information about the virtual indexes that were used by the 292 | * planner in an advisory table. 293 | * 294 | * If it is called by the Explain-hook, then it returns the newly generated 295 | * plan (allocated in caller's memory context), so that ExplainOnePlan() can 296 | * generate and send a string representation of the plan to the log or the client. 297 | */ 298 | 299 | /* TODO: Make recursion suppression more bullet-proof. ERRORs can leave this indicator on. */ 300 | static int8 SuppressRecursion = 0; /* suppress recursive calls */ 301 | 302 | static PlannedStmt* index_adviser( Query* queryCopy, 303 | int cursorOptions, 304 | ParamListInfo boundParams, 305 | PlannedStmt *actual_plan, 306 | ExplainState * es, 307 | bool doingExplain) 308 | { 309 | bool saveCandidates = false; 310 | int i; 311 | ListCell *cell; 312 | List* candidates = NIL; /* the resulting candidates */ 313 | OpnosContext *context; /* contains all valid operator-ids */ 314 | 315 | Cost actualStartupCost; 316 | Cost actualTotalCost; 317 | Cost newStartupCost; 318 | Cost newTotalCost; 319 | Cost startupCostSaved; 320 | Cost totalCostSaved; 321 | float4 startupGainPerc; /* in percentages */ 322 | float4 totalGainPerc; 323 | 324 | ResourceOwner oldResourceOwner; 325 | PlannedStmt *new_plan; 326 | MemoryContext outerContext; 327 | 328 | 329 | char *SupportedOps[] = { "=", "<", ">", "<=", ">=", "~~", }; /* Added support for LIKE ~~ */ 330 | char *SupportedGistOps[] = { "<<", "&<", "&>", ">>", "<<|", "&<|", "|&>", "|>>", "@>", "<@", "~=", "&&", }; 331 | char *SupportedGinOps[] = { "<@", "@>", "=", "&&", }; 332 | 333 | elog( DEBUG3, "IND ADV: Entering" ); 334 | 335 | 336 | /* We work only in Normal Mode, and non-recursively; that is, we do not work 337 | * on our own DML. 338 | */ 339 | if( IsBootstrapProcessingMode() || SuppressRecursion++ > 0 ) 340 | { 341 | new_plan = NULL; 342 | goto DoneCleanly; 343 | } 344 | 345 | /* Remember the memory context; we use it to pass interesting data back. */ 346 | outerContext = CurrentMemoryContext; 347 | 348 | /* reset these globals; since an ERROR might have left them unclean */ 349 | index_candidates = NIL; 350 | table_clauses = NIL; 351 | 352 | 353 | /* get the costs without any virtual index */ 354 | actualStartupCost = actual_plan->planTree->startup_cost; 355 | actualTotalCost = actual_plan->planTree->total_cost; 356 | elog( DEBUG2 , "IND ADV: actual plan costs: %lf .. %lf",actualStartupCost,actualTotalCost); 357 | 358 | /* create list containing all operators supported by the index advisor */ 359 | context = (OpnosContext*)palloc(sizeof(OpnosContext)); 360 | context->opnos = NIL; 361 | context->ginopnos = NIL; 362 | context->gistopnos = NIL; 363 | context->opnos = create_operator_list(SupportedOps, lengthof(SupportedOps)) ; 364 | context->ginopnos = create_operator_list(SupportedGinOps, lengthof(SupportedGinOps)) ; 365 | context->gistopnos = create_operator_list(SupportedGistOps, lengthof(SupportedGistOps)) ; 366 | 367 | elog( DEBUG3, "IND ADV: Generate index candidates" ); 368 | /* Generate index candidates */ 369 | candidates = scan_query( queryCopy, context, NULL ); 370 | 371 | /* the list of operator oids isn't needed anymore */ 372 | list_free( context->opnos ); 373 | list_free( context->ginopnos ); 374 | list_free( context->gistopnos ); 375 | pfree(context); 376 | 377 | if (list_length(candidates) == 0) 378 | goto DoneCleanly; 379 | 380 | log_candidates( "Generated candidates", candidates ); 381 | elog( DEBUG3, "IND ADV: remove all irrelevant candidates" ); 382 | /* remove all irrelevant candidates */ 383 | candidates = remove_irrelevant_candidates( candidates ); 384 | 385 | if (list_length(candidates) == 0) 386 | goto DoneCleanly; 387 | 388 | log_candidates( "Relevant candidates", candidates ); 389 | /* 390 | * We need to restore the resource-owner after RARCST(), only if we are 391 | * called from the executor; but we do it all the time because, 392 | * (1) Its difficult to determine if we are being called by the executor. 393 | * (2) It is harmless. 394 | * (3) It is not much of an overhead! 395 | */ 396 | oldResourceOwner = CurrentResourceOwner; 397 | 398 | /* 399 | * Setup an SPI frame around the BeginInternalSubTransaction() and 400 | * RollbackAndReleaseCurrentSubTransaction(), since xact.c assumes that 401 | * BIST()/RARCST() infrastructure is used only by PL/ interpreters (like 402 | * pl/pgsql), and hence it calls AtEOSubXact_SPI(), and that in turn frees 403 | * all the execution context memory of the SPI (which _may_ have invoked the 404 | * adviser). By setting up our own SPI frame here, we make sure that 405 | * AtEOSubXact_SPI() frees this frame's memory. 406 | */ 407 | elog( DEBUG1, "About to call SPI connect - push SPI first"); 408 | //SPI_push(); 409 | if (SPI_push_conditional()) 410 | { 411 | elog( DEBUG1, "pushed SPI" ); 412 | } 413 | elog( DEBUG1, "SPI connection start - TODO FIX THIS!!"); 414 | if( SPI_connect() != SPI_OK_CONNECT ) 415 | { 416 | elog( WARNING, "IND ADV: SPI_connect() call failed - pre virtual index creation." ); 417 | // goto DoneCleanly; 418 | } 419 | 420 | /* 421 | * DO NOT access any data-structure allocated between BEGIN/ROLLBACK 422 | * transaction, after the ROLLBACK! All the memory allocated after BEGIN is 423 | * freed in ROLLBACK. 424 | */ 425 | elog( DEBUG1, "Start internal sub transaction"); 426 | BeginInternalSubTransaction( "index_adviser" ); 427 | 428 | elog( DEBUG1, "now create the virtual indexes "); 429 | /* now create the virtual indexes */ 430 | candidates = create_virtual_indexes( candidates ); 431 | 432 | /* update the global var */ 433 | index_candidates = candidates; 434 | 435 | /* 436 | * Setup the hook in the planner that injects information into base-tables 437 | * as they are prepared 438 | */ 439 | get_relation_info_hook = get_relation_info_callback; 440 | 441 | elog( DEBUG1, "IDX ADV: do re-planning using virtual indexes" ); 442 | /* do re-planning using virtual indexes */ 443 | /* TODO: is the plan ever freed? */ 444 | 445 | new_plan = standard_planner(queryCopy, cursorOptions, boundParams); 446 | 447 | elog( DEBUG1, "IND ADV: release the hook" ); 448 | /* reset the hook */ 449 | get_relation_info_hook = NULL; 450 | 451 | elog( DEBUG1, "IND ADV: remove the virtual-indexes" ); 452 | /* remove the virtual-indexes */ 453 | drop_virtual_indexes( candidates ); 454 | 455 | newStartupCost = new_plan->planTree->startup_cost; 456 | newTotalCost = new_plan->planTree->total_cost; 457 | elog( DEBUG1 , "IND ADV: new plan costs: %lf .. %lf ",newStartupCost,newTotalCost); 458 | 459 | elog( DEBUG1, "IND ADV: calculate the cost benefits" ); 460 | /* calculate the cost benefits */ 461 | startupGainPerc = 462 | actualStartupCost == 0 ? 0 : 463 | (1 - newStartupCost/actualStartupCost) * 100; 464 | 465 | totalGainPerc = 466 | actualTotalCost == 0 ? 0 : 467 | (1 - newTotalCost/actualTotalCost) * 100; 468 | 469 | startupCostSaved = actualStartupCost - newStartupCost; 470 | 471 | totalCostSaved = actualTotalCost - newTotalCost; 472 | 473 | 474 | tag_and_remove_candidates(startupCostSaved, totalCostSaved, new_plan, (Node*)new_plan->planTree, candidates); 475 | /* 476 | if( startupCostSaved >0 || totalCostSaved > 0 ) 477 | { 478 | 479 | plannedStmtGlobal = new_plan; 480 | 481 | //elog_node_display( DEBUG4, "plan (using Index Adviser)",(Node*)new_plan->planTree, true ); 482 | 483 | mark_used_candidates(, candidates ); 484 | 485 | plannedStmtGlobal = NULL; 486 | } 487 | */ 488 | 489 | 490 | /* update the global var */ 491 | index_candidates = candidates; 492 | 493 | elog( DEBUG2, "IND ADV: log the candidates used by the planner" ); 494 | /* log the candidates used by the planner */ 495 | log_candidates( "Used candidates", candidates ); 496 | 497 | if( list_length( candidates ) > 0 ) 498 | saveCandidates = true; 499 | 500 | /* calculate the share of cost saved by each index */ 501 | if( saveCandidates ) 502 | { 503 | int8 totalSize = 0; 504 | IndexCandidate *cand; 505 | 506 | foreach( cell, candidates ) 507 | totalSize += ((IndexCandidate*)lfirst( cell ))->pages; 508 | 509 | foreach( cell, candidates ) 510 | { 511 | cand = (IndexCandidate*)lfirst( cell ); 512 | 513 | elog( DEBUG2, "IND ADV: benefit: saved: %f, pages: %d, size: %d", totalCostSaved,cand->pages,totalSize); 514 | cand->benefit = (float4)totalCostSaved 515 | * ((float4)cand->pages/totalSize); 516 | } 517 | } 518 | 519 | elog( DEBUG2, "IND ADV: Print the new plan if debugging" ); 520 | /* Print the new plan if debugging. */ 521 | if( saveCandidates && Debug_print_plan ) 522 | elog_node_display( DEBUG2, "plan (using Index Adviser)", 523 | new_plan, Debug_pretty_print ); 524 | 525 | /* If called from the EXPLAIN hook, make a copy of the plan to be passed back */ 526 | if( saveCandidates && doingExplain ) 527 | { 528 | MemoryContext oldContext = MemoryContextSwitchTo( outerContext ); 529 | 530 | new_plan = copyObject( new_plan ); 531 | 532 | MemoryContextSwitchTo( oldContext ); 533 | } 534 | else 535 | { 536 | /* TODO: try to free the new plan node */ 537 | new_plan = NULL; 538 | } 539 | /* 540 | * Undo the metadata changes; for eg. pg_depends entries will be removed 541 | * (from our MVCC view). 542 | * 543 | * Again: DO NOT access any data-structure allocated between BEGIN/ROLLBACK 544 | * transaction, after the ROLLBACK! All the memory allocated after BEGIN is 545 | * freed in ROLLBACK. 546 | */ 547 | RollbackAndReleaseCurrentSubTransaction(); 548 | 549 | /* restore the resource-owner */ 550 | CurrentResourceOwner = oldResourceOwner; 551 | 552 | elog( DEBUG1, "SPI connection finish"); 553 | if( SPI_finish() != SPI_OK_FINISH ) 554 | elog( WARNING, "IND ADV: SPI_finish failed." ); 555 | 556 | elog( DEBUG1, "IDX_ADV: save the advice into the table" ); 557 | /* save the advise into the table */ 558 | if( saveCandidates ) 559 | { 560 | /* catch any ERROR */ 561 | PG_TRY(); 562 | { 563 | elog( DEBUG1, "IND ADV: pre-save the advise into the table" ); 564 | store_idx_advice(candidates, es); 565 | elog( DEBUG1, "IND ADV: post-save the advise into the table" ); 566 | } 567 | PG_CATCH(); 568 | { 569 | /* reset our 'running' state... */ 570 | --SuppressRecursion; 571 | 572 | /* 573 | * Add a detailed explanation to the ERROR. Note that these function 574 | * calls will overwrite the DETAIL and HINT that are already 575 | * associated (if any) with this ERROR. XXX consider errcontext(). 576 | */ 577 | errdetail( IDX_ADV_ERROR_DETAIL ); 578 | errhint( IDX_ADV_ERROR_HINT ); 579 | 580 | /* ... and re-throw the ERROR */ 581 | PG_RE_THROW(); 582 | } 583 | PG_END_TRY(); 584 | 585 | } 586 | 587 | /* free the candidate-list */ 588 | elog( DEBUG3, "IND ADV: Deleting candidate list." ); 589 | if( !saveCandidates || !doingExplain ) 590 | { 591 | foreach( cell, index_candidates ) 592 | pfree( (IndexCandidate*)lfirst( cell ) ); 593 | 594 | list_free( index_candidates ); 595 | index_candidates = NIL; 596 | 597 | foreach( cell, table_clauses ) 598 | pfree( (RelClause*)lfirst( cell ) ); 599 | 600 | list_free( table_clauses ); 601 | table_clauses = NIL; 602 | } 603 | 604 | elog( DEBUG3, "IND ADV: Done." ); 605 | 606 | /* emit debug info */ 607 | elog( DEBUG1, "IND ADV: old cost %.2f..%.2f", actualStartupCost, 608 | actualTotalCost ); 609 | elog( DEBUG1, "IND ADV: new cost %.2f..%.2f", newStartupCost, newTotalCost); 610 | elog( DEBUG1, "IND ADV: cost saved %.2f..%.2f, these are %lu..%lu percent", 611 | startupCostSaved, 612 | totalCostSaved, 613 | (unsigned long)startupGainPerc, 614 | (unsigned long)totalGainPerc ); 615 | 616 | 617 | DoneCleanly: 618 | /* allow new calls to the index-adviser */ 619 | --SuppressRecursion; 620 | 621 | elog( DEBUG3, "IDX ADV: EXIT" ); 622 | return doingExplain && saveCandidates ? new_plan : NULL; 623 | } 624 | 625 | /* 626 | * This callback is registered immediately upon loading this plugin. It is 627 | * responsible for taking over control from the planner. 628 | * 629 | * It calls the standard planner and sends the resulting plan to 630 | * index_adviser() for comparison with a plan generated after creating 631 | * hypothetical indexes. 632 | */ 633 | static PlannedStmt* planner_callback( Query* query, 634 | int cursorOptions, 635 | ParamListInfo boundParams) 636 | { 637 | Query *queryCopy; 638 | PlannedStmt *actual_plan; 639 | PlannedStmt *new_plan; 640 | 641 | resetSecondaryHooks(); 642 | 643 | /* TODO : try to avoid making a copy if the index_adviser() is not going 644 | * to use it; Index Adviser may not use the query copy at all if we are 645 | * running in BootProcessing mode, or if the Index Adviser is being called 646 | * recursively. 647 | */ 648 | 649 | elog( DEBUG3 , "planner_callback: enter"); 650 | /* planner() scribbles on it's input, so make a copy of the query-tree */ 651 | queryCopy = copyObject( query ); 652 | 653 | /* Generate a plan using the standard planner */ 654 | elog( DEBUG3 , "planner_callback: standard planner"); 655 | actual_plan = standard_planner( query, cursorOptions, boundParams ); 656 | 657 | PG_TRY(); 658 | { 659 | 660 | 661 | /* send the actual plan for comparison with a hypothetical plan */ 662 | elog( DEBUG3 , "planner_callback: index_adviser"); 663 | new_plan = index_adviser( queryCopy, cursorOptions, boundParams, 664 | actual_plan,NULL, false ); 665 | } 666 | PG_CATCH(); 667 | { 668 | elog(WARNING, "Failed to create index advice for: %s",debug_query_string); 669 | /* reset our 'running' state... */ 670 | SuppressRecursion=0; 671 | 672 | } 673 | PG_END_TRY(); 674 | 675 | /* TODO: try to free the redundant new_plan */ 676 | elog( DEBUG3 , "planner_callback: Done"); 677 | 678 | return actual_plan; 679 | } 680 | 681 | /* 682 | * This callback is registered immediately upon loading this plugin. It is 683 | * responsible for taking over control from the ExplainOneQuery() function. 684 | * 685 | * It calls the standard planner and sends the resultant plan to 686 | * index_adviser() for comparison with a plan generated after creating 687 | * hypothetical indexes. 688 | * 689 | * If the index_adviser() finds the hypothetical plan to be beneficial 690 | * than the real plan, it returns the hypothetical plan's copy so that this 691 | * hook can send it to the log. 692 | */ 693 | static void 694 | ExplainOneQuery_callback(Query *query, IntoClause *into, 695 | ExplainState *stmt, 696 | const char *queryString, 697 | ParamListInfo params) 698 | { 699 | Query *queryCopy; 700 | PlannedStmt *actual_plan; 701 | PlannedStmt *new_plan; 702 | ListCell *cell; 703 | instr_time planduration; // TODO: consider printing this as well 704 | 705 | resetSecondaryHooks(); 706 | 707 | //elog(DEBUG1, "Original Query: "); 708 | /* planner() scribbles on it's input, so make a copy of the query-tree */ 709 | queryCopy = copyObject( query ); 710 | 711 | /* plan the query */ 712 | actual_plan = standard_planner( query, 0, params ); 713 | 714 | /* run it (if needed) and produce output */ 715 | ExplainOnePlan( actual_plan, into, stmt, queryString, params 716 | #if PG_VERSION_NUM >= 90400 717 | , &planduration 718 | #endif 719 | ); 720 | 721 | elog( DEBUG1 , "IND ADV: re-plan the query"); 722 | 723 | PG_TRY(); 724 | { 725 | 726 | /* re-plan the query */ 727 | appendStringInfo(stmt->str, "\n** Plan with hypothetical indexes **\n"); 728 | new_plan = index_adviser( queryCopy, 0, params, actual_plan,stmt, true ); 729 | elog( DEBUG3 , "IND ADV: after call to Index_adviser"); 730 | if ( new_plan ) 731 | { 732 | bool analyze = stmt->analyze; 733 | 734 | stmt->analyze = false; 735 | elog( DEBUG1 , "got new plan"); 736 | 737 | explain_get_index_name_hook = explain_get_index_name_callback; 738 | //DestReceiver *dest = CreateDestReceiver(DestDebug); 739 | //TupOutputState *tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt)); 740 | elog( INFO , "\n** Plan with Original indexes **\n"); 741 | //do_text_output_multiline(tstate, "\n** Plan with hypothetical indexes **\n"); /* separator line */ 742 | //end_tup_output(tstate); 743 | ExplainOnePlan( new_plan, into, stmt, queryString, params 744 | #if PG_VERSION_NUM >= 90400 745 | , &planduration 746 | #endif 747 | ); 748 | 749 | explain_get_index_name_hook = NULL; 750 | 751 | stmt->analyze = analyze; 752 | } 753 | } 754 | PG_CATCH(); 755 | { 756 | elog(WARNING, "Failed to create index advice for: %s",debug_query_string); 757 | /* reset our 'running' state... */ 758 | SuppressRecursion=0; 759 | 760 | } 761 | PG_END_TRY(); 762 | 763 | /* The candidates might not have been destroyed by the Index Adviser, do it 764 | * now. FIXME: this block belongs inside the 'if ( new_plan )' block above. */ 765 | foreach( cell, index_candidates ) 766 | pfree( (IndexCandidate*)lfirst( cell ) ); 767 | 768 | list_free( index_candidates ); 769 | 770 | index_candidates = NIL; 771 | 772 | foreach( cell, table_clauses ) 773 | pfree( (RelClause*)lfirst( cell ) ); 774 | 775 | list_free( table_clauses ); 776 | table_clauses = NIL; 777 | 778 | /* TODO: try to free the now-redundant new_plan */ 779 | } 780 | 781 | /* 782 | * get_relation_info() calls this callback after it has prepared a RelOptInfo 783 | * for a relation. 784 | * 785 | * The Job of this callback is to fill in the information about the virtual 786 | * index, that get_rel_info() could not load from the catalogs. As of now, the 787 | * number of disk-pages that might be occupied by the virtual index (if created 788 | * on-disk), is the only information that needs to be updated. 789 | * 790 | * selectivity computations are basaed on "clause_selectivity" in src/backend/optimizer/path/clausesel.c:484 791 | * 792 | * Given the Oid of the relation, return the following info into fields 793 | * of the RelOptInfo struct: 794 | * indexlist list of IndexOptInfos for relation's indexes 795 | * pages number of pages 796 | * tuples number of tuples 797 | * 798 | * BUG: this fails if we have actual data in the table - must fix. 799 | */ 800 | static void get_relation_info_callback( PlannerInfo *root, 801 | Oid relationObjectId, 802 | bool inhparent, 803 | RelOptInfo *rel) 804 | { 805 | //ListCell *cell1; 806 | List *indexoidlist; 807 | ListCell *l; 808 | LOCKMODE lmode; 809 | IndexCandidate *cand; 810 | Index varno = rel->relid; 811 | Relation relation; 812 | //bool hasindex; 813 | List *indexinfos = NIL; 814 | 815 | elog( DEBUG1, "IND ADV: get_relation_info_callback: ENTER." ); 816 | relation = heap_open( relationObjectId, NoLock); 817 | 818 | indexoidlist = RelationGetIndexList(relation); 819 | 820 | elog( DEBUG3, "IND ADV: get_relation_info_callback: index list length %d",list_length(indexoidlist)); 821 | lmode = AccessShareLock; 822 | foreach( l, indexoidlist) 823 | { 824 | Oid indexoid = lfirst_oid(l); 825 | Relation indexRelation; 826 | Form_pg_index index; 827 | IndexOptInfo *info; 828 | int ncolumns; 829 | int i; 830 | int simpleColumns = 0; 831 | 832 | if( !is_virtual_index( indexoid, NULL ) ) { elog( DEBUG1, "IND ADV: get_relation_info_callback: real index - skipping "); continue; }// skip actual indexes 833 | 834 | elog( DEBUG1, "IND ADV: get_relation_info_callback: index list loop"); 835 | indexRelation = index_open(indexoid, lmode); 836 | index = indexRelation->rd_index; 837 | 838 | info = makeNode(IndexOptInfo); 839 | 840 | info->indexoid = index->indexrelid; 841 | info->reltablespace = RelationGetForm(indexRelation)->reltablespace; 842 | info->rel = rel; 843 | info->ncolumns = ncolumns = index->indnatts; 844 | info->indexkeys = (int *) palloc(sizeof(int) * INDEX_MAX_KEYS); 845 | info->indexcollations = (Oid *) palloc(sizeof(Oid) * ncolumns); 846 | info->opfamily = (Oid *) palloc(sizeof(Oid) * ncolumns); 847 | info->opcintype = (Oid *) palloc(sizeof(Oid) * ncolumns); 848 | info->canreturn = (bool *) palloc(sizeof(bool) * ncolumns); 849 | elog( DEBUG3, "IND ADV: get_relation_info_callback: index oid: %d, ncols: %d",indexoid,ncolumns); 850 | 851 | for (i = 0; i < ncolumns; i++) 852 | { 853 | elog( DEBUG3, "IDX_ADV: column %d ",i); 854 | info->indexkeys[i] = index->indkey.values[i]; 855 | if(info->indexkeys[i] != 0) 856 | simpleColumns +=1; 857 | info->indexcollations[i] = indexRelation->rd_indcollation[i]; //InvalidOid; 858 | info->opfamily[i] = indexRelation->rd_opfamily[i]; 859 | info->opcintype[i] = indexRelation->rd_opcintype[i]; 860 | #if PG_VERSION_NUM >= 90500 861 | info->canreturn[i] = index_can_return(indexRelation, i + 1); 862 | #endif 863 | } 864 | elog( DEBUG3, "IDX_ADV: done with per column "); 865 | for (; i < INDEX_MAX_KEYS; i++) 866 | { 867 | info->indexkeys[i] = 0; 868 | } 869 | 870 | info->relam = indexRelation->rd_rel->relam; 871 | 872 | elog( DEBUG4 ,"IND ADV: amcostestimate=%d",info->amcostestimate); 873 | if(info->amcostestimate == InvalidOid) 874 | { 875 | elog( DEBUG4 ,"IND ADV: need to figure out the right valuse for amcostestimate"); 876 | info->amcostestimate=(RegProcedure)1268; //btcostestimate 877 | // TODO: consider using: (*indexRelation->rd_am)->amcostestimate 878 | switch (info->relam) 879 | { 880 | case BTREE_AM_OID: 881 | info->amcostestimate=(RegProcedure)1268; //btcostestimate 882 | break; 883 | case GIN_AM_OID: 884 | info->amcostestimate=(RegProcedure)772; //gistcostestimate 885 | //info->amcostestimate=(RegProcedure)2741; //gincostestimate 886 | //TODO: ginGetStats (called by gistcostestimate) fails as it reads the index directly - find workaround. 887 | break; 888 | case GIST_AM_OID: 889 | info->amcostestimate=(RegProcedure)772; //gistcostestimate 890 | break; 891 | #if PG_VERSION_NUM >= 90500 892 | case BRIN_AM_OID: 893 | info->amcostestimate=(RegProcedure)3800; //brincostestimate 894 | break; 895 | #endif 896 | } 897 | } 898 | #if PG_VERSION_NUM < 90500 899 | info->canreturn = index_can_return(indexRelation); 900 | #endif 901 | info->amcanorderbyop = indexRelation->rd_am->amcanorderbyop; 902 | info->amoptionalkey = indexRelation->rd_am->amoptionalkey; 903 | info->amsearcharray = indexRelation->rd_am->amsearcharray; 904 | info->amsearchnulls = indexRelation->rd_am->amsearchnulls; 905 | info->amhasgettuple = OidIsValid(indexRelation->rd_am->amgettuple); 906 | info->amhasgetbitmap = OidIsValid(indexRelation->rd_am->amgetbitmap); 907 | 908 | //info->amoptionalkey = false; 909 | //info->amsearchnulls = false; 910 | 911 | /* 912 | * v9.4 introduced a concept of tree height for btree, we'll use unkonown for now 913 | * loot at _bt_getrootheight on how to estimate this. 914 | */ 915 | #if PG_VERSION_NUM >= 90300 916 | info->tree_height = -1; 917 | #endif 918 | /* 919 | * Fetch the ordering information for the index, if any. 920 | */ 921 | // TODO: how to handle non BTREE ops (support other index types, see: get_relation_info: plancat.c:88) 922 | if (info->relam == BTREE_AM_OID) 923 | //if ( 1 == 1) 924 | { 925 | elog( DEBUG3 , "IND ADV: in BTREE_AM_OID"); 926 | /* 927 | * If it's a btree index, we can use its opfamily OIDs 928 | * directly as the sort ordering opfamily OIDs. 929 | */ 930 | Assert(indexRelation->rd_am->amcanorder); 931 | 932 | info->sortopfamily = info->opfamily; 933 | info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns); 934 | info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns); 935 | 936 | for (i = 0; i < ncolumns; i++) 937 | { 938 | int16 opt = indexRelation->rd_indoption[i]; 939 | 940 | info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0; 941 | info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; 942 | } 943 | } 944 | else 945 | { 946 | info->sortopfamily = NULL; 947 | info->reverse_sort = NULL; 948 | info->nulls_first = NULL; 949 | } 950 | 951 | elog( DEBUG3 , "IND ADV: almost there..."); 952 | /* 953 | * Fetch the index expressions and predicate, if any. We must 954 | * modify the copies we obtain from the relcache to have the 955 | * correct varno for the parent relation, so that they match up 956 | * correctly against qual clauses. 957 | */ 958 | elog( DEBUG3 , "IND ADV: getting realtion expressions"); 959 | info->indexprs = RelationGetIndexExpressions(indexRelation); 960 | info->ncolumns = simpleColumns + list_length(info->indexprs); // TODO: why is this?!?! really ugly hack?!? 961 | 962 | elog( DEBUG3 , "IND ADV: get index predicates"); 963 | info->indpred = RelationGetIndexPredicate(indexRelation); 964 | elog( DEBUG3 , "IND ADV: change var nodes - expr"); 965 | if (info->indexprs && varno != 1) 966 | ChangeVarNodes((Node *) info->indexprs, 1, varno, 0); 967 | elog( DEBUG3 , "IND ADV: change var nodes - pred"); 968 | if (info->indpred && varno != 1) 969 | ChangeVarNodes((Node *) info->indpred, 1, varno, 0); 970 | 971 | //elog_node_display( DEBUG3 , "IND ADV: get_relation_info_callback: ", (const OpExpr*)list_nth(context->predicate,0),true); 972 | elog( DEBUG3 , "IND ADV: Build targetlist using the completed indexprs data"); 973 | 974 | /* Build targetlist using the completed indexprs data - used in index only scans */ 975 | info->indextlist = build_index_tlist(root, info, relation); 976 | elog_node_display( DEBUG3, "IND ADV: (fill in tlist )", info->indextlist, true ); 977 | 978 | info->predOK = false; /* set later in indxpath.c */ 979 | info->unique = index->indisunique; 980 | info->immediate = index->indimmediate; 981 | info->hypothetical = true; // used to prevent access to the disc. see: src/backend/utils/adt/selfuncs.c -> get_actual_variable_range 982 | 983 | /* We call estimate_index_pages() here, instead of immediately after 984 | * index_create() API call, since rel has been run through 985 | * estimate_rel_size() by the caller! 986 | */ 987 | elog( DEBUG1, "IND ADV: get_relation_info_callback: hypothetical? %s",BOOL_FMT(info->hypothetical)); 988 | 989 | if( is_virtual_index( info->indexoid, &cand ) ) 990 | { 991 | Selectivity btreeSelectivity; 992 | Node *left, *right; 993 | Var *var; 994 | Const *cons; 995 | VariableStatData ldata,rdata; 996 | VariableStatData *vardata; 997 | 998 | elog( DEBUG3 , "IND ADV: get index predicates args"); 999 | elog_node_display( DEBUG3, "IND ADV: (info->indpred)", info->indpred, true ); 1000 | if (info->indpred){ // TODO: do i need the varno != 1 1001 | OpExpr *opclause = (OpExpr *) linitial(info->indpred); 1002 | Oid opno = opclause->opno; 1003 | RegProcedure oprrest = get_oprrest(opno); 1004 | elog( DEBUG3 , "IND ADV: get opno 2 %d",opno); 1005 | elog( DEBUG3 , "IND ADV: get oprrest 2 %d",oprrest); 1006 | 1007 | /* TODO: add support for boolean selectivity, create a " var = 't' " clause */ 1008 | if(not_clause(opclause)) 1009 | { 1010 | elog( DEBUG3 , "IND ADV: boolean not expression - todo: compute selectivity"); 1011 | var = (Var *) get_notclausearg((Expr *) opclause); 1012 | cons = (Const *) makeBoolConst(false,false); 1013 | opno = BooleanNotEqualOperator ; 1014 | } 1015 | else if(IsA(opclause, Var)) 1016 | { 1017 | elog( DEBUG3 , "IND ADV: var expression - todo: compute selectivity"); 1018 | var = (Var *) opclause; 1019 | cons = (Const *) makeBoolConst(true,false); 1020 | opno = BooleanEqualOperator; 1021 | } 1022 | else 1023 | { 1024 | 1025 | left = (Node *) linitial(opclause->args); 1026 | right = (Node *) lsecond(opclause->args); 1027 | /* 1028 | * Examine both sides. Note that when varRelid is nonzero, Vars of other 1029 | * relations will be treated as pseudoconstants. 1030 | */ 1031 | elog( DEBUG3 , "IND ADV: get oprrest3"); 1032 | examine_variable(root, left, cand->idxoid, &ldata); 1033 | examine_variable(root, right, cand->idxoid, &rdata); 1034 | 1035 | /* Set up result fields other than the stats tuple */ 1036 | if(IsA(right, Var)) 1037 | { 1038 | var = (Var *) right; 1039 | cons = (Const *) left; 1040 | }else 1041 | { 1042 | var = (Var *) left; 1043 | cons = (Const *) right; 1044 | } 1045 | } 1046 | elog( DEBUG3 , "IND ADV: get oprrest 4"); 1047 | 1048 | elog( DEBUG4, "IND ADV: get_relation_info_callback: pallocate mem for vardata, size: %ld",sizeof(VariableStatData)); 1049 | vardata = palloc(sizeof(VariableStatData)); 1050 | 1051 | vardata->var = (Node *)var; /* return Var without relabeling */ 1052 | vardata->rel = rel; 1053 | vardata->atttype = var->vartype; 1054 | vardata->atttypmod = var->vartypmod; 1055 | vardata->isunique = has_unique_index(vardata->rel, var->varattno); 1056 | /* Try to locate some stats */ 1057 | vardata->statsTuple = SearchSysCache3(STATRELATTINH, 1058 | ObjectIdGetDatum(relationObjectId), 1059 | Int16GetDatum(var->varattno), 1060 | BoolGetDatum(false)); 1061 | vardata->freefunc = ReleaseSysCache; 1062 | elog( DEBUG3, "IND ADV: get_relation_info_callback: %s stats found for %d",(vardata->statsTuple == NULL) ? "No":"",relationObjectId); 1063 | 1064 | 1065 | elog( DEBUG3, "IND ADV: get_relation_info_callback: estimate virtual index pages for: %d",cand->idxoid); 1066 | elog( DEBUG3, "IND ADV: get_relation_info_callback: opno: %d",opno); 1067 | elog( DEBUG3, "IND ADV: get_relation_info_callback: cluse type : %d",nodeTag((Node *) linitial(info->indpred))); 1068 | elog( DEBUG3, "IND ADV: get_relation_info_callback: oprrest : %d",oprrest); 1069 | //elog( DEBUG3, "IND ADV: get_relation_info_callback: arg list length : %d",list_length(opclause->args)); 1070 | 1071 | /* Estimate selectivity for a restriction clause. */ 1072 | btreeSelectivity = var_eq_cons(vardata, opno,cons->constvalue, 1073 | cons->constisnull,true); 1074 | 1075 | }else 1076 | { 1077 | elog( DEBUG3, "IND ADV: get_relation_info_callback: no index predicates"); 1078 | btreeSelectivity = 1; 1079 | } 1080 | 1081 | elog( DEBUG3, "IND ADV: get_relation_info_callback: selectivity = %.5f", btreeSelectivity); 1082 | 1083 | /* estimate the size */ 1084 | cand->pages = (BlockNumber)lrint(btreeSelectivity * estimate_index_pages(cand->reloid, cand->idxoid)); 1085 | if(cand->pages == 0) // we must allocate at least 1 page 1086 | cand->pages=1; 1087 | info->pages = cand->pages; 1088 | elog( DEBUG3, "IDX_ADV: get_relation_info_callback: pages: %d",info->pages); 1089 | info->tuples = (int) ceil(btreeSelectivity * rel->tuples); 1090 | cand->tuples = (int) ceil(btreeSelectivity * rel->tuples); 1091 | //elog( DEBUG3, "IND ADV: get_relation_info_callback: tuples: %d",info->tuples); 1092 | } 1093 | index_close(indexRelation, NoLock); 1094 | elog( DEBUG3 , "add the index to the indexinfos list"); 1095 | indexinfos = lcons(info, indexinfos); 1096 | } 1097 | heap_close(relation, NoLock); 1098 | rel->indexlist = indexinfos; 1099 | elog( DEBUG1, "IDX ADV: get_relation_info_callback: cand list length %d",list_length(rel->indexlist)); 1100 | 1101 | elog( DEBUG1, "IDX ADV: get_relation_info_callback: EXIT"); 1102 | } 1103 | 1104 | /* Use this function to reset the hooks that are required to be registered only 1105 | * for a short while; these may have been left registered by the previous call, in 1106 | * case of an ERROR. 1107 | */ 1108 | static void resetSecondaryHooks() 1109 | { 1110 | get_relation_info_hook = NULL; 1111 | explain_get_index_name_hook = NULL; 1112 | } 1113 | 1114 | static bool is_virtual_index( Oid oid, IndexCandidate **cand_out ) 1115 | { 1116 | ListCell *cell1; 1117 | 1118 | foreach( cell1, index_candidates ) 1119 | { 1120 | IndexCandidate *cand = (IndexCandidate*)lfirst( cell1 ); 1121 | //elog( DEBUG4 ,"is_virtual_index compare: oid:%d, cand:%d",oid,cand->idxoid); 1122 | if( cand->idxoid == oid ) 1123 | { 1124 | if( cand_out ) 1125 | *cand_out = cand; 1126 | return true; 1127 | } 1128 | } 1129 | 1130 | return false; 1131 | } 1132 | 1133 | static const char * explain_get_index_name_callback(Oid indexId) 1134 | { 1135 | StringInfoData buf; 1136 | IndexCandidate *cand; 1137 | 1138 | elog( DEBUG1 ,"explain_get_index_name_callback: ENTER - looking at oid: %d",indexId); 1139 | 1140 | if( is_virtual_index( indexId, &cand ) ) 1141 | { 1142 | elog( DEBUG1 ,"explain_get_index_name_callback: our virt index"); 1143 | initStringInfo(&buf); 1144 | 1145 | appendStringInfo( &buf, ":%d", cand->idxoid ); 1146 | 1147 | return buf.data; 1148 | } 1149 | 1150 | elog( DEBUG1 ,"explain_get_index_name_callback: EXIT - not ours"); 1151 | return NULL; /* allow default behavior */ 1152 | } 1153 | 1154 | 1155 | /*! 1156 | * store_idx_advice 1157 | * \brief for every candidate insert an entry into IDX_ADV_TABL. 1158 | * @param List* of candidates 1159 | */ 1160 | static void store_idx_advice( List* candidates , ExplainState * es ) 1161 | { 1162 | StringInfoData query; /*!< string for Query */ 1163 | StringInfoData cols; /*!< string for Columns */ 1164 | StringInfoData pcols; /*!< string for Partial clause Columns */ 1165 | StringInfoData pvals; /*!< string for Partial clause Values */ 1166 | StringInfoData op_class; /*!< string for op class family */ 1167 | StringInfoData collationObjectId; /*!< string for collation object */ 1168 | StringInfoData attList; /*!< string for functional attributes */ 1169 | StringInfoData partialClause; /*!< string for partial clause */ 1170 | StringInfoData indexDef; /*!< string for index definition */ 1171 | Oid advise_oid; 1172 | List *context; 1173 | ListCell *cell; 1174 | ListCell *indexpr_item; 1175 | List *rel_clauses = NIL; 1176 | 1177 | 1178 | elog( DEBUG2, "IDX_ADV: store_idx_advice: ENTER" ); 1179 | 1180 | Assert( list_length(candidates) != 0 ); 1181 | 1182 | /* 1183 | * Minimal check: check that IDX_ADV_TABL is at least visible to us. There 1184 | * are a lot more checks we should do in order to not let the INSERT fail, 1185 | * like permissions, datatype mis-match, etc., but we leave those checks 1186 | * upto the executor. 1187 | */ 1188 | 1189 | /* find a relation named IDX_ADV_TABL on the search path */ 1190 | advise_oid = RelnameGetRelid( IDX_ADV_TABL ); 1191 | 1192 | if (advise_oid != InvalidOid) 1193 | { 1194 | Relation advise_rel = relation_open(advise_oid, AccessShareLock); 1195 | 1196 | if (advise_rel->rd_rel->relkind != RELKIND_RELATION 1197 | && advise_rel->rd_rel->relkind != RELKIND_VIEW) 1198 | { 1199 | relation_close(advise_rel, AccessShareLock); 1200 | 1201 | /* FIXME: add errdetail() and/or errcontext() calls here. */ 1202 | ereport(ERROR, 1203 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), 1204 | errmsg( IDX_ADV_ERROR_NTV ))); 1205 | } 1206 | 1207 | relation_close(advise_rel, AccessShareLock); 1208 | } 1209 | else 1210 | { 1211 | /* FIXME: add errdetail() and/or errcontext() calls here. */ 1212 | ereport(ERROR, 1213 | (errcode(ERRCODE_UNDEFINED_TABLE), 1214 | errmsg( IDX_ADV_ERROR_NE ))); 1215 | } 1216 | 1217 | initStringInfo( &query ); 1218 | initStringInfo( &cols ); 1219 | initStringInfo( &pvals ); 1220 | initStringInfo( &pcols ); 1221 | initStringInfo( &op_class ); 1222 | initStringInfo( &collationObjectId ); 1223 | initStringInfo( &attList ); 1224 | initStringInfo( &partialClause ); 1225 | initStringInfo( &indexDef ); 1226 | 1227 | foreach( cell, candidates ) 1228 | { 1229 | int i; 1230 | //int val; 1231 | IndexCandidate* idxcd = (IndexCandidate*)lfirst( cell ); 1232 | 1233 | if( !idxcd->idxused ) 1234 | continue; 1235 | 1236 | /* pfree() the memory allocated for the previous candidate. FIXME: Avoid 1237 | * meddling with the internals of a StringInfo, and try to use an API. 1238 | */ 1239 | /*if( cols.len > 0 ) 1240 | { 1241 | pfree( cols.data ); 1242 | cols.data = NULL; 1243 | }*/ 1244 | 1245 | resetStringInfo( &query ); 1246 | resetStringInfo( &cols ); 1247 | resetStringInfo( &pvals ); 1248 | resetStringInfo( &pcols ); 1249 | resetStringInfo( &op_class ); 1250 | resetStringInfo( &collationObjectId ); 1251 | resetStringInfo( &attList ); 1252 | resetStringInfo( &partialClause ); 1253 | resetStringInfo( &indexDef ); 1254 | 1255 | //initStringInfo( &cols ); 1256 | indexpr_item = list_head(idxcd->attList); 1257 | context = deparse_context_for(idxcd->erefAlias, idxcd->reloid); 1258 | 1259 | //elog( DEBUG2 , "IDX_ADV: store_idx_advice: num cols: %d ",idxcd->ncols); 1260 | for (i = 0; i < idxcd->ncols; ++i){ 1261 | Oid keycoltype; 1262 | 1263 | appendStringInfo( &cols, "%s%d", (i>0?",":""), idxcd->varattno[i]); 1264 | appendStringInfo( &op_class, "%s%d", (i>0?",":""), idxcd->op_class[i]); 1265 | appendStringInfo( &collationObjectId, "%s%d", (i>0?",":""), idxcd->collationObjectId[i]); 1266 | 1267 | if (idxcd->varattno[i] == 0) 1268 | { 1269 | /* expressional index */ 1270 | Node *indexkey; 1271 | 1272 | indexkey = (Node *) lfirst(indexpr_item); 1273 | indexpr_item = lnext(indexpr_item); 1274 | keycoltype = exprType(indexkey); // get the attribut column type 1275 | //elog( DEBUG2 , "IND ADV: store_idx_advice: column collation: %d",exprCollation(indexkey)); // get the attribut collation 1276 | appendStringInfo(&attList,"%s%s", (i>0?",":""),deparse_expression(indexkey, context, false, false)); 1277 | get_opclass_name(idxcd->op_class[i], keycoltype, &attList); 1278 | //elog( DEBUG2 , "IND ADV: store_idx_advice: column: %s",deparse_expression(indexkey, context, false, false)); 1279 | //elog( DEBUG2 , "IND ADV: store_idx_advice: column opclass: %s",attList.data); 1280 | } 1281 | else 1282 | { 1283 | //elog( DEBUG2 , "IND ADV: store_idx_advice: column name: %s",get_attname(idxcd->reloid,idxcd->varattno[i])); 1284 | appendStringInfo(&attList,"%s%s", (i>0?",":""),get_attname(idxcd->reloid,idxcd->varattno[i])); 1285 | } 1286 | } 1287 | //elog( DEBUG2 , "IND ADV: store_idx_advice: const exsits %s",pg_get_indexdef_columns(idxcd->idxoid,2)); 1288 | elog( DEBUG2 , "IDX_ADV: store_idx_advice: idx am %d",idxcd->amOid); 1289 | 1290 | //appendStringInfo(&attList,TextDatumGetCString(DirectFunctionCall2(pg_get_expr,CStringGetTextDatum(nodeToString(make_ands_explicit(idxcd->attList))), ObjectIdGetDatum(idxcd->reloid)))); 1291 | //elog( DEBUG2 , "IND ADV: store_idx_advice - idxcd->attList: %s",TextDatumGetCString(DirectFunctionCall2(pg_get_expr,CStringGetTextDatum(nodeToString(make_ands_explicit(idxcd->attList))), ObjectIdGetDatum(idxcd->reloid)))); 1292 | // TODO: go over this in a loop 1293 | if(table_clauses != NIL){ 1294 | //int j=0; 1295 | //ListCell *clausCell; 1296 | rel_clauses = get_rel_clauses(table_clauses, idxcd->reloid,idxcd->erefAlias); 1297 | //elog( INFO , "IND ADV: store_idx_advice: no where clause oid: %d, alias: %s, name: %s",idxcd->reloid,idxcd->erefAlias,get_rel_name(idxcd->reloid)); 1298 | 1299 | //elog( DEBUG2 , "IND ADV: store_idx_advice - rel_clauses: %s",TextDatumGetCString(DirectFunctionCall2(pg_get_expr,CStringGetTextDatum(nodeToString(make_ands_explicit(rel_clauses))), ObjectIdGetDatum(idxcd->reloid)))); 1300 | if(rel_clauses != NIL){ 1301 | appendStringInfoString(&partialClause,deparse_expression((Node *)make_ands_explicit(rel_clauses), context, false, false)); 1302 | } 1303 | //elog( DEBUG2 , "IND ADV: store_idx_advice: rel_clauses: %s",deparse_expression(make_ands_explicit(rel_clauses), context, false, false)); 1304 | } else 1305 | { 1306 | elog( DEBUG3 , "IND ADV: store_idx_advice: no where clause"); 1307 | } 1308 | 1309 | //appendStringInfo( &indexDef,"create index on %s(%s)%s%s",get_rel_name(idxcd->reloid),attList.data,partialClause.len>0?" where":"",partialClause.len>0?partialClause.data:""); 1310 | appendStringInfo( &indexDef,"create index on %s",get_rel_name(idxcd->reloid)); 1311 | switch (idxcd->amOid) 1312 | { 1313 | case BTREE_AM_OID: 1314 | break; 1315 | case GIN_AM_OID: 1316 | appendStringInfo( &indexDef," USING GIN"); 1317 | break; 1318 | case GIST_AM_OID: 1319 | appendStringInfo( &indexDef," USING GIST"); 1320 | break; 1321 | #if PG_VERSION_NUM >= 90500 1322 | case BRIN_AM_OID: 1323 | appendStringInfo( &indexDef," USING BRIN"); 1324 | break; 1325 | #endif 1326 | } 1327 | appendStringInfo( &indexDef,"(%s)%s%s",attList.data,partialClause.len>0?" where":"",partialClause.len>0?partialClause.data:""); 1328 | 1329 | 1330 | /* FIXME: Mention the column names explicitly after the table name. */ 1331 | appendStringInfo( &query, "insert into %s.\""IDX_ADV_TABL"\" values ( %d, array[%s], %f, %d, %d, now(),array[%s],array[%s],array[%s],$$%s$$,$$%s$$,$$%s$$,$$%s$$);", 1332 | idxadv_schema, 1333 | idxcd->reloid, 1334 | cols.data, 1335 | idxcd->benefit, 1336 | idxcd->pages * BLCKSZ/1024, /* in KBs */ 1337 | MyProcPid, 1338 | collationObjectId.data, 1339 | op_class.data, 1340 | op_class.data, 1341 | nodeToString(idxcd->attList), 1342 | nodeToString(rel_clauses), 1343 | strstr(debug_query_string,"explain ")!=NULL?(debug_query_string+8):debug_query_string, /* the explain cmd without the "explain " at the begining... - if it's not found return the original string*/ 1344 | indexDef.data); 1345 | //elog( DEBUG4 , "IND ADV: store_idx_advice: build the index: create index on %s(%s)%s%s",idxcd->erefAlias,attList.data,partialClause.len>0?" where":"",partialClause.len>0?partialClause.data:""); 1346 | 1347 | if( query.len > 0 ) /* if we generated any SQL */ 1348 | { 1349 | //appendStringInfo(es->str, "read only, advice, index: %s\n",indexDef.data); 1350 | 1351 | elog(DEBUG1, "IDX ADV: read only, advice: %s, \n index: %s\n",query.data,indexDef.data); 1352 | if (es != NULL) { appendStringInfo(es->str, "read only, advice, index: %s\n",indexDef.data); } 1353 | 1354 | elog( DEBUG1, "SPI connection start - save advice"); 1355 | if( SPI_connect() == SPI_OK_CONNECT ) 1356 | { 1357 | elog( DEBUG1 , "IND ADV: store_idx_advice: build the insert query %s",query.data); 1358 | if( SPI_execute( query.data, false, 0 ) != SPI_OK_INSERT ) 1359 | elog( WARNING, "IND ADV: SPI_execute failed while saving advice." ); 1360 | 1361 | elog( DEBUG1, "SPI connection finish"); 1362 | if( SPI_finish() != SPI_OK_FINISH ) 1363 | elog( WARNING, "IND ADV: SPI_finish failed while saving advice." ); 1364 | } 1365 | else 1366 | elog( WARNING, "IND ADV: SPI_connect failed while saving advice." ); 1367 | } 1368 | } 1369 | 1370 | 1371 | /* TODO: Propose to -hackers to introduce API to free a StringInfoData . */ 1372 | if ( query.len > 0 ) 1373 | pfree( query.data ); 1374 | 1375 | if ( cols.len > 0 ) 1376 | pfree( cols.data ); 1377 | 1378 | if ( pcols.len > 0 ) 1379 | pfree( pcols.data ); 1380 | 1381 | if ( pvals.len > 0 ) 1382 | pfree( pvals.data ); 1383 | 1384 | if ( attList.len > 0 ) 1385 | pfree( attList.data ); 1386 | 1387 | if ( partialClause.len > 0 ) 1388 | pfree( partialClause.data ); 1389 | if ( indexDef.len > 0 ) 1390 | pfree( indexDef.data ); 1391 | 1392 | 1393 | elog( DEBUG3, "IND ADV: store_idx_advice: EXIT" ); 1394 | } 1395 | 1396 | /** 1397 | * remove_irrelevant_candidates 1398 | * 1399 | * A candidate is irrelevant if it has one of the followingg properties: 1400 | * 1401 | * (a) it indexes an unsupported relation (system-relations or temp-relations) 1402 | * (b) it matches an already present index. 1403 | * 1404 | * TODO Log the candidates as they are pruned, and remove the call to 1405 | * log_candidates() in index_adviser() after this function is called. 1406 | * 1407 | * REALLY BIG TODO/FIXME: simplify this function. 1408 | * 1409 | */ 1410 | static List* remove_irrelevant_candidates( List* candidates ) 1411 | { 1412 | ListCell *cell = list_head(candidates); 1413 | ListCell *prev = NULL; 1414 | 1415 | while(cell != NULL) 1416 | { 1417 | ListCell *old_cell = cell; 1418 | 1419 | Oid base_rel_oid = ((IndexCandidate*)lfirst( cell ))->reloid; 1420 | Relation base_rel = heap_open( base_rel_oid, AccessShareLock ); 1421 | 1422 | /* decide if the relation is unsupported. This check is now done before 1423 | * creating a candidate in scan_generic_node(); but still keeping the 1424 | * code here. 1425 | */ 1426 | // if((base_rel->rd_istemp == true) replaced 1427 | if((!RelationNeedsWAL(base_rel)) 1428 | || IsSystemRelation(base_rel)) 1429 | { 1430 | ListCell *cell2; 1431 | ListCell *prev2; 1432 | ListCell *next; 1433 | 1434 | /* remove all candidates that are on currently unsupported relations */ 1435 | elog( DEBUG1, 1436 | "Index candidate(s) on an unsupported relation (%d) found!", 1437 | base_rel_oid ); 1438 | 1439 | /* Remove all candidates with same unsupported relation */ 1440 | for(cell2 = cell, prev2 = prev; cell2 != NULL; cell2 = next) 1441 | { 1442 | next = lnext(cell2); 1443 | 1444 | if(((IndexCandidate*)lfirst(cell2))->reloid == base_rel_oid) 1445 | { 1446 | pfree((IndexCandidate*)lfirst(cell2)); 1447 | candidates = list_delete_cell( candidates, cell2, prev2 ); 1448 | 1449 | if(cell2 == cell) 1450 | cell = next; 1451 | } 1452 | else 1453 | { 1454 | prev2 = cell2; 1455 | } 1456 | } 1457 | } 1458 | else 1459 | { 1460 | /* Remove candidates that match any of already existing indexes. 1461 | * The prefix old_ in these variables means 'existing' index 1462 | */ 1463 | 1464 | /* get all index Oids */ 1465 | ListCell *index_cell; 1466 | List *old_index_oids = RelationGetIndexList( base_rel ); 1467 | 1468 | foreach( index_cell, old_index_oids ) 1469 | { 1470 | /* open index relation and get the index info */ 1471 | Oid old_index_oid = lfirst_oid( index_cell ); 1472 | Relation old_index_rel = index_open( old_index_oid, 1473 | AccessShareLock ); 1474 | IndexInfo *old_index_info = BuildIndexInfo( old_index_rel ); 1475 | 1476 | /* We ignore expressional indexes and partial indexes */ 1477 | if( old_index_rel->rd_index->indisvalid 1478 | && old_index_info->ii_Expressions == NIL 1479 | && old_index_info->ii_Predicate == NIL ) 1480 | { 1481 | ListCell *cell2; 1482 | ListCell *prev2; 1483 | ListCell *next; 1484 | 1485 | Assert( old_index_info->ii_Expressions == NIL ); 1486 | Assert( old_index_info->ii_Predicate == NIL ); 1487 | 1488 | /* search for a matching candidate */ 1489 | for(cell2 = cell, prev2 = prev; 1490 | cell2 != NULL; 1491 | cell2 = next) 1492 | {next = lnext(cell2);{ /* FIXME: move this line to the block below; it doesn't need to be here. */ 1493 | 1494 | IndexCandidate* cand = (IndexCandidate*)lfirst(cell2); 1495 | 1496 | signed int cmp = (signed int)cand->ncols 1497 | - old_index_info->ii_NumIndexAttrs; 1498 | 1499 | if(cmp == 0) 1500 | { 1501 | int i = 0; 1502 | do 1503 | { 1504 | cmp = 1505 | cand->varattno[i] 1506 | - old_index_info->ii_KeyAttrNumbers[i]; 1507 | ++i; 1508 | /* FIXME: should this while condition be: cmp==0&&(incols)); 1511 | } 1512 | 1513 | if(cmp != 0) 1514 | { 1515 | /* current candidate does not match the current 1516 | * index, so go to next candidate. 1517 | */ 1518 | prev2 = cell2; 1519 | } 1520 | else 1521 | { 1522 | elog( DEBUG1, 1523 | "A candidate matches the index oid of : %d;" 1524 | "hence ignoring it.", 1525 | old_index_oid ); 1526 | 1527 | /* remove the candidate from the list */ 1528 | candidates = list_delete_cell(candidates, 1529 | cell2, prev2); 1530 | pfree( cand ); 1531 | 1532 | /* If we just deleted the current node of the outer-most loop, fix that. */ 1533 | if (cell2 == cell) 1534 | cell = next; 1535 | 1536 | break; /* while */ 1537 | } 1538 | }} /* for */ 1539 | } 1540 | 1541 | /* close index relation and free index info */ 1542 | index_close( old_index_rel, AccessShareLock ); 1543 | pfree( old_index_info ); 1544 | } 1545 | 1546 | /* free the list of existing index Oids */ 1547 | list_free( old_index_oids ); 1548 | 1549 | /* clear the index-list, else the planner can not see the 1550 | * virtual-indexes 1551 | * TODO: Really?? Verify this. 1552 | */ 1553 | base_rel->rd_indexlist = NIL; 1554 | base_rel->rd_indexvalid = 0; 1555 | } 1556 | 1557 | /* close the relation */ 1558 | heap_close( base_rel, AccessShareLock ); 1559 | 1560 | /* 1561 | * Move the pointer forward, only if the crazy logic above did not do it 1562 | * else, cell is already pointing to a new list-element that needs 1563 | * processing 1564 | */ 1565 | if(cell == old_cell) 1566 | { 1567 | prev = cell; 1568 | cell = lnext(cell); 1569 | } 1570 | } 1571 | 1572 | return candidates; 1573 | } 1574 | 1575 | /** 1576 | * tag_and_remove_candidates 1577 | * tag every candidate we do use and remove those unneeded 1578 | * Note: should i really remove or wait for cleanup?. 1579 | */ 1580 | static void tag_and_remove_candidates(Cost startupCostSaved, Cost totalCostSaved,PlannedStmt *new_plan, const Node* const head,List* const candidates) 1581 | { 1582 | 1583 | if( startupCostSaved >0 || totalCostSaved > 0 ) 1584 | { 1585 | /* scan the plan for virtual indexes used */ 1586 | plannedStmtGlobal = new_plan; 1587 | 1588 | //elog_node_display( DEBUG4, "plan (using Index Adviser)",(Node*)new_plan->planTree, true ); 1589 | 1590 | mark_used_candidates( head, candidates ); 1591 | 1592 | plannedStmtGlobal = NULL; 1593 | } 1594 | 1595 | elog( DEBUG3, "IND ADV: Remove unused candidates from the list" ); 1596 | /* Remove unused candidates from the list. */ 1597 | /*for( prev = NULL, cell = list_head(candidates); 1598 | cell != NULL; 1599 | cell = next ) 1600 | { 1601 | IndexCandidate *cand = (IndexCandidate*)lfirst( cell ); 1602 | 1603 | next = lnext( cell ); 1604 | 1605 | if( !cand->idxused ) 1606 | { 1607 | pfree( cand ); 1608 | candidates = list_delete_cell( candidates, cell, prev ); 1609 | } 1610 | else 1611 | prev = cell; 1612 | }*/ 1613 | } 1614 | 1615 | /** 1616 | * mark_used_candidates 1617 | * scan the execution plan to find hypothetical indexes used by the planner 1618 | * Note: there is no tree_walker for execution plans so we need to drill down ourselves. 1619 | */ 1620 | static void mark_used_candidates(const Node* const node, List* const candidates) 1621 | { 1622 | const ListCell *cell; 1623 | bool planNode = true; /* assume it to be a plan node */ 1624 | 1625 | elog( DEBUG3, "IND ADV: mark_used_candidates: ENTER" ); 1626 | 1627 | //TODO: remove this 1628 | //foreach( cell, candidates ) 1629 | //{ 1630 | // /* is virtual-index-oid in the IndexScan-list? */ 1631 | // IndexCandidate* const idxcd = (IndexCandidate*)lfirst( cell ); 1632 | // idxcd->idxused = true; 1633 | //} 1634 | //return; 1635 | elog( DEBUG3, "IND ADV: mark_used_candidates: node tag: %d ", nodeTag( node ) ); 1636 | switch( nodeTag( node ) ) 1637 | { 1638 | /* if the node is an indexscan */ 1639 | case T_IndexScan: // TAG: 110 1640 | { 1641 | /* are there any used virtual-indexes? */ 1642 | const IndexScan* const idxScan = (const IndexScan*)node; 1643 | elog( DEBUG3, "IND ADV: mark_used_candidates: plan idx: %d ", idxScan->indexid ); 1644 | 1645 | foreach( cell, candidates ) 1646 | { 1647 | 1648 | /* is virtual-index-oid in the IndexScan-list? */ 1649 | IndexCandidate* const idxcd = (IndexCandidate*)lfirst( cell ); 1650 | elog( DEBUG3, "IND ADV: mark_used_candidates: cand idx: %d ", idxcd->idxoid ); 1651 | const bool used = (idxcd->idxoid == idxScan->indexid); 1652 | 1653 | /* connect the existing value per OR */ 1654 | idxcd->idxused = (idxcd->idxused || used); 1655 | 1656 | } 1657 | } 1658 | break; 1659 | case T_IndexOnlyScan: // TAG: 110 1660 | { 1661 | /* are there any used virtual-indexes? */ 1662 | const IndexOnlyScan* const idxScan = (const IndexOnlyScan*)node; 1663 | elog( DEBUG3, "IND ADV: mark_used_candidates: plan idx: %d ", idxScan->indexid ); 1664 | 1665 | foreach( cell, candidates ) 1666 | { 1667 | 1668 | /* is virtual-index-oid in the IndexScan-list? */ 1669 | IndexCandidate* const idxcd = (IndexCandidate*)lfirst( cell ); 1670 | elog( DEBUG3, "IND ADV: mark_used_candidates: cand idx: %d ", idxcd->idxoid ); 1671 | const bool used = (idxcd->idxoid == idxScan->indexid); 1672 | 1673 | /* connect the existing value per OR */ 1674 | idxcd->idxused = (idxcd->idxused || used); 1675 | 1676 | } 1677 | } 1678 | break; 1679 | 1680 | 1681 | /* if the node is a bitmap-index-scan */ 1682 | case T_BitmapIndexScan: 1683 | { 1684 | /* are there any used virtual-indexes? */ 1685 | const BitmapIndexScan* const bmiScan = (const BitmapIndexScan*)node; 1686 | elog( DEBUG3, "IND ADV: mark_used_candidates: plan idx: %d ", bmiScan->indexid ); 1687 | 1688 | foreach( cell, candidates ) 1689 | { 1690 | /* is virtual-index-oid in the BMIndexScan-list? */ 1691 | IndexCandidate* const idxcd = (IndexCandidate*)lfirst( cell ); 1692 | elog( DEBUG3, "IND ADV: mark_used_candidates: cand idx: %d ", idxcd->idxoid ); 1693 | const bool used = idxcd->idxoid == bmiScan->indexid; 1694 | 1695 | /* conntect the existing value per OR */ 1696 | idxcd->idxused = idxcd->idxused || used; 1697 | } 1698 | } 1699 | break; 1700 | 1701 | /* if the node is a bitmap-and */ 1702 | case T_BitmapAnd: 1703 | { 1704 | /* are there any used virtual-indexes? */ 1705 | const BitmapAnd* const bmiAndScan = (const BitmapAnd*)node; 1706 | 1707 | foreach( cell, bmiAndScan->bitmapplans ) 1708 | mark_used_candidates( (Node*)lfirst( cell ), candidates ); 1709 | } 1710 | break; 1711 | 1712 | /* if the node is a bitmap-or */ 1713 | case T_BitmapOr: 1714 | { 1715 | /* are there any used virtual-indexes? */ 1716 | const BitmapOr* const bmiOrScan = (const BitmapOr*)node; 1717 | 1718 | foreach( cell, bmiOrScan->bitmapplans ) 1719 | mark_used_candidates( (Node*)lfirst( cell ), candidates ); 1720 | } 1721 | break; 1722 | 1723 | case T_SubqueryScan: 1724 | { 1725 | /* scan subqueryplan */ 1726 | const SubqueryScan* const subScan = (const SubqueryScan*)node; 1727 | mark_used_candidates( (const Node*)subScan->subplan, candidates ); 1728 | } 1729 | break; 1730 | 1731 | case T_NestLoop: 1732 | case T_MergeJoin: 1733 | case T_HashJoin: 1734 | case T_Join: 1735 | { 1736 | /* scan join-quals */ 1737 | const Join* const join = (const Join*)node; 1738 | 1739 | foreach( cell, join->joinqual ) 1740 | { 1741 | const Node* const qualPlan = (const Node*)lfirst( cell ); 1742 | mark_used_candidates( qualPlan, candidates ); 1743 | } 1744 | } 1745 | break; 1746 | 1747 | case T_OpExpr: 1748 | { 1749 | const OpExpr* const expr = (const OpExpr*)node; 1750 | 1751 | planNode = false; 1752 | 1753 | foreach( cell, expr->args ) 1754 | mark_used_candidates( (const Node*)lfirst( cell ), candidates ); 1755 | } 1756 | break; 1757 | 1758 | case T_SubPlan: 1759 | { 1760 | /* scan the subplan */ 1761 | const SubPlan* const subPlan = (const SubPlan*)node; 1762 | 1763 | planNode = false; 1764 | 1765 | mark_used_candidates( (const Node*)&plannedStmtGlobal->subplans[subPlan->plan_id], candidates ); 1766 | } 1767 | break; 1768 | 1769 | 1770 | case T_BoolExpr: 1771 | { 1772 | const BoolExpr* const expr = (const BoolExpr*)node; 1773 | 1774 | planNode = false; 1775 | 1776 | foreach( cell, expr->args ) 1777 | { 1778 | const Node* const nodeBool = (const Node*)lfirst( cell ); 1779 | mark_used_candidates( nodeBool, candidates ); 1780 | } 1781 | } 1782 | break; 1783 | /* 1784 | case T_CoerceViaIO: 1785 | { 1786 | elog_node_display( DEBUG3 , "T_CoerceViaIO", node,true); 1787 | } 1788 | break; 1789 | */ 1790 | 1791 | case T_FunctionScan: 1792 | case T_CteScan: 1793 | case T_RecursiveUnion: 1794 | case T_Result: 1795 | case T_Append: 1796 | case T_MergeAppend: 1797 | case T_TidScan: 1798 | case T_Material: 1799 | case T_Sort: 1800 | case T_Group: 1801 | case T_Agg: 1802 | case T_WindowAgg: 1803 | case T_Unique: 1804 | case T_Hash: 1805 | case T_SetOp: 1806 | case T_Limit: 1807 | case T_Scan: 1808 | case T_SeqScan: 1809 | case T_BitmapHeapScan: 1810 | 1811 | break; 1812 | 1813 | case T_AlternativeSubPlan: 1814 | case T_FuncExpr: 1815 | case T_Const: 1816 | case T_MinMaxExpr: 1817 | case T_CoerceViaIO: 1818 | case T_ArrayCoerceExpr: 1819 | case T_NullTest: 1820 | case T_Var: 1821 | planNode = false; 1822 | break; 1823 | 1824 | /* report parse-node types that we missed */ 1825 | default: 1826 | { 1827 | elog( NOTICE, "IND ADV: unhandled plan-node type: %d; Query: %s\n", 1828 | (int)nodeTag( node ), debug_query_string ); 1829 | planNode = false; /* stop scanning the tree here */ 1830 | } 1831 | break; 1832 | } 1833 | 1834 | if( planNode ) 1835 | { 1836 | const Plan* const plan = (Plan *) node; 1837 | 1838 | if( plan->initPlan ) 1839 | { 1840 | ListCell *cell; 1841 | 1842 | foreach( cell, ((Plan*)node)->initPlan ) 1843 | { 1844 | SubPlan *subPlan = (SubPlan*)lfirst( cell ); 1845 | 1846 | mark_used_candidates( (Node*)exec_subplan_get_plan( 1847 | plannedStmtGlobal, 1848 | subPlan), 1849 | candidates ); 1850 | } 1851 | } 1852 | 1853 | if( IsA(((Node*)plan), Append) ) 1854 | { 1855 | Append *appendplan = (Append *)node; 1856 | ListCell *cell; 1857 | 1858 | foreach( cell, appendplan->appendplans ) 1859 | { 1860 | Plan *child = (Plan*)lfirst( cell ); 1861 | 1862 | mark_used_candidates( (Node*)child, candidates ); 1863 | } 1864 | } 1865 | if( IsA(((Node*)plan), MergeAppend) ) 1866 | { 1867 | MergeAppend *appendplan = (MergeAppend *)node; 1868 | ListCell *cell; 1869 | 1870 | foreach( cell, appendplan->mergeplans ) 1871 | { 1872 | Plan *child = (Plan*)lfirst( cell ); 1873 | 1874 | mark_used_candidates( (Node*)child, candidates ); 1875 | } 1876 | } 1877 | 1878 | 1879 | /* scan left- and right-tree */ 1880 | if( outerPlan(plan) ) 1881 | mark_used_candidates( (const Node*)outerPlan(plan), candidates ); 1882 | 1883 | if( innerPlan(plan) ) 1884 | mark_used_candidates( (const Node*)innerPlan(plan), candidates ); 1885 | 1886 | /* walk through the qual-list */ 1887 | foreach( cell, plan->qual ) 1888 | { 1889 | const Node* const nodeQual = (const Node*)lfirst( cell ); 1890 | mark_used_candidates( nodeQual, candidates ); 1891 | } 1892 | } 1893 | 1894 | elog( DEBUG3, "IND ADV: mark_used_candidates: EXIT" ); 1895 | } 1896 | 1897 | /** 1898 | * scan_query 1899 | * Runs thru the whole query to find columns to create index candidates. 1900 | * Note: We do not use the query_tree_walker here because it doesn't go into 1901 | * GROUP BY, ORDER BY, and we also add aditional handeling of inheritence table expension. 1902 | */ 1903 | static List* scan_query( const Query* const query, 1904 | OpnosContext* context, 1905 | List* rangeTableStack ) 1906 | { 1907 | const ListCell* cell; 1908 | List* candidates = NIL; 1909 | List* newCandidates = NIL; 1910 | 1911 | elog( DEBUG4, "IND ADV: scan_query: ENTER" ); 1912 | 1913 | /* add the current rangetable to the stack */ 1914 | rangeTableStack = lcons( query->rtable, rangeTableStack ); 1915 | // TODO: can i use this to allow inheritance? 1916 | //elog_node_display( DEBUG4 , "query - print whole", query,true); 1917 | 1918 | /* scan CTE-queries */ 1919 | foreach( cell, query->cteList ) 1920 | { 1921 | const CommonTableExpr* const rte = (const CommonTableExpr*)lfirst( cell ); 1922 | elog( DEBUG3 , "IND ADV: scan_query: CTE working on: %s",rte->ctename); 1923 | 1924 | if( rte->ctequery ) 1925 | { 1926 | elog_node_display( DEBUG4 , "CTE query", rte->ctequery,true); 1927 | candidates = merge_candidates( candidates, scan_query( 1928 | rte->ctequery, 1929 | context, 1930 | rangeTableStack)); 1931 | } 1932 | } 1933 | 1934 | /* scan sub-queries */ 1935 | foreach( cell, query->rtable ) 1936 | { 1937 | const RangeTblEntry* const rte = (const RangeTblEntry*)lfirst( cell ); 1938 | elog( DEBUG3 , "IND ADV: scan_query: SUB working on: %s",rte->eref->aliasname); 1939 | //elog( DEBUG3 , "IND ADV: scan_query: working on: %d",rte->rtekind); 1940 | 1941 | if( rte->subquery ) 1942 | { 1943 | elog_node_display( DEBUG4 , "sub query", rte->subquery,true); 1944 | candidates = merge_candidates( candidates, scan_query( 1945 | rte->subquery, 1946 | context, 1947 | rangeTableStack)); 1948 | } 1949 | 1950 | /* adding support for join clause */ 1951 | if (rte->joinaliasvars) 1952 | { 1953 | //elog( DEBUG3 , "IND ADV: scan_query: join"); 1954 | //elog( DEBUG3 , "IND ADV: scan_query: join type : %d",rte->jointype); 1955 | //elog_node_display( DEBUG4 , "sub query", rte->joinaliasvars,true); 1956 | candidates = merge_candidates( candidates, scan_generic_node( rte->joinaliasvars, 1957 | context, 1958 | rangeTableStack)); 1959 | } 1960 | } 1961 | 1962 | /* scan "where" from the current query */ 1963 | if( query->jointree->quals != NULL ) 1964 | { 1965 | newCandidates = scan_generic_node( query->jointree->quals, context, 1966 | rangeTableStack ); 1967 | } 1968 | 1969 | /* FIXME: Why don't we consider the GROUP BY and ORDER BY clause 1970 | * irrespective of whether we found candidates in WHERE clause? 1971 | */ 1972 | elog( DEBUG3, "IND ADV: scan_query: at FIXME"); 1973 | 1974 | /* if no indexcadidate found in "where", scan "group" */ 1975 | if( ( newCandidates == NIL ) && ( query->groupClause != NULL ) ) 1976 | { 1977 | newCandidates = scan_group_clause( query->groupClause, 1978 | query->targetList, 1979 | context, 1980 | rangeTableStack ); 1981 | } 1982 | 1983 | /* if no indexcadidate found in "group", scan "order by" */ 1984 | if( ( newCandidates == NIL ) && ( query->sortClause != NULL ) ) 1985 | { 1986 | newCandidates = scan_group_clause( query->sortClause, 1987 | query->targetList, 1988 | context, 1989 | rangeTableStack ); 1990 | } 1991 | /* if no indexcadidate found until now, scan the target list "select" */ 1992 | if( ( newCandidates == NIL ) && ( query->targetList != NULL ) ) 1993 | { 1994 | newCandidates = scan_targetList( query->targetList, 1995 | context, 1996 | rangeTableStack ); 1997 | } 1998 | 1999 | /* remove the current rangetable from the stack */ 2000 | rangeTableStack = list_delete_ptr( rangeTableStack, query->rtable ); 2001 | 2002 | /* merge indexcandiates */ 2003 | candidates = merge_candidates( candidates, newCandidates ); 2004 | 2005 | /* expend inherited tables */ 2006 | candidates = expand_inherited_candidates( candidates); 2007 | expand_inherited_rel_clauses(); 2008 | 2009 | elog( DEBUG3, "IND ADV: scan_query: EXIT" ); 2010 | 2011 | return candidates; 2012 | } 2013 | 2014 | /** 2015 | * scan_group_clause 2016 | * Runs thru the GROUP BY clause looking for columns to create index candidates. 2017 | */ 2018 | static List* scan_group_clause( List* const groupList, 2019 | List* const targetList, 2020 | OpnosContext* context, 2021 | List* const rangeTableStack ) 2022 | { 2023 | const ListCell* cell; 2024 | List* candidates = NIL; 2025 | 2026 | elog( DEBUG3, "IND ADV: scan_group_clause: ENTER" ); 2027 | 2028 | /* scan every entry in the group-list */ 2029 | foreach( cell , groupList ) 2030 | { 2031 | /* convert to group-element */ 2032 | const SortGroupClause* const groupElm = (const SortGroupClause*)lfirst( cell ); 2033 | 2034 | /* get the column the group-clause is for */ 2035 | const TargetEntry* const targetElm = list_nth( targetList, 2036 | groupElm->tleSortGroupRef - 1); 2037 | 2038 | /* scan the node and get candidates */ 2039 | const Node* const node = (const Node*)targetElm->expr; 2040 | 2041 | candidates = merge_candidates( candidates, scan_generic_node( node, 2042 | context, 2043 | rangeTableStack)); 2044 | } 2045 | 2046 | elog( DEBUG3, "IND ADV: scan_group_clause: EXIT" ); 2047 | 2048 | return candidates; 2049 | } 2050 | 2051 | 2052 | /** 2053 | * scan_targetList 2054 | * Runs thru the GROUP BY clause looking for columns to create index candidates. 2055 | */ 2056 | static List* scan_targetList( List* const targetList, 2057 | OpnosContext* context, 2058 | List* const rangeTableStack ) 2059 | { 2060 | const ListCell* cell; 2061 | List* candidates = NIL; 2062 | 2063 | elog( DEBUG3, "IND ADV: scan_targetList: ENTER" ); 2064 | 2065 | /* scan every entry in the target-list */ 2066 | foreach( cell , targetList ) 2067 | { 2068 | /* convert to TargetEntry - the column with the expression */ 2069 | const TargetEntry* const targetElm = (const TargetEntry*)lfirst( cell ); 2070 | 2071 | /* scan the node and get candidates */ 2072 | const Node* const node = (const Node*)targetElm->expr; 2073 | 2074 | candidates = merge_candidates( candidates, scan_generic_node( node, 2075 | context, 2076 | rangeTableStack)); 2077 | } 2078 | 2079 | elog( DEBUG3, "IND ADV: scan_targetList: EXIT" ); 2080 | 2081 | return candidates; 2082 | } 2083 | 2084 | 2085 | static bool index_candidates_walker (Node *root, ScanContext *context) 2086 | { 2087 | ListCell* cell; 2088 | List* candidates = NIL; 2089 | 2090 | elog( DEBUG4, "IND ADV: scan_generic_node: ENTER" ); 2091 | 2092 | if (root == NULL) 2093 | return false; 2094 | // check for nodes that special work is required for, eg: 2095 | 2096 | 2097 | elog( DEBUG4, "IND ADV: scan_generic_node, tag: %d", nodeTag( root )); 2098 | switch( nodeTag( root ) ) 2099 | { 2100 | 2101 | /* if the node is a boolean-expression */ 2102 | case T_BoolExpr: 2103 | { 2104 | const BoolExpr* const expr = (const BoolExpr*)root; 2105 | 2106 | if( expr->boolop != AND_EXPR ) 2107 | { 2108 | /* non-AND expression */ 2109 | Assert( expr->boolop == OR_EXPR || expr->boolop == NOT_EXPR ); 2110 | 2111 | foreach( cell, expr->args ) 2112 | { 2113 | const Node* const node = (const Node*)lfirst( cell ); 2114 | context->candidates = merge_candidates( context->candidates, 2115 | scan_generic_node( node, context->context, 2116 | context->rangeTableStack)); 2117 | } 2118 | } 2119 | else 2120 | { 2121 | /* AND expression */ 2122 | List* compositeCandidates = NIL; 2123 | 2124 | foreach( cell, expr->args ) 2125 | { 2126 | const Node* const node = (const Node*)lfirst( cell ); 2127 | List *icList; /* Index candidate list */ 2128 | List *cicList; /* Composite index candidate list */ 2129 | 2130 | icList = scan_generic_node( node, context->context, context->rangeTableStack ); 2131 | cicList = build_composite_candidates(candidates, icList); 2132 | context->candidates = merge_candidates(context->candidates, icList); 2133 | compositeCandidates = merge_candidates(compositeCandidates, 2134 | cicList); 2135 | } 2136 | 2137 | /* now append the composite (multi-col) indexes to the list */ 2138 | context->candidates = merge_candidates(context->candidates, compositeCandidates); 2139 | } 2140 | return false; 2141 | } 2142 | break; 2143 | 2144 | /* if the node is an operator */ 2145 | case T_OpExpr: 2146 | { 2147 | /* get candidates if operator is supported */ 2148 | const OpExpr* const expr = (const OpExpr*)root; 2149 | elog( DEBUG3 , "IND ADV: OpExpr: opno:%d, location:%d",expr->opno,expr->location); 2150 | 2151 | if( list_member_oid( context->context->opnos, expr->opno ) || list_member_oid( context->context->ginopnos, expr->opno) ) 2152 | { 2153 | bool foundToken = false; 2154 | /* this part extracts the expr to be used as the predicate for the partial index */ 2155 | elog( DEBUG3 , "IND ADV: OpExpr: check context"); 2156 | 2157 | foreach( cell, expr->args ) 2158 | { 2159 | const Node* const node = (const Node*)lfirst( cell ); 2160 | 2161 | elog( DEBUG4 , "IND ADV: OpExpr: check var %d",nodeTag(node)); 2162 | if(nodeTag(node)==T_Var){ 2163 | const Var* const e = (const Var*)(node); 2164 | List* rt = list_nth( context->rangeTableStack, e->varlevelsup ); 2165 | const RangeTblEntry* rte = list_nth( rt, e->varno - 1 ); 2166 | if (rte->rtekind == RTE_CTE) break; // break if working on CTE. 2167 | RelClause* rc = NULL; 2168 | char *token = NULL; 2169 | 2170 | elog( DEBUG3 , "IND ADV: OpExpr: working on: %s",rte->eref->aliasname); 2171 | char *varname = get_relid_attribute_name(rte->relid, e->varattno); 2172 | elog( DEBUG3 , "IND ADV: OpExpr: working on: %d",rte->relid); 2173 | char *token_str = strdup(idxadv_columns); 2174 | 2175 | elog( DEBUG1 , "IND ADV: OpExpr: check right var, %s, cols: %s",varname,idxadv_columns); 2176 | token = strtok(token_str, ","); 2177 | elog( DEBUG1 , "IND ADV: token %s",token); 2178 | while (token && ! foundToken){ 2179 | foundToken = (strcmp(token,varname)==0) ? true : false ; 2180 | token = strtok(NULL, ","); 2181 | elog( DEBUG4 , "IND ADV: token %s",token); 2182 | } 2183 | 2184 | if (foundToken) 2185 | { 2186 | ListCell* relPredicates = get_rel_clausesCell(table_clauses, rte->relid,rte->eref->aliasname); 2187 | elog( INFO , "IND ADV: create the clause for: %s",rte->eref->aliasname); 2188 | if (relPredicates == NULL){ 2189 | Node* f = linitial(expr->args); 2190 | Node* s = lsecond(expr->args); 2191 | elog( DEBUG4 , "index candidate - create a new entry for the relation"); 2192 | // create a new entry for the relation 2193 | rc = (RelClause*)palloc0(sizeof(RelClause)); 2194 | rc->reloid = rte->relid; 2195 | rc->erefAlias = pstrdup(rte->eref->aliasname); 2196 | if(nodeTag(f)==T_Var){ 2197 | rc->predicate = lappend(rc->predicate,(Expr *)makePredicateClause((Expr *) root, (Const*) s, (Var*) f)); 2198 | }else{ 2199 | rc->predicate = lappend(rc->predicate,(Expr *)makePredicateClause((Expr *) root, (Const*) f, (Var*) s)); 2200 | } 2201 | elog( DEBUG4 , "IND ADV: created the clause"); 2202 | 2203 | table_clauses = lappend(table_clauses, rc ); 2204 | }else{ 2205 | //use the existing rc 2206 | Node* f = linitial(expr->args); 2207 | Node* s = lsecond(expr->args); 2208 | elog( DEBUG4 , "index candidate - use the existing rc"); 2209 | rc = (RelClause*)lfirst( relPredicates ); 2210 | if(nodeTag(f)==T_Var){ 2211 | rc->predicate = lappend(rc->predicate,(Expr *)makePredicateClause((Expr *) root, (Const*) s, (Var*) f)); 2212 | }else{ 2213 | rc->predicate = lappend(rc->predicate,(Expr *)makePredicateClause((Expr *) root, (Const*) f, (Var*) s)); 2214 | } 2215 | elog( DEBUG4 , "IND ADV: created the clause"); 2216 | } 2217 | 2218 | //context = (QueryContext*)palloc0(sizeof(QueryContext)); 2219 | elog_node_display( DEBUG4 , "predicate", linitial(rc->predicate),true); 2220 | //context->predicate = make_ands_implicit((Expr *) root); 2221 | 2222 | elog( DEBUG4 , "index candidate - context->predicate set"); 2223 | break; 2224 | } 2225 | } 2226 | } 2227 | 2228 | if(! foundToken){ 2229 | foreach( cell, expr->args ) 2230 | { 2231 | const Node* const node = (const Node*)lfirst( cell ); 2232 | 2233 | context->candidates = merge_candidates( context->candidates, 2234 | scan_generic_node( node, context->context, 2235 | context->rangeTableStack)); 2236 | 2237 | } 2238 | } 2239 | } 2240 | return false; 2241 | } 2242 | break; 2243 | 2244 | /* if this case is reached, the variable is an index-candidate */ 2245 | case T_Var: 2246 | { 2247 | const Var* const expr = (const Var*)root; 2248 | List* rt = list_nth( context->rangeTableStack, expr->varlevelsup ); 2249 | const RangeTblEntry* rte = list_nth( rt, expr->varno - 1 ); 2250 | 2251 | elog( DEBUG3 , "index candidate - var: %d rtekind: %d",expr->varattno,rte->rtekind); 2252 | 2253 | /* only relations have indexes */ 2254 | if( rte->rtekind == RTE_RELATION ) 2255 | { 2256 | Relation base_rel = heap_open( rte->relid, AccessShareLock ); 2257 | elog( DEBUG3 , "index candidate - here %d %d %d %d %d",RelationNeedsWAL(base_rel),!IsSystemRelation(base_rel),expr->varattno,base_rel->rd_rel->relpages,base_rel->rd_rel->reltuples); 2258 | /* We do not support catalog tables and temporary tables */ 2259 | if( RelationNeedsWAL(base_rel) 2260 | && !IsSystemRelation(base_rel) 2261 | /* and don't recommend indexes on hidden/system columns */ 2262 | && expr->varattno > 0 2263 | /* and it should have at least two tuples */ 2264 | //TODO: Do we really need these checks? 2265 | //&& base_rel->rd_rel->relpages > 1 2266 | //&& base_rel->rd_rel->reltuples > 1 2267 | ) 2268 | { 2269 | /* create index-candidate and build a new list */ 2270 | int i; 2271 | TYPCATEGORY tcategory ; 2272 | IndexCandidate *cand = (IndexCandidate*)palloc0(sizeof(IndexCandidate)); 2273 | 2274 | elog( DEBUG3 , "index candidate - in here"); 2275 | 2276 | cand->varno = expr->varno; 2277 | cand->varlevelsup = expr->varlevelsup; 2278 | cand->ncols = 1; 2279 | cand->reloid = rte->relid; 2280 | cand->erefAlias = pstrdup(rte->eref->aliasname); 2281 | cand->inh = rte->inh; 2282 | elog( DEBUG3 , "index candidate - rel: %s, inh: %s",cand->erefAlias,BOOL_FMT(rte->inh)); 2283 | cand->vartype[ 0 ] = expr->vartype; 2284 | cand->varattno[ 0 ] = expr->varattno; 2285 | cand->varname[ 0 ] = get_relid_attribute_name(rte->relid, expr->varattno); 2286 | elog( DEBUG3 , "index candidate - rel: %s, var: %s",cand->erefAlias,cand->varname[ 0 ]); 2287 | /*FIXME: Do we really need this loop? palloc0 and ncols, 2288 | * above, should have taken care of this! 2289 | */ 2290 | tcategory = TypeCategory(expr->vartype); 2291 | cand->amOid = ((tcategory == TYPCATEGORY_ARRAY)||(tcategory == TYPCATEGORY_USER)) ? GIN_AM_OID : BTREE_AM_OID ; 2292 | elog( DEBUG3 , "index candidate - am: %d, category: %c",cand->amOid,tcategory); 2293 | for( i = 1; i < INDEX_MAX_KEYS; ++i ) 2294 | cand->varattno[i] = 0; 2295 | 2296 | context->candidates = list_make1( cand ); 2297 | } 2298 | 2299 | heap_close( base_rel, AccessShareLock ); 2300 | } 2301 | return false; 2302 | } 2303 | break; 2304 | 2305 | /* Query found */ 2306 | case T_Query: 2307 | { 2308 | const Query* const query = (const Query*)root; 2309 | 2310 | context->candidates = scan_query( query, context->context, context->rangeTableStack ); 2311 | return false; 2312 | } 2313 | break; 2314 | case T_WindowFunc: 2315 | { 2316 | elog(DEBUG4, "IDX_ADV: inside window func"); 2317 | }break; 2318 | case T_MinMaxExpr: 2319 | { 2320 | elog(DEBUG4, "IDX_ADV: inside T_MinMaxExpr func"); 2321 | }break; 2322 | #if PG_VERSION_NUM >= 90500 2323 | case T_GroupingFunc: 2324 | { 2325 | elog(DEBUG4, "IDX_ADV: inside grouping func"); 2326 | }break; 2327 | #endif 2328 | /* create functional index */ 2329 | case T_FuncExpr: 2330 | { 2331 | int i; 2332 | bool too_complex = false; 2333 | IndexCandidate *cand; 2334 | //IndexCandidate *cand = (IndexCandidate*)palloc0(sizeof(IndexCandidate)); 2335 | IndexElem *ind_elm = palloc0(sizeof(IndexElem)); 2336 | FuncExpr* expr = (FuncExpr*) palloc0(sizeof(FuncExpr)); 2337 | Node *func_var; /* use this to find the relation info*/ 2338 | 2339 | elog(DEBUG4, "IND ADV: duplicate func expr properties."); 2340 | expr->xpr = ((const FuncExpr*)root)->xpr; 2341 | expr->funcid = ((const FuncExpr*)root)->funcid; 2342 | expr->funcresulttype = ((const FuncExpr*)root)->funcresulttype; 2343 | expr->funcretset = ((const FuncExpr*)root)->funcretset; 2344 | //expr->funcvariadic = ((const FuncExpr*)root)->funcvariadic; 2345 | expr->funcformat = ((const FuncExpr*)root)->funcformat; 2346 | expr->funccollid = ((const FuncExpr*)root)->funccollid; 2347 | expr->inputcollid = ((const FuncExpr*)root)->inputcollid; 2348 | expr->args = list_copy(((const FuncExpr*)root)->args); 2349 | expr->location = ((const FuncExpr*)root)->location; 2350 | 2351 | elog(DEBUG4, "TBD: support functional indexes."); 2352 | //elog_node_display(DEBUG2,"Func Expr: ",root,true); 2353 | elog_node_display(DEBUG2,"Func Expr: ",expr,true); 2354 | //ind_elm->type = T_FuncExpr; 2355 | //ind_elm->name = NULL; 2356 | //ind_elm->expr = (Node *)expr; 2357 | //ind_elm->indexcolname = NULL; 2358 | //ind_elm->collation = NIL; 2359 | //ind_elm->opclass = NIL; //TODO: change to the opclass needed for varchar_pattern_ops 2360 | //ind_elm->ordering = SORTBY_DEFAULT ; 2361 | //ind_elm->nulls_ordering = SORTBY_NULLS_DEFAULT ; 2362 | 2363 | 2364 | 2365 | /* TODO: support func indexes. 2366 | * currently we only support functions with variables on the first parameter. 2367 | * other types of functions will be ignored. 2368 | */ 2369 | // indexInfo->ii_KeyAttrNumbers[attn] = 0; /* marks expression */ 2370 | // indexInfo->ii_Expressions = lappend(indexInfo->ii_Expressions, expr); 2371 | 2372 | // get to the buttom Var 2373 | elog( DEBUG4 , "index candidate - get to buttom var"); 2374 | if(list_length(expr->args)==0) { break;} // don't create indexes for expressions with no variables 2375 | func_var = list_nth(expr->args,0); 2376 | elog( DEBUG4 , "index candidate - get to buttom type: %d",func_var->type); 2377 | elog_node_display(DEBUG2,"Func Expr: - maybe ",func_var,true); 2378 | while((!IsA(func_var, Var))&&(!IsA(func_var, Const)) && !too_complex){ 2379 | elog( DEBUG4 , "index candidate - loop to get to buttom type"); 2380 | if(IsA(func_var, FuncExpr)) 2381 | { 2382 | func_var = list_nth(((FuncExpr *)func_var)->args,0); // we only address first parameter. 2383 | } 2384 | else if(IsA(func_var, OpExpr)) 2385 | { 2386 | too_complex = true; 2387 | } 2388 | else 2389 | { 2390 | func_var = (Node *)((RelabelType *) func_var)->arg; 2391 | } 2392 | elog( DEBUG4 , "index candidate - get to buttom type: %d",func_var->type); 2393 | } 2394 | if (too_complex){ break;} // too complex 2395 | elog( DEBUG4 , "index candidate - get to buttom var - check const"); 2396 | if (IsA(func_var, Const)){ break;} // don't create indexes for expressions on constants 2397 | elog( DEBUG4 , "index candidate - function on var"); 2398 | if (IsA(func_var, Var) && 2399 | ((Var *) func_var)->varattno != InvalidAttrNumber) 2400 | { 2401 | // const Var* const expr = (const Var*)expr->args; 2402 | List* rt = list_nth( context->rangeTableStack, ((Var *)func_var)->varlevelsup ); 2403 | const RangeTblEntry* rte = list_nth( rt, ((Var *)func_var)->varno - 1 ); 2404 | if (rte->rtekind != RTE_RELATION) {break;} 2405 | char *varname = get_attname(rte->relid, ((Var *)func_var)->varattno); 2406 | if (varname == NULL) 2407 | elog(ERROR, "cache lookup failed for attribute %d of relation %u",varname, rte->relid); 2408 | 2409 | elog( DEBUG4 , "index candidate - function on var: %s",varname); 2410 | cand = (IndexCandidate*)palloc0(sizeof(IndexCandidate)); 2411 | 2412 | ///* only relations have indexes */ 2413 | //if( rte->rtekind == RTE_RELATION ) 2414 | //{ 2415 | // Relation base_rel = heap_open( rte->relid, AccessShareLock ); 2416 | 2417 | // /* We do not support catalog tables and temporary tables */ 2418 | // if( RelationNeedsWAL(base_rel) 2419 | // && !IsSystemRelation(base_rel) 2420 | // /* and don't recommend indexes on hidden/system columns */ 2421 | // && expr->varattno > 0 2422 | // /* and it should have at least two tuples */ 2423 | // //TODO: Do we really need these checks? 2424 | // && base_rel->rd_rel->relpages > 1 2425 | // && base_rel->rd_rel->reltuples > 1 2426 | // && strcmp(varname,IDX_ADV_PART_COL)!=0 ) 2427 | // { 2428 | // /* create index-candidate and build a new list */ 2429 | 2430 | 2431 | // cand->varno = expr->varno; 2432 | cand->varlevelsup = ((Var *)func_var)->varlevelsup; 2433 | cand->ncols = 1; 2434 | cand->reloid = rte->relid; 2435 | cand->erefAlias = pstrdup(rte->eref->aliasname); 2436 | cand->idxused = false; 2437 | cand->inh = rte->inh; 2438 | 2439 | cand->vartype[ 0 ] = ((Var *)func_var)->vartype; 2440 | // cand->varattno[ 0 ] = expr->varattno; 2441 | // cand->varname[ 0 ] = get_relid_attribute_name(rte->relid, expr->varattno); 2442 | // elog( DEBUG3 , "index candidate - rel: %s, var: %s",cand->erefAlias,cand->varname[ 0 ]); 2443 | // /*FIXME: Do we really need this loop? palloc0 and ncols, 2444 | // * above, should have taken care of this! 2445 | // */ 2446 | for( i = 0; i < INDEX_MAX_KEYS; ++i ) 2447 | cand->varattno[i] = 0; 2448 | 2449 | 2450 | 2451 | // } 2452 | 2453 | // heap_close( base_rel, AccessShareLock ); 2454 | } 2455 | //elog_node_display(DEBUG2,"Func Expr pre list make: ",ind_elm,true); 2456 | elog( DEBUG4 , "index candidate - func expr"); 2457 | cand->attList = lappend(cand->attList, expr); 2458 | elog_node_display(DEBUG4,"Func Expr: ",cand->attList,true); 2459 | context->candidates = list_make1( cand ); 2460 | return false; 2461 | } 2462 | break; 2463 | /* default: 2464 | { 2465 | elog( DEBUG4 , "IDX ADV: tree walker - reached the default handler "); 2466 | elog(ERROR, "unrecognized node type: %d",(int) nodeTag(root)); 2467 | } 2468 | break; */ 2469 | } 2470 | 2471 | elog( DEBUG4, "IND ADV: scan_generic_node: EXIT" ); 2472 | 2473 | 2474 | 2475 | // for any root type not specially processed, do: 2476 | return expression_tree_walker(root, index_candidates_walker, (void *) context); 2477 | } 2478 | 2479 | 2480 | /** 2481 | * scan_generic_node 2482 | * \brief this scanner uses the tree walker to drill down into the query tree to find Indexing candidates. 2483 | */ 2484 | static List* scan_generic_node( const Node* const root, 2485 | OpnosContext* opnos_context, 2486 | List* const rangeTableStack ) 2487 | { 2488 | ScanContext context; 2489 | context.candidates = NIL; 2490 | context.context = opnos_context; 2491 | context.rangeTableStack = rangeTableStack; 2492 | elog( DEBUG4, "IND ADV: scan_generic_node: before tree walk" ); 2493 | query_or_expression_tree_walker(root, 2494 | index_candidates_walker, 2495 | (void *) &context, 2496 | 0); 2497 | elog( DEBUG4, "IND ADV: scan_generic_node: return index candidates" ); 2498 | return context.candidates; 2499 | 2500 | } 2501 | 2502 | 2503 | 2504 | 2505 | /** 2506 | * compare_candidates 2507 | * \brief compares 2 index candidates based on thier OID, alias, and columns 2508 | * \TODO extend to support functional indexes 2509 | */ 2510 | static int compare_candidates( const IndexCandidate* ic1, 2511 | const IndexCandidate* ic2 ) 2512 | { 2513 | int result = (signed int)ic1->reloid - (signed int)ic2->reloid; 2514 | elog( DEBUG3, "IND ADV: compare_candidates: ENTER" ); 2515 | 2516 | if( result == 0 ) 2517 | { 2518 | result = strcmp(ic1->erefAlias,ic2->erefAlias ); 2519 | if (result == 0) 2520 | { 2521 | result = ic1->ncols - ic2->ncols; 2522 | 2523 | if( result == 0 ) 2524 | { 2525 | int i = 0; 2526 | 2527 | do 2528 | { 2529 | result = ic1->varattno[ i ] - ic2->varattno[ i ]; 2530 | ++i; 2531 | } while( ( result == 0 ) && ( i < ic1->ncols ) ); 2532 | } 2533 | } 2534 | } 2535 | 2536 | return result; 2537 | } 2538 | 2539 | /*! 2540 | * \brief get the List containing the predicate clauses for a specific rel 2541 | */ 2542 | static List* get_rel_clauses(List* table_clauses, Oid reloid, char* erefAlias) 2543 | { 2544 | ListCell *cell; 2545 | elog( DEBUG3 , "IND ADV: get_rel_clauses: enter - look for: %s",erefAlias); 2546 | foreach( cell, table_clauses ) 2547 | { 2548 | const RelClause* const relClause = (RelClause*)lfirst( cell ); 2549 | elog( DEBUG3 , "IND ADV: get_rel_clauses: in loop"); 2550 | if ((relClause->reloid == reloid) && (strcmp(relClause->erefAlias,erefAlias)==0)){ 2551 | return relClause->predicate; 2552 | } 2553 | } 2554 | elog( DEBUG3 , "IND ADV: get_rel_clauses: exit - found nothing"); 2555 | return NIL; 2556 | } 2557 | 2558 | /*! 2559 | * \brief get the ListCell containing the clauses for a specific rel 2560 | */ 2561 | static ListCell* get_rel_clausesCell(List* table_clauses, Oid reloid, char* erefAlias) 2562 | { 2563 | ListCell *cell; 2564 | 2565 | elog( DEBUG4 , "IND ADV: get_rel_clausesCell: enter - look for: %s",erefAlias); 2566 | elog( DEBUG4 , "IND ADV: get_rel_clausesCell: looking inlist of len: %d",list_length(table_clauses)); 2567 | foreach( cell, table_clauses ) 2568 | { 2569 | const RelClause* const relClause = (RelClause*)lfirst( cell ); 2570 | elog( DEBUG4 , "IND ADV: get_rel_clausesCell: RelClause"); 2571 | elog( DEBUG4 , "IND ADV: get_rel_clausesCell: RelClause: %s",relClause->erefAlias); 2572 | if ((relClause->reloid == reloid) && (strcmp(relClause->erefAlias,erefAlias)==0)){ 2573 | elog( DEBUG4 , "IND ADV: get_rel_clausesCell: found cell"); 2574 | return cell; 2575 | } 2576 | } 2577 | elog( DEBUG4 , "IND ADV: get_rel_clausesCell: found NULL"); 2578 | return NULL; 2579 | } 2580 | 2581 | /** 2582 | * log_candidates 2583 | */ 2584 | static void log_candidates( const char* prefix, List* list ) 2585 | { 2586 | ListCell *cell; 2587 | StringInfoData str;/* output string */ 2588 | elog( DEBUG4 , "IND ADV: log_candidates: enter"); 2589 | /* Verify the list is not empty */ 2590 | if(list_length(list)==0){ 2591 | elog( DEBUG4 , "IND ADV: empty list: exit"); 2592 | return; 2593 | } 2594 | 2595 | /* don't do anything unless we are going to log it */ 2596 | /*if( log_min_messages < DEBUG1 ) 2597 | return;*/ 2598 | 2599 | initStringInfo( &str ); 2600 | 2601 | foreach( cell, list ) 2602 | { 2603 | int i; 2604 | const IndexCandidate* const cand = (IndexCandidate*)lfirst( cell ); 2605 | 2606 | appendStringInfo( &str, " %d_(", cand->reloid ); 2607 | 2608 | for( i = 0; i < cand->ncols; ++i ) 2609 | appendStringInfo( &str, "%s%d", (i>0?",":""), cand->varattno[ i ] ); 2610 | 2611 | appendStringInfo( &str, ")%c", ((lnext( cell ) != NULL)?',':' ') ); 2612 | } 2613 | 2614 | elog( DEBUG1 , "IND ADV: %s: |%d| {%s}", prefix, list_length(list), 2615 | str.len ? str.data : "" ); 2616 | 2617 | if( str.len > 0 ) pfree( str.data ); 2618 | } 2619 | 2620 | /** 2621 | * merge_candidates 2622 | * It builds new list out of passed in lists, and then frees the two lists. 2623 | * 2624 | * This function maintains order of the candidates as determined by 2625 | * compare_candidates() function. 2626 | */ 2627 | static List* 2628 | merge_candidates( List* list1, List* list2 ) 2629 | { 2630 | List *ret; 2631 | ListCell *cell1; 2632 | ListCell *cell2; 2633 | ListCell *prev2; 2634 | 2635 | if( list_length( list1 ) == 0 && list_length( list2 ) == 0 ) 2636 | return NIL; 2637 | 2638 | elog( DEBUG4, "IND ADV: merge_candidates: ENTER" ); 2639 | elog( DEBUG4, "IND ADV: merge_candidates: list 1 length: %d",list_length( list1 ) ); 2640 | elog( DEBUG4, "IND ADV: merge_candidates: list 2 length: %d",list_length( list2 ) ); 2641 | 2642 | /* list1 and list2 are assumed to be sorted in ascending order */ 2643 | 2644 | elog( DEBUG1, "IND ADV: ---merge_candidates---" ); 2645 | log_candidates( "idxcd-list1", list1 ); 2646 | log_candidates( "idxcd-list2", list2 ); 2647 | 2648 | if( list_length( list1 ) == 0 ) 2649 | return list2; 2650 | 2651 | if( list_length( list2 ) == 0 ) 2652 | return list1; 2653 | 2654 | if ( list1 == list2 ) return list1; 2655 | 2656 | ret = NIL; 2657 | prev2 = NULL; 2658 | 2659 | for( cell1 = list_head(list1), cell2 = list_head(list2); 2660 | (cell1 != NULL) && (cell2 != NULL); ) 2661 | { 2662 | const int cmp = compare_candidates( (IndexCandidate*)lfirst( cell1 ), 2663 | (IndexCandidate*)lfirst( cell2 ) ); 2664 | elog( DEBUG4, "IDX_ADV: candidate compare returns: %d",cmp); 2665 | if( cmp <= 0 ) 2666 | { 2667 | /* next candidate comes from list 1 */ 2668 | ret = lappend( ret, lfirst( cell1 ) ); 2669 | 2670 | cell1 = lnext( cell1 ); 2671 | 2672 | /* if we have found two identical candidates then we remove the 2673 | * candidate from list 2 2674 | */ 2675 | if( cmp == 0 ) 2676 | { 2677 | ListCell *next = lnext( cell2 ); 2678 | 2679 | //pfree( (IndexCandidate*)lfirst( cell2 ) ); 2680 | //list2 = list_delete_cell( list2, cell2, prev2 ); 2681 | 2682 | cell2 = next; 2683 | } 2684 | } 2685 | else 2686 | { 2687 | /* next candidate comes from list 2 */ 2688 | ret = lappend( ret, lfirst( cell2 ) ); 2689 | 2690 | prev2 = cell2; 2691 | cell2 = lnext( cell2 ); 2692 | } 2693 | } 2694 | 2695 | elog( DEBUG4, "IDX_ADV: so far we have: %d",list_length( ret )); 2696 | log_candidates( "so far: ", ret ); 2697 | 2698 | /* Now append the leftovers from both the lists; only one of them should have any elements left */ 2699 | for( ; cell1 != NULL; cell1 = lnext(cell1) ) 2700 | ret = lappend( ret, lfirst(cell1) ); 2701 | 2702 | for( ; cell2 != NULL ; cell2 = lnext(cell2) ) 2703 | ret = lappend( ret, lfirst(cell2) ); 2704 | 2705 | elog( DEBUG4, "IDX_ADV: free current candidate lists"); 2706 | list1->type = T_List; 2707 | list2->type = T_List; 2708 | elog( DEBUG4, "IDX_ADV: free current candidate list1"); 2709 | if (list1 != NIL ) list_free( list1 ); 2710 | elog( DEBUG4, "IDX_ADV: free current candidate list2"); 2711 | if (list2 != NIL ) list_free( list2 ); 2712 | 2713 | log_candidates( "merged-list", ret ); 2714 | 2715 | elog( DEBUG4, "IND ADV: merge_candidates: EXIT" ); 2716 | ret->type = T_List; 2717 | return ret; 2718 | } 2719 | 2720 | /** 2721 | * expand_inherited_candidates 2722 | * It builds new list out of passed in list, to expand inherited tables. 2723 | * 2724 | * see: expand_inherited_rtentry 2725 | */ 2726 | static List* expand_inherited_candidates(List* list) 2727 | { 2728 | ListCell *cell; 2729 | LOCKMODE lockmode = NoLock; 2730 | IndexCandidate *cand; 2731 | List *inhOIDs; 2732 | List *newCandidates = NIL; 2733 | ListCell *l; 2734 | 2735 | elog(DEBUG3,"expand_inherited_candidates: Enter - length: %d",list_length(list)); 2736 | for( cell = list_head(list); (cell != NULL) ; cell = lnext( cell )) 2737 | { 2738 | cand = ((IndexCandidate*)lfirst( cell )); 2739 | newCandidates = lappend(newCandidates, cand); 2740 | if(!cand->inh) 2741 | { 2742 | elog(DEBUG3,"expand_inherited_candidates: not inh skipping"); 2743 | continue; 2744 | } 2745 | elog(DEBUG3,"expand_inherited_candidates: inh expending"); 2746 | /* Scan for all members of inheritance set, acquire needed locks */ 2747 | //inhOIDs = find_all_inheritors(cand->reloid, lockmode, NULL); 2748 | inhOIDs = find_inheritance_children(cand->reloid, lockmode); 2749 | /* Check that there's at least one descendant, else treat as no-child 2750 | * case. This could happen despite above has_subclass() check, if table 2751 | * once had a child but no longer does. 2752 | */ 2753 | //if (list_length(inhOIDs) < 2) 2754 | if (list_length(inhOIDs) < 1) 2755 | { 2756 | /* Clear flag before returning */ 2757 | elog(DEBUG3,"expand_inherited_candidates: not enough inh -> skipping"); 2758 | cand->inh = false; 2759 | continue; 2760 | } 2761 | 2762 | elog(DEBUG3,"expand_inherited_candidates: loop over sons: %d ",list_length(inhOIDs)); 2763 | foreach(l, inhOIDs) 2764 | { 2765 | int i; 2766 | Oid childOID = lfirst_oid(l); 2767 | IndexCandidate* cic = (IndexCandidate*)palloc(sizeof(IndexCandidate)); 2768 | //Relation base_rel = heap_open( childOID, AccessShareLock ); 2769 | /* init some members of composite candidate 1 */ 2770 | cic->varno = -1; 2771 | cic->varlevelsup = -1; 2772 | cic->ncols = cand->ncols; 2773 | cic->reloid = childOID; 2774 | cic->erefAlias = pstrdup(cand->erefAlias); 2775 | cic->idxused = false; 2776 | cic->parentOid = cand->reloid; 2777 | 2778 | elog( DEBUG3, "expand_inherited_candidates: start att copy, ncols: %d", cand->ncols); 2779 | /* copy attributes of the candidate to the inherited candidate */ 2780 | for( i = 0; i < cand->ncols; ++i) 2781 | { 2782 | cic->vartype[ i ] = cand->vartype[ i ]; 2783 | cic->varattno[ i ] = cand->varattno[ i ]; 2784 | cic->varname[ i ] = cand->varname[ i ]; 2785 | } 2786 | 2787 | /* set remaining attributes to null */ 2788 | for( i = cand->ncols ; i < INDEX_MAX_KEYS; ++i ) 2789 | { 2790 | cic->varattno[ i ] = 0; 2791 | 2792 | } 2793 | 2794 | /* cope index experessions for the new composite indexes */ 2795 | cic->attList = list_copy(cand->attList); 2796 | cic->amOid = cand->amOid; 2797 | elog(DEBUG3,"expand_inherited_candidates: start att copy,attlist cic: %d ",list_length(cic->attList)); 2798 | newCandidates = lappend(newCandidates, cic); 2799 | 2800 | } 2801 | } 2802 | 2803 | list_free(list); 2804 | elog(DEBUG3,"expand_inherited_candidates: Exit - length: %d",list_length(newCandidates)); 2805 | return newCandidates; 2806 | } 2807 | 2808 | 2809 | /** 2810 | * \brief expand the inherited relation clause for the table_clauses list. 2811 | **/ 2812 | static void expand_inherited_rel_clauses() 2813 | { 2814 | ListCell *cell; 2815 | LOCKMODE lockmode = NoLock; 2816 | RelClause *cand; 2817 | List *inhOIDs; 2818 | ListCell *l; 2819 | 2820 | elog(DEBUG3,"expand_inherited_rel_clauses: Enter - length: %d",list_length(table_clauses)); 2821 | for( cell = list_head(table_clauses); (cell != NULL) ; cell = lnext( cell )) 2822 | { 2823 | cand = ((RelClause*)lfirst( cell )); 2824 | 2825 | elog(DEBUG3,"expand_inherited_rel_clauses: inh expending"); 2826 | /* Scan for all members of inheritance set, acquire needed locks */ 2827 | //inhOIDs = find_all_inheritors(cand->reloid, lockmode, NULL); 2828 | inhOIDs = find_inheritance_children(cand->reloid, lockmode); 2829 | /* Check that there's at least one descendant, else treat as no-child 2830 | * case. This could happen despite above has_subclass() check, if table 2831 | * once had a child but no longer does. 2832 | */ 2833 | //if (list_length(inhOIDs) < 2) 2834 | if (list_length(inhOIDs) < 1) 2835 | { 2836 | /* Clear flag before returning */ 2837 | elog(DEBUG3,"expand_inherited_rel_clauses: not enough inh -> skipping"); 2838 | continue; 2839 | } 2840 | 2841 | elog(DEBUG3,"expand_inherited_rel_clauses: loop over sons: %d ",list_length(inhOIDs)); 2842 | foreach(l, inhOIDs) 2843 | { 2844 | //int i; 2845 | Oid childOID = lfirst_oid(l); 2846 | RelClause* cic = (RelClause*)palloc(sizeof(RelClause)); 2847 | 2848 | //Relation base_rel = heap_open( childOID, AccessShareLock ); 2849 | /* init some members of composite candidate 1 */ 2850 | cic->reloid = childOID; 2851 | cic->erefAlias = cand->erefAlias; 2852 | elog(DEBUG3,"expand_inherited_rel_clauses: create chield clause for %d, name: %s",childOID,cic->erefAlias); 2853 | 2854 | /* cope table clause experessions for the new cheild table */ 2855 | cic->predicate = list_copy(cand->predicate); 2856 | table_clauses = lappend(table_clauses, cic); 2857 | } 2858 | } 2859 | elog(DEBUG3,"expand_inherited_rel_clauses: Exit - length: %d",list_length(table_clauses)); 2860 | } 2861 | 2862 | 2863 | /** 2864 | * build_composite_candidates. 2865 | * 2866 | * @param [IN] list1 is a sorted list of candidates in ascending order. 2867 | * @param [IN] list2 is a sorted list of candidates in ascending order. 2868 | * 2869 | * @returns A new sorted list containing composite candidates. 2870 | */ 2871 | static List* 2872 | build_composite_candidates( List* list1, List* list2 ) 2873 | { 2874 | ListCell *cell1 = list_head( list1 ); 2875 | ListCell *cell2 = list_head( list2 ); 2876 | IndexCandidate *cand1; 2877 | IndexCandidate *cand2; 2878 | 2879 | List* compositeCandidates = NIL; 2880 | 2881 | elog( DEBUG4, "IND ADV: build_composite_candidates: ENTER" ); 2882 | 2883 | if( cell1 == NULL || cell2 == NULL ) 2884 | goto DoneCleanly; 2885 | 2886 | elog( DEBUG4, "IND ADV: ---build_composite_candidates---" ); 2887 | log_candidates( "idxcd-list1", list1 ); 2888 | log_candidates( "idxcd-list2", list2 ); 2889 | 2890 | /* build list with composite candiates */ 2891 | while( ( cell1 != NULL ) && ( cell2 != NULL ) ) 2892 | { 2893 | int cmp ; 2894 | 2895 | cand1 = ((IndexCandidate*)lfirst( cell1 )); 2896 | cand2 = ((IndexCandidate*)lfirst( cell2 )); 2897 | 2898 | /*! 2899 | * \TODO: fix the comparisson to create composite indexes 2900 | */ 2901 | elog( DEBUG4, "IND ADV: build_composite_candidates: compare reloids %d %d", cand1->reloid, cand2->reloid); 2902 | cmp = cand1->reloid - cand2->reloid; 2903 | elog( DEBUG4, "IND ADV: build_composite_candidates: compare aliases %s %s",cand1->erefAlias,cand2->erefAlias ); 2904 | cmp = cmp + strcmp(cand1->erefAlias,cand2->erefAlias); 2905 | 2906 | if( cmp != 0 ) 2907 | { 2908 | Oid relOid; 2909 | 2910 | if( cmp < 0 ) 2911 | { 2912 | /* advance in list 1 */ 2913 | relOid = cand2->reloid; 2914 | 2915 | do 2916 | cell1 = lnext( cell1 ); 2917 | while( cell1 != NULL && (relOid > cand1->reloid)); 2918 | } 2919 | else 2920 | { 2921 | /* advance in list 2 */ 2922 | relOid = cand1->reloid; 2923 | 2924 | do 2925 | cell2 = lnext( cell2 ); 2926 | while( cell2 != NULL && ( relOid > cand2->reloid )); 2927 | } 2928 | } 2929 | else 2930 | { 2931 | /* build composite candidates */ 2932 | Oid relationOid = ((IndexCandidate*)lfirst(cell1))->reloid; 2933 | char* alias = ((IndexCandidate*)lfirst(cell1))->erefAlias; 2934 | ListCell* l1b; 2935 | 2936 | elog( DEBUG3, "IND ADV: build_composite_candidates: build composite candidates %s ",alias ); 2937 | 2938 | do 2939 | { 2940 | cand2 = lfirst( cell2 ); 2941 | 2942 | l1b = cell1; 2943 | do 2944 | { 2945 | cand1 = lfirst( l1b ); 2946 | 2947 | /* do not build a composite candidate if the number of 2948 | * attributes would exceed INDEX_MAX_KEYS 2949 | */ 2950 | if(( ( cand1->ncols + cand2->ncols ) < INDEX_MAX_KEYS )&&(( cand1->ncols + cand2->ncols ) <= idxadv_composit_max_cols)) 2951 | { 2952 | 2953 | /* Check if candidates have any common attribute */ 2954 | int i1, i2; 2955 | bool foundCommon = false; 2956 | 2957 | for(i1 = 0; i1 < cand1->ncols && !foundCommon; ++i1) 2958 | for(i2 = 0; i2 < cand2->ncols && !foundCommon; ++i2) 2959 | if(cand1->varattno[i1] == cand2->varattno[i2]) 2960 | foundCommon = true; 2961 | if( foundCommon ) 2962 | elog( DEBUG3, "IND ADV: build_composite_candidates: found common - %d - skipping ",cand1->varattno[i1] ); 2963 | 2964 | /* build composite candidates if the previous test 2965 | * succeeded 2966 | */ 2967 | if( !foundCommon ) 2968 | { 2969 | signed int cmp; 2970 | 2971 | /* composite candidate 1 is a combination of 2972 | * candidates 1,2 AND 2973 | * composite candidate 2 is a combination of 2974 | * candidates 2,1 2975 | */ 2976 | IndexCandidate* cic1 2977 | = (IndexCandidate*)palloc( 2978 | sizeof(IndexCandidate)); 2979 | IndexCandidate* cic2 2980 | = (IndexCandidate*)palloc( 2981 | sizeof(IndexCandidate)); 2982 | 2983 | /* init some members of composite candidate 1 */ 2984 | cic1->varno = -1; 2985 | cic1->varlevelsup = -1; 2986 | cic1->ncols = cand1->ncols + cand2->ncols; 2987 | cic1->reloid = relationOid; 2988 | cic1->erefAlias = pstrdup(alias); 2989 | cic1->idxused = false; 2990 | 2991 | /* init some members of composite candidate 2 */ 2992 | cic2->varno = -1; 2993 | cic2->varlevelsup = -1; 2994 | cic2->ncols = cand1->ncols + cand2->ncols; 2995 | cic2->reloid = relationOid; 2996 | cic2->erefAlias = pstrdup(alias); 2997 | cic2->idxused = false; 2998 | 2999 | elog( DEBUG3, "IND ADV: build_composite_candidates: start att copy, ncols1: %d, ncols2: %d - total: %d", cand1->ncols , cand2->ncols,cic2->ncols); 3000 | /* copy attributes of candidate 1 to attributes of 3001 | * composite candidates 1,2 3002 | */ 3003 | for( i1 = 0; i1 < cand1->ncols; ++i1) 3004 | { 3005 | cic1->vartype[ i1 ] 3006 | = cic2->vartype[cand2->ncols + i1] 3007 | = cand1->vartype[ i1 ]; 3008 | 3009 | cic1->varattno[ i1 ] 3010 | = cic2->varattno[cand2->ncols + i1] 3011 | = cand1->varattno[ i1 ]; 3012 | cic1->varname[ i1 ] 3013 | = cic2->varname[cand2->ncols + i1] 3014 | = cand1->varname[ i1 ]; 3015 | } 3016 | 3017 | /* copy attributes of candidate 2 to attributes of 3018 | * composite candidates 2,1 3019 | */ 3020 | for( i1 = 0; i1 < cand2->ncols; ++i1) 3021 | { 3022 | cic1->vartype[cand1->ncols + i1] 3023 | = cic2->vartype[ i1 ] 3024 | = cand2->vartype[ i1 ]; 3025 | 3026 | cic1->varattno[cand1->ncols + i1] 3027 | = cic2->varattno[ i1 ] 3028 | = cand2->varattno[ i1 ]; 3029 | 3030 | cic1->varname[cand1->ncols + i1] 3031 | = cic2->varname[ i1 ] 3032 | = cand2->varname[ i1 ]; 3033 | } 3034 | 3035 | /* set remaining attributes to null */ 3036 | for( i1 = cand1->ncols + cand2->ncols; 3037 | i1 < INDEX_MAX_KEYS; 3038 | ++i1 ) 3039 | { 3040 | cic1->varattno[ i1 ] = 0; 3041 | cic2->varattno[ i1 ] = 0; 3042 | } 3043 | 3044 | /* cope index experessions for the new composite indexes */ 3045 | cic1->attList = list_concat_unique(cand1->attList,cand2->attList); 3046 | cic2->attList = list_concat_unique(cand2->attList,cand1->attList); 3047 | elog(DEBUG3,"build_composite_candidates: start att copy,attlist cic1: %d, cic2: %d ",list_length(cic1->attList),list_length(cic2->attList)); 3048 | 3049 | /* add new composite candidates to list */ 3050 | cmp = compare_candidates(cic1, cic2); 3051 | 3052 | if( cmp == 0 ) 3053 | { 3054 | compositeCandidates = 3055 | merge_candidates( list_make1( cic1 ), 3056 | compositeCandidates ); 3057 | pfree( cic2 ); 3058 | } 3059 | else 3060 | { 3061 | List* l; 3062 | 3063 | if( cmp < 0 ) 3064 | l = lcons( cic1, list_make1( cic2 ) ); 3065 | else 3066 | l = lcons( cic2, list_make1( cic1 ) ); 3067 | 3068 | compositeCandidates = 3069 | merge_candidates(l, compositeCandidates); 3070 | } 3071 | } 3072 | } 3073 | 3074 | l1b = lnext( l1b ); 3075 | 3076 | } while( ( l1b != NULL ) && 3077 | ( relationOid == ((IndexCandidate*)lfirst( l1b ))->reloid)); 3078 | 3079 | cell2 = lnext( cell2 ); 3080 | 3081 | } while( ( cell2 != NULL ) && 3082 | ( relationOid == ((IndexCandidate*)lfirst( cell2 ))->reloid ) ); 3083 | cell1 = l1b; 3084 | } 3085 | } 3086 | 3087 | log_candidates( "composite-l", compositeCandidates ); 3088 | 3089 | DoneCleanly: 3090 | elog( DEBUG4, "IND ADV: build_composite_candidates: EXIT" ); 3091 | 3092 | return compositeCandidates; 3093 | } 3094 | 3095 | /** 3096 | * create_virtual_indexes 3097 | * creates an index for every entry in the index-candidate-list. 3098 | * 3099 | * It may delete some candidates from the list passed in to it. 3100 | */ 3101 | static List* create_virtual_indexes( List* candidates ) 3102 | { 3103 | ListCell *cell; /* an entry from the candidate-list */ 3104 | ListCell *prev, *next; /* for list manipulation */ 3105 | char idx_name[ 16 ]; /* contains the name of the current index */ 3106 | int idx_count = 0; /* number of the current index */ 3107 | IndexInfo* indexInfo; 3108 | Oid op_class[INDEX_MAX_KEYS]; /* the field op class family */ 3109 | Oid collationObjectId[INDEX_MAX_KEYS]; /* the field collation */ 3110 | 3111 | elog( DEBUG4, "IND ADV: create_virtual_indexes: ENTER" ); 3112 | 3113 | /* fill index-info */ 3114 | indexInfo = makeNode( IndexInfo ); 3115 | 3116 | indexInfo->ii_Expressions = NIL; 3117 | indexInfo->ii_ExpressionsState = NIL; 3118 | indexInfo->ii_PredicateState = NIL; 3119 | indexInfo->ii_Unique = false; 3120 | indexInfo->ii_Concurrent = true; 3121 | indexInfo->ii_ReadyForInserts = false; 3122 | indexInfo->ii_BrokenHotChain = false; 3123 | indexInfo->ii_ExclusionOps = NULL; 3124 | indexInfo->ii_ExclusionProcs = NULL; 3125 | indexInfo->ii_ExclusionStrats = NULL; 3126 | 3127 | /* create index for every list entry */ 3128 | /* TODO: simplify the check condition of the loop; it is basically 3129 | * advancing the 'next' pointer, so maybe this will work 3130 | * (next=lnext(), cell()); Also, advance the 'prev' pointer in the loop 3131 | */ 3132 | elog( DEBUG1, "IND ADV: create_virtual_indexes: number of cand: %d", list_length(candidates) ); 3133 | 3134 | for( prev = NULL, cell = list_head(candidates); 3135 | (cell && (next = lnext(cell))) || cell != NULL; 3136 | cell = next) 3137 | { 3138 | int i; 3139 | 3140 | IndexCandidate* const cand = (IndexCandidate*)lfirst( cell ); 3141 | List *colNames=NIL; 3142 | 3143 | indexInfo->ii_NumIndexAttrs = cand->ncols; 3144 | elog( DEBUG3, "IND ADV: create_virtual_indexes: pre predicate %d, %s, %s", cand->reloid,cand->erefAlias,cand->varname[0]); 3145 | 3146 | indexInfo->ii_Predicate = get_rel_clauses(table_clauses, cand->reloid,cand->erefAlias); /* use the clause found for the relevant relation. */ 3147 | elog_node_display( DEBUG4 , "index_create", (Node*)indexInfo->ii_Predicate,true); 3148 | // indexInfo->ii_Predicate = NIL; 3149 | // indexInfo->ii_KeyAttrNumbers[attn] = 0; /* marks expression */ 3150 | elog( DEBUG4, "IND ADV: create_virtual_indexes: add the predicate list to the index, length %d, ncols: %d", list_length(cand->attList),cand->ncols); 3151 | 3152 | indexInfo->ii_Expressions = list_concat_unique(indexInfo->ii_Expressions, cand->attList); 3153 | 3154 | //elog_node_display( DEBUG2 , "index_create - func: ", (Node*)indexInfo->ii_Expressions,true); 3155 | 3156 | for( i = 0; i < cand->ncols; ++i ) 3157 | { 3158 | elog( DEBUG4, "IND ADV: create_virtual_indexes: prepare op_class[] vartype: %d", cand->vartype[ i ]); 3159 | /* prepare op_class[] */ 3160 | collationObjectId[i] = 0; 3161 | op_class[i] = GetDefaultOpClass( cand->vartype[ i ], cand->amOid ); 3162 | /* Replace text_ops with text_pattern_ops */ 3163 | if (op_class[i]==3126){ 3164 | idxadv_text_pattern_ops?op_class[i] = 10049:NULL; 3165 | // see pg_opclass.oid - this actually works, changes to text_pattern_ops instead of pattern ops (in te strangest way ever... see: http://doxygen.postgresql.org/indxpath_8c_source.html#l03403) 3166 | // TODO: find a way to get this via SYSCACHE instead of fixed numbers (or at least make CONSTS) 3167 | collationObjectId[i] = DEFAULT_COLLATION_OID ; //100; // need to figure this out - 100 is the default but doesn't pass for some reason... 3168 | } 3169 | 3170 | 3171 | if( op_class[i] == InvalidOid ) 3172 | /* don't create this index if couldn't find a default operator*/ 3173 | break; 3174 | 3175 | /* ... and set indexed attribute number */ 3176 | indexInfo->ii_KeyAttrNumbers[i] = cand->varattno[i]; 3177 | colNames = lappend(colNames,cand->varname[i]); 3178 | elog( DEBUG3, "col: %d, attrno: %d, opclass: %d", cand->varname[i],cand->varattno[i],op_class[i]); 3179 | } 3180 | elog( DEBUG4, "IND ADV: create_virtual_indexes: pre create" ); 3181 | 3182 | /* if we decided not to create the index above, try next candidate */ 3183 | if( i < cand->ncols ) 3184 | { 3185 | candidates = list_delete_cell( candidates, cell, prev ); 3186 | continue; 3187 | } 3188 | 3189 | /* generate indexname */ 3190 | /* FIXME: This index name can very easily collide with any other index 3191 | * being created simultaneously by other backend running index adviser. 3192 | */ 3193 | sprintf( idx_name, "idx_adv_%d", idx_count ); 3194 | elog( DEBUG4, "IND ADV: create_virtual_indexes: pre create" ); 3195 | elog( DEBUG4, "idxx name: %s", idx_name ); 3196 | 3197 | elog( DEBUG4, "IND ADV: create_virtual_indexes: open relation" ); 3198 | // CHECK: get Relation from cand->reloid 3199 | Relation relation = heap_open( cand->reloid, AccessShareLock ); 3200 | elog( DEBUG4, "IND ADV: create_virtual_indexes: create the index" ); 3201 | 3202 | /* create the index without data */ 3203 | cand->idxoid = index_create( relation 3204 | , idx_name 3205 | , InvalidOid 3206 | , InvalidOid 3207 | , indexInfo 3208 | , colNames 3209 | , cand->amOid //, BTREE_AM_OID 3210 | , InvalidOid 3211 | , collationObjectId 3212 | , op_class 3213 | , NULL 3214 | , (Datum)0 //reloptions 3215 | , false //isprimary 3216 | , false //isconstraint 3217 | , false // deferrable 3218 | , false //initdeferred 3219 | , false // allow_system_table_mods 3220 | ,true // skip build 3221 | #if PG_VERSION_NUM >= 90300 3222 | ,true // concurrent 3223 | #endif 3224 | ,false //is_internal true/false? 3225 | #if PG_VERSION_NUM >= 90500 3226 | ,false // if_not_exists 3227 | #endif 3228 | ); 3229 | 3230 | // TODO: update candidate fields: 3231 | for( i = 0; i < cand->ncols; ++i ) 3232 | { 3233 | cand->op_class[i] = op_class[i]; 3234 | cand->collationObjectId[i] = collationObjectId[i]; 3235 | } 3236 | 3237 | 3238 | elog( DEBUG4, "IND ADV: virtual index created: oid=%d name=%s size=%d", 3239 | cand->idxoid, idx_name, cand->pages ); 3240 | 3241 | /* close the heap */ 3242 | heap_close(relation, AccessShareLock); 3243 | elog( DEBUG4, "IND ADV: create_virtual_indexes: numindex %d",list_length( RelationGetIndexList(relation))); 3244 | 3245 | /* increase count for the next index */ 3246 | ++idx_count; 3247 | prev = cell; 3248 | } 3249 | 3250 | pfree( indexInfo ); 3251 | 3252 | /* do CCI to make the new metadata changes "visible" */ 3253 | CommandCounterIncrement(); 3254 | 3255 | elog( DEBUG1, "IND ADV: create_virtual_indexes: EXIT" ); 3256 | 3257 | return candidates; 3258 | } 3259 | /** 3260 | * drop_virtual_indexes 3261 | * drops all virtual-indexes 3262 | */ 3263 | static void drop_virtual_indexes( List* candidates ) 3264 | { 3265 | ListCell* cell; /* a entry from the index-candidate-list */ 3266 | 3267 | elog( DEBUG1, "IND ADV: drop_virtual_indexes: ENTER" ); 3268 | 3269 | /* drop index for every list entry */ 3270 | foreach( cell, candidates ) 3271 | { 3272 | /* TODO: have a look at implementation of index_drop! citation: 3273 | * "NOTE: this routine should now only be called through 3274 | * performDeletion(), else associated dependencies won't be cleaned up." 3275 | */ 3276 | 3277 | /* disabling index_drop() call, since it acquires AccessExclusiveLock 3278 | * on the base table, and hence causing a deadlock when multiple 3279 | * clients are running the same query 3280 | */ 3281 | 3282 | IndexCandidate* cand = (IndexCandidate*)lfirst( cell ); 3283 | elog( DEBUG1, "IND ADV: dropping virtual index: oid=%d", cand->idxoid ); 3284 | //index_drop( cand->idxoid,true ); 3285 | elog( DEBUG1, "IND ADV: virtual index dropped: oid=%d", cand->idxoid ); 3286 | } 3287 | 3288 | /* do CCI to make the new metadata changes "visible" */ 3289 | CommandCounterIncrement(); 3290 | 3291 | elog( DEBUG3, "IND ADV: drop_virtual_indexes: EXIT" ); 3292 | } 3293 | 3294 | static BlockNumber estimate_index_pages(Oid rel_oid, Oid ind_oid ) 3295 | { 3296 | Size data_length; 3297 | int i; 3298 | int natts; 3299 | int8 var_att_count; 3300 | BlockNumber rel_pages; /* diskpages of heap relation */ 3301 | float4 rel_tuples; /* tupes in the heap relation */ 3302 | double idx_pages; /* diskpages in index relation */ 3303 | 3304 | TupleDesc ind_tup_desc; 3305 | Relation base_rel; 3306 | Relation index_rel; 3307 | Form_pg_attribute *atts; 3308 | 3309 | base_rel = heap_open( rel_oid, AccessShareLock ); 3310 | index_rel = index_open( ind_oid, AccessShareLock ); 3311 | 3312 | // rel_pages = base_rel->rd_rel->relpages; 3313 | rel_tuples = base_rel->rd_rel->reltuples; 3314 | rel_pages = RelationGetNumberOfBlocks(base_rel); 3315 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: rel_id: %d, pages: %d,, tuples: %f",rel_oid,rel_pages,rel_tuples); 3316 | 3317 | ind_tup_desc = RelationGetDescr( index_rel ); 3318 | 3319 | atts = ind_tup_desc->attrs; 3320 | natts = ind_tup_desc->natts; 3321 | 3322 | 3323 | /* 3324 | * These calculations are heavily borrowed from index_form_tuple(), and 3325 | * heap_compute_data_size(). The only difference is that, that they have a 3326 | * real tuple being inserted, and hence all the VALUES are available, 3327 | * whereas, we don't have any of them available here. 3328 | */ 3329 | 3330 | /* 3331 | * First, let's calculate the contribution of fixed size columns to the size 3332 | * of index tuple 3333 | */ 3334 | var_att_count = 0; 3335 | data_length = 0; 3336 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: natts: %d",natts); 3337 | for( i = 0; i < natts; ++i) 3338 | { 3339 | /* the following is based on att_addlength() macro */ 3340 | if( atts[i]->attlen > 0 ) 3341 | { 3342 | /* No need to do +=; RHS is incrementing data_length by including it in the sum */ 3343 | data_length = att_align_nominal(data_length, atts[i]->attalign); 3344 | data_length += atts[i]->attlen; 3345 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: data_length: %d",data_length); 3346 | } 3347 | else if( atts[i]->attlen == -1 ) 3348 | { 3349 | data_length += atts[i]->atttypmod + VARHDRSZ; 3350 | } 3351 | else 3352 | { /* null terminated data */ 3353 | Assert( atts[i]->attlen == -2 ); 3354 | ++var_att_count; 3355 | } 3356 | } 3357 | 3358 | /* 3359 | * Now, estimate the average space occupied by variable-length columns, per 3360 | * tuple. This is calculated as: 3361 | * Total 'available' space 3362 | * minus space consumed by ItemIdData 3363 | * minus space consumed by fixed-length columns 3364 | * 3365 | * This calculation is very version specific, so do it for every major release. 3366 | * TODO: Analyze it for at least 1 major release and document it (perhaps 3367 | * branch the code if it deviates to a later release). 3368 | */ 3369 | if( var_att_count ) 3370 | data_length += (((float)rel_pages * (BLCKSZ - (sizeof(PageHeaderData) 3371 | - sizeof(ItemIdData) 3372 | ) ) ) 3373 | - (rel_tuples * sizeof(ItemIdData)) 3374 | - (data_length * rel_tuples) 3375 | ) 3376 | /rel_tuples; 3377 | 3378 | /* Take into account the possibility that we might have NULL values */ 3379 | data_length += IndexInfoFindDataOffset( INDEX_NULL_MASK ); 3380 | 3381 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: data_length: %d",data_length); 3382 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: sizeof(ItemIdData): %d",sizeof(ItemIdData)); 3383 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: rel_tuples: %f",rel_tuples); 3384 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: SizeOfPageHeaderData: %d",SizeOfPageHeaderData); 3385 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: BLCKSZ: %d",BLCKSZ); 3386 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: sizeof(BTPageOpaqueData: %d",sizeof(BTPageOpaqueData)); 3387 | 3388 | idx_pages = (rel_tuples * (data_length + sizeof(ItemIdData))) 3389 | /((BLCKSZ - SizeOfPageHeaderData 3390 | - sizeof(BTPageOpaqueData) 3391 | ) 3392 | * ((float)BTREE_DEFAULT_FILLFACTOR/100)); 3393 | //idx_pages = ceil( idx_pages ); 3394 | 3395 | heap_close( base_rel, AccessShareLock ); 3396 | index_close( index_rel, AccessShareLock ); 3397 | 3398 | elog(DEBUG3, "IDX_ADV: estimate_index_pages: idx_pages: %d, %d",(int8)lrint(idx_pages), (BlockNumber)lround(idx_pages)); 3399 | return (BlockNumber)lrint(idx_pages); 3400 | } 3401 | 3402 | 3403 | static Expr* makePredicateClause(OpExpr* root,Const* constArg, Var* VarArg) 3404 | { 3405 | elog(DEBUG4, "IND ADV: makePredicateClause: Enter"); 3406 | VarArg->varno=1; 3407 | return make_opclause(root->opno, root->opresulttype, root->opretset, 3408 | VarArg, constArg, 3409 | root->opcollid, root->inputcollid); 3410 | } 3411 | 3412 | 3413 | 3414 | /* 3415 | * build_index_tlist 3416 | * 3417 | * Build a targetlist representing the columns of the specified index. 3418 | * Each column is represented by a Var for the corresponding base-relation 3419 | * column, or an expression in base-relation Vars, as appropriate. 3420 | * 3421 | * There are never any dropped columns in indexes, so unlike 3422 | * build_physical_tlist, we need no failure case. 3423 | * 3424 | * taken from: src/backend/optimizer/util/plancat.c (PostgreSQL source code) 3425 | * 3426 | */ 3427 | static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index, Relation heapRelation) 3428 | { 3429 | List *tlist = NIL; 3430 | Index varno = index->rel->relid; 3431 | ListCell *indexpr_item; 3432 | int i; 3433 | 3434 | elog(DEBUG1, "build_index_tlist: Enter, ncols: %d, indexpr: %d",index->ncolumns,list_length(index->indexprs)); 3435 | indexpr_item = list_head(index->indexprs); 3436 | for (i = 0; i < index->ncolumns; i++) 3437 | { 3438 | int indexkey = index->indexkeys[i]; 3439 | Expr *indexvar; 3440 | 3441 | elog(DEBUG4, "build_index_tlist: in loop indexkey: %d",indexkey); 3442 | if (indexkey != 0) 3443 | { 3444 | /* simple column */ 3445 | Form_pg_attribute att_tup; 3446 | 3447 | if (indexkey < 0) 3448 | att_tup = SystemAttributeDefinition(indexkey, 3449 | heapRelation->rd_rel->relhasoids); 3450 | else 3451 | att_tup = heapRelation->rd_att->attrs[indexkey - 1]; 3452 | 3453 | indexvar = (Expr *) makeVar(varno, 3454 | indexkey, 3455 | att_tup->atttypid, 3456 | att_tup->atttypmod, 3457 | att_tup->attcollation, 3458 | 0); 3459 | } 3460 | else 3461 | { 3462 | /* expression column */ 3463 | if (indexpr_item == NULL) 3464 | elog(ERROR, "wrong number of index expressions - expressions column not defined properly"); 3465 | indexvar = (Expr *) lfirst(indexpr_item); 3466 | indexpr_item = lnext(indexpr_item); 3467 | elog(DEBUG4, "build_index_tlist: in loop advance indexpr_item"); 3468 | if (indexpr_item != NULL){ 3469 | elog(DEBUG4, " more to advance..."); 3470 | } 3471 | 3472 | } 3473 | 3474 | tlist = lappend(tlist, 3475 | makeTargetEntry(indexvar, 3476 | i + 1, 3477 | NULL, 3478 | false)); 3479 | } 3480 | if (indexpr_item != NULL){ 3481 | elog(ERROR, "wrong number of index expressions - ncols not setup properly"); 3482 | } 3483 | 3484 | return tlist; 3485 | } 3486 | -------------------------------------------------------------------------------- /src/idx_adviser.h: -------------------------------------------------------------------------------- 1 | /*!------------------------------------------------------------------------- 2 | * 3 | * \file idx_adviser.h 4 | * \brief Prototypes for idx_adviser.c 5 | * 6 | *------------------------------------------------------------------------- 7 | */ 8 | 9 | #ifndef IDX_ADVISOR_H 10 | #define IDX_ADVISOR_H 1 11 | 12 | 13 | #include "postgres.h" 14 | 15 | #include "nodes/print.h" 16 | #include "parser/parsetree.h" 17 | #include "catalog/namespace.h" 18 | #include "executor/executor.h" 19 | #include "utils.h" 20 | 21 | /*! \struct IndexCandidate 22 | * \brief A struct to represent an index candidate. 23 | * contains all the information needed to create the virtual index 24 | */ 25 | typedef struct { 26 | 27 | Index varno; /**< index into the rangetable */ 28 | Index varlevelsup; /**< points to the correct rangetable */ 29 | int8 ncols; /**< number of indexed columns */ 30 | Oid vartype[INDEX_MAX_KEYS];/**< type of the column(s) */ 31 | AttrNumber varattno[INDEX_MAX_KEYS];/**< attribute number of the column(s) */ 32 | char* varname[INDEX_MAX_KEYS];/**< attribute name */ 33 | Oid op_class[INDEX_MAX_KEYS]; /* the field op class family */ 34 | Oid collationObjectId[INDEX_MAX_KEYS]; /* the field collation */ 35 | List * attList; /**< list of IndexElem's - describe each parameter */ 36 | Oid reloid; /**< the table oid */ 37 | char* erefAlias; /**< hold rte->eref->aliasname */ 38 | Oid idxoid; /**< the virtual index oid */ 39 | BlockNumber pages; /**< the estimated size of index */ 40 | double tuples; /**< number of index tuples in index */ 41 | bool idxused; /**< was this used by the planner? */ 42 | float4 benefit; /**< benefit made by using this cand */ 43 | bool inh; /**< does the RTE allow inheritance */ 44 | Oid parentOid; /**< the parent table oid */ 45 | Oid amOid; 46 | } IndexCandidate; 47 | 48 | /*! 49 | * \brief A struct to keep the relation clause until we create the relevant candidates. 50 | */ 51 | typedef struct { 52 | Oid reloid; /**< the table oid */ 53 | char* erefAlias; /**< hold rte->eref->aliasname */ 54 | List* predicate; /**< the predicates used for the partial indexs */ 55 | } RelClause; 56 | 57 | typedef struct { 58 | List* predicate; /**< the predicates used for the partial indexs */ 59 | List* candidates; /**< list of candidates init to NIL; */ 60 | } QueryContext; 61 | 62 | typedef struct { 63 | List* opnos; /**< list of supported b-tree operations */ 64 | List* ginopnos; /**< list of supported gin operations */ 65 | List* gistopnos; /**< list of supported gist operations */ 66 | } OpnosContext; 67 | 68 | typedef struct 69 | { 70 | List* candidates; 71 | OpnosContext* context; 72 | List* rangeTableStack; 73 | } ScanContext; 74 | 75 | extern void _PG_init(void); 76 | extern void _PG_fini(void); 77 | 78 | #define compile_assert(x) extern int _compile_assert_array[(x)?1:-1] 79 | 80 | /* ***************************************************************************** 81 | * DEBUG Level : Information dumped 82 | * ------------ ------------------ 83 | * DEBUG1 : code level logging. What came in and what went out of a 84 | * function. candidates generated, cost estimates, etc. 85 | * DEBUG2 : DEBUG1 plus : Profiling info. Time consumed in each of the 86 | * major functions. 87 | * DEBUG3 : Above plus : function enter/leave info. 88 | * ****************************************************************************/ 89 | 90 | #define DEBUG_LEVEL_COST (log_min_messages >= DEBUG1) 91 | #define DEBUG_LEVEL_PROFILE (log_min_messages >= DEBUG2) 92 | #define DEBUG_LEVEL_CANDS (log_min_messages >= DEBUG3) 93 | 94 | /* Index Adviser output table */ 95 | #define IDX_ADV_TABL "index_advisory" 96 | 97 | /* IDX_ADV_TABL does Not Exist */ 98 | #define IDX_ADV_ERROR_NE "relation \""IDX_ADV_TABL"\" does not exist." 99 | 100 | /* IDX_ADV_TABL is Not a Table or a View */ 101 | #define IDX_ADV_ERROR_NTV "\""IDX_ADV_TABL"\" is not a table or view." 102 | 103 | #define IDX_ADV_ERROR_DETAIL \ 104 | "Index Adviser uses \""IDX_ADV_TABL"\" table to store it's advisory. You" \ 105 | " should have INSERT permissions on a table or an (INSERT-able) view named"\ 106 | " \""IDX_ADV_TABL"\". Also, make sure that you are NOT running the Index" \ 107 | " Adviser under a read-only transaction." 108 | 109 | #define IDX_ADV_ERROR_HINT \ 110 | "Please create the \""IDX_ADV_TABL"\" table." 111 | 112 | 113 | #endif /* IDX_ADVISOR_H */ 114 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | /*!------------------------------------------------------------------------- 2 | * 3 | * \file utils.c 4 | * \brief utility functions taken from PostgreSQL src code as is. 5 | * 6 | * these functions were hidden behind assertions and were copied here to by pass these assertions. 7 | * 8 | * Version History: 9 | * ================ 10 | * created by Cohen Jony 11 | * 29/07/2013 Jony Cohen taken from PostgreSQL 9.2.4 12 | * 13 | *------------------------------------------------------------------------- 14 | */ 15 | 16 | /* ------------------------------------------------------------------------ 17 | * includes (ordered alphabetically) 18 | * ------------------------------------------------------------------------ 19 | */ 20 | #include "utils.h" 21 | /* 22 | * var_eq_const --- eqsel for var = const case 23 | * 24 | * This is split out so that some other estimation functions can use it. 25 | */ 26 | 27 | /** 28 | * \brief this function computes the selectivity of 'var=const' clause. 29 | * Note: this code was taken from PostgreeSQL source code to by pass the verifications made in the code. 30 | */ 31 | extern double var_eq_cons(VariableStatData *vardata, Oid operator, Datum constval, bool constisnull, bool varonleft) 32 | { 33 | double selec; 34 | bool isdefault; 35 | 36 | elog(DEBUG4, "IND ADV: var_eq_cons: Enter"); 37 | 38 | /* 39 | * If the constant is NULL, assume operator is strict and return zero, ie, 40 | * operator will never return TRUE. 41 | */ 42 | if (constisnull) 43 | return 0.0; 44 | 45 | /* 46 | * If we matched the var to a unique index or DISTINCT clause, assume 47 | * there is exactly one match regardless of anything else. (This is 48 | * slightly bogus, since the index or clause's equality operator might be 49 | * different from ours, but it's much more likely to be right than 50 | * ignoring the information.) 51 | */ 52 | if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0) 53 | return 1.0 / vardata->rel->tuples; 54 | 55 | elog(DEBUG4, "IND ADV: var_eq_cons: non unique - look at stats"); 56 | if (HeapTupleIsValid(vardata->statsTuple)) 57 | { 58 | Form_pg_statistic stats; 59 | Datum *values; 60 | int nvalues; 61 | float4 *numbers; 62 | int nnumbers; 63 | bool match = false; 64 | int i; 65 | 66 | elog(DEBUG4, "IND ADV: var_eq_cons: get stats tuple struct"); 67 | stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); 68 | 69 | elog(DEBUG4, "IND ADV: var_eq_cons: check for common values"); 70 | /* 71 | * Is the constant "=" to any of the column's most common values? 72 | * (Although the given operator may not really be "=", we will assume 73 | * that seeing whether it returns TRUE is an appropriate test. If you 74 | * don't like this, maybe you shouldn't be using eqsel for your 75 | * operator...) 76 | */ 77 | if (get_attstatsslot(vardata->statsTuple, 78 | vardata->atttype, vardata->atttypmod, 79 | STATISTIC_KIND_MCV, InvalidOid, 80 | NULL, 81 | &values, &nvalues, 82 | &numbers, &nnumbers)) 83 | { 84 | FmgrInfo eqproc; 85 | 86 | elog(DEBUG4, "IND ADV: var_eq_cons: check common - get context for operator: %d",operator); 87 | elog(DEBUG4, "IND ADV: var_eq_cons: check common - get context for opcode: %d",get_opcode(operator)); 88 | fmgr_info(get_opcode(operator), &eqproc); 89 | 90 | elog(DEBUG4, "IND ADV: var_eq_cons: loop over vals, var on left? %s",BOOL_FMT(varonleft)); 91 | for (i = 0; i < nvalues; i++) 92 | { 93 | /* be careful to apply operator right way 'round */ 94 | if (varonleft) 95 | match = DatumGetBool(FunctionCall2Coll(&eqproc, 96 | DEFAULT_COLLATION_OID, 97 | values[i], 98 | constval)); 99 | else 100 | match = DatumGetBool(FunctionCall2Coll(&eqproc, 101 | DEFAULT_COLLATION_OID, 102 | constval, 103 | values[i])); 104 | if (match) 105 | break; 106 | } 107 | } 108 | else 109 | { 110 | elog(DEBUG4, "IND ADV: var_eq_cons: no most-common-value info available"); 111 | /* no most-common-value info available */ 112 | values = NULL; 113 | numbers = NULL; 114 | i = nvalues = nnumbers = 0; 115 | } 116 | 117 | if (match) 118 | { 119 | /* 120 | * Constant is "=" to this common value. We know selectivity 121 | * exactly (or as exactly as ANALYZE could calculate it, anyway). 122 | */ 123 | selec = numbers[i]; 124 | elog(DEBUG4, "IND ADV: var_eq_cons: Constant is \"=\" to this common value"); 125 | } 126 | else 127 | { 128 | /* 129 | * Comparison is against a constant that is neither NULL nor any 130 | * of the common values. Its selectivity cannot be more than 131 | * this: 132 | */ 133 | double sumcommon = 0.0; 134 | double otherdistinct; 135 | 136 | elog(DEBUG4, "IND ADV: var_eq_cons: Comparison is against a constant that is neither NULL nor common value"); 137 | for (i = 0; i < nnumbers; i++) 138 | sumcommon += numbers[i]; 139 | selec = 1.0 - sumcommon - stats->stanullfrac; 140 | CLAMP_PROBABILITY(selec); 141 | 142 | /* 143 | * and in fact it's probably a good deal less. We approximate that 144 | * all the not-common values share this remaining fraction 145 | * equally, so we divide by the number of other distinct values. 146 | */ 147 | otherdistinct = get_variable_numdistinct(vardata, &isdefault) - nnumbers; 148 | if (otherdistinct > 1) 149 | selec /= otherdistinct; 150 | 151 | /* 152 | * Another cross-check: selectivity shouldn't be estimated as more 153 | * than the least common "most common value". 154 | */ 155 | if (nnumbers > 0 && selec > numbers[nnumbers - 1]) 156 | selec = numbers[nnumbers - 1]; 157 | } 158 | elog(DEBUG4, "IND ADV: var_eq_cons: free the stats"); 159 | free_attstatsslot(vardata->atttype, values, nvalues, 160 | numbers, nnumbers); 161 | } 162 | else 163 | { 164 | elog(DEBUG4, "IND ADV: var_eq_cons: No ANALYZE stats available, so make a guess using estimated number."); 165 | /* 166 | * No ANALYZE stats available, so make a guess using estimated number 167 | * of distinct values and assuming they are equally common. (The guess 168 | * is unlikely to be very good, but we do know a few special cases.) 169 | */ 170 | selec = 1.0 / get_variable_numdistinct(vardata, &isdefault); 171 | } 172 | 173 | /* result should be in range, but make sure... */ 174 | CLAMP_PROBABILITY(selec); 175 | elog(DEBUG4, "IND ADV: var_eq_cons: END. retuning %.5f",selec); 176 | return selec; 177 | } 178 | 179 | /* 180 | void dump_trace() { 181 | void * buffer[255]; 182 | const int calls = backtrace(buffer, 183 | sizeof(buffer) / sizeof(void *)); 184 | backtrace_symbols_fd(buffer, calls, 1); 185 | } 186 | */ 187 | 188 | 189 | Configuration* parse_config_file(const char *filename, const char *cols, const bool text_pattern_ops, const int composit_max_cols, const bool read_only) 190 | { 191 | // FILE* file; 192 | Configuration* config; 193 | // char line[256]; 194 | // int linenum=0; 195 | // int params = 0; 196 | // char b[8]; 197 | 198 | /* 199 | file = fopen(filename, "r"); 200 | 201 | if (!file){ 202 | elog(WARNING, "parse_config_file: file failed to open, %s",filename); 203 | return NULL; 204 | } 205 | */ 206 | 207 | config = palloc(sizeof(Configuration)); 208 | config->text_pattern_ops = text_pattern_ops; 209 | config->composit_max_cols = composit_max_cols; 210 | strcpy(&(config->partQlauseCol),cols); 211 | config->read_only = read_only; 212 | 213 | /* 214 | while(fgets(line, 256, file) != NULL) 215 | { 216 | linenum++; 217 | if(line[0] == '#') continue; 218 | 219 | params = sscanf(line, "cols: %s", &(config->partQlauseCol)); 220 | params = sscanf(line, "composit_max_cols: %d", &(config->composit_max_cols)); 221 | if(!config->text_pattern_ops) 222 | config->text_pattern_ops = (sscanf(line,"text_pattern_ops: %[TtRrUuEe]",b)==1?true:false); 223 | 224 | } 225 | fclose(file); 226 | */ 227 | 228 | return config; 229 | } 230 | 231 | 232 | /* 233 | * get_opclass_name - fetch name of an index operator class 234 | * 235 | * The opclass name is appended (after a space) to buf. 236 | * 237 | * Output is suppressed if the opclass is the default for the given 238 | * actual_datatype. (If you don't want this behavior, just pass 239 | * InvalidOid for actual_datatype.) 240 | */ 241 | void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf) 242 | { 243 | HeapTuple ht_opc; 244 | Form_pg_opclass opcrec; 245 | char *opcname; 246 | char *nspname; 247 | 248 | ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); 249 | if (!HeapTupleIsValid(ht_opc)) 250 | elog(ERROR, "cache lookup failed for opclass %u", opclass); 251 | opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc); 252 | 253 | if (!OidIsValid(actual_datatype) || 254 | GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass) 255 | { 256 | /* Okay, we need the opclass name. Do we need to qualify it? */ 257 | opcname = NameStr(opcrec->opcname); 258 | if (OpclassIsVisible(opclass)) 259 | appendStringInfo(buf, " %s", quote_identifier(opcname)); 260 | else 261 | { 262 | nspname = get_namespace_name(opcrec->opcnamespace); 263 | appendStringInfo(buf, " %s.%s", 264 | quote_identifier(nspname), 265 | quote_identifier(opcname)); 266 | } 267 | } 268 | ReleaseSysCache(ht_opc); 269 | } 270 | 271 | List* create_operator_list(char *SupportedOps[], int numOps) 272 | { 273 | List* opnos = NIL; 274 | int i; 275 | 276 | elog(DEBUG2, "IDX_ADV: create_operator_list: we have %d ops to scan.",lengthof(SupportedOps)); 277 | for( i=0; i < numOps; i++ ) 278 | { 279 | FuncCandidateList opnosResult; 280 | 281 | List* supop = list_make1( makeString( SupportedOps[i] ) ); 282 | 283 | /* 284 | * collect operator ids into an array. 285 | */ 286 | for( opnosResult = OpernameGetCandidates( supop, '\0' 287 | #if PG_VERSION_NUM >= 90400 288 | , true 289 | #endif 290 | ); 291 | opnosResult != NULL; 292 | opnosResult = lnext(opnosResult) ) 293 | { 294 | elog(DEBUG2, "opno: %d, %s",opnosResult->oid ,SupportedOps[i]); 295 | opnos = lappend_oid( opnos, opnosResult->oid ); 296 | } 297 | 298 | /* free the Value* (T_String) and the list */ 299 | pfree( linitial( supop ) ); 300 | list_free( supop ); 301 | } 302 | return opnos; 303 | } 304 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | 2 | /*!------------------------------------------------------------------------- 3 | * 4 | * \file index_advisor.h 5 | * \brief Prototypes for indexadvisor.h 6 | * 7 | *------------------------------------------------------------------------- 8 | */ 9 | 10 | #ifndef UTILS_H 11 | #define UTILS_H 1 12 | 13 | 14 | #include "postgres.h" 15 | 16 | #include 17 | #include 18 | 19 | #include "access/gin.h" 20 | #include "access/sysattr.h" 21 | #if PG_VERSION_NUM >= 90300 22 | #include "access/htup_details.h" 23 | #else 24 | #include "access/htup.h" 25 | #endif 26 | #include "catalog/index.h" 27 | #include "catalog/pg_collation.h" 28 | #include "catalog/pg_opfamily.h" 29 | #include "catalog/pg_statistic.h" 30 | #include "catalog/pg_type.h" 31 | #include "catalog/pg_opclass.h" 32 | #include "catalog/namespace.h" 33 | #include "executor/executor.h" 34 | #include "mb/pg_wchar.h" 35 | #include "nodes/makefuncs.h" 36 | #include "nodes/nodeFuncs.h" 37 | #include "optimizer/clauses.h" 38 | #include "optimizer/cost.h" 39 | #include "optimizer/pathnode.h" 40 | #include "optimizer/paths.h" 41 | #include "optimizer/plancat.h" 42 | #include "optimizer/predtest.h" 43 | #include "optimizer/restrictinfo.h" 44 | #include "optimizer/var.h" 45 | #include "parser/parse_clause.h" 46 | #include "parser/parse_coerce.h" 47 | #include "parser/parsetree.h" 48 | #include "utils/builtins.h" 49 | #include "utils/bytea.h" 50 | #include "utils/date.h" 51 | #include "utils/datum.h" 52 | #include "utils/fmgroids.h" 53 | #include "utils/lsyscache.h" 54 | #include "utils/nabstime.h" 55 | #include "utils/pg_locale.h" 56 | #include "utils/rel.h" 57 | #include "utils/selfuncs.h" 58 | #include "utils/spccache.h" 59 | #include "utils/syscache.h" 60 | #include "utils/timestamp.h" 61 | #include "utils/tqual.h" 62 | #include "utils/typcache.h" 63 | #include "catalog/pg_operator.h" 64 | 65 | 66 | 67 | typedef struct { 68 | char partQlauseCol[80]; /**< holds partial clause column names, seperated by ',' */ 69 | bool text_pattern_ops; 70 | int composit_max_cols; 71 | bool read_only; 72 | } Configuration; 73 | 74 | 75 | 76 | 77 | Configuration* parse_config_file(const char *filename, const char *cols, const bool text_pattern_ops, const int composit_max_cols, const bool read_only); 78 | //Configuration* parse_config_file(const char *file_name); 79 | 80 | 81 | void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf); 82 | extern double var_eq_cons(VariableStatData *vardata, Oid operator,Datum constval, bool constisnull,bool varonleft); 83 | //void dump_trace(); 84 | List* create_operator_list(char *SupportedOps[], int numOps); 85 | #define BOOL_FMT(bool_expr) (bool_expr) ? "true" : "false" 86 | 87 | #endif /* UTILS_H */ 88 | -------------------------------------------------------------------------------- /test/expected/base.out: -------------------------------------------------------------------------------- 1 | -- set client_min_messages to log; 2 | create extension pg_idx_advisor; 3 | load 'pg_idx_advisor.so'; 4 | NOTICE: IND ADV: plugin loaded 5 | \o /tmp/pg_idx_tst.out 6 | drop table if exists t, t1; 7 | NOTICE: table "t" does not exist, skipping 8 | NOTICE: table "t1" does not exist, skipping 9 | create table t( a int, b int ); 10 | create table t1( a int, b int ); 11 | explain select * from t where a = 100; 12 | INFO: 13 | ** Plan with Original indexes ** 14 | 15 | explain select * from t where b = 100; 16 | INFO: 17 | ** Plan with Original indexes ** 18 | 19 | explain select * from t where a = 100 and b = 100; 20 | INFO: 21 | ** Plan with Original indexes ** 22 | 23 | explain select * from t where a = 100 or b = 100; 24 | INFO: 25 | ** Plan with Original indexes ** 26 | 27 | -- /* let's do some sensible join over these two tables */; 28 | -- explain select * from t, t1 where t.a = 100 and t1.a = 100 and t1.b = 100; 29 | -- explain with acte as (select * from t where a = 200) select * from acte; 30 | \o 31 | select attrs,benefit,indclass,indoption,query,recommendation from index_advisory; 32 | attrs | benefit | indclass | indoption | query | recommendation 33 | -------+---------+----------+-----------+--------------------------------------------+---------------------- 34 | {1} | 21.9635 | {1978} | {1978} | select * from t where a = 100; | create index on t(a) 35 | {2} | 21.9635 | {1978} | {1978} | select * from t where b = 100; | create index on t(b) 36 | {1} | 14.8048 | {1978} | {1978} | select * from t where a = 100 and b = 100; | create index on t(a) 37 | {2} | 14.8048 | {1978} | {1978} | select * from t where a = 100 and b = 100; | create index on t(b) 38 | {1} | 11.7747 | {1978} | {1978} | select * from t where a = 100 or b = 100; | create index on t(a) 39 | {2} | 11.7747 | {1978} | {1978} | select * from t where a = 100 or b = 100; | create index on t(b) 40 | (6 rows) 41 | 42 | -------------------------------------------------------------------------------- /test/expected/json.out: -------------------------------------------------------------------------------- 1 | CREATE TABLE webapp (doc JSONB); 2 | INSERT INTO webapp VALUES('{"name" : "Bill", "active" : true}'), ('{"name" : "Jack", "active" : true}'); 3 | insert into webapp select row_to_json(foo) from (select 'jack_'||i as name, (i%5 = 0) as active from generate_series(1,2000) i)foo; 4 | ERROR: column "doc" is of type jsonb but expression is of type json 5 | LINE 1: insert into webapp select row_to_json(foo) from (select 'jac... 6 | ^ 7 | HINT: You will need to rewrite or cast the expression. 8 | SELECT ctid, * FROM webapp ORDER BY 1; 9 | ctid | doc 10 | -------+---------------------------------- 11 | (0,1) | {"name": "Bill", "active": true} 12 | (0,2) | {"name": "Jack", "active": true} 13 | (2 rows) 14 | 15 | CREATE INDEX i_webapp_yc ON webapp USING gin (doc /* jsonb_ops */); 16 | CREATE INDEX i_webapp_doc_path ON webapp USING gin (doc jsonb_path_ops); 17 | explain select doc->'active' from webapp where doc @> '{"name" : "Bill"}'::jsonb; 18 | QUERY PLAN 19 | ------------------------------------------------------- 20 | Seq Scan on webapp (cost=0.00..1.03 rows=1 width=32) 21 | Filter: (doc @> '{"name": "Bill"}'::jsonb) 22 | (2 rows) 23 | 24 | -------------------------------------------------------------------------------- /test/expected/measurement.out: -------------------------------------------------------------------------------- 1 | -- test for target list and inherited tables 2 | CREATE OR REPLACE FUNCTION measurement_insert_trigger() 3 | RETURNS TRIGGER AS $$ 4 | BEGIN 5 | IF ( NEW.logdate >= DATE '2006-02-01' AND 6 | NEW.logdate < DATE '2006-03-01' ) THEN 7 | INSERT INTO measurement_y2006m02 VALUES (NEW.*); 8 | ELSIF ( NEW.logdate >= DATE '2006-03-01' AND 9 | NEW.logdate < DATE '2006-04-01' ) THEN 10 | INSERT INTO measurement_y2006m03 VALUES (NEW.*); 11 | ELSE 12 | RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; 13 | END IF; 14 | RETURN NULL; 15 | END; 16 | $$ language plpgsql; 17 | CREATE TABLE measurement ( 18 | city_id int not null, 19 | logdate date not null, 20 | peaktemp int, 21 | unitsales int 22 | ); 23 | CREATE TABLE measurement_y2006m02 ( 24 | CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) 25 | ) INHERITS (measurement); 26 | CREATE TABLE measurement_y2006m03 ( 27 | CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) 28 | ) INHERITS (measurement); 29 | CREATE TRIGGER insert_measurement_trigger 30 | BEFORE INSERT ON measurement 31 | FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); 32 | create index idx_measurement_unitsales_y2006m02 ON measurement_y2006m02 (unitsales); 33 | -- now load the advisor 34 | load 'pg_idx_advisor.so'; 35 | NOTICE: IND ADV: plugin loaded 36 | \o /tmp/pg_idx_tst.out 37 | -- we should get advices on both parent and cheild tables 38 | explain select max(unitsales) from measurement; 39 | INFO: 40 | ** Plan with Original indexes ** 41 | 42 | \o 43 | select attrs,benefit,indclass,indoption,query,recommendation from index_advisory; 44 | attrs | benefit | indclass | indoption | query | recommendation 45 | -------+----------+----------+-----------+--------------------------------------------+------------------------------------------------- 46 | {1} | 21.9635 | {1978} | {1978} | select * from t where a = 100; | create index on t(a) 47 | {2} | 21.9635 | {1978} | {1978} | select * from t where b = 100; | create index on t(b) 48 | {1} | 14.8048 | {1978} | {1978} | select * from t where a = 100 and b = 100; | create index on t(a) 49 | {2} | 14.8048 | {1978} | {1978} | select * from t where a = 100 and b = 100; | create index on t(b) 50 | {1} | 11.7747 | {1978} | {1978} | select * from t where a = 100 or b = 100; | create index on t(a) 51 | {2} | 11.7747 | {1978} | {1978} | select * from t where a = 100 or b = 100; | create index on t(b) 52 | {4} | 0.066363 | {1978} | {1978} | select max(unitsales) from measurement; | create index on measurement(unitsales) 53 | {4} | 0.066363 | {1978} | {1978} | select max(unitsales) from measurement; | create index on measurement_y2006m03(unitsales) 54 | (8 rows) 55 | 56 | -------------------------------------------------------------------------------- /test/sql/base.sql: -------------------------------------------------------------------------------- 1 | -- set client_min_messages to log; 2 | 3 | create extension pg_idx_advisor; 4 | 5 | load 'pg_idx_advisor.so'; 6 | 7 | \o /tmp/pg_idx_tst.out 8 | 9 | drop table if exists t, t1; 10 | create table t( a int, b int ); 11 | create table t1( a int, b int ); 12 | 13 | 14 | explain select * from t where a = 100; 15 | 16 | explain select * from t where b = 100; 17 | 18 | explain select * from t where a = 100 and b = 100; 19 | 20 | explain select * from t where a = 100 or b = 100; 21 | 22 | -- /* let's do some sensible join over these two tables */; 23 | -- explain select * from t, t1 where t.a = 100 and t1.a = 100 and t1.b = 100; 24 | 25 | -- explain with acte as (select * from t where a = 200) select * from acte; 26 | \o 27 | select attrs,benefit,indclass,indoption,query,recommendation from index_advisory; 28 | -------------------------------------------------------------------------------- /test/sql/json.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE webapp (doc JSONB); 2 | 3 | INSERT INTO webapp VALUES('{"name" : "Bill", "active" : true}'), ('{"name" : "Jack", "active" : true}'); 4 | insert into webapp select row_to_json(foo) from (select 'jack_'||i as name, (i%5 = 0) as active from generate_series(1,2000) i)foo; 5 | 6 | 7 | SELECT ctid, * FROM webapp ORDER BY 1; 8 | CREATE INDEX i_webapp_yc ON webapp USING gin (doc /* jsonb_ops */); 9 | CREATE INDEX i_webapp_doc_path ON webapp USING gin (doc jsonb_path_ops); 10 | 11 | explain select doc->'active' from webapp where doc @> '{"name" : "Bill"}'::jsonb; 12 | 13 | -------------------------------------------------------------------------------- /test/sql/measurement.sql: -------------------------------------------------------------------------------- 1 | -- test for target list and inherited tables 2 | 3 | CREATE OR REPLACE FUNCTION measurement_insert_trigger() 4 | RETURNS TRIGGER AS $$ 5 | BEGIN 6 | IF ( NEW.logdate >= DATE '2006-02-01' AND 7 | NEW.logdate < DATE '2006-03-01' ) THEN 8 | INSERT INTO measurement_y2006m02 VALUES (NEW.*); 9 | ELSIF ( NEW.logdate >= DATE '2006-03-01' AND 10 | NEW.logdate < DATE '2006-04-01' ) THEN 11 | INSERT INTO measurement_y2006m03 VALUES (NEW.*); 12 | ELSE 13 | RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; 14 | END IF; 15 | RETURN NULL; 16 | END; 17 | $$ language plpgsql; 18 | 19 | CREATE TABLE measurement ( 20 | city_id int not null, 21 | logdate date not null, 22 | peaktemp int, 23 | unitsales int 24 | ); 25 | 26 | CREATE TABLE measurement_y2006m02 ( 27 | CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) 28 | ) INHERITS (measurement); 29 | 30 | CREATE TABLE measurement_y2006m03 ( 31 | CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) 32 | ) INHERITS (measurement); 33 | 34 | CREATE TRIGGER insert_measurement_trigger 35 | BEFORE INSERT ON measurement 36 | FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); 37 | 38 | create index idx_measurement_unitsales_y2006m02 ON measurement_y2006m02 (unitsales); 39 | 40 | -- now load the advisor 41 | load 'pg_idx_advisor.so'; 42 | \o /tmp/pg_idx_tst.out 43 | -- we should get advices on both parent and cheild tables 44 | explain select max(unitsales) from measurement; 45 | \o 46 | select attrs,benefit,indclass,indoption,query,recommendation from index_advisory; 47 | 48 | --------------------------------------------------------------------------------