File 00010-joinsel.patch of Package postgresql10
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 132916aa70..75e1986ecd 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -306,6 +306,27 @@ performDeletion(const ObjectAddress *object,
Relation depRel;
ObjectAddresses *targetObjects;
+ if (flags & PERFORM_DELETION_CONCURRENTLY)
+ {
+ /*
+ * We must commit our transaction in order to make the first pg_index
+ * state update visible to other sessions. If the DROP machinery has
+ * already performed any other actions (removal of other objects,
+ * pg_depend entries, etc), the commit would make those actions
+ * permanent, which would leave us with inconsistent catalog state if
+ * we fail partway through the following sequence. Since DROP INDEX
+ * CONCURRENTLY is restricted to dropping just one index that has no
+ * dependencies, we should get here before anything's been done ---
+ * but let's check that to be sure. We can verify that the current
+ * transaction has not executed any transactional updates by checking
+ * that no XID has been assigned.
+ */
+ if (GetTopTransactionIdIfAny() != InvalidTransactionId)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DROP INDEX CONCURRENTLY must be first action in transaction")));
+ }
+
/*
* We save some cycles by opening pg_depend just once and passing the
* Relation pointer down to all the recursive deletion steps.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0c2e457697..764d9de75f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -46,9 +46,11 @@
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_type_fn.h"
#include "catalog/storage.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
+#include "commands/typecmds.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -94,7 +96,8 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
List *indexColNames,
Oid accessMethodObjectId,
Oid *collationObjectId,
- Oid *classObjectId);
+ Oid *classObjectId,
+ bool *preserveAttrType);
static void InitializeAttributeOids(Relation indexRelation,
int numatts, Oid indexoid);
static void AppendAttributeTuples(Relation indexRelation, int numatts);
@@ -282,7 +285,8 @@ ConstructTupleDescriptor(Relation heapRelation,
List *indexColNames,
Oid accessMethodObjectId,
Oid *collationObjectId,
- Oid *classObjectId)
+ Oid *classObjectId,
+ bool *preserveAttrType)
{
int numatts = indexInfo->ii_NumIndexAttrs;
ListCell *colnames_item = list_head(indexColNames);
@@ -306,6 +310,11 @@ ConstructTupleDescriptor(Relation heapRelation,
indexTupDesc = CreateTemplateTupleDesc(numatts, false);
/*
+ * By default assume that index will reuse attribute types of the heap relation
+ */
+ *preserveAttrType = true;
+
+ /*
* For simple index columns, we copy the pg_attribute row from the parent
* relation and modify it as necessary. For expressions we have to cons
* up a pg_attribute row the hard way.
@@ -476,6 +485,7 @@ ConstructTupleDescriptor(Relation heapRelation,
to->attbyval = typeTup->typbyval;
to->attalign = typeTup->typalign;
to->attstorage = typeTup->typstorage;
+ *preserveAttrType = false;
ReleaseSysCache(tuple);
}
@@ -732,10 +742,14 @@ index_create(Relation heapRelation,
Oid namespaceId;
int i;
char relpersistence;
+ bool preserve_attr_type;
+ Oid ownerId;
+ ObjectAddress new_type_addr;
is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+ ownerId = GetUserId();
/*
* The index will be in the same namespace as its parent table, and is
@@ -821,7 +835,8 @@ index_create(Relation heapRelation,
indexColNames,
accessMethodObjectId,
collationObjectId,
- classObjectId);
+ classObjectId,
+ &preserve_attr_type);
/*
* Allocate an OID for the index, unless we were told what to use.
@@ -868,6 +883,95 @@ index_create(Relation heapRelation,
Assert(indexRelationId == RelationGetRelid(indexRelation));
+ if (indexInfo->ii_NumIndexAttrs > 1
+ && preserve_attr_type
+ && !is_internal
+ && (!IsBinaryUpgrade || binary_upgrade_next_pg_type_oid != InvalidOid))
+ {
+ Oid new_array_oid = AssignTypeArrayOid();
+ char* relarrayname;
+
+ /*
+ * Build compound type for compound index to be able to use it in statistic.
+ * We need to collect statistic for compound indexes to be able to better predict selectivity of multicolumn joins.
+ */
+ new_type_addr = TypeCreate(InvalidOid,
+ indexRelationName,
+ namespaceId,
+ indexRelationId,
+ RELKIND_INDEX,
+ ownerId, /* owner's ID */
+ -1, /* internal size (varlena) */
+ TYPTYPE_COMPOSITE, /* type-type (composite) */
+ TYPCATEGORY_COMPOSITE, /* type-category (ditto) */
+ false, /* composite types are never preferred */
+ DEFAULT_TYPDELIM, /* default array delimiter */
+ F_RECORD_IN, /* input procedure */
+ F_RECORD_OUT, /* output procedure */
+ F_RECORD_RECV, /* receive procedure */
+ F_RECORD_SEND, /* send procedure */
+ InvalidOid, /* typmodin procedure - none */
+ InvalidOid, /* typmodout procedure - none */
+ InvalidOid, /* analyze procedure - default */
+ InvalidOid, /* array element type - irrelevant */
+ false, /* this is not an array type */
+ new_array_oid, /* array type if any */
+ InvalidOid, /* domain base type - irrelevant */
+ NULL, /* default value - none */
+ NULL, /* default binary representation */
+ false, /* passed by reference */
+ 'd', /* alignment - must be the largest! */
+ 'x', /* fully TOASTable */
+ -1, /* typmod */
+ 0, /* array dimensions for typBaseType */
+ false, /* Type NOT NULL */
+ InvalidOid); /* rowtypes never have a collation */
+
+ indexRelation->rd_rel->reltype = new_type_addr.objectId;
+
+ relarrayname = makeArrayTypeName(indexRelationName, namespaceId);
+
+ TypeCreate(new_array_oid, /* force the type's OID to this */
+ relarrayname, /* Array type name */
+ namespaceId, /* Same namespace as parent */
+ InvalidOid, /* Not composite, no relationOid */
+ 0, /* relkind, also N/A here */
+ ownerId, /* owner's ID */
+ -1, /* Internal size (varlena) */
+ TYPTYPE_BASE, /* Not composite - typelem is */
+ TYPCATEGORY_ARRAY, /* type-category (array) */
+ false, /* array types are never preferred */
+ DEFAULT_TYPDELIM, /* default array delimiter */
+ F_ARRAY_IN, /* array input proc */
+ F_ARRAY_OUT, /* array output proc */
+ F_ARRAY_RECV, /* array recv (bin) proc */
+ F_ARRAY_SEND, /* array send (bin) proc */
+ InvalidOid, /* typmodin procedure - none */
+ InvalidOid, /* typmodout procedure - none */
+ F_ARRAY_TYPANALYZE, /* array analyze procedure */
+ indexRelation->rd_rel->reltype, /* array element type - the rowtype */
+ true, /* yes, this is an array type */
+ InvalidOid, /* this has no array type */
+ InvalidOid, /* domain base type - irrelevant */
+ NULL, /* default value - none */
+ NULL, /* default binary representation */
+ false, /* passed by reference */
+ 'd', /* alignment - must be the largest! */
+ 'x', /* fully TOASTable */
+ -1, /* typmod */
+ 0, /* array dimensions for typBaseType */
+ false, /* Type NOT NULL */
+ InvalidOid); /* rowtypes never have a collation */
+
+ pfree(relarrayname);
+ }
+
+ /*
+ * Register relcache invalidation on the indexes' heap relation, to
+ * maintain consistency of its index list
+ */
+ CacheInvalidateRelcache(heapRelation);
+
/*
* Obtain exclusive lock on it. Although no other backends can see it
* until we commit, this prevents deadlock-risk complaints from lock
@@ -923,12 +1027,6 @@ index_create(Relation heapRelation,
!deferrable,
!concurrent);
- /*
- * Register relcache invalidation on the indexes' heap relation, to
- * maintain consistency of its index list
- */
- CacheInvalidateRelcache(heapRelation);
-
/*
* Register constraint and dependencies for the index.
*
@@ -1428,24 +1526,6 @@ index_drop(Oid indexId, bool concurrent)
*/
if (concurrent)
{
- /*
- * We must commit our transaction in order to make the first pg_index
- * state update visible to other sessions. If the DROP machinery has
- * already performed any other actions (removal of other objects,
- * pg_depend entries, etc), the commit would make those actions
- * permanent, which would leave us with inconsistent catalog state if
- * we fail partway through the following sequence. Since DROP INDEX
- * CONCURRENTLY is restricted to dropping just one index that has no
- * dependencies, we should get here before anything's been done ---
- * but let's check that to be sure. We can verify that the current
- * transaction has not executed any transactional updates by checking
- * that no XID has been assigned.
- */
- if (GetTopTransactionIdIfAny() != InvalidTransactionId)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("DROP INDEX CONCURRENTLY must be first action in transaction")));
-
/*
* Mark index invalid by updating its pg_index entry
*/
@@ -3219,9 +3299,6 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
HeapTuple indexTuple;
Form_pg_index indexForm;
- /* Assert that current xact hasn't done any transactional updates */
- Assert(GetTopTransactionIdIfAny() == InvalidTransactionId);
-
/* Open pg_index and fetch a writable copy of the index's tuple */
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 925d06ed8c..d5a024db14 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -35,8 +35,11 @@
#include "commands/vacuum.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
+#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/makefuncs.h"
+#include "nodes/pg_list.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "pgstat.h"
@@ -61,6 +64,7 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "utils/tqual.h"
+#include "utils/typcache.h"
/* Per-index data for ANALYZE */
@@ -70,6 +74,7 @@ typedef struct AnlIndexData
double tupleFract; /* fraction of rows for partial index */
VacAttrStats **vacattrstats; /* index attrs to analyze */
int attr_cnt;
+ bool multicolumn; /* Collect compound row statistic for multicolumn index */
} AnlIndexData;
@@ -480,6 +485,21 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
}
thisdata->attr_cnt = tcnt;
}
+ else if (indexInfo->ii_NumIndexAttrs > 1 && va_cols == NIL &&
+ Irel[ind]->rd_rel->reltype != InvalidOid)
+ {
+ /* Collect statistic for multicolumn index for better predicting selectivity of multicolumn joins */
+ RowExpr* row = makeNode(RowExpr);
+ row->row_typeid = Irel[ind]->rd_rel->reltype;
+ row->row_format = COERCE_EXPLICIT_CAST;
+ row->location = -1;
+ row->colnames = NULL;
+ thisdata->vacattrstats = (VacAttrStats **)palloc(sizeof(VacAttrStats *));
+ thisdata->vacattrstats[0] = examine_attribute(Irel[ind], 1, (Node*)row);
+ thisdata->vacattrstats[0]->tupDesc = lookup_type_cache(row->row_typeid, TYPECACHE_TUPDESC)->tupDesc;
+ thisdata->attr_cnt = 1;
+ thisdata->multicolumn = true;
+ }
}
}
@@ -797,28 +817,41 @@ compute_index_stats(Relation onerel, double totalrows,
values,
isnull);
- /*
- * Save just the columns we care about. We copy the values
- * into ind_context from the estate's per-tuple context.
- */
- for (i = 0; i < attr_cnt; i++)
+ if (thisdata->multicolumn)
{
- VacAttrStats *stats = thisdata->vacattrstats[i];
- int attnum = stats->attr->attnum;
-
- if (isnull[attnum - 1])
- {
- exprvals[tcnt] = (Datum) 0;
- exprnulls[tcnt] = true;
- }
- else
+ /* For multicolumn index construct compound value */
+ VacAttrStats *stats = thisdata->vacattrstats[0];
+ exprvals[tcnt] = HeapTupleGetDatum(heap_form_tuple(stats->tupDesc,
+ values,
+ isnull));
+ exprnulls[tcnt] = false;
+ tcnt++;
+ }
+ else
+ {
+ /*
+ * Save just the columns we care about. We copy the values
+ * into ind_context from the estate's per-tuple context.
+ */
+ for (i = 0; i < attr_cnt; i++)
{
- exprvals[tcnt] = datumCopy(values[attnum - 1],
- stats->attrtype->typbyval,
- stats->attrtype->typlen);
- exprnulls[tcnt] = false;
+ VacAttrStats *stats = thisdata->vacattrstats[i];
+ int attnum = stats->attr->attnum;
+
+ if (isnull[attnum - 1])
+ {
+ exprvals[tcnt] = (Datum) 0;
+ exprnulls[tcnt] = true;
+ }
+ else
+ {
+ exprvals[tcnt] = datumCopy(values[attnum - 1],
+ stats->attrtype->typbyval,
+ stats->attrtype->typlen);
+ exprnulls[tcnt] = false;
+ }
+ tcnt++;
}
- tcnt++;
}
}
}
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 9d340255c3..ea9660bb62 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -14,6 +14,9 @@
*/
#include "postgres.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "funcapi.h"
#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
@@ -21,7 +24,10 @@
#include "optimizer/plancat.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/selfuncs.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
#include "statistics/statistics.h"
@@ -43,6 +49,327 @@ static void addRangeClause(RangeQueryClause **rqlist, Node *clause,
bool varonleft, bool isLTsel, Selectivity s2);
static RelOptInfo *find_single_rel_for_clauses(PlannerInfo *root,
List *clauses);
+/*
+ * Get variabe node. Returns null if node is not a Var node.
+ */
+static inline Var*
+get_var(Node* node)
+{
+ if (IsA(node, RelabelType))
+ node = (Node *) ((RelabelType *) node)->arg;
+
+ return IsA(node, Var) ? (Var*)node : NULL;
+}
+
+/*
+ * Locate compound index which can be used for multicolumn join. Join clauses can refer to index keys in any order, but this order should be the same
+ * for left and right indexes. This is why we first call this function with strict_vars_order == false and fill permutation array. And second time wee use this
+ * permutation for previously chosen order.
+ */
+static IndexOptInfo*
+locate_multicolumn_index(PlannerInfo *root, Index varno, List* vars, int* permutation, bool strict_vars_order)
+{
+ ListCell *ilist;
+ RelOptInfo* rel = find_base_rel(root, varno);
+ int n_vars = list_length(vars);
+
+ Assert(n_vars > 1);
+
+ foreach(ilist, rel->indexlist)
+ {
+ IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
+ ListCell *vlist = list_head(vars);
+ int i = 0;
+ bool used[INDEX_MAX_KEYS] = {false};
+
+ if (index->ncolumns != n_vars)
+ continue;
+
+ foreach (vlist, vars)
+ {
+ Var* var = lfirst(vlist);
+ if (strict_vars_order)
+ {
+ if (index->indexkeys[permutation[i]] != var->varattno)
+ break;
+ }
+ else
+ {
+ int pos;
+ for (pos = 0; pos < n_vars; pos++)
+ {
+ if (index->indexkeys[pos] == var->varattno && !used[pos])
+ {
+ used[pos] = true;
+ permutation[i] = pos;
+ break;
+ }
+ }
+ if (pos == n_vars)
+ break;
+ }
+ i += 1;
+ }
+ if (vlist == NULL)
+ return index;
+ }
+ return NULL;
+}
+
+/*
+ * treat_as_join_clause -
+ * Decide whether an operator clause is to be handled by the
+ * restriction or join estimator. Subroutine for clause_selectivity().
+ */
+static inline bool
+treat_as_join_clause(Node *clause, RestrictInfo *rinfo,
+ int varRelid, SpecialJoinInfo *sjinfo)
+{
+ if (varRelid != 0)
+ {
+ /*
+ * Caller is forcing restriction mode (eg, because we are examining an
+ * inner indexscan qual).
+ */
+ return false;
+ }
+ else if (sjinfo == NULL)
+ {
+ /*
+ * It must be a restriction clause, since it's being evaluated at a
+ * scan node.
+ */
+ return false;
+ }
+ else
+ {
+ /*
+ * Otherwise, it's a join if there's more than one relation used. We
+ * can optimize this calculation if an rinfo was passed.
+ *
+ * XXX Since we know the clause is being evaluated at a join, the
+ * only way it could be single-relation is if it was delayed by outer
+ * joins. Although we can make use of the restriction qual estimators
+ * anyway, it seems likely that we ought to account for the
+ * probability of injected nulls somehow.
+ */
+ if (rinfo)
+ return (bms_membership(rinfo->clause_relids) == BMS_MULTIPLE);
+ else
+ return (NumRelids(clause) > 1);
+ }
+}
+
+
+/*
+ * Check if clauses represent multicolumnn join with compound indexes available for both side or
+ * comparison of indexed columns of one relation with constant values.
+ * If so, calculates selectivity of compound type comparison and returns true
+ */
+static bool
+use_multicolumn_statistic(PlannerInfo *root, List *clauses, int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo, Selectivity* selectivity)
+{
+ ListCell *l;
+ List* vars1 = NULL;
+ List* vars2 = NULL;
+ Index varno1 = 0;
+ Index varno2 = 0;
+ VariableStatData vardata[2];
+ IndexOptInfo* index[2];
+ TypeCacheEntry *typentry;
+ int i;
+ int n_indexes;
+ int permutation[INDEX_MAX_KEYS];
+ bool varonleft = true;
+ Datum constant = 0;
+
+ foreach(l, clauses)
+ {
+ Node* clause = (Node *) lfirst(l);
+ RestrictInfo* rinfo = NULL;
+ OpExpr *opclause = NULL;
+
+ if (IsA(clause, RestrictInfo))
+ {
+ rinfo = (RestrictInfo *) clause;
+ if (!rinfo->orclause)
+ clause = (Node*)rinfo->clause;
+ }
+ if (IsA(clause, OpExpr))
+ opclause = (OpExpr*)clause;
+
+ if (opclause
+ && list_length(opclause->args) == 2
+ && get_oprrest(opclause->opno) == F_EQSEL)
+ {
+ Node* arg1 = (Node*) linitial(opclause->args);
+ Node* arg2 = (Node*) lsecond(opclause->args);
+ Var* var1 = get_var(arg1);
+ Var* var2 = get_var(arg2);
+
+ if (treat_as_join_clause((Node*)opclause, NULL, varRelid, sjinfo))
+ {
+ if (var1 == NULL || var2 == NULL || var1->vartype != var2->vartype)
+ return false;
+
+ if (varno1 == 0)
+ {
+ varno1 = var1->varno;
+ varno2 = var2->varno;
+ vars1 = list_make1(var1);
+ vars2 = list_make1(var2);
+ }
+ else if (var1->varno == varno1 && var2->varno == varno2)
+ {
+ vars1 = lappend(vars1, var1);
+ vars2 = lappend(vars2, var2);
+ }
+ else if (var1->varno == varno2 && var2->varno == varno1)
+ {
+ vars1 = lappend(vars1, var2);
+ vars2 = lappend(vars2, var1);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else /* Estimate selectivity for a restriction clause. */
+ {
+ /*
+ * Give up if it is not equality comparison of variable with
+ * constant or some other clause is treated as join condition
+ */
+ if (((var1 == NULL) == (var2 == NULL)) || varno2 != 0)
+ return false;
+
+ if (var1 != NULL) /* variable at left */
+ {
+ if (varno1 == 0)
+ varno1 = var1->varno;
+ else if (var1->varno != varno1)
+ return false;
+ vars1 = lappend(vars1, var1);
+
+ if ((rinfo && is_pseudo_constant_clause_relids(arg2, rinfo->right_relids))
+ || (!rinfo && NumRelids(clause) == 1 && is_pseudo_constant_clause(arg2)))
+ {
+ /* Restriction clause with a pseudoconstant on right side. */
+ Node* const_val = estimate_expression_value(root, arg2);
+ if (IsA(const_val, Const))
+ vars2 = lappend(vars2, const_val);
+ else
+ return false;
+ }
+ else
+ return false;
+ }
+ else
+ {
+ varonleft = false; /* Case (x=1 and 2=x) is not considered */
+
+ if (varno1 == 0)
+ varno1 = var2->varno;
+ else if (var2->varno != varno1)
+ return false;
+ vars1 = lappend(vars1, var2);
+
+ if ((rinfo && is_pseudo_constant_clause_relids(arg1, rinfo->left_relids))
+ || (!rinfo && NumRelids(clause) == 1 && is_pseudo_constant_clause(arg1)))
+ {
+ /* Restriction clause with a pseudoconstant on left side. */
+ Node* const_val = estimate_expression_value(root, arg1);
+ if (IsA(const_val, Const))
+ vars2 = lappend(vars2, const_val);
+ else
+ return false;
+ }
+ else
+ return false;
+ }
+ }
+ }
+ else
+ return false;
+ }
+ if (varno1 == 0)
+ return false;
+
+ index[0] = locate_multicolumn_index(root, varno1, vars1, permutation, false);
+ if (!index[0])
+ return false;
+
+ if (varno2 != 0)
+ {
+ index[1] = locate_multicolumn_index(root, varno2, vars2, permutation, true);
+ if (!index[1])
+ return false;
+ n_indexes = 2;
+ }
+ else
+ n_indexes = 1;
+
+ for (i = 0; i < n_indexes; i++)
+ {
+ Relation indexRel = index_open(index[i]->indexoid, AccessShareLock);
+
+ if (!indexRel->rd_rel->reltype)
+ {
+ index_close(indexRel, AccessShareLock);
+
+ while (--i >= 0)
+ ReleaseVariableStats(vardata[i]);
+
+ return false;
+ }
+ memset(&vardata[i], 0, sizeof(vardata[i]));
+ vardata[i].isunique = index[i]->unique;
+ vardata[i].atttype = indexRel->rd_rel->reltype;
+ vardata[i].rel = index[i]->rel;
+ vardata[i].acl_ok = true;
+ vardata[i].statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index[i]->indexoid),
+ Int16GetDatum(1),
+ BoolGetDatum(false));
+ vardata[i].freefunc = ReleaseSysCache;
+
+ index_close(indexRel, AccessShareLock);
+ }
+
+ /*
+ * Assume that two compound types are coheerent, so we can use equality function from one type
+ * to compare it with other type.
+ */
+ typentry = lookup_type_cache(vardata[0].atttype, TYPECACHE_EQ_OPR|TYPECACHE_TUPDESC);
+
+ if (n_indexes == 1) /* restriction clause selectivity */
+ {
+ /* Create compound constant value */
+ Datum values[INDEX_MAX_KEYS];
+ bool isnull[INDEX_MAX_KEYS];
+ i = 0;
+ foreach (l, vars2)
+ {
+ Const* c = (Const*) lfirst(l);
+ int j = permutation[i++];
+ values[j] = c->constvalue;
+ isnull[j] = c->constisnull;
+ }
+ constant = HeapTupleGetDatum(heap_form_tuple(typentry->tupDesc, values, isnull));
+ *selectivity = eqconst_selectivity(typentry->eq_opr, &vardata[0],
+ constant, false, varonleft, false);
+ }
+ else /* join clause selectivity */
+ {
+ *selectivity = eqjoin_selectivity(root, typentry->eq_opr,
+ &vardata[0], &vardata[1], sjinfo);
+ }
+
+ for (i = 0; i < n_indexes; i++)
+ ReleaseVariableStats(vardata[i]);
+
+ return true;
+}
/****************************************************************************
* ROUTINES TO COMPUTE SELECTIVITIES
@@ -103,6 +430,7 @@ clauselist_selectivity(PlannerInfo *root,
SpecialJoinInfo *sjinfo)
{
Selectivity s1 = 1.0;
+ Selectivity s2;
RelOptInfo *rel;
Bitmapset *estimatedclauses = NULL;
RangeQueryClause *rqlist = NULL;
@@ -141,108 +469,120 @@ clauselist_selectivity(PlannerInfo *root,
}
/*
- * Apply normal selectivity estimates for remaining clauses. We'll be
- * careful to skip any clauses which were already estimated above.
- *
- * Anything that doesn't look like a potential rangequery clause gets
- * multiplied into s1 and forgotten. Anything that does gets inserted into
- * an rqlist entry.
+ * Check if join conjuncts corresponds to some compound indexes on left and
+ * right joined relations. In this case selectivity of join can be
+ * calculated based on statistic of this compound indexes.
*/
- listidx = -1;
- foreach(l, clauses)
+ if (use_multicolumn_statistic(root, clauses, varRelid, jointype, sjinfo, &s2))
+ {
+ s1 *= s2;
+ }
+ else
{
- Node *clause = (Node *) lfirst(l);
- RestrictInfo *rinfo;
- Selectivity s2;
-
- listidx++;
-
- /*
- * Skip this clause if it's already been estimated by some other
- * statistics above.
- */
- if (bms_is_member(listidx, estimatedclauses))
- continue;
-
- /* Always compute the selectivity using clause_selectivity */
- s2 = clause_selectivity(root, clause, varRelid, jointype, sjinfo);
-
/*
- * Check for being passed a RestrictInfo.
+ * Apply normal selectivity estimates for remaining clauses. We'll be
+ * careful to skip any clauses which were already estimated above.
*
- * If it's a pseudoconstant RestrictInfo, then s2 is either 1.0 or
- * 0.0; just use that rather than looking for range pairs.
+ * Anything that doesn't look like a potential rangequery clause gets
+ * multiplied into s1 and forgotten. Anything that does gets inserted
+ * into an rqlist entry.
*/
- if (IsA(clause, RestrictInfo))
+
+ listidx = -1;
+ foreach(l, clauses)
{
- rinfo = (RestrictInfo *) clause;
- if (rinfo->pseudoconstant)
- {
- s1 = s1 * s2;
+ Node *clause = (Node *) lfirst(l);
+ RestrictInfo *rinfo;
+
+ listidx++;
+
+ /*
+ * Skip this clause if it's already been estimated by some other
+ * statistics above.
+ */
+ if (bms_is_member(listidx, estimatedclauses))
continue;
- }
- clause = (Node *) rinfo->clause;
- }
- else
- rinfo = NULL;
- /*
- * See if it looks like a restriction clause with a pseudoconstant on
- * one side. (Anything more complicated than that might not behave in
- * the simple way we are expecting.) Most of the tests here can be
- * done more efficiently with rinfo than without.
- */
- if (is_opclause(clause) && list_length(((OpExpr *) clause)->args) == 2)
- {
- OpExpr *expr = (OpExpr *) clause;
- bool varonleft = true;
- bool ok;
+ /* Always compute the selectivity using clause_selectivity */
+ s2 = clause_selectivity(root, clause, varRelid, jointype, sjinfo);
- if (rinfo)
+ /*
+ * Check for being passed a RestrictInfo.
+ *
+ * If it's a pseudoconstant RestrictInfo, then s2 is either 1.0 or
+ * 0.0; just use that rather than looking for range pairs.
+ */
+ if (IsA(clause, RestrictInfo))
{
- ok = (bms_membership(rinfo->clause_relids) == BMS_SINGLETON) &&
- (is_pseudo_constant_clause_relids(lsecond(expr->args),
- rinfo->right_relids) ||
- (varonleft = false,
- is_pseudo_constant_clause_relids(linitial(expr->args),
- rinfo->left_relids)));
+ rinfo = (RestrictInfo *) clause;
+ if (rinfo->pseudoconstant)
+ {
+ s1 = s1 * s2;
+ continue;
+ }
+ clause = (Node *) rinfo->clause;
}
else
- {
- ok = (NumRelids(clause) == 1) &&
- (is_pseudo_constant_clause(lsecond(expr->args)) ||
- (varonleft = false,
- is_pseudo_constant_clause(linitial(expr->args))));
- }
+ rinfo = NULL;
- if (ok)
+ /*
+ * See if it looks like a restriction clause with a pseudoconstant
+ * on one side. (Anything more complicated than that might not
+ * behave in the simple way we are expecting.) Most of the tests
+ * here can be done more efficiently with rinfo than without.
+ */
+ if (is_opclause(clause) && list_length(((OpExpr *) clause)->args) == 2)
{
- /*
- * If it's not a "<" or ">" operator, just merge the
- * selectivity in generically. But if it's the right oprrest,
- * add the clause to rqlist for later processing.
- */
- switch (get_oprrest(expr->opno))
+ OpExpr *expr = (OpExpr *) clause;
+ bool varonleft = true;
+ bool ok;
+
+ if (rinfo)
{
- case F_SCALARLTSEL:
- addRangeClause(&rqlist, clause,
- varonleft, true, s2);
- break;
- case F_SCALARGTSEL:
- addRangeClause(&rqlist, clause,
- varonleft, false, s2);
+ ok = (bms_membership(rinfo->clause_relids) == BMS_SINGLETON) &&
+ (is_pseudo_constant_clause_relids(lsecond(expr->args),
+ rinfo->right_relids) ||
+ (varonleft = false,
+ is_pseudo_constant_clause_relids(linitial(expr->args),
+ rinfo->left_relids)));
+ }
+ else
+ {
+ ok = (NumRelids(clause) == 1) &&
+ (is_pseudo_constant_clause(lsecond(expr->args)) ||
+ (varonleft = false,
+ is_pseudo_constant_clause(linitial(expr->args))));
+ }
+
+ if (ok)
+ {
+ /*
+ * If it's not a "<"/"<="/">"/">=" operator, just merge the
+ * selectivity in generically. But if it's the right
+ * oprrest, add the clause to rqlist for later processing.
+ */
+ switch (get_oprrest(expr->opno))
+ {
+ case F_SCALARLTSEL:
+ addRangeClause(&rqlist, clause,
+ varonleft, true, s2);
break;
- default:
- /* Just merge the selectivity in generically */
- s1 = s1 * s2;
+ case F_SCALARGTSEL:
+ addRangeClause(&rqlist, clause,
+ varonleft, false, s2);
break;
+ default:
+ /* Just merge the selectivity in generically */
+ s1 = s1 * s2;
+ break;
+ }
+ continue; /* drop to loop bottom */
}
- continue; /* drop to loop bottom */
}
- }
- /* Not the right form, so treat it generically. */
- s1 = s1 * s2;
+ /* Not the right form, so treat it generically. */
+ s1 = s1 * s2;
+ }
}
/*
@@ -485,51 +825,6 @@ bms_is_subset_singleton(const Bitmapset *s, int x)
return false;
}
-/*
- * treat_as_join_clause -
- * Decide whether an operator clause is to be handled by the
- * restriction or join estimator. Subroutine for clause_selectivity().
- */
-static inline bool
-treat_as_join_clause(Node *clause, RestrictInfo *rinfo,
- int varRelid, SpecialJoinInfo *sjinfo)
-{
- if (varRelid != 0)
- {
- /*
- * Caller is forcing restriction mode (eg, because we are examining an
- * inner indexscan qual).
- */
- return false;
- }
- else if (sjinfo == NULL)
- {
- /*
- * It must be a restriction clause, since it's being evaluated at a
- * scan node.
- */
- return false;
- }
- else
- {
- /*
- * Otherwise, it's a join if there's more than one relation used. We
- * can optimize this calculation if an rinfo was passed.
- *
- * XXX Since we know the clause is being evaluated at a join, the
- * only way it could be single-relation is if it was delayed by outer
- * joins. Although we can make use of the restriction qual estimators
- * anyway, it seems likely that we ought to account for the
- * probability of injected nulls somehow.
- */
- if (rinfo)
- return (bms_membership(rinfo->clause_relids) == BMS_MULTIPLE);
- else
- return (NumRelids(clause) > 1);
- }
-}
-
-
/*
* clause_selectivity -
* Compute the selectivity of a general boolean expression clause.
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 3d633de8ff..2e570f9d8b 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -297,12 +297,21 @@ static double
var_eq_const(VariableStatData *vardata, Oid operator,
Datum constval, bool constisnull,
bool varonleft, bool negate)
+{
+ return eqconst_selectivity(operator, vardata, constval, constisnull, varonleft, negate);
+}
+
+
+Selectivity
+eqconst_selectivity(Oid operator,
+ VariableStatData *vardata,
+ Datum constval, bool constisnull,
+ bool varonleft, bool negate)
{
double selec;
double nullfrac = 0.0;
bool isdefault;
Oid opfuncoid;
-
/*
* If the constant is NULL, assume operator is strict and return zero, ie,
* operator will never return TRUE. (It's zero even for a negator op.)
@@ -2208,21 +2217,36 @@ eqjoinsel(PG_FUNCTION_ARGS)
JoinType jointype = (JoinType) PG_GETARG_INT16(3);
#endif
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4);
- double selec;
VariableStatData vardata1;
VariableStatData vardata2;
bool join_is_reversed;
- RelOptInfo *inner_rel;
+ double selec;
get_join_variables(root, args, sjinfo,
&vardata1, &vardata2, &join_is_reversed);
+ selec = join_is_reversed
+ ? eqjoin_selectivity(root, operator, &vardata2, &vardata1, sjinfo)
+ : eqjoin_selectivity(root, operator, &vardata1, &vardata2, sjinfo);
+
+ ReleaseVariableStats(vardata1);
+ ReleaseVariableStats(vardata2);
+
+ PG_RETURN_FLOAT8((float8)selec);
+}
+
+Selectivity
+eqjoin_selectivity(PlannerInfo *root, Oid operator, VariableStatData* vardata1, VariableStatData* vardata2, SpecialJoinInfo *sjinfo)
+{
+ Selectivity selec;
+ RelOptInfo *inner_rel;
+
switch (sjinfo->jointype)
{
case JOIN_INNER:
case JOIN_LEFT:
case JOIN_FULL:
- selec = eqjoinsel_inner(operator, &vardata1, &vardata2);
+ selec = eqjoinsel_inner(operator, vardata1, vardata2);
break;
case JOIN_SEMI:
case JOIN_ANTI:
@@ -2235,13 +2259,7 @@ eqjoinsel(PG_FUNCTION_ARGS)
*/
inner_rel = find_join_input_rel(root, sjinfo->min_righthand);
- if (!join_is_reversed)
- selec = eqjoinsel_semi(operator, &vardata1, &vardata2,
- inner_rel);
- else
- selec = eqjoinsel_semi(get_commutator(operator),
- &vardata2, &vardata1,
- inner_rel);
+ selec = eqjoinsel_semi(operator, vardata1, vardata2, inner_rel);
break;
default:
/* other values not expected here */
@@ -2251,12 +2269,9 @@ eqjoinsel(PG_FUNCTION_ARGS)
break;
}
- ReleaseVariableStats(vardata1);
- ReleaseVariableStats(vardata2);
-
CLAMP_PROBABILITY(selec);
- PG_RETURN_FLOAT8((float8) selec);
+ return selec;
}
/*
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index a7b07827e0..7bf031ce60 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -180,6 +180,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
/* Otherwise we need the pg_proc entry */
procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId));
+ Assert(procedureTuple);
if (!HeapTupleIsValid(procedureTuple))
elog(ERROR, "cache lookup failed for function %u", functionId);
procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 57acc1932f..b511423bca 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1146,6 +1146,25 @@ repairDependencyLoop(DumpableObject **loop,
return;
}
+ /* Indirect loop between index and its generated type.
+ * Originally types were generated only for tables,
+ * but for better estimation of multicolumn join selectivity
+ * types are also generated for compound indexes.
+ * But indexes and tables are assigned difference priorities
+ * DO_INDEX=28, DO_TABLE=18 which are on different sides of
+ * DO_PRE_DATA_BOUNDARY=26. The trick with introducing DO_DUMMY_TYPE=19
+ * done in selectDumpableType doesn't work for types generated for indexes
+ * and we got cyclic dependency here. So we have to explicitly break such loop
+ * here to avoid circular constrains warning
+ */
+ if (nLoop >= 2 &&
+ loop[0]->objType == DO_DUMMY_TYPE &&
+ loop[1]->objType == DO_INDEX)
+ {
+ removeObjectDependency(loop[0], loop[1]->dumpId);
+ return;
+ }
+
/* Indirect loop involving domain and CHECK constraint */
if (nLoop > 2)
{
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index c7fdd540e8..0d20ef3561 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -221,5 +221,14 @@ extern Selectivity scalararraysel_containment(PlannerInfo *root,
Node *leftop, Node *rightop,
Oid elemtype, bool isEquality, bool useOr,
int varRelid);
+extern Selectivity
+eqjoin_selectivity(PlannerInfo *root, Oid operator, VariableStatData* vardata1, VariableStatData* vardata2, SpecialJoinInfo *sjinfo);
+
+
+extern Selectivity
+eqconst_selectivity(Oid operator,
+ VariableStatData *vardata,
+ Datum constval, bool constisnull,
+ bool varonleft, bool negate);
#endif /* SELFUNCS_H */
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index f60f76756a..e5e2a69264 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2935,11 +2935,13 @@ RESET enable_indexonlyscan;
--
explain (costs off)
select * from tenk1 where (thousand, tenthous) in ((1,1001), (null,null));
- QUERY PLAN
-------------------------------------------------------
- Index Scan using tenk1_thous_tenthous on tenk1
- Index Cond: ((thousand = 1) AND (tenthous = 1001))
-(2 rows)
+ QUERY PLAN
+------------------------------------------------------------
+ Bitmap Heap Scan on tenk1
+ Recheck Cond: ((thousand = 1) AND (tenthous = 1001))
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: ((thousand = 1) AND (tenthous = 1001))
+(4 rows)
--
-- Check matching of boolean index columns to WHERE conditions and sort keys
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 054a381dad..a4b0ce6dd8 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -445,19 +445,24 @@ INSERT INTO functional_dependencies (a, b, c, filler1)
ANALYZE functional_dependencies;
EXPLAIN (COSTS OFF)
SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1';
- QUERY PLAN
------------------------------------------------------------
- Index Scan using fdeps_abc_idx on functional_dependencies
- Index Cond: ((a = 1) AND (b = '1'::text))
-(2 rows)
+ QUERY PLAN
+---------------------------------------------------
+ Bitmap Heap Scan on functional_dependencies
+ Recheck Cond: ((a = 1) AND (b = '1'::text))
+ -> Bitmap Index Scan on fdeps_abc_idx
+ Index Cond: ((a = 1) AND (b = '1'::text))
+(4 rows)
EXPLAIN (COSTS OFF)
SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1;
- QUERY PLAN
------------------------------------------------------------
- Index Scan using fdeps_abc_idx on functional_dependencies
- Index Cond: ((a = 1) AND (b = '1'::text) AND (c = 1))
-(2 rows)
+ QUERY PLAN
+---------------------------------------------------
+ Bitmap Heap Scan on functional_dependencies
+ Recheck Cond: ((a = 1) AND (b = '1'::text))
+ Filter: (c = 1)
+ -> Bitmap Index Scan on fdeps_ab_idx
+ Index Cond: ((a = 1) AND (b = '1'::text))
+(5 rows)
-- create statistics
CREATE STATISTICS func_deps_stat (dependencies) ON a, b, c FROM functional_dependencies;