diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 1f40b7c..5fe1710 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -339,8 +339,6 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (attr1->atttypid != attr2->atttypid) return false; - if (attr1->attstattarget != attr2->attstattarget) - return false; if (attr1->attlen != attr2->attlen) return false; if (attr1->attndims != attr2->attndims) @@ -473,7 +471,6 @@ TupleDescInitEntry(TupleDesc desc, else MemSet(NameStr(att->attname), 0, NAMEDATALEN); - att->attstattarget = -1; att->attcacheoff = -1; att->atttypmod = typmod; diff --git a/src/backend/access/nbtree/nbtcompare.c b/src/backend/access/nbtree/nbtcompare.c index fedde93..c5a9474 100644 --- a/src/backend/access/nbtree/nbtcompare.c +++ b/src/backend/access/nbtree/nbtcompare.c @@ -308,6 +308,30 @@ btoidvectorcmp(PG_FUNCTION_ARGS) } Datum +btint2vectorcmp(PG_FUNCTION_ARGS) +{ + int2vector *a = (int2vector *) PG_GETARG_POINTER(0); + int2vector *b = (int2vector *) PG_GETARG_POINTER(1); + int i; + + /* We arbitrarily choose to sort first by vector length */ + if (a->dim1 != b->dim1) + PG_RETURN_INT32(a->dim1 - b->dim1); + + for (i = 0; i < a->dim1; i++) + { + if (a->values[i] != b->values[i]) + { + if (a->values[i] > b->values[i]) + PG_RETURN_INT32(1); + else + PG_RETURN_INT32(-1); + } + } + PG_RETURN_INT32(0); +} + +Datum btcharcmp(PG_FUNCTION_ARGS) { char a = PG_GETARG_CHAR(0); diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index e3ae92d..bde0285 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -737,7 +737,6 @@ DefineAttr(char *name, char *type, int attnum) attrtypes[attnum]->attndims = 0; } - attrtypes[attnum]->attstattarget = -1; attrtypes[attnum]->attcacheoff = -1; attrtypes[attnum]->atttypmod = -1; attrtypes[attnum]->attislocal = true; diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index a8653cd..e3b17e8 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -126,37 +126,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum); */ static FormData_pg_attribute a1 = { - 0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData), + 0, {"ctid"}, TIDOID, sizeof(ItemPointerData), SelfItemPointerAttributeNumber, 0, -1, -1, false, 'p', 's', true, false, false, true, 0 }; static FormData_pg_attribute a2 = { - 0, {"oid"}, OIDOID, 0, sizeof(Oid), + 0, {"oid"}, OIDOID, sizeof(Oid), ObjectIdAttributeNumber, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }; static FormData_pg_attribute a3 = { - 0, {"xmin"}, XIDOID, 0, sizeof(TransactionId), + 0, {"xmin"}, XIDOID, sizeof(TransactionId), MinTransactionIdAttributeNumber, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }; static FormData_pg_attribute a4 = { - 0, {"cmin"}, CIDOID, 0, sizeof(CommandId), + 0, {"cmin"}, CIDOID, sizeof(CommandId), MinCommandIdAttributeNumber, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }; static FormData_pg_attribute a5 = { - 0, {"xmax"}, XIDOID, 0, sizeof(TransactionId), + 0, {"xmax"}, XIDOID, sizeof(TransactionId), MaxTransactionIdAttributeNumber, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }; static FormData_pg_attribute a6 = { - 0, {"cmax"}, CIDOID, 0, sizeof(CommandId), + 0, {"cmax"}, CIDOID, sizeof(CommandId), MaxCommandIdAttributeNumber, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }; @@ -168,7 +168,7 @@ static FormData_pg_attribute a6 = { * used in SQL. */ static FormData_pg_attribute a7 = { - 0, {"tableoid"}, OIDOID, 0, sizeof(Oid), + 0, {"tableoid"}, OIDOID, sizeof(Oid), TableOidAttributeNumber, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }; @@ -595,7 +595,6 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid); values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname); values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid); - values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget); values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen); values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum); values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims); @@ -667,12 +666,13 @@ AddNewAttributeTuples(Oid new_rel_oid, attr = tupdesc->attrs[i]; /* Fill in the correct relation OID */ attr->attrelid = new_rel_oid; - /* Make sure these are OK, too */ - attr->attstattarget = -1; + /* Make sure this is OK, too */ attr->attcacheoff = -1; InsertPgAttributeTuple(rel, attr, indstate); + AddStatistics(new_rel_oid, &attr->attnum, 1, (oidinhcount > 0), -1); + /* Add dependency info */ myself.classId = RelationRelationId; myself.objectId = new_rel_oid; @@ -1490,9 +1490,6 @@ RemoveAttributeById(Oid relid, AttrNumber attnum) /* Remove any NOT NULL constraint the column may have */ attStruct->attnotnull = false; - /* We don't want to keep stats for it anymore */ - attStruct->attstattarget = 0; - /* * Change the column name to something that isn't likely to conflict */ @@ -1514,8 +1511,9 @@ RemoveAttributeById(Oid relid, AttrNumber attnum) heap_close(attr_rel, RowExclusiveLock); + /* Only drop pg_statistic entries for non system columns. */ if (attnum > 0) - RemoveStatistics(relid, attnum); + RemoveStatistics(relid, &attnum, 1); relation_close(rel, NoLock); } @@ -1740,7 +1738,7 @@ heap_drop_with_catalog(Oid relid) /* * delete statistics */ - RemoveStatistics(relid, 0); + RemoveStatistics(relid, NULL, 0); /* * delete attribute tuples @@ -2542,19 +2540,214 @@ cookConstraint(ParseState *pstate, /* - * RemoveStatistics --- remove entries in pg_statistic for a rel or column + * AddStatistics --- add an entry in pg_statistic + * + * attnums - an ordered array of AttrNumbers + * n_attnum - number of elements in the array + * statistics_target - the sampling size for this statistics + * + * Entries in pg_statistic are used by the planner to collect selectivity values. + * This function is called when a new relation is created or a new column is added + * to a relation. It is therefore ensured that every column has an entry during the + * lifetime of the relation since its creation. There is one exception from under + * this rule: thus function is a no-op during bootstrapping to avoid a catch-22 + * situation where a pg_statistic entry would be created when pg_statistic itself + * doesn't exist yet. pg_statistic entries for system tables will be created by + * ANALYZE as before. The entry is created as invalid (stavalid == false) and + * the histogram columns are NULLs. This will also be fixed by ANALYZE. + */ +void +AddStatistics(Oid relid, AttrNumber *attnums, int n_attnums, bool inherited, int statistics_target) +{ + Relation rel; + ScanKeyData scanKey[2]; + SysScanDesc scan; + int2vector *attnumvector; + HeapTuple tuple; + TupleDesc tupDesc; + Datum values[Natts_pg_statistic]; + bool nulls[Natts_pg_statistic]; + int i, j; + + if (IsBootstrapProcessingMode()) + return; + + Assert(attnums != NULL); + Assert(n_attnums > 0); + + attnumvector = buildint2vector(attnums, n_attnums); + + rel = heap_open(StatisticRelationId, RowExclusiveLock); + + ScanKeyInit(&scanKey[0], + Anum_pg_statistic_starelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + ScanKeyInit(&scanKey[1], + Anum_pg_statistic_staattnums, + BTEqualStrategyNumber, F_ARRAY_EQ, + PointerGetDatum(attnumvector)); + + scan = systable_beginscan(rel, StatisticRelidAttnumsInhIndexId, true, + SnapshotNow, 2, scanKey); + + tuple = systable_getnext(scan); + if (HeapTupleIsValid(tuple)) + { + systable_endscan(scan); + elog(ERROR, "pg_statistic entry already exists for this table and set of columns"); + } + + systable_endscan(scan); + + for (i = 0; i < Natts_pg_statistic; i++) + nulls[i] = true; + + i = 0; + values[i] = ObjectIdGetDatum(relid); nulls[i++] = false; /* starelid */ + values[i] = BoolGetDatum(inherited); nulls[i++] = false; /* stainherit */ + values[i] = BoolGetDatum(false); nulls[i++] = false; /* stavalid */ + values[i] = Int32GetDatum(statistics_target); nulls[i++] = false; /* statarget */ + values[i] = Float4GetDatum(0); nulls[i++] = false; /* stanullfrac */ + values[i] = Int32GetDatum(0); nulls[i++] = false; /* stawidth */ + values[i] = Float4GetDatum(0); nulls[i++] = false; /* stadistinct */ + for (j = 0; j < STATISTIC_NUM_SLOTS; j++) + { + values[i] = Int16GetDatum(0); nulls[i++] = false; /* stakindN */ + } + for (j = 0; j < STATISTIC_NUM_SLOTS; j++) + { + values[i] = ObjectIdGetDatum(0); nulls[i++] = false; /* staopN */ + } + values[i] = PointerGetDatum(attnumvector); nulls[i++] = false; /* stainherit */ + + tupDesc = RelationGetDescr(rel); + + tuple = heap_form_tuple(tupDesc, values, nulls); + + simple_heap_insert(rel, tuple); + + CatalogUpdateIndexes(rel, tuple); + + pfree(attnumvector); + + relation_close(rel, RowExclusiveLock); +} + +typedef struct invalidate_stats { + HeapTuple tuple; + struct invalidate_stats *next; +} invalidate_stats; + +/* + * InvalidateStatistics --- invalidate all pg_statistic entries of this attnum + * + * We need to collect copies of tuples that need invalidating in order not to + * conflict with the system table scan. + */ +void +InvalidateStatistics(Oid relid, AttrNumber attnum) +{ + Relation rel; + ScanKeyData scanKey; + SysScanDesc scan; + HeapTuple tuple; + TupleDesc tupDesc; + Form_pg_statistic stattuple; + invalidate_stats *tupptr; + invalidate_stats *tupptr_next; + int i; + + if (IsBootstrapProcessingMode()) + return; + + rel = heap_open(StatisticRelationId, RowExclusiveLock); + tupDesc = RelationGetDescr(rel); + + ScanKeyInit(&scanKey, + Anum_pg_statistic_starelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + scan = systable_beginscan(rel, StatisticRelidAttnumsInhIndexId, true, + SnapshotNow, 1, &scanKey); + + + tupptr = tupptr_next = NULL; + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + bool isnull; + Datum attnvec; + int2vector *attnumvector; + + attnvec = heap_getattr(tuple, Anum_pg_statistic_staattnums, tupDesc, &isnull); + + Assert(!isnull); + + attnumvector = (int2vector *) DatumGetPointer(attnvec); + + for (i = 0; i < attnumvector->dim1; i++) + { + if (attnumvector->values[i] == attnum) + { + invalidate_stats *tmp; + + + tmp = palloc(sizeof(invalidate_stats)); + tmp->tuple = heap_copytuple(tuple); + tmp->next = NULL; + + if (tupptr == NULL) + tupptr = tupptr_next = tmp; + else + { + tupptr_next->next = tmp; + tupptr_next = tmp; + } + + break; /* find next tuple */ + } + } + } + + systable_endscan(scan); + + while (tupptr) + { + stattuple = (Form_pg_statistic) GETSTRUCT(tupptr->tuple); + + stattuple->stavalid = false; + + simple_heap_update(rel, &tupptr->tuple->t_self, tupptr->tuple); + + CatalogUpdateIndexes(rel, tupptr->tuple); + + tupptr_next = tupptr->next; + + heap_freetuple(tupptr->tuple); + pfree(tupptr); + + tupptr = tupptr_next; + } + + relation_close(rel, RowExclusiveLock); +} + +/* + * RemoveStatistics --- remove entries in pg_statistic for a rel's set of columns * - * If attnum is zero, remove all entries for rel; else remove only the one(s) - * for that column. + * If attnums is NULL, remove all entries for rel; else remove only the one + * for that set of column(s). */ void -RemoveStatistics(Oid relid, AttrNumber attnum) +RemoveStatistics(Oid relid, AttrNumber *attnums, int n_attnums) { Relation pgstatistic; SysScanDesc scan; ScanKeyData key[2]; int nkeys; HeapTuple tuple; + int2vector *attnumvector = NULL; pgstatistic = heap_open(StatisticRelationId, RowExclusiveLock); @@ -2563,18 +2756,19 @@ RemoveStatistics(Oid relid, AttrNumber attnum) BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid)); - if (attnum == 0) + if (attnums == NULL) nkeys = 1; else { + attnumvector = buildint2vector(attnums, n_attnums); ScanKeyInit(&key[1], - Anum_pg_statistic_staattnum, - BTEqualStrategyNumber, F_INT2EQ, - Int16GetDatum(attnum)); + Anum_pg_statistic_staattnums, + BTEqualStrategyNumber, F_INT2VECTOREQ, + PointerGetDatum(attnumvector)); nkeys = 2; } - scan = systable_beginscan(pgstatistic, StatisticRelidAttnumInhIndexId, true, + scan = systable_beginscan(pgstatistic, StatisticRelidAttnumsInhIndexId, true, SnapshotNow, nkeys, key); /* we must loop even when attnum != 0, in case of inherited stats */ @@ -2583,6 +2777,9 @@ RemoveStatistics(Oid relid, AttrNumber attnum) systable_endscan(scan); + if (attnumvector) + pfree(attnumvector); + heap_close(pgstatistic, RowExclusiveLock); } diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index bfbe642..74cfa58 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -342,7 +342,6 @@ ConstructTupleDescriptor(Relation heapRelation, */ to->attnum = i + 1; - to->attstattarget = -1; to->attcacheoff = -1; to->attnotnull = false; to->atthasdef = false; @@ -380,7 +379,6 @@ ConstructTupleDescriptor(Relation heapRelation, to->attbyval = typeTup->typbyval; to->attstorage = typeTup->typstorage; to->attalign = typeTup->typalign; - to->attstattarget = -1; to->attcacheoff = -1; to->atttypmod = -1; to->attislocal = true; @@ -1353,7 +1351,7 @@ index_drop(Oid indexId) * them. */ if (hasexprs) - RemoveStatistics(indexId, 0); + RemoveStatistics(indexId, NULL, 0); /* * fix ATTRIBUTE relation diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index ab594eb..62c4ce3 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -166,7 +166,7 @@ CREATE VIEW pg_stats AS WHEN stakind5 = 5 THEN stanumbers5 END AS elem_count_histogram FROM pg_statistic s JOIN pg_class c ON (c.oid = s.starelid) - JOIN pg_attribute a ON (c.oid = attrelid AND attnum = s.staattnum) + JOIN pg_attribute a ON (c.oid = attrelid AND array_length(s.staattnums, 1) = 1 AND attnum = s.staattnums[0]) LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace) WHERE NOT attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'); diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 9cd6e67..1ac9b5a 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -21,12 +21,15 @@ #include "access/tuptoaster.h" #include "access/visibilitymap.h" #include "access/xact.h" +#include "catalog/heap.h" #include "catalog/index.h" #include "catalog/indexing.h" +#include "catalog/namespace.h" #include "catalog/pg_collation.h" #include "catalog/pg_inherits_fn.h" #include "catalog/pg_namespace.h" #include "commands/dbcommands.h" +#include "commands/defrem.h" #include "commands/tablecmds.h" #include "commands/vacuum.h" #include "executor/executor.h" @@ -42,7 +45,9 @@ #include "storage/procarray.h" #include "utils/acl.h" #include "utils/attoptcache.h" +#include "utils/builtins.h" #include "utils/datum.h" +#include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -69,8 +74,10 @@ typedef struct AnlIndexData { IndexInfo *indexInfo; /* BuildIndexInfo result */ double tupleFract; /* fraction of rows for partial index */ - VacAttrStats **vacattrstats; /* index attrs to analyze */ - int attr_cnt; + VacStats **vacstats; /* index attrs to analyze */ + int stats_cnt; /* this many stats we need to compute - single and cross-column stats included */ + int attr_cnt; /* the relation has attr_cnt number of columns */ + bool has_xcol_stats; } AnlIndexData; @@ -94,8 +101,8 @@ static void compute_index_stats(Relation onerel, double totalrows, AnlIndexData *indexdata, int nindexes, HeapTuple *rows, int numrows, MemoryContext col_context); -static VacAttrStats *examine_attribute(Relation onerel, int attnum, - Node *index_expr); +static VacStats *examine_attribute(Relation onerel, int2vector *attnums, bool inh, + Node **index_expr); static int acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows, double *totalrows, double *totaldeadrows); static double random_fract(void); @@ -106,10 +113,16 @@ static int acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows, double *totalrows, double *totaldeadrows); static void update_attstats(Oid relid, bool inh, - int natts, VacAttrStats **vacattrstats); -static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); -static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); + int natts, VacStats **vacstats); +static Datum std_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum, + bool *isNull); +static Datum ind_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum, + bool *isNull); +static int ordered_findval(VacStatsP stats, int rownum, int index, AnalyzeAttrFetchFunc fetchfunc, void *arg); +static int unordered_findval(VacStatsP stats, int rownum, int index, AnalyzeAttrFetchFunc fetchfunc, void *arg); +static void compute_cross_column_stats(VacStatsP stats, AnalyzeAttrFetchFunc fetchfunc, + int numrows); /* * analyze_rel() -- analyze one relation @@ -252,20 +265,78 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) LWLockRelease(ProcArrayLock); } +static int +get_extrastats_count(Oid relid, int2vector ***out_attnumvectors) +{ + Relation pgstatistic; + TupleDesc tupDesc; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tuple; + int count = 0; + bool isnull; + int2vector *attnumvector = NULL; + int2vector **attnumvectors = NULL; + + pgstatistic = heap_open(StatisticRelationId, RowExclusiveLock); + tupDesc = RelationGetDescr(pgstatistic); + + ScanKeyInit(&key, + Anum_pg_statistic_starelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + scan = systable_beginscan(pgstatistic, StatisticRelidAttnumsInhIndexId, true, + SnapshotNow, 1, &key); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Datum attnvec = heap_getattr(tuple, Anum_pg_statistic_staattnums, tupDesc, &isnull); + + Assert(!isnull); + + attnumvector = (int2vector *) DatumGetPointer(attnvec); + + if (attnumvector->dim1 > 1) + { + count++; + if (out_attnumvectors) + { + if (attnumvectors == NULL) + attnumvectors = palloc(count * sizeof(int2vector *)); + else + attnumvectors = repalloc(attnumvectors, count * sizeof(int2vector *)); + attnumvectors[count - 1] = (int2vector *)datumCopy(attnvec, false, -1); + } + } + } + + systable_endscan(scan); + + heap_close(pgstatistic, RowExclusiveLock); + + if (out_attnumvectors) + *out_attnumvectors = attnumvectors; + + return count; +} + /* * do_analyze_rel() -- analyze one relation, recursively or not */ static void do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) { - int attr_cnt, + int stats_cnt, + extrastats_cnt, tcnt, i, ind; Relation *Irel; int nindexes; bool hasindex; - VacAttrStats **vacattrstats; + VacStats **vacstats; + int2vector **extrastats_attnums; AnlIndexData *indexdata; int targrows, numrows; @@ -328,38 +399,75 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) { ListCell *le; - vacattrstats = (VacAttrStats **) palloc(list_length(vacstmt->va_cols) * - sizeof(VacAttrStats *)); + vacstats = (VacStats **) palloc(list_length(vacstmt->va_cols) * + sizeof(VacStats *)); tcnt = 0; foreach(le, vacstmt->va_cols) { char *col = strVal(lfirst(le)); + AttrNumber attnum; + int2vector *attnums; - i = attnameAttNum(onerel, col, false); - if (i == InvalidAttrNumber) + attnum = attnameAttNum(onerel, col, false); + if (attnum == InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\" does not exist", col, RelationGetRelationName(onerel)))); - vacattrstats[tcnt] = examine_attribute(onerel, i, NULL); - if (vacattrstats[tcnt] != NULL) + attnums = buildint2vector(&attnum, 1); + vacstats[tcnt] = examine_attribute(onerel, attnums, inh, NULL); + if (vacstats[tcnt] != NULL) tcnt++; + else + pfree(attnums); } - attr_cnt = tcnt; + stats_cnt = tcnt; + extrastats_cnt = 0; } else { - attr_cnt = onerel->rd_att->natts; - vacattrstats = (VacAttrStats **) - palloc(attr_cnt * sizeof(VacAttrStats *)); + /* + * Process all attributes of the relation unconditionally + * as system tables may not have pg_statistic entries at + * this point, they will be created in update_attstats(). + */ + stats_cnt = onerel->rd_att->natts; + + extrastats_cnt = get_extrastats_count(onerel->rd_id, &extrastats_attnums); + + vacstats = (VacStats **) + palloc((stats_cnt + extrastats_cnt) * sizeof(VacStats *)); tcnt = 0; - for (i = 1; i <= attr_cnt; i++) + for (i = 1; i <= stats_cnt; i++) { - vacattrstats[tcnt] = examine_attribute(onerel, i, NULL); - if (vacattrstats[tcnt] != NULL) + AttrNumber attnum = i; + int2vector *attnums; + + attnums = buildint2vector(&attnum, 1); + + vacstats[tcnt] = examine_attribute(onerel, attnums, inh, NULL); + if (vacstats[tcnt] != NULL) tcnt++; + else + pfree(attnums); } - attr_cnt = tcnt; + + /* Now process the extra statistics */ + if (extrastats_attnums) + { + for (i = 0; extrastats_attnums && i < extrastats_cnt; i++) + { + vacstats[tcnt] = examine_attribute(onerel, extrastats_attnums[i], inh, NULL); + if (vacstats[tcnt] != NULL) + tcnt++; + else + pfree(extrastats_attnums[i]); + } + pfree(extrastats_attnums); + extrastats_attnums = NULL; + } + + stats_cnt = tcnt; } /* @@ -388,13 +496,20 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) thisdata->indexInfo = indexInfo = BuildIndexInfo(Irel[ind]); thisdata->tupleFract = 1.0; /* fix later if partial */ + + tcnt = 0; + extrastats_cnt = get_extrastats_count(Irel[ind]->rd_id, &extrastats_attnums); + + /* + * Process expression index single-attribute statistics. + */ if (indexInfo->ii_Expressions != NIL && vacstmt->va_cols == NIL) { ListCell *indexpr_item = list_head(indexInfo->ii_Expressions); - thisdata->vacattrstats = (VacAttrStats **) - palloc(indexInfo->ii_NumIndexAttrs * sizeof(VacAttrStats *)); - tcnt = 0; + thisdata->vacstats = (VacStats **) + palloc((indexInfo->ii_NumIndexAttrs + extrastats_cnt) * sizeof(VacStats *)); + for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) { int keycol = indexInfo->ii_KeyAttrNumbers[i]; @@ -403,19 +518,74 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) { /* Found an index expression */ Node *indexkey; + AttrNumber attnum; + int2vector *attnums; if (indexpr_item == NULL) /* shouldn't happen */ elog(ERROR, "too few entries in indexprs list"); indexkey = (Node *) lfirst(indexpr_item); indexpr_item = lnext(indexpr_item); - thisdata->vacattrstats[tcnt] = - examine_attribute(Irel[ind], i + 1, indexkey); - if (thisdata->vacattrstats[tcnt] != NULL) + + attnum = i + 1; + attnums = buildint2vector(&attnum, 1); + + thisdata->vacstats[tcnt] = + examine_attribute(Irel[ind], attnums, inh, &indexkey); + if (thisdata->vacstats[tcnt] != NULL) tcnt++; + else + pfree(attnums); } } - thisdata->attr_cnt = tcnt; + } + + /* + * Process cross-column statistics. There can only be at most one + * multi-column statistics for an index that covers every attributes. + */ + if (extrastats_attnums && vacstmt->va_cols == NIL) + { + Node *indexkeys[STATISTIC_NUM_SLOTS]; + + if (thisdata->vacstats == NULL) + thisdata->vacstats = (VacStats **) palloc(sizeof(VacStats *)); + + /* + * Don't process indexes with too many attributes. + * CREATE EXTRA STATISTICS bails out for such a case + * but better safe than sorry. + */ + if (indexInfo->ii_NumIndexAttrs <= STATISTIC_NUM_SLOTS) + { + ListCell *indexpr_item = list_head(indexInfo->ii_Expressions); + + for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) + { + int keycol = indexInfo->ii_KeyAttrNumbers[i]; + if (keycol == 0) + { + indexkeys[i] = (Node *) lfirst(indexpr_item); + indexpr_item = lnext(indexpr_item); + } + else + indexkeys[i] = NULL; + } + + thisdata->vacstats[tcnt] = examine_attribute(onerel, extrastats_attnums[0], inh, indexkeys); + if (thisdata->vacstats[tcnt] != NULL) + tcnt++; + else + pfree(extrastats_attnums[i]); + } + pfree(extrastats_attnums); + extrastats_attnums = NULL; + + thisdata->has_xcol_stats = true; + } + + thisdata->stats_cnt = tcnt; + thisdata->attr_cnt = indexInfo->ii_NumIndexAttrs; } } @@ -426,19 +596,19 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) * the target in the corner case where there are no analyzable columns.) */ targrows = 100; - for (i = 0; i < attr_cnt; i++) + for (i = 0; i < stats_cnt; i++) { - if (targrows < vacattrstats[i]->minrows) - targrows = vacattrstats[i]->minrows; + if (targrows < vacstats[i]->minrows) + targrows = vacstats[i]->minrows; } for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; - for (i = 0; i < thisdata->attr_cnt; i++) + for (i = 0; i < thisdata->stats_cnt; i++) { - if (targrows < thisdata->vacattrstats[i]->minrows) - targrows = thisdata->vacattrstats[i]->minrows; + if (targrows < thisdata->vacstats[i]->minrows) + targrows = thisdata->vacstats[i]->minrows; } } @@ -456,7 +626,7 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) /* * Compute the statistics. Temporary results during the calculations for * each column are stored in a child context. The calc routines are - * responsible to make sure that whatever they store into the VacAttrStats + * responsible to make sure that whatever they store into the VacStats * structure is allocated in anl_context. */ if (numrows > 0) @@ -471,33 +641,40 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) ALLOCSET_DEFAULT_MAXSIZE); old_context = MemoryContextSwitchTo(col_context); - for (i = 0; i < attr_cnt; i++) + for (i = 0; i < stats_cnt; i++) { - VacAttrStats *stats = vacattrstats[i]; + VacStats *stats = vacstats[i]; AttributeOpts *aopt; + int j; stats->rows = rows; stats->tupDesc = onerel->rd_att; - (*stats->compute_stats) (stats, + + for (j = 0; j < stats->attnums->dim1; j++) + { + (*stats->statfuncs[j].compute_func_ptr) (stats, + j, std_fetch_func, numrows, totalrows); + /* + * If the appropriate flavor of the n_distinct option is + * specified, override with the corresponding value. + */ + aopt = get_attribute_options(onerel->rd_id, stats->attnums->values[j]); + if (aopt != NULL) + { + float8 n_distinct = + inh ? aopt->n_distinct_inherited : aopt->n_distinct; - /* - * If the appropriate flavor of the n_distinct option is - * specified, override with the corresponding value. - */ - aopt = get_attribute_options(onerel->rd_id, stats->attr->attnum); - if (aopt != NULL) - { - float8 n_distinct; - - n_distinct = inh ? aopt->n_distinct_inherited : aopt->n_distinct; - if (n_distinct != 0.0) - stats->stadistinct = n_distinct; + if (n_distinct != 0.0) + stats->stadistinct[j] = n_distinct; + } } MemoryContextResetAndDeleteChildren(col_context); + + compute_cross_column_stats(stats, std_fetch_func, numrows); } if (hasindex) @@ -515,14 +692,17 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) * pg_statistic for columns we didn't process, we leave them alone.) */ update_attstats(RelationGetRelid(onerel), inh, - attr_cnt, vacattrstats); + stats_cnt, vacstats); for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; + for (i = 0; i < thisdata->stats_cnt; i++) + compute_cross_column_stats(thisdata->vacstats[i], ind_fetch_func, numrows); + update_attstats(RelationGetRelid(Irel[ind]), false, - thisdata->attr_cnt, thisdata->vacattrstats); + thisdata->stats_cnt, thisdata->vacstats); } } @@ -632,6 +812,8 @@ compute_index_stats(Relation onerel, double totalrows, old_context; Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; + bool typbyval[INDEX_MAX_KEYS]; + int16 typlen[INDEX_MAX_KEYS]; int ind, i; @@ -646,7 +828,7 @@ compute_index_stats(Relation onerel, double totalrows, { AnlIndexData *thisdata = &indexdata[ind]; IndexInfo *indexInfo = thisdata->indexInfo; - int attr_cnt = thisdata->attr_cnt; + int stats_cnt = thisdata->stats_cnt; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -657,11 +839,30 @@ compute_index_stats(Relation onerel, double totalrows, tcnt, rowno; double totalindexrows; + ListCell *indexpr_item = list_head(indexInfo->ii_Expressions); /* Ignore index if no columns to analyze and not partial */ - if (attr_cnt == 0 && indexInfo->ii_Predicate == NIL) + if (stats_cnt == 0 && indexInfo->ii_Predicate == NIL) continue; + for (i = 0; i < thisdata->attr_cnt; i++) + { + int keycol = indexInfo->ii_KeyAttrNumbers[i]; + + if (keycol == 0) + { + Node *indexkey = (Node *) lfirst(indexpr_item); + + get_typlenbyval(exprType(indexkey), &typlen[i], &typbyval[i]); + indexpr_item = lnext(indexpr_item); + } + else + { + typbyval[i] = onerel->rd_att->attrs[keycol - 1]->attbyval; + typlen[i] = onerel->rd_att->attrs[keycol - 1]->attlen; + } + } + /* * Need an EState for evaluation of index expressions and * partial-index predicates. Create it in the per-index context to be @@ -681,8 +882,8 @@ compute_index_stats(Relation onerel, double totalrows, estate); /* Compute and save index expression values */ - exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum)); - exprnulls = (bool *) palloc(numrows * attr_cnt * sizeof(bool)); + exprvals = (Datum *) palloc(numrows * thisdata->attr_cnt * sizeof(Datum)); + exprnulls = (bool *) palloc(numrows * thisdata->attr_cnt * sizeof(bool)); numindexrows = 0; tcnt = 0; for (rowno = 0; rowno < numrows; rowno++) @@ -706,7 +907,7 @@ compute_index_stats(Relation onerel, double totalrows, } numindexrows++; - if (attr_cnt > 0) + if (stats_cnt > 0) { /* * Evaluate the index row to compute expression values. We @@ -719,24 +920,18 @@ compute_index_stats(Relation onerel, double totalrows, isnull); /* - * Save just the columns we care about. We copy the values - * into ind_context from the estate's per-tuple context. + * We copy the values * into ind_context from the estate's per-tuple context. */ - for (i = 0; i < attr_cnt; i++) + for (i = 0; i < thisdata->attr_cnt; i++) { - VacAttrStats *stats = thisdata->vacattrstats[i]; - int attnum = stats->attr->attnum; - - if (isnull[attnum - 1]) + if (isnull[i]) { exprvals[tcnt] = (Datum) 0; exprnulls[tcnt] = true; } else { - exprvals[tcnt] = datumCopy(values[attnum - 1], - stats->attrtype->typbyval, - stats->attrtype->typlen); + exprvals[tcnt] = datumCopy(values[i], typbyval[i], typlen[i]); exprnulls[tcnt] = false; } tcnt++; @@ -757,28 +952,37 @@ compute_index_stats(Relation onerel, double totalrows, if (numindexrows > 0) { MemoryContextSwitchTo(col_context); - for (i = 0; i < attr_cnt; i++) + for (i = 0; i < stats_cnt; i++) { - VacAttrStats *stats = thisdata->vacattrstats[i]; - AttributeOpts *aopt = - get_attribute_options(stats->attr->attrelid, - stats->attr->attnum); - - stats->exprvals = exprvals + i; - stats->exprnulls = exprnulls + i; - stats->rowstride = attr_cnt; - (*stats->compute_stats) (stats, - ind_fetch_func, - numindexrows, - totalindexrows); + VacStats *stats = thisdata->vacstats[i]; + AttributeOpts *aopt; + int j; - /* - * If the n_distinct option is specified, it overrides the - * above computation. For indices, we always use just - * n_distinct, not n_distinct_inherited. - */ - if (aopt != NULL && aopt->n_distinct != 0.0) - stats->stadistinct = aopt->n_distinct; + for (j = 0; j < stats->attnums->dim1; j++) + { + if (stats->attnums->dim1 == 1) + aopt = get_attribute_options(stats->attrs[j]->attrelid, + stats->attrs[j]->attnum); + else + aopt = NULL; + + stats->exprvals = exprvals; + stats->exprnulls = exprnulls; + stats->rowstride = thisdata->attr_cnt; + (*stats->statfuncs[j].compute_func_ptr) (stats, + j, + ind_fetch_func, + numindexrows, + totalindexrows); + + /* + * If the n_distinct option is specified, it overrides the + * above computation. For indices, we always use just + * n_distinct, not n_distinct_inherited. + */ + if (aopt != NULL && aopt->n_distinct != 0.0) + stats->stadistinct[j] = aopt->n_distinct; + } MemoryContextResetAndDeleteChildren(col_context); } @@ -797,38 +1001,77 @@ compute_index_stats(Relation onerel, double totalrows, } /* - * examine_attribute -- pre-analysis of a single column + * statistics_target -- returns pg_statistic.statarget + */ +static int4 +statistics_target(Oid relid, int2vector *attnums, bool inherited) +{ + HeapTuple tuple; + int4 statarget = -1; /* default */ + + tuple = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(relid), + PointerGetDatum(attnums), + BoolGetDatum(inherited)); + if (HeapTupleIsValid(tuple)) + { + Form_pg_statistic stats = (Form_pg_statistic) GETSTRUCT(tuple); + + statarget = stats->statarget; + + ReleaseSysCache(tuple); + } + + return statarget; +} + + +/* + * examine_attribute -- pre-analysis of a single column or a set of columns * * Determine whether the column is analyzable; if so, create and initialize - * a VacAttrStats struct for it. If not, return NULL. + * a VacStats struct for it. If not, return NULL. * * If index_expr isn't NULL, then we're trying to analyze an expression index, * and index_expr is the expression tree representing the column's data. */ -static VacAttrStats * -examine_attribute(Relation onerel, int attnum, Node *index_expr) +static VacStats * +examine_attribute(Relation onerel, int2vector *attnums, bool inh, Node **index_exprs) { - Form_pg_attribute attr = onerel->rd_att->attrs[attnum - 1]; HeapTuple typtuple; - VacAttrStats *stats; - int i; + int4 statarget; + VacStats *stats; + int i, j; bool ok; - /* Never analyze dropped columns */ - if (attr->attisdropped) - return NULL; + for (i = 0; i < attnums->dim1; i++) + { + Form_pg_attribute attr = onerel->rd_att->attrs[attnums->values[i] - 1]; + + /* Never analyze dropped columns */ + if (attr->attisdropped) + return NULL; + } /* Don't analyze column if user has specified not to */ - if (attr->attstattarget == 0) + statarget = statistics_target(onerel->rd_id, attnums, inh); + if (statarget == 0) return NULL; /* - * Create the VacAttrStats struct. Note that we only have a copy of the + * Create the VacStats struct. Note that we only have a copy of the * fixed fields of the pg_attribute tuple. */ - stats = (VacAttrStats *) palloc0(sizeof(VacAttrStats)); - stats->attr = (Form_pg_attribute) palloc(ATTRIBUTE_FIXED_PART_SIZE); - memcpy(stats->attr, attr, ATTRIBUTE_FIXED_PART_SIZE); + stats = (VacStats *) palloc0(sizeof(VacStats)); + for (i = 0; i < attnums->dim1; i++) + { + Form_pg_attribute attr = onerel->rd_att->attrs[attnums->values[i] - 1]; + stats->attrs[i] = (Form_pg_attribute) palloc(ATTRIBUTE_FIXED_PART_SIZE); + memcpy(stats->attrs[i], attr, ATTRIBUTE_FIXED_PART_SIZE); + } + + stats->attnums = attnums; + stats->statarget = statarget; /* * When analyzing an expression index, believe the expression tree's type @@ -839,54 +1082,86 @@ examine_attribute(Relation onerel, int attnum, Node *index_expr) * not a problem.) It's not clear whether anyone will care about the * typmod, but we store that too just in case. */ - if (index_expr) - { - stats->attrtypid = exprType(index_expr); - stats->attrtypmod = exprTypmod(index_expr); - } - else + + for (i = 0; i < attnums->dim1; i++) { - stats->attrtypid = attr->atttypid; - stats->attrtypmod = attr->atttypmod; + if (index_exprs && index_exprs[i]) + { + stats->attrtypids[i] = exprType(index_exprs[i]); + stats->attrtypmods[i] = exprTypmod(index_exprs[i]); + } + else + { + stats->attrtypids[i] = stats->attrs[i]->atttypid; + stats->attrtypmods[i] = stats->attrs[i]->atttypmod; + } + + typtuple = SearchSysCacheCopy1(TYPEOID, + ObjectIdGetDatum(stats->attrtypids[i])); + if (!HeapTupleIsValid(typtuple)) + elog(ERROR, "cache lookup failed for type %u", stats->attrtypids[i]); + stats->attrtypes[i] = (Form_pg_type) GETSTRUCT(typtuple); } - typtuple = SearchSysCacheCopy1(TYPEOID, - ObjectIdGetDatum(stats->attrtypid)); - if (!HeapTupleIsValid(typtuple)) - elog(ERROR, "cache lookup failed for type %u", stats->attrtypid); - stats->attrtype = (Form_pg_type) GETSTRUCT(typtuple); stats->anl_context = anl_context; - stats->tupattnum = attnum; /* * The fields describing the stats->stavalues[n] element types default to * the type of the data being analyzed, but the type-specific typanalyze * function can change them if it wants to store something else. */ - for (i = 0; i < STATISTIC_NUM_SLOTS; i++) + for (i = 0; i < attnums->dim1; i++) { - stats->statypid[i] = stats->attrtypid; - stats->statyplen[i] = stats->attrtype->typlen; - stats->statypbyval[i] = stats->attrtype->typbyval; - stats->statypalign[i] = stats->attrtype->typalign; + for (j = 0; j < STATISTIC_NUM_SLOTS; j++) + { + stats->statypid[i][j] = stats->attrtypids[i]; + stats->statyplen[i][j] = stats->attrtypes[i]->typlen; + stats->statypbyval[i][j] = stats->attrtypes[i]->typbyval; + stats->statypalign[i][j] = stats->attrtypes[i]->typalign; + } + + /* + * Call the type-specific typanalyze function. If none is specified, use + * std_typanalyze(). + */ + if (OidIsValid(stats->attrtypes[i]->typanalyze)) + ok = DatumGetBool(OidFunctionCall2(stats->attrtypes[i]->typanalyze, + PointerGetDatum(stats), + Int32GetDatum(i))); + else + ok = std_typanalyze(stats, i); + + if (!ok || + stats->statfuncs[i].compute_func_ptr == NULL || + /* intentionally don't check stats->statfuncs[i].findval_func_ptr == NULL */ + stats->minrows <= 0) + { + for (; i >= 0; i--) + { + heap_freetuple((HeapTuple) stats->attrtypes[i]); + pfree(stats->attrs[i]); + } + pfree(stats); + + return NULL; + } } /* - * Call the type-specific typanalyze function. If none is specified, use - * std_typanalyze(). + * If we have a cross-column statistics, compute the cube size for + * the histogram so pg_statistic.statarget really means the number of + * cells in the multi-dimensional cube. */ - if (OidIsValid(stats->attrtype->typanalyze)) - ok = DatumGetBool(OidFunctionCall1(stats->attrtype->typanalyze, - PointerGetDatum(stats))); - else - ok = std_typanalyze(stats); - - if (!ok || stats->compute_stats == NULL || stats->minrows <= 0) + if (stats->attnums->dim1 > 1) { - heap_freetuple(typtuple); - pfree(stats->attr); - pfree(stats); - return NULL; + double x, y, n; + + y = stats->statarget; + n = stats->attnums->dim1; + x = pow(M_E, (log(y) / n)); + + /* On some systems, ceil() returns surprising result. */ + stats->statarget = trunc(x + 0.5); } return stats; @@ -1560,7 +1835,7 @@ acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows, * by taking a self-exclusive lock on the relation in analyze_rel(). */ static void -update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) +update_attstats(Oid relid, bool inh, int natts, VacStats **vacstats) { Relation sd; int attno; @@ -1572,7 +1847,7 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) for (attno = 0; attno < natts; attno++) { - VacAttrStats *stats = vacattrstats[attno]; + VacStats *stats = vacstats[attno]; HeapTuple stup, oldtup; int i, @@ -1595,26 +1870,35 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) replaces[i] = true; } + /* + * We use statarget = -1 here, so if the pg_statistic entry is new, + * we use the default_statistics_target for it. If there's a pg_statistic + * row for this statistics, we will set the "replaces" flag to false. + */ + values[Anum_pg_statistic_statarget - 1] = Int32GetDatum(-1); values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(relid); - values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(stats->attr->attnum); values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inh); - values[Anum_pg_statistic_stanullfrac - 1] = Float4GetDatum(stats->stanullfrac); - values[Anum_pg_statistic_stawidth - 1] = Int32GetDatum(stats->stawidth); - values[Anum_pg_statistic_stadistinct - 1] = Float4GetDatum(stats->stadistinct); + values[Anum_pg_statistic_stavalid - 1] = BoolGetDatum(true); + values[Anum_pg_statistic_stanullfrac - 1] = Float4GetDatum(stats->stanullfrac[0]); + values[Anum_pg_statistic_stawidth - 1] = Int32GetDatum(stats->stawidth[0]); + values[Anum_pg_statistic_stadistinct - 1] = Float4GetDatum(stats->stadistinct[0]); i = Anum_pg_statistic_stakind1 - 1; for (k = 0; k < STATISTIC_NUM_SLOTS; k++) { - values[i++] = Int16GetDatum(stats->stakind[k]); /* stakindN */ + values[i++] = Int16GetDatum(stats->stakind[0][k]); /* stakindN */ } i = Anum_pg_statistic_staop1 - 1; for (k = 0; k < STATISTIC_NUM_SLOTS; k++) { - values[i++] = ObjectIdGetDatum(stats->staop[k]); /* staopN */ + values[i++] = ObjectIdGetDatum(stats->staop[0][k]); /* staopN */ } + + values[Anum_pg_statistic_staattnums - 1] = PointerGetDatum(stats->attnums); /* staattnums */ + i = Anum_pg_statistic_stanumbers1 - 1; for (k = 0; k < STATISTIC_NUM_SLOTS; k++) { - int nnum = stats->numnumbers[k]; + int nnum = stats->numnumbers[0][k]; if (nnum > 0) { @@ -1622,7 +1906,7 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) ArrayType *arry; for (n = 0; n < nnum; n++) - numdatums[n] = Float4GetDatum(stats->stanumbers[k][n]); + numdatums[n] = Float4GetDatum(stats->stanumbers[0][k][n]); /* XXX knows more than it should about type float4: */ arry = construct_array(numdatums, nnum, FLOAT4OID, @@ -1638,16 +1922,29 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) i = Anum_pg_statistic_stavalues1 - 1; for (k = 0; k < STATISTIC_NUM_SLOTS; k++) { - if (stats->numvalues[k] > 0) + int idx1, idx2; + + if (stats->attnums->dim1 > 1 && k < stats->attnums->dim1) + { + idx1 = k; + idx2 = 0; + } + else + { + idx1 = 0; + idx2 = k; + } + + if (stats->numvalues[idx1][idx2] > 0) { ArrayType *arry; - arry = construct_array(stats->stavalues[k], - stats->numvalues[k], - stats->statypid[k], - stats->statyplen[k], - stats->statypbyval[k], - stats->statypalign[k]); + arry = construct_array(stats->stavalues[idx1][idx2], + stats->numvalues[idx1][idx2], + stats->statypid[idx1][idx2], + stats->statyplen[idx1][idx2], + stats->statypbyval[idx1][idx2], + stats->statypalign[idx1][idx2]); values[i++] = PointerGetDatum(arry); /* stavaluesN */ } else @@ -1660,11 +1957,14 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) /* Is there already a pg_statistic tuple for this attribute? */ oldtup = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(relid), - Int16GetDatum(stats->attr->attnum), + PointerGetDatum(stats->attnums), BoolGetDatum(inh)); if (HeapTupleIsValid(oldtup)) { + /* Skip replacing the statarget value, so we remember its value. */ + replaces[Anum_pg_statistic_statarget - 1] = false; + /* Yes, replace it */ stup = heap_modify_tuple(oldtup, RelationGetDescr(sd), @@ -1697,13 +1997,12 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) * and the actual storage of the sample data. */ static Datum -std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull) +std_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum, bool *isNull) { - int attnum = stats->tupattnum; HeapTuple tuple = stats->rows[rownum]; TupleDesc tupDesc = stats->tupDesc; - return heap_getattr(tuple, attnum, tupDesc, isNull); + return heap_getattr(tuple, tupattnum, tupDesc, isNull); } /* @@ -1713,12 +2012,11 @@ std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull) * just in Datum arrays. */ static Datum -ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull) +ind_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum, bool *isNull) { int i; - /* exprvals and exprnulls are already offset for proper column */ - i = rownum * stats->rowstride; + i = rownum * stats->rowstride + tupattnum - 1; *isNull = stats->exprnulls[i]; return stats->exprvals[i]; } @@ -1776,12 +2074,19 @@ typedef struct int *tupnoLink; } CompareScalarsContext; +typedef struct +{ + SortSupport ssup; + FmgrInfo cmpFn; /* FIXME: this should be somewhere else? */ +} CompareDatumsContext; -static void compute_minimal_stats(VacAttrStatsP stats, +static void compute_minimal_stats(VacStatsP stats, + int index, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows); -static void compute_scalar_stats(VacAttrStatsP stats, +static void compute_scalar_stats(VacStatsP stats, + int index, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows); @@ -1793,20 +2098,19 @@ static int compare_mcvs(const void *a, const void *b); * std_typanalyze -- the default type-specific typanalyze function */ bool -std_typanalyze(VacAttrStats *stats) +std_typanalyze(VacStats *stats, int index) { - Form_pg_attribute attr = stats->attr; Oid ltopr; Oid eqopr; StdAnalyzeData *mystats; - /* If the attstattarget column is negative, use the default value */ + /* If the statarget column is negative, use the default value */ /* NB: it is okay to scribble on stats->attr since it's a copy */ - if (attr->attstattarget < 0) - attr->attstattarget = default_statistics_target; + if (stats->statarget < 0) + stats->statarget = default_statistics_target; /* Look for default "<" and "=" operators for column's type */ - get_sort_group_operators(stats->attrtypid, + get_sort_group_operators(stats->attrtypids[index], false, false, false, <opr, &eqopr, NULL, NULL); @@ -1820,7 +2124,7 @@ std_typanalyze(VacAttrStats *stats) mystats->eqopr = eqopr; mystats->eqfunc = get_opcode(eqopr); mystats->ltopr = ltopr; - stats->extra_data = mystats; + stats->extra_data[index] = mystats; /* * Determine which standard statistics algorithm to use @@ -1828,7 +2132,8 @@ std_typanalyze(VacAttrStats *stats) if (OidIsValid(ltopr)) { /* Seems to be a scalar datatype */ - stats->compute_stats = compute_scalar_stats; + stats->statfuncs[index].compute_func_ptr = compute_scalar_stats; + stats->statfuncs[index].findval_func_ptr = ordered_findval; /*-------------------- * The following choice of minrows is based on the paper * "Random sampling for histogram construction: how much is enough?" @@ -1848,14 +2153,15 @@ std_typanalyze(VacAttrStats *stats) * know it at this point. *-------------------- */ - stats->minrows = 300 * attr->attstattarget; + stats->minrows = 300 * stats->statarget; } else { /* Can't do much but the minimal stuff */ - stats->compute_stats = compute_minimal_stats; + stats->statfuncs[index].compute_func_ptr = compute_minimal_stats; + stats->statfuncs[index].findval_func_ptr = unordered_findval; /* Might as well use the same minrows as above */ - stats->minrows = 300 * attr->attstattarget; + stats->minrows = 300 * stats->statarget; } return true; @@ -1877,7 +2183,8 @@ std_typanalyze(VacAttrStats *stats) * depend mainly on the length of the list we are willing to keep. */ static void -compute_minimal_stats(VacAttrStatsP stats, +compute_minimal_stats(VacStatsP stats, + int index, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows) @@ -1887,10 +2194,10 @@ compute_minimal_stats(VacAttrStatsP stats, int nonnull_cnt = 0; int toowide_cnt = 0; double total_width = 0; - bool is_varlena = (!stats->attrtype->typbyval && - stats->attrtype->typlen == -1); - bool is_varwidth = (!stats->attrtype->typbyval && - stats->attrtype->typlen < 0); + bool is_varlena = (!stats->attrtypes[index]->typbyval && + stats->attrtypes[index]->typlen == -1); + bool is_varwidth = (!stats->attrtypes[index]->typbyval && + stats->attrtypes[index]->typlen < 0); FmgrInfo f_cmpeq; typedef struct { @@ -1900,8 +2207,8 @@ compute_minimal_stats(VacAttrStatsP stats, TrackItem *track; int track_cnt, track_max; - int num_mcv = stats->attr->attstattarget; - StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data; + int num_mcv = stats->statarget; + StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data[index]; /* * We track up to 2*n values for an n-element MCV list; but at least 10 @@ -1924,7 +2231,7 @@ compute_minimal_stats(VacAttrStatsP stats, vacuum_delay_point(); - value = fetchfunc(stats, i, &isnull); + value = fetchfunc(stats, i, stats->attnums->values[index], &isnull); /* Check for null/nonnull */ if (isnull) @@ -2021,11 +2328,11 @@ compute_minimal_stats(VacAttrStatsP stats, stats->stats_valid = true; /* Do the simple null-frac and width stats */ - stats->stanullfrac = (double) null_cnt / (double) samplerows; + stats->stanullfrac[index] = (double) null_cnt / (double) samplerows; if (is_varwidth) - stats->stawidth = total_width / (double) nonnull_cnt; + stats->stawidth[index] = total_width / (double) nonnull_cnt; else - stats->stawidth = stats->attrtype->typlen; + stats->stawidth[index] = stats->attrtypes[index]->typlen; /* Count the number of values we found multiple times */ summultiple = 0; @@ -2039,7 +2346,7 @@ compute_minimal_stats(VacAttrStatsP stats, if (nmultiple == 0) { /* If we found no repeated values, assume it's a unique column */ - stats->stadistinct = -1.0; + stats->stadistinct[index] = -1.0; } else if (track_cnt < track_max && toowide_cnt == 0 && nmultiple == track_cnt) @@ -2049,7 +2356,7 @@ compute_minimal_stats(VacAttrStatsP stats, * value appeared more than once. Assume the column has just * these values. */ - stats->stadistinct = track_cnt; + stats->stadistinct[index] = track_cnt; } else { @@ -2088,7 +2395,7 @@ compute_minimal_stats(VacAttrStatsP stats, stadistinct = (double) d; if (stadistinct > totalrows) stadistinct = totalrows; - stats->stadistinct = floor(stadistinct + 0.5); + stats->stadistinct[index] = floor(stadistinct + 0.5); } /* @@ -2097,8 +2404,8 @@ compute_minimal_stats(VacAttrStatsP stats, * stadistinct should scale with the row count rather than be a fixed * value. */ - if (stats->stadistinct > 0.1 * totalrows) - stats->stadistinct = -(stats->stadistinct / totalrows); + if (stats->stadistinct[index] > 0.1 * totalrows) + stats->stadistinct[index] = -(stats->stadistinct[index] / totalrows); /* * Decide how many values are worth storing as most-common values. If @@ -2118,7 +2425,7 @@ compute_minimal_stats(VacAttrStatsP stats, } else { - double ndistinct = stats->stadistinct; + double ndistinct = stats->stadistinct[index]; double avgcount, mincount; @@ -2156,18 +2463,18 @@ compute_minimal_stats(VacAttrStatsP stats, for (i = 0; i < num_mcv; i++) { mcv_values[i] = datumCopy(track[i].value, - stats->attrtype->typbyval, - stats->attrtype->typlen); + stats->attrtypes[index]->typbyval, + stats->attrtypes[index]->typlen); mcv_freqs[i] = (double) track[i].count / (double) samplerows; } MemoryContextSwitchTo(old_context); - stats->stakind[0] = STATISTIC_KIND_MCV; - stats->staop[0] = mystats->eqopr; - stats->stanumbers[0] = mcv_freqs; - stats->numnumbers[0] = num_mcv; - stats->stavalues[0] = mcv_values; - stats->numvalues[0] = num_mcv; + stats->stakind[index][0] = STATISTIC_KIND_MCV; + stats->staop[index][0] = mystats->eqopr; + stats->stanumbers[index][0] = mcv_freqs; + stats->numnumbers[index][0] = num_mcv; + stats->stavalues[index][0] = mcv_values; + stats->numvalues[index][0] = num_mcv; /* * Accept the defaults for stats->statypid and others. They have @@ -2179,12 +2486,12 @@ compute_minimal_stats(VacAttrStatsP stats, { /* We found only nulls; assume the column is entirely null */ stats->stats_valid = true; - stats->stanullfrac = 1.0; + stats->stanullfrac[index] = 1.0; if (is_varwidth) - stats->stawidth = 0; /* "unknown" */ + stats->stawidth[index] = 0; /* "unknown" */ else - stats->stawidth = stats->attrtype->typlen; - stats->stadistinct = 0.0; /* "unknown" */ + stats->stawidth[index] = stats->attrtypes[index]->typlen; + stats->stadistinct[index] = 0.0; /* "unknown" */ } /* We don't need to bother cleaning up any of our temporary palloc's */ @@ -2204,7 +2511,8 @@ compute_minimal_stats(VacAttrStatsP stats, * data values into order. */ static void -compute_scalar_stats(VacAttrStatsP stats, +compute_scalar_stats(VacStatsP stats, + int index, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows) @@ -2214,10 +2522,10 @@ compute_scalar_stats(VacAttrStatsP stats, int nonnull_cnt = 0; int toowide_cnt = 0; double total_width = 0; - bool is_varlena = (!stats->attrtype->typbyval && - stats->attrtype->typlen == -1); - bool is_varwidth = (!stats->attrtype->typbyval && - stats->attrtype->typlen < 0); + bool is_varlena = (!stats->attrtypes[index]->typbyval && + stats->attrtypes[index]->typlen == -1); + bool is_varwidth = (!stats->attrtypes[index]->typbyval && + stats->attrtypes[index]->typlen < 0); double corr_xysum; SortSupportData ssup; ScalarItem *values; @@ -2225,9 +2533,9 @@ compute_scalar_stats(VacAttrStatsP stats, int *tupnoLink; ScalarMCVItem *track; int track_cnt = 0; - int num_mcv = stats->attr->attstattarget; - int num_bins = stats->attr->attstattarget; - StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data; + int num_mcv = stats->statarget; + int num_bins = stats->statarget; + StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data[index]; values = (ScalarItem *) palloc(samplerows * sizeof(ScalarItem)); tupnoLink = (int *) palloc(samplerows * sizeof(int)); @@ -2249,7 +2557,7 @@ compute_scalar_stats(VacAttrStatsP stats, vacuum_delay_point(); - value = fetchfunc(stats, i, &isnull); + value = fetchfunc(stats, i, stats->attnums->values[index], &isnull); /* Check for null/nonnull */ if (isnull) @@ -2378,16 +2686,16 @@ compute_scalar_stats(VacAttrStatsP stats, stats->stats_valid = true; /* Do the simple null-frac and width stats */ - stats->stanullfrac = (double) null_cnt / (double) samplerows; + stats->stanullfrac[index] = (double) null_cnt / (double) samplerows; if (is_varwidth) - stats->stawidth = total_width / (double) nonnull_cnt; + stats->stawidth[index] = total_width / (double) nonnull_cnt; else - stats->stawidth = stats->attrtype->typlen; + stats->stawidth[index] = stats->attrtypes[index]->typlen; if (nmultiple == 0) { /* If we found no repeated values, assume it's a unique column */ - stats->stadistinct = -1.0; + stats->stadistinct[index] = -1.0; } else if (toowide_cnt == 0 && nmultiple == ndistinct) { @@ -2395,7 +2703,7 @@ compute_scalar_stats(VacAttrStatsP stats, * Every value in the sample appeared more than once. Assume the * column has just these values. */ - stats->stadistinct = ndistinct; + stats->stadistinct[index] = ndistinct; } else { @@ -2430,7 +2738,7 @@ compute_scalar_stats(VacAttrStatsP stats, stadistinct = (double) d; if (stadistinct > totalrows) stadistinct = totalrows; - stats->stadistinct = floor(stadistinct + 0.5); + stats->stadistinct[index] = floor(stadistinct + 0.5); } /* @@ -2439,8 +2747,8 @@ compute_scalar_stats(VacAttrStatsP stats, * stadistinct should scale with the row count rather than be a fixed * value. */ - if (stats->stadistinct > 0.1 * totalrows) - stats->stadistinct = -(stats->stadistinct / totalrows); + if (stats->stadistinct[index] > 0.1 * totalrows) + stats->stadistinct[index] = -(stats->stadistinct[index] / totalrows); /* * Decide how many values are worth storing as most-common values. If @@ -2457,7 +2765,7 @@ compute_scalar_stats(VacAttrStatsP stats, * but we prefer to treat such values as MCVs if at all possible.) */ if (track_cnt == ndistinct && toowide_cnt == 0 && - stats->stadistinct > 0 && + stats->stadistinct[index] > 0 && track_cnt <= num_mcv) { /* Track list includes all values seen, and all will fit */ @@ -2465,7 +2773,7 @@ compute_scalar_stats(VacAttrStatsP stats, } else { - double ndistinct = stats->stadistinct; + double ndistinct = stats->stadistinct[index]; double avgcount, mincount, maxmincount; @@ -2508,18 +2816,18 @@ compute_scalar_stats(VacAttrStatsP stats, for (i = 0; i < num_mcv; i++) { mcv_values[i] = datumCopy(values[track[i].first].value, - stats->attrtype->typbyval, - stats->attrtype->typlen); + stats->attrtypes[index]->typbyval, + stats->attrtypes[index]->typlen); mcv_freqs[i] = (double) track[i].count / (double) samplerows; } MemoryContextSwitchTo(old_context); - stats->stakind[slot_idx] = STATISTIC_KIND_MCV; - stats->staop[slot_idx] = mystats->eqopr; - stats->stanumbers[slot_idx] = mcv_freqs; - stats->numnumbers[slot_idx] = num_mcv; - stats->stavalues[slot_idx] = mcv_values; - stats->numvalues[slot_idx] = num_mcv; + stats->stakind[index][slot_idx] = STATISTIC_KIND_MCV; + stats->staop[index][slot_idx] = mystats->eqopr; + stats->stanumbers[index][slot_idx] = mcv_freqs; + stats->numnumbers[index][slot_idx] = num_mcv; + stats->stavalues[index][slot_idx] = mcv_values; + stats->numvalues[index][slot_idx] = num_mcv; /* * Accept the defaults for stats->statypid and others. They have @@ -2615,8 +2923,8 @@ compute_scalar_stats(VacAttrStatsP stats, for (i = 0; i < num_hist; i++) { hist_values[i] = datumCopy(values[pos].value, - stats->attrtype->typbyval, - stats->attrtype->typlen); + stats->attrtypes[index]->typbyval, + stats->attrtypes[index]->typlen); pos += delta; posfrac += deltafrac; if (posfrac >= (num_hist - 1)) @@ -2629,10 +2937,10 @@ compute_scalar_stats(VacAttrStatsP stats, MemoryContextSwitchTo(old_context); - stats->stakind[slot_idx] = STATISTIC_KIND_HISTOGRAM; - stats->staop[slot_idx] = mystats->ltopr; - stats->stavalues[slot_idx] = hist_values; - stats->numvalues[slot_idx] = num_hist; + stats->stakind[index][slot_idx] = STATISTIC_KIND_HISTOGRAM; + stats->staop[index][slot_idx] = mystats->ltopr; + stats->stavalues[index][slot_idx] = hist_values; + stats->numvalues[index][slot_idx] = num_hist; /* * Accept the defaults for stats->statypid and others. They have @@ -2672,10 +2980,10 @@ compute_scalar_stats(VacAttrStatsP stats, corrs[0] = (values_cnt * corr_xysum - corr_xsum * corr_xsum) / (values_cnt * corr_x2sum - corr_xsum * corr_xsum); - stats->stakind[slot_idx] = STATISTIC_KIND_CORRELATION; - stats->staop[slot_idx] = mystats->ltopr; - stats->stanumbers[slot_idx] = corrs; - stats->numnumbers[slot_idx] = 1; + stats->stakind[index][slot_idx] = STATISTIC_KIND_CORRELATION; + stats->staop[index][slot_idx] = mystats->ltopr; + stats->stanumbers[index][slot_idx] = corrs; + stats->numnumbers[index][slot_idx] = 1; slot_idx++; } } @@ -2683,12 +2991,12 @@ compute_scalar_stats(VacAttrStatsP stats, { /* We found only nulls; assume the column is entirely null */ stats->stats_valid = true; - stats->stanullfrac = 1.0; + stats->stanullfrac[index] = 1.0; if (is_varwidth) - stats->stawidth = 0; /* "unknown" */ + stats->stawidth[index] = 0; /* "unknown" */ else - stats->stawidth = stats->attrtype->typlen; - stats->stadistinct = 0.0; /* "unknown" */ + stats->stawidth[index] = stats->attrtypes[index]->typlen; + stats->stadistinct[index] = 0.0; /* "unknown" */ } /* We don't need to bother cleaning up any of our temporary palloc's */ @@ -2732,6 +3040,209 @@ compare_scalars(const void *a, const void *b, void *arg) } /* + * qsort_arg comparator for cross column statistics + * The dimension axes need to be properly ordered. + */ +static int +compare_datums(const void *a, const void *b, void *arg) +{ + Datum da = *((Datum *) a); + Datum db = *((Datum *) b); + CompareDatumsContext *ctx = (CompareDatumsContext *) arg; + + return ApplySortComparator(da, false, db, false, ctx->ssup); +} + +/* + * Find the index in stavalues for a Datum with ordering operators. + * Because it's ordered, we can use the ordering operator for binary searching. + * Returns: 0 for a NULL value, 1 for a non MCV value, >= 2 for a valid index. + */ +static int +ordered_findval(VacStatsP stats, int rownum, int index, AnalyzeAttrFetchFunc fetchfunc, void *arg) +{ + Datum value; + bool isnull; + int idx_lo, idx_hi, idx_mid; + int32 compare; + CompareDatumsContext *ctx = (CompareDatumsContext *) arg; + + value = fetchfunc(stats, rownum, stats->attnums->values[index], &isnull); + if (isnull) + return 0; + + idx_lo = 0; + idx_hi = stats->numvalues[index][0] - 1; + + idx_mid = (idx_lo + idx_hi) / 2; + while (idx_lo <= idx_hi) + { + compare = ApplySortComparator(value, false, + stats->stavalues[index][0][idx_mid], false, ctx->ssup); + if (compare == 0) + break; + else if (compare < 0) + idx_hi = idx_mid - 1; + else /* compare > 0 */ + idx_lo = idx_mid + 1; + + idx_mid = (idx_lo + idx_hi) / 2; + } + + if (compare == 0) + return idx_mid + 2; + + /* Not found, return "other" special slot */ + return 1; +} + +/* + * Find the index in stavalues for a Datum with equality operator only. + * Byte the bullet and loop through the stavalues array. + * Returns: 0 for NULL value, >= 2 for a valid index and 1 for a Datum + * that isn't in stavalues ("other" value). + */ +static int +unordered_findval(VacStatsP stats, int rownum, int index, AnalyzeAttrFetchFunc fetchfunc, void *arg) +{ + CompareDatumsContext *ctx = (CompareDatumsContext *) arg; + Datum value; + bool isnull; + int i; + + /* FIXME: Assert ctx->cmpFn is correct */ + + value = fetchfunc(stats, rownum, stats->attnums->values[index], &isnull); + if (isnull) + return 0; + + for (i = 0; i < stats->numvalues[index][0]; i++) + { + if (DatumGetBool(FunctionCall2Coll(&(ctx->cmpFn), + DEFAULT_COLLATION_OID, + value, stats->stavalues[index][0][i]))) + return i + 2; + } + + return 1; +} + +/* + * Compute a cross-column statistics. + */ +static void +compute_cross_column_stats(VacStatsP stats, AnalyzeAttrFetchFunc fetchfunc, int numrows) +{ + int i, j, numvalues; + int dimstride[STATISTIC_NUM_SLOTS]; + MemoryContext old_context; + CompareDatumsContext ctx[STATISTIC_NUM_SLOTS]; + float4 *hist; + + /* Don't touch single column statistics. */ + if (stats->attnums->dim1 == 1) + return; + + /* + * Don't compute cross-column statistics if there is no way to find + * a Datum's index in the stavalues[] array + */ + for (i = 0; i < stats->attnums->dim1; i++) + if (stats->statfuncs[i].findval_func_ptr == NULL) + return; + + /* We need ordered stavalues arrays whenever possible. */ + for (i = 0; i < stats->attnums->dim1; i++) + { + StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data[i]; + + if (OidIsValid(mystats->ltopr)) + { + Oid cmpFn; + SortSupportData *ssup; + /* FIXME: shouldn't be necessary in this case */ + bool reverse, issupport; + + if (!get_sort_function_for_ordering_op(mystats->ltopr, &cmpFn, + &issupport, &reverse)) { + elog(ERROR, "operator %u is not a valid ordering operator", + mystats->ltopr); + } + fmgr_info(cmpFn, &(ctx[i].cmpFn)); + + ssup = (SortSupportData*) palloc0(sizeof(SortSupportData)); + ssup->ssup_cxt = CurrentMemoryContext; + /* We always use the default collation for statistics */ + ssup->ssup_collation = DEFAULT_COLLATION_OID; + ssup->ssup_nulls_first = false; + + PrepareSortSupportFromOrderingOp(mystats->ltopr, ssup); + + ctx[i].ssup = ssup; + + qsort_arg((void *) stats->stavalues[i][0], stats->numvalues[i][0], sizeof(Datum), + compare_datums, (void *) &ctx[i]); + } + else + fmgr_info(mystats->eqfunc, &(ctx[i].cmpFn)); + } + + /* Free up previously computed stanumbers arrays. */ + for (i = 0; i < STATISTIC_NUM_SLOTS; i++) + for (j = 0; j < STATISTIC_NUM_SLOTS; j++) + { + if (stats->stanumbers[i][j]) + { + pfree(stats->stanumbers[i][j]); + stats->stanumbers[i][j] = NULL; + } + stats->numnumbers[i][j] = 0; + } + + /* + * Compute the size of the N-dimension histogram and the dimension stride values. + * The histogram cells at the 0th index of a dimension is the nullfrac value, + * hence the + 1. + */ + for (i = 0, numvalues = 1; i < stats->attnums->dim1; i++) + { + dimstride[stats->attnums->dim1 - i - 1] = numvalues; + numvalues *= stats->numvalues[stats->attnums->dim1 - i - 1][0] + 2; + } + + old_context = MemoryContextSwitchTo(stats->anl_context); + + hist = (float *) palloc(numvalues * sizeof(float)); + for (i = 0; i < numvalues; i++) + hist[i] = 0.0; + + MemoryContextSwitchTo(old_context); + + for (i = 0; i < numrows; i++) + { + int idx, idx_tmp; + + idx = 0; + for (j = 0; j < stats->attnums->dim1; j++) + { + idx_tmp = stats->statfuncs[j].findval_func_ptr(stats, i, j, fetchfunc, &ctx[j]); + if (idx_tmp < 0) + continue; + + idx += idx_tmp * dimstride[j]; + } + hist[idx] = hist[idx] + 1.0; + } + + /* Store selectivity instead of actual counts. */ + for (i = 0; i < numvalues; i ++) + hist[i] /= numrows; + + stats->numnumbers[0][0] = numvalues; + stats->stanumbers[0][0] = hist; +} + +/* * qsort comparator for sorting ScalarMCVItems by position */ static int @@ -2742,3 +3253,74 @@ compare_mcvs(const void *a, const void *b) return da - db; } + +/* + * ExtraColStat + * Add or remove one extra entry in pg_statistics + */ +void ExtraStatistics(ExtraStatStmt *stmt) +{ + Oid relId; + int len, i, j; + bool differ = false; + AttrNumber *attnums; + AttrNumber *sorted_attnums; + ListCell *l; + + relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false); + + len = list_length(stmt->columns); + if (len < 2) + elog(ERROR, "cross column statistics need at least two columns"); + if (len > STATISTIC_NUM_SLOTS) + elog(ERROR, "cross column statistics can cover at most %d columns", STATISTIC_NUM_SLOTS); + + attnums = (int2 *)palloc(len * sizeof(AttrNumber)); + sorted_attnums = (int2 *)palloc(len * sizeof(AttrNumber)); + + i = 0; + foreach(l, stmt->columns) + { + Node *node = (Node *) lfirst(l); + Var *var; + + if (!IsA(node, Var)) + elog(ERROR, "not a column reference"); + + var = (Var *) node; + + if (var->varattno == 0) + elog(ERROR, "row expansion via \"*\" is not supported here"); + + sorted_attnums[i] = attnums[i] = var->varattno; + + i++; + } + + for (i = 0; i < len - 1; i++) + for (j = i+1; j < len; j++) + if (sorted_attnums[i] > sorted_attnums[j]) + { + AttrNumber tmp = sorted_attnums[i]; + + sorted_attnums[i] = sorted_attnums[j]; + sorted_attnums[j] = tmp; + } + + for (i = 0; i < len; i++) + { + if (!differ && attnums[i] != sorted_attnums[i]) + differ = true; + + if ((i < len - 1) && sorted_attnums[i] == sorted_attnums[i+1]) + elog(ERROR, "column list must contain every column exactly once"); + + } + if (differ) + elog(WARNING, "the column list was reordered in the order of table attributes"); + + if (stmt->create) + AddStatistics(relId, sorted_attnums, len, false, stmt->statistics_target); + else + RemoveStatistics(relId, sorted_attnums, len); +} diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cd4490a..4ab4b4c 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -35,6 +35,7 @@ #include "catalog/pg_inherits_fn.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" +#include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -4342,7 +4343,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, attribute.attrelid = myrelid; namestrcpy(&(attribute.attname), colDef->colname); attribute.atttypid = typeOid; - attribute.attstattarget = (newattnum > 0) ? -1 : 0; attribute.attlen = tform->typlen; attribute.attcacheoff = -1; attribute.atttypmod = typmod; @@ -4504,6 +4504,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid); add_column_collation_dependency(myrelid, newattnum, attribute.attcollation); + AddStatistics(myrelid, &attribute.attnum, 1, attribute.attinhcount, (newattnum > 0) ? -1 : 0); + /* * Propagate to children as appropriate. Unlike most other ALTER * routines, we have to do this one level of recursion at a time; we can't @@ -4885,8 +4887,14 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE { int newtarget; Relation attrelation; + Relation statsrelation; + Oid relid; HeapTuple tuple; Form_pg_attribute attrtuple; + AttrNumber attnum; + bool inherited; + int2vector *attnumvector; + Form_pg_statistic stattuple; Assert(IsA(newValue, Integer)); newtarget = intVal(newValue); @@ -4912,7 +4920,9 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE attrelation = heap_open(AttributeRelationId, RowExclusiveLock); - tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); + relid = RelationGetRelid(rel); + + tuple = SearchSysCacheAttName(relid, colName); if (!HeapTupleIsValid(tuple)) ereport(ERROR, @@ -4927,16 +4937,41 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE errmsg("cannot alter system column \"%s\"", colName))); - attrtuple->attstattarget = newtarget; + attnum = attrtuple->attnum; + inherited = (attrtuple->attinhcount > 0); - simple_heap_update(attrelation, &tuple->t_self, tuple); + ReleaseSysCache(tuple); - /* keep system catalog indexes current */ - CatalogUpdateIndexes(attrelation, tuple); + heap_close(attrelation, RowExclusiveLock); - heap_freetuple(tuple); + statsrelation = heap_open(StatisticRelationId, RowExclusiveLock); - heap_close(attrelation, RowExclusiveLock); + attnumvector = buildint2vector(&attnum, 1); + + tuple = SearchSysCacheCopy3(STATRELATTINH, + ObjectIdGetDatum(relid), + PointerGetDatum(attnumvector), + BoolGetDatum(inherited)); + + pfree(attnumvector); + + if (!HeapTupleIsValid(tuple)) + AddStatistics(relid, &attnum, 1, inherited, newtarget); + else + { + stattuple = (Form_pg_statistic) GETSTRUCT(tuple); + + stattuple->statarget = newtarget; + + simple_heap_update(statsrelation, &tuple->t_self, tuple); + + /* keep system catalog indexes current */ + CatalogUpdateIndexes(statsrelation, tuple); + + heap_freetuple(tuple); + } + + heap_close(statsrelation, RowExclusiveLock); } static void @@ -7587,9 +7622,9 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid); /* - * Drop any pg_statistic entry for the column, since it's now wrong type + * Invalidate any pg_statistic entry for the column, since it's now wrong type */ - RemoveStatistics(RelationGetRelid(rel), attnum); + InvalidateStatistics(RelationGetRelid(rel), attnum); /* * Update the default, if present, by brute force --- remove and re-add diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c index cabe663..38b5f73 100644 --- a/src/backend/executor/nodeHash.c +++ b/src/backend/executor/nodeHash.c @@ -31,6 +31,7 @@ #include "executor/nodeHash.h" #include "executor/nodeHashjoin.h" #include "miscadmin.h" +#include "utils/builtins.h" #include "utils/dynahash.h" #include "utils/memutils.h" #include "utils/lsyscache.h" @@ -1124,6 +1125,7 @@ ExecHashBuildSkewHash(HashJoinTable hashtable, Hash *node, int mcvsToUse) int nvalues; float4 *numbers; int nnumbers; + int2vector *attnumvector; /* Do nothing if planner didn't identify the outer relation's join key */ if (!OidIsValid(node->skewTable)) @@ -1135,10 +1137,23 @@ ExecHashBuildSkewHash(HashJoinTable hashtable, Hash *node, int mcvsToUse) /* * Try to find the MCV statistics for the outer relation's join key. */ + attnumvector = buildint2vector(&(node->skewColumn), 1); statsTuple = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(node->skewTable), - Int16GetDatum(node->skewColumn), + PointerGetDatum(attnumvector), BoolGetDatum(node->skewInherit)); + pfree(attnumvector); + + /* check whether the stats entry is valid */ + if (HeapTupleIsValid(statsTuple)) + { + if (!((Form_pg_statistic)GETSTRUCT(statsTuple))->stavalid) + { + ReleaseSysCache(statsTuple); + statsTuple = NULL; + } + } + if (!HeapTupleIsValid(statsTuple)) return; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 868fb71..66dc24a 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3422,6 +3422,19 @@ _copyCreateForeignTableStmt(const CreateForeignTableStmt *from) return newnode; } +static ExtraStatStmt * +_copyExtraStatStmt(ExtraStatStmt *from) +{ + ExtraStatStmt *newnode = makeNode(ExtraStatStmt); + + COPY_SCALAR_FIELD(create); + newnode->relation = _copyRangeVar(from->relation); + COPY_NODE_FIELD(columns); + COPY_SCALAR_FIELD(statistics_target); + + return newnode; +} + static CreateTrigStmt * _copyCreateTrigStmt(const CreateTrigStmt *from) { @@ -4290,6 +4303,9 @@ copyObject(const void *from) case T_CreateForeignTableStmt: retval = _copyCreateForeignTableStmt(from); break; + case T_ExtraStatStmt: + retval = _copyExtraStatStmt(from); + break; case T_CreateTrigStmt: retval = _copyCreateTrigStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index d2a79eb..a7bf2a6 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1758,6 +1758,18 @@ _equalCreateForeignTableStmt(const CreateForeignTableStmt *a, const CreateForeig } static bool +_equalExtraStatStmt(ExtraStatStmt *a, ExtraStatStmt *b) +{ + COMPARE_SCALAR_FIELD(create); + if (!_equalRangeVar(a->relation, b->relation)) + return FALSE; + COMPARE_NODE_FIELD(columns); + COMPARE_SCALAR_FIELD(statistics_target); + + return true; +} + +static bool _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b) { COMPARE_STRING_FIELD(trigname); @@ -2850,6 +2862,9 @@ equal(const void *a, const void *b) case T_CreateForeignTableStmt: retval = _equalCreateForeignTableStmt(a, b); break; + case T_ExtraStatStmt: + retval = _equalExtraStatStmt(a, b); + break; case T_CreateTrigStmt: retval = _equalCreateTrigStmt(a, b); break; diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index bb148d7..cae5562 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -13,17 +13,29 @@ *------------------------------------------------------------------------- */ #include "postgres.h" +#include "postgres_ext.h" +#include "access/skey.h" +#include "access/relscan.h" +#include "catalog/indexing.h" +#include "catalog/pg_collation.h" #include "catalog/pg_operator.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_type.h" #include "nodes/makefuncs.h" +#include "nodes/pg_list.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/plancat.h" +#include "optimizer/var.h" +#include "utils/array.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/selfuncs.h" - +#include "utils/syscache.h" +#include "utils/tqual.h" /* * Data structure for accumulating info about possible range-query @@ -33,6 +45,7 @@ typedef struct RangeQueryClause { struct RangeQueryClause *next; /* next in linked list */ Node *var; /* The common variable of the clauses */ + AttrNumber varattno; /* for finding cross-column statistics */ bool have_lobound; /* found a low-bound clause yet? */ bool have_hibound; /* found a high-bound clause yet? */ Selectivity lobound; /* Selectivity of a var > something clause */ @@ -42,6 +55,24 @@ typedef struct RangeQueryClause static void addRangeClause(RangeQueryClause **rqlist, Node *clause, bool varonleft, bool isLTsel, Selectivity s2); +typedef struct CrossColumnClause +{ + struct CrossColumnClause *next; + Node *var; + AttrNumber varattno; + OpExpr *expr; + Selectivity sel; +} CrossColumnClause; + +static void addXCClause(CrossColumnClause **xclist, OpExpr *clause, + bool varonleft, Selectivity s2); + +static bool crosscolumn_selectivity(Oid relId, + CrossColumnClause **xcnext, RangeQueryClause **rqlist, + Selectivity *result_sel); + +static Selectivity calculate_xcol_sel(PlannerInfo *root, CrossColumnClause **clauses, int2vector *attnums, HeapTuple statstuple, TupleDesc tupDesc); +static Selectivity match_xcol_stats(PlannerInfo *root, Oid relId, CrossColumnClause **xclist); /**************************************************************************** * ROUTINES TO COMPUTE SELECTIVITIES @@ -98,8 +129,13 @@ clauselist_selectivity(PlannerInfo *root, { Selectivity s1 = 1.0; RangeQueryClause *rqlist = NULL; + CrossColumnClause *xclist = NULL; + Oid relId = InvalidOid; + bool onerel = false; ListCell *l; +// elog(NOTICE, "clauselist_selectivity varRelid %d, list length %d", varRelid, list_length(clauses)); + /* * If there's exactly one clause, then no use in trying to match up pairs, * so just go directly to clause_selectivity(). @@ -161,6 +197,22 @@ clauselist_selectivity(PlannerInfo *root, (varonleft = false, is_pseudo_constant_clause_relids(linitial(expr->args), rinfo->left_relids))); + if (ok) + { + int relid; + Oid tmprelId; + + relid = bms_singleton_member(rinfo->clause_relids); + tmprelId = root->simple_rte_array[relid]->relid; + + if (!OidIsValid(relId)) + { + onerel = true; + relId = tmprelId; + } + else if (relId != tmprelId) + onerel = false; + } } else { @@ -168,6 +220,25 @@ clauselist_selectivity(PlannerInfo *root, (is_pseudo_constant_clause(lsecond(expr->args)) || (varonleft = false, is_pseudo_constant_clause(linitial(expr->args)))); + if (ok) + { + Relids relids; + int relid; + Oid tmprelId; + + relids = pull_varnos(clause); + relid = bms_singleton_member(relids); + tmprelId = root->simple_rte_array[relid]->relid; + bms_free(relids); + + if (!OidIsValid(relId)) + { + onerel = true; + relId = tmprelId; + } + else if (relId != tmprelId) + onerel = false; + } } if (ok) @@ -187,6 +258,10 @@ clauselist_selectivity(PlannerInfo *root, addRangeClause(&rqlist, clause, varonleft, false, s2); break; + case F_EQSEL: + addXCClause(&xclist, expr, + varonleft, s2); + break; default: /* Just merge the selectivity in generically */ s1 = s1 * s2; @@ -201,6 +276,34 @@ clauselist_selectivity(PlannerInfo *root, } /* + * Scan xclist and rqlist recursively and filter out + * all possible cross-column selectivities. + */ + if (onerel) + { + // elog(NOTICE, "Old selectivity: %0.5f", s1); + s1 = s1 * match_xcol_stats(root, relId, &xclist); + /*crosscolumn_selectivity(relId, &xclist, &rqlist, &s1);*/ + // elog(NOTICE, "New selectivity: %0.5f", s1); + } + + /* + * Free the cross-column clauses + */ + while (xclist != NULL) + { + CrossColumnClause *xcnext; + + // elog(NOTICE, "Freeing att %d", xclist->varattno); + + s1 = s1 * xclist->sel; + + xcnext = xclist->next; + pfree(xclist); + xclist = xcnext; + } + + /* * Now scan the rangequery pair list. */ while (rqlist != NULL) @@ -278,6 +381,22 @@ clauselist_selectivity(PlannerInfo *root, return s1; } +static AttrNumber +var_get_attno(Node *clause) +{ + Var *var; + + if (IsA(clause, Var)) + { + var = (Var *)clause; +// elog(NOTICE, "var_get_attno varattno %d", var->varattno); + return var->varattno; + } + +// elog(NOTICE, "var_get_attno default 0"); + return 0; +} + /* * addRangeClause --- add a new range clause for clauselist_selectivity * @@ -357,6 +476,8 @@ addRangeClause(RangeQueryClause **rqlist, Node *clause, /* No matching var found, so make a new clause-pair data structure */ rqelem = (RangeQueryClause *) palloc(sizeof(RangeQueryClause)); rqelem->var = var; + rqelem->varattno = var_get_attno(var); + if (is_lobound) { rqelem->have_lobound = true; @@ -374,6 +495,49 @@ addRangeClause(RangeQueryClause **rqlist, Node *clause, } /* + * addXCClause - add a new clause to the list of clauses for cross-column stats inspection + * + * Clauses are added in varattno order. + */ +static void +addXCClause(CrossColumnClause **xclist, OpExpr *clause, + bool varonleft, Selectivity s) +{ + CrossColumnClause *xcelem; + CrossColumnClause **xcinsertpos; + CrossColumnClause *xcnext; + Node *var; + /*Node *expr;*/ + + if (varonleft) + { + var = get_leftop((Expr *) clause); + /*expr = get_rightop((Expr *) clause);*/ + } + else + { + var = get_rightop((Expr *) clause); + /*expr = get_leftop((Expr *) clause);*/ + } + + xcelem = (CrossColumnClause *) palloc(sizeof(CrossColumnClause)); + xcelem->var = var; + xcelem->varattno = var_get_attno(var); + xcelem->expr = clause; + xcelem->sel = s; + + xcinsertpos = xclist; + xcnext = *xclist; + while (xcnext && xcnext->varattno < xcelem->varattno) + { + xcinsertpos = &(xcnext->next); + xcnext = xcnext->next; + } + xcelem->next = xcnext; + *xcinsertpos = xcelem; +} + +/* * bms_is_subset_singleton * * Same result as bms_is_subset(s, bms_make_singleton(x)), @@ -498,6 +662,8 @@ clause_selectivity(PlannerInfo *root, { rinfo = (RestrictInfo *) clause; +// elog(NOTICE, "RestrictInfo, %s", nodeToString(rinfo->clause)); + /* * If the clause is marked pseudoconstant, then it will be used as a * gating qual and should not affect selectivity estimates; hence @@ -563,7 +729,7 @@ clause_selectivity(PlannerInfo *root, /* * We probably shouldn't ever see an uplevel Var here, but if we do, - * return the default selectivity... + * return the default selectivity... (FIXME: wrong comment given code below) */ if (var->varlevelsup == 0 && (varRelid == 0 || varRelid == (int) var->varno)) @@ -778,3 +944,571 @@ clause_selectivity(PlannerInfo *root, return s1; } + +static bool +has_xcol_selectivity(Oid relId, int natts, AttrNumber *attnums, Selectivity *result_sel) +{ + Relation rel; + Datum *datums = (Datum *)palloc(natts * sizeof(Datum)); + ArrayType *arr_attnums; + int i; + int16 typlen; + bool typbyval; + char typalign; + ScanKeyData scanKey[2]; + SysScanDesc scan; + HeapTuple tuple; + bool result; + Selectivity sel = 1e-5; /* fixed selectivity for now */ + + for (i = 0; i < natts; i++) + datums[i] = Int16GetDatum(attnums[i]); + + get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); + arr_attnums = construct_array(datums, natts, + INT2OID, typlen, typbyval, typalign); + + rel = heap_open(StatisticRelationId, AccessShareLock); + + ScanKeyInit(&scanKey[0], + Anum_pg_statistic_starelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relId)); + ScanKeyInit(&scanKey[1], + Anum_pg_statistic_staattnums, + BTEqualStrategyNumber, F_ARRAY_EQ, + PointerGetDatum(arr_attnums)); + + scan = systable_beginscan(rel, StatisticRelidAttnumsInhIndexId, true, + SnapshotNow, 2, scanKey); + + tuple = systable_getnext(scan); + + result = HeapTupleIsValid(tuple); + + systable_endscan(scan); + + heap_close(rel, NoLock); + + pfree(datums); + + if (result) + *result_sel = sel; + + return result; +} + +static Selectivity +match_xcol_stats(PlannerInfo *root, Oid relId, CrossColumnClause **xclist) +{ + Relation pgstatistic; + Datum datums[2]; + ArrayType *arr_attnums; + int16 typlen; + bool typbyval; + char typalign; + ScanKeyData scanKey[2]; + SysScanDesc scan; + HeapTuple tuple; + TupleDesc tupDesc; + bool isnull; + Datum sta_attnums_dat; + int2vector *sta_attnum_vec; + CrossColumnClause *clauses[STATISTIC_NUM_SLOTS]; + Selectivity s1, s2; + Form_pg_statistic stats; + + s1 = 1.0; + + datums[0] = Int16GetDatum(0); + datums[1] = Int16GetDatum(0); + get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); + arr_attnums = construct_array(datums, 2, + INT2OID, typlen, typbyval, typalign); + + + pgstatistic = heap_open(StatisticRelationId, AccessShareLock); + tupDesc = RelationGetDescr(pgstatistic); + + ScanKeyInit(&scanKey[0], + Anum_pg_statistic_starelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relId)); + ScanKeyInit(&scanKey[1], + Anum_pg_statistic_staattnums, + BTGreaterEqualStrategyNumber, F_INT2VECTORGE, + PointerGetDatum(arr_attnums)); + + scan = systable_beginscan(pgstatistic, StatisticRelidAttnumsInhIndexId, true, + SnapshotNow, 2, scanKey); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) { + CrossColumnClause *xc_tmp; + CrossColumnClause **prev_link; + int i, cur_attno; + Selectivity old_sel = 1.0; + + stats = (Form_pg_statistic) GETSTRUCT(tuple); + if (!stats->stavalid) + continue; + + sta_attnums_dat = heap_getattr(tuple, Anum_pg_statistic_staattnums, tupDesc, &isnull); + Assert(!isnull && sta_attnums_dat); + sta_attnum_vec = (int2vector *) DatumGetPointer(sta_attnums_dat); + + /* match up against clauses */ + xc_tmp = *xclist; + for (i = 0; i < sta_attnum_vec->dim1; i++) + { + cur_attno = sta_attnum_vec->values[i]; + while (xc_tmp && xc_tmp->varattno < cur_attno) + xc_tmp = xc_tmp->next; + + if (!xc_tmp || xc_tmp->varattno > cur_attno) + break; + + /* store picked clause */ + clauses[i] = xc_tmp; + } + if (i != sta_attnum_vec->dim1) /* No match */ + continue; + + + + s2 = calculate_xcol_sel(root, clauses, sta_attnum_vec, tuple, tupDesc); + if (s2 < 0.0) + continue; + + /* remove matched clauses from consideration */ + prev_link = xclist; + xc_tmp = *xclist; + i = 0; + while (xc_tmp && i < sta_attnum_vec->dim1) + { + if (xc_tmp == clauses[i]) + { + CrossColumnClause *xc_removed; + // elog(NOTICE, "Removing clause with att %d from consideration", xc_tmp->varattno); + old_sel *= xc_tmp->sel; + *prev_link = xc_tmp->next; + xc_removed = xc_tmp; + xc_tmp = xc_tmp->next; + pfree(xc_removed); + i++; + + } else { + prev_link = &(xc_tmp->next); + xc_tmp = xc_tmp->next; + } + } + // elog(NOTICE, "New selectivity %0.5f old sel %0.5f", s2, old_sel); + /* Merge in selectivity */ + s1 = s1*s2; + } + systable_endscan(scan); + + heap_close(pgstatistic, AccessShareLock); + + return s1; +} + +/* Calculates selectivity from cross column statistics. + * Returns a negative number if not possible to calculate. */ +static Selectivity +calculate_xcol_sel(PlannerInfo *root, CrossColumnClause **clauses, int2vector *attnums, HeapTuple statstuple, TupleDesc tupDesc) +{ + int i; + /* elog(NOTICE, "Calculating stats for %d clauses", attnums->dim1); + for (i = 0; i < attnums->dim1; i++) + { + elog(NOTICE, "Attribute number: %d", clauses[i]->varattno); + }*/ + bool isnull; + Datum values_dat; + ArrayType *statarray; + Oid arrayelemtype; + HeapTuple typeTuple; + Form_pg_type typeForm; + Datum *values; + int nvalues; + int j; + CrossColumnClause *clause; + Form_pg_statistic stats; + int dimindices[STATISTIC_NUM_SLOTS]; + int dimstrides[STATISTIC_NUM_SLOTS]; + int mcv_index; + Selectivity s1 = 1.0; + + stats = (Form_pg_statistic) GETSTRUCT(statstuple); + + for (i = 0; i < attnums->dim1; i++) + { + VariableStatData vardata; + Node *other; + bool varonleft; + Datum constval; + bool result; + FmgrInfo eqproc; + + clause = clauses[i]; + + /* FIXME: refactor multi-col stats storage to store operators per col + * so we can check that the correct op is used + opproc = DatumGetObjectId(heap_getattr(statstuple, Anum_pg_statistic_staop1 + i, + tupDesc, &isnull)); + Assert(opproc == clause->expr->opno); */ + values_dat = heap_getattr(statstuple, Anum_pg_statistic_stavalues1 + i, + tupDesc, &isnull); + Assert(!isnull); + + statarray = DatumGetArrayTypeP(values_dat); + + arrayelemtype = ARR_ELEMTYPE(statarray); + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(arrayelemtype)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "cache lookup failed for type %u", arrayelemtype); + + typeForm = (Form_pg_type) GETSTRUCT(typeTuple); + deconstruct_array(statarray, + arrayelemtype, + typeForm->typlen, + typeForm->typbyval, + typeForm->typalign, + &values, NULL, &nvalues); + ReleaseSysCache(typeTuple); + + dimstrides[i] = 1; + for (j = i-1; j >= 0; j--) + { + dimstrides[j] *= (nvalues + 2); + } + + get_restriction_variable(root, clause->expr->args, 0 /* FIXME: pass this in */, + &vardata, &other, &varonleft); + constval = ((Const *) other)->constvalue; + + /* Null is stored in the first item of a dimension */ + if (((Const *)other)->constisnull) + { + dimindices[i] = 0; + ReleaseVariableStats(vardata); + continue; + } + + /* TODO: use binary search when possible */ + for (j = 0; j < nvalues; j++) + { + fmgr_info(get_opcode(clause->expr->opno), &eqproc); + // elog(NOTICE, "Comparing %d to %d using %d", values[j], constval, clause->expr->opno); + result = DatumGetBool(FunctionCall2Coll(&eqproc, + DEFAULT_COLLATION_OID, + values[j], + constval)); + if (result) { /* Found matching value */ + dimindices[i] = j+2; + break; + } + } + + if (j == nvalues) /* Nothing matching was found in MCV's */ + { + double ndistinct; + bool isdefault; + + // elog(NOTICE, "Value for dimension %d not found, constval %d", i, constval); + dimindices[i] = 1; + + /* We presume uniform distribution of values not present in MCV. + * Predivide selectivity with ndistinct for this column. */ + ndistinct = get_variable_numdistinct(&vardata, &isdefault); + ndistinct -= nvalues; + // elog(NOTICE, "Predividing selectivity with %f", ndistinct); + if (ndistinct > 1.0) + s1 = s1 / ndistinct; + } + + /* + * Free statarray if it's a detoasted copy. + */ + if ((Pointer) statarray != DatumGetPointer(values_dat)) + pfree(statarray); + + ReleaseVariableStats(vardata); + } + + mcv_index = 0; + for (i = 0; i < attnums->dim1; i++) + { + mcv_index += dimindices[i]*dimstrides[i]; + // elog(NOTICE, "MCV dimension %d index %d", i, dimindices[i]); + } + // elog(NOTICE, "MCV position %d", mcv_index); + do { + Datum numbers_dat; + int narrayelem; + + numbers_dat = heap_getattr(statstuple, Anum_pg_statistic_stanumbers1, + tupDesc, &isnull); + Assert(!isnull); + + statarray = DatumGetArrayTypeP(numbers_dat); + + /* + * We expect the array to be a 1-D float4 array; verify that. We don't + * need to use deconstruct_array() since the array data is just going + * to look like a C array of float4 values. + */ + narrayelem = ARR_DIMS(statarray)[0]; + if (ARR_NDIM(statarray) != 1 || narrayelem <= 0 || + ARR_HASNULL(statarray) || + ARR_ELEMTYPE(statarray) != FLOAT4OID) + elog(ERROR, "stanumbers is not a 1-D float4 array"); + if (narrayelem <= mcv_index) + elog(ERROR, "stanumbers is too short"); + s1 = s1 * ((float4 *) ARR_DATA_PTR(statarray))[mcv_index]; + + /* + * Free statarray if it's a detoasted copy. + */ + if ((Pointer) statarray != DatumGetPointer(numbers_dat)) + pfree(statarray); + + } while(0); + + //elog(NOTICE, "Returning selectivity %0.7f", s1); + return s1; +} + +typedef struct { + CrossColumnClause *xc; + RangeQueryClause *rq; +} reclist; + +typedef struct { + int len; + reclist *rclist; + AttrNumber *attnums; +} reclist2; + + +/* add rclist to our list, so the ordered attnums arrays are unique */ +static void +add_reclist(int len, reclist *rclist, List **results) +{ + ListCell *lc; + int i, j; + reclist2 *rclist2; + AttrNumber *attnums = (AttrNumber *) palloc(len * sizeof(AttrNumber)); + + /* collect the ordered varattnos from the Vars */ + for (i = 0; i < len; i++) + { + if (rclist[i].xc) + attnums[i] = rclist[i].xc->varattno; + else + attnums[i] = rclist[i].rq->varattno; + } + /* FIXME: is this supposed to be a bubblesort? Maybe do an insertion sort? */ + for (i = 0; i < len - 1; i++) + for (j = i + 1; j < len; j++) + { + AttrNumber tmp = attnums[i]; + attnums[i] = attnums[j]; + attnums[j] = tmp; + } + + /* match this ordered attnum list against the current list of attnum arrays */ + foreach(lc, *results) + { + reclist2 *rc2 = (reclist2 *) lfirst(lc); + + if (len != rc2->len) + continue; + + for (i = 0; i < len; i++) + if (attnums[i] != rc2->attnums[i]) + break; + if (i < len) + continue; + + /* found */ + return; + } + + /* not found, add it to the list */ + rclist2 = (reclist2 *) palloc(sizeof(reclist2)); + rclist2->len = len; + rclist2->rclist = (reclist *) palloc(len * sizeof(reclist)); + for (i = 0; i < len; i++) + { + rclist2->rclist[i].xc = rclist[i].xc; + rclist2->rclist[i].rq = rclist[i].rq; + } + rclist2->attnums = attnums; + + *results = lappend(*results, rclist2); +} + +static int +compare_reclist2(reclist2 *a, reclist2 *b) +{ + int i; + + if (a->len < b->len) + return -1; + else if (a->len > b->len) + return 1; + + for (i = 0; i < a->len; i++) + { + if (a->attnums[i] < b->attnums[i]) + return -1; + else if (a->attnums[i] > b->attnums[i]) + return 1; + } + + return 0; +} + +static bool +add_reclist2(int *len, reclist2 **p_reclist2, reclist2 *rclist2) +{ + int curr_len = *len; + int i, j; + + if (curr_len == 0) + { + p_reclist2[i] = rclist2; + curr_len++; + *len = curr_len; + return true; + } + + for (i = 0; i < curr_len; i++) + { + if (compare_reclist2(rclist2, p_reclist2[i]) > 0) + { + for (j = curr_len; j > i; j--) + p_reclist2[j] = p_reclist2[j - 1]; + p_reclist2[i] = rclist2; + curr_len++; + *len = curr_len; + return true; + } + } + + return false; +} + +static void +collect_xcol_lists(int curr_depth, CrossColumnClause *xclist, RangeQueryClause *rqlist, reclist *rclist, List **results) +{ + CrossColumnClause *xc_tmp; + RangeQueryClause *rq_tmp; + + for (xc_tmp = xclist; xc_tmp; xc_tmp = xc_tmp->next) + { + if (xc_tmp->varattno == 0) + continue; + + rclist[curr_depth].xc = xc_tmp; + collect_xcol_lists(curr_depth + 1, xc_tmp->next, rqlist, rclist, results); + add_reclist(curr_depth + 1, rclist, results); + rclist[curr_depth].xc = NULL; + } + + for (rq_tmp = rqlist; rq_tmp; rq_tmp = rq_tmp->next) + { + if (rq_tmp->varattno == 0) + continue; + + rclist[curr_depth].rq = rq_tmp; + collect_xcol_lists(curr_depth + 1, (xclist ? xclist->next : xclist), rq_tmp->next, rclist, results); + add_reclist(curr_depth + 1, rclist, results); + rclist[curr_depth].rq = NULL; + } +} + +static bool +crosscolumn_selectivity(Oid relId, CrossColumnClause **xclist, RangeQueryClause **rqlist, Selectivity *result_sel) +{ + CrossColumnClause *xc; + RangeQueryClause *rq; + List *resultlist = NIL; + ListCell *lc; + reclist *rclist; + reclist2 **p_rclist2; + int max_len, i; + Selectivity sel = 1.0; + bool found_xc_sel = false; + + max_len = 0; + for (rq = *rqlist; rq; max_len++, rq = rq->next) + ; + for (xc = *xclist; xc; max_len++, xc = xc->next) + ; + +// elog(NOTICE, "crosscolumn_selectivity max length of array %d", max_len); + + rclist = (reclist *) palloc(max_len * sizeof(reclist)); + for (i = 0; i < max_len; i++) + { + rclist[i].xc = NULL; + rclist[i].rq = NULL; + } + + collect_xcol_lists(0, *xclist, *rqlist, rclist, &resultlist); + + pfree(rclist); + + max_len = list_length(resultlist); +// elog(NOTICE, "crosscolumn_selectivity list length of arrays %d", max_len); + p_rclist2 = (reclist2 **) palloc(max_len * sizeof(reclist2 *)); + + max_len = 0; + foreach (lc, resultlist) + { + reclist2 *rclist2 = (reclist2 *) lfirst(lc); + + if (!add_reclist2(&max_len, p_rclist2, rclist2)) + { + pfree(rclist2->rclist); + pfree(rclist2->attnums); + pfree(rclist2); + } + } +// elog(NOTICE, "crosscolumn_selectivity length of ordered/unique array of previous list %d", max_len); + + list_free(resultlist); + + for (i = 0; i < max_len; i++) + { + if (p_rclist2[i] == NULL) + continue; + + if (has_xcol_selectivity(relId, p_rclist2[i]->len, p_rclist2[i]->attnums, &sel)) + { + int j; + + /* remove the xclist and rqlist members found in p_rclist2[i] */ + for (j = 0; j < p_rclist2[i]->len; j++) + { + /* TODO ... */ + } + + /* also, remove later elements in p_rclist2 that has any of the removed elements */ + /* TODO ... */ + +// elog(NOTICE, "crosscolumn_selectivity found xc selectivity %lf", sel); + found_xc_sel = true; + *result_sel *= sel; + } + + pfree(p_rclist2[i]->rclist); + pfree(p_rclist2[i]->attnums); + pfree(p_rclist2[i]); + } + pfree(p_rclist2); + + return found_xc_sel; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9aea2cd..7e4f352 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -214,7 +214,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt - DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt + DropForeignServerStmt DropUserMappingStmt ExplainStmt ExtraStatStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt @@ -246,7 +246,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, transaction_mode_item create_extension_opt_item alter_extension_opt_item -%type opt_lock lock_type cast_context +%type opt_lock lock_type cast_context opt_stattarget %type vacuum_option_list vacuum_option_elem %type opt_force opt_or_replace opt_grant_grant_option opt_grant_admin_option @@ -325,6 +325,8 @@ static void processCASbits(int cas_bits, int location, const char *constrType, %type opt_fdw_options fdw_options %type fdw_option +%type cc_column_list + %type OptTempTableName %type into_clause create_as_target @@ -755,6 +757,7 @@ stmt : | DropdbStmt | ExecuteStmt | ExplainStmt + | ExtraStatStmt | FetchStmt | GrantStmt | GrantRoleStmt @@ -1199,6 +1202,74 @@ schema_stmt: /***************************************************************************** * + * Add / drop extra statistics + * + *****************************************************************************/ + +ExtraStatStmt: + CREATE CROSS COLUMN STATISTICS ON TABLE qualified_name '(' cc_column_list ')' opt_stattarget + { + ExtraStatStmt *n = makeNode(ExtraStatStmt); + + n->relkind = 'r'; + n->create = true; + n->relation = $7; + n->columns = $9; + n->statistics_target = $11; + $$ = (Node *)n; + } + | DROP CROSS COLUMN STATISTICS ON TABLE qualified_name '(' cc_column_list ')' + { + ExtraStatStmt *n = makeNode(ExtraStatStmt); + + n->relkind = 'r'; + n->create = false; + n->relation = $7; + n->columns = $9; + $$ = (Node *)n; + } + | CREATE CROSS COLUMN STATISTICS ON INDEX qualified_name opt_stattarget + { + ExtraStatStmt *n = makeNode(ExtraStatStmt); + + n->relkind = 'i'; + n->create = true; + n->relation = $7; + n->columns = NIL; + n->statistics_target = $8; + $$ = (Node *)n; + } + | DROP CROSS COLUMN STATISTICS ON INDEX qualified_name + { + ExtraStatStmt *n = makeNode(ExtraStatStmt); + + n->relkind = 'i'; + n->create = false; + n->relation = $7; + n->columns = NIL; + $$ = (Node *)n; + } + ; + +cc_column_list: + columnref + { + $$ = list_make1($1); + } + | cc_column_list ',' columnref + { + $$ = lappend($1, $3); + } + ; + +opt_stattarget: + WITH '(' Iconst ')' { $$ = $3; } + | /* EMPTY */ { $$ = -1; } + ; + + +/***************************************************************************** + * * Set PG internal variable * SET name TO 'var_value' * Include SQL92 syntax (thomas 1997-10-22): diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 43f5634..2378435 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -2754,3 +2754,96 @@ setSchemaName(char *context_schema, char **stmt_schema_name) "different from the one being created (%s)", *stmt_schema_name, context_schema))); } + +/* + * transformExtraStatistics + * Transform the column list or the expression into a form + * usable by the executor. + */ +ExtraStatStmt * +transformExtraStatistics(ExtraStatStmt *stmt, const char *queryString) +{ + ParseState *pstate; + RangeTblEntry *rte; + ExtraStatStmt *newstmt; + List *columns = NIL; + ListCell *cell; + Oid relId; + HeapTuple tuple; + HeapTuple attuple; + Form_pg_class classptr; + Form_pg_index indexptr; + Form_pg_attribute attptr; + AttrNumber i; + + switch (stmt->relkind) + { + case 'r': + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true); + addRTEtoQuery(pstate, rte, true, true, true); + + foreach(cell, stmt->columns) + { + Node *col = lfirst(cell); + + columns = lappend(columns, transformExpr(pstate, col)); + } + + break; + + case 'i': + relId = RangeVarGetRelid(stmt->relation, ShareLock, false); + + tuple = SearchSysCache1(RELOID, relId); + classptr = (Form_pg_class) GETSTRUCT(tuple); + + if (classptr->relkind != 'i') + elog(ERROR, "not an index"); + + ReleaseSysCache(tuple); + + tuple = SearchSysCache1(INDEXRELID, relId); + indexptr = (Form_pg_index) GETSTRUCT(tuple); + + if (indexptr->indnatts < 2) + { + ReleaseSysCache(tuple); + + elog(ERROR, "cross column statistics are only usable on multi-column indexes"); + } + + for (i = 1; i <= indexptr->indnatts; i++) + { + attuple = SearchSysCache2(ATTNUM, relId, i); + if (!HeapTupleIsValid(attuple)) + elog(ERROR, "pg_attribute row not found for index"); + + attptr = (Form_pg_attribute) GETSTRUCT(attuple); + + columns = lappend(columns, makeVar(0, i, + attptr->atttypid, + attptr->atttypmod, + InvalidOid, 0)); + + ReleaseSysCache(attuple); + } + + ReleaseSysCache(tuple); + break; + + default: + elog(ERROR, "invalid relkind"); + } + + newstmt = makeNode(ExtraStatStmt); + newstmt->relkind = stmt->relkind; + newstmt->create = stmt->create; + newstmt->relation = copyObject(stmt->relation); + newstmt->columns = columns; + newstmt->statistics_target = stmt->statistics_target; + + return newstmt; +} diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 5b81c0b..6506142 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -230,6 +230,7 @@ check_xact_readonly(Node *parsetree) case T_AlterTableSpaceOptionsStmt: case T_CreateForeignTableStmt: case T_SecLabelStmt: + case T_ExtraStatStmt: PreventCommandIfReadOnly(CreateCommandTag(parsetree)); break; default: @@ -574,6 +575,14 @@ standard_ProcessUtility(Node *parsetree, } break; + case T_ExtraStatStmt: + { + ExtraStatStmt *newstmt = transformExtraStatistics((ExtraStatStmt *)parsetree, queryString); + + ExtraStatistics(newstmt); + } + break; + case T_CreateTableSpaceStmt: PreventTransactionChain(isTopLevel, "CREATE TABLESPACE"); CreateTableSpace((CreateTableSpaceStmt *) parsetree); @@ -1638,6 +1647,17 @@ CreateCommandTag(Node *parsetree) tag = "CREATE FOREIGN TABLE"; break; + case T_ExtraStatStmt: + { + ExtraStatStmt *stmt = (ExtraStatStmt *)parsetree; + + if (stmt->create) + tag = "CREATE CROSS COLUMN STATISTICS"; + else + tag = "DROP CROSS COLUMN STATISTICS"; + } + break; + case T_DropStmt: switch (((DropStmt *) parsetree)->removeType) { diff --git a/src/backend/tsearch/ts_typanalyze.c b/src/backend/tsearch/ts_typanalyze.c index 9771415..170d5ca 100644 --- a/src/backend/tsearch/ts_typanalyze.c +++ b/src/backend/tsearch/ts_typanalyze.c @@ -35,7 +35,8 @@ typedef struct int delta; /* And this is 'delta'. */ } TrackItem; -static void compute_tsvector_stats(VacAttrStats *stats, +static void compute_tsvector_stats(VacStats *stats, + int index, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows); @@ -53,17 +54,21 @@ static int trackitem_compare_lexemes(const void *e1, const void *e2); Datum ts_typanalyze(PG_FUNCTION_ARGS) { - VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); - Form_pg_attribute attr = stats->attr; + VacStats *stats = (VacStats *) PG_GETARG_POINTER(0); + int4 i = PG_GETARG_INT32(1); - /* If the attstattarget column is negative, use the default value */ - /* NB: it is okay to scribble on stats->attr since it's a copy */ - if (attr->attstattarget < 0) - attr->attstattarget = default_statistics_target; + if (i < 0 || i >= STATISTIC_NUM_SLOTS) + PG_RETURN_BOOL(false); - stats->compute_stats = compute_tsvector_stats; + /* If the statarget column is negative, use the default value */ + /* NB: it is okay to scribble on stats->statarget since it's a copy */ + if (stats->statarget < 0) + stats->statarget = default_statistics_target; + + stats->statfuncs[i].compute_func_ptr = compute_tsvector_stats; + stats->statfuncs[i].findval_func_ptr = NULL; /* this prevents cross-column stats to be computed */ /* see comment about the choice of minrows in commands/analyze.c */ - stats->minrows = 300 * attr->attstattarget; + stats->minrows = 300 * stats->statarget; PG_RETURN_BOOL(true); } @@ -136,7 +141,8 @@ ts_typanalyze(PG_FUNCTION_ARGS) * want. */ static void -compute_tsvector_stats(VacAttrStats *stats, +compute_tsvector_stats(VacStats *stats, + int index, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows) @@ -166,7 +172,7 @@ compute_tsvector_stats(VacAttrStats *stats, * the number of individual lexeme values tracked in pg_statistic ought to * be more than the number of values for a simple scalar column. */ - num_mcelem = stats->attr->attstattarget * 10; + num_mcelem = stats->statarget * 10; /* * We set bucket width equal to (num_mcelem + 10) / 0.007 as per the @@ -206,7 +212,7 @@ compute_tsvector_stats(VacAttrStats *stats, vacuum_delay_point(); - value = fetchfunc(stats, vector_no, &isnull); + value = fetchfunc(stats, vector_no, stats->attnums->values[index], &isnull); /* * Check for null/nonnull. @@ -291,11 +297,11 @@ compute_tsvector_stats(VacAttrStats *stats, stats->stats_valid = true; /* Do the simple null-frac and average width stats */ - stats->stanullfrac = (double) null_cnt / (double) samplerows; - stats->stawidth = total_width / (double) nonnull_cnt; + stats->stanullfrac[index] = (double) null_cnt / (double) samplerows; + stats->stawidth[index] = total_width / (double) nonnull_cnt; /* Assume it's a unique column (see notes above) */ - stats->stadistinct = -1.0; + stats->stadistinct[index] = -1.0; /* * Construct an array of the interesting hashtable items, that is, @@ -403,27 +409,27 @@ compute_tsvector_stats(VacAttrStats *stats, mcelem_freqs[i] = (double) maxfreq / (double) nonnull_cnt; MemoryContextSwitchTo(old_context); - stats->stakind[0] = STATISTIC_KIND_MCELEM; - stats->staop[0] = TextEqualOperator; - stats->stanumbers[0] = mcelem_freqs; + stats->stakind[index][0] = STATISTIC_KIND_MCELEM; + stats->staop[index][0] = TextEqualOperator; + stats->stanumbers[index][0] = mcelem_freqs; /* See above comment about two extra frequency fields */ - stats->numnumbers[0] = num_mcelem + 2; - stats->stavalues[0] = mcelem_values; - stats->numvalues[0] = num_mcelem; + stats->numnumbers[index][0] = num_mcelem + 2; + stats->stavalues[index][0] = mcelem_values; + stats->numvalues[index][0] = num_mcelem; /* We are storing text values */ - stats->statypid[0] = TEXTOID; - stats->statyplen[0] = -1; /* typlen, -1 for varlena */ - stats->statypbyval[0] = false; - stats->statypalign[0] = 'i'; + stats->statypid[index][0] = TEXTOID; + stats->statyplen[index][0] = -1; /* typlen, -1 for varlena */ + stats->statypbyval[index][0] = false; + stats->statypalign[index][0] = 'i'; } } else { /* We found only nulls; assume the column is entirely null */ stats->stats_valid = true; - stats->stanullfrac = 1.0; - stats->stawidth = 0; /* "unknown" */ - stats->stadistinct = 0.0; /* "unknown" */ + stats->stanullfrac[index] = 1.0; + stats->stawidth[index] = 0; /* "unknown" */ + stats->stadistinct[index] = 0.0; /* "unknown" */ } /* diff --git a/src/backend/utils/adt/array_typanalyze.c b/src/backend/utils/adt/array_typanalyze.c index ba98739..4abc29e 100644 --- a/src/backend/utils/adt/array_typanalyze.c +++ b/src/backend/utils/adt/array_typanalyze.c @@ -50,7 +50,7 @@ typedef struct FmgrInfo *hash; /* Saved state from std_typanalyze() */ - AnalyzeAttrComputeStatsFunc std_compute_stats; + ComputeStatsFunc std_compute_stats; void *std_extra_data; } ArrayAnalyzeExtraData; @@ -78,7 +78,7 @@ typedef struct int frequency; /* Number of arrays seen with this count */ } DECountItem; -static void compute_array_stats(VacAttrStats *stats, +static void compute_array_stats(VacStats *stats, int index, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows); static void prune_element_hashtable(HTAB *elements_tab, int b_current); static uint32 element_hash(const void *key, Size keysize); @@ -95,7 +95,8 @@ static int countitem_compare_count(const void *e1, const void *e2); Datum array_typanalyze(PG_FUNCTION_ARGS) { - VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); + VacStats *stats = (VacStats *) PG_GETARG_POINTER(0); + int4 index = PG_GETARG_INT32(1); Oid element_typeid; TypeCacheEntry *typentry; ArrayAnalyzeExtraData *extra_data; @@ -104,17 +105,17 @@ array_typanalyze(PG_FUNCTION_ARGS) * Call the standard typanalyze function. It may fail to find needed * operators, in which case we also can't do anything, so just fail. */ - if (!std_typanalyze(stats)) + if (!std_typanalyze(stats, index)) PG_RETURN_BOOL(false); /* * Check attribute data type is a varlena array. */ - element_typeid = stats->attrtype->typelem; + element_typeid = stats->attrtypes[index]->typelem; - if (!OidIsValid(element_typeid) || stats->attrtype->typlen != -1) + if (!OidIsValid(element_typeid) || stats->attrtypes[index]->typlen != -1) elog(ERROR, "array_typanalyze was invoked for non-array type %u", - stats->attrtypid); + stats->attrtypids[index]); /* * Gather information about the element type. If we fail to find @@ -141,12 +142,12 @@ array_typanalyze(PG_FUNCTION_ARGS) extra_data->hash = &typentry->hash_proc_finfo; /* Save old compute_stats and extra_data for scalar statistics ... */ - extra_data->std_compute_stats = stats->compute_stats; - extra_data->std_extra_data = stats->extra_data; + extra_data->std_compute_stats = stats->statfuncs[index].compute_func_ptr; + extra_data->std_extra_data = stats->extra_data[index]; /* ... and replace with our info */ - stats->compute_stats = compute_array_stats; - stats->extra_data = extra_data; + stats->statfuncs[index].compute_func_ptr = compute_array_stats; + stats->extra_data[index] = extra_data; /* * Note we leave stats->minrows set as std_typanalyze set it. Should @@ -211,7 +212,7 @@ array_typanalyze(PG_FUNCTION_ARGS) * We divide the raw counts by nonnull_cnt to get those figures. */ static void -compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, +compute_array_stats(VacStats *stats, int index, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows) { ArrayAnalyzeExtraData *extra_data; @@ -238,16 +239,16 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, HASHCTL count_hash_ctl; DECountItem *count_item; - extra_data = (ArrayAnalyzeExtraData *) stats->extra_data; + extra_data = (ArrayAnalyzeExtraData *) stats->extra_data[index]; /* * Invoke analyze.c's standard analysis function to create scalar-style * stats for the column. It will expect its own extra_data pointer, * so temporarily install that. */ - stats->extra_data = extra_data->std_extra_data; - (*extra_data->std_compute_stats) (stats, fetchfunc, samplerows, totalrows); - stats->extra_data = extra_data; + stats->extra_data[index] = extra_data->std_extra_data; + (*extra_data->std_compute_stats) (stats, index, fetchfunc, samplerows, totalrows); + stats->extra_data[index] = extra_data; /* * Set up static pointer for use by subroutines. We wait till here in @@ -262,7 +263,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, * the number of individual elements tracked in pg_statistic ought to be * more than the number of values for a simple scalar column. */ - num_mcelem = stats->attr->attstattarget * 10; + num_mcelem = stats->statarget * 10; /* * We set bucket width equal to num_mcelem / 0.007 as per the comment @@ -318,7 +319,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, vacuum_delay_point(); - value = fetchfunc(stats, array_no, &isnull); + value = fetchfunc(stats, array_no, stats->attnums->values[index], &isnull); if (isnull) { /* array is null, just count that */ @@ -437,7 +438,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, /* Skip pg_statistic slots occupied by standard statistics */ slot_idx = 0; - while (slot_idx < STATISTIC_NUM_SLOTS && stats->stakind[slot_idx] != 0) + while (slot_idx < STATISTIC_NUM_SLOTS && stats->stakind[index][slot_idx] != 0) slot_idx++; if (slot_idx > STATISTIC_NUM_SLOTS - 2) elog(ERROR, "insufficient pg_statistic slots for array stats"); @@ -558,18 +559,18 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, MemoryContextSwitchTo(old_context); - stats->stakind[slot_idx] = STATISTIC_KIND_MCELEM; - stats->staop[slot_idx] = extra_data->eq_opr; - stats->stanumbers[slot_idx] = mcelem_freqs; + stats->stakind[index][slot_idx] = STATISTIC_KIND_MCELEM; + stats->staop[index][slot_idx] = extra_data->eq_opr; + stats->stanumbers[index][slot_idx] = mcelem_freqs; /* See above comment about extra stanumber entries */ - stats->numnumbers[slot_idx] = num_mcelem + 3; - stats->stavalues[slot_idx] = mcelem_values; - stats->numvalues[slot_idx] = num_mcelem; + stats->numnumbers[index][slot_idx] = num_mcelem + 3; + stats->stavalues[index][slot_idx] = mcelem_values; + stats->numvalues[index][slot_idx] = num_mcelem; /* We are storing values of element type */ - stats->statypid[slot_idx] = extra_data->type_id; - stats->statyplen[slot_idx] = extra_data->typlen; - stats->statypbyval[slot_idx] = extra_data->typbyval; - stats->statypalign[slot_idx] = extra_data->typalign; + stats->statypid[index][slot_idx] = extra_data->type_id; + stats->statyplen[index][slot_idx] = extra_data->typlen; + stats->statypbyval[index][slot_idx] = extra_data->typbyval; + stats->statypalign[index][slot_idx] = extra_data->typalign; slot_idx++; } @@ -577,7 +578,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, count_items_count = hash_get_num_entries(count_tab); if (count_items_count > 0) { - int num_hist = stats->attr->attstattarget; + int num_hist = stats->statarget; DECountItem **sorted_count_items; int j; int delta; @@ -659,10 +660,10 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, } Assert(j == count_items_count - 1); - stats->stakind[slot_idx] = STATISTIC_KIND_DECHIST; - stats->staop[slot_idx] = extra_data->eq_opr; - stats->stanumbers[slot_idx] = hist; - stats->numnumbers[slot_idx] = num_hist + 1; + stats->stakind[index][slot_idx] = STATISTIC_KIND_DECHIST; + stats->staop[index][slot_idx] = extra_data->eq_opr; + stats->stanumbers[index][slot_idx] = hist; + stats->numnumbers[index][slot_idx] = num_hist + 1; slot_idx++; } } diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index a339abf..cb10dbe 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -254,21 +254,53 @@ int2vectorsend(PG_FUNCTION_ARGS) return array_send(fcinfo); } -/* - * We don't have a complete set of int2vector support routines, - * but we need int2vectoreq for catcache indexing. - */ Datum int2vectoreq(PG_FUNCTION_ARGS) { - int2vector *a = (int2vector *) PG_GETARG_POINTER(0); - int2vector *b = (int2vector *) PG_GETARG_POINTER(1); + int32 cmp = DatumGetInt32(btint2vectorcmp(fcinfo)); - if (a->dim1 != b->dim1) - PG_RETURN_BOOL(false); - PG_RETURN_BOOL(memcmp(a->values, b->values, a->dim1 * sizeof(int2)) == 0); + PG_RETURN_BOOL(cmp == 0); +} + +Datum +int2vectorne(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btint2vectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp != 0); } +Datum +int2vectorlt(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btint2vectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp < 0); +} + +Datum +int2vectorle(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btint2vectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp <= 0); +} + +Datum +int2vectorge(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btint2vectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp >= 0); +} + +Datum +int2vectorgt(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btint2vectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp > 0); +} /***************************************************************************** * PUBLIC ROUTINES * diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 382cd73..34db4a6 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -94,6 +94,7 @@ #include "access/gin.h" #include "access/sysattr.h" #include "catalog/index.h" +#include "catalog/indexing.h" #include "catalog/pg_collation.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_statistic.h" @@ -112,6 +113,7 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parse_coerce.h" +#include "parser/parse_utilcmd.h" #include "parser/parsetree.h" #include "utils/builtins.h" #include "utils/bytea.h" @@ -4140,6 +4142,34 @@ get_join_variables(PlannerInfo *root, List *args, SpecialJoinInfo *sjinfo, } /* + * validate_statistics -- sets vardata->statsTuple only if the statistics is valid + */ +void +validate_statistics(VariableStatData *vardata, + Oid relid, AttrNumber *attnums, int n_attnums, bool inherited) +{ + int2vector *attnumvector = buildint2vector(attnums, n_attnums); + HeapTuple tuple; + + tuple = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(relid), + PointerGetDatum(attnumvector), + BoolGetDatum(inherited)); + pfree(attnumvector); + + if (HeapTupleIsValid(tuple)) + { + if (((Form_pg_statistic) GETSTRUCT(tuple))->stavalid) + { + vardata->statsTuple = tuple; + vardata->freefunc = ReleaseSysCache; + } + else + ReleaseSysCache(tuple); + } +} + +/* * examine_variable * Try to look up statistical data about an expression. * Fill in a VariableStatData struct to describe the expression. @@ -4177,6 +4207,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, { Node *basenode; Relids varnos; + int onerelid = 0; RelOptInfo *onerel; /* Make sure we don't return dangling pointers in vardata */ @@ -4229,8 +4260,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, case BMS_SINGLETON: if (varRelid == 0 || bms_is_member(varRelid, varnos)) { - onerel = find_base_rel(root, - (varRelid ? varRelid : bms_singleton_member(varnos))); + onerelid = (varRelid ? varRelid : bms_singleton_member(varnos)); + onerel = find_base_rel(root, onerelid); vardata->rel = onerel; node = basenode; /* strip any relabeling */ } @@ -4264,15 +4295,82 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, { /* * We have an expression in vars of a single relation. Try to match - * it to expressional index columns, in hopes of finding some - * statistics. + * it to expression statistics first then to expressional index columns, + * in hopes of finding some statistics. * * XXX it's conceivable that there are multiple matches with different * index opfamilies; if so, we need to pick one that matches the * operator we are estimating for. FIXME later. */ ListCell *ilist; + Node *expr = copyObject(node); + char *exprbin; + Datum exprbindatum; + +#if 0 +#define USE_SYSCACHE_FOR_SEARCH 0 +#if !USE_SYSCACHE_FOR_SEARCH + Relation rel; + ScanKeyData scanKey[2]; + SysScanDesc scan; + HeapTuple tuple; +#endif +#endif + + /* FIXME: + * query_or_expression_tree_walker(expr, set_location_unknown_walker, NULL, 0);*/ + exprbin = nodeToString(expr); + exprbindatum = CStringGetTextDatum(exprbin); + +#if 0 +#if USE_SYSCACHE_FOR_SEARCH + vardata->statsTuple = SearchSysCache3(STAT3RELEXPRINH, + ObjectIdGetDatum(root->simple_rte_array[onerelid]->relid), + exprbindatum, + BoolGetDatum(false)); + if (HeapTupleIsValid(vardata->statsTuple)) + { + vardata->stats_type = STAT_EXPRESSION; + vardata->freefunc = ReleaseSysCache; + return; + } +#else + + rel = heap_open(StatisticRelationId, RowShareLock); + + ScanKeyInit(&scanKey[0], + Anum_pg_statistic3_sta3relid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(root->simple_rte_array[onerelid]->relid)); + ScanKeyInit(&scanKey[1], + Anum_pg_statistic3_sta3expr, + BTEqualStrategyNumber, F_TEXTEQ, + exprbindatum); + + scan = systable_beginscan(rel, Statistic3RelidExprInhIndexId, true, + SnapshotNow, 2, scanKey); + + tuple = systable_getnext(scan); + if (HeapTupleIsValid(tuple)) + { +// elog(NOTICE, "examine_variable expression found"); + vardata->stats_type = STAT_EXPRESSION; + vardata->statsTuple = heap_copytuple(tuple); + vardata->freefunc = heap_freetuple; + } + + systable_endscan(scan); + + pfree(exprbin); + pfree(DatumGetPointer(exprbindatum)); + + relation_close(rel, RowShareLock); + + if (vardata->statsTuple) + return; +#endif +#endif foreach(ilist, onerel->indexlist) { IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); @@ -4330,12 +4428,9 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, } else if (index->indpred == NIL) { - vardata->statsTuple = - SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(index->indexoid), - Int16GetDatum(pos + 1), - BoolGetDatum(false)); - vardata->freefunc = ReleaseSysCache; + int2 attnum = pos + 1; + + validate_statistics(vardata, index->indexoid, &attnum, 1, false); } if (vardata->statsTuple) break; @@ -4383,11 +4478,7 @@ examine_simple_variable(PlannerInfo *root, Var *var, * Plain table or parent of an inheritance appendrel, so look up the * column in pg_statistic */ - vardata->statsTuple = SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(rte->relid), - Int16GetDatum(var->varattno), - BoolGetDatum(rte->inh)); - vardata->freefunc = ReleaseSysCache; + validate_statistics(vardata, rte->relid, &(var->varattno), 1, rte->inh); } else if (rte->rtekind == RTE_SUBQUERY && !rte->inh) { @@ -6514,13 +6605,7 @@ btcostestimate(PG_FUNCTION_ARGS) elog(ERROR, "no function provided to release variable stats with"); } else - { - vardata.statsTuple = SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(relid), - Int16GetDatum(colnum), - BoolGetDatum(rte->inh)); - vardata.freefunc = ReleaseSysCache; - } + validate_statistics(&vardata, relid, &colnum, 1, rte->inh); } else { @@ -6540,13 +6625,7 @@ btcostestimate(PG_FUNCTION_ARGS) elog(ERROR, "no function provided to release variable stats with"); } else - { - vardata.statsTuple = SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(relid), - Int16GetDatum(colnum), - BoolGetDatum(false)); - vardata.freefunc = ReleaseSysCache; - } + validate_statistics(&vardata, relid, &colnum, 1, false); } if (HeapTupleIsValid(vardata.statsTuple)) diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 44dab82..6391d92 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -2674,6 +2674,7 @@ get_attavgwidth(Oid relid, AttrNumber attnum) { HeapTuple tp; int32 stawidth; + int2vector *attnumvector = NULL; if (get_attavgwidth_hook) { @@ -2681,10 +2682,12 @@ get_attavgwidth(Oid relid, AttrNumber attnum) if (stawidth > 0) return stawidth; } + attnumvector = buildint2vector(&attnum, 1); tp = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(relid), - Int16GetDatum(attnum), + PointerGetDatum(attnumvector), BoolGetDatum(false)); + pfree(attnumvector); if (HeapTupleIsValid(tp)) { stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth; @@ -2735,7 +2738,7 @@ get_attstatsslot(HeapTuple statstuple, Datum **values, int *nvalues, float4 **numbers, int *nnumbers) { - Form_pg_statistic stats = (Form_pg_statistic) GETSTRUCT(statstuple); + Form_pg_statistic stats; int i, j; Datum val; @@ -2746,12 +2749,15 @@ get_attstatsslot(HeapTuple statstuple, HeapTuple typeTuple; Form_pg_type typeForm; + stats = (Form_pg_statistic) GETSTRUCT(statstuple); + for (i = 0; i < STATISTIC_NUM_SLOTS; i++) { if ((&stats->stakind1)[i] == reqkind && (reqop == InvalidOid || (&stats->staop1)[i] == reqop)) break; } + if (i >= STATISTIC_NUM_SLOTS) return false; /* not there */ @@ -2763,8 +2769,9 @@ get_attstatsslot(HeapTuple statstuple, val = SysCacheGetAttr(STATRELATTINH, statstuple, Anum_pg_statistic_stavalues1 + i, &isnull); + /* invalid stats record, i.e. analyze hasn't yet run for this column */ if (isnull) - elog(ERROR, "stavalues is null"); + return false; statarray = DatumGetArrayTypeP(val); /* @@ -2817,8 +2824,9 @@ get_attstatsslot(HeapTuple statstuple, val = SysCacheGetAttr(STATRELATTINH, statstuple, Anum_pg_statistic_stanumbers1 + i, &isnull); + /* invalid stats record, i.e. analyze hasn't yet run for this column */ if (isnull) - elog(ERROR, "stanumbers is null"); + return false; statarray = DatumGetArrayTypeP(val); /* diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index a59950e..9da6ed8 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -4333,6 +4333,7 @@ write_relcache_init_file(bool shared) write_item(relform, CLASS_TUPLE_SIZE, fp); /* next, do all the attribute tuple form data entries */ + elog(LOG, "write_relcache_init_file relname '%s' relnatts %d", NameStr(relform->relname), relform->relnatts); for (i = 0; i < relform->relnatts; i++) { write_item(rel->rd_att->attrs[i], ATTRIBUTE_FIXED_PART_SIZE, fp); diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 78ce0b8..f7c1ba0 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -600,11 +600,11 @@ static const struct cachedesc cacheinfo[] = { 1024 }, {StatisticRelationId, /* STATRELATTINH */ - StatisticRelidAttnumInhIndexId, + StatisticRelidAttnumsInhIndexId, 3, { Anum_pg_statistic_starelid, - Anum_pg_statistic_staattnum, + Anum_pg_statistic_staattnums, Anum_pg_statistic_stainherit, 0 }, diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 99cf5b4..bb95a6d 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -119,6 +119,12 @@ static bool noclean = false; static bool show_setting = false; static char *xlog_dir = ""; +#ifdef DEVNULL +#undef DEVNULL +#endif +#define DEVNULL "/tmp/initdb.log" + +#define DEVNULLINPUT "/dev/zero" /* internal vars */ static const char *progname; @@ -165,7 +171,7 @@ static char *authwarning = NULL; * (no quoting to worry about). */ static const char *boot_options = "-F"; -static const char *backend_options = "--single -F -O -c search_path=pg_catalog -c exit_on_error=true"; +static const char *backend_options = "--single -F -O -c search_path=pg_catalog -c exit_on_error=true -c log_min_messages=warning -c log_min_error_statement=info "; /* path to 'initdb' binary directory */ @@ -924,7 +930,7 @@ test_config_settings(void) "< \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE, backend_exec, boot_options, test_conns, test_buffs, - DEVNULL, DEVNULL); + DEVNULLINPUT, DEVNULL); status = system(cmd); if (status == 0) { @@ -958,7 +964,7 @@ test_config_settings(void) "< \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE, backend_exec, boot_options, n_connections, test_buffs, - DEVNULL, DEVNULL); + DEVNULLINPUT, DEVNULL); status = system(cmd); if (status == 0) break; @@ -1273,7 +1279,7 @@ setup_auth(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1351,7 +1357,7 @@ get_set_pwd(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1451,7 +1457,7 @@ setup_depend(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1484,7 +1490,7 @@ setup_sysviews(void) * We use -j here to avoid backslashing stuff in system_views.sql */ snprintf(cmd, sizeof(cmd), - "\"%s\" %s -j template1 >%s", + "\"%s\" %s -j template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1515,7 +1521,7 @@ setup_description(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1622,7 +1628,7 @@ setup_collation(void) #if defined(HAVE_LOCALE_T) && !defined(WIN32) snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1761,7 +1767,7 @@ setup_conversion(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1799,7 +1805,7 @@ setup_dictionary(void) * We use -j here to avoid backslashing stuff */ snprintf(cmd, sizeof(cmd), - "\"%s\" %s -j template1 >%s", + "\"%s\" %s -j template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1850,7 +1856,7 @@ setup_privileges(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1913,7 +1919,7 @@ setup_schema(void) * We use -j here to avoid backslashing stuff in information_schema.sql */ snprintf(cmd, sizeof(cmd), - "\"%s\" %s -j template1 >%s", + "\"%s\" %s -j template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1930,7 +1936,7 @@ setup_schema(void) PG_CMD_CLOSE; snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1964,7 +1970,7 @@ load_plpgsql(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -1989,7 +1995,7 @@ vacuum_db(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -2045,7 +2051,7 @@ make_template0(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); @@ -2077,7 +2083,7 @@ make_postgres(void) fflush(stdout); snprintf(cmd, sizeof(cmd), - "\"%s\" %s template1 >%s", + "\"%s\" %s template1 >>%s 2>&1", backend_exec, backend_options, DEVNULL); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 4eee4be..42153de 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1300,7 +1300,8 @@ describeOneTableDetails(const char *schemaname, if (verbose) { appendPQExpBuffer(&buf, ",\n a.attstorage"); - appendPQExpBuffer(&buf, ",\n CASE WHEN a.attstattarget=-1 THEN NULL ELSE a.attstattarget END AS attstattarget"); + /* FIXME: need stat target value here*/ + appendPQExpBuffer(&buf, ",\n NULL AS attstattarget"); /* * In 9.0+, we have column comments for: relations, views, composite * types, and foreign tables (c.f. CommentObject() in comment.c). diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index 2055382..b0a3009 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -109,7 +109,12 @@ extern void RemoveAttributeById(Oid relid, AttrNumber attnum); extern void RemoveAttrDefault(Oid relid, AttrNumber attnum, DropBehavior behavior, bool complain, bool internal); extern void RemoveAttrDefaultById(Oid attrdefId); -extern void RemoveStatistics(Oid relid, AttrNumber attnum); +extern void AddStatistics(Oid relid, AttrNumber *attnums, + int n_attnums, + bool inherited, + int statistics_target); +extern void InvalidateStatistics(Oid relid, AttrNumber attnum); +extern void RemoveStatistics(Oid relid, AttrNumber *attnums, int n_attnums); extern Form_pg_attribute SystemAttributeDefinition(AttrNumber attno, bool relhasoids); diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 450ec25..90a5c7b 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -218,8 +218,8 @@ DECLARE_INDEX(pg_shdepend_depender_index, 1232, on pg_shdepend using btree(dbid DECLARE_INDEX(pg_shdepend_reference_index, 1233, on pg_shdepend using btree(refclassid oid_ops, refobjid oid_ops)); #define SharedDependReferenceIndexId 1233 -DECLARE_UNIQUE_INDEX(pg_statistic_relid_att_inh_index, 2696, on pg_statistic using btree(starelid oid_ops, staattnum int2_ops, stainherit bool_ops)); -#define StatisticRelidAttnumInhIndexId 2696 +DECLARE_UNIQUE_INDEX(pg_statistic_relid_att_inh_index, 2696, on pg_statistic using btree(starelid oid_ops, staattnums int2vector_ops, stainherit bool_ops)); +#define StatisticRelidAttnumsInhIndexId 2696 DECLARE_UNIQUE_INDEX(pg_tablespace_oid_index, 2697, on pg_tablespace using btree(oid oid_ops)); #define TablespaceOidIndexId 2697 diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h index 82391b0..b6d9cd6 100644 --- a/src/include/catalog/pg_amop.h +++ b/src/include/catalog/pg_amop.h @@ -185,6 +185,16 @@ DATA(insert ( 1991 30 30 4 s 648 403 0 )); DATA(insert ( 1991 30 30 5 s 646 403 0 )); /* + * btree int2vector_ops + */ + +DATA(insert ( 3171 22 22 1 s 3166 403 0 )); +DATA(insert ( 3171 22 22 2 s 3169 403 0 )); +DATA(insert ( 3171 22 22 3 s 386 403 0 )); +DATA(insert ( 3171 22 22 4 s 3170 403 0 )); +DATA(insert ( 3171 22 22 5 s 3167 403 0 )); + +/* * btree float_ops */ diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index 72307de..191270c 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -133,6 +133,7 @@ DATA(insert ( 2233 703 703 1 380 )); DATA(insert ( 2234 704 704 1 381 )); DATA(insert ( 2789 27 27 1 2794 )); DATA(insert ( 2968 2950 2950 1 2960 )); +DATA(insert ( 3171 22 22 1 3168 )); DATA(insert ( 3522 3500 3500 1 3514 )); diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index 45e38e4..083629c 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -48,15 +48,6 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK Oid atttypid; /* - * attstattarget is the target number of statistics datapoints to collect - * during VACUUM ANALYZE of this column. A zero here indicates that we do - * not wish to collect any stats about this column. A "-1" here indicates - * that no value has been explicitly set for this column, so ANALYZE - * should use the default setting. - */ - int4 attstattarget; - - /* * attlen is a copy of the typlen field from pg_type for this attribute. * See atttypid comments above. */ @@ -180,28 +171,27 @@ typedef FormData_pg_attribute *Form_pg_attribute; * ---------------- */ -#define Natts_pg_attribute 21 +#define Natts_pg_attribute 20 #define Anum_pg_attribute_attrelid 1 #define Anum_pg_attribute_attname 2 #define Anum_pg_attribute_atttypid 3 -#define Anum_pg_attribute_attstattarget 4 -#define Anum_pg_attribute_attlen 5 -#define Anum_pg_attribute_attnum 6 -#define Anum_pg_attribute_attndims 7 -#define Anum_pg_attribute_attcacheoff 8 -#define Anum_pg_attribute_atttypmod 9 -#define Anum_pg_attribute_attbyval 10 -#define Anum_pg_attribute_attstorage 11 -#define Anum_pg_attribute_attalign 12 -#define Anum_pg_attribute_attnotnull 13 -#define Anum_pg_attribute_atthasdef 14 -#define Anum_pg_attribute_attisdropped 15 -#define Anum_pg_attribute_attislocal 16 -#define Anum_pg_attribute_attinhcount 17 -#define Anum_pg_attribute_attcollation 18 -#define Anum_pg_attribute_attacl 19 -#define Anum_pg_attribute_attoptions 20 -#define Anum_pg_attribute_attfdwoptions 21 +#define Anum_pg_attribute_attlen 4 +#define Anum_pg_attribute_attnum 5 +#define Anum_pg_attribute_attndims 6 +#define Anum_pg_attribute_attcacheoff 7 +#define Anum_pg_attribute_atttypmod 8 +#define Anum_pg_attribute_attbyval 9 +#define Anum_pg_attribute_attstorage 10 +#define Anum_pg_attribute_attalign 11 +#define Anum_pg_attribute_attnotnull 12 +#define Anum_pg_attribute_atthasdef 13 +#define Anum_pg_attribute_attisdropped 14 +#define Anum_pg_attribute_attislocal 15 +#define Anum_pg_attribute_attinhcount 16 +#define Anum_pg_attribute_attcollation 17 +#define Anum_pg_attribute_attacl 18 +#define Anum_pg_attribute_attoptions 19 +#define Anum_pg_attribute_attfdwoptions 20 /* ---------------- diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 1567206..4df03b4 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -132,7 +132,7 @@ typedef FormData_pg_class *Form_pg_class; /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */ DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ )); DESCR(""); -DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ )); +DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 20 0 f f f f f 3 _null_ _null_ )); DESCR(""); DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ )); DESCR(""); diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h index 96eaa60..94b74ce 100644 --- a/src/include/catalog/pg_opclass.h +++ b/src/include/catalog/pg_opclass.h @@ -165,6 +165,7 @@ DATA(insert ( 403 bpchar_pattern_ops PGNSP PGUID 2097 1042 f 0 )); DATA(insert ( 403 money_ops PGNSP PGUID 2099 790 t 0 )); DATA(insert ( 405 bool_ops PGNSP PGUID 2222 16 t 0 )); DATA(insert ( 405 bytea_ops PGNSP PGUID 2223 17 t 0 )); +DATA(insert ( 403 int2vector_ops PGNSP PGUID 3171 22 t 0 )); DATA(insert ( 405 int2vector_ops PGNSP PGUID 2224 22 t 0 )); DATA(insert ( 403 tid_ops PGNSP PGUID 2789 27 t 0 )); DATA(insert ( 405 xid_ops PGNSP PGUID 2225 28 t 0 )); diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index 48ddd16..c0b19b2 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -134,6 +134,19 @@ DATA(insert OID = 98 ( "=" PGNSP PGUID b t t 25 25 16 98 531 texteq eqsel e DESCR("equal"); #define TextEqualOperator 98 +DATA(insert OID = 386 ( "=" PGNSP PGUID b t t 22 22 16 386 3165 int2vectoreq eqsel eqjoinsel )); +DESCR("equal"); +DATA(insert OID = 3165 ( "<>" PGNSP PGUID b f f 22 22 16 3165 386 int2vectorne neqsel neqjoinsel )); +DESCR("not equal"); +DATA(insert OID = 3166 ( "<" PGNSP PGUID b f f 22 22 16 3167 3170 int2vectorlt scalarltsel scalarltjoinsel )); +DESCR("less than"); +DATA(insert OID = 3167 ( ">" PGNSP PGUID b f f 22 22 16 3166 3169 int2vectorgt scalargtsel scalargtjoinsel )); +DESCR("greater than"); +DATA(insert OID = 3169 ( "<=" PGNSP PGUID b f f 22 22 16 3170 3167 int2vectorle scalarltsel scalarltjoinsel )); +DESCR("less than or equal"); +DATA(insert OID = 3170 ( ">=" PGNSP PGUID b f f 22 22 16 3169 3166 int2vectorge scalargtsel scalargtjoinsel )); +DESCR("greater than or equal"); + DATA(insert OID = 349 ( "||" PGNSP PGUID b f f 2277 2283 2277 0 0 array_append - - )); DESCR("append element onto end of array"); DATA(insert OID = 374 ( "||" PGNSP PGUID b f f 2283 2277 2277 0 0 array_prepend - - )); @@ -151,8 +164,6 @@ DATA(insert OID = 389 ( "!!" PGNSP PGUID l f f 0 20 1700 0 0 numeric_fac DESCR("deprecated, use ! instead"); DATA(insert OID = 385 ( "=" PGNSP PGUID b f t 29 29 16 385 0 cideq eqsel eqjoinsel )); DESCR("equal"); -DATA(insert OID = 386 ( "=" PGNSP PGUID b f t 22 22 16 386 0 int2vectoreq eqsel eqjoinsel )); -DESCR("equal"); DATA(insert OID = 387 ( "=" PGNSP PGUID b t f 27 27 16 387 402 tideq eqsel eqjoinsel )); DESCR("equal"); diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index 41ebccc..69b67ec 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -114,6 +114,7 @@ DATA(insert OID = 2099 ( 403 money_ops PGNSP PGUID )); DATA(insert OID = 2222 ( 405 bool_ops PGNSP PGUID )); #define BOOL_HASH_FAM_OID 2222 DATA(insert OID = 2223 ( 405 bytea_ops PGNSP PGUID )); +DATA(insert OID = 3171 ( 403 int2vector_ops PGNSP PGUID )); DATA(insert OID = 2224 ( 405 int2vector_ops PGNSP PGUID )); DATA(insert OID = 2789 ( 403 tid_ops PGNSP PGUID )); DATA(insert OID = 2225 ( 405 xid_ops PGNSP PGUID )); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 2db8489..541f487 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -220,6 +220,13 @@ DESCR("length"); DATA(insert OID = 1258 ( textcat PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ textcat _null_ _null_ _null_ )); DATA(insert OID = 84 ( boolne PGNSP PGUID 12 1 0 0 0 f f f t t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolne _null_ _null_ _null_ )); + +DATA(insert OID = 86 ( int2vectorne PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorne _null_ _null_ _null_ )); +DATA(insert OID = 87 ( int2vectorlt PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorlt _null_ _null_ _null_ )); +DATA(insert OID = 88 ( int2vectorle PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorle _null_ _null_ _null_ )); +DATA(insert OID = 90 ( int2vectorge PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorge _null_ _null_ _null_ )); +DATA(insert OID = 3172 ( int2vectorgt PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorgt _null_ _null_ _null_ )); + DATA(insert OID = 89 ( version PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 25 "" _null_ _null_ _null_ _null_ pgsql_version _null_ _null_ _null_ )); DESCR("PostgreSQL version string"); @@ -574,6 +581,8 @@ DATA(insert OID = 350 ( btint2cmp PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 DESCR("less-equal-greater"); DATA(insert OID = 3129 ( btint2sortsupport PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2278 "2281" _null_ _null_ _null_ _null_ btint2sortsupport _null_ _null_ _null_ )); DESCR("sort support"); +DATA(insert OID = 3168 ( btint2vectorcmp PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "22 22" _null_ _null_ _null_ _null_ btint2vectorcmp _null_ _null_ _null_ )); +DESCR("less-equal-greater"); DATA(insert OID = 351 ( btint4cmp PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ btint4cmp _null_ _null_ _null_ )); DESCR("less-equal-greater"); DATA(insert OID = 3130 ( btint4sortsupport PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2278 "2281" _null_ _null_ _null_ _null_ btint4sortsupport _null_ _null_ _null_ )); @@ -2936,7 +2945,7 @@ DESCR("xlog filename and byte offset, given an xlog location"); DATA(insert OID = 2851 ( pg_xlogfile_name PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "25" _null_ _null_ _null_ _null_ pg_xlogfile_name _null_ _null_ _null_ )); DESCR("xlog filename, given an xlog location"); -DATA(insert OID = 3165 ( pg_xlog_location_diff PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "25 25" _null_ _null_ _null_ _null_ pg_xlog_location_diff _null_ _null_ _null_ )); +DATA(insert OID = 3160 ( pg_xlog_location_diff PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "25 25" _null_ _null_ _null_ _null_ pg_xlog_location_diff _null_ _null_ _null_ )); DESCR("difference in bytes, given two xlog locations"); DATA(insert OID = 3809 ( pg_export_snapshot PGNSP PGUID 12 1 0 0 0 f f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_export_snapshot _null_ _null_ _null_ )); @@ -4246,7 +4255,7 @@ DATA(insert OID = 3686 ( tsmatchsel PGNSP PGUID 12 1 0 0 0 f f f f t f s 4 0 7 DESCR("restriction selectivity of tsvector @@ tsquery"); DATA(insert OID = 3687 ( tsmatchjoinsel PGNSP PGUID 12 1 0 0 0 f f f f t f s 5 0 701 "2281 26 2281 21 2281" _null_ _null_ _null_ _null_ tsmatchjoinsel _null_ _null_ _null_ )); DESCR("join selectivity of tsvector @@ tsquery"); -DATA(insert OID = 3688 ( ts_typanalyze PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "2281" _null_ _null_ _null_ _null_ ts_typanalyze _null_ _null_ _null_ )); +DATA(insert OID = 3688 ( ts_typanalyze PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "2281 23" _null_ _null_ _null_ _null_ ts_typanalyze _null_ _null_ _null_ )); DESCR("tsvector typanalyze"); DATA(insert OID = 3689 ( ts_stat PGNSP PGUID 12 10 10000 0 0 f f f f t t v 1 0 2249 "25" "{25,25,23,23}" "{i,o,o,o}" "{query,word,ndoc,nentry}" _null_ ts_stat1 _null_ _null_ _null_ )); @@ -4515,7 +4524,7 @@ DATA(insert OID = 3881 ( range_gist_same PGNSP PGUID 12 1 0 0 0 f f f f t f i DESCR("GiST support"); DATA(insert OID = 3902 ( hash_range PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "3831" _null_ _null_ _null_ _null_ hash_range _null_ _null_ _null_ )); DESCR("hash a range"); -DATA(insert OID = 3916 ( range_typanalyze PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "2281" _null_ _null_ _null_ _null_ range_typanalyze _null_ _null_ _null_ )); +DATA(insert OID = 3916 ( range_typanalyze PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "2281 23" _null_ _null_ _null_ _null_ range_typanalyze _null_ _null_ _null_ )); DESCR("range typanalyze"); DATA(insert OID = 3914 ( int4range_canonical PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3904 "3904" _null_ _null_ _null_ _null_ int4range_canonical _null_ _null_ _null_ )); diff --git a/src/include/catalog/pg_statistic.h b/src/include/catalog/pg_statistic.h index 383cc01..c35198a 100644 --- a/src/include/catalog/pg_statistic.h +++ b/src/include/catalog/pg_statistic.h @@ -30,11 +30,24 @@ CATALOG(pg_statistic,2619) BKI_WITHOUT_OIDS { - /* These fields form the unique key for the entry: */ + /* + * These fields (together with the staattnums field below in the variable fields) + * form the unique key for the entry: + */ Oid starelid; /* relation containing attribute */ - int2 staattnum; /* attribute (column) stats are for */ bool stainherit; /* true if inheritance children are included */ + /* this entry is valid */ + bool stavalid; + + /* statarget is the target number of statistics datapoints to collect + * during VACUUM ANALYZE of this column. A zero here indicates that we do + * not wish to collect any stats about this column. A "-1" here indicates + * that no value has been explicitly set for this column, so ANALYZE + * should use the default setting. + */ + int4 statarget; + /* the fraction of the column's entries that are NULL: */ float4 stanullfrac; @@ -96,7 +109,15 @@ CATALOG(pg_statistic,2619) BKI_WITHOUT_OIDS Oid staop4; Oid staop5; -#ifdef CATALOG_VARLEN /* variable-length fields start here */ +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + /* + * THE REST OF THESE ARE VARIABLE LENGTH FIELDS, and may even be absent + * (NULL). They cannot be accessed as C struct entries; you have to use + * the full field access machinery (heap_getattr) for them. We declare + * them here for the catalog machinery. + */ + int2vector staattnums; /* attributes (columns) stats are for */ + float4 stanumbers1[1]; float4 stanumbers2[1]; float4 stanumbers3[1]; @@ -131,33 +152,35 @@ typedef FormData_pg_statistic *Form_pg_statistic; * compiler constants for pg_statistic * ---------------- */ -#define Natts_pg_statistic 26 +#define Natts_pg_statistic 28 #define Anum_pg_statistic_starelid 1 -#define Anum_pg_statistic_staattnum 2 -#define Anum_pg_statistic_stainherit 3 -#define Anum_pg_statistic_stanullfrac 4 -#define Anum_pg_statistic_stawidth 5 -#define Anum_pg_statistic_stadistinct 6 -#define Anum_pg_statistic_stakind1 7 -#define Anum_pg_statistic_stakind2 8 -#define Anum_pg_statistic_stakind3 9 -#define Anum_pg_statistic_stakind4 10 -#define Anum_pg_statistic_stakind5 11 -#define Anum_pg_statistic_staop1 12 -#define Anum_pg_statistic_staop2 13 -#define Anum_pg_statistic_staop3 14 -#define Anum_pg_statistic_staop4 15 -#define Anum_pg_statistic_staop5 16 -#define Anum_pg_statistic_stanumbers1 17 -#define Anum_pg_statistic_stanumbers2 18 -#define Anum_pg_statistic_stanumbers3 19 -#define Anum_pg_statistic_stanumbers4 20 -#define Anum_pg_statistic_stanumbers5 21 -#define Anum_pg_statistic_stavalues1 22 -#define Anum_pg_statistic_stavalues2 23 -#define Anum_pg_statistic_stavalues3 24 -#define Anum_pg_statistic_stavalues4 25 -#define Anum_pg_statistic_stavalues5 26 +#define Anum_pg_statistic_stainherit 2 +#define Anum_pg_statistic_stavalid 3 +#define Anum_pg_statistic_statarget 4 +#define Anum_pg_statistic_stanullfrac 5 +#define Anum_pg_statistic_stawidth 6 +#define Anum_pg_statistic_stadistinct 7 +#define Anum_pg_statistic_stakind1 8 +#define Anum_pg_statistic_stakind2 9 +#define Anum_pg_statistic_stakind3 10 +#define Anum_pg_statistic_stakind4 11 +#define Anum_pg_statistic_stakind5 12 +#define Anum_pg_statistic_staop1 13 +#define Anum_pg_statistic_staop2 14 +#define Anum_pg_statistic_staop3 15 +#define Anum_pg_statistic_staop4 16 +#define Anum_pg_statistic_staop5 17 +#define Anum_pg_statistic_staattnums 18 +#define Anum_pg_statistic_stanumbers1 19 +#define Anum_pg_statistic_stanumbers2 20 +#define Anum_pg_statistic_stanumbers3 21 +#define Anum_pg_statistic_stanumbers4 22 +#define Anum_pg_statistic_stanumbers5 23 +#define Anum_pg_statistic_stavalues1 24 +#define Anum_pg_statistic_stavalues2 25 +#define Anum_pg_statistic_stavalues3 26 +#define Anum_pg_statistic_stavalues4 27 +#define Anum_pg_statistic_stavalues5 28 /* * Currently, five statistical slot "kinds" are defined by core PostgreSQL, diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 163b2ea..f57be6a 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -91,6 +91,9 @@ extern void DefineAggregate(List *name, List *args, bool oldstyle, extern void RenameAggregate(List *name, List *args, const char *newname); extern void AlterAggregateOwner(List *name, List *args, Oid newOwnerId); +/* commands/analyze.c */ +extern void ExtraStatistics(ExtraStatStmt *stmt); + /* commands/opclasscmds.c */ extern void DefineOpClass(CreateOpClassStmt *stmt); extern void DefineOpFamily(CreateOpFamilyStmt *stmt); diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 3deee66..45bb99e 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -56,17 +56,29 @@ * This might change in some future release. *---------- */ -typedef struct VacAttrStats *VacAttrStatsP; +typedef struct VacStats *VacStatsP; -typedef Datum (*AnalyzeAttrFetchFunc) (VacAttrStatsP stats, int rownum, +typedef Datum (*AnalyzeAttrFetchFunc) (VacStatsP stats, int rownum, AttrNumber tupattnum, bool *isNull); -typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats, - AnalyzeAttrFetchFunc fetchfunc, - int samplerows, - double totalrows); +typedef void (*ComputeStatsFunc) (VacStatsP stats, + int index, + AnalyzeAttrFetchFunc fetchfunc, + int samplerows, + double totalrows); -typedef struct VacAttrStats +typedef int (*FindValueIndex) (VacStatsP stats, + int rownum, + int index, + AnalyzeAttrFetchFunc fetchfunc, + void *arg); + +typedef struct StatsFuncStruct { + ComputeStatsFunc compute_func_ptr; + FindValueIndex findval_func_ptr; +} StatsFuncStruct; + +typedef struct VacStats { /* * These fields are set up by the main ANALYZE code before invoking the @@ -78,34 +90,36 @@ typedef struct VacAttrStats * column/expression. Instead use attrtypid, attrtypmod, and attrtype for * information about the datatype being fed to the typanalyze function. */ - Form_pg_attribute attr; /* copy of pg_attribute row for column */ - Oid attrtypid; /* type of data being analyzed */ - int32 attrtypmod; /* typmod of data being analyzed */ - Form_pg_type attrtype; /* copy of pg_type row for attrtypid */ + Form_pg_attribute attrs[STATISTIC_NUM_SLOTS]; /* copy of pg_attribute row for column */ + int2vector *attnums; /* array of attributes this statistics is for */ + int4 statarget; /* effective statistics target */ + Oid attrtypids[STATISTIC_NUM_SLOTS]; /* type of data being analyzed */ + int32 attrtypmods[STATISTIC_NUM_SLOTS]; /* typmod of data being analyzed */ + Form_pg_type attrtypes[STATISTIC_NUM_SLOTS]; /* copy of pg_type row for attrtypid */ MemoryContext anl_context; /* where to save long-lived data */ /* * These fields must be filled in by the typanalyze routine, unless it * returns FALSE. */ - AnalyzeAttrComputeStatsFunc compute_stats; /* function pointer */ + StatsFuncStruct statfuncs[STATISTIC_NUM_SLOTS]; int minrows; /* Minimum # of rows wanted for stats */ - void *extra_data; /* for extra type-specific data */ + void *extra_data[STATISTIC_NUM_SLOTS]; /* for extra type-specific data */ /* * These fields are to be filled in by the compute_stats routine. (They * are initialized to zero when the struct is created.) */ bool stats_valid; - float4 stanullfrac; /* fraction of entries that are NULL */ - int4 stawidth; /* average width of column values */ - float4 stadistinct; /* # distinct values */ - int2 stakind[STATISTIC_NUM_SLOTS]; - Oid staop[STATISTIC_NUM_SLOTS]; - int numnumbers[STATISTIC_NUM_SLOTS]; - float4 *stanumbers[STATISTIC_NUM_SLOTS]; - int numvalues[STATISTIC_NUM_SLOTS]; - Datum *stavalues[STATISTIC_NUM_SLOTS]; + float4 stanullfrac[STATISTIC_NUM_SLOTS]; /* fraction of entries that are NULL */ + int4 stawidth[STATISTIC_NUM_SLOTS]; /* average width of column values */ + float4 stadistinct[STATISTIC_NUM_SLOTS]; /* # distinct values */ + int2 stakind[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; + Oid staop[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; + int numnumbers[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; + float4 *stanumbers[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; + int numvalues[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; + Datum *stavalues[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; /* * These fields describe the stavalues[n] element types. They will be @@ -113,22 +127,21 @@ typedef struct VacAttrStats * want to store an array of something other than the analyzed column's * elements. It should then overwrite these fields. */ - Oid statypid[STATISTIC_NUM_SLOTS]; - int2 statyplen[STATISTIC_NUM_SLOTS]; - bool statypbyval[STATISTIC_NUM_SLOTS]; - char statypalign[STATISTIC_NUM_SLOTS]; + Oid statypid[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; + int2 statyplen[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; + bool statypbyval[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; + char statypalign[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS]; /* * These fields are private to the main ANALYZE code and should not be * looked at by type-specific functions. */ - int tupattnum; /* attribute number within tuples */ HeapTuple *rows; /* access info for std fetch function */ TupleDesc tupDesc; Datum *exprvals; /* access info for index fetch function */ bool *exprnulls; int rowstride; -} VacAttrStats; +} VacStats; /* GUC parameters */ @@ -169,6 +182,6 @@ extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, /* in commands/analyze.c */ extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy); -extern bool std_typanalyze(VacAttrStats *stats); +extern bool std_typanalyze(VacStats *stats, int index); #endif /* VACUUM_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 905458f..3f1ff9c 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -355,6 +355,7 @@ typedef enum NodeTag T_CreateExtensionStmt, T_AlterExtensionStmt, T_AlterExtensionContentsStmt, + T_ExtraStatStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ab55639..8c4340c 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1162,6 +1162,20 @@ typedef enum DropBehavior } DropBehavior; /* ---------------------- + * Create Cross Column Statistics + * ---------------------- + */ +typedef struct ExtraStatStmt +{ + NodeTag type; + char relkind; + bool create; + RangeVar *relation; + List *columns; + int statistics_target; +} ExtraStatStmt; + +/* ---------------------- * Alter Table * ---------------------- */ diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 4ad793a..541d8d1 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -25,4 +25,7 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt); +extern ExtraStatStmt *transformExtraStatistics(ExtraStatStmt *stmt, + const char *queryString); + #endif /* PARSE_UTILCMD_H */ diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 9fda7ad..0a34097 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -180,6 +180,11 @@ extern Datum int2vectorout(PG_FUNCTION_ARGS); extern Datum int2vectorrecv(PG_FUNCTION_ARGS); extern Datum int2vectorsend(PG_FUNCTION_ARGS); extern Datum int2vectoreq(PG_FUNCTION_ARGS); +extern Datum int2vectorne(PG_FUNCTION_ARGS); +extern Datum int2vectorlt(PG_FUNCTION_ARGS); +extern Datum int2vectorle(PG_FUNCTION_ARGS); +extern Datum int2vectorgt(PG_FUNCTION_ARGS); +extern Datum int2vectorge(PG_FUNCTION_ARGS); extern Datum int4in(PG_FUNCTION_ARGS); extern Datum int4out(PG_FUNCTION_ARGS); extern Datum int4recv(PG_FUNCTION_ARGS); @@ -289,6 +294,7 @@ extern void pg_lltoa(int64 ll, char *a); */ extern Datum btboolcmp(PG_FUNCTION_ARGS); extern Datum btint2cmp(PG_FUNCTION_ARGS); +extern Datum btint2vectorcmp(PG_FUNCTION_ARGS); extern Datum btint4cmp(PG_FUNCTION_ARGS); extern Datum btint8cmp(PG_FUNCTION_ARGS); extern Datum btfloat4cmp(PG_FUNCTION_ARGS); diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h index 4529f27..0b90312 100644 --- a/src/include/utils/selfuncs.h +++ b/src/include/utils/selfuncs.h @@ -107,6 +107,10 @@ typedef bool (*get_index_stats_hook_type) (PlannerInfo *root, VariableStatData *vardata); extern PGDLLIMPORT get_index_stats_hook_type get_index_stats_hook; +extern void validate_statistics(VariableStatData *vardata, + Oid relid, + AttrNumber *attnums, int n_attnums, + bool inherited); /* Functions in selfuncs.c */ extern void examine_variable(PlannerInfo *root, Node *node, int varRelid, diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index aaf0cca..92a141e 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1317,7 +1317,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem pg_statio_user_indexes | SELECT pg_statio_all_indexes.relid, pg_statio_all_indexes.indexrelid, pg_statio_all_indexes.schemaname, pg_statio_all_indexes.relname, pg_statio_all_indexes.indexrelname, pg_statio_all_indexes.idx_blks_read, pg_statio_all_indexes.idx_blks_hit FROM pg_statio_all_indexes WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text)); pg_statio_user_sequences | SELECT pg_statio_all_sequences.relid, pg_statio_all_sequences.schemaname, pg_statio_all_sequences.relname, pg_statio_all_sequences.blks_read, pg_statio_all_sequences.blks_hit FROM pg_statio_all_sequences WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text)); pg_statio_user_tables | SELECT pg_statio_all_tables.relid, pg_statio_all_tables.schemaname, pg_statio_all_tables.relname, pg_statio_all_tables.heap_blks_read, pg_statio_all_tables.heap_blks_hit, pg_statio_all_tables.idx_blks_read, pg_statio_all_tables.idx_blks_hit, pg_statio_all_tables.toast_blks_read, pg_statio_all_tables.toast_blks_hit, pg_statio_all_tables.tidx_blks_read, pg_statio_all_tables.tidx_blks_hit FROM pg_statio_all_tables WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text)); - pg_stats | SELECT n.nspname AS schemaname, c.relname AS tablename, a.attname, s.stainherit AS inherited, s.stanullfrac AS null_frac, s.stawidth AS avg_width, s.stadistinct AS n_distinct, CASE WHEN (s.stakind1 = 1) THEN s.stavalues1 WHEN (s.stakind2 = 1) THEN s.stavalues2 WHEN (s.stakind3 = 1) THEN s.stavalues3 WHEN (s.stakind4 = 1) THEN s.stavalues4 WHEN (s.stakind5 = 1) THEN s.stavalues5 ELSE NULL::anyarray END AS most_common_vals, CASE WHEN (s.stakind1 = 1) THEN s.stanumbers1 WHEN (s.stakind2 = 1) THEN s.stanumbers2 WHEN (s.stakind3 = 1) THEN s.stanumbers3 WHEN (s.stakind4 = 1) THEN s.stanumbers4 WHEN (s.stakind5 = 1) THEN s.stanumbers5 ELSE NULL::real[] END AS most_common_freqs, CASE WHEN (s.stakind1 = 2) THEN s.stavalues1 WHEN (s.stakind2 = 2) THEN s.stavalues2 WHEN (s.stakind3 = 2) THEN s.stavalues3 WHEN (s.stakind4 = 2) THEN s.stavalues4 WHEN (s.stakind5 = 2) THEN s.stavalues5 ELSE NULL::anyarray END AS histogram_bounds, CASE WHEN (s.stakind1 = 3) THEN s.stanumbers1[1] WHEN (s.stakind2 = 3) THEN s.stanumbers2[1] WHEN (s.stakind3 = 3) THEN s.stanumbers3[1] WHEN (s.stakind4 = 3) THEN s.stanumbers4[1] WHEN (s.stakind5 = 3) THEN s.stanumbers5[1] ELSE NULL::real END AS correlation, CASE WHEN (s.stakind1 = 4) THEN s.stavalues1 WHEN (s.stakind2 = 4) THEN s.stavalues2 WHEN (s.stakind3 = 4) THEN s.stavalues3 WHEN (s.stakind4 = 4) THEN s.stavalues4 WHEN (s.stakind5 = 4) THEN s.stavalues5 ELSE NULL::anyarray END AS most_common_elems, CASE WHEN (s.stakind1 = 4) THEN s.stanumbers1 WHEN (s.stakind2 = 4) THEN s.stanumbers2 WHEN (s.stakind3 = 4) THEN s.stanumbers3 WHEN (s.stakind4 = 4) THEN s.stanumbers4 WHEN (s.stakind5 = 4) THEN s.stanumbers5 ELSE NULL::real[] END AS most_common_elem_freqs, CASE WHEN (s.stakind1 = 5) THEN s.stanumbers1 WHEN (s.stakind2 = 5) THEN s.stanumbers2 WHEN (s.stakind3 = 5) THEN s.stanumbers3 WHEN (s.stakind4 = 5) THEN s.stanumbers4 WHEN (s.stakind5 = 5) THEN s.stanumbers5 ELSE NULL::real[] END AS elem_count_histogram FROM (((pg_statistic s JOIN pg_class c ON ((c.oid = s.starelid))) JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum)))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text)); + pg_stats | SELECT n.nspname AS schemaname, c.relname AS tablename, a.attname, s.stainherit AS inherited, s.stanullfrac AS null_frac, s.stawidth AS avg_width, s.stadistinct AS n_distinct, CASE WHEN (s.stakind1 = 1) THEN s.stavalues1 WHEN (s.stakind2 = 1) THEN s.stavalues2 WHEN (s.stakind3 = 1) THEN s.stavalues3 WHEN (s.stakind4 = 1) THEN s.stavalues4 WHEN (s.stakind5 = 1) THEN s.stavalues5 ELSE NULL::anyarray END AS most_common_vals, CASE WHEN (s.stakind1 = 1) THEN s.stanumbers1 WHEN (s.stakind2 = 1) THEN s.stanumbers2 WHEN (s.stakind3 = 1) THEN s.stanumbers3 WHEN (s.stakind4 = 1) THEN s.stanumbers4 WHEN (s.stakind5 = 1) THEN s.stanumbers5 ELSE NULL::real[] END AS most_common_freqs, CASE WHEN (s.stakind1 = 2) THEN s.stavalues1 WHEN (s.stakind2 = 2) THEN s.stavalues2 WHEN (s.stakind3 = 2) THEN s.stavalues3 WHEN (s.stakind4 = 2) THEN s.stavalues4 WHEN (s.stakind5 = 2) THEN s.stavalues5 ELSE NULL::anyarray END AS histogram_bounds, CASE WHEN (s.stakind1 = 3) THEN s.stanumbers1[1] WHEN (s.stakind2 = 3) THEN s.stanumbers2[1] WHEN (s.stakind3 = 3) THEN s.stanumbers3[1] WHEN (s.stakind4 = 3) THEN s.stanumbers4[1] WHEN (s.stakind5 = 3) THEN s.stanumbers5[1] ELSE NULL::real END AS correlation, CASE WHEN (s.stakind1 = 4) THEN s.stavalues1 WHEN (s.stakind2 = 4) THEN s.stavalues2 WHEN (s.stakind3 = 4) THEN s.stavalues3 WHEN (s.stakind4 = 4) THEN s.stavalues4 WHEN (s.stakind5 = 4) THEN s.stavalues5 ELSE NULL::anyarray END AS most_common_elems, CASE WHEN (s.stakind1 = 4) THEN s.stanumbers1 WHEN (s.stakind2 = 4) THEN s.stanumbers2 WHEN (s.stakind3 = 4) THEN s.stanumbers3 WHEN (s.stakind4 = 4) THEN s.stanumbers4 WHEN (s.stakind5 = 4) THEN s.stanumbers5 ELSE NULL::real[] END AS most_common_elem_freqs, CASE WHEN (s.stakind1 = 5) THEN s.stanumbers1 WHEN (s.stakind2 = 5) THEN s.stanumbers2 WHEN (s.stakind3 = 5) THEN s.stanumbers3 WHEN (s.stakind4 = 5) THEN s.stanumbers4 WHEN (s.stakind5 = 5) THEN s.stanumbers5 ELSE NULL::real[] END AS elem_count_histogram FROM (((pg_statistic s JOIN pg_class c ON ((c.oid = s.starelid))) JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (array_length(s.staattnums, 1) = 1)) AND (a.attnum = s.staattnum)))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text)); pg_tables | SELECT n.nspname AS schemaname, c.relname AS tablename, pg_get_userbyid(c.relowner) AS tableowner, t.spcname AS tablespace, c.relhasindex AS hasindexes, c.relhasrules AS hasrules, c.relhastriggers AS hastriggers FROM ((pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace))) WHERE (c.relkind = 'r'::"char"); pg_timezone_abbrevs | SELECT pg_timezone_abbrevs.abbrev, pg_timezone_abbrevs.utc_offset, pg_timezone_abbrevs.is_dst FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst); pg_timezone_names | SELECT pg_timezone_names.name, pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst); diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 70eab92..c983a11 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -368,8 +368,9 @@ WHERE p1.typarray = p2.oid AND SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 WHERE p1.typanalyze = p2.oid AND NOT - (p2.pronargs = 1 AND + (p2.pronargs = 2 AND p2.proargtypes[0] = 'internal'::regtype AND + p2.proargtypes[1] = 'int4'::regtype AND p2.prorettype = 'bool'::regtype AND NOT p2.proretset); oid | typname | oid | proname -----+---------+-----+--------- diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index 413b220..a8d2a8a 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -268,8 +268,9 @@ WHERE p1.typarray = p2.oid AND SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 WHERE p1.typanalyze = p2.oid AND NOT - (p2.pronargs = 1 AND + (p2.pronargs = 2 AND p2.proargtypes[0] = 'internal'::regtype AND + p2.proargtypes[1] = 'int4'::regtype AND p2.prorettype = 'bool'::regtype AND NOT p2.proretset); -- domains inherit their base type's typanalyze