v2.source_files.tar
application/octet-stream
Filename: v2.source_files.tar
Type: application/octet-stream
Part: 6
./analyze.c 0000664 0001750 0001750 00000272520 15221675371 011535 0 ustar xman xman /*-------------------------------------------------------------------------
*
* analyze.c
* the Postgres statistics generator
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/commands/analyze.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <math.h>
#include "access/detoast.h"
#include "access/genam.h"
#include "access/multixact.h"
#include "access/relation.h"
#include "access/table.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/tupconvert.h"
#include "access/visibilitymap.h"
#include "access/xact.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/pg_inherits.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "common/pg_prng.h"
#include "executor/executor.h"
#include "executor/instrument.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "pgstat.h"
#include "statistics/extended_stats_internal.h"
#include "statistics/statistics.h"
#include "storage/bufmgr.h"
#include "storage/procarray.h"
#include "utils/attoptcache.h"
#include "utils/datum.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/sampling.h"
#include "utils/sortsupport.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
/* Per-index data for ANALYZE */
typedef struct AnlIndexData
{
IndexInfo *indexInfo; /* BuildIndexInfo result */
double tupleFract; /* fraction of rows for partial index */
VacAttrStats **vacattrstats; /* index attrs to analyze */
int attr_cnt;
} AnlIndexData;
/* Default statistics target (GUC parameter) */
int default_statistics_target = 100;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext anl_context = NULL;
static BufferAccessStrategy vac_strategy;
static void do_analyze_rel(Relation onerel,
const VacuumParams *params, List *va_cols,
AcquireSampleRowsFunc acquirefunc, BlockNumber relpages,
bool inh, bool in_outer_xact, int elevel);
static void compute_index_stats(Relation onerel, double totalrows,
AnlIndexData *indexdata, int nindexes,
HeapTuple *rows, int numrows,
MemoryContext col_context);
static void validate_va_cols_list(Relation onerel, List *va_cols);
static VacAttrStats *examine_attribute(Relation onerel, int attnum,
Node *index_expr);
static int acquire_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b, void *arg);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
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);
/*
* analyze_rel() -- analyze one relation
*
* relid identifies the relation to analyze. If relation is supplied, use
* the name therein for reporting any failure to open/lock the rel; do not
* use it once we've successfully opened the rel, since it might be stale.
*/
void
analyze_rel(Oid relid, RangeVar *relation,
const VacuumParams *params, List *va_cols, bool in_outer_xact,
BufferAccessStrategy bstrategy)
{
Relation onerel;
int elevel;
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
bool stats_imported = false;
/* Select logging level */
if (params->options & VACOPT_VERBOSE)
elevel = INFO;
else
elevel = DEBUG2;
/* Set up static variables */
vac_strategy = bstrategy;
/*
* Check for user-requested abort.
*/
CHECK_FOR_INTERRUPTS();
/*
* Open the relation, getting ShareUpdateExclusiveLock to ensure that two
* ANALYZEs don't run on it concurrently. (This also locks out a
* concurrent VACUUM, which doesn't matter much at the moment but might
* matter if we ever try to accumulate stats on dead tuples.) If the rel
* has been dropped since we last saw it, we don't need to process it.
*
* Make sure to generate only logs for ANALYZE in this case.
*/
onerel = vacuum_open_relation(relid, relation, params->options & ~(VACOPT_VACUUM),
params->log_analyze_min_duration >= 0,
ShareUpdateExclusiveLock);
/* leave if relation could not be opened or locked */
if (!onerel)
return;
/*
* Check if relation needs to be skipped based on privileges. This check
* happens also when building the relation list to analyze for a manual
* operation, and needs to be done additionally here as ANALYZE could
* happen across multiple transactions where privileges could have changed
* in-between. Make sure to generate only logs for ANALYZE in this case.
*/
if (!vacuum_is_permitted_for_relation(RelationGetRelid(onerel),
onerel->rd_rel,
params->options & ~VACOPT_VACUUM))
{
relation_close(onerel, ShareUpdateExclusiveLock);
return;
}
/*
* Silently ignore tables that are temp tables of other backends ---
* trying to analyze these is rather pointless, since their contents are
* probably not up-to-date on disk. (We don't throw a warning here; it
* would just lead to chatter during a database-wide ANALYZE.)
*/
if (RELATION_IS_OTHER_TEMP(onerel))
{
relation_close(onerel, ShareUpdateExclusiveLock);
return;
}
/*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
if (RelationGetRelid(onerel) == StatisticRelationId)
{
relation_close(onerel, ShareUpdateExclusiveLock);
return;
}
/*
* Check the given list of columns
*/
if (va_cols != NIL)
validate_va_cols_list(onerel, va_cols);
/*
* Initialize progress reporting before setup for regular/foreign tables.
* (For the former, the time spent on it would be negligible, but for the
* latter, if FDWs support statistics import or analysis, they'd do some
* work that needs the remote access, so the time might be
* non-negligible.)
*/
pgstat_progress_start_command(PROGRESS_COMMAND_ANALYZE,
RelationGetRelid(onerel));
if (AmAutoVacuumWorkerProcess())
pgstat_progress_update_param(PROGRESS_ANALYZE_STARTED_BY,
PROGRESS_ANALYZE_STARTED_BY_AUTOVACUUM);
else
pgstat_progress_update_param(PROGRESS_ANALYZE_STARTED_BY,
PROGRESS_ANALYZE_STARTED_BY_MANUAL);
/*
* Check that it's of an analyzable relkind, and set up appropriately.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
acquirefunc = acquire_sample_rows;
/* Also get regular table's size */
relpages = RelationGetNumberOfBlocks(onerel);
}
else if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
{
/*
* For a foreign table, call the FDW's hook functions to see whether
* it supports statistics import or analysis.
*/
FdwRoutine *fdwroutine;
fdwroutine = GetFdwRoutineForRelation(onerel, false);
if (fdwroutine->ImportForeignStatistics != NULL &&
fdwroutine->ImportForeignStatistics(onerel, va_cols, elevel))
stats_imported = true;
else
{
bool ok = false;
if (fdwroutine->AnalyzeForeignTable != NULL)
ok = fdwroutine->AnalyzeForeignTable(onerel,
&acquirefunc,
&relpages);
if (!ok)
{
ereport(WARNING,
errmsg("skipping \"%s\" -- cannot analyze this foreign table.",
RelationGetRelationName(onerel)));
relation_close(onerel, ShareUpdateExclusiveLock);
goto out;
}
}
}
else if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* For partitioned tables, we want to do the recursive ANALYZE below.
*/
}
else
{
/* No need for a WARNING if we already complained during VACUUM */
if (!(params->options & VACOPT_VACUUM))
ereport(WARNING,
(errmsg("skipping \"%s\" --- cannot analyze non-tables or special system tables",
RelationGetRelationName(onerel))));
relation_close(onerel, ShareUpdateExclusiveLock);
goto out;
}
/*
* Do the normal non-recursive ANALYZE. We can skip this for partitioned
* tables, which don't contain any rows, and foreign tables that
* successfully imported statistics.
*/
if ((onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
&& !stats_imported)
do_analyze_rel(onerel, params, va_cols, acquirefunc,
relpages, false, in_outer_xact, elevel);
/*
* If there are child tables, do recursive ANALYZE.
*/
if (onerel->rd_rel->relhassubclass)
do_analyze_rel(onerel, params, va_cols, acquirefunc, relpages,
true, in_outer_xact, elevel);
/*
* Close source relation now, but keep lock so that no one deletes it
* before we commit. (If someone did, they'd fail to clean up the entries
* we made in pg_statistic. Also, releasing the lock before commit would
* expose us to concurrent-update failures in update_attstats.)
*/
relation_close(onerel, NoLock);
out:
pgstat_progress_end_command();
}
/*
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
* For the inherited case, acquire_inherited_sample_rows() determines the
* appropriate acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, const VacuumParams *params,
List *va_cols, AcquireSampleRowsFunc acquirefunc,
BlockNumber relpages, bool inh, bool in_outer_xact,
int elevel)
{
int attr_cnt,
tcnt,
i,
ind;
Relation *Irel;
int nindexes;
bool verbose,
instrument,
hasindex;
VacAttrStats **vacattrstats;
AnlIndexData *indexdata;
int targrows,
numrows,
minrows;
double totalrows,
totaldeadrows;
HeapTuple *rows;
PGRUsage ru0;
TimestampTz starttime = 0;
MemoryContext caller_context;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
WalUsage startwalusage = pgWalUsage;
BufferUsage startbufferusage = pgBufferUsage;
BufferUsage bufferusage;
PgStat_Counter startreadtime = 0;
PgStat_Counter startwritetime = 0;
verbose = (params->options & VACOPT_VERBOSE) != 0;
instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
params->log_analyze_min_duration >= 0));
if (inh)
ereport(elevel,
(errmsg("analyzing \"%s.%s\" inheritance tree",
get_namespace_name(RelationGetNamespace(onerel)),
RelationGetRelationName(onerel))));
else
ereport(elevel,
(errmsg("analyzing \"%s.%s\"",
get_namespace_name(RelationGetNamespace(onerel)),
RelationGetRelationName(onerel))));
/*
* Set up a working context so that we can easily free whatever junk gets
* created.
*/
anl_context = AllocSetContextCreate(CurrentMemoryContext,
"Analyze",
ALLOCSET_DEFAULT_SIZES);
caller_context = MemoryContextSwitchTo(anl_context);
/*
* Switch to the table owner's userid, so that any index functions are run
* as that user. Also lock down security-restricted operations and
* arrange to make GUC variable changes local to this command.
*/
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(onerel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
/*
* When verbose or autovacuum logging is used, initialize a resource usage
* snapshot and optionally track I/O timing.
*/
if (instrument)
{
if (track_io_timing)
{
startreadtime = pgStatBlockReadTime;
startwritetime = pgStatBlockWriteTime;
}
pg_rusage_init(&ru0);
}
/* Used for instrumentation and stats report */
starttime = GetCurrentTimestamp();
/*
* Determine which columns to analyze.
*/
if (va_cols != NIL)
{
ListCell *le;
vacattrstats = (VacAttrStats **) palloc(list_length(va_cols) *
sizeof(VacAttrStats *));
tcnt = 0;
foreach(le, va_cols)
{
char *col = strVal(lfirst(le));
i = attnameAttNum(onerel, col, false);
Assert(i != InvalidAttrNumber);
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
}
attr_cnt = tcnt;
}
else
{
attr_cnt = onerel->rd_att->natts;
vacattrstats = (VacAttrStats **)
palloc(attr_cnt * sizeof(VacAttrStats *));
tcnt = 0;
for (i = 1; i <= attr_cnt; i++)
{
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
}
attr_cnt = tcnt;
}
/*
* Open all indexes of the relation, and see if there are any analyzable
* columns in the indexes. We do not analyze index columns if there was
* an explicit column list in the ANALYZE command, however.
*
* If we are doing a recursive scan, we don't want to touch the parent's
* indexes at all. If we're processing a partitioned table, we need to
* know if there are any indexes, but we don't want to process them.
*/
if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
List *idxs = RelationGetIndexList(onerel);
Irel = NULL;
nindexes = 0;
hasindex = idxs != NIL;
list_free(idxs);
}
else if (!inh)
{
vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel);
hasindex = nindexes > 0;
}
else
{
Irel = NULL;
nindexes = 0;
hasindex = false;
}
indexdata = NULL;
if (nindexes > 0)
{
indexdata = (AnlIndexData *) palloc0(nindexes * sizeof(AnlIndexData));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
IndexInfo *indexInfo;
thisdata->indexInfo = indexInfo = BuildIndexInfo(Irel[ind]);
thisdata->tupleFract = 1.0; /* fix later if partial */
if (indexInfo->ii_Expressions != NIL && va_cols == NIL)
{
ListCell *indexpr_item = list_head(indexInfo->ii_Expressions);
thisdata->vacattrstats = (VacAttrStats **)
palloc(indexInfo->ii_NumIndexAttrs * sizeof(VacAttrStats *));
tcnt = 0;
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
int keycol = indexInfo->ii_IndexAttrNumbers[i];
if (keycol == 0)
{
/* Found an index expression */
Node *indexkey;
if (indexpr_item == NULL) /* shouldn't happen */
elog(ERROR, "too few entries in indexprs list");
indexkey = (Node *) lfirst(indexpr_item);
indexpr_item = lnext(indexInfo->ii_Expressions,
indexpr_item);
thisdata->vacattrstats[tcnt] =
examine_attribute(Irel[ind], i + 1, indexkey);
if (thisdata->vacattrstats[tcnt] != NULL)
tcnt++;
}
}
thisdata->attr_cnt = tcnt;
}
}
}
/*
* Determine how many rows we need to sample, using the worst case from
* all analyzable columns. We use a lower bound of 100 rows to avoid
* possible overflow in Vitter's algorithm. (Note: that will also be the
* target in the corner case where there are no analyzable columns.)
*/
targrows = 100;
for (i = 0; i < attr_cnt; i++)
{
if (targrows < vacattrstats[i]->minrows)
targrows = vacattrstats[i]->minrows;
}
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
for (i = 0; i < thisdata->attr_cnt; i++)
{
if (targrows < thisdata->vacattrstats[i]->minrows)
targrows = thisdata->vacattrstats[i]->minrows;
}
}
/*
* Look at extended statistics objects too, as those may define custom
* statistics target. So we may need to sample more rows and then build
* the statistics with enough detail.
*/
minrows = ComputeExtStatisticsRows(onerel, attr_cnt, vacattrstats);
if (targrows < minrows)
targrows = minrows;
/*
* Acquire the sample rows
*/
rows = (HeapTuple *) palloc(targrows * sizeof(HeapTuple));
pgstat_progress_update_param(PROGRESS_ANALYZE_PHASE,
inh ? PROGRESS_ANALYZE_PHASE_ACQUIRE_SAMPLE_ROWS_INH :
PROGRESS_ANALYZE_PHASE_ACQUIRE_SAMPLE_ROWS);
if (inh)
numrows = acquire_inherited_sample_rows(onerel, elevel,
rows, targrows,
&totalrows, &totaldeadrows);
else
numrows = (*acquirefunc) (onerel, elevel,
rows, targrows,
&totalrows, &totaldeadrows);
/*
* 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
* structure is allocated in anl_context.
*/
if (numrows > 0)
{
MemoryContext col_context,
old_context;
pgstat_progress_update_param(PROGRESS_ANALYZE_PHASE,
PROGRESS_ANALYZE_PHASE_COMPUTE_STATS);
col_context = AllocSetContextCreate(anl_context,
"Analyze Column",
ALLOCSET_DEFAULT_SIZES);
old_context = MemoryContextSwitchTo(col_context);
for (i = 0; i < attr_cnt; i++)
{
VacAttrStats *stats = vacattrstats[i];
AttributeOpts *aopt;
stats->rows = rows;
stats->tupDesc = onerel->rd_att;
stats->compute_stats(stats,
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->tupattnum);
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;
}
MemoryContextReset(col_context);
}
if (nindexes > 0)
compute_index_stats(onerel, totalrows,
indexdata, nindexes,
rows, numrows,
col_context);
MemoryContextSwitchTo(old_context);
MemoryContextDelete(col_context);
/*
* Emit the completed stats rows into pg_statistic, replacing any
* previous statistics for the target columns. (If there are stats in
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
attr_cnt, vacattrstats);
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
thisdata->attr_cnt, thisdata->vacattrstats);
}
/* Build extended statistics (if there are any). */
BuildRelationExtStatistics(onerel, inh, totalrows, numrows, rows,
attr_cnt, vacattrstats);
}
pgstat_progress_update_param(PROGRESS_ANALYZE_PHASE,
PROGRESS_ANALYZE_PHASE_FINALIZE_ANALYZE);
/*
* Update pages/tuples stats in pg_class ... but not if we're doing
* inherited stats.
*
* We assume that VACUUM hasn't set pg_class.reltuples already, even
* during a VACUUM ANALYZE. Although VACUUM often updates pg_class,
* exceptions exist. A "VACUUM (ANALYZE, INDEX_CLEANUP OFF)" command will
* never update pg_class entries for index relations. It's also possible
* that an individual index's pg_class entry won't be updated during
* VACUUM if the index AM returns NULL from its amvacuumcleanup() routine.
*/
if (!inh)
{
BlockNumber relallvisible = 0;
BlockNumber relallfrozen = 0;
if (RELKIND_HAS_STORAGE(onerel->rd_rel->relkind))
visibilitymap_count(onerel, &relallvisible, &relallfrozen);
/*
* Update pg_class for table relation. CCI first, in case acquirefunc
* updated pg_class.
*/
CommandCounterIncrement();
vac_update_relstats(onerel,
relpages,
totalrows,
relallvisible,
relallfrozen,
hasindex,
InvalidTransactionId,
InvalidMultiXactId,
NULL, NULL,
in_outer_xact);
/* Same for indexes */
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
double totalindexrows;
totalindexrows = ceil(thisdata->tupleFract * totalrows);
vac_update_relstats(Irel[ind],
RelationGetNumberOfBlocks(Irel[ind]),
totalindexrows,
0, 0,
false,
InvalidTransactionId,
InvalidMultiXactId,
NULL, NULL,
in_outer_xact);
}
}
else if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Partitioned tables don't have storage, so we don't set any fields
* in their pg_class entries except for reltuples and relhasindex.
*/
CommandCounterIncrement();
vac_update_relstats(onerel, -1, totalrows,
0, 0, hasindex, InvalidTransactionId,
InvalidMultiXactId,
NULL, NULL,
in_outer_xact);
}
/*
* Now report ANALYZE to the cumulative stats system. For regular tables,
* we do it only if not doing inherited stats. For partitioned tables, we
* only do it for inherited stats. (We're never called for not-inherited
* stats on partitioned tables anyway.)
*
* Reset the mod_since_analyze counter only if we analyzed all columns;
* otherwise, there is still work for auto-analyze to do.
*/
if (!inh)
pgstat_report_analyze(onerel, totalrows, totaldeadrows,
(va_cols == NIL), starttime);
else if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
pgstat_report_analyze(onerel, 0, 0, (va_cols == NIL), starttime);
/*
* If this isn't part of VACUUM ANALYZE, let index AMs do cleanup.
*
* Note that most index AMs perform a no-op as a matter of policy for
* amvacuumcleanup() when called in ANALYZE-only mode. The only exception
* among core index AMs is GIN/ginvacuumcleanup().
*/
if (!(params->options & VACOPT_VACUUM))
{
for (ind = 0; ind < nindexes; ind++)
{
IndexBulkDeleteResult *stats;
IndexVacuumInfo ivinfo;
ivinfo.index = Irel[ind];
ivinfo.heaprel = onerel;
ivinfo.analyze_only = true;
ivinfo.estimated_count = true;
ivinfo.message_level = elevel;
ivinfo.num_heap_tuples = onerel->rd_rel->reltuples;
ivinfo.strategy = vac_strategy;
stats = index_vacuum_cleanup(&ivinfo, NULL);
if (stats)
pfree(stats);
}
}
/* Done with indexes */
vac_close_indexes(nindexes, Irel, NoLock);
/* Log the action if appropriate */
if (instrument)
{
TimestampTz endtime = GetCurrentTimestamp();
if (verbose || params->log_analyze_min_duration == 0 ||
TimestampDifferenceExceeds(starttime, endtime,
params->log_analyze_min_duration))
{
long delay_in_ms;
WalUsage walusage;
double read_rate = 0;
double write_rate = 0;
char *msgfmt;
StringInfoData buf;
int64 total_blks_hit;
int64 total_blks_read;
int64 total_blks_dirtied;
memset(&bufferusage, 0, sizeof(BufferUsage));
BufferUsageAccumDiff(&bufferusage, &pgBufferUsage, &startbufferusage);
memset(&walusage, 0, sizeof(WalUsage));
WalUsageAccumDiff(&walusage, &pgWalUsage, &startwalusage);
total_blks_hit = bufferusage.shared_blks_hit +
bufferusage.local_blks_hit;
total_blks_read = bufferusage.shared_blks_read +
bufferusage.local_blks_read;
total_blks_dirtied = bufferusage.shared_blks_dirtied +
bufferusage.local_blks_dirtied;
/*
* We do not expect an analyze to take > 25 days and it simplifies
* things a bit to use TimestampDifferenceMilliseconds.
*/
delay_in_ms = TimestampDifferenceMilliseconds(starttime, endtime);
/*
* Note that we are reporting these read/write rates in the same
* manner as VACUUM does, which means that while the 'average read
* rate' here actually corresponds to page misses and resulting
* reads which are also picked up by track_io_timing, if enabled,
* the 'average write rate' is actually talking about the rate of
* pages being dirtied, not being written out, so it's typical to
* have a non-zero 'avg write rate' while I/O timings only reports
* reads.
*
* It's not clear that an ANALYZE will ever result in
* FlushBuffer() being called, but we track and support reporting
* on I/O write time in case that changes as it's practically free
* to do so anyway.
*/
if (delay_in_ms > 0)
{
read_rate = (double) BLCKSZ * total_blks_read /
(1024 * 1024) / (delay_in_ms / 1000.0);
write_rate = (double) BLCKSZ * total_blks_dirtied /
(1024 * 1024) / (delay_in_ms / 1000.0);
}
/*
* We split this up so we don't emit empty I/O timing values when
* track_io_timing isn't enabled.
*/
initStringInfo(&buf);
if (AmAutoVacuumWorkerProcess())
msgfmt = _("automatic analyze of table \"%s.%s.%s\"\n");
else
msgfmt = _("finished analyzing table \"%s.%s.%s\"\n");
appendStringInfo(&buf, msgfmt,
get_database_name(MyDatabaseId),
get_namespace_name(RelationGetNamespace(onerel)),
RelationGetRelationName(onerel));
if (track_cost_delay_timing)
{
/*
* We bypass the changecount mechanism because this value is
* only updated by the calling process.
*/
appendStringInfo(&buf, _("delay time: %.3f ms\n"),
(double) MyBEEntry->st_progress_param[PROGRESS_ANALYZE_DELAY_TIME] / 1000000.0);
}
if (track_io_timing)
{
double read_ms = (double) (pgStatBlockReadTime - startreadtime) / 1000;
double write_ms = (double) (pgStatBlockWriteTime - startwritetime) / 1000;
appendStringInfo(&buf, _("I/O timings: read: %.3f ms, write: %.3f ms\n"),
read_ms, write_ms);
}
appendStringInfo(&buf, _("avg read rate: %.3f MB/s, avg write rate: %.3f MB/s\n"),
read_rate, write_rate);
appendStringInfo(&buf, _("buffer usage: %" PRId64 " hits, %" PRId64 " reads, %" PRId64 " dirtied\n"),
total_blks_hit,
total_blks_read,
total_blks_dirtied);
appendStringInfo(&buf,
_("WAL usage: %" PRId64 " records, %" PRId64 " full page images, %" PRIu64 " bytes, %" PRIu64 " full page image bytes, %" PRId64 " buffers full\n"),
walusage.wal_records,
walusage.wal_fpi,
walusage.wal_bytes,
walusage.wal_fpi_bytes,
walusage.wal_buffers_full);
appendStringInfo(&buf, _("system usage: %s"), pg_rusage_show(&ru0));
ereport(verbose ? INFO : LOG,
(errmsg_internal("%s", buf.data)));
pfree(buf.data);
}
}
/* Roll back any GUC changes executed by index functions */
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
/* Restore current context and release memory */
MemoryContextSwitchTo(caller_context);
MemoryContextDelete(anl_context);
anl_context = NULL;
}
/*
* Compute statistics about indexes of a relation
*/
static void
compute_index_stats(Relation onerel, double totalrows,
AnlIndexData *indexdata, int nindexes,
HeapTuple *rows, int numrows,
MemoryContext col_context)
{
MemoryContext ind_context,
old_context;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
int ind,
i;
ind_context = AllocSetContextCreate(anl_context,
"Analyze Index",
ALLOCSET_DEFAULT_SIZES);
old_context = MemoryContextSwitchTo(ind_context);
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
IndexInfo *indexInfo = thisdata->indexInfo;
int attr_cnt = thisdata->attr_cnt;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
ExprState *predicateExpand;
Datum *exprvals;
bool *exprnulls;
int numindexrows,
tcnt,
rowno;
double totalindexrows;
/* Ignore index if no columns to analyze and not partial */
if (attr_cnt == 0 && indexInfo->ii_PredicateExpand == NIL)
continue;
/*
* Need an EState for evaluation of index expressions and
* partial-index predicates. Create it in the per-index context to be
* sure it gets cleaned up at the bottom of the loop.
*/
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
/* Need a slot to hold the current heap tuple, too */
slot = MakeSingleTupleTableSlot(RelationGetDescr(onerel),
&TTSOpsHeapTuple);
/* Arrange for econtext's scan tuple to be the tuple under test */
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate. */
predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/* Compute and save index expression values */
exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
exprnulls = (bool *) palloc(numrows * attr_cnt * sizeof(bool));
numindexrows = 0;
tcnt = 0;
for (rowno = 0; rowno < numrows; rowno++)
{
HeapTuple heapTuple = rows[rowno];
vacuum_delay_point(true);
/*
* Reset the per-tuple context each time, to reclaim any cruft
* left behind by evaluating the predicate or index expressions.
*/
ResetExprContext(econtext);
/* Set up for predicate or expression evaluation */
ExecStoreHeapTuple(heapTuple, slot, false);
/* If index is partial, check predicate */
if (predicateExpand != NULL)
{
if (!ExecQual(predicateExpand, econtext))
continue;
}
numindexrows++;
if (attr_cnt > 0)
{
/*
* Evaluate the index row to compute expression values. We
* could do this by hand, but FormIndexDatum is convenient.
*/
FormIndexDatum(indexInfo,
slot,
estate,
values,
isnull);
/*
* Save just the columns we care about. We copy the values
* into ind_context from the estate's per-tuple context.
*/
for (i = 0; i < attr_cnt; i++)
{
VacAttrStats *stats = thisdata->vacattrstats[i];
int attnum = stats->tupattnum;
if (isnull[attnum - 1])
{
exprvals[tcnt] = (Datum) 0;
exprnulls[tcnt] = true;
}
else
{
exprvals[tcnt] = datumCopy(values[attnum - 1],
stats->attrtype->typbyval,
stats->attrtype->typlen);
exprnulls[tcnt] = false;
}
tcnt++;
}
}
}
/*
* Having counted the number of rows that pass the predicate in the
* sample, we can estimate the total number of rows in the index.
*/
thisdata->tupleFract = (double) numindexrows / (double) numrows;
totalindexrows = ceil(thisdata->tupleFract * totalrows);
/*
* Now we can compute the statistics for the expression columns.
*/
if (numindexrows > 0)
{
MemoryContextSwitchTo(col_context);
for (i = 0; i < attr_cnt; i++)
{
VacAttrStats *stats = thisdata->vacattrstats[i];
stats->exprvals = exprvals + i;
stats->exprnulls = exprnulls + i;
stats->rowstride = attr_cnt;
stats->compute_stats(stats,
ind_fetch_func,
numindexrows,
totalindexrows);
MemoryContextReset(col_context);
}
}
/* And clean up */
MemoryContextSwitchTo(ind_context);
ExecDropSingleTupleTableSlot(slot);
FreeExecutorState(estate);
MemoryContextReset(ind_context);
}
MemoryContextSwitchTo(old_context);
MemoryContextDelete(ind_context);
}
/*
* validate_va_cols_list -- validate the columns list given to analyze_rel
*
* Note that system attributes are never analyzed, so we just reject them at
* the lookup stage. We also reject duplicate column mentions. (We could
* alternatively ignore duplicates, but analyzing a column twice won't work;
* we'd end up making a conflicting update in pg_statistic.)
*/
static void
validate_va_cols_list(Relation onerel, List *va_cols)
{
Bitmapset *unique_cols = NULL;
ListCell *le;
Assert(va_cols != NIL);
foreach(le, va_cols)
{
char *col = strVal(lfirst(le));
int i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
col, RelationGetRelationName(onerel))));
if (bms_is_member(i, unique_cols))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" of relation \"%s\" appears more than once",
col, RelationGetRelationName(onerel))));
unique_cols = bms_add_member(unique_cols, i);
}
}
/*
* examine_attribute -- pre-analysis of a single column
*
* Determine whether the column is analyzable; if so, create and initialize
* a VacAttrStats 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)
{
Form_pg_attribute attr = TupleDescAttr(onerel->rd_att, attnum - 1);
int attstattarget;
HeapTuple typtuple;
VacAttrStats *stats;
int i;
bool ok;
/*
* Check if the column is analyzable.
*/
if (!attribute_is_analyzable(onerel, attnum, attr, &attstattarget))
return NULL;
/*
* Create the VacAttrStats struct.
*/
stats = palloc0_object(VacAttrStats);
stats->attstattarget = attstattarget;
/*
* When analyzing an expression index, believe the expression tree's type
* not the column datatype --- the latter might be the opckeytype storage
* type of the opclass, which is not interesting for our purposes. (Note:
* if we did anything with non-expression index columns, we'd need to
* figure out where to get the correct type info from, but for now that's
* 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);
/*
* If a collation has been specified for the index column, use that in
* preference to anything else; but if not, fall back to whatever we
* can get from the expression.
*/
if (OidIsValid(onerel->rd_indcollation[attnum - 1]))
stats->attrcollid = onerel->rd_indcollation[attnum - 1];
else
stats->attrcollid = exprCollation(index_expr);
}
else
{
stats->attrtypid = attr->atttypid;
stats->attrtypmod = attr->atttypmod;
stats->attrcollid = attr->attcollation;
}
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++)
{
stats->statypid[i] = stats->attrtypid;
stats->statyplen[i] = stats->attrtype->typlen;
stats->statypbyval[i] = stats->attrtype->typbyval;
stats->statypalign[i] = stats->attrtype->typalign;
}
/*
* Call the type-specific typanalyze function. If none is specified, use
* std_typanalyze().
*/
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)
{
heap_freetuple(typtuple);
pfree(stats);
return NULL;
}
return stats;
}
bool
attribute_is_analyzable(Relation onerel, int attnum, Form_pg_attribute attr,
int *p_attstattarget)
{
int attstattarget;
HeapTuple atttuple;
Datum dat;
bool isnull;
/* Never analyze dropped columns */
if (attr->attisdropped)
return false;
/* Don't analyze virtual generated columns */
if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
return false;
/*
* Get attstattarget value. Set to -1 if null. (Analyze functions expect
* -1 to mean use default_statistics_target; see for example
* std_typanalyze.)
*/
atttuple = SearchSysCache2(ATTNUM, ObjectIdGetDatum(RelationGetRelid(onerel)), Int16GetDatum(attnum));
if (!HeapTupleIsValid(atttuple))
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, RelationGetRelid(onerel));
dat = SysCacheGetAttr(ATTNUM, atttuple, Anum_pg_attribute_attstattarget, &isnull);
attstattarget = isnull ? -1 : DatumGetInt16(dat);
ReleaseSysCache(atttuple);
/* Don't analyze column if user has specified not to */
if (attstattarget == 0)
return false;
if (p_attstattarget)
*p_attstattarget = attstattarget;
return true;
}
/*
* Read stream callback returning the next BlockNumber as chosen by the
* BlockSampling algorithm.
*/
static BlockNumber
block_sampling_read_stream_next(ReadStream *stream,
void *callback_private_data,
void *per_buffer_data)
{
BlockSamplerData *bs = callback_private_data;
return BlockSampler_HasMore(bs) ? BlockSampler_Next(bs) : InvalidBlockNumber;
}
/*
* acquire_sample_rows -- acquire a random sample of rows from the table
*
* Selected rows are returned in the caller-allocated array rows[], which
* must have at least targrows entries.
* The actual number of rows selected is returned as the function result.
* We also estimate the total numbers of live and dead rows in the table,
* and return them into *totalrows and *totaldeadrows, respectively.
*
* The returned list of tuples is in order by physical position in the table.
* (We will rely on this later to derive correlation estimates.)
*
* As of May 2004 we use a new two-stage method: Stage one selects up
* to targrows random blocks (or all blocks, if there aren't so many).
* Stage two scans these blocks and uses the Vitter algorithm to create
* a random sample of targrows rows (or less, if there are less in the
* sample of blocks). The two stages are executed simultaneously: each
* block is processed as soon as stage one returns its number and while
* the rows are read stage two controls which ones are to be inserted
* into the sample.
*
* Although every row has an equal chance of ending up in the final
* sample, this sampling method is not perfect: not every possible
* sample has an equal chance of being selected. For large relations
* the number of different blocks represented by the sample tends to be
* too small. We can live with that for now. Improvements are welcome.
*
* An important property of this sampling method is that because we do
* look at a statistically unbiased set of blocks, we should get
* unbiased estimates of the average numbers of live and dead rows per
* block. The previous sampling method put too much credence in the row
* density near the start of the table.
*/
static int
acquire_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows)
{
int numrows = 0; /* # rows now in reservoir */
double samplerows = 0; /* total # rows collected */
double liverows = 0; /* # live rows seen */
double deadrows = 0; /* # dead rows seen */
double rowstoskip = -1; /* -1 means not set yet */
uint32 randseed; /* Seed for block sampler(s) */
BlockNumber totalblocks;
BlockSamplerData bs;
ReservoirStateData rstate;
TupleTableSlot *slot;
TableScanDesc scan;
BlockNumber nblocks;
BlockNumber blksdone = 0;
ReadStream *stream;
Assert(targrows > 0);
totalblocks = RelationGetNumberOfBlocks(onerel);
/* Prepare for sampling block numbers */
randseed = pg_prng_uint32(&pg_global_prng_state);
nblocks = BlockSampler_Init(&bs, totalblocks, targrows, randseed);
/* Report sampling block numbers */
pgstat_progress_update_param(PROGRESS_ANALYZE_BLOCKS_TOTAL,
nblocks);
/* Prepare for sampling rows */
reservoir_init_selection_state(&rstate, targrows);
scan = table_beginscan_analyze(onerel);
slot = table_slot_create(onerel, NULL);
/*
* It is safe to use batching, as block_sampling_read_stream_next never
* blocks.
*/
stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
READ_STREAM_USE_BATCHING,
vac_strategy,
scan->rs_rd,
MAIN_FORKNUM,
block_sampling_read_stream_next,
&bs,
0);
/* Outer loop over blocks to sample */
while (table_scan_analyze_next_block(scan, stream))
{
vacuum_delay_point(true);
while (table_scan_analyze_next_tuple(scan, &liverows, &deadrows, slot))
{
/*
* The first targrows sample rows are simply copied into the
* reservoir. Then we start replacing tuples in the sample until
* we reach the end of the relation. This algorithm is from Jeff
* Vitter's paper (see full citation in utils/misc/sampling.c). It
* works by repeatedly computing the number of tuples to skip
* before selecting a tuple, which replaces a randomly chosen
* element of the reservoir (current set of tuples). At all times
* the reservoir is a true random sample of the tuples we've
* passed over so far, so when we fall off the end of the relation
* we're done.
*/
if (numrows < targrows)
rows[numrows++] = ExecCopySlotHeapTuple(slot);
else
{
/*
* t in Vitter's paper is the number of records already
* processed. If we need to compute a new S value, we must
* use the not-yet-incremented value of samplerows as t.
*/
if (rowstoskip < 0)
rowstoskip = reservoir_get_next_S(&rstate, samplerows, targrows);
if (rowstoskip <= 0)
{
/*
* Found a suitable tuple, so save it, replacing one old
* tuple at random
*/
int k = (int) (targrows * sampler_random_fract(&rstate.randstate));
Assert(k >= 0 && k < targrows);
heap_freetuple(rows[k]);
rows[k] = ExecCopySlotHeapTuple(slot);
}
rowstoskip -= 1;
}
samplerows += 1;
}
pgstat_progress_update_param(PROGRESS_ANALYZE_BLOCKS_DONE,
++blksdone);
}
read_stream_end(stream);
ExecDropSingleTupleTableSlot(slot);
table_endscan(scan);
/*
* If we didn't find as many tuples as we wanted then we're done. No sort
* is needed, since they're already in order.
*
* Otherwise we need to sort the collected tuples by position
* (itempointer). It's not worth worrying about corner cases where the
* tuples are already sorted.
*/
if (numrows == targrows)
qsort_interruptible(rows, numrows, sizeof(HeapTuple),
compare_rows, NULL);
/*
* Estimate total numbers of live and dead rows in relation, extrapolating
* on the assumption that the average tuple density in pages we didn't
* scan is the same as in the pages we did scan. Since what we scanned is
* a random sample of the pages in the relation, this should be a good
* assumption.
*/
if (bs.m > 0)
{
*totalrows = floor((liverows / bs.m) * totalblocks + 0.5);
*totaldeadrows = floor((deadrows / bs.m) * totalblocks + 0.5);
}
else
{
*totalrows = 0.0;
*totaldeadrows = 0.0;
}
/*
* Emit some interesting relation info
*/
ereport(elevel,
(errmsg("\"%s\": scanned %d of %u pages, "
"containing %.0f live rows and %.0f dead rows; "
"%d rows in sample, %.0f estimated total rows",
RelationGetRelationName(onerel),
bs.m, totalblocks,
liverows, deadrows,
numrows, *totalrows)));
return numrows;
}
/*
* Comparator for sorting rows[] array
*/
static int
compare_rows(const void *a, const void *b, void *arg)
{
HeapTuple ha = *(const HeapTuple *) a;
HeapTuple hb = *(const HeapTuple *) b;
BlockNumber ba = ItemPointerGetBlockNumber(&ha->t_self);
OffsetNumber oa = ItemPointerGetOffsetNumber(&ha->t_self);
BlockNumber bb = ItemPointerGetBlockNumber(&hb->t_self);
OffsetNumber ob = ItemPointerGetOffsetNumber(&hb->t_self);
if (ba < bb)
return -1;
if (ba > bb)
return 1;
if (oa < ob)
return -1;
if (oa > ob)
return 1;
return 0;
}
/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
* We fail and return zero if there are no inheritance children, or if all
* children are foreign tables that don't support ANALYZE.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows)
{
List *tableOIDs;
Relation *rels;
AcquireSampleRowsFunc *acquirefuncs;
double *relblocks;
double totalblocks;
int numrows,
nrels,
i;
ListCell *lc;
bool has_child;
/* Initialize output parameters to zero now, in case we exit early */
*totalrows = 0;
*totaldeadrows = 0;
/*
* Find all members of inheritance set. We only need AccessShareLock on
* the children.
*/
tableOIDs =
find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL);
/*
* Check that there's at least one descendant, else fail. This could
* happen despite analyze_rel's relhassubclass check, if table once had a
* child but no longer does. In that case, we can clear the
* relhassubclass field so as not to make the same mistake again later.
* (This is safe because we hold ShareUpdateExclusiveLock.)
*/
if (list_length(tableOIDs) < 2)
{
/* CCI because we already updated the pg_class row in this command */
CommandCounterIncrement();
SetRelationHasSubclass(RelationGetRelid(onerel), false);
ereport(elevel,
(errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains no child tables",
get_namespace_name(RelationGetNamespace(onerel)),
RelationGetRelationName(onerel))));
return 0;
}
/*
* Identify acquirefuncs to use, and count blocks in all the relations.
* The result could overflow BlockNumber, so we use double arithmetic.
*/
rels = (Relation *) palloc(list_length(tableOIDs) * sizeof(Relation));
acquirefuncs = (AcquireSampleRowsFunc *)
palloc(list_length(tableOIDs) * sizeof(AcquireSampleRowsFunc));
relblocks = (double *) palloc(list_length(tableOIDs) * sizeof(double));
totalblocks = 0;
nrels = 0;
has_child = false;
foreach(lc, tableOIDs)
{
Oid childOID = lfirst_oid(lc);
Relation childrel;
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
/* We already got the needed lock */
childrel = table_open(childOID, NoLock);
/* Ignore if temp table of another backend */
if (RELATION_IS_OTHER_TEMP(childrel))
{
/* ... but release the lock on it */
Assert(childrel != onerel);
table_close(childrel, AccessShareLock);
continue;
}
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
acquirefunc = acquire_sample_rows;
relpages = RelationGetNumberOfBlocks(childrel);
}
else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
{
/*
* For a foreign table, call the FDW's hook function to see
* whether it supports analysis.
*/
FdwRoutine *fdwroutine;
bool ok = false;
fdwroutine = GetFdwRoutineForRelation(childrel, false);
if (fdwroutine->AnalyzeForeignTable != NULL)
ok = fdwroutine->AnalyzeForeignTable(childrel,
&acquirefunc,
&relpages);
if (!ok)
{
/* ignore, but release the lock on it */
Assert(childrel != onerel);
table_close(childrel, AccessShareLock);
continue;
}
}
else
{
/*
* ignore, but release the lock on it. don't try to unlock the
* passed-in relation
*/
Assert(childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
if (childrel != onerel)
table_close(childrel, AccessShareLock);
else
table_close(childrel, NoLock);
continue;
}
/* OK, we'll process this child */
has_child = true;
rels[nrels] = childrel;
acquirefuncs[nrels] = acquirefunc;
relblocks[nrels] = (double) relpages;
totalblocks += (double) relpages;
nrels++;
}
/*
* If we don't have at least one child table to consider, fail. If the
* relation is a partitioned table, it's not counted as a child table.
*/
if (!has_child)
{
ereport(elevel,
(errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains no analyzable child tables",
get_namespace_name(RelationGetNamespace(onerel)),
RelationGetRelationName(onerel))));
return 0;
}
/*
* Now sample rows from each relation, proportionally to its fraction of
* the total block count. (This might be less than desirable if the child
* rels have radically different free-space percentages, but it's not
* clear that it's worth working harder.)
*/
pgstat_progress_update_param(PROGRESS_ANALYZE_CHILD_TABLES_TOTAL,
nrels);
numrows = 0;
for (i = 0; i < nrels; i++)
{
Relation childrel = rels[i];
AcquireSampleRowsFunc acquirefunc = acquirefuncs[i];
double childblocks = relblocks[i];
/*
* Report progress. The sampling function will normally report blocks
* done/total, but we need to reset them to 0 here, so that they don't
* show an old value until that.
*/
{
const int progress_index[] = {
PROGRESS_ANALYZE_CURRENT_CHILD_TABLE_RELID,
PROGRESS_ANALYZE_BLOCKS_DONE,
PROGRESS_ANALYZE_BLOCKS_TOTAL
};
const int64 progress_vals[] = {
RelationGetRelid(childrel),
0,
0,
};
pgstat_progress_update_multi_param(3, progress_index, progress_vals);
}
if (childblocks > 0)
{
int childtargrows;
childtargrows = (int) rint(targrows * childblocks / totalblocks);
/* Make sure we don't overrun due to roundoff error */
childtargrows = Min(childtargrows, targrows - numrows);
if (childtargrows > 0)
{
int childrows;
double trows,
tdrows;
/* Fetch a random sample of the child's rows */
childrows = (*acquirefunc) (childrel, elevel,
rows + numrows, childtargrows,
&trows, &tdrows);
/* We may need to convert from child's rowtype to parent's */
if (childrows > 0 &&
!equalRowTypes(RelationGetDescr(childrel),
RelationGetDescr(onerel)))
{
TupleConversionMap *map;
map = convert_tuples_by_name(RelationGetDescr(childrel),
RelationGetDescr(onerel));
if (map != NULL)
{
int j;
for (j = 0; j < childrows; j++)
{
HeapTuple newtup;
newtup = execute_attr_map_tuple(rows[numrows + j], map);
heap_freetuple(rows[numrows + j]);
rows[numrows + j] = newtup;
}
free_conversion_map(map);
}
}
/* And add to counts */
numrows += childrows;
*totalrows += trows;
*totaldeadrows += tdrows;
}
}
/*
* Note: we cannot release the child-table locks, since we may have
* pointers to their TOAST tables in the sampled rows.
*/
table_close(childrel, NoLock);
pgstat_progress_update_param(PROGRESS_ANALYZE_CHILD_TABLES_DONE,
i + 1);
}
return numrows;
}
/*
* update_attstats() -- update attribute statistics for one relation
*
* Statistics are stored in several places: the pg_class row for the
* relation has stats about the whole relation, and there is a
* pg_statistic row for each (non-system) attribute that has ever
* been analyzed. The pg_class values are updated by VACUUM, not here.
*
* pg_statistic rows are just added or updated normally. This means
* that pg_statistic will probably contain some deleted rows at the
* completion of a vacuum cycle, unless it happens to get vacuumed last.
*
* To keep things simple, we punt for pg_statistic, and don't try
* to compute or store rows for pg_statistic itself in pg_statistic.
* This could possibly be made to work, but it's not worth the trouble.
* Note analyze_rel() has seen to it that we won't come here when
* vacuuming pg_statistic itself.
*
* Note: there would be a race condition here if two backends could
* ANALYZE the same table concurrently. Presently, we lock that out
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
{
Relation sd;
int attno;
CatalogIndexState indstate = NULL;
if (natts <= 0)
return; /* nothing to do */
sd = table_open(StatisticRelationId, RowExclusiveLock);
for (attno = 0; attno < natts; attno++)
{
VacAttrStats *stats = vacattrstats[attno];
HeapTuple stup,
oldtup;
int i,
k,
n;
Datum values[Natts_pg_statistic];
bool nulls[Natts_pg_statistic];
bool replaces[Natts_pg_statistic];
/* Ignore attr if we weren't able to collect stats */
if (!stats->stats_valid)
continue;
/*
* Construct a new pg_statistic tuple
*/
for (i = 0; i < Natts_pg_statistic; ++i)
{
nulls[i] = false;
replaces[i] = true;
}
values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(relid);
values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(stats->tupattnum);
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);
i = Anum_pg_statistic_stakind1 - 1;
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
{
values[i++] = Int16GetDatum(stats->stakind[k]); /* stakindN */
}
i = Anum_pg_statistic_staop1 - 1;
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
{
values[i++] = ObjectIdGetDatum(stats->staop[k]); /* staopN */
}
i = Anum_pg_statistic_stacoll1 - 1;
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
{
values[i++] = ObjectIdGetDatum(stats->stacoll[k]); /* stacollN */
}
i = Anum_pg_statistic_stanumbers1 - 1;
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
{
if (stats->stanumbers[k] != NULL)
{
int nnum = stats->numnumbers[k];
Datum *numdatums = (Datum *) palloc(nnum * sizeof(Datum));
ArrayType *arry;
for (n = 0; n < nnum; n++)
numdatums[n] = Float4GetDatum(stats->stanumbers[k][n]);
arry = construct_array_builtin(numdatums, nnum, FLOAT4OID);
values[i++] = PointerGetDatum(arry); /* stanumbersN */
}
else
{
nulls[i] = true;
values[i++] = (Datum) 0;
}
}
i = Anum_pg_statistic_stavalues1 - 1;
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
{
if (stats->stavalues[k] != NULL)
{
ArrayType *arry;
arry = construct_array(stats->stavalues[k],
stats->numvalues[k],
stats->statypid[k],
stats->statyplen[k],
stats->statypbyval[k],
stats->statypalign[k]);
values[i++] = PointerGetDatum(arry); /* stavaluesN */
}
else
{
nulls[i] = true;
values[i++] = (Datum) 0;
}
}
/* Is there already a pg_statistic tuple for this attribute? */
oldtup = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(stats->tupattnum),
BoolGetDatum(inh));
/* Open index information when we know we need it */
if (indstate == NULL)
indstate = CatalogOpenIndexes(sd);
if (HeapTupleIsValid(oldtup))
{
/* Yes, replace it */
stup = heap_modify_tuple(oldtup,
RelationGetDescr(sd),
values,
nulls,
replaces);
ReleaseSysCache(oldtup);
CatalogTupleUpdateWithInfo(sd, &stup->t_self, stup, indstate);
}
else
{
/* No, insert new tuple */
stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
CatalogTupleInsertWithInfo(sd, stup, indstate);
}
heap_freetuple(stup);
}
if (indstate != NULL)
CatalogCloseIndexes(indstate);
table_close(sd, RowExclusiveLock);
}
/*
* Standard fetch function for use by compute_stats subroutines.
*
* This exists to provide some insulation between compute_stats routines
* and the actual storage of the sample data.
*/
static Datum
std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull)
{
int attnum = stats->tupattnum;
HeapTuple tuple = stats->rows[rownum];
TupleDesc tupDesc = stats->tupDesc;
return heap_getattr(tuple, attnum, tupDesc, isNull);
}
/*
* Fetch function for analyzing index expressions.
*
* We have not bothered to construct index tuples, instead the data is
* just in Datum arrays.
*/
static Datum
ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull)
{
int i;
/* exprvals and exprnulls are already offset for proper column */
i = rownum * stats->rowstride;
*isNull = stats->exprnulls[i];
return stats->exprvals[i];
}
/*
* ==========================================================================
*
* Code below this point represents the "standard" type-specific statistics
* analysis algorithms. This code can be replaced on a per-data-type basis
* by setting a nonzero value in pg_type.typanalyze.
*
*==========================================================================
*/
/*
* To avoid consuming too much memory during analysis and/or too much space
* in the resulting pg_statistic rows, we ignore varlena datums that are wider
* than WIDTH_THRESHOLD (after detoasting!). This is legitimate for MCV
* and distinct-value calculations since a wide value is unlikely to be
* duplicated at all, much less be a most-common value. For the same reason,
* ignoring wide values will not affect our estimates of histogram bin
* boundaries very much.
*/
#define WIDTH_THRESHOLD 1024
#define swapInt(a,b) do {int _tmp; _tmp=a; a=b; b=_tmp;} while(0)
#define swapDatum(a,b) do {Datum _tmp; _tmp=a; a=b; b=_tmp;} while(0)
/*
* Extra information used by the default analysis routines
*/
typedef struct
{
int count; /* # of duplicates */
int first; /* values[] index of first occurrence */
} ScalarMCVItem;
typedef struct
{
SortSupport ssup;
int *tupnoLink;
} CompareScalarsContext;
static void compute_trivial_stats(VacAttrStatsP stats,
AnalyzeAttrFetchFunc fetchfunc,
int samplerows,
double totalrows);
static void compute_distinct_stats(VacAttrStatsP stats,
AnalyzeAttrFetchFunc fetchfunc,
int samplerows,
double totalrows);
static void compute_scalar_stats(VacAttrStatsP stats,
AnalyzeAttrFetchFunc fetchfunc,
int samplerows,
double totalrows);
static int compare_scalars(const void *a, const void *b, void *arg);
static int compare_mcvs(const void *a, const void *b, void *arg);
static int analyze_mcv_list(int *mcv_counts,
int num_mcv,
double stadistinct,
double stanullfrac,
int samplerows,
double totalrows);
/*
* std_typanalyze -- the default type-specific typanalyze function
*/
bool
std_typanalyze(VacAttrStats *stats)
{
Oid ltopr;
Oid eqopr;
StdAnalyzeData *mystats;
/* If the attstattarget column is negative, use the default value */
if (stats->attstattarget < 0)
stats->attstattarget = default_statistics_target;
/* Look for default "<" and "=" operators for column's type */
get_sort_group_operators(stats->attrtypid,
false, false, false,
<opr, &eqopr, NULL,
NULL);
/* Save the operator info for compute_stats routines */
mystats = palloc_object(StdAnalyzeData);
mystats->eqopr = eqopr;
mystats->eqfunc = OidIsValid(eqopr) ? get_opcode(eqopr) : InvalidOid;
mystats->ltopr = ltopr;
stats->extra_data = mystats;
/*
* Determine which standard statistics algorithm to use
*/
if (OidIsValid(eqopr) && OidIsValid(ltopr))
{
/* Seems to be a scalar datatype */
stats->compute_stats = compute_scalar_stats;
/*--------------------
* The following choice of minrows is based on the paper
* "Random sampling for histogram construction: how much is enough?"
* by Surajit Chaudhuri, Rajeev Motwani and Vivek Narasayya, in
* Proceedings of ACM SIGMOD International Conference on Management
* of Data, 1998, Pages 436-447. Their Corollary 1 to Theorem 5
* says that for table size n, histogram size k, maximum relative
* error in bin size f, and error probability gamma, the minimum
* random sample size is
* r = 4 * k * ln(2*n/gamma) / f^2
* Taking f = 0.5, gamma = 0.01, n = 10^6 rows, we obtain
* r = 305.82 * k
* Note that because of the log function, the dependence on n is
* quite weak; even at n = 10^12, a 300*k sample gives <= 0.66
* bin size error with probability 0.99. So there's no real need to
* scale for n, which is a good thing because we don't necessarily
* know it at this point.
*--------------------
*/
stats->minrows = 300 * stats->attstattarget;
}
else if (OidIsValid(eqopr))
{
/* We can still recognize distinct values */
stats->compute_stats = compute_distinct_stats;
/* Might as well use the same minrows as above */
stats->minrows = 300 * stats->attstattarget;
}
else
{
/* Can't do much but the trivial stuff */
stats->compute_stats = compute_trivial_stats;
/* Might as well use the same minrows as above */
stats->minrows = 300 * stats->attstattarget;
}
return true;
}
/*
* compute_trivial_stats() -- compute very basic column statistics
*
* We use this when we cannot find a hash "=" operator for the datatype.
*
* We determine the fraction of non-null rows and the average datum width.
*/
static void
compute_trivial_stats(VacAttrStatsP stats,
AnalyzeAttrFetchFunc fetchfunc,
int samplerows,
double totalrows)
{
int i;
int null_cnt = 0;
int nonnull_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);
for (i = 0; i < samplerows; i++)
{
Datum value;
bool isnull;
vacuum_delay_point(true);
value = fetchfunc(stats, i, &isnull);
/* Check for null/nonnull */
if (isnull)
{
null_cnt++;
continue;
}
nonnull_cnt++;
/*
* If it's a variable-width field, add up widths for average width
* calculation. Note that if the value is toasted, we use the toasted
* width. We don't bother with this calculation if it's a fixed-width
* type.
*/
if (is_varlena)
{
total_width += VARSIZE_ANY(DatumGetPointer(value));
}
else if (is_varwidth)
{
/* must be cstring */
total_width += strlen(DatumGetCString(value)) + 1;
}
}
/* We can only compute average width if we found some non-null values. */
if (nonnull_cnt > 0)
{
stats->stats_valid = true;
/* Do the simple null-frac and width stats */
stats->stanullfrac = (double) null_cnt / (double) samplerows;
if (is_varwidth)
stats->stawidth = total_width / (double) nonnull_cnt;
else
stats->stawidth = stats->attrtype->typlen;
stats->stadistinct = 0.0; /* "unknown" */
}
else if (null_cnt > 0)
{
/* We found only nulls; assume the column is entirely null */
stats->stats_valid = true;
stats->stanullfrac = 1.0;
if (is_varwidth)
stats->stawidth = 0; /* "unknown" */
else
stats->stawidth = stats->attrtype->typlen;
stats->stadistinct = 0.0; /* "unknown" */
}
}
/*
* compute_distinct_stats() -- compute column statistics including ndistinct
*
* We use this when we can find only an "=" operator for the datatype.
*
* We determine the fraction of non-null rows, the average width, the
* most common values, and the (estimated) number of distinct values.
*
* The most common values are determined by brute force: we keep a list
* of previously seen values, ordered by number of times seen, as we scan
* the samples. A newly seen value is inserted just after the last
* multiply-seen value, causing the bottommost (oldest) singly-seen value
* to drop off the list. The accuracy of this method, and also its cost,
* depend mainly on the length of the list we are willing to keep.
*/
static void
compute_distinct_stats(VacAttrStatsP stats,
AnalyzeAttrFetchFunc fetchfunc,
int samplerows,
double totalrows)
{
int i;
int null_cnt = 0;
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);
FmgrInfo f_cmpeq;
typedef struct
{
Datum value;
int count;
} TrackItem;
TrackItem *track;
int track_cnt,
track_max;
int num_mcv = stats->attstattarget;
StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data;
/*
* We track up to 2*n values for an n-element MCV list; but at least 10
*/
track_max = 2 * num_mcv;
if (track_max < 10)
track_max = 10;
track = (TrackItem *) palloc(track_max * sizeof(TrackItem));
track_cnt = 0;
fmgr_info(mystats->eqfunc, &f_cmpeq);
for (i = 0; i < samplerows; i++)
{
Datum value;
bool isnull;
bool match;
int firstcount1,
j;
vacuum_delay_point(true);
value = fetchfunc(stats, i, &isnull);
/* Check for null/nonnull */
if (isnull)
{
null_cnt++;
continue;
}
nonnull_cnt++;
/*
* If it's a variable-width field, add up widths for average width
* calculation. Note that if the value is toasted, we use the toasted
* width. We don't bother with this calculation if it's a fixed-width
* type.
*/
if (is_varlena)
{
total_width += VARSIZE_ANY(DatumGetPointer(value));
/*
* If the value is toasted, we want to detoast it just once to
* avoid repeated detoastings and resultant excess memory usage
* during the comparisons. Also, check to see if the value is
* excessively wide, and if so don't detoast at all --- just
* ignore the value.
*/
if (toast_raw_datum_size(value) > WIDTH_THRESHOLD)
{
toowide_cnt++;
continue;
}
value = PointerGetDatum(PG_DETOAST_DATUM(value));
}
else if (is_varwidth)
{
/* must be cstring */
total_width += strlen(DatumGetCString(value)) + 1;
}
/*
* See if the value matches anything we're already tracking.
*/
match = false;
firstcount1 = track_cnt;
for (j = 0; j < track_cnt; j++)
{
if (DatumGetBool(FunctionCall2Coll(&f_cmpeq,
stats->attrcollid,
value, track[j].value)))
{
match = true;
break;
}
if (j < firstcount1 && track[j].count == 1)
firstcount1 = j;
}
if (match)
{
/* Found a match */
track[j].count++;
/* This value may now need to "bubble up" in the track list */
while (j > 0 && track[j].count > track[j - 1].count)
{
swapDatum(track[j].value, track[j - 1].value);
swapInt(track[j].count, track[j - 1].count);
j--;
}
}
else
{
/* No match. Insert at head of count-1 list */
if (track_cnt < track_max)
track_cnt++;
for (j = track_cnt - 1; j > firstcount1; j--)
{
track[j].value = track[j - 1].value;
track[j].count = track[j - 1].count;
}
if (firstcount1 < track_cnt)
{
track[firstcount1].value = value;
track[firstcount1].count = 1;
}
}
}
/* We can only compute real stats if we found some non-null values. */
if (nonnull_cnt > 0)
{
int nmultiple,
summultiple;
stats->stats_valid = true;
/* Do the simple null-frac and width stats */
stats->stanullfrac = (double) null_cnt / (double) samplerows;
if (is_varwidth)
stats->stawidth = total_width / (double) nonnull_cnt;
else
stats->stawidth = stats->attrtype->typlen;
/* Count the number of values we found multiple times */
summultiple = 0;
for (nmultiple = 0; nmultiple < track_cnt; nmultiple++)
{
if (track[nmultiple].count == 1)
break;
summultiple += track[nmultiple].count;
}
if (nmultiple == 0)
{
/*
* If we found no repeated non-null values, assume it's a unique
* column; but be sure to discount for any nulls we found.
*/
stats->stadistinct = -1.0 * (1.0 - stats->stanullfrac);
}
else if (track_cnt < track_max && toowide_cnt == 0 &&
nmultiple == track_cnt)
{
/*
* Our track list includes every value in the sample, and every
* value appeared more than once. Assume the column has just
* these values. (This case is meant to address columns with
* small, fixed sets of possible values, such as boolean or enum
* columns. If there are any values that appear just once in the
* sample, including too-wide values, we should assume that that's
* not what we're dealing with.)
*/
stats->stadistinct = track_cnt;
}
else
{
/*----------
* Estimate the number of distinct values using the estimator
* proposed by Haas and Stokes in IBM Research Report RJ 10025:
* n*d / (n - f1 + f1*n/N)
* where f1 is the number of distinct values that occurred
* exactly once in our sample of n rows (from a total of N),
* and d is the total number of distinct values in the sample.
* This is their Duj1 estimator; the other estimators they
* recommend are considerably more complex, and are numerically
* very unstable when n is much smaller than N.
*
* In this calculation, we consider only non-nulls. We used to
* include rows with null values in the n and N counts, but that
* leads to inaccurate answers in columns with many nulls, and
* it's intuitively bogus anyway considering the desired result is
* the number of distinct non-null values.
*
* We assume (not very reliably!) that all the multiply-occurring
* values are reflected in the final track[] list, and the other
* nonnull values all appeared but once. (XXX this usually
* results in a drastic overestimate of ndistinct. Can we do
* any better?)
*----------
*/
int f1 = nonnull_cnt - summultiple;
int d = f1 + nmultiple;
double n = samplerows - null_cnt;
double N = totalrows * (1.0 - stats->stanullfrac);
double stadistinct;
/* N == 0 shouldn't happen, but just in case ... */
if (N > 0)
stadistinct = (n * d) / ((n - f1) + f1 * n / N);
else
stadistinct = 0;
/* Clamp to sane range in case of roundoff error */
if (stadistinct < d)
stadistinct = d;
if (stadistinct > N)
stadistinct = N;
/* And round to integer */
stats->stadistinct = floor(stadistinct + 0.5);
}
/*
* If we estimated the number of distinct values at more than 10% of
* the total row count (a very arbitrary limit), then assume that
* stadistinct should scale with the row count rather than be a fixed
* value.
*/
if (stats->stadistinct > 0.1 * totalrows)
stats->stadistinct = -(stats->stadistinct / totalrows);
/*
* Decide how many values are worth storing as most-common values. If
* we are able to generate a complete MCV list (all the values in the
* sample will fit, and we think these are all the ones in the table),
* then do so. Otherwise, store only those values that are
* significantly more common than the values not in the list.
*
* Note: the first of these cases is meant to address columns with
* small, fixed sets of possible values, such as boolean or enum
* columns. If we can *completely* represent the column population by
* an MCV list that will fit into the stats target, then we should do
* so and thus provide the planner with complete information. But if
* the MCV list is not complete, it's generally worth being more
* selective, and not just filling it all the way up to the stats
* target.
*/
if (track_cnt < track_max && toowide_cnt == 0 &&
stats->stadistinct > 0 &&
track_cnt <= num_mcv)
{
/* Track list includes all values seen, and all will fit */
num_mcv = track_cnt;
}
else
{
int *mcv_counts;
/* Incomplete list; decide how many values are worth keeping */
if (num_mcv > track_cnt)
num_mcv = track_cnt;
if (num_mcv > 0)
{
mcv_counts = (int *) palloc(num_mcv * sizeof(int));
for (i = 0; i < num_mcv; i++)
mcv_counts[i] = track[i].count;
num_mcv = analyze_mcv_list(mcv_counts, num_mcv,
stats->stadistinct,
stats->stanullfrac,
samplerows, totalrows);
}
}
/* Generate MCV slot entry */
if (num_mcv > 0)
{
MemoryContext old_context;
Datum *mcv_values;
float4 *mcv_freqs;
/* Must copy the target values into anl_context */
old_context = MemoryContextSwitchTo(stats->anl_context);
mcv_values = (Datum *) palloc(num_mcv * sizeof(Datum));
mcv_freqs = (float4 *) palloc(num_mcv * sizeof(float4));
for (i = 0; i < num_mcv; i++)
{
mcv_values[i] = datumCopy(track[i].value,
stats->attrtype->typbyval,
stats->attrtype->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->stacoll[0] = stats->attrcollid;
stats->stanumbers[0] = mcv_freqs;
stats->numnumbers[0] = num_mcv;
stats->stavalues[0] = mcv_values;
stats->numvalues[0] = num_mcv;
/*
* Accept the defaults for stats->statypid and others. They have
* been set before we were called (see vacuum.h)
*/
}
}
else if (null_cnt > 0)
{
/* We found only nulls; assume the column is entirely null */
stats->stats_valid = true;
stats->stanullfrac = 1.0;
if (is_varwidth)
stats->stawidth = 0; /* "unknown" */
else
stats->stawidth = stats->attrtype->typlen;
stats->stadistinct = 0.0; /* "unknown" */
}
/* We don't need to bother cleaning up any of our temporary palloc's */
}
/*
* compute_scalar_stats() -- compute column statistics
*
* We use this when we can find "=" and "<" operators for the datatype.
*
* We determine the fraction of non-null rows, the average width, the
* most common values, the (estimated) number of distinct values, the
* distribution histogram, and the correlation of physical to logical order.
*
* The desired stats can be determined fairly easily after sorting the
* data values into order.
*/
static void
compute_scalar_stats(VacAttrStatsP stats,
AnalyzeAttrFetchFunc fetchfunc,
int samplerows,
double totalrows)
{
int i;
int null_cnt = 0;
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);
double corr_xysum;
SortSupportData ssup;
ScalarItem *values;
int values_cnt = 0;
int *tupnoLink;
ScalarMCVItem *track;
int track_cnt = 0;
int num_mcv = stats->attstattarget;
int num_bins = stats->attstattarget;
StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data;
values = (ScalarItem *) palloc(samplerows * sizeof(ScalarItem));
tupnoLink = (int *) palloc(samplerows * sizeof(int));
track = (ScalarMCVItem *) palloc(num_mcv * sizeof(ScalarMCVItem));
memset(&ssup, 0, sizeof(ssup));
ssup.ssup_cxt = CurrentMemoryContext;
ssup.ssup_collation = stats->attrcollid;
ssup.ssup_nulls_first = false;
/*
* For now, don't perform abbreviated key conversion, because full values
* are required for MCV slot generation. Supporting that optimization
* would necessitate teaching compare_scalars() to call a tie-breaker.
*/
ssup.abbreviate = false;
PrepareSortSupportFromOrderingOp(mystats->ltopr, &ssup);
/* Initial scan to find sortable values */
for (i = 0; i < samplerows; i++)
{
Datum value;
bool isnull;
vacuum_delay_point(true);
value = fetchfunc(stats, i, &isnull);
/* Check for null/nonnull */
if (isnull)
{
null_cnt++;
continue;
}
nonnull_cnt++;
/*
* If it's a variable-width field, add up widths for average width
* calculation. Note that if the value is toasted, we use the toasted
* width. We don't bother with this calculation if it's a fixed-width
* type.
*/
if (is_varlena)
{
total_width += VARSIZE_ANY(DatumGetPointer(value));
/*
* If the value is toasted, we want to detoast it just once to
* avoid repeated detoastings and resultant excess memory usage
* during the comparisons. Also, check to see if the value is
* excessively wide, and if so don't detoast at all --- just
* ignore the value.
*/
if (toast_raw_datum_size(value) > WIDTH_THRESHOLD)
{
toowide_cnt++;
continue;
}
value = PointerGetDatum(PG_DETOAST_DATUM(value));
}
else if (is_varwidth)
{
/* must be cstring */
total_width += strlen(DatumGetCString(value)) + 1;
}
/* Add it to the list to be sorted */
values[values_cnt].value = value;
values[values_cnt].tupno = values_cnt;
tupnoLink[values_cnt] = values_cnt;
values_cnt++;
}
/* We can only compute real stats if we found some sortable values. */
if (values_cnt > 0)
{
int ndistinct, /* # distinct values in sample */
nmultiple, /* # that appear multiple times */
num_hist,
dups_cnt;
int slot_idx = 0;
CompareScalarsContext cxt;
/* Sort the collected values */
cxt.ssup = &ssup;
cxt.tupnoLink = tupnoLink;
qsort_interruptible(values, values_cnt, sizeof(ScalarItem),
compare_scalars, &cxt);
/*
* Now scan the values in order, find the most common ones, and also
* accumulate ordering-correlation statistics.
*
* To determine which are most common, we first have to count the
* number of duplicates of each value. The duplicates are adjacent in
* the sorted list, so a brute-force approach is to compare successive
* datum values until we find two that are not equal. However, that
* requires N-1 invocations of the datum comparison routine, which are
* completely redundant with work that was done during the sort. (The
* sort algorithm must at some point have compared each pair of items
* that are adjacent in the sorted order; otherwise it could not know
* that it's ordered the pair correctly.) We exploit this by having
* compare_scalars remember the highest tupno index that each
* ScalarItem has been found equal to. At the end of the sort, a
* ScalarItem's tupnoLink will still point to itself if and only if it
* is the last item of its group of duplicates (since the group will
* be ordered by tupno).
*/
corr_xysum = 0;
ndistinct = 0;
nmultiple = 0;
dups_cnt = 0;
for (i = 0; i < values_cnt; i++)
{
int tupno = values[i].tupno;
corr_xysum += ((double) i) * ((double) tupno);
dups_cnt++;
if (tupnoLink[tupno] == tupno)
{
/* Reached end of duplicates of this value */
ndistinct++;
if (dups_cnt > 1)
{
nmultiple++;
if (track_cnt < num_mcv ||
dups_cnt > track[track_cnt - 1].count)
{
/*
* Found a new item for the mcv list; find its
* position, bubbling down old items if needed. Loop
* invariant is that j points at an empty/ replaceable
* slot.
*/
int j;
if (track_cnt < num_mcv)
track_cnt++;
for (j = track_cnt - 1; j > 0; j--)
{
if (dups_cnt <= track[j - 1].count)
break;
track[j].count = track[j - 1].count;
track[j].first = track[j - 1].first;
}
track[j].count = dups_cnt;
track[j].first = i + 1 - dups_cnt;
}
}
dups_cnt = 0;
}
}
stats->stats_valid = true;
/* Do the simple null-frac and width stats */
stats->stanullfrac = (double) null_cnt / (double) samplerows;
if (is_varwidth)
stats->stawidth = total_width / (double) nonnull_cnt;
else
stats->stawidth = stats->attrtype->typlen;
if (nmultiple == 0)
{
/*
* If we found no repeated non-null values, assume it's a unique
* column; but be sure to discount for any nulls we found.
*/
stats->stadistinct = -1.0 * (1.0 - stats->stanullfrac);
}
else if (toowide_cnt == 0 && nmultiple == ndistinct)
{
/*
* Every value in the sample appeared more than once. Assume the
* column has just these values. (This case is meant to address
* columns with small, fixed sets of possible values, such as
* boolean or enum columns. If there are any values that appear
* just once in the sample, including too-wide values, we should
* assume that that's not what we're dealing with.)
*/
stats->stadistinct = ndistinct;
}
else
{
/*----------
* Estimate the number of distinct values using the estimator
* proposed by Haas and Stokes in IBM Research Report RJ 10025:
* n*d / (n - f1 + f1*n/N)
* where f1 is the number of distinct values that occurred
* exactly once in our sample of n rows (from a total of N),
* and d is the total number of distinct values in the sample.
* This is their Duj1 estimator; the other estimators they
* recommend are considerably more complex, and are numerically
* very unstable when n is much smaller than N.
*
* In this calculation, we consider only non-nulls. We used to
* include rows with null values in the n and N counts, but that
* leads to inaccurate answers in columns with many nulls, and
* it's intuitively bogus anyway considering the desired result is
* the number of distinct non-null values.
*
* Overwidth values are assumed to have been distinct.
*----------
*/
int f1 = ndistinct - nmultiple + toowide_cnt;
int d = f1 + nmultiple;
double n = samplerows - null_cnt;
double N = totalrows * (1.0 - stats->stanullfrac);
double stadistinct;
/* N == 0 shouldn't happen, but just in case ... */
if (N > 0)
stadistinct = (n * d) / ((n - f1) + f1 * n / N);
else
stadistinct = 0;
/* Clamp to sane range in case of roundoff error */
if (stadistinct < d)
stadistinct = d;
if (stadistinct > N)
stadistinct = N;
/* And round to integer */
stats->stadistinct = floor(stadistinct + 0.5);
}
/*
* If we estimated the number of distinct values at more than 10% of
* the total row count (a very arbitrary limit), then assume that
* stadistinct should scale with the row count rather than be a fixed
* value.
*/
if (stats->stadistinct > 0.1 * totalrows)
stats->stadistinct = -(stats->stadistinct / totalrows);
/*
* Decide how many values are worth storing as most-common values. If
* we are able to generate a complete MCV list (all the values in the
* sample will fit, and we think these are all the ones in the table),
* then do so. Otherwise, store only those values that are
* significantly more common than the values not in the list.
*
* Note: the first of these cases is meant to address columns with
* small, fixed sets of possible values, such as boolean or enum
* columns. If we can *completely* represent the column population by
* an MCV list that will fit into the stats target, then we should do
* so and thus provide the planner with complete information. But if
* the MCV list is not complete, it's generally worth being more
* selective, and not just filling it all the way up to the stats
* target.
*/
if (track_cnt == ndistinct && toowide_cnt == 0 &&
stats->stadistinct > 0 &&
track_cnt <= num_mcv)
{
/* Track list includes all values seen, and all will fit */
num_mcv = track_cnt;
}
else
{
int *mcv_counts;
/* Incomplete list; decide how many values are worth keeping */
if (num_mcv > track_cnt)
num_mcv = track_cnt;
if (num_mcv > 0)
{
mcv_counts = (int *) palloc(num_mcv * sizeof(int));
for (i = 0; i < num_mcv; i++)
mcv_counts[i] = track[i].count;
num_mcv = analyze_mcv_list(mcv_counts, num_mcv,
stats->stadistinct,
stats->stanullfrac,
samplerows, totalrows);
}
}
/* Generate MCV slot entry */
if (num_mcv > 0)
{
MemoryContext old_context;
Datum *mcv_values;
float4 *mcv_freqs;
/* Must copy the target values into anl_context */
old_context = MemoryContextSwitchTo(stats->anl_context);
mcv_values = (Datum *) palloc(num_mcv * sizeof(Datum));
mcv_freqs = (float4 *) palloc(num_mcv * sizeof(float4));
for (i = 0; i < num_mcv; i++)
{
mcv_values[i] = datumCopy(values[track[i].first].value,
stats->attrtype->typbyval,
stats->attrtype->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->stacoll[slot_idx] = stats->attrcollid;
stats->stanumbers[slot_idx] = mcv_freqs;
stats->numnumbers[slot_idx] = num_mcv;
stats->stavalues[slot_idx] = mcv_values;
stats->numvalues[slot_idx] = num_mcv;
/*
* Accept the defaults for stats->statypid and others. They have
* been set before we were called (see vacuum.h)
*/
slot_idx++;
}
/*
* Generate a histogram slot entry if there are at least two distinct
* values not accounted for in the MCV list. (This ensures the
* histogram won't collapse to empty or a singleton.)
*/
num_hist = ndistinct - num_mcv;
if (num_hist > num_bins)
num_hist = num_bins + 1;
if (num_hist >= 2)
{
MemoryContext old_context;
Datum *hist_values;
int nvals;
int pos,
posfrac,
delta,
deltafrac;
/* Sort the MCV items into position order to speed next loop */
qsort_interruptible(track, num_mcv, sizeof(ScalarMCVItem),
compare_mcvs, NULL);
/*
* Collapse out the MCV items from the values[] array.
*
* Note we destroy the values[] array here... but we don't need it
* for anything more. We do, however, still need values_cnt.
* nvals will be the number of remaining entries in values[].
*/
if (num_mcv > 0)
{
int src,
dest;
int j;
src = dest = 0;
j = 0; /* index of next interesting MCV item */
while (src < values_cnt)
{
int ncopy;
if (j < num_mcv)
{
int first = track[j].first;
if (src >= first)
{
/* advance past this MCV item */
src = first + track[j].count;
j++;
continue;
}
ncopy = first - src;
}
else
ncopy = values_cnt - src;
memmove(&values[dest], &values[src],
ncopy * sizeof(ScalarItem));
src += ncopy;
dest += ncopy;
}
nvals = dest;
}
else
nvals = values_cnt;
Assert(nvals >= num_hist);
/* Must copy the target values into anl_context */
old_context = MemoryContextSwitchTo(stats->anl_context);
hist_values = (Datum *) palloc(num_hist * sizeof(Datum));
/*
* The object of this loop is to copy the first and last values[]
* entries along with evenly-spaced values in between. So the
* i'th value is values[(i * (nvals - 1)) / (num_hist - 1)]. But
* computing that subscript directly risks integer overflow when
* the stats target is more than a couple thousand. Instead we
* add (nvals - 1) / (num_hist - 1) to pos at each step, tracking
* the integral and fractional parts of the sum separately.
*/
delta = (nvals - 1) / (num_hist - 1);
deltafrac = (nvals - 1) % (num_hist - 1);
pos = posfrac = 0;
for (i = 0; i < num_hist; i++)
{
hist_values[i] = datumCopy(values[pos].value,
stats->attrtype->typbyval,
stats->attrtype->typlen);
pos += delta;
posfrac += deltafrac;
if (posfrac >= (num_hist - 1))
{
/* fractional part exceeds 1, carry to integer part */
pos++;
posfrac -= (num_hist - 1);
}
}
MemoryContextSwitchTo(old_context);
stats->stakind[slot_idx] = STATISTIC_KIND_HISTOGRAM;
stats->staop[slot_idx] = mystats->ltopr;
stats->stacoll[slot_idx] = stats->attrcollid;
stats->stavalues[slot_idx] = hist_values;
stats->numvalues[slot_idx] = num_hist;
/*
* Accept the defaults for stats->statypid and others. They have
* been set before we were called (see vacuum.h)
*/
slot_idx++;
}
/* Generate a correlation entry if there are multiple values */
if (values_cnt > 1)
{
MemoryContext old_context;
float4 *corrs;
double corr_xsum,
corr_x2sum;
/* Must copy the target values into anl_context */
old_context = MemoryContextSwitchTo(stats->anl_context);
corrs = palloc_object(float4);
MemoryContextSwitchTo(old_context);
/*----------
* Since we know the x and y value sets are both
* 0, 1, ..., values_cnt-1
* we have sum(x) = sum(y) =
* (values_cnt-1)*values_cnt / 2
* and sum(x^2) = sum(y^2) =
* (values_cnt-1)*values_cnt*(2*values_cnt-1) / 6.
*----------
*/
corr_xsum = ((double) (values_cnt - 1)) *
((double) values_cnt) / 2.0;
corr_x2sum = ((double) (values_cnt - 1)) *
((double) values_cnt) * (double) (2 * values_cnt - 1) / 6.0;
/* And the correlation coefficient reduces to */
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->stacoll[slot_idx] = stats->attrcollid;
stats->stanumbers[slot_idx] = corrs;
stats->numnumbers[slot_idx] = 1;
slot_idx++;
}
}
else if (nonnull_cnt > 0)
{
/* We found some non-null values, but they were all too wide */
Assert(nonnull_cnt == toowide_cnt);
stats->stats_valid = true;
/* Do the simple null-frac and width stats */
stats->stanullfrac = (double) null_cnt / (double) samplerows;
if (is_varwidth)
stats->stawidth = total_width / (double) nonnull_cnt;
else
stats->stawidth = stats->attrtype->typlen;
/* Assume all too-wide values are distinct, so it's a unique column */
stats->stadistinct = -1.0 * (1.0 - stats->stanullfrac);
}
else if (null_cnt > 0)
{
/* We found only nulls; assume the column is entirely null */
stats->stats_valid = true;
stats->stanullfrac = 1.0;
if (is_varwidth)
stats->stawidth = 0; /* "unknown" */
else
stats->stawidth = stats->attrtype->typlen;
stats->stadistinct = 0.0; /* "unknown" */
}
/* We don't need to bother cleaning up any of our temporary palloc's */
}
/*
* Comparator for sorting ScalarItems
*
* Aside from sorting the items, we update the tupnoLink[] array
* whenever two ScalarItems are found to contain equal datums. The array
* is indexed by tupno; for each ScalarItem, it contains the highest
* tupno that that item's datum has been found to be equal to. This allows
* us to avoid additional comparisons in compute_scalar_stats().
*/
static int
compare_scalars(const void *a, const void *b, void *arg)
{
Datum da = ((const ScalarItem *) a)->value;
int ta = ((const ScalarItem *) a)->tupno;
Datum db = ((const ScalarItem *) b)->value;
int tb = ((const ScalarItem *) b)->tupno;
CompareScalarsContext *cxt = (CompareScalarsContext *) arg;
int compare;
compare = ApplySortComparator(da, false, db, false, cxt->ssup);
if (compare != 0)
return compare;
/*
* The two datums are equal, so update cxt->tupnoLink[].
*/
if (cxt->tupnoLink[ta] < tb)
cxt->tupnoLink[ta] = tb;
if (cxt->tupnoLink[tb] < ta)
cxt->tupnoLink[tb] = ta;
/*
* For equal datums, sort by tupno
*/
return ta - tb;
}
/*
* Comparator for sorting ScalarMCVItems by position
*/
static int
compare_mcvs(const void *a, const void *b, void *arg)
{
int da = ((const ScalarMCVItem *) a)->first;
int db = ((const ScalarMCVItem *) b)->first;
return da - db;
}
/*
* Analyze the list of common values in the sample and decide how many are
* worth storing in the table's MCV list.
*
* mcv_counts is assumed to be a list of the counts of the most common values
* seen in the sample, starting with the most common. The return value is the
* number that are significantly more common than the values not in the list,
* and which are therefore deemed worth storing in the table's MCV list.
*/
static int
analyze_mcv_list(int *mcv_counts,
int num_mcv,
double stadistinct,
double stanullfrac,
int samplerows,
double totalrows)
{
double ndistinct_table;
double sumcount;
int i;
/*
* If the entire table was sampled, keep the whole list. This also
* protects us against division by zero in the code below.
*/
if (samplerows == totalrows || totalrows <= 1.0)
return num_mcv;
/* Re-extract the estimated number of distinct nonnull values in table */
ndistinct_table = stadistinct;
if (ndistinct_table < 0)
ndistinct_table = -ndistinct_table * totalrows;
/*
* Exclude the least common values from the MCV list, if they are not
* significantly more common than the estimated selectivity they would
* have if they weren't in the list. All non-MCV values are assumed to be
* equally common, after taking into account the frequencies of all the
* values in the MCV list and the number of nulls (c.f. eqsel()).
*
* Here sumcount tracks the total count of all but the last (least common)
* value in the MCV list, allowing us to determine the effect of excluding
* that value from the list.
*
* Note that we deliberately do this by removing values from the full
* list, rather than starting with an empty list and adding values,
* because the latter approach can fail to add any values if all the most
* common values have around the same frequency and make up the majority
* of the table, so that the overall average frequency of all values is
* roughly the same as that of the common values. This would lead to any
* uncommon values being significantly overestimated.
*/
sumcount = 0.0;
for (i = 0; i < num_mcv - 1; i++)
sumcount += mcv_counts[i];
while (num_mcv > 0)
{
double selec,
otherdistinct,
N,
n,
K,
variance,
stddev;
/*
* Estimated selectivity the least common value would have if it
* wasn't in the MCV list (c.f. eqsel()).
*/
selec = 1.0 - sumcount / samplerows - stanullfrac;
if (selec < 0.0)
selec = 0.0;
if (selec > 1.0)
selec = 1.0;
otherdistinct = ndistinct_table - (num_mcv - 1);
if (otherdistinct > 1)
selec /= otherdistinct;
/*
* If the value is kept in the MCV list, its population frequency is
* assumed to equal its sample frequency. We use the lower end of a
* textbook continuity-corrected Wald-type confidence interval to
* determine if that is significantly more common than the non-MCV
* frequency --- specifically we assume the population frequency is
* highly likely to be within around 2 standard errors of the sample
* frequency, which equates to an interval of 2 standard deviations
* either side of the sample count, plus an additional 0.5 for the
* continuity correction. Since we are sampling without replacement,
* this is a hypergeometric distribution.
*
* XXX: Empirically, this approach seems to work quite well, but it
* may be worth considering more advanced techniques for estimating
* the confidence interval of the hypergeometric distribution.
*/
N = totalrows;
n = samplerows;
K = N * mcv_counts[num_mcv - 1] / n;
variance = n * K * (N - K) * (N - n) / (N * N * (N - 1));
stddev = sqrt(variance);
if (mcv_counts[num_mcv - 1] > selec * samplerows + 2 * stddev + 0.5)
{
/*
* The value is significantly more common than the non-MCV
* selectivity would suggest. Keep it, and all the other more
* common values in the list.
*/
break;
}
else
{
/* Discard this value and consider the next least common value */
num_mcv--;
if (num_mcv == 0)
break;
sumcount -= mcv_counts[num_mcv - 1];
}
}
return num_mcv;
}
./bootstrap.c 0000664 0001750 0001750 00000100767 15221603750 012102 0 ustar xman xman /*-------------------------------------------------------------------------
*
* bootstrap.c
* routines to support running postgres in 'bootstrap' mode
* bootstrap mode is used to create the initial template database
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/backend/bootstrap/bootstrap.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <unistd.h>
#include <signal.h>
#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/tableam.h"
#include "access/toast_compression.h"
#include "access/xact.h"
#include "bootstrap/bootstrap.h"
#include "catalog/index.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "common/link-canary.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "port/pg_getopt_ctx.h"
#include "postmaster/postmaster.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "storage/shmem_internal.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/relmapper.h"
static void CheckerModeMain(void);
static void bootstrap_signals(void);
static Form_pg_attribute AllocateAttribute(void);
static void InsertOneProargdefaultsValue(char *value);
static void populate_typ_list(void);
static Oid gettype(char *type);
static void cleanup(void);
/* ----------------
* global variables
* ----------------
*/
Relation boot_reldesc; /* current relation descriptor */
Form_pg_attribute attrtypes[MAXATTR]; /* points to attribute info */
int numattr; /* number of attributes for cur. rel */
/*
* Basic information associated with each type. This is used before
* pg_type is filled, so it has to cover the datatypes used as column types
* in the core "bootstrapped" catalogs.
*
* XXX several of these input/output functions do catalog scans
* (e.g., F_REGPROCIN scans pg_proc). this obviously creates some
* order dependencies in the catalog creation process.
*/
struct typinfo
{
char name[NAMEDATALEN];
Oid oid;
Oid elem;
int16 len;
bool byval;
char align;
char storage;
Oid collation;
Oid inproc;
Oid outproc;
};
static const struct typinfo TypInfo[] = {
{"bool", BOOLOID, 0, 1, true, TYPALIGN_CHAR, TYPSTORAGE_PLAIN, InvalidOid,
F_BOOLIN, F_BOOLOUT},
{"bytea", BYTEAOID, 0, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
F_BYTEAIN, F_BYTEAOUT},
{"char", CHAROID, 0, 1, true, TYPALIGN_CHAR, TYPSTORAGE_PLAIN, InvalidOid,
F_CHARIN, F_CHAROUT},
{"cstring", CSTRINGOID, 0, -2, false, TYPALIGN_CHAR, TYPSTORAGE_PLAIN, InvalidOid,
F_CSTRING_IN, F_CSTRING_OUT},
{"int2", INT2OID, 0, 2, true, TYPALIGN_SHORT, TYPSTORAGE_PLAIN, InvalidOid,
F_INT2IN, F_INT2OUT},
{"int4", INT4OID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
F_INT4IN, F_INT4OUT},
{"int8", INT8OID, 0, 8, true, TYPALIGN_DOUBLE, TYPSTORAGE_PLAIN, InvalidOid,
F_INT8IN, F_INT8OUT},
{"float4", FLOAT4OID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
F_FLOAT4IN, F_FLOAT4OUT},
{"float8", FLOAT8OID, 0, 8, true, TYPALIGN_DOUBLE, TYPSTORAGE_PLAIN, InvalidOid,
F_FLOAT8IN, F_FLOAT8OUT},
{"name", NAMEOID, CHAROID, NAMEDATALEN, false, TYPALIGN_CHAR, TYPSTORAGE_PLAIN, C_COLLATION_OID,
F_NAMEIN, F_NAMEOUT},
{"regproc", REGPROCOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
F_REGPROCIN, F_REGPROCOUT},
{"text", TEXTOID, 0, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, DEFAULT_COLLATION_OID,
F_TEXTIN, F_TEXTOUT},
{"jsonb", JSONBOID, 0, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
F_JSONB_IN, F_JSONB_OUT},
{"oid", OIDOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
F_OIDIN, F_OIDOUT},
{"aclitem", ACLITEMOID, 0, 16, false, TYPALIGN_DOUBLE, TYPSTORAGE_PLAIN, InvalidOid,
F_ACLITEMIN, F_ACLITEMOUT},
{"pg_node_tree", PG_NODE_TREEOID, 0, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, DEFAULT_COLLATION_OID,
F_PG_NODE_TREE_IN, F_PG_NODE_TREE_OUT},
{"int2vector", INT2VECTOROID, INT2OID, -1, false, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
F_INT2VECTORIN, F_INT2VECTOROUT},
{"oidvector", OIDVECTOROID, OIDOID, -1, false, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid,
F_OIDVECTORIN, F_OIDVECTOROUT},
{"_int4", INT4ARRAYOID, INT4OID, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
F_ARRAY_IN, F_ARRAY_OUT},
{"_text", TEXTARRAYOID, TEXTOID, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, DEFAULT_COLLATION_OID,
F_ARRAY_IN, F_ARRAY_OUT},
{"_oid", OIDARRAYOID, OIDOID, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
F_ARRAY_IN, F_ARRAY_OUT},
{"_char", CHARARRAYOID, CHAROID, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, InvalidOid,
F_ARRAY_IN, F_ARRAY_OUT},
{"_aclitem", ACLITEMARRAYOID, ACLITEMOID, -1, false, TYPALIGN_DOUBLE, TYPSTORAGE_EXTENDED, InvalidOid,
F_ARRAY_IN, F_ARRAY_OUT}
};
static const int n_types = sizeof(TypInfo) / sizeof(struct typinfo);
struct typmap
{ /* a hack */
Oid am_oid;
FormData_pg_type am_typ;
};
static List *Typ = NIL; /* List of struct typmap* */
static struct typmap *Ap = NULL;
/*
* Basic information about built-in roles.
*
* Presently, this need only list roles that are mentioned in aclitem arrays
* in the catalog .dat files. We might as well list everything that is in
* pg_authid.dat, since there aren't that many. Like pg_authid.dat, we
* represent the bootstrap superuser's name as "POSTGRES", even though it
* (probably) won't be that in the finished installation; this means aclitem
* entries in .dat files must spell it like that.
*/
struct rolinfo
{
const char *rolname;
Oid oid;
};
static const struct rolinfo RolInfo[] = {
{"POSTGRES", BOOTSTRAP_SUPERUSERID},
{"pg_database_owner", ROLE_PG_DATABASE_OWNER},
{"pg_read_all_data", ROLE_PG_READ_ALL_DATA},
{"pg_write_all_data", ROLE_PG_WRITE_ALL_DATA},
{"pg_monitor", ROLE_PG_MONITOR},
{"pg_read_all_settings", ROLE_PG_READ_ALL_SETTINGS},
{"pg_read_all_stats", ROLE_PG_READ_ALL_STATS},
{"pg_stat_scan_tables", ROLE_PG_STAT_SCAN_TABLES},
{"pg_read_server_files", ROLE_PG_READ_SERVER_FILES},
{"pg_write_server_files", ROLE_PG_WRITE_SERVER_FILES},
{"pg_execute_server_program", ROLE_PG_EXECUTE_SERVER_PROGRAM},
{"pg_signal_backend", ROLE_PG_SIGNAL_BACKEND},
{"pg_checkpoint", ROLE_PG_CHECKPOINT},
{"pg_maintain", ROLE_PG_MAINTAIN},
{"pg_use_reserved_connections", ROLE_PG_USE_RESERVED_CONNECTIONS},
{"pg_create_subscription", ROLE_PG_CREATE_SUBSCRIPTION},
{"pg_signal_autovacuum_worker", ROLE_PG_SIGNAL_AUTOVACUUM_WORKER}
};
static Datum values[MAXATTR]; /* current row's attribute values */
static bool Nulls[MAXATTR];
static MemoryContext nogc = NULL; /* special no-gc mem context */
/*
* At bootstrap time, we first declare all the indices to be built, and
* then build them. The IndexList structure stores enough information
* to allow us to build the indices after they've been declared.
*/
typedef struct _IndexList
{
Oid il_heap;
Oid il_ind;
IndexInfo *il_info;
struct _IndexList *il_next;
} IndexList;
static IndexList *ILHead = NULL;
/*
* In shared memory checker mode, all we really want to do is create shared
* memory and semaphores (just to prove we can do it with the current GUC
* settings). Since, in fact, that was already done by
* CreateSharedMemoryAndSemaphores(), we have nothing more to do here.
*/
static void
CheckerModeMain(void)
{
proc_exit(0);
}
/*
* The main entry point for running the backend in bootstrap mode
*
* The bootstrap mode is used to initialize the template database.
* The bootstrap backend doesn't speak SQL, but instead expects
* commands in a special bootstrap language.
*
* When check_only is true, startup is done only far enough to verify that
* the current configuration, particularly the passed in options pertaining
* to shared memory sizing, options work (or at least do not cause an error
* up to shared memory creation).
*/
void
BootstrapModeMain(int argc, char *argv[], bool check_only)
{
int i;
char *progname = argv[0];
pg_getopt_ctx optctx;
int flag;
char *userDoption = NULL;
uint32 bootstrap_data_checksum_version = PG_DATA_CHECKSUM_OFF;
yyscan_t scanner;
Assert(!IsUnderPostmaster);
InitStandaloneProcess(argv[0]);
/* Set defaults, to be overridden by explicit options below */
InitializeGUCOptions();
/* an initial --boot or --check should be present */
Assert(argc > 1
&& (strcmp(argv[1], "--boot") == 0
|| strcmp(argv[1], "--check") == 0));
argv++;
argc--;
pg_getopt_start(&optctx, argc, argv, "B:c:d:D:Fkr:X:-:");
while ((flag = pg_getopt_next(&optctx)) != -1)
{
switch (flag)
{
case 'B':
SetConfigOption("shared_buffers", optctx.optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
case '-':
/*
* Error if the user misplaced a special must-be-first option
* for dispatching to a subprogram. parse_dispatch_option()
* returns DISPATCH_POSTMASTER if it doesn't find a match, so
* error for anything else.
*/
if (parse_dispatch_option(optctx.optarg) != DISPATCH_POSTMASTER)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("--%s must be first argument", optctx.optarg)));
pg_fallthrough;
case 'c':
{
char *name,
*value;
ParseLongOption(optctx.optarg, &name, &value);
if (!value)
{
if (flag == '-')
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("--%s requires a value",
optctx.optarg)));
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("-c %s requires a value",
optctx.optarg)));
}
SetConfigOption(name, value, PGC_POSTMASTER, PGC_S_ARGV);
pfree(name);
pfree(value);
break;
}
case 'D':
userDoption = pstrdup(optctx.optarg);
break;
case 'd':
{
/* Turn on debugging for the bootstrap process. */
char *debugstr;
debugstr = psprintf("debug%s", optctx.optarg);
SetConfigOption("log_min_messages", debugstr,
PGC_POSTMASTER, PGC_S_ARGV);
SetConfigOption("client_min_messages", debugstr,
PGC_POSTMASTER, PGC_S_ARGV);
pfree(debugstr);
}
break;
case 'F':
SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV);
break;
case 'k':
bootstrap_data_checksum_version = PG_DATA_CHECKSUM_VERSION;
break;
case 'r':
strlcpy(OutputFileName, optctx.optarg, MAXPGPATH);
break;
case 'X':
SetConfigOption("wal_segment_size", optctx.optarg, PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT);
break;
default:
write_stderr("Try \"%s --help\" for more information.\n",
progname);
proc_exit(1);
break;
}
}
if (argc != optctx.optind)
{
write_stderr("%s: invalid command-line arguments\n", progname);
proc_exit(1);
}
/* Acquire configuration parameters */
if (!SelectConfigFiles(userDoption, progname))
proc_exit(1);
/*
* Validate we have been given a reasonable-looking DataDir and change
* into it
*/
checkDataDir();
ChangeToDataDir();
CreateDataDirLockFile(false);
SetProcessingMode(BootstrapProcessing);
IgnoreSystemIndexes = true;
RegisterBuiltinShmemCallbacks();
InitializeMaxBackends();
/*
* Even though bootstrapping runs in single-process mode, initialize
* postmaster child slots array so that --check can detect running out of
* shared memory or other resources if max_connections is set too high.
*/
InitPostmasterChildSlots();
InitializeFastPathLocks();
ShmemCallRequestCallbacks();
CreateSharedMemoryAndSemaphores();
/*
* Estimate number of openable files. This is essential too in --check
* mode, because on some platforms semaphores count as open files.
*/
set_max_safe_fds();
/*
* XXX: It might make sense to move this into its own function at some
* point. Right now it seems like it'd cause more code duplication than
* it's worth.
*/
if (check_only)
{
SetProcessingMode(NormalProcessing);
CheckerModeMain();
abort();
}
/*
* Do backend-like initialization for bootstrap mode
*/
InitProcess();
BaseInit();
bootstrap_signals();
BootStrapXLOG(bootstrap_data_checksum_version);
/*
* To ensure that src/common/link-canary.c is linked into the backend, we
* must call it from somewhere. Here is as good as anywhere.
*/
if (pg_link_canary_is_frontend())
elog(ERROR, "backend is incorrectly linked to frontend functions");
InitPostgres(NULL, InvalidOid, NULL, InvalidOid, 0, NULL);
/* Initialize stuff for bootstrap-file processing */
for (i = 0; i < MAXATTR; i++)
{
attrtypes[i] = NULL;
Nulls[i] = false;
}
if (boot_yylex_init(&scanner) != 0)
elog(ERROR, "yylex_init() failed: %m");
/*
* Process bootstrap input.
*/
StartTransactionCommand();
boot_yyparse(scanner);
CommitTransactionCommand();
/*
* We should now know about all mapped relations, so it's okay to write
* out the initial relation mapping files.
*/
RelationMapFinishBootstrap();
/* Clean up and exit */
cleanup();
proc_exit(0);
}
/* ----------------------------------------------------------------
* misc functions
* ----------------------------------------------------------------
*/
/*
* Set up signal handling for a bootstrap process
*/
static void
bootstrap_signals(void)
{
Assert(!IsUnderPostmaster);
/*
* We don't actually need any non-default signal handling in bootstrap
* mode; "curl up and die" is a sufficient response for all these cases.
* Let's set that handling explicitly, as documentation if nothing else.
*/
pqsignal(SIGHUP, PG_SIG_DFL);
pqsignal(SIGINT, PG_SIG_DFL);
pqsignal(SIGTERM, PG_SIG_DFL);
pqsignal(SIGQUIT, PG_SIG_DFL);
}
/* ----------------------------------------------------------------
* MANUAL BACKEND INTERACTIVE INTERFACE COMMANDS
* ----------------------------------------------------------------
*/
/* ----------------
* boot_openrel
*
* Execute BKI OPEN command.
* ----------------
*/
void
boot_openrel(char *relname)
{
int i;
if (strlen(relname) >= NAMEDATALEN)
relname[NAMEDATALEN - 1] = '\0';
/*
* pg_type must be filled before any OPEN command is executed, hence we
* can now populate Typ if we haven't yet.
*/
if (Typ == NIL)
populate_typ_list();
if (boot_reldesc != NULL)
closerel(NULL);
elog(DEBUG4, "open relation %s, attrsize %d",
relname, (int) ATTRIBUTE_FIXED_PART_SIZE);
boot_reldesc = table_openrv(makeRangeVar(NULL, relname, -1), NoLock);
numattr = RelationGetNumberOfAttributes(boot_reldesc);
for (i = 0; i < numattr; i++)
{
if (attrtypes[i] == NULL)
attrtypes[i] = AllocateAttribute();
memmove(attrtypes[i],
TupleDescAttr(boot_reldesc->rd_att, i),
ATTRIBUTE_FIXED_PART_SIZE);
{
Form_pg_attribute at = attrtypes[i];
elog(DEBUG4, "create attribute %d name %s len %d num %d type %u",
i, NameStr(at->attname), at->attlen, at->attnum,
at->atttypid);
}
}
}
/* ----------------
* closerel
* ----------------
*/
void
closerel(char *relname)
{
if (relname)
{
if (boot_reldesc)
{
if (strcmp(RelationGetRelationName(boot_reldesc), relname) != 0)
elog(ERROR, "close of %s when %s was expected",
relname, RelationGetRelationName(boot_reldesc));
}
else
elog(ERROR, "close of %s before any relation was opened",
relname);
}
if (boot_reldesc == NULL)
elog(ERROR, "no open relation to close");
else
{
elog(DEBUG4, "close relation %s",
RelationGetRelationName(boot_reldesc));
table_close(boot_reldesc, NoLock);
boot_reldesc = NULL;
}
}
/* ----------------
* DEFINEATTR()
*
* define a <field,type> pair
* if there are n fields in a relation to be created, this routine
* will be called n times
* ----------------
*/
void
DefineAttr(char *name, char *type, int attnum, int nullness)
{
Oid typeoid;
if (boot_reldesc != NULL)
{
elog(WARNING, "no open relations allowed with CREATE command");
closerel(NULL);
}
if (attrtypes[attnum] == NULL)
attrtypes[attnum] = AllocateAttribute();
MemSet(attrtypes[attnum], 0, ATTRIBUTE_FIXED_PART_SIZE);
namestrcpy(&attrtypes[attnum]->attname, name);
elog(DEBUG4, "column %s %s", NameStr(attrtypes[attnum]->attname), type);
attrtypes[attnum]->attnum = attnum + 1;
typeoid = gettype(type);
if (Typ != NIL)
{
attrtypes[attnum]->atttypid = Ap->am_oid;
attrtypes[attnum]->attlen = Ap->am_typ.typlen;
attrtypes[attnum]->attbyval = Ap->am_typ.typbyval;
attrtypes[attnum]->attalign = Ap->am_typ.typalign;
attrtypes[attnum]->attstorage = Ap->am_typ.typstorage;
attrtypes[attnum]->attcompression = InvalidCompressionMethod;
attrtypes[attnum]->attcollation = Ap->am_typ.typcollation;
/* if an array type, assume 1-dimensional attribute */
if (Ap->am_typ.typelem != InvalidOid && Ap->am_typ.typlen < 0)
attrtypes[attnum]->attndims = 1;
else
attrtypes[attnum]->attndims = 0;
}
else
{
attrtypes[attnum]->atttypid = TypInfo[typeoid].oid;
attrtypes[attnum]->attlen = TypInfo[typeoid].len;
attrtypes[attnum]->attbyval = TypInfo[typeoid].byval;
attrtypes[attnum]->attalign = TypInfo[typeoid].align;
attrtypes[attnum]->attstorage = TypInfo[typeoid].storage;
attrtypes[attnum]->attcompression = InvalidCompressionMethod;
attrtypes[attnum]->attcollation = TypInfo[typeoid].collation;
/* if an array type, assume 1-dimensional attribute */
if (TypInfo[typeoid].elem != InvalidOid &&
attrtypes[attnum]->attlen < 0)
attrtypes[attnum]->attndims = 1;
else
attrtypes[attnum]->attndims = 0;
}
/*
* If a system catalog column is collation-aware, force it to use C
* collation, so that its behavior is independent of the database's
* collation. This is essential to allow template0 to be cloned with a
* different database collation.
*/
if (OidIsValid(attrtypes[attnum]->attcollation))
attrtypes[attnum]->attcollation = C_COLLATION_OID;
attrtypes[attnum]->atttypmod = -1;
attrtypes[attnum]->attislocal = true;
if (nullness == BOOTCOL_NULL_FORCE_NOT_NULL)
{
attrtypes[attnum]->attnotnull = true;
}
else if (nullness == BOOTCOL_NULL_FORCE_NULL)
{
attrtypes[attnum]->attnotnull = false;
}
else
{
Assert(nullness == BOOTCOL_NULL_AUTO);
/*
* Mark as "not null" if type is fixed-width and prior columns are
* likewise fixed-width and not-null. This corresponds to case where
* column can be accessed directly via C struct declaration.
*/
if (attrtypes[attnum]->attlen > 0)
{
int i;
/* check earlier attributes */
for (i = 0; i < attnum; i++)
{
if (attrtypes[i]->attlen <= 0 ||
!attrtypes[i]->attnotnull)
break;
}
if (i == attnum)
attrtypes[attnum]->attnotnull = true;
}
}
}
/* ----------------
* InsertOneTuple
*
* If objectid is not zero, it is a specific OID to assign to the tuple.
* Otherwise, an OID will be assigned (if necessary) by heap_insert.
* ----------------
*/
void
InsertOneTuple(void)
{
HeapTuple tuple;
TupleDesc tupDesc;
int i;
elog(DEBUG4, "inserting row with %d columns", numattr);
tupDesc = CreateTupleDesc(numattr, attrtypes);
tuple = heap_form_tuple(tupDesc, values, Nulls);
pfree(tupDesc); /* just free's tupDesc, not the attrtypes */
simple_heap_insert(boot_reldesc, tuple);
heap_freetuple(tuple);
elog(DEBUG4, "row inserted");
/*
* Reset null markers for next tuple
*/
for (i = 0; i < numattr; i++)
Nulls[i] = false;
}
/* ----------------
* InsertOneValue
* ----------------
*/
void
InsertOneValue(char *value, int i)
{
Form_pg_attribute attr;
Oid typoid;
int16 typlen;
bool typbyval;
char typalign;
char typdelim;
Oid typioparam;
Oid typinput;
Oid typoutput;
Oid typcollation;
Assert(i >= 0 && i < MAXATTR);
elog(DEBUG4, "inserting column %d value \"%s\"", i, value);
attr = TupleDescAttr(RelationGetDescr(boot_reldesc), i);
typoid = attr->atttypid;
boot_get_type_io_data(typoid,
&typlen, &typbyval, &typalign,
&typdelim, &typioparam,
&typinput, &typoutput,
&typcollation);
/*
* pg_node_tree values can't be inserted normally (pg_node_tree_in would
* just error out), so provide special cases for such columns that we
* would like to fill during bootstrap.
*/
if (typoid == PG_NODE_TREEOID)
{
/* pg_proc.proargdefaults */
if (RelationGetRelid(boot_reldesc) == ProcedureRelationId &&
i == Anum_pg_proc_proargdefaults - 1)
InsertOneProargdefaultsValue(value);
else /* maybe other cases later */
elog(ERROR, "can't handle pg_node_tree input for %s.%s",
RelationGetRelationName(boot_reldesc),
NameStr(attr->attname));
}
else
{
/* Normal case */
values[i] = OidInputFunctionCall(typinput, value, typioparam, -1);
}
/*
* We use ereport not elog here so that parameters aren't evaluated unless
* the message is going to be printed, which generally it isn't
*/
ereport(DEBUG4,
(errmsg_internal("inserted -> %s",
OidOutputFunctionCall(typoutput, values[i]))));
}
/* ----------------
* InsertOneProargdefaultsValue
*
* In general, proargdefaults can be a list of any expressions, but
* for bootstrap we only support a list of Const nodes. The input
* has the form of a text array, and we feed non-null elements to the
* typinput functions for the appropriate parameters.
* ----------------
*/
static void
InsertOneProargdefaultsValue(char *value)
{
int pronargs;
oidvector *proargtypes;
Datum arrayval;
Datum *array_datums;
bool *array_nulls;
int array_count;
List *proargdefaults;
char *nodestring;
/* The pg_proc columns we need to use must have been filled already */
StaticAssertDecl(Anum_pg_proc_pronargs < Anum_pg_proc_proargdefaults,
"pronargs must come before proargdefaults");
StaticAssertDecl(Anum_pg_proc_pronargdefaults < Anum_pg_proc_proargdefaults,
"pronargdefaults must come before proargdefaults");
StaticAssertDecl(Anum_pg_proc_proargtypes < Anum_pg_proc_proargdefaults,
"proargtypes must come before proargdefaults");
if (Nulls[Anum_pg_proc_pronargs - 1])
elog(ERROR, "pronargs must not be null");
if (Nulls[Anum_pg_proc_proargtypes - 1])
elog(ERROR, "proargtypes must not be null");
pronargs = DatumGetInt16(values[Anum_pg_proc_pronargs - 1]);
proargtypes = DatumGetPointer(values[Anum_pg_proc_proargtypes - 1]);
Assert(pronargs == proargtypes->dim1);
/* Parse the input string as an array value, then deconstruct to Datums */
arrayval = OidFunctionCall3(F_ARRAY_IN,
CStringGetDatum(value),
ObjectIdGetDatum(CSTRINGOID),
Int32GetDatum(-1));
deconstruct_array_builtin(DatumGetArrayTypeP(arrayval), CSTRINGOID,
&array_datums, &array_nulls, &array_count);
/* The values should correspond to the last N argtypes */
if (array_count > pronargs)
elog(ERROR, "too many proargdefaults entries");
/* Build the List of Const nodes */
proargdefaults = NIL;
for (int i = 0; i < array_count; i++)
{
Oid argtype = proargtypes->values[pronargs - array_count + i];
int16 typlen;
bool typbyval;
char typalign;
char typdelim;
Oid typioparam;
Oid typinput;
Oid typoutput;
Oid typcollation;
Datum defval;
bool defnull;
Const *defConst;
boot_get_type_io_data(argtype,
&typlen, &typbyval, &typalign,
&typdelim, &typioparam,
&typinput, &typoutput,
&typcollation);
defnull = array_nulls[i];
if (defnull)
defval = (Datum) 0;
else
defval = OidInputFunctionCall(typinput,
DatumGetCString(array_datums[i]),
typioparam, -1);
defConst = makeConst(argtype,
-1, /* never any typmod */
typcollation,
typlen,
defval,
defnull,
typbyval);
proargdefaults = lappend(proargdefaults, defConst);
}
/*
* Flatten the List to a node-tree string, then convert to a text datum,
* which is the storage representation of pg_node_tree.
*/
nodestring = nodeToString(proargdefaults);
values[Anum_pg_proc_proargdefaults - 1] = CStringGetTextDatum(nodestring);
Nulls[Anum_pg_proc_proargdefaults - 1] = false;
/*
* Hack: fill in pronargdefaults with the right value. This is surely
* ugly, but it beats making the programmer do it.
*/
values[Anum_pg_proc_pronargdefaults - 1] = Int16GetDatum(array_count);
Nulls[Anum_pg_proc_pronargdefaults - 1] = false;
}
/* ----------------
* InsertOneNull
* ----------------
*/
void
InsertOneNull(int i)
{
elog(DEBUG4, "inserting column %d NULL", i);
Assert(i >= 0 && i < MAXATTR);
if (TupleDescAttr(boot_reldesc->rd_att, i)->attnotnull)
elog(ERROR,
"NULL value specified for not-null column \"%s\" of relation \"%s\"",
NameStr(TupleDescAttr(boot_reldesc->rd_att, i)->attname),
RelationGetRelationName(boot_reldesc));
values[i] = PointerGetDatum(NULL);
Nulls[i] = true;
}
/* ----------------
* cleanup
* ----------------
*/
static void
cleanup(void)
{
if (boot_reldesc != NULL)
closerel(NULL);
}
/* ----------------
* populate_typ_list
*
* Load the Typ list by reading pg_type.
* ----------------
*/
static void
populate_typ_list(void)
{
Relation rel;
TableScanDesc scan;
HeapTuple tup;
MemoryContext old;
Assert(Typ == NIL);
rel = table_open(TypeRelationId, NoLock);
scan = table_beginscan_catalog(rel, 0, NULL);
old = MemoryContextSwitchTo(TopMemoryContext);
while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
Form_pg_type typForm = (Form_pg_type) GETSTRUCT(tup);
struct typmap *newtyp;
newtyp = palloc_object(struct typmap);
Typ = lappend(Typ, newtyp);
newtyp->am_oid = typForm->oid;
memcpy(&newtyp->am_typ, typForm, sizeof(newtyp->am_typ));
}
MemoryContextSwitchTo(old);
table_endscan(scan);
table_close(rel, NoLock);
}
/* ----------------
* gettype
*
* NB: this is really ugly; it will return an integer index into TypInfo[],
* and not an OID at all, until the first reference to a type not known in
* TypInfo[]. At that point it will read and cache pg_type in Typ,
* and subsequently return a real OID (and set the global pointer Ap to
* point at the found row in Typ). So caller must check whether Typ is
* still NIL to determine what the return value is!
* ----------------
*/
static Oid
gettype(char *type)
{
if (Typ != NIL)
{
ListCell *lc;
foreach(lc, Typ)
{
struct typmap *app = lfirst(lc);
if (strncmp(NameStr(app->am_typ.typname), type, NAMEDATALEN) == 0)
{
Ap = app;
return app->am_oid;
}
}
/*
* The type wasn't known; reload the pg_type contents and check again
* to handle composite types, added since last populating the list.
*/
list_free_deep(Typ);
Typ = NIL;
populate_typ_list();
/*
* Calling gettype would result in infinite recursion for types
* missing in pg_type, so just repeat the lookup.
*/
foreach(lc, Typ)
{
struct typmap *app = lfirst(lc);
if (strncmp(NameStr(app->am_typ.typname), type, NAMEDATALEN) == 0)
{
Ap = app;
return app->am_oid;
}
}
}
else
{
int i;
for (i = 0; i < n_types; i++)
{
if (strncmp(type, TypInfo[i].name, NAMEDATALEN) == 0)
return i;
}
/* Not in TypInfo, so we'd better be able to read pg_type now */
elog(DEBUG4, "external type: %s", type);
populate_typ_list();
return gettype(type);
}
elog(ERROR, "unrecognized type \"%s\"", type);
/* not reached, here to make compiler happy */
return 0;
}
/* ----------------
* boot_get_type_io_data
*
* Obtain type I/O information at bootstrap time. This intentionally has
* an API very close to that of lsyscache.c's get_type_io_data, except that
* we only support obtaining the typinput and typoutput routines, not
* the binary I/O routines, and we also return the type's collation.
* This is exported so that array_in and array_out can be made to work
* during early bootstrap.
* ----------------
*/
void
boot_get_type_io_data(Oid typid,
int16 *typlen,
bool *typbyval,
char *typalign,
char *typdelim,
Oid *typioparam,
Oid *typinput,
Oid *typoutput,
Oid *typcollation)
{
if (Typ != NIL)
{
/* We have the boot-time contents of pg_type, so use it */
struct typmap *ap = NULL;
ListCell *lc;
foreach(lc, Typ)
{
ap = lfirst(lc);
if (ap->am_oid == typid)
break;
}
if (!ap || ap->am_oid != typid)
elog(ERROR, "type OID %u not found in Typ list", typid);
*typlen = ap->am_typ.typlen;
*typbyval = ap->am_typ.typbyval;
*typalign = ap->am_typ.typalign;
*typdelim = ap->am_typ.typdelim;
/* XXX this logic must match getTypeIOParam() */
if (OidIsValid(ap->am_typ.typelem))
*typioparam = ap->am_typ.typelem;
else
*typioparam = typid;
*typinput = ap->am_typ.typinput;
*typoutput = ap->am_typ.typoutput;
*typcollation = ap->am_typ.typcollation;
}
else
{
/* We don't have pg_type yet, so use the hard-wired TypInfo array */
int typeindex;
for (typeindex = 0; typeindex < n_types; typeindex++)
{
if (TypInfo[typeindex].oid == typid)
break;
}
if (typeindex >= n_types)
elog(ERROR, "type OID %u not found in TypInfo", typid);
*typlen = TypInfo[typeindex].len;
*typbyval = TypInfo[typeindex].byval;
*typalign = TypInfo[typeindex].align;
/* We assume typdelim is ',' for all boot-time types */
*typdelim = ',';
/* XXX this logic must match getTypeIOParam() */
if (OidIsValid(TypInfo[typeindex].elem))
*typioparam = TypInfo[typeindex].elem;
else
*typioparam = typid;
*typinput = TypInfo[typeindex].inproc;
*typoutput = TypInfo[typeindex].outproc;
*typcollation = TypInfo[typeindex].collation;
}
}
/* ----------------
* boot_get_role_oid
*
* Look up a role name at bootstrap time. This is equivalent to
* get_role_oid(rolname, true): return the role OID or InvalidOid if
* not found. We only need to cope with built-in role names.
* ----------------
*/
Oid
boot_get_role_oid(const char *rolname)
{
for (int i = 0; i < lengthof(RolInfo); i++)
{
if (strcmp(RolInfo[i].rolname, rolname) == 0)
return RolInfo[i].oid;
}
return InvalidOid;
}
/* ----------------
* AllocateAttribute
*
* Note: bootstrap never sets any per-column ACLs, so we only need
* ATTRIBUTE_FIXED_PART_SIZE space per attribute.
* ----------------
*/
static Form_pg_attribute
AllocateAttribute(void)
{
return (Form_pg_attribute)
MemoryContextAllocZero(TopMemoryContext, ATTRIBUTE_FIXED_PART_SIZE);
}
/*
* index_register() -- record an index that has been set up for building
* later.
*
* At bootstrap time, we define a bunch of indexes on system catalogs.
* We postpone actually building the indexes until just before we're
* finished with initialization, however. This is because the indexes
* themselves have catalog entries, and those have to be included in the
* indexes on those catalogs. Doing it in two phases is the simplest
* way of making sure the indexes have the right contents at the end.
*/
void
index_register(Oid heap,
Oid ind,
const IndexInfo *indexInfo)
{
IndexList *newind;
MemoryContext oldcxt;
/*
* XXX mao 10/31/92 -- don't gc index reldescs, associated info at
* bootstrap time. we'll declare the indexes now, but want to create them
* later.
*/
if (nogc == NULL)
nogc = AllocSetContextCreate(NULL,
"BootstrapNoGC",
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(nogc);
newind = palloc_object(IndexList);
newind->il_heap = heap;
newind->il_ind = ind;
newind->il_info = palloc_object(IndexInfo);
memcpy(newind->il_info, indexInfo, sizeof(IndexInfo));
/* expressions will likely be null, but may as well copy it */
newind->il_info->ii_Expressions =
copyObject(indexInfo->ii_Expressions);
newind->il_info->ii_ExpressionsExpand =
copyObject(indexInfo->ii_ExpressionsExpand);
newind->il_info->ii_ExpressionsState = NIL;
newind->il_info->ii_ExpressionsExpandState = NIL;
/* predicate will likely be null, but may as well copy it */
newind->il_info->ii_Predicate =
copyObject(indexInfo->ii_Predicate);
newind->il_info->ii_PredicateExpand =
copyObject(indexInfo->ii_PredicateExpand);
newind->il_info->ii_PredicateState = NULL;
newind->il_info->ii_PredicateExpandState = NULL;
/* no exclusion constraints at bootstrap time, so no need to copy */
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(indexInfo->ii_ExclusionProcs == NULL);
Assert(indexInfo->ii_ExclusionStrats == NULL);
newind->il_next = ILHead;
ILHead = newind;
MemoryContextSwitchTo(oldcxt);
}
/*
* build_indices -- fill in all the indexes registered earlier
*/
void
build_indices(void)
{
for (; ILHead != NULL; ILHead = ILHead->il_next)
{
Relation heap;
Relation ind;
/* need not bother with locks during bootstrap */
heap = table_open(ILHead->il_heap, NoLock);
ind = index_open(ILHead->il_ind, NoLock);
index_build(heap, ind, ILHead->il_info, false, false, false);
index_close(ind, NoLock);
table_close(heap, NoLock);
}
}
./createplan.c 0000664 0001750 0001750 00000666531 15221730313 012205 0 ustar xman xman /*-------------------------------------------------------------------------
*
* createplan.c
* Routines to create the desired plan for processing a query.
* Planning is complete, we just need to convert the selected
* Path into a Plan.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/optimizer/plan/createplan.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "catalog/pg_class.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/paramassign.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
#include "partitioning/partprune.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h"
/*
* Flag bits that can appear in the flags argument of create_plan_recurse().
* These can be OR-ed together.
*
* CP_EXACT_TLIST specifies that the generated plan node must return exactly
* the tlist specified by the path's pathtarget (this overrides both
* CP_SMALL_TLIST and CP_LABEL_TLIST, if those are set). Otherwise, the
* plan node is allowed to return just the Vars and PlaceHolderVars needed
* to evaluate the pathtarget.
*
* CP_SMALL_TLIST specifies that a narrower tlist is preferred. This is
* passed down by parent nodes such as Sort and Hash, which will have to
* store the returned tuples.
*
* CP_LABEL_TLIST specifies that the plan node must return columns matching
* any sortgrouprefs specified in its pathtarget, with appropriate
* ressortgroupref labels. This is passed down by parent nodes such as Sort
* and Group, which need these values to be available in their inputs.
*
* CP_IGNORE_TLIST specifies that the caller plans to replace the targetlist,
* and therefore it doesn't matter a bit what target list gets generated.
*/
#define CP_EXACT_TLIST 0x0001 /* Plan must return specified tlist */
#define CP_SMALL_TLIST 0x0002 /* Prefer narrower tlists */
#define CP_LABEL_TLIST 0x0004 /* tlist must contain sortgrouprefs */
#define CP_IGNORE_TLIST 0x0008 /* caller will replace tlist */
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path,
int flags);
static Plan *create_scan_plan(PlannerInfo *root, Path *best_path,
int flags);
static List *build_path_tlist(PlannerInfo *root, Path *path);
static bool use_physical_tlist(PlannerInfo *root, Path *path, int flags);
static List *get_gating_quals(PlannerInfo *root, List *quals);
static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
List *gating_quals);
static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
static bool mark_async_capable_plan(Plan *plan, Path *path);
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path,
int flags);
static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
int flags);
static Result *create_group_result_plan(PlannerInfo *root,
GroupResultPath *best_path);
static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path);
static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path,
int flags);
static Memoize *create_memoize_plan(PlannerInfo *root, MemoizePath *best_path,
int flags);
static Gather *create_gather_plan(PlannerInfo *root, GatherPath *best_path);
static Plan *create_projection_plan(PlannerInfo *root,
ProjectionPath *best_path,
int flags);
static Plan *inject_projection_plan(Plan *subplan, List *tlist,
bool parallel_safe);
static Sort *create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags);
static IncrementalSort *create_incrementalsort_plan(PlannerInfo *root,
IncrementalSortPath *best_path, int flags);
static Group *create_group_plan(PlannerInfo *root, GroupPath *best_path);
static Unique *create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags);
static Agg *create_agg_plan(PlannerInfo *root, AggPath *best_path);
static Plan *create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path);
static Result *create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path);
static WindowAgg *create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path);
static SetOp *create_setop_plan(PlannerInfo *root, SetOpPath *best_path,
int flags);
static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path);
static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
int flags);
static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path);
static Limit *create_limit_plan(PlannerInfo *root, LimitPath *best_path,
int flags);
static SeqScan *create_seqscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static SampleScan *create_samplescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static Scan *create_indexscan_plan(PlannerInfo *root, IndexPath *best_path,
List *tlist, List *scan_clauses, bool indexonly);
static BitmapHeapScan *create_bitmap_scan_plan(PlannerInfo *root,
BitmapHeapPath *best_path,
List *tlist, List *scan_clauses);
static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
List **qual, List **indexqual, List **indexECs);
static void bitmap_subplan_mark_shared(Plan *plan);
static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
List *tlist, List *scan_clauses);
static TidRangeScan *create_tidrangescan_plan(PlannerInfo *root,
TidRangePath *best_path,
List *tlist,
List *scan_clauses);
static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root,
SubqueryScanPath *best_path,
List *tlist, List *scan_clauses);
static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static TableFuncScan *create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root,
Path *best_path, List *tlist, List *scan_clauses);
static Result *create_resultscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
List *tlist, List *scan_clauses);
static CustomScan *create_customscan_plan(PlannerInfo *root,
CustomPath *best_path,
List *tlist, List *scan_clauses);
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path);
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path);
static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path);
static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
static void fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
List **stripped_indexquals_p,
List **fixed_indexquals_p);
static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path);
static Node *fix_indexqual_clause(PlannerInfo *root,
IndexOptInfo *index, int indexcol,
Node *clause, List *indexcolnos);
static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol);
static List *get_switched_clauses(List *clauses, Relids outerrelids);
static List *order_qual_clauses(PlannerInfo *root, List *clauses);
static void copy_generic_path_info(Plan *dest, Path *src);
static void copy_plan_costsize(Plan *dest, Plan *src);
static void label_sort_with_costsize(PlannerInfo *root, Sort *plan,
double limit_tuples);
static void label_incrementalsort_with_costsize(PlannerInfo *root, IncrementalSort *plan,
List *pathkeys, double limit_tuples);
static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
static SampleScan *make_samplescan(List *qptlist, List *qpqual, Index scanrelid,
TableSampleClause *tsc);
static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
Oid indexid, List *indexqual, List *indexqualorig,
List *indexorderby, List *indexorderbyorig,
List *indexorderbyops,
ScanDirection indexscandir);
static IndexOnlyScan *make_indexonlyscan(List *qptlist, List *qpqual,
Index scanrelid, Oid indexid,
List *indexqual, List *recheckqual,
List *indexorderby,
List *indextlist,
ScanDirection indexscandir);
static BitmapIndexScan *make_bitmap_indexscan(Index scanrelid, Oid indexid,
List *indexqual,
List *indexqualorig);
static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
List *qpqual,
Plan *lefttree,
List *bitmapqualorig,
Index scanrelid);
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
List *tidquals);
static TidRangeScan *make_tidrangescan(List *qptlist, List *qpqual,
Index scanrelid, List *tidrangequals);
static SubqueryScan *make_subqueryscan(List *qptlist,
List *qpqual,
Index scanrelid,
Plan *subplan);
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
Index scanrelid, List *functions, bool funcordinality);
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
Index scanrelid, List *values_lists);
static TableFuncScan *make_tablefuncscan(List *qptlist, List *qpqual,
Index scanrelid, TableFunc *tablefunc);
static CteScan *make_ctescan(List *qptlist, List *qpqual,
Index scanrelid, int ctePlanId, int cteParam);
static NamedTuplestoreScan *make_namedtuplestorescan(List *qptlist, List *qpqual,
Index scanrelid, char *enrname);
static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
Index scanrelid, int wtParam);
static RecursiveUnion *make_recursive_union(List *tlist,
Plan *lefttree,
Plan *righttree,
int wtParam,
List *distinctList,
Cardinality numGroups);
static BitmapAnd *make_bitmap_and(List *bitmapplans);
static BitmapOr *make_bitmap_or(List *bitmapplans);
static NestLoop *make_nestloop(List *tlist,
List *joinclauses, List *otherclauses, List *nestParams,
Plan *lefttree, Plan *righttree,
JoinType jointype, bool inner_unique);
static HashJoin *make_hashjoin(List *tlist,
List *joinclauses, List *otherclauses,
List *hashclauses,
List *hashoperators, List *hashcollations,
List *hashkeys,
Plan *lefttree, Plan *righttree,
JoinType jointype, bool inner_unique);
static Hash *make_hash(Plan *lefttree,
List *hashkeys,
Oid skewTable,
AttrNumber skewColumn,
bool skewInherit);
static MergeJoin *make_mergejoin(List *tlist,
List *joinclauses, List *otherclauses,
List *mergeclauses,
Oid *mergefamilies,
Oid *mergecollations,
bool *mergereversals,
bool *mergenullsfirst,
Plan *lefttree, Plan *righttree,
JoinType jointype, bool inner_unique,
bool skip_mark_restore);
static Sort *make_sort(Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators,
Oid *collations, bool *nullsFirst);
static IncrementalSort *make_incrementalsort(Plan *lefttree,
int numCols, int nPresortedCols,
AttrNumber *sortColIdx, Oid *sortOperators,
Oid *collations, bool *nullsFirst);
static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Relids relids,
const AttrNumber *reqColIdx,
bool adjust_tlist_in_place,
int *p_numsortkeys,
AttrNumber **p_sortColIdx,
Oid **p_sortOperators,
Oid **p_collations,
bool **p_nullsFirst);
static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Relids relids);
static IncrementalSort *make_incrementalsort_from_pathkeys(Plan *lefttree,
List *pathkeys, Relids relids, int nPresortedCols);
static Sort *make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree);
static Material *make_material(Plan *lefttree);
static Memoize *make_memoize(Plan *lefttree, Oid *hashoperators,
Oid *collations, List *param_exprs,
bool singlerow, bool binary_mode,
uint32 est_entries, Bitmapset *keyparamids,
Cardinality est_calls,
Cardinality est_unique_keys,
double est_hit_ratio);
static WindowAgg *make_windowagg(List *tlist, WindowClause *wc,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators, Oid *partCollations,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, Oid *ordCollations,
List *runCondition, List *qual, bool topWindow,
Plan *lefttree);
static Group *make_group(List *tlist, List *qual, int numGroupCols,
AttrNumber *grpColIdx, Oid *grpOperators, Oid *grpCollations,
Plan *lefttree);
static Unique *make_unique_from_pathkeys(Plan *lefttree,
List *pathkeys, int numCols,
Relids relids);
static Gather *make_gather(List *qptlist, List *qpqual,
int nworkers, int rescan_param, bool single_copy, Plan *subplan);
static SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy,
List *tlist, Plan *lefttree, Plan *righttree,
List *groupList, Cardinality numGroups);
static LockRows *make_lockrows(Plan *lefttree, List *rowMarks, int epqParam);
static Result *make_gating_result(List *tlist, Node *resconstantqual,
Plan *subplan);
static Result *make_one_row_result(List *tlist, Node *resconstantqual,
RelOptInfo *rel);
static ProjectSet *make_project_set(List *tlist, Plan *subplan);
static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
CmdType operation, bool canSetTag,
Index nominalRelation, Index rootRelation,
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
List *mergeActionLists, List *mergeJoinConditions,
ForPortionOfExpr *forPortionOf, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
GatherMergePath *best_path);
/*
* create_plan
* Creates the access plan for a query by recursively processing the
* desired tree of pathnodes, starting at the node 'best_path'. For
* every pathnode found, we create a corresponding plan node containing
* appropriate id, target list, and qualification information.
*
* The tlists and quals in the plan tree are still in planner format,
* ie, Vars still correspond to the parser's numbering. This will be
* fixed later by setrefs.c.
*
* best_path is the best access path
*
* Returns a Plan tree.
*/
Plan *
create_plan(PlannerInfo *root, Path *best_path)
{
Plan *plan;
/* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL);
/* Initialize this module's workspace in PlannerInfo */
root->curOuterRels = NULL;
root->curOuterParams = NIL;
/* Recursively process the path tree, demanding the correct tlist result */
plan = create_plan_recurse(root, best_path, CP_EXACT_TLIST);
/*
* Make sure the topmost plan node's targetlist exposes the original
* column names and other decorative info. Targetlists generated within
* the planner don't bother with that stuff, but we must have it on the
* top-level tlist seen at execution time. However, ModifyTable plan
* nodes don't have a tlist matching the querytree targetlist.
*/
if (!IsA(plan, ModifyTable))
apply_tlist_labeling(plan->targetlist, root->processed_tlist);
/*
* Attach any initPlans created in this query level to the topmost plan
* node. (In principle the initplans could go in any plan node at or
* above where they're referenced, but there seems no reason to put them
* any lower than the topmost node for the query level. Also, see
* comments for SS_finalize_plan before you try to change this.)
*/
SS_attach_initplans(root, plan);
/* Check we successfully assigned all NestLoopParams to plan nodes */
if (root->curOuterParams != NIL)
elog(ERROR, "failed to assign all NestLoopParams to plan nodes");
/*
* Reset plan_params to ensure param IDs used for nestloop params are not
* re-used later
*/
root->plan_params = NIL;
return plan;
}
/*
* create_plan_recurse
* Recursive guts of create_plan().
*/
static Plan *
create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
{
Plan *plan;
/* Guard against stack overflow due to overly complex plans */
check_stack_depth();
switch (best_path->pathtype)
{
case T_SeqScan:
case T_SampleScan:
case T_IndexScan:
case T_IndexOnlyScan:
case T_BitmapHeapScan:
case T_TidScan:
case T_TidRangeScan:
case T_SubqueryScan:
case T_FunctionScan:
case T_TableFuncScan:
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
case T_NamedTuplestoreScan:
case T_ForeignScan:
case T_CustomScan:
plan = create_scan_plan(root, best_path, flags);
break;
case T_HashJoin:
case T_MergeJoin:
case T_NestLoop:
plan = create_join_plan(root,
(JoinPath *) best_path);
break;
case T_Append:
plan = create_append_plan(root,
(AppendPath *) best_path,
flags);
break;
case T_MergeAppend:
plan = create_merge_append_plan(root,
(MergeAppendPath *) best_path,
flags);
break;
case T_Result:
if (IsA(best_path, ProjectionPath))
{
plan = create_projection_plan(root,
(ProjectionPath *) best_path,
flags);
}
else if (IsA(best_path, MinMaxAggPath))
{
plan = (Plan *) create_minmaxagg_plan(root,
(MinMaxAggPath *) best_path);
}
else if (IsA(best_path, GroupResultPath))
{
plan = (Plan *) create_group_result_plan(root,
(GroupResultPath *) best_path);
}
else
{
/* Simple RTE_RESULT base relation */
Assert(IsA(best_path, Path));
plan = create_scan_plan(root, best_path, flags);
}
break;
case T_ProjectSet:
plan = (Plan *) create_project_set_plan(root,
(ProjectSetPath *) best_path);
break;
case T_Material:
plan = (Plan *) create_material_plan(root,
(MaterialPath *) best_path,
flags);
break;
case T_Memoize:
plan = (Plan *) create_memoize_plan(root,
(MemoizePath *) best_path,
flags);
break;
case T_Unique:
plan = (Plan *) create_unique_plan(root,
(UniquePath *) best_path,
flags);
break;
case T_Gather:
plan = (Plan *) create_gather_plan(root,
(GatherPath *) best_path);
break;
case T_Sort:
plan = (Plan *) create_sort_plan(root,
(SortPath *) best_path,
flags);
break;
case T_IncrementalSort:
plan = (Plan *) create_incrementalsort_plan(root,
(IncrementalSortPath *) best_path,
flags);
break;
case T_Group:
plan = (Plan *) create_group_plan(root,
(GroupPath *) best_path);
break;
case T_Agg:
if (IsA(best_path, GroupingSetsPath))
plan = create_groupingsets_plan(root,
(GroupingSetsPath *) best_path);
else
{
Assert(IsA(best_path, AggPath));
plan = (Plan *) create_agg_plan(root,
(AggPath *) best_path);
}
break;
case T_WindowAgg:
plan = (Plan *) create_windowagg_plan(root,
(WindowAggPath *) best_path);
break;
case T_SetOp:
plan = (Plan *) create_setop_plan(root,
(SetOpPath *) best_path,
flags);
break;
case T_RecursiveUnion:
plan = (Plan *) create_recursiveunion_plan(root,
(RecursiveUnionPath *) best_path);
break;
case T_LockRows:
plan = (Plan *) create_lockrows_plan(root,
(LockRowsPath *) best_path,
flags);
break;
case T_ModifyTable:
plan = (Plan *) create_modifytable_plan(root,
(ModifyTablePath *) best_path);
break;
case T_Limit:
plan = (Plan *) create_limit_plan(root,
(LimitPath *) best_path,
flags);
break;
case T_GatherMerge:
plan = (Plan *) create_gather_merge_plan(root,
(GatherMergePath *) best_path);
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
plan = NULL; /* keep compiler quiet */
break;
}
return plan;
}
/*
* create_scan_plan
* Create a scan plan for the parent relation of 'best_path'.
*/
static Plan *
create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
{
RelOptInfo *rel = best_path->parent;
List *scan_clauses;
List *gating_clauses;
List *tlist;
Plan *plan;
/*
* Extract the relevant restriction clauses from the parent relation. The
* executor must apply all these restrictions during the scan, except for
* pseudoconstants which we'll take care of below.
*
* If this is a plain indexscan or index-only scan, we need not consider
* restriction clauses that are implied by the index's predicate, so use
* indrestrictinfo not baserestrictinfo. Note that we can't do that for
* bitmap indexscans, since there's not necessarily a single index
* involved; but it doesn't matter since create_bitmap_scan_plan() will be
* able to get rid of such clauses anyway via predicate proof.
*/
switch (best_path->pathtype)
{
case T_IndexScan:
case T_IndexOnlyScan:
scan_clauses = castNode(IndexPath, best_path)->indexinfo->indrestrictinfo;
break;
default:
scan_clauses = rel->baserestrictinfo;
break;
}
/*
* If this is a parameterized scan, we also need to enforce all the join
* clauses available from the outer relation(s).
*
* For paranoia's sake, don't modify the stored baserestrictinfo list.
*/
if (best_path->param_info)
scan_clauses = list_concat_copy(scan_clauses,
best_path->param_info->ppi_clauses);
/*
* Detect whether we have any pseudoconstant quals to deal with. Then, if
* we'll need a gating Result node, it will be able to project, so there
* are no requirements on the child's tlist.
*
* If this replaces a join, it must be a foreign scan or a custom scan,
* and the FDW or the custom scan provider would have stored in the best
* path the list of RestrictInfo nodes to apply to the join; check against
* that list in that case.
*/
if (IS_JOIN_REL(rel))
{
List *join_clauses;
Assert(best_path->pathtype == T_ForeignScan ||
best_path->pathtype == T_CustomScan);
if (best_path->pathtype == T_ForeignScan)
join_clauses = ((ForeignPath *) best_path)->fdw_restrictinfo;
else
join_clauses = ((CustomPath *) best_path)->custom_restrictinfo;
gating_clauses = get_gating_quals(root, join_clauses);
}
else
gating_clauses = get_gating_quals(root, scan_clauses);
if (gating_clauses)
flags = 0;
/*
* For table scans, rather than using the relation targetlist (which is
* only those Vars actually needed by the query), we prefer to generate a
* tlist containing all Vars in order. This will allow the executor to
* optimize away projection of the table tuples, if possible.
*
* But if the caller is going to ignore our tlist anyway, then don't
* bother generating one at all. We use an exact equality test here, so
* that this only applies when CP_IGNORE_TLIST is the only flag set.
*/
if (flags == CP_IGNORE_TLIST)
{
tlist = NULL;
}
else if (use_physical_tlist(root, best_path, flags))
{
if (best_path->pathtype == T_IndexOnlyScan)
{
/* For index-only scan, the preferred tlist is the index's */
tlist = copyObject(((IndexPath *) best_path)->indexinfo->indextlist);
/*
* Transfer sortgroupref data to the replacement tlist, if
* requested (use_physical_tlist checked that this will work).
*/
if (flags & CP_LABEL_TLIST)
apply_pathtarget_labeling_to_tlist(tlist, best_path->pathtarget);
}
else
{
tlist = build_physical_tlist(root, rel);
if (tlist == NIL)
{
/* Failed because of dropped cols, so use regular method */
tlist = build_path_tlist(root, best_path);
}
else
{
/* As above, transfer sortgroupref data to replacement tlist */
if (flags & CP_LABEL_TLIST)
apply_pathtarget_labeling_to_tlist(tlist, best_path->pathtarget);
}
}
}
else
{
tlist = build_path_tlist(root, best_path);
}
switch (best_path->pathtype)
{
case T_SeqScan:
plan = (Plan *) create_seqscan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_SampleScan:
plan = (Plan *) create_samplescan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_IndexScan:
plan = (Plan *) create_indexscan_plan(root,
(IndexPath *) best_path,
tlist,
scan_clauses,
false);
break;
case T_IndexOnlyScan:
plan = (Plan *) create_indexscan_plan(root,
(IndexPath *) best_path,
tlist,
scan_clauses,
true);
break;
case T_BitmapHeapScan:
plan = (Plan *) create_bitmap_scan_plan(root,
(BitmapHeapPath *) best_path,
tlist,
scan_clauses);
break;
case T_TidScan:
plan = (Plan *) create_tidscan_plan(root,
(TidPath *) best_path,
tlist,
scan_clauses);
break;
case T_TidRangeScan:
plan = (Plan *) create_tidrangescan_plan(root,
(TidRangePath *) best_path,
tlist,
scan_clauses);
break;
case T_SubqueryScan:
plan = (Plan *) create_subqueryscan_plan(root,
(SubqueryScanPath *) best_path,
tlist,
scan_clauses);
break;
case T_FunctionScan:
plan = (Plan *) create_functionscan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_TableFuncScan:
plan = (Plan *) create_tablefuncscan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_ValuesScan:
plan = (Plan *) create_valuesscan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_CteScan:
plan = (Plan *) create_ctescan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_NamedTuplestoreScan:
plan = (Plan *) create_namedtuplestorescan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_Result:
plan = (Plan *) create_resultscan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_WorkTableScan:
plan = (Plan *) create_worktablescan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_ForeignScan:
plan = (Plan *) create_foreignscan_plan(root,
(ForeignPath *) best_path,
tlist,
scan_clauses);
break;
case T_CustomScan:
plan = (Plan *) create_customscan_plan(root,
(CustomPath *) best_path,
tlist,
scan_clauses);
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
plan = NULL; /* keep compiler quiet */
break;
}
/*
* If there are any pseudoconstant clauses attached to this node, insert a
* gating Result node that evaluates the pseudoconstants as one-time
* quals.
*/
if (gating_clauses)
plan = create_gating_plan(root, best_path, plan, gating_clauses);
return plan;
}
/*
* Build a target list (ie, a list of TargetEntry) for the Path's output.
*
* This is almost just make_tlist_from_pathtarget(), but we also have to
* deal with replacing nestloop params.
*/
static List *
build_path_tlist(PlannerInfo *root, Path *path)
{
List *tlist = NIL;
Index *sortgrouprefs = path->pathtarget->sortgrouprefs;
int resno = 1;
ListCell *v;
foreach(v, path->pathtarget->exprs)
{
Node *node = (Node *) lfirst(v);
TargetEntry *tle;
/*
* If it's a parameterized path, there might be lateral references in
* the tlist, which need to be replaced with Params. There's no need
* to remake the TargetEntry nodes, so apply this to each list item
* separately.
*/
if (path->param_info)
node = replace_nestloop_params(root, node);
tle = makeTargetEntry((Expr *) node,
resno,
NULL,
false);
if (sortgrouprefs)
tle->ressortgroupref = sortgrouprefs[resno - 1];
tlist = lappend(tlist, tle);
resno++;
}
return tlist;
}
/*
* use_physical_tlist
* Decide whether to use a tlist matching relation structure,
* rather than only those Vars actually referenced.
*/
static bool
use_physical_tlist(PlannerInfo *root, Path *path, int flags)
{
RelOptInfo *rel = path->parent;
int i;
ListCell *lc;
/*
* Forget it if either exact tlist or small tlist is demanded.
*/
if (flags & (CP_EXACT_TLIST | CP_SMALL_TLIST))
return false;
/*
* We can do this for real relation scans, subquery scans, function scans,
* tablefunc scans, values scans, and CTE scans (but not for, eg, joins).
*/
if (rel->rtekind != RTE_RELATION &&
rel->rtekind != RTE_SUBQUERY &&
rel->rtekind != RTE_FUNCTION &&
rel->rtekind != RTE_TABLEFUNC &&
rel->rtekind != RTE_VALUES &&
rel->rtekind != RTE_CTE)
return false;
/*
* Can't do it with inheritance cases either (mainly because Append
* doesn't project; this test may be unnecessary now that
* create_append_plan instructs its children to return an exact tlist).
*/
if (rel->reloptkind != RELOPT_BASEREL)
return false;
/*
* Also, don't do it to a CustomPath; the premise that we're extracting
* columns from a simple physical tuple is unlikely to hold for those.
* (When it does make sense, the custom path creator can set up the path's
* pathtarget that way.)
*/
if (IsA(path, CustomPath))
return false;
/*
* If a bitmap scan's tlist is empty, keep it as-is. This may allow the
* executor to skip heap page fetches, and in any case, the benefit of
* using a physical tlist instead would be minimal.
*/
if (IsA(path, BitmapHeapPath) &&
path->pathtarget->exprs == NIL)
return false;
/*
* Can't do it if any system columns or whole-row Vars are requested.
* (This could possibly be fixed but would take some fragile assumptions
* in setrefs.c, I think.)
*/
for (i = rel->min_attr; i <= 0; i++)
{
if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
return false;
}
/*
* Can't do it if the rel is required to emit any placeholder expressions,
* either.
*/
foreach(lc, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
if (bms_nonempty_difference(phinfo->ph_needed, rel->relids) &&
bms_is_subset(phinfo->ph_eval_at, rel->relids))
return false;
}
/*
* For an index-only scan, the "physical tlist" is the index's indextlist.
* We can only return that without a projection if all the index's columns
* are returnable.
*/
if (path->pathtype == T_IndexOnlyScan)
{
IndexOptInfo *indexinfo = ((IndexPath *) path)->indexinfo;
for (i = 0; i < indexinfo->ncolumns; i++)
{
if (!indexinfo->canreturn[i])
return false;
}
}
/*
* Also, can't do it if CP_LABEL_TLIST is specified and path is requested
* to emit any sort/group columns that are not simple Vars. (If they are
* simple Vars, they should appear in the physical tlist, and
* apply_pathtarget_labeling_to_tlist will take care of getting them
* labeled again.) We also have to check that no two sort/group columns
* are the same Var, else that element of the physical tlist would need
* conflicting ressortgroupref labels.
*/
if ((flags & CP_LABEL_TLIST) && path->pathtarget->sortgrouprefs)
{
Bitmapset *sortgroupatts = NULL;
i = 0;
foreach(lc, path->pathtarget->exprs)
{
Expr *expr = (Expr *) lfirst(lc);
if (path->pathtarget->sortgrouprefs[i])
{
if (expr && IsA(expr, Var))
{
int attno = ((Var *) expr)->varattno;
attno -= FirstLowInvalidHeapAttributeNumber;
if (bms_is_member(attno, sortgroupatts))
return false;
sortgroupatts = bms_add_member(sortgroupatts, attno);
}
else
return false;
}
i++;
}
}
return true;
}
/*
* get_gating_quals
* See if there are pseudoconstant quals in a node's quals list
*
* If the node's quals list includes any pseudoconstant quals,
* return just those quals.
*/
static List *
get_gating_quals(PlannerInfo *root, List *quals)
{
/* No need to look if we know there are no pseudoconstants */
if (!root->hasPseudoConstantQuals)
return NIL;
/* Sort into desirable execution order while still in RestrictInfo form */
quals = order_qual_clauses(root, quals);
/* Pull out any pseudoconstant quals from the RestrictInfo list */
return extract_actual_clauses(quals, true);
}
/*
* create_gating_plan
* Deal with pseudoconstant qual clauses
*
* Add a gating Result node atop the already-built plan.
*/
static Plan *
create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
List *gating_quals)
{
Result *gplan;
Assert(gating_quals);
/*
* Since we need a Result node anyway, always return the path's requested
* tlist; that's never a wrong choice, even if the parent node didn't ask
* for CP_EXACT_TLIST.
*/
gplan = make_gating_result(build_path_tlist(root, path),
(Node *) gating_quals, plan);
/*
* We might have had a trivial Result plan already. Stacking one Result
* atop another is silly, so if that applies, just discard the input plan.
* (We're assuming its targetlist is uninteresting; it should be either
* the same as the result of build_path_tlist, or a simplified version.
* However, we preserve the set of relids that it purports to scan and
* attribute that to our replacement Result instead, and likewise for the
* result_type.)
*/
if (IsA(plan, Result))
{
Result *rplan = (Result *) plan;
gplan->plan.lefttree = NULL;
gplan->relids = rplan->relids;
gplan->result_type = rplan->result_type;
}
/*
* Notice that we don't change cost or size estimates when doing gating.
* The costs of qual eval were already included in the subplan's cost.
* Leaving the size alone amounts to assuming that the gating qual will
* succeed, which is the conservative estimate for planning upper queries.
* We certainly don't want to assume the output size is zero (unless the
* gating qual is actually constant FALSE, and that case is dealt with in
* clausesel.c). Interpolating between the two cases is silly, because it
* doesn't reflect what will really happen at runtime, and besides which
* in most cases we have only a very bad idea of the probability of the
* gating qual being true.
*/
copy_plan_costsize(&gplan->plan, plan);
/* Gating quals could be unsafe, so better use the Path's safety flag */
gplan->plan.parallel_safe = path->parallel_safe;
return &gplan->plan;
}
/*
* create_join_plan
* Create a join plan for 'best_path' and (recursively) plans for its
* inner and outer paths.
*/
static Plan *
create_join_plan(PlannerInfo *root, JoinPath *best_path)
{
Plan *plan;
List *gating_clauses;
switch (best_path->path.pathtype)
{
case T_MergeJoin:
plan = (Plan *) create_mergejoin_plan(root,
(MergePath *) best_path);
break;
case T_HashJoin:
plan = (Plan *) create_hashjoin_plan(root,
(HashPath *) best_path);
break;
case T_NestLoop:
plan = (Plan *) create_nestloop_plan(root,
(NestPath *) best_path);
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->path.pathtype);
plan = NULL; /* keep compiler quiet */
break;
}
/*
* If there are any pseudoconstant clauses attached to this node, insert a
* gating Result node that evaluates the pseudoconstants as one-time
* quals.
*/
gating_clauses = get_gating_quals(root, best_path->joinrestrictinfo);
if (gating_clauses)
plan = create_gating_plan(root, (Path *) best_path, plan,
gating_clauses);
#ifdef NOT_USED
/*
* * Expensive function pullups may have pulled local predicates * into
* this path node. Put them in the qpqual of the plan node. * JMH,
* 6/15/92
*/
if (get_loc_restrictinfo(best_path) != NIL)
set_qpqual((Plan) plan,
list_concat(get_qpqual((Plan) plan),
get_actual_clauses(get_loc_restrictinfo(best_path))));
#endif
return plan;
}
/*
* mark_async_capable_plan
* Check whether the Plan node created from a Path node is async-capable,
* and if so, mark the Plan node as such and return true, otherwise
* return false.
*/
static bool
mark_async_capable_plan(Plan *plan, Path *path)
{
switch (nodeTag(path))
{
case T_SubqueryScanPath:
{
SubqueryScan *scan_plan = (SubqueryScan *) plan;
/*
* If the generated plan node includes a gating Result node,
* we can't execute it asynchronously.
*/
if (IsA(plan, Result))
return false;
/*
* If a SubqueryScan node atop of an async-capable plan node
* is deletable, consider it as async-capable.
*/
if (trivial_subqueryscan(scan_plan) &&
mark_async_capable_plan(scan_plan->subplan,
((SubqueryScanPath *) path)->subpath))
break;
return false;
}
case T_ForeignPath:
{
FdwRoutine *fdwroutine = path->parent->fdwroutine;
/*
* If the generated plan node includes a gating Result node,
* we can't execute it asynchronously.
*/
if (IsA(plan, Result))
return false;
Assert(fdwroutine != NULL);
if (fdwroutine->IsForeignPathAsyncCapable != NULL &&
fdwroutine->IsForeignPathAsyncCapable((ForeignPath *) path))
break;
return false;
}
case T_ProjectionPath:
/*
* If the generated plan node includes a Result node for the
* projection, we can't execute it asynchronously.
*/
if (IsA(plan, Result))
return false;
/*
* create_projection_plan() would have pulled up the subplan, so
* check the capability using the subpath.
*/
if (mark_async_capable_plan(plan,
((ProjectionPath *) path)->subpath))
return true;
return false;
default:
return false;
}
plan->async_capable = true;
return true;
}
/*
* create_append_plan
* Create an Append plan for 'best_path' and (recursively) plans
* for its subpaths.
*
* Returns a Plan node.
*/
static Plan *
create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
{
Append *plan;
List *tlist = build_path_tlist(root, &best_path->path);
int orig_tlist_length = list_length(tlist);
bool tlist_was_changed = false;
List *pathkeys = best_path->path.pathkeys;
List *subplans = NIL;
ListCell *subpaths;
int nasyncplans = 0;
RelOptInfo *rel = best_path->path.parent;
int nodenumsortkeys = 0;
AttrNumber *nodeSortColIdx = NULL;
Oid *nodeSortOperators = NULL;
Oid *nodeCollations = NULL;
bool *nodeNullsFirst = NULL;
bool consider_async = false;
/*
* The subpaths list could be empty, if every child was proven empty by
* constraint exclusion. In that case generate a dummy plan that returns
* no rows.
*
* Note that an AppendPath with no members is also generated in certain
* cases where there was no appending construct at all, but we know the
* relation is empty (see set_dummy_rel_pathlist and mark_dummy_rel).
*/
if (best_path->subpaths == NIL)
{
/* Generate a Result plan with constant-FALSE gating qual */
Plan *plan;
plan = (Plan *) make_one_row_result(tlist,
(Node *) list_make1(makeBoolConst(false,
false)),
best_path->path.parent);
copy_generic_path_info(plan, (Path *) best_path);
return plan;
}
/*
* Otherwise build an Append plan. Note that if there's just one child,
* the Append is pretty useless; but we wait till setrefs.c to get rid of
* it. Doing so here doesn't work because the varno of the child scan
* plan won't match the parent-rel Vars it'll be asked to emit.
*
* We don't have the actual creation of the Append node split out into a
* separate make_xxx function. This is because we want to run
* prepare_sort_from_pathkeys on it before we do so on the individual
* child plans, to make cross-checking the sort info easier.
*/
plan = makeNode(Append);
plan->plan.targetlist = tlist;
plan->plan.qual = NIL;
plan->plan.lefttree = NULL;
plan->plan.righttree = NULL;
plan->apprelids = rel->relids;
plan->child_append_relid_sets = best_path->child_append_relid_sets;
if (pathkeys != NIL)
{
/*
* Compute sort column info, and adjust the Append's tlist as needed.
* Because we pass adjust_tlist_in_place = true, we may ignore the
* function result; it must be the same plan node. However, we then
* need to detect whether any tlist entries were added.
*/
(void) prepare_sort_from_pathkeys((Plan *) plan, pathkeys,
best_path->path.parent->relids,
NULL,
true,
&nodenumsortkeys,
&nodeSortColIdx,
&nodeSortOperators,
&nodeCollations,
&nodeNullsFirst);
tlist_was_changed = (orig_tlist_length != list_length(plan->plan.targetlist));
}
/* If appropriate, consider async append */
consider_async = (enable_async_append && pathkeys == NIL &&
!best_path->path.parallel_safe &&
list_length(best_path->subpaths) > 1);
/* Build the plan for each child */
foreach(subpaths, best_path->subpaths)
{
Path *subpath = (Path *) lfirst(subpaths);
Plan *subplan;
/* Must insist that all children return the same tlist */
subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
/*
* For ordered Appends, we must insert a Sort node if subplan isn't
* sufficiently ordered.
*/
if (pathkeys != NIL)
{
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
Oid *collations;
bool *nullsFirst;
int presorted_keys;
/*
* Compute sort column info, and adjust subplan's tlist as needed.
* We must apply prepare_sort_from_pathkeys even to subplans that
* don't need an explicit sort, to make sure they are returning
* the same sort key columns the Append expects.
*/
subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
subpath->parent->relids,
nodeSortColIdx,
false,
&numsortkeys,
&sortColIdx,
&sortOperators,
&collations,
&nullsFirst);
/*
* Check that we got the same sort key information. We just
* Assert that the sortops match, since those depend only on the
* pathkeys; but it seems like a good idea to check the sort
* column numbers explicitly, to ensure the tlists match up.
*/
Assert(numsortkeys == nodenumsortkeys);
if (memcmp(sortColIdx, nodeSortColIdx,
numsortkeys * sizeof(AttrNumber)) != 0)
elog(ERROR, "Append child's targetlist doesn't match Append");
Assert(memcmp(sortOperators, nodeSortOperators,
numsortkeys * sizeof(Oid)) == 0);
Assert(memcmp(collations, nodeCollations,
numsortkeys * sizeof(Oid)) == 0);
Assert(memcmp(nullsFirst, nodeNullsFirst,
numsortkeys * sizeof(bool)) == 0);
/* Now, insert a Sort node if subplan isn't sufficiently ordered */
if (!pathkeys_count_contained_in(pathkeys, subpath->pathkeys,
&presorted_keys))
{
Plan *sort_plan;
/*
* We choose to use incremental sort if it is enabled and
* there are presorted keys; otherwise we use full sort.
*/
if (enable_incremental_sort && presorted_keys > 0)
{
sort_plan = (Plan *)
make_incrementalsort(subplan, numsortkeys, presorted_keys,
sortColIdx, sortOperators,
collations, nullsFirst);
label_incrementalsort_with_costsize(root,
(IncrementalSort *) sort_plan,
pathkeys,
best_path->limit_tuples);
}
else
{
sort_plan = (Plan *) make_sort(subplan, numsortkeys,
sortColIdx, sortOperators,
collations, nullsFirst);
label_sort_with_costsize(root, (Sort *) sort_plan,
best_path->limit_tuples);
}
subplan = sort_plan;
}
}
/* If needed, check to see if subplan can be executed asynchronously */
if (consider_async && mark_async_capable_plan(subplan, subpath))
{
Assert(subplan->async_capable);
++nasyncplans;
}
subplans = lappend(subplans, subplan);
}
/* Set below if we find quals that we can use to run-time prune */
plan->part_prune_index = -1;
/*
* If any quals exist, they may be useful to perform further partition
* pruning during execution. Gather information needed by the executor to
* do partition pruning.
*/
if (enable_partition_pruning)
{
List *prunequal;
prunequal = extract_actual_clauses(rel->baserestrictinfo, false);
if (best_path->path.param_info)
{
List *prmquals = best_path->path.param_info->ppi_clauses;
prmquals = extract_actual_clauses(prmquals, false);
prmquals = (List *) replace_nestloop_params(root,
(Node *) prmquals);
prunequal = list_concat(prunequal, prmquals);
}
if (prunequal != NIL)
plan->part_prune_index = make_partition_pruneinfo(root, rel,
best_path->subpaths,
prunequal);
}
plan->appendplans = subplans;
plan->nasyncplans = nasyncplans;
plan->first_partial_plan = best_path->first_partial_path;
copy_generic_path_info(&plan->plan, (Path *) best_path);
/*
* If prepare_sort_from_pathkeys added sort columns, but we were told to
* produce either the exact tlist or a narrow tlist, we should get rid of
* the sort columns again. We must inject a projection node to do so.
*/
if (tlist_was_changed && (flags & (CP_EXACT_TLIST | CP_SMALL_TLIST)))
{
tlist = list_copy_head(plan->plan.targetlist, orig_tlist_length);
return inject_projection_plan((Plan *) plan, tlist,
plan->plan.parallel_safe);
}
else
return (Plan *) plan;
}
/*
* create_merge_append_plan
* Create a MergeAppend plan for 'best_path' and (recursively) plans
* for its subpaths.
*
* Returns a Plan node.
*/
static Plan *
create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
int flags)
{
MergeAppend *node = makeNode(MergeAppend);
Plan *plan = &node->plan;
List *tlist = build_path_tlist(root, &best_path->path);
int orig_tlist_length = list_length(tlist);
bool tlist_was_changed;
List *pathkeys = best_path->path.pathkeys;
List *subplans = NIL;
ListCell *subpaths;
RelOptInfo *rel = best_path->path.parent;
/*
* We don't have the actual creation of the MergeAppend node split out
* into a separate make_xxx function. This is because we want to run
* prepare_sort_from_pathkeys on it before we do so on the individual
* child plans, to make cross-checking the sort info easier.
*/
copy_generic_path_info(plan, (Path *) best_path);
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = NULL;
plan->righttree = NULL;
node->apprelids = rel->relids;
node->child_append_relid_sets = best_path->child_append_relid_sets;
/*
* Compute sort column info, and adjust MergeAppend's tlist as needed.
* Because we pass adjust_tlist_in_place = true, we may ignore the
* function result; it must be the same plan node. However, we then need
* to detect whether any tlist entries were added.
*/
(void) prepare_sort_from_pathkeys(plan, pathkeys,
best_path->path.parent->relids,
NULL,
true,
&node->numCols,
&node->sortColIdx,
&node->sortOperators,
&node->collations,
&node->nullsFirst);
tlist_was_changed = (orig_tlist_length != list_length(plan->targetlist));
/*
* Now prepare the child plans. We must apply prepare_sort_from_pathkeys
* even to subplans that don't need an explicit sort, to make sure they
* are returning the same sort key columns the MergeAppend expects.
*/
foreach(subpaths, best_path->subpaths)
{
Path *subpath = (Path *) lfirst(subpaths);
Plan *subplan;
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
Oid *collations;
bool *nullsFirst;
int presorted_keys;
/* Build the child plan */
/* Must insist that all children return the same tlist */
subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
/* Compute sort column info, and adjust subplan's tlist as needed */
subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
subpath->parent->relids,
node->sortColIdx,
false,
&numsortkeys,
&sortColIdx,
&sortOperators,
&collations,
&nullsFirst);
/*
* Check that we got the same sort key information. We just Assert
* that the sortops match, since those depend only on the pathkeys;
* but it seems like a good idea to check the sort column numbers
* explicitly, to ensure the tlists really do match up.
*/
Assert(numsortkeys == node->numCols);
if (memcmp(sortColIdx, node->sortColIdx,
numsortkeys * sizeof(AttrNumber)) != 0)
elog(ERROR, "MergeAppend child's targetlist doesn't match MergeAppend");
Assert(memcmp(sortOperators, node->sortOperators,
numsortkeys * sizeof(Oid)) == 0);
Assert(memcmp(collations, node->collations,
numsortkeys * sizeof(Oid)) == 0);
Assert(memcmp(nullsFirst, node->nullsFirst,
numsortkeys * sizeof(bool)) == 0);
/* Now, insert a Sort node if subplan isn't sufficiently ordered */
if (!pathkeys_count_contained_in(pathkeys, subpath->pathkeys,
&presorted_keys))
{
Plan *sort_plan;
/*
* We choose to use incremental sort if it is enabled and there
* are presorted keys; otherwise we use full sort.
*/
if (enable_incremental_sort && presorted_keys > 0)
{
sort_plan = (Plan *)
make_incrementalsort(subplan, numsortkeys, presorted_keys,
sortColIdx, sortOperators,
collations, nullsFirst);
label_incrementalsort_with_costsize(root,
(IncrementalSort *) sort_plan,
pathkeys,
best_path->limit_tuples);
}
else
{
sort_plan = (Plan *) make_sort(subplan, numsortkeys,
sortColIdx, sortOperators,
collations, nullsFirst);
label_sort_with_costsize(root, (Sort *) sort_plan,
best_path->limit_tuples);
}
subplan = sort_plan;
}
subplans = lappend(subplans, subplan);
}
/* Set below if we find quals that we can use to run-time prune */
node->part_prune_index = -1;
/*
* If any quals exist, they may be useful to perform further partition
* pruning during execution. Gather information needed by the executor to
* do partition pruning.
*/
if (enable_partition_pruning)
{
List *prunequal;
prunequal = extract_actual_clauses(rel->baserestrictinfo, false);
/* We don't currently generate any parameterized MergeAppend paths */
Assert(best_path->path.param_info == NULL);
if (prunequal != NIL)
node->part_prune_index = make_partition_pruneinfo(root, rel,
best_path->subpaths,
prunequal);
}
node->mergeplans = subplans;
/*
* If prepare_sort_from_pathkeys added sort columns, but we were told to
* produce either the exact tlist or a narrow tlist, we should get rid of
* the sort columns again. We must inject a projection node to do so.
*/
if (tlist_was_changed && (flags & (CP_EXACT_TLIST | CP_SMALL_TLIST)))
{
tlist = list_copy_head(plan->targetlist, orig_tlist_length);
return inject_projection_plan(plan, tlist, plan->parallel_safe);
}
else
return plan;
}
/*
* create_group_result_plan
* Create a Result plan for 'best_path'.
* This is only used for degenerate grouping cases.
*
* Returns a Plan node.
*/
static Result *
create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
{
Result *plan;
List *tlist;
List *quals;
tlist = build_path_tlist(root, &best_path->path);
/* best_path->quals is just bare clauses */
quals = order_qual_clauses(root, best_path->quals);
plan = make_one_row_result(tlist, (Node *) quals, best_path->path.parent);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_project_set_plan
* Create a ProjectSet plan for 'best_path'.
*
* Returns a Plan node.
*/
static ProjectSet *
create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path)
{
ProjectSet *plan;
Plan *subplan;
List *tlist;
/* Since we intend to project, we don't need to constrain child tlist */
subplan = create_plan_recurse(root, best_path->subpath, 0);
tlist = build_path_tlist(root, &best_path->path);
plan = make_project_set(tlist, subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_material_plan
* Create a Material plan for 'best_path' and (recursively) plans
* for its subpaths.
*
* Returns a Plan node.
*/
static Material *
create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags)
{
Material *plan;
Plan *subplan;
/*
* We don't want any excess columns in the materialized tuples, so request
* a smaller tlist. Otherwise, since Material doesn't project, tlist
* requirements pass through.
*/
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
plan = make_material(subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_memoize_plan
* Create a Memoize plan for 'best_path' and (recursively) plans for its
* subpaths.
*
* Returns a Plan node.
*/
static Memoize *
create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
{
Memoize *plan;
Bitmapset *keyparamids;
Plan *subplan;
Oid *operators;
Oid *collations;
List *param_exprs = NIL;
ListCell *lc;
ListCell *lc2;
int nkeys;
int i;
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
param_exprs = (List *) replace_nestloop_params(root, (Node *)
best_path->param_exprs);
nkeys = list_length(param_exprs);
Assert(nkeys > 0);
operators = palloc(nkeys * sizeof(Oid));
collations = palloc(nkeys * sizeof(Oid));
i = 0;
forboth(lc, param_exprs, lc2, best_path->hash_operators)
{
Expr *param_expr = (Expr *) lfirst(lc);
Oid opno = lfirst_oid(lc2);
operators[i] = opno;
collations[i] = exprCollation((Node *) param_expr);
i++;
}
keyparamids = pull_paramids((Expr *) param_exprs);
plan = make_memoize(subplan, operators, collations, param_exprs,
best_path->singlerow, best_path->binary_mode,
best_path->est_entries, keyparamids, best_path->est_calls,
best_path->est_unique_keys, best_path->est_hit_ratio);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_gather_plan
*
* Create a Gather plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Gather *
create_gather_plan(PlannerInfo *root, GatherPath *best_path)
{
Gather *gather_plan;
Plan *subplan;
List *tlist;
/*
* Push projection down to the child node. That way, the projection work
* is parallelized, and there can be no system columns in the result (they
* can't travel through a tuple queue because it uses MinimalTuple
* representation).
*/
subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST);
tlist = build_path_tlist(root, &best_path->path);
gather_plan = make_gather(tlist,
NIL,
best_path->num_workers,
assign_special_exec_param(root),
best_path->single_copy,
subplan);
copy_generic_path_info(&gather_plan->plan, &best_path->path);
/* use parallel mode for parallel plans. */
root->glob->parallelModeNeeded = true;
return gather_plan;
}
/*
* create_gather_merge_plan
*
* Create a Gather Merge plan for 'best_path' and (recursively)
* plans for its subpaths.
*/
static GatherMerge *
create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
{
GatherMerge *gm_plan;
Plan *subplan;
List *pathkeys = best_path->path.pathkeys;
List *tlist = build_path_tlist(root, &best_path->path);
/* As with Gather, project away columns in the workers. */
subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST);
/* Create a shell for a GatherMerge plan. */
gm_plan = makeNode(GatherMerge);
gm_plan->plan.targetlist = tlist;
gm_plan->num_workers = best_path->num_workers;
copy_generic_path_info(&gm_plan->plan, &best_path->path);
/* Assign the rescan Param. */
gm_plan->rescan_param = assign_special_exec_param(root);
/* Gather Merge is pointless with no pathkeys; use Gather instead. */
Assert(pathkeys != NIL);
/* Compute sort column info, and adjust subplan's tlist as needed */
subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
best_path->subpath->parent->relids,
gm_plan->sortColIdx,
false,
&gm_plan->numCols,
&gm_plan->sortColIdx,
&gm_plan->sortOperators,
&gm_plan->collations,
&gm_plan->nullsFirst);
/*
* All gather merge paths should have already guaranteed the necessary
* sort order. See create_gather_merge_path.
*/
Assert(pathkeys_contained_in(pathkeys, best_path->subpath->pathkeys));
/* Now insert the subplan under GatherMerge. */
gm_plan->plan.lefttree = subplan;
/* use parallel mode for parallel plans. */
root->glob->parallelModeNeeded = true;
return gm_plan;
}
/*
* create_projection_plan
*
* Create a plan tree to do a projection step and (recursively) plans
* for its subpaths. We may need a Result node for the projection,
* but sometimes we can just let the subplan do the work.
*/
static Plan *
create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
{
Plan *plan;
Plan *subplan;
List *tlist;
bool needs_result_node = false;
/*
* Convert our subpath to a Plan and determine whether we need a Result
* node.
*
* In most cases where we don't need to project, create_projection_path
* will have set dummypp, but not always. First, some createplan.c
* routines change the tlists of their nodes. (An example is that
* create_merge_append_plan might add resjunk sort columns to a
* MergeAppend.) Second, create_projection_path has no way of knowing
* what path node will be placed on top of the projection path and
* therefore can't predict whether it will require an exact tlist. For
* both of these reasons, we have to recheck here.
*/
if (use_physical_tlist(root, &best_path->path, flags))
{
/*
* Our caller doesn't really care what tlist we return, so we don't
* actually need to project. However, we may still need to ensure
* proper sortgroupref labels, if the caller cares about those.
*/
subplan = create_plan_recurse(root, best_path->subpath, 0);
tlist = subplan->targetlist;
if (flags & CP_LABEL_TLIST)
apply_pathtarget_labeling_to_tlist(tlist,
best_path->path.pathtarget);
}
else if (is_projection_capable_path(best_path->subpath))
{
/*
* Our caller requires that we return the exact tlist, but no separate
* result node is needed because the subpath is projection-capable.
* Tell create_plan_recurse that we're going to ignore the tlist it
* produces.
*/
subplan = create_plan_recurse(root, best_path->subpath,
CP_IGNORE_TLIST);
Assert(is_projection_capable_plan(subplan));
tlist = build_path_tlist(root, &best_path->path);
}
else
{
/*
* It looks like we need a result node, unless by good fortune the
* requested tlist is exactly the one the child wants to produce.
*/
subplan = create_plan_recurse(root, best_path->subpath, 0);
tlist = build_path_tlist(root, &best_path->path);
needs_result_node = !tlist_same_exprs(tlist, subplan->targetlist);
}
/*
* If we make a different decision about whether to include a Result node
* than create_projection_path did, we'll have made slightly wrong cost
* estimates; but label the plan with the cost estimates we actually used,
* not "corrected" ones. (XXX this could be cleaned up if we moved more
* of the sortcolumn setup logic into Path creation, but that would add
* expense to creating Paths we might end up not using.)
*/
if (!needs_result_node)
{
/* Don't need a separate Result, just assign tlist to subplan */
plan = subplan;
plan->targetlist = tlist;
/* Label plan with the estimated costs we actually used */
plan->startup_cost = best_path->path.startup_cost;
plan->total_cost = best_path->path.total_cost;
plan->plan_rows = best_path->path.rows;
plan->plan_width = best_path->path.pathtarget->width;
plan->parallel_safe = best_path->path.parallel_safe;
/* ... but don't change subplan's parallel_aware flag */
}
else
{
plan = (Plan *) make_gating_result(tlist, NULL, subplan);
copy_generic_path_info(plan, (Path *) best_path);
}
return plan;
}
/*
* inject_projection_plan
* Insert a Result node to do a projection step.
*
* This is used in a few places where we decide on-the-fly that we need a
* projection step as part of the tree generated for some Path node.
* We should try to get rid of this in favor of doing it more honestly.
*
* One reason it's ugly is we have to be told the right parallel_safe marking
* to apply (since the tlist might be unsafe even if the child plan is safe).
*/
static Plan *
inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe)
{
Plan *plan;
plan = (Plan *) make_gating_result(tlist, NULL, subplan);
/*
* In principle, we should charge tlist eval cost plus cpu_per_tuple per
* row for the Result node. But the former has probably been factored in
* already and the latter was not accounted for during Path construction,
* so being formally correct might just make the EXPLAIN output look less
* consistent not more so. Hence, just copy the subplan's cost.
*/
copy_plan_costsize(plan, subplan);
plan->parallel_safe = parallel_safe;
return plan;
}
/*
* change_plan_targetlist
* Externally available wrapper for inject_projection_plan.
*
* This is meant for use by FDW plan-generation functions, which might
* want to adjust the tlist computed by some subplan tree. In general,
* a Result node is needed to compute the new tlist, but we can optimize
* some cases.
*
* In most cases, tlist_parallel_safe can just be passed as the parallel_safe
* flag of the FDW's own Path node.
*/
Plan *
change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe)
{
/*
* If the top plan node can't do projections and its existing target list
* isn't already what we need, we need to add a Result node to help it
* along.
*/
if (!is_projection_capable_plan(subplan) &&
!tlist_same_exprs(tlist, subplan->targetlist))
subplan = inject_projection_plan(subplan, tlist,
subplan->parallel_safe &&
tlist_parallel_safe);
else
{
/* Else we can just replace the plan node's tlist */
subplan->targetlist = tlist;
subplan->parallel_safe &= tlist_parallel_safe;
}
return subplan;
}
/*
* create_sort_plan
*
* Create a Sort plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Sort *
create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
{
Sort *plan;
Plan *subplan;
/*
* We don't want any excess columns in the sorted tuples, so request a
* smaller tlist. Otherwise, since Sort doesn't project, tlist
* requirements pass through.
*/
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
/*
* make_sort_from_pathkeys indirectly calls find_ec_member_matching_expr,
* which will ignore any child EC members that don't belong to the given
* relids. Thus, if this sort path is based on a child relation, we must
* pass its relids.
*/
plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys,
IS_OTHER_REL(best_path->subpath->parent) ?
best_path->path.parent->relids : NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_incrementalsort_plan
*
* Do the same as create_sort_plan, but create IncrementalSort plan.
*/
static IncrementalSort *
create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path,
int flags)
{
IncrementalSort *plan;
Plan *subplan;
/* See comments in create_sort_plan() above */
subplan = create_plan_recurse(root, best_path->spath.subpath,
flags | CP_SMALL_TLIST);
plan = make_incrementalsort_from_pathkeys(subplan,
best_path->spath.path.pathkeys,
IS_OTHER_REL(best_path->spath.subpath->parent) ?
best_path->spath.path.parent->relids : NULL,
best_path->nPresortedCols);
copy_generic_path_info(&plan->sort.plan, (Path *) best_path);
return plan;
}
/*
* create_group_plan
*
* Create a Group plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Group *
create_group_plan(PlannerInfo *root, GroupPath *best_path)
{
Group *plan;
Plan *subplan;
List *tlist;
List *quals;
/*
* Group can project, so no need to be terribly picky about child tlist,
* but we do need grouping columns to be available
*/
subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
tlist = build_path_tlist(root, &best_path->path);
quals = order_qual_clauses(root, best_path->qual);
plan = make_group(tlist,
quals,
list_length(best_path->groupClause),
extract_grouping_cols(best_path->groupClause,
subplan->targetlist),
extract_grouping_ops(best_path->groupClause),
extract_grouping_collations(best_path->groupClause,
subplan->targetlist),
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_unique_plan
*
* Create a Unique plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Unique *
create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
{
Unique *plan;
Plan *subplan;
/*
* Unique doesn't project, so tlist requirements pass through; moreover we
* need grouping columns to be labeled.
*/
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_LABEL_TLIST);
/*
* make_unique_from_pathkeys calls find_ec_member_matching_expr, which
* will ignore any child EC members that don't belong to the given relids.
* Thus, if this unique path is based on a child relation, we must pass
* its relids.
*/
plan = make_unique_from_pathkeys(subplan,
best_path->path.pathkeys,
best_path->numkeys,
IS_OTHER_REL(best_path->path.parent) ?
best_path->path.parent->relids : NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_agg_plan
*
* Create an Agg plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Agg *
create_agg_plan(PlannerInfo *root, AggPath *best_path)
{
Agg *plan;
Plan *subplan;
List *tlist;
List *quals;
/*
* Agg can project, so no need to be terribly picky about child tlist, but
* we do need grouping columns to be available
*/
subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
tlist = build_path_tlist(root, &best_path->path);
quals = order_qual_clauses(root, best_path->qual);
plan = make_agg(tlist, quals,
best_path->aggstrategy,
best_path->aggsplit,
list_length(best_path->groupClause),
extract_grouping_cols(best_path->groupClause,
subplan->targetlist),
extract_grouping_ops(best_path->groupClause),
extract_grouping_collations(best_path->groupClause,
subplan->targetlist),
NIL,
NIL,
best_path->numGroups,
best_path->transitionSpace,
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* Given a groupclause for a collection of grouping sets, produce the
* corresponding groupColIdx.
*
* root->grouping_map maps the tleSortGroupRef to the actual column position in
* the input tuple. So we get the ref from the entries in the groupclause and
* look them up there.
*/
static AttrNumber *
remap_groupColIdx(PlannerInfo *root, List *groupClause)
{
AttrNumber *grouping_map = root->grouping_map;
AttrNumber *new_grpColIdx;
ListCell *lc;
int i;
Assert(grouping_map);
new_grpColIdx = palloc0_array(AttrNumber, list_length(groupClause));
i = 0;
foreach(lc, groupClause)
{
SortGroupClause *clause = lfirst(lc);
new_grpColIdx[i++] = grouping_map[clause->tleSortGroupRef];
}
return new_grpColIdx;
}
/*
* create_groupingsets_plan
* Create a plan for 'best_path' and (recursively) plans
* for its subpaths.
*
* What we emit is an Agg plan with some vestigial Agg and Sort nodes
* hanging off the side. The top Agg implements the last grouping set
* specified in the GroupingSetsPath, and any additional grouping sets
* each give rise to a subsidiary Agg and Sort node in the top Agg's
* "chain" list. These nodes don't participate in the plan directly,
* but they are a convenient way to represent the required data for
* the extra steps.
*
* Returns a Plan node.
*/
static Plan *
create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
{
Agg *plan;
Plan *subplan;
List *rollups = best_path->rollups;
AttrNumber *grouping_map;
int maxref;
List *chain;
ListCell *lc;
/* Shouldn't get here without grouping sets */
Assert(root->parse->groupingSets);
Assert(rollups != NIL);
/*
* Agg can project, so no need to be terribly picky about child tlist, but
* we do need grouping columns to be available
*/
subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
/*
* Compute the mapping from tleSortGroupRef to column index in the child's
* tlist. First, identify max SortGroupRef in groupClause, for array
* sizing.
*/
maxref = 0;
foreach(lc, root->processed_groupClause)
{
SortGroupClause *gc = (SortGroupClause *) lfirst(lc);
if (gc->tleSortGroupRef > maxref)
maxref = gc->tleSortGroupRef;
}
grouping_map = (AttrNumber *) palloc0((maxref + 1) * sizeof(AttrNumber));
/* Now look up the column numbers in the child's tlist */
foreach(lc, root->processed_groupClause)
{
SortGroupClause *gc = (SortGroupClause *) lfirst(lc);
TargetEntry *tle = get_sortgroupclause_tle(gc, subplan->targetlist);
grouping_map[gc->tleSortGroupRef] = tle->resno;
}
/*
* During setrefs.c, we'll need the grouping_map to fix up the cols lists
* in GroupingFunc nodes. Save it for setrefs.c to use.
*/
Assert(root->grouping_map == NULL);
root->grouping_map = grouping_map;
/*
* Generate the side nodes that describe the other sort and group
* operations besides the top one. Note that we don't worry about putting
* accurate cost estimates in the side nodes; only the topmost Agg node's
* costs will be shown by EXPLAIN.
*/
chain = NIL;
if (list_length(rollups) > 1)
{
bool is_first_sort = ((RollupData *) linitial(rollups))->is_hashed;
for_each_from(lc, rollups, 1)
{
RollupData *rollup = lfirst(lc);
AttrNumber *new_grpColIdx;
Plan *sort_plan = NULL;
Plan *agg_plan;
AggStrategy strat;
new_grpColIdx = remap_groupColIdx(root, rollup->groupClause);
if (!rollup->is_hashed && !is_first_sort)
{
sort_plan = (Plan *)
make_sort_from_groupcols(rollup->groupClause,
new_grpColIdx,
subplan);
}
if (!rollup->is_hashed)
is_first_sort = false;
if (rollup->is_hashed)
strat = AGG_HASHED;
else if (linitial(rollup->gsets) == NIL)
strat = AGG_PLAIN;
else
strat = AGG_SORTED;
agg_plan = (Plan *) make_agg(NIL,
NIL,
strat,
AGGSPLIT_SIMPLE,
list_length((List *) linitial(rollup->gsets)),
new_grpColIdx,
extract_grouping_ops(rollup->groupClause),
extract_grouping_collations(rollup->groupClause, subplan->targetlist),
rollup->gsets,
NIL,
rollup->numGroups,
best_path->transitionSpace,
sort_plan);
/*
* Remove stuff we don't need to avoid bloating debug output.
*/
if (sort_plan)
{
sort_plan->targetlist = NIL;
sort_plan->lefttree = NULL;
}
chain = lappend(chain, agg_plan);
}
}
/*
* Now make the real Agg node
*/
{
RollupData *rollup = linitial(rollups);
AttrNumber *top_grpColIdx;
int numGroupCols;
top_grpColIdx = remap_groupColIdx(root, rollup->groupClause);
numGroupCols = list_length((List *) linitial(rollup->gsets));
plan = make_agg(build_path_tlist(root, &best_path->path),
best_path->qual,
best_path->aggstrategy,
AGGSPLIT_SIMPLE,
numGroupCols,
top_grpColIdx,
extract_grouping_ops(rollup->groupClause),
extract_grouping_collations(rollup->groupClause, subplan->targetlist),
rollup->gsets,
chain,
rollup->numGroups,
best_path->transitionSpace,
subplan);
/* Copy cost data from Path to Plan */
copy_generic_path_info(&plan->plan, &best_path->path);
}
return (Plan *) plan;
}
/*
* create_minmaxagg_plan
*
* Create a Result plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Result *
create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
{
Result *plan;
List *tlist;
ListCell *lc;
/* Prepare an InitPlan for each aggregate's subquery. */
foreach(lc, best_path->mmaggregates)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
PlannerInfo *subroot = mminfo->subroot;
Query *subparse = subroot->parse;
Plan *plan;
/*
* Generate the plan for the subquery. We already have a Path, but we
* have to convert it to a Plan and attach a LIMIT node above it.
* Since we are entering a different planner context (subroot),
* recurse to create_plan not create_plan_recurse.
*/
plan = create_plan(subroot, mminfo->path);
plan = (Plan *) make_limit(plan,
subparse->limitOffset,
subparse->limitCount,
subparse->limitOption,
0, NULL, NULL, NULL);
/* Must apply correct cost/width data to Limit node */
plan->disabled_nodes = mminfo->path->disabled_nodes;
plan->startup_cost = mminfo->path->startup_cost;
plan->total_cost = mminfo->pathcost;
plan->plan_rows = 1;
plan->plan_width = mminfo->path->pathtarget->width;
plan->parallel_aware = false;
plan->parallel_safe = mminfo->path->parallel_safe;
/* Convert the plan into an InitPlan in the outer query. */
SS_make_initplan_from_plan(root, subroot, plan, mminfo->param);
}
/* Generate the output plan --- basically just a Result */
tlist = build_path_tlist(root, &best_path->path);
plan = make_one_row_result(tlist, (Node *) best_path->quals,
best_path->path.parent);
plan->result_type = RESULT_TYPE_MINMAX;
copy_generic_path_info(&plan->plan, (Path *) best_path);
/*
* During setrefs.c, we'll need to replace references to the Agg nodes
* with InitPlan output params. (We can't just do that locally in the
* MinMaxAgg node, because path nodes above here may have Agg references
* as well.) Save the mmaggregates list to tell setrefs.c to do that.
*/
Assert(root->minmax_aggs == NIL);
root->minmax_aggs = best_path->mmaggregates;
return plan;
}
/*
* create_windowagg_plan
*
* Create a WindowAgg plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static WindowAgg *
create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
{
WindowAgg *plan;
WindowClause *wc = best_path->winclause;
int numPart = list_length(wc->partitionClause);
int numOrder = list_length(wc->orderClause);
Plan *subplan;
List *tlist;
int partNumCols;
AttrNumber *partColIdx;
Oid *partOperators;
Oid *partCollations;
int ordNumCols;
AttrNumber *ordColIdx;
Oid *ordOperators;
Oid *ordCollations;
ListCell *lc;
/*
* Choice of tlist here is motivated by the fact that WindowAgg will be
* storing the input rows of window frames in a tuplestore; it therefore
* behooves us to request a small tlist to avoid wasting space. We do of
* course need grouping columns to be available.
*/
subplan = create_plan_recurse(root, best_path->subpath,
CP_LABEL_TLIST | CP_SMALL_TLIST);
tlist = build_path_tlist(root, &best_path->path);
/*
* Convert SortGroupClause lists into arrays of attr indexes and equality
* operators, as wanted by executor.
*/
partColIdx = palloc_array(AttrNumber, numPart);
partOperators = palloc_array(Oid, numPart);
partCollations = palloc_array(Oid, numPart);
partNumCols = 0;
foreach(lc, wc->partitionClause)
{
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
TargetEntry *tle = get_sortgroupclause_tle(sgc, subplan->targetlist);
Assert(OidIsValid(sgc->eqop));
partColIdx[partNumCols] = tle->resno;
partOperators[partNumCols] = sgc->eqop;
partCollations[partNumCols] = exprCollation((Node *) tle->expr);
partNumCols++;
}
ordColIdx = palloc_array(AttrNumber, numOrder);
ordOperators = palloc_array(Oid, numOrder);
ordCollations = palloc_array(Oid, numOrder);
ordNumCols = 0;
foreach(lc, wc->orderClause)
{
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
TargetEntry *tle = get_sortgroupclause_tle(sgc, subplan->targetlist);
Assert(OidIsValid(sgc->eqop));
ordColIdx[ordNumCols] = tle->resno;
ordOperators[ordNumCols] = sgc->eqop;
ordCollations[ordNumCols] = exprCollation((Node *) tle->expr);
ordNumCols++;
}
/* And finally we can make the WindowAgg node */
plan = make_windowagg(tlist,
wc,
partNumCols,
partColIdx,
partOperators,
partCollations,
ordNumCols,
ordColIdx,
ordOperators,
ordCollations,
best_path->runCondition,
best_path->qual,
best_path->topwindow,
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_setop_plan
*
* Create a SetOp plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static SetOp *
create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
{
SetOp *plan;
List *tlist = build_path_tlist(root, &best_path->path);
Plan *leftplan;
Plan *rightplan;
/*
* SetOp doesn't project, so tlist requirements pass through; moreover we
* need grouping columns to be labeled.
*/
leftplan = create_plan_recurse(root, best_path->leftpath,
flags | CP_LABEL_TLIST);
rightplan = create_plan_recurse(root, best_path->rightpath,
flags | CP_LABEL_TLIST);
plan = make_setop(best_path->cmd,
best_path->strategy,
tlist,
leftplan,
rightplan,
best_path->groupList,
best_path->numGroups);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_recursiveunion_plan
*
* Create a RecursiveUnion plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static RecursiveUnion *
create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
{
RecursiveUnion *plan;
Plan *leftplan;
Plan *rightplan;
List *tlist;
/* Need both children to produce same tlist, so force it */
leftplan = create_plan_recurse(root, best_path->leftpath, CP_EXACT_TLIST);
rightplan = create_plan_recurse(root, best_path->rightpath, CP_EXACT_TLIST);
tlist = build_path_tlist(root, &best_path->path);
plan = make_recursive_union(tlist,
leftplan,
rightplan,
best_path->wtParam,
best_path->distinctList,
best_path->numGroups);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_lockrows_plan
*
* Create a LockRows plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static LockRows *
create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
int flags)
{
LockRows *plan;
Plan *subplan;
/* LockRows doesn't project, so tlist requirements pass through */
subplan = create_plan_recurse(root, best_path->subpath, flags);
plan = make_lockrows(subplan, best_path->rowMarks, best_path->epqParam);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_modifytable_plan
* Create a ModifyTable plan for 'best_path'.
*
* Returns a Plan node.
*/
static ModifyTable *
create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
{
ModifyTable *plan;
Path *subpath = best_path->subpath;
Plan *subplan;
/* Subplan must produce exactly the specified tlist */
subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
/* Transfer resname/resjunk labeling, too, to keep executor happy */
apply_tlist_labeling(subplan->targetlist, root->processed_tlist);
plan = make_modifytable(root,
subplan,
best_path->operation,
best_path->canSetTag,
best_path->nominalRelation,
best_path->rootRelation,
best_path->resultRelations,
best_path->updateColnosLists,
best_path->withCheckOptionLists,
best_path->returningLists,
best_path->rowMarks,
best_path->onconflict,
best_path->mergeActionLists,
best_path->mergeJoinConditions,
best_path->forPortionOf,
best_path->epqParam);
copy_generic_path_info(&plan->plan, &best_path->path);
return plan;
}
/*
* create_limit_plan
*
* Create a Limit plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Limit *
create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
{
Limit *plan;
Plan *subplan;
int numUniqkeys = 0;
AttrNumber *uniqColIdx = NULL;
Oid *uniqOperators = NULL;
Oid *uniqCollations = NULL;
/* Limit doesn't project, so tlist requirements pass through */
subplan = create_plan_recurse(root, best_path->subpath, flags);
/* Extract information necessary for comparing rows for WITH TIES. */
if (best_path->limitOption == LIMIT_OPTION_WITH_TIES)
{
Query *parse = root->parse;
ListCell *l;
numUniqkeys = list_length(parse->sortClause);
uniqColIdx = (AttrNumber *) palloc(numUniqkeys * sizeof(AttrNumber));
uniqOperators = (Oid *) palloc(numUniqkeys * sizeof(Oid));
uniqCollations = (Oid *) palloc(numUniqkeys * sizeof(Oid));
numUniqkeys = 0;
foreach(l, parse->sortClause)
{
SortGroupClause *sortcl = (SortGroupClause *) lfirst(l);
TargetEntry *tle = get_sortgroupclause_tle(sortcl, parse->targetList);
uniqColIdx[numUniqkeys] = tle->resno;
uniqOperators[numUniqkeys] = sortcl->eqop;
uniqCollations[numUniqkeys] = exprCollation((Node *) tle->expr);
numUniqkeys++;
}
}
plan = make_limit(subplan,
best_path->limitOffset,
best_path->limitCount,
best_path->limitOption,
numUniqkeys, uniqColIdx, uniqOperators, uniqCollations);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*****************************************************************************
*
* BASE-RELATION SCAN METHODS
*
*****************************************************************************/
/*
* create_seqscan_plan
* Returns a seqscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static SeqScan *
create_seqscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
SeqScan *scan_plan;
Index scan_relid = best_path->parent->relid;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_RELATION);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
}
scan_plan = make_seqscan(tlist,
scan_clauses,
scan_relid);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_samplescan_plan
* Returns a samplescan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static SampleScan *
create_samplescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
SampleScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
TableSampleClause *tsc;
/* it should be a base rel with a tablesample clause... */
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_RELATION);
tsc = rte->tablesample;
Assert(tsc != NULL);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
tsc = (TableSampleClause *)
replace_nestloop_params(root, (Node *) tsc);
}
scan_plan = make_samplescan(tlist,
scan_clauses,
scan_relid,
tsc);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_indexscan_plan
* Returns an indexscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*
* We use this for both plain IndexScans and IndexOnlyScans, because the
* qual preprocessing work is the same for both. Note that the caller tells
* us which to build --- we don't look at best_path->path.pathtype, because
* create_bitmap_subplan needs to be able to override the prior decision.
*/
static Scan *
create_indexscan_plan(PlannerInfo *root,
IndexPath *best_path,
List *tlist,
List *scan_clauses,
bool indexonly)
{
Scan *scan_plan;
List *indexclauses = best_path->indexclauses;
List *indexorderbys = best_path->indexorderbys;
Index baserelid = best_path->path.parent->relid;
IndexOptInfo *indexinfo = best_path->indexinfo;
Oid indexoid = indexinfo->indexoid;
List *qpqual;
List *stripped_indexquals;
List *fixed_indexquals;
List *fixed_indexorderbys;
List *indexorderbyops = NIL;
ListCell *l;
/* it should be a base rel... */
Assert(baserelid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
/* check the scan direction is valid */
Assert(best_path->indexscandir == ForwardScanDirection ||
best_path->indexscandir == BackwardScanDirection);
/*
* Extract the index qual expressions (stripped of RestrictInfos) from the
* IndexClauses list, and prepare a copy with index Vars substituted for
* table Vars. (This step also does replace_nestloop_params on the
* fixed_indexquals.)
*/
fix_indexqual_references(root, best_path,
&stripped_indexquals,
&fixed_indexquals);
/*
* Likewise fix up index attr references in the ORDER BY expressions.
*/
fixed_indexorderbys = fix_indexorderby_references(root, best_path);
/*
* The qpqual list must contain all restrictions not automatically handled
* by the index, other than pseudoconstant clauses which will be handled
* by a separate gating plan node. All the predicates in the indexquals
* will be checked (either by the index itself, or by nodeIndexscan.c),
* but if there are any "special" operators involved then they must be
* included in qpqual. The upshot is that qpqual must contain
* scan_clauses minus whatever appears in indexquals.
*
* is_redundant_with_indexclauses() detects cases where a scan clause is
* present in the indexclauses list or is generated from the same
* EquivalenceClass as some indexclause, and is therefore redundant with
* it, though not equal. (The latter happens when indxpath.c prefers a
* different derived equality than what generate_join_implied_equalities
* picked for a parameterized scan's ppi_clauses.) Note that it will not
* match to lossy index clauses, which is critical because we have to
* include the original clause in qpqual in that case.
*
* In some situations (particularly with OR'd index conditions) we may
* have scan_clauses that are not equal to, but are logically implied by,
* the index quals; so we also try a predicate_implied_by() check to see
* if we can discard quals that way. (predicate_implied_by assumes its
* first input contains only immutable functions, so we have to check
* that.)
*
* Note: if you change this bit of code you should also look at
* extract_nonindex_conditions() in costsize.c.
*/
qpqual = NIL;
foreach(l, scan_clauses)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
if (rinfo->pseudoconstant)
continue; /* we may drop pseudoconstants here */
if (is_redundant_with_indexclauses(rinfo, indexclauses))
continue; /* dup or derived from same EquivalenceClass */
if (!contain_mutable_functions((Node *) rinfo->clause) &&
predicate_implied_by(list_make1(rinfo->clause), stripped_indexquals,
false))
continue; /* provably implied by indexquals */
qpqual = lappend(qpqual, rinfo);
}
/* Sort clauses into best execution order */
qpqual = order_qual_clauses(root, qpqual);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
qpqual = extract_actual_clauses(qpqual, false);
/*
* We have to replace any outer-relation variables with nestloop params in
* the indexqualorig, qpqual, and indexorderbyorig expressions. A bit
* annoying to have to do this separately from the processing in
* fix_indexqual_references --- rethink this when generalizing the inner
* indexscan support. But note we can't really do this earlier because
* it'd break the comparisons to predicates above ... (or would it? Those
* wouldn't have outer refs)
*/
if (best_path->path.param_info)
{
stripped_indexquals = (List *)
replace_nestloop_params(root, (Node *) stripped_indexquals);
qpqual = (List *)
replace_nestloop_params(root, (Node *) qpqual);
indexorderbys = (List *)
replace_nestloop_params(root, (Node *) indexorderbys);
}
/*
* If there are ORDER BY expressions, look up the sort operators for their
* result datatypes.
*/
if (indexorderbys)
{
ListCell *pathkeyCell,
*exprCell;
/*
* PathKey contains OID of the btree opfamily we're sorting by, but
* that's not quite enough because we need the expression's datatype
* to look up the sort operator in the operator family.
*/
Assert(list_length(best_path->path.pathkeys) == list_length(indexorderbys));
forboth(pathkeyCell, best_path->path.pathkeys, exprCell, indexorderbys)
{
PathKey *pathkey = (PathKey *) lfirst(pathkeyCell);
Node *expr = (Node *) lfirst(exprCell);
Oid exprtype = exprType(expr);
Oid sortop;
/* Get sort operator from opfamily */
sortop = get_opfamily_member_for_cmptype(pathkey->pk_opfamily,
exprtype,
exprtype,
pathkey->pk_cmptype);
if (!OidIsValid(sortop))
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
pathkey->pk_cmptype, exprtype, exprtype, pathkey->pk_opfamily);
indexorderbyops = lappend_oid(indexorderbyops, sortop);
}
}
/*
* For an index-only scan, we must mark indextlist entries as resjunk if
* they are columns that the index AM can't return; this cues setrefs.c to
* not generate references to those columns.
*/
if (indexonly)
{
int i = 0;
foreach(l, indexinfo->indextlist)
{
TargetEntry *indextle = (TargetEntry *) lfirst(l);
indextle->resjunk = !indexinfo->canreturn[i];
i++;
}
}
/* Finally ready to build the plan node */
if (indexonly)
scan_plan = (Scan *) make_indexonlyscan(tlist,
qpqual,
baserelid,
indexoid,
fixed_indexquals,
stripped_indexquals,
fixed_indexorderbys,
indexinfo->indextlist,
best_path->indexscandir);
else
scan_plan = (Scan *) make_indexscan(tlist,
qpqual,
baserelid,
indexoid,
fixed_indexquals,
stripped_indexquals,
fixed_indexorderbys,
indexorderbys,
indexorderbyops,
best_path->indexscandir);
copy_generic_path_info(&scan_plan->plan, &best_path->path);
return scan_plan;
}
/*
* create_bitmap_scan_plan
* Returns a bitmap scan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static BitmapHeapScan *
create_bitmap_scan_plan(PlannerInfo *root,
BitmapHeapPath *best_path,
List *tlist,
List *scan_clauses)
{
Index baserelid = best_path->path.parent->relid;
Plan *bitmapqualplan;
List *bitmapqualorig;
List *indexquals;
List *indexECs;
List *qpqual;
ListCell *l;
BitmapHeapScan *scan_plan;
/* it should be a base rel... */
Assert(baserelid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
/* Process the bitmapqual tree into a Plan tree and qual lists */
bitmapqualplan = create_bitmap_subplan(root, best_path->bitmapqual,
&bitmapqualorig, &indexquals,
&indexECs);
if (best_path->path.parallel_aware)
bitmap_subplan_mark_shared(bitmapqualplan);
/*
* The qpqual list must contain all restrictions not automatically handled
* by the index, other than pseudoconstant clauses which will be handled
* by a separate gating plan node. All the predicates in the indexquals
* will be checked (either by the index itself, or by
* nodeBitmapHeapscan.c), but if there are any "special" operators
* involved then they must be added to qpqual. The upshot is that qpqual
* must contain scan_clauses minus whatever appears in indexquals.
*
* This loop is similar to the comparable code in create_indexscan_plan(),
* but with some differences because it has to compare the scan clauses to
* stripped (no RestrictInfos) indexquals. See comments there for more
* info.
*
* In normal cases simple equal() checks will be enough to spot duplicate
* clauses, so we try that first. We next see if the scan clause is
* redundant with any top-level indexqual by virtue of being generated
* from the same EC. After that, try predicate_implied_by().
*
* Unlike create_indexscan_plan(), the predicate_implied_by() test here is
* useful for getting rid of qpquals that are implied by index predicates,
* because the predicate conditions are included in the "indexquals"
* returned by create_bitmap_subplan(). Bitmap scans have to do it that
* way because predicate conditions need to be rechecked if the scan
* becomes lossy, so they have to be included in bitmapqualorig.
*/
qpqual = NIL;
foreach(l, scan_clauses)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
Node *clause = (Node *) rinfo->clause;
if (rinfo->pseudoconstant)
continue; /* we may drop pseudoconstants here */
if (list_member(indexquals, clause))
continue; /* simple duplicate */
if (rinfo->parent_ec && list_member_ptr(indexECs, rinfo->parent_ec))
continue; /* derived from same EquivalenceClass */
if (!contain_mutable_functions(clause) &&
predicate_implied_by(list_make1(clause), indexquals, false))
continue; /* provably implied by indexquals */
qpqual = lappend(qpqual, rinfo);
}
/* Sort clauses into best execution order */
qpqual = order_qual_clauses(root, qpqual);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
qpqual = extract_actual_clauses(qpqual, false);
/*
* When dealing with special operators, we will at this point have
* duplicate clauses in qpqual and bitmapqualorig. We may as well drop
* 'em from bitmapqualorig, since there's no point in making the tests
* twice.
*/
bitmapqualorig = list_difference_ptr(bitmapqualorig, qpqual);
/*
* We have to replace any outer-relation variables with nestloop params in
* the qpqual and bitmapqualorig expressions. (This was already done for
* expressions attached to plan nodes in the bitmapqualplan tree.)
*/
if (best_path->path.param_info)
{
qpqual = (List *)
replace_nestloop_params(root, (Node *) qpqual);
bitmapqualorig = (List *)
replace_nestloop_params(root, (Node *) bitmapqualorig);
}
/* Finally ready to build the plan node */
scan_plan = make_bitmap_heapscan(tlist,
qpqual,
bitmapqualplan,
bitmapqualorig,
baserelid);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
return scan_plan;
}
/*
* Given a bitmapqual tree, generate the Plan tree that implements it
*
* As byproducts, we also return in *qual and *indexqual the qual lists
* (in implicit-AND form, without RestrictInfos) describing the original index
* conditions and the generated indexqual conditions. (These are the same in
* simple cases, but when special index operators are involved, the former
* list includes the special conditions while the latter includes the actual
* indexable conditions derived from them.) Both lists include partial-index
* predicates, because we have to recheck predicates as well as index
* conditions if the bitmap scan becomes lossy.
*
* In addition, we return a list of EquivalenceClass pointers for all the
* top-level indexquals that were possibly-redundantly derived from ECs.
* This allows removal of scan_clauses that are redundant with such quals.
* (We do not attempt to detect such redundancies for quals that are within
* OR subtrees. This could be done in a less hacky way if we returned the
* indexquals in RestrictInfo form, but that would be slower and still pretty
* messy, since we'd have to build new RestrictInfos in many cases.)
*/
static Plan *
create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
List **qual, List **indexqual, List **indexECs)
{
Plan *plan;
if (IsA(bitmapqual, BitmapAndPath))
{
BitmapAndPath *apath = (BitmapAndPath *) bitmapqual;
List *subplans = NIL;
List *subquals = NIL;
List *subindexquals = NIL;
List *subindexECs = NIL;
ListCell *l;
/*
* There may well be redundant quals among the subplans, since a
* top-level WHERE qual might have gotten used to form several
* different index quals. We don't try exceedingly hard to eliminate
* redundancies, but we do eliminate obvious duplicates by using
* list_concat_unique.
*/
foreach(l, apath->bitmapquals)
{
Plan *subplan;
List *subqual;
List *subindexqual;
List *subindexEC;
subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
&subqual, &subindexqual,
&subindexEC);
subplans = lappend(subplans, subplan);
subquals = list_concat_unique(subquals, subqual);
subindexquals = list_concat_unique(subindexquals, subindexqual);
/* Duplicates in indexECs aren't worth getting rid of */
subindexECs = list_concat(subindexECs, subindexEC);
}
plan = (Plan *) make_bitmap_and(subplans);
plan->startup_cost = apath->path.startup_cost;
plan->total_cost = apath->path.total_cost;
plan->plan_rows =
clamp_row_est(apath->bitmapselectivity * apath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */
plan->parallel_aware = false;
plan->parallel_safe = apath->path.parallel_safe;
*qual = subquals;
*indexqual = subindexquals;
*indexECs = subindexECs;
}
else if (IsA(bitmapqual, BitmapOrPath))
{
BitmapOrPath *opath = (BitmapOrPath *) bitmapqual;
List *subplans = NIL;
List *subquals = NIL;
List *subindexquals = NIL;
bool const_true_subqual = false;
bool const_true_subindexqual = false;
ListCell *l;
/*
* Here, we only detect qual-free subplans. A qual-free subplan would
* cause us to generate "... OR true ..." which we may as well reduce
* to just "true". We do not try to eliminate redundant subclauses
* because (a) it's not as likely as in the AND case, and (b) we might
* well be working with hundreds or even thousands of OR conditions,
* perhaps from a long IN list. The performance of list_append_unique
* would be unacceptable.
*/
foreach(l, opath->bitmapquals)
{
Plan *subplan;
List *subqual;
List *subindexqual;
List *subindexEC;
subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
&subqual, &subindexqual,
&subindexEC);
subplans = lappend(subplans, subplan);
if (subqual == NIL)
const_true_subqual = true;
else if (!const_true_subqual)
subquals = lappend(subquals,
make_ands_explicit(subqual));
if (subindexqual == NIL)
const_true_subindexqual = true;
else if (!const_true_subindexqual)
subindexquals = lappend(subindexquals,
make_ands_explicit(subindexqual));
}
/*
* In the presence of ScalarArrayOpExpr quals, we might have built
* BitmapOrPaths with just one subpath; don't add an OR step.
*/
if (list_length(subplans) == 1)
{
plan = (Plan *) linitial(subplans);
}
else
{
plan = (Plan *) make_bitmap_or(subplans);
plan->startup_cost = opath->path.startup_cost;
plan->total_cost = opath->path.total_cost;
plan->plan_rows =
clamp_row_est(opath->bitmapselectivity * opath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */
plan->parallel_aware = false;
plan->parallel_safe = opath->path.parallel_safe;
}
/*
* If there were constant-TRUE subquals, the OR reduces to constant
* TRUE. Also, avoid generating one-element ORs, which could happen
* due to redundancy elimination or ScalarArrayOpExpr quals.
*/
if (const_true_subqual)
*qual = NIL;
else if (list_length(subquals) <= 1)
*qual = subquals;
else
*qual = list_make1(make_orclause(subquals));
if (const_true_subindexqual)
*indexqual = NIL;
else if (list_length(subindexquals) <= 1)
*indexqual = subindexquals;
else
*indexqual = list_make1(make_orclause(subindexquals));
*indexECs = NIL;
}
else if (IsA(bitmapqual, IndexPath))
{
IndexPath *ipath = (IndexPath *) bitmapqual;
IndexScan *iscan;
List *subquals;
List *subindexquals;
List *subindexECs;
ListCell *l;
/* Use the regular indexscan plan build machinery... */
iscan = castNode(IndexScan,
create_indexscan_plan(root, ipath,
NIL, NIL, false));
/* then convert to a bitmap indexscan */
plan = (Plan *) make_bitmap_indexscan(iscan->scan.scanrelid,
iscan->indexid,
iscan->indexqual,
iscan->indexqualorig);
/* and set its cost/width fields appropriately */
plan->startup_cost = 0.0;
plan->total_cost = ipath->indextotalcost;
plan->plan_rows =
clamp_row_est(ipath->indexselectivity * ipath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */
plan->parallel_aware = false;
plan->parallel_safe = ipath->path.parallel_safe;
/* Extract original index clauses, actual index quals, relevant ECs */
subquals = NIL;
subindexquals = NIL;
subindexECs = NIL;
foreach(l, ipath->indexclauses)
{
IndexClause *iclause = (IndexClause *) lfirst(l);
RestrictInfo *rinfo = iclause->rinfo;
Assert(!rinfo->pseudoconstant);
subquals = lappend(subquals, rinfo->clause);
subindexquals = list_concat(subindexquals,
get_actual_clauses(iclause->indexquals));
if (rinfo->parent_ec)
subindexECs = lappend(subindexECs, rinfo->parent_ec);
}
/* We can add any index predicate conditions, too */
foreach(l, ipath->indexinfo->indpredExpand)
{
Expr *pred = (Expr *) lfirst(l);
/*
* We know that the index predicate must have been implied by the
* query condition as a whole, but it may or may not be implied by
* the conditions that got pushed into the bitmapqual. Avoid
* generating redundant conditions.
*/
if (!predicate_implied_by(list_make1(pred), subquals, false))
{
subquals = lappend(subquals, pred);
subindexquals = lappend(subindexquals, pred);
}
}
*qual = subquals;
*indexqual = subindexquals;
*indexECs = subindexECs;
}
else
{
elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
plan = NULL; /* keep compiler quiet */
}
return plan;
}
/*
* create_tidscan_plan
* Returns a tidscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static TidScan *
create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
List *tlist, List *scan_clauses)
{
TidScan *scan_plan;
Index scan_relid = best_path->path.parent->relid;
List *tidquals = best_path->tidquals;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
/*
* The qpqual list must contain all restrictions not enforced by the
* tidquals list. Since tidquals has OR semantics, we have to be careful
* about matching it up to scan_clauses. It's convenient to handle the
* single-tidqual case separately from the multiple-tidqual case. In the
* single-tidqual case, we look through the scan_clauses while they are
* still in RestrictInfo form, and drop any that are redundant with the
* tidqual.
*
* In normal cases simple pointer equality checks will be enough to spot
* duplicate RestrictInfos, so we try that first.
*
* Another common case is that a scan_clauses entry is generated from the
* same EquivalenceClass as some tidqual, and is therefore redundant with
* it, though not equal.
*
* Unlike indexpaths, we don't bother with predicate_implied_by(); the
* number of cases where it could win are pretty small.
*/
if (list_length(tidquals) == 1)
{
List *qpqual = NIL;
ListCell *l;
foreach(l, scan_clauses)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
if (rinfo->pseudoconstant)
continue; /* we may drop pseudoconstants here */
if (list_member_ptr(tidquals, rinfo))
continue; /* simple duplicate */
if (is_redundant_derived_clause(rinfo, tidquals))
continue; /* derived from same EquivalenceClass */
qpqual = lappend(qpqual, rinfo);
}
scan_clauses = qpqual;
}
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo lists to bare expressions; ignore pseudoconstants */
tidquals = extract_actual_clauses(tidquals, false);
scan_clauses = extract_actual_clauses(scan_clauses, false);
/*
* If we have multiple tidquals, it's more convenient to remove duplicate
* scan_clauses after stripping the RestrictInfos. In this situation,
* because the tidquals represent OR sub-clauses, they could not have come
* from EquivalenceClasses so we don't have to worry about matching up
* non-identical clauses. On the other hand, because tidpath.c will have
* extracted those sub-clauses from some OR clause and built its own list,
* we will certainly not have pointer equality to any scan clause. So
* convert the tidquals list to an explicit OR clause and see if we can
* match it via equal() to any scan clause.
*/
if (list_length(tidquals) > 1)
scan_clauses = list_difference(scan_clauses,
list_make1(make_orclause(tidquals)));
/* Replace any outer-relation variables with nestloop params */
if (best_path->path.param_info)
{
tidquals = (List *)
replace_nestloop_params(root, (Node *) tidquals);
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
}
scan_plan = make_tidscan(tlist,
scan_clauses,
scan_relid,
tidquals);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
return scan_plan;
}
/*
* create_tidrangescan_plan
* Returns a tidrangescan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static TidRangeScan *
create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path,
List *tlist, List *scan_clauses)
{
TidRangeScan *scan_plan;
Index scan_relid = best_path->path.parent->relid;
List *tidrangequals = best_path->tidrangequals;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
/*
* The qpqual list must contain all restrictions not enforced by the
* tidrangequals list. tidrangequals has AND semantics, so we can simply
* remove any qual that appears in it.
*/
{
List *qpqual = NIL;
ListCell *l;
foreach(l, scan_clauses)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
if (rinfo->pseudoconstant)
continue; /* we may drop pseudoconstants here */
if (list_member_ptr(tidrangequals, rinfo))
continue; /* simple duplicate */
qpqual = lappend(qpqual, rinfo);
}
scan_clauses = qpqual;
}
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo lists to bare expressions; ignore pseudoconstants */
tidrangequals = extract_actual_clauses(tidrangequals, false);
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->path.param_info)
{
tidrangequals = (List *)
replace_nestloop_params(root, (Node *) tidrangequals);
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
}
scan_plan = make_tidrangescan(tlist,
scan_clauses,
scan_relid,
tidrangequals);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
return scan_plan;
}
/*
* create_subqueryscan_plan
* Returns a subqueryscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static SubqueryScan *
create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
List *tlist, List *scan_clauses)
{
SubqueryScan *scan_plan;
RelOptInfo *rel = best_path->path.parent;
Index scan_relid = rel->relid;
Plan *subplan;
/* it should be a subquery base rel... */
Assert(scan_relid > 0);
Assert(rel->rtekind == RTE_SUBQUERY);
/*
* Recursively create Plan from Path for subquery. Since we are entering
* a different planner context (subroot), recurse to create_plan not
* create_plan_recurse.
*/
subplan = create_plan(rel->subroot, best_path->subpath);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/*
* Replace any outer-relation variables with nestloop params.
*
* We must provide nestloop params for both lateral references of the
* subquery and outer vars in the scan_clauses. It's better to assign the
* former first, because that code path requires specific param IDs, while
* replace_nestloop_params can adapt to the IDs assigned by
* process_subquery_nestloop_params. This avoids possibly duplicating
* nestloop params when the same Var is needed for both reasons.
*/
if (best_path->path.param_info)
{
process_subquery_nestloop_params(root,
rel->subplan_params);
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
}
scan_plan = make_subqueryscan(tlist,
scan_clauses,
scan_relid,
subplan);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
return scan_plan;
}
/*
* create_functionscan_plan
* Returns a functionscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static FunctionScan *
create_functionscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
FunctionScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
List *functions;
/* it should be a function base rel... */
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_FUNCTION);
functions = rte->functions;
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
/* The function expressions could contain nestloop params, too */
functions = (List *) replace_nestloop_params(root, (Node *) functions);
}
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
functions, rte->funcordinality);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_tablefuncscan_plan
* Returns a tablefuncscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static TableFuncScan *
create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
TableFuncScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
TableFunc *tablefunc;
/* it should be a function base rel... */
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_TABLEFUNC);
tablefunc = rte->tablefunc;
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
/* The function expressions could contain nestloop params, too */
tablefunc = (TableFunc *) replace_nestloop_params(root, (Node *) tablefunc);
}
scan_plan = make_tablefuncscan(tlist, scan_clauses, scan_relid,
tablefunc);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_valuesscan_plan
* Returns a valuesscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static ValuesScan *
create_valuesscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
ValuesScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
List *values_lists;
/* it should be a values base rel... */
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_VALUES);
values_lists = rte->values_lists;
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
/* The values lists could contain nestloop params, too */
values_lists = (List *)
replace_nestloop_params(root, (Node *) values_lists);
}
scan_plan = make_valuesscan(tlist, scan_clauses, scan_relid,
values_lists);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_ctescan_plan
* Returns a ctescan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static CteScan *
create_ctescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
CteScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
SubPlan *ctesplan = NULL;
int plan_id;
int cte_param_id;
PlannerInfo *cteroot;
Index levelsup;
int ndx;
ListCell *lc;
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_CTE);
Assert(!rte->self_reference);
/*
* Find the referenced CTE, and locate the SubPlan previously made for it.
*/
levelsup = rte->ctelevelsup;
cteroot = root;
while (levelsup-- > 0)
{
cteroot = cteroot->parent_root;
if (!cteroot) /* shouldn't happen */
elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
}
/*
* Note: cte_plan_ids can be shorter than cteList, if we are still working
* on planning the CTEs (ie, this is a side-reference from another CTE).
* So we mustn't use forboth here.
*/
ndx = 0;
foreach(lc, cteroot->parse->cteList)
{
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
if (strcmp(cte->ctename, rte->ctename) == 0)
break;
ndx++;
}
if (lc == NULL) /* shouldn't happen */
elog(ERROR, "could not find CTE \"%s\"", rte->ctename);
if (ndx >= list_length(cteroot->cte_plan_ids))
elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
plan_id = list_nth_int(cteroot->cte_plan_ids, ndx);
if (plan_id <= 0)
elog(ERROR, "no plan was made for CTE \"%s\"", rte->ctename);
foreach(lc, cteroot->init_plans)
{
ctesplan = (SubPlan *) lfirst(lc);
if (ctesplan->plan_id == plan_id)
break;
}
if (lc == NULL) /* shouldn't happen */
elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
/*
* We need the CTE param ID, which is the sole member of the SubPlan's
* setParam list.
*/
cte_param_id = linitial_int(ctesplan->setParam);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
}
scan_plan = make_ctescan(tlist, scan_clauses, scan_relid,
plan_id, cte_param_id);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_namedtuplestorescan_plan
* Returns a tuplestorescan plan for the base relation scanned by
* 'best_path' with restriction clauses 'scan_clauses' and targetlist
* 'tlist'.
*/
static NamedTuplestoreScan *
create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
NamedTuplestoreScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
}
scan_plan = make_namedtuplestorescan(tlist, scan_clauses, scan_relid,
rte->enrname);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_resultscan_plan
* Returns a Result plan for the RTE_RESULT base relation scanned by
* 'best_path' with restriction clauses 'scan_clauses' and targetlist
* 'tlist'.
*/
static Result *
create_resultscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
Result *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY;
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_RESULT);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
}
scan_plan = make_one_row_result(tlist, (Node *) scan_clauses,
best_path->parent);
copy_generic_path_info(&scan_plan->plan, best_path);
return scan_plan;
}
/*
* create_worktablescan_plan
* Returns a worktablescan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static WorkTableScan *
create_worktablescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
WorkTableScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
Index levelsup;
PlannerInfo *cteroot;
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_CTE);
Assert(rte->self_reference);
/*
* We need to find the worktable param ID, which is in the plan level
* that's processing the recursive UNION, which is one level *below* where
* the CTE comes from.
*/
levelsup = rte->ctelevelsup;
if (levelsup == 0) /* shouldn't happen */
elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
levelsup--;
cteroot = root;
while (levelsup-- > 0)
{
cteroot = cteroot->parent_root;
if (!cteroot) /* shouldn't happen */
elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
}
if (cteroot->wt_param_id < 0) /* shouldn't happen */
elog(ERROR, "could not find param ID for CTE \"%s\"", rte->ctename);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
}
scan_plan = make_worktablescan(tlist, scan_clauses, scan_relid,
cteroot->wt_param_id);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_foreignscan_plan
* Returns a foreignscan plan for the relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static ForeignScan *
create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
List *tlist, List *scan_clauses)
{
ForeignScan *scan_plan;
RelOptInfo *rel = best_path->path.parent;
Index scan_relid = rel->relid;
Oid rel_oid = InvalidOid;
Plan *outer_plan = NULL;
Assert(rel->fdwroutine != NULL);
/* transform the child path if any */
if (best_path->fdw_outerpath)
outer_plan = create_plan_recurse(root, best_path->fdw_outerpath,
CP_EXACT_TLIST);
/*
* If we're scanning a base relation, fetch its OID. (Irrelevant if
* scanning a join relation.)
*/
if (scan_relid > 0)
{
RangeTblEntry *rte;
Assert(rel->rtekind == RTE_RELATION);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_RELATION);
rel_oid = rte->relid;
}
/*
* Sort clauses into best execution order. We do this first since the FDW
* might have more info than we do and wish to adjust the ordering.
*/
scan_clauses = order_qual_clauses(root, scan_clauses);
/*
* Let the FDW perform its processing on the restriction clauses and
* generate the plan node. Note that the FDW might remove restriction
* clauses that it intends to execute remotely, or even add more (if it
* has selected some join clauses for remote use but also wants them
* rechecked locally).
*/
scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rel_oid,
best_path,
tlist, scan_clauses,
outer_plan);
/* Copy cost data from Path to Plan; no need to make FDW do this */
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
/* Copy user OID to access as; likewise no need to make FDW do this */
scan_plan->checkAsUser = rel->userid;
/* Copy foreign server OID; likewise, no need to make FDW do this */
scan_plan->fs_server = rel->serverid;
/*
* Likewise, copy the relids that are represented by this foreign scan. An
* upper rel doesn't have relids set, but it covers all the relations
* participating in the underlying scan/join, so use root->all_query_rels.
*/
if (rel->reloptkind == RELOPT_UPPER_REL)
scan_plan->fs_relids = root->all_query_rels;
else
scan_plan->fs_relids = best_path->path.parent->relids;
/*
* Join relid sets include relevant outer joins, but FDWs may need to know
* which are the included base rels. That's a bit tedious to get without
* access to the plan-time data structures, so compute it here.
*/
scan_plan->fs_base_relids = bms_difference(scan_plan->fs_relids,
root->outer_join_rels);
/*
* If this is a foreign join, and to make it valid to push down we had to
* assume that the current user is the same as some user explicitly named
* in the query, mark the finished plan as depending on the current user.
*/
if (rel->useridiscurrent)
root->glob->dependsOnRole = true;
/*
* Replace any outer-relation variables with nestloop params in the qual,
* fdw_exprs and fdw_recheck_quals expressions. We do this last so that
* the FDW doesn't have to be involved. (Note that parts of fdw_exprs or
* fdw_recheck_quals could have come from join clauses, so doing this
* beforehand on the scan_clauses wouldn't work.) We assume
* fdw_scan_tlist contains no such variables.
*/
if (best_path->path.param_info)
{
scan_plan->scan.plan.qual = (List *)
replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual);
scan_plan->fdw_exprs = (List *)
replace_nestloop_params(root, (Node *) scan_plan->fdw_exprs);
scan_plan->fdw_recheck_quals = (List *)
replace_nestloop_params(root,
(Node *) scan_plan->fdw_recheck_quals);
}
/*
* If rel is a base relation, detect whether any system columns are
* requested from the rel. (If rel is a join relation, rel->relid will be
* 0, but there can be no Var with relid 0 in the rel's targetlist or the
* restriction clauses, so we skip this in that case. Note that any such
* columns in base relations that were joined are assumed to be contained
* in fdw_scan_tlist.) This is a bit of a kluge and might go away
* someday, so we intentionally leave it out of the API presented to FDWs.
*/
scan_plan->fsSystemCol = false;
if (scan_relid > 0)
{
Bitmapset *attrs_used = NULL;
ListCell *lc;
int i;
/*
* First, examine all the attributes needed for joins or final output.
* Note: we must look at rel's targetlist, not the attr_needed data,
* because attr_needed isn't computed for inheritance child rels.
*/
pull_varattnos((Node *) rel->reltarget->exprs, scan_relid, &attrs_used);
/* Add all the attributes used by restriction clauses. */
foreach(lc, rel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
pull_varattnos((Node *) rinfo->clause, scan_relid, &attrs_used);
}
/* Now, are any system columns requested from rel? */
for (i = FirstLowInvalidHeapAttributeNumber + 1; i < 0; i++)
{
if (bms_is_member(i - FirstLowInvalidHeapAttributeNumber, attrs_used))
{
scan_plan->fsSystemCol = true;
break;
}
}
bms_free(attrs_used);
}
return scan_plan;
}
/*
* create_customscan_plan
*
* Transform a CustomPath into a Plan.
*/
static CustomScan *
create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
List *tlist, List *scan_clauses)
{
CustomScan *cplan;
RelOptInfo *rel = best_path->path.parent;
List *custom_plans = NIL;
ListCell *lc;
/* Recursively transform child paths. */
foreach(lc, best_path->custom_paths)
{
Plan *plan = create_plan_recurse(root, (Path *) lfirst(lc),
CP_EXACT_TLIST);
custom_plans = lappend(custom_plans, plan);
}
/*
* Sort clauses into the best execution order, although custom-scan
* provider can reorder them again.
*/
scan_clauses = order_qual_clauses(root, scan_clauses);
/*
* Invoke custom plan provider to create the Plan node represented by the
* CustomPath.
*/
cplan = castNode(CustomScan,
best_path->methods->PlanCustomPath(root,
rel,
best_path,
tlist,
scan_clauses,
custom_plans));
/*
* Copy cost data from Path to Plan; no need to make custom-plan providers
* do this
*/
copy_generic_path_info(&cplan->scan.plan, &best_path->path);
/* Likewise, copy the relids that are represented by this custom scan */
cplan->custom_relids = best_path->path.parent->relids;
/*
* Replace any outer-relation variables with nestloop params in the qual
* and custom_exprs expressions. We do this last so that the custom-plan
* provider doesn't have to be involved. (Note that parts of custom_exprs
* could have come from join clauses, so doing this beforehand on the
* scan_clauses wouldn't work.) We assume custom_scan_tlist contains no
* such variables.
*/
if (best_path->path.param_info)
{
cplan->scan.plan.qual = (List *)
replace_nestloop_params(root, (Node *) cplan->scan.plan.qual);
cplan->custom_exprs = (List *)
replace_nestloop_params(root, (Node *) cplan->custom_exprs);
}
return cplan;
}
/*****************************************************************************
*
* JOIN METHODS
*
*****************************************************************************/
static NestLoop *
create_nestloop_plan(PlannerInfo *root,
NestPath *best_path)
{
NestLoop *join_plan;
Plan *outer_plan;
Plan *inner_plan;
Relids outerrelids;
List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinrestrictclauses = best_path->jpath.joinrestrictinfo;
List *joinclauses;
List *otherclauses;
List *nestParams;
List *outer_tlist;
bool outer_parallel_safe;
Relids saveOuterRels = root->curOuterRels;
ListCell *lc;
/*
* If the inner path is parameterized by the topmost parent of the outer
* rel rather than the outer rel itself, fix that. (Nothing happens here
* if it is not so parameterized.)
*/
best_path->jpath.innerjoinpath =
reparameterize_path_by_child(root,
best_path->jpath.innerjoinpath,
best_path->jpath.outerjoinpath->parent);
/*
* Failure here probably means that reparameterize_path_by_child() is not
* in sync with path_is_reparameterizable_by_child().
*/
Assert(best_path->jpath.innerjoinpath != NULL);
/* NestLoop can project, so no need to be picky about child tlists */
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath, 0);
/* For a nestloop, include outer relids in curOuterRels for inner side */
outerrelids = best_path->jpath.outerjoinpath->parent->relids;
root->curOuterRels = bms_union(root->curOuterRels, outerrelids);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath, 0);
/* Restore curOuterRels */
bms_free(root->curOuterRels);
root->curOuterRels = saveOuterRels;
/* Sort join qual clauses into best execution order */
joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses);
/* Get the join qual clauses (in plain expression form) */
/* Any pseudoconstant clauses are ignored here */
if (IS_OUTER_JOIN(best_path->jpath.jointype))
{
extract_actual_join_clauses(joinrestrictclauses,
best_path->jpath.path.parent->relids,
&joinclauses, &otherclauses);
}
else
{
/* We can treat all clauses alike for an inner join */
joinclauses = extract_actual_clauses(joinrestrictclauses, false);
otherclauses = NIL;
}
/* Replace any outer-relation variables with nestloop params */
if (best_path->jpath.path.param_info)
{
joinclauses = (List *)
replace_nestloop_params(root, (Node *) joinclauses);
otherclauses = (List *)
replace_nestloop_params(root, (Node *) otherclauses);
}
/*
* Identify any nestloop parameters that should be supplied by this join
* node, and remove them from root->curOuterParams.
*/
nestParams = identify_current_nestloop_params(root,
outerrelids,
PATH_REQ_OUTER((Path *) best_path));
/*
* While nestloop parameters that are Vars had better be available from
* the outer_plan already, there are edge cases where nestloop parameters
* that are PHVs won't be. In such cases we must add them to the
* outer_plan's tlist, since the executor's NestLoopParam machinery
* requires the params to be simple outer-Var references to that tlist.
* (This is cheating a little bit, because the outer path's required-outer
* relids might not be enough to allow evaluating such a PHV. But in
* practice, if we could have evaluated the PHV at the nestloop node, we
* can do so in the outer plan too.)
*/
outer_tlist = outer_plan->targetlist;
outer_parallel_safe = outer_plan->parallel_safe;
foreach(lc, nestParams)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
PlaceHolderVar *phv;
TargetEntry *tle;
if (IsA(nlp->paramval, Var))
continue; /* nothing to do for simple Vars */
/* Otherwise it must be a PHV */
phv = castNode(PlaceHolderVar, nlp->paramval);
if (tlist_member((Expr *) phv, outer_tlist))
continue; /* already available */
/*
* It's possible that nestloop parameter PHVs selected to evaluate
* here contain references to surviving root->curOuterParams items
* (that is, they reference values that will be supplied by some
* higher-level nestloop). Those need to be converted to Params now.
* Note: it's safe to do this after the tlist_member() check, because
* equal() won't pay attention to phv->phexpr.
*/
phv->phexpr = (Expr *) replace_nestloop_params(root,
(Node *) phv->phexpr);
/* Make a shallow copy of outer_tlist, if we didn't already */
if (outer_tlist == outer_plan->targetlist)
outer_tlist = list_copy(outer_tlist);
/* ... and add the needed expression */
tle = makeTargetEntry((Expr *) copyObject(phv),
list_length(outer_tlist) + 1,
NULL,
true);
outer_tlist = lappend(outer_tlist, tle);
/* ... and track whether tlist is (still) parallel-safe */
if (outer_parallel_safe)
outer_parallel_safe = is_parallel_safe(root, (Node *) phv);
}
if (outer_tlist != outer_plan->targetlist)
outer_plan = change_plan_targetlist(outer_plan, outer_tlist,
outer_parallel_safe);
/* And finally, we can build the join plan node */
join_plan = make_nestloop(tlist,
joinclauses,
otherclauses,
nestParams,
outer_plan,
inner_plan,
best_path->jpath.jointype,
best_path->jpath.inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
return join_plan;
}
static MergeJoin *
create_mergejoin_plan(PlannerInfo *root,
MergePath *best_path)
{
MergeJoin *join_plan;
Plan *outer_plan;
Plan *inner_plan;
List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinclauses;
List *otherclauses;
List *mergeclauses;
List *outerpathkeys;
List *innerpathkeys;
int nClauses;
Oid *mergefamilies;
Oid *mergecollations;
bool *mergereversals;
bool *mergenullsfirst;
PathKey *opathkey;
EquivalenceClass *opeclass;
int i;
ListCell *lc;
ListCell *lop;
ListCell *lip;
Path *outer_path = best_path->jpath.outerjoinpath;
Path *inner_path = best_path->jpath.innerjoinpath;
/*
* MergeJoin can project, so we don't have to demand exact tlists from the
* inputs. However, if we're intending to sort an input's result, it's
* best to request a small tlist so we aren't sorting more data than
* necessary.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
(best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
(best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0);
/* Sort join qual clauses into best execution order */
/* NB: do NOT reorder the mergeclauses */
joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
/* Get the join qual clauses (in plain expression form) */
/* Any pseudoconstant clauses are ignored here */
if (IS_OUTER_JOIN(best_path->jpath.jointype))
{
extract_actual_join_clauses(joinclauses,
best_path->jpath.path.parent->relids,
&joinclauses, &otherclauses);
}
else
{
/* We can treat all clauses alike for an inner join */
joinclauses = extract_actual_clauses(joinclauses, false);
otherclauses = NIL;
}
/*
* Remove the mergeclauses from the list of join qual clauses, leaving the
* list of quals that must be checked as qpquals.
*/
mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
joinclauses = list_difference(joinclauses, mergeclauses);
/*
* Replace any outer-relation variables with nestloop params. There
* should not be any in the mergeclauses.
*/
if (best_path->jpath.path.param_info)
{
joinclauses = (List *)
replace_nestloop_params(root, (Node *) joinclauses);
otherclauses = (List *)
replace_nestloop_params(root, (Node *) otherclauses);
}
/*
* Rearrange mergeclauses, if needed, so that the outer variable is always
* on the left; mark the mergeclause restrictinfos with correct
* outer_is_left status.
*/
mergeclauses = get_switched_clauses(best_path->path_mergeclauses,
best_path->jpath.outerjoinpath->parent->relids);
/*
* Create explicit sort nodes for the outer and inner paths if necessary.
*/
if (best_path->outersortkeys)
{
Relids outer_relids = outer_path->parent->relids;
Plan *sort_plan;
/*
* We can assert that the outer path is not already ordered
* appropriately for the mergejoin; otherwise, outersortkeys would
* have been set to NIL.
*/
Assert(!pathkeys_contained_in(best_path->outersortkeys,
outer_path->pathkeys));
/*
* We choose to use incremental sort if it is enabled and there are
* presorted keys; otherwise we use full sort.
*/
if (enable_incremental_sort && best_path->outer_presorted_keys > 0)
{
sort_plan = (Plan *)
make_incrementalsort_from_pathkeys(outer_plan,
best_path->outersortkeys,
outer_relids,
best_path->outer_presorted_keys);
label_incrementalsort_with_costsize(root,
(IncrementalSort *) sort_plan,
best_path->outersortkeys,
-1.0);
}
else
{
sort_plan = (Plan *)
make_sort_from_pathkeys(outer_plan,
best_path->outersortkeys,
outer_relids);
label_sort_with_costsize(root, (Sort *) sort_plan, -1.0);
}
outer_plan = sort_plan;
outerpathkeys = best_path->outersortkeys;
}
else
outerpathkeys = best_path->jpath.outerjoinpath->pathkeys;
if (best_path->innersortkeys)
{
/*
* We do not consider incremental sort for inner path, because
* incremental sort does not support mark/restore.
*/
Relids inner_relids = inner_path->parent->relids;
Sort *sort;
/*
* We can assert that the inner path is not already ordered
* appropriately for the mergejoin; otherwise, innersortkeys would
* have been set to NIL.
*/
Assert(!pathkeys_contained_in(best_path->innersortkeys,
inner_path->pathkeys));
sort = make_sort_from_pathkeys(inner_plan,
best_path->innersortkeys,
inner_relids);
label_sort_with_costsize(root, sort, -1.0);
inner_plan = (Plan *) sort;
innerpathkeys = best_path->innersortkeys;
}
else
innerpathkeys = best_path->jpath.innerjoinpath->pathkeys;
/*
* If specified, add a materialize node to shield the inner plan from the
* need to handle mark/restore.
*/
if (best_path->materialize_inner)
{
Plan *matplan = (Plan *) make_material(inner_plan);
/*
* We assume the materialize will not spill to disk, and therefore
* charge just cpu_operator_cost per tuple. (Keep this estimate in
* sync with final_cost_mergejoin.)
*/
copy_plan_costsize(matplan, inner_plan);
matplan->total_cost += cpu_operator_cost * matplan->plan_rows;
inner_plan = matplan;
}
/*
* Compute the opfamily/collation/strategy/nullsfirst arrays needed by the
* executor. The information is in the pathkeys for the two inputs, but
* we need to be careful about the possibility of mergeclauses sharing a
* pathkey, as well as the possibility that the inner pathkeys are not in
* an order matching the mergeclauses.
*/
nClauses = list_length(mergeclauses);
Assert(nClauses == list_length(best_path->path_mergeclauses));
mergefamilies = (Oid *) palloc(nClauses * sizeof(Oid));
mergecollations = (Oid *) palloc(nClauses * sizeof(Oid));
mergereversals = (bool *) palloc(nClauses * sizeof(bool));
mergenullsfirst = (bool *) palloc(nClauses * sizeof(bool));
opathkey = NULL;
opeclass = NULL;
lop = list_head(outerpathkeys);
lip = list_head(innerpathkeys);
i = 0;
foreach(lc, best_path->path_mergeclauses)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
EquivalenceClass *oeclass;
EquivalenceClass *ieclass;
PathKey *ipathkey = NULL;
EquivalenceClass *ipeclass = NULL;
bool first_inner_match = false;
/* fetch outer/inner eclass from mergeclause */
if (rinfo->outer_is_left)
{
oeclass = rinfo->left_ec;
ieclass = rinfo->right_ec;
}
else
{
oeclass = rinfo->right_ec;
ieclass = rinfo->left_ec;
}
Assert(oeclass != NULL);
Assert(ieclass != NULL);
/*
* We must identify the pathkey elements associated with this clause
* by matching the eclasses (which should give a unique match, since
* the pathkey lists should be canonical). In typical cases the merge
* clauses are one-to-one with the pathkeys, but when dealing with
* partially redundant query conditions, things are more complicated.
*
* lop and lip reference the first as-yet-unmatched pathkey elements.
* If they're NULL then all pathkey elements have been matched.
*
* The ordering of the outer pathkeys should match the mergeclauses,
* by construction (see find_mergeclauses_for_outer_pathkeys()). There
* could be more than one mergeclause for the same outer pathkey, but
* no pathkey may be entirely skipped over.
*/
if (oeclass != opeclass) /* multiple matches are not interesting */
{
/* doesn't match the current opathkey, so must match the next */
if (lop == NULL)
elog(ERROR, "outer pathkeys do not match mergeclauses");
opathkey = (PathKey *) lfirst(lop);
opeclass = opathkey->pk_eclass;
lop = lnext(outerpathkeys, lop);
if (oeclass != opeclass)
elog(ERROR, "outer pathkeys do not match mergeclauses");
}
/*
* The inner pathkeys likewise should not have skipped-over keys, but
* it's possible for a mergeclause to reference some earlier inner
* pathkey if we had redundant pathkeys. For example we might have
* mergeclauses like "o.a = i.x AND o.b = i.y AND o.c = i.x". The
* implied inner ordering is then "ORDER BY x, y, x", but the pathkey
* mechanism drops the second sort by x as redundant, and this code
* must cope.
*
* It's also possible for the implied inner-rel ordering to be like
* "ORDER BY x, y, x DESC". We still drop the second instance of x as
* redundant; but this means that the sort ordering of a redundant
* inner pathkey should not be considered significant. So we must
* detect whether this is the first clause matching an inner pathkey.
*/
if (lip)
{
ipathkey = (PathKey *) lfirst(lip);
ipeclass = ipathkey->pk_eclass;
if (ieclass == ipeclass)
{
/* successful first match to this inner pathkey */
lip = lnext(innerpathkeys, lip);
first_inner_match = true;
}
}
if (!first_inner_match)
{
/* redundant clause ... must match something before lip */
ListCell *l2;
foreach(l2, innerpathkeys)
{
if (l2 == lip)
break;
ipathkey = (PathKey *) lfirst(l2);
ipeclass = ipathkey->pk_eclass;
if (ieclass == ipeclass)
break;
}
if (ieclass != ipeclass)
elog(ERROR, "inner pathkeys do not match mergeclauses");
}
/*
* The pathkeys should always match each other as to opfamily and
* collation (which affect equality), but if we're considering a
* redundant inner pathkey, its sort ordering might not match. In
* such cases we may ignore the inner pathkey's sort ordering and use
* the outer's. (In effect, we're lying to the executor about the
* sort direction of this inner column, but it does not matter since
* the run-time row comparisons would only reach this column when
* there's equality for the earlier column containing the same eclass.
* There could be only one value in this column for the range of inner
* rows having a given value in the earlier column, so it does not
* matter which way we imagine this column to be ordered.) But a
* non-redundant inner pathkey had better match outer's ordering too.
*/
if (opathkey->pk_opfamily != ipathkey->pk_opfamily ||
opathkey->pk_eclass->ec_collation != ipathkey->pk_eclass->ec_collation)
elog(ERROR, "left and right pathkeys do not match in mergejoin");
if (first_inner_match &&
(opathkey->pk_cmptype != ipathkey->pk_cmptype ||
opathkey->pk_nulls_first != ipathkey->pk_nulls_first))
elog(ERROR, "left and right pathkeys do not match in mergejoin");
/* OK, save info for executor */
mergefamilies[i] = opathkey->pk_opfamily;
mergecollations[i] = opathkey->pk_eclass->ec_collation;
mergereversals[i] = (opathkey->pk_cmptype == COMPARE_GT ? true : false);
mergenullsfirst[i] = opathkey->pk_nulls_first;
i++;
}
/*
* Note: it is not an error if we have additional pathkey elements (i.e.,
* lop or lip isn't NULL here). The input paths might be better-sorted
* than we need for the current mergejoin.
*/
/*
* Now we can build the mergejoin node.
*/
join_plan = make_mergejoin(tlist,
joinclauses,
otherclauses,
mergeclauses,
mergefamilies,
mergecollations,
mergereversals,
mergenullsfirst,
outer_plan,
inner_plan,
best_path->jpath.jointype,
best_path->jpath.inner_unique,
best_path->skip_mark_restore);
/* Costs of sort and material steps are included in path cost already */
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
return join_plan;
}
static HashJoin *
create_hashjoin_plan(PlannerInfo *root,
HashPath *best_path)
{
HashJoin *join_plan;
Hash *hash_plan;
Plan *outer_plan;
Plan *inner_plan;
List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinclauses;
List *otherclauses;
List *hashclauses;
List *hashoperators = NIL;
List *hashcollations = NIL;
List *inner_hashkeys = NIL;
List *outer_hashkeys = NIL;
Oid skewTable = InvalidOid;
AttrNumber skewColumn = InvalidAttrNumber;
bool skewInherit = false;
ListCell *lc;
/*
* HashJoin can project, so we don't have to demand exact tlists from the
* inputs. However, it's best to request a small tlist from the inner
* side, so that we aren't storing more data than necessary. Likewise, if
* we anticipate batching, request a small tlist from the outer side so
* that we don't put extra data in the outer batch files.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
(best_path->num_batches > 1) ? CP_SMALL_TLIST : 0);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
CP_SMALL_TLIST);
/* Sort join qual clauses into best execution order */
joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
/* There's no point in sorting the hash clauses ... */
/* Get the join qual clauses (in plain expression form) */
/* Any pseudoconstant clauses are ignored here */
if (IS_OUTER_JOIN(best_path->jpath.jointype))
{
extract_actual_join_clauses(joinclauses,
best_path->jpath.path.parent->relids,
&joinclauses, &otherclauses);
}
else
{
/* We can treat all clauses alike for an inner join */
joinclauses = extract_actual_clauses(joinclauses, false);
otherclauses = NIL;
}
/*
* Remove the hashclauses from the list of join qual clauses, leaving the
* list of quals that must be checked as qpquals.
*/
hashclauses = get_actual_clauses(best_path->path_hashclauses);
joinclauses = list_difference(joinclauses, hashclauses);
/*
* Replace any outer-relation variables with nestloop params. There
* should not be any in the hashclauses.
*/
if (best_path->jpath.path.param_info)
{
joinclauses = (List *)
replace_nestloop_params(root, (Node *) joinclauses);
otherclauses = (List *)
replace_nestloop_params(root, (Node *) otherclauses);
}
/*
* Rearrange hashclauses, if needed, so that the outer variable is always
* on the left.
*/
hashclauses = get_switched_clauses(best_path->path_hashclauses,
best_path->jpath.outerjoinpath->parent->relids);
/*
* If there is a single join clause and we can identify the outer variable
* as a simple column reference, supply its identity for possible use in
* skew optimization. (Note: in principle we could do skew optimization
* with multiple join clauses, but we'd have to be able to determine the
* most common combinations of outer values, which we don't currently have
* enough stats for.)
*/
if (list_length(hashclauses) == 1)
{
OpExpr *clause = (OpExpr *) linitial(hashclauses);
Node *node;
Assert(is_opclause(clause));
node = (Node *) linitial(clause->args);
if (IsA(node, RelabelType))
node = (Node *) ((RelabelType *) node)->arg;
if (IsA(node, Var))
{
Var *var = (Var *) node;
RangeTblEntry *rte;
rte = root->simple_rte_array[var->varno];
if (rte->rtekind == RTE_RELATION)
{
skewTable = rte->relid;
skewColumn = var->varattno;
skewInherit = rte->inh;
}
}
}
/*
* Collect hash related information. The hashed expressions are
* deconstructed into outer/inner expressions, so they can be computed
* separately (inner expressions are used to build the hashtable via Hash,
* outer expressions to perform lookups of tuples from HashJoin's outer
* plan in the hashtable). Also collect operator information necessary to
* build the hashtable.
*/
foreach(lc, hashclauses)
{
OpExpr *hclause = lfirst_node(OpExpr, lc);
hashoperators = lappend_oid(hashoperators, hclause->opno);
hashcollations = lappend_oid(hashcollations, hclause->inputcollid);
outer_hashkeys = lappend(outer_hashkeys, linitial(hclause->args));
inner_hashkeys = lappend(inner_hashkeys, lsecond(hclause->args));
}
/*
* Build the hash node and hash join node.
*/
hash_plan = make_hash(inner_plan,
inner_hashkeys,
skewTable,
skewColumn,
skewInherit);
/*
* Set Hash node's startup & total costs equal to total cost of input
* plan; this only affects EXPLAIN display not decisions.
*/
copy_plan_costsize(&hash_plan->plan, inner_plan);
hash_plan->plan.startup_cost = hash_plan->plan.total_cost;
/*
* If parallel-aware, the executor will also need an estimate of the total
* number of rows expected from all participants so that it can size the
* shared hash table.
*/
if (best_path->jpath.path.parallel_aware)
{
hash_plan->plan.parallel_aware = true;
hash_plan->rows_total = best_path->inner_rows_total;
}
join_plan = make_hashjoin(tlist,
joinclauses,
otherclauses,
hashclauses,
hashoperators,
hashcollations,
outer_hashkeys,
outer_plan,
(Plan *) hash_plan,
best_path->jpath.jointype,
best_path->jpath.inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
return join_plan;
}
/*****************************************************************************
*
* SUPPORTING ROUTINES
*
*****************************************************************************/
/*
* replace_nestloop_params
* Replace outer-relation Vars and PlaceHolderVars in the given expression
* with nestloop Params
*
* All Vars and PlaceHolderVars belonging to the relation(s) identified by
* root->curOuterRels are replaced by Params, and entries are added to
* root->curOuterParams if not already present.
*/
static Node *
replace_nestloop_params(PlannerInfo *root, Node *expr)
{
/* No setup needed for tree walk, so away we go */
return replace_nestloop_params_mutator(expr, root);
}
static Node *
replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
{
if (node == NULL)
return NULL;
if (IsA(node, Var))
{
Var *var = (Var *) node;
/* Upper-level Vars should be long gone at this point */
Assert(var->varlevelsup == 0);
/* If not to be replaced, we can just return the Var unmodified */
if (IS_SPECIAL_VARNO(var->varno) ||
!bms_is_member(var->varno, root->curOuterRels))
return node;
/* Replace the Var with a nestloop Param */
return (Node *) replace_nestloop_param_var(root, var);
}
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
/* Upper-level PlaceHolderVars should be long gone at this point */
Assert(phv->phlevelsup == 0);
/* Check whether we need to replace the PHV */
if (!bms_is_subset(find_placeholder_info(root, phv)->ph_eval_at,
root->curOuterRels))
{
/*
* We can't replace the whole PHV, but we might still need to
* replace Vars or PHVs within its expression, in case it ends up
* actually getting evaluated here. (It might get evaluated in
* this plan node, or some child node; in the latter case we don't
* really need to process the expression here, but we haven't got
* enough info to tell if that's the case.) Flat-copy the PHV
* node and then recurse on its expression.
*
* Note that after doing this, we might have different
* representations of the contents of the same PHV in different
* parts of the plan tree. This is OK because equal() will just
* match on phid/phlevelsup, so setrefs.c will still recognize an
* upper-level reference to a lower-level copy of the same PHV.
*/
PlaceHolderVar *newphv = makeNode(PlaceHolderVar);
memcpy(newphv, phv, sizeof(PlaceHolderVar));
newphv->phexpr = (Expr *)
replace_nestloop_params_mutator((Node *) phv->phexpr,
root);
return (Node *) newphv;
}
/* Replace the PlaceHolderVar with a nestloop Param */
return (Node *) replace_nestloop_param_placeholdervar(root, phv);
}
return expression_tree_mutator(node, replace_nestloop_params_mutator, root);
}
/*
* fix_indexqual_references
* Adjust indexqual clauses to the form the executor's indexqual
* machinery needs.
*
* We have three tasks here:
* * Select the actual qual clauses out of the input IndexClause list,
* and remove RestrictInfo nodes from the qual clauses.
* * Replace any outer-relation Var or PHV nodes with nestloop Params.
* (XXX eventually, that responsibility should go elsewhere?)
* * Index keys must be represented by Var nodes with varattno set to the
* index's attribute number, not the attribute number in the original rel.
*
* *stripped_indexquals_p receives a list of the actual qual clauses.
*
* *fixed_indexquals_p receives a list of the adjusted quals. This is a copy
* that shares no substructure with the original; this is needed in case there
* are subplans in it (we need two separate copies of the subplan tree, or
* things will go awry).
*/
static void
fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
List **stripped_indexquals_p, List **fixed_indexquals_p)
{
IndexOptInfo *index = index_path->indexinfo;
List *stripped_indexquals;
List *fixed_indexquals;
ListCell *lc;
stripped_indexquals = fixed_indexquals = NIL;
foreach(lc, index_path->indexclauses)
{
IndexClause *iclause = lfirst_node(IndexClause, lc);
int indexcol = iclause->indexcol;
ListCell *lc2;
foreach(lc2, iclause->indexquals)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2);
Node *clause = (Node *) rinfo->clause;
stripped_indexquals = lappend(stripped_indexquals, clause);
clause = fix_indexqual_clause(root, index, indexcol,
clause, iclause->indexcols);
fixed_indexquals = lappend(fixed_indexquals, clause);
}
}
*stripped_indexquals_p = stripped_indexquals;
*fixed_indexquals_p = fixed_indexquals;
}
/*
* fix_indexorderby_references
* Adjust indexorderby clauses to the form the executor's index
* machinery needs.
*
* This is a simplified version of fix_indexqual_references. The input is
* bare clauses and a separate indexcol list, instead of IndexClauses.
*/
static List *
fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path)
{
IndexOptInfo *index = index_path->indexinfo;
List *fixed_indexorderbys;
ListCell *lcc,
*lci;
fixed_indexorderbys = NIL;
forboth(lcc, index_path->indexorderbys, lci, index_path->indexorderbycols)
{
Node *clause = (Node *) lfirst(lcc);
int indexcol = lfirst_int(lci);
clause = fix_indexqual_clause(root, index, indexcol, clause, NIL);
fixed_indexorderbys = lappend(fixed_indexorderbys, clause);
}
return fixed_indexorderbys;
}
/*
* fix_indexqual_clause
* Convert a single indexqual clause to the form needed by the executor.
*
* We replace nestloop params here, and replace the index key variables
* or expressions by index Var nodes.
*/
static Node *
fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol,
Node *clause, List *indexcolnos)
{
/*
* Replace any outer-relation variables with nestloop params.
*
* This also makes a copy of the clause, so it's safe to modify it
* in-place below.
*/
clause = replace_nestloop_params(root, clause);
if (IsA(clause, OpExpr))
{
OpExpr *op = (OpExpr *) clause;
/* Replace the indexkey expression with an index Var. */
linitial(op->args) = fix_indexqual_operand(linitial(op->args),
index,
indexcol);
}
else if (IsA(clause, RowCompareExpr))
{
RowCompareExpr *rc = (RowCompareExpr *) clause;
ListCell *lca,
*lcai;
/* Replace the indexkey expressions with index Vars. */
Assert(list_length(rc->largs) == list_length(indexcolnos));
forboth(lca, rc->largs, lcai, indexcolnos)
{
lfirst(lca) = fix_indexqual_operand(lfirst(lca),
index,
lfirst_int(lcai));
}
}
else if (IsA(clause, ScalarArrayOpExpr))
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
/* Replace the indexkey expression with an index Var. */
linitial(saop->args) = fix_indexqual_operand(linitial(saop->args),
index,
indexcol);
}
else if (IsA(clause, NullTest))
{
NullTest *nt = (NullTest *) clause;
/* Replace the indexkey expression with an index Var. */
nt->arg = (Expr *) fix_indexqual_operand((Node *) nt->arg,
index,
indexcol);
}
else
elog(ERROR, "unsupported indexqual type: %d",
(int) nodeTag(clause));
return clause;
}
/*
* fix_indexqual_operand
* Convert an indexqual expression to a Var referencing the index column.
*
* We represent index keys by Var nodes having varno == INDEX_VAR and varattno
* equal to the index's attribute number (index column position).
*
* Most of the code here is just for sanity cross-checking that the given
* expression actually matches the index column it's claimed to. It should
* match the logic in match_index_to_operand().
*/
static Node *
fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
{
Var *result;
int pos;
ListCell *indexpr_item;
Assert(indexcol >= 0 && indexcol < index->ncolumns);
/*
* Remove any PlaceHolderVar wrapping of the indexkey
*/
node = strip_noop_phvs(node);
/*
* Remove any binary-compatible relabeling of the indexkey
*/
while (IsA(node, RelabelType))
node = (Node *) ((RelabelType *) node)->arg;
if (index->indexkeys[indexcol] != 0)
{
/* It's a simple index column */
if (IsA(node, Var) &&
((Var *) node)->varno == index->rel->relid &&
((Var *) node)->varattno == index->indexkeys[indexcol])
{
result = (Var *) copyObject(node);
result->varno = INDEX_VAR;
result->varattno = indexcol + 1;
return (Node *) result;
}
else
elog(ERROR, "index key does not match expected index column");
}
/* It's an index expression, so find and cross-check the expression */
indexpr_item = list_head(index->indexprsExpand);
for (pos = 0; pos < index->ncolumns; pos++)
{
if (index->indexkeys[pos] == 0)
{
if (indexpr_item == NULL)
elog(ERROR, "too few entries in indexprs list");
if (pos == indexcol)
{
Node *indexkey;
indexkey = (Node *) lfirst(indexpr_item);
if (indexkey && IsA(indexkey, RelabelType))
indexkey = (Node *) ((RelabelType *) indexkey)->arg;
if (equal(node, indexkey))
{
result = makeVar(INDEX_VAR, indexcol + 1,
exprType(lfirst(indexpr_item)), -1,
exprCollation(lfirst(indexpr_item)),
0);
return (Node *) result;
}
else
elog(ERROR, "index key does not match expected index column");
}
indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
/* Oops... */
elog(ERROR, "index key does not match expected index column");
return NULL; /* keep compiler quiet */
}
/*
* get_switched_clauses
* Given a list of merge or hash joinclauses (as RestrictInfo nodes),
* extract the bare clauses, and rearrange the elements within the
* clauses, if needed, so the outer join variable is on the left and
* the inner is on the right. The original clause data structure is not
* touched; a modified list is returned. We do, however, set the transient
* outer_is_left field in each RestrictInfo to show which side was which.
*/
static List *
get_switched_clauses(List *clauses, Relids outerrelids)
{
List *t_list = NIL;
ListCell *l;
foreach(l, clauses)
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
OpExpr *clause = (OpExpr *) restrictinfo->clause;
Assert(is_opclause(clause));
if (bms_is_subset(restrictinfo->right_relids, outerrelids))
{
/*
* Duplicate just enough of the structure to allow commuting the
* clause without changing the original list. Could use
* copyObject, but a complete deep copy is overkill.
*/
OpExpr *temp = makeNode(OpExpr);
temp->opno = clause->opno;
temp->opfuncid = InvalidOid;
temp->opresulttype = clause->opresulttype;
temp->opretset = clause->opretset;
temp->opcollid = clause->opcollid;
temp->inputcollid = clause->inputcollid;
temp->args = list_copy(clause->args);
temp->location = clause->location;
/* Commute it --- note this modifies the temp node in-place. */
CommuteOpExpr(temp);
t_list = lappend(t_list, temp);
restrictinfo->outer_is_left = false;
}
else
{
Assert(bms_is_subset(restrictinfo->left_relids, outerrelids));
t_list = lappend(t_list, clause);
restrictinfo->outer_is_left = true;
}
}
return t_list;
}
/*
* order_qual_clauses
* Given a list of qual clauses that will all be evaluated at the same
* plan node, sort the list into the order we want to check the quals
* in at runtime.
*
* When security barrier quals are used in the query, we may have quals with
* different security levels in the list. Quals of lower security_level
* must go before quals of higher security_level, except that we can grant
* exceptions to move up quals that are leakproof. When security level
* doesn't force the decision, we prefer to order clauses by estimated
* execution cost, cheapest first.
*
* Ideally the order should be driven by a combination of execution cost and
* selectivity, but it's not immediately clear how to account for both,
* and given the uncertainty of the estimates the reliability of the decisions
* would be doubtful anyway. So we just order by security level then
* estimated per-tuple cost, being careful not to change the order when
* (as is often the case) the estimates are identical.
*
* Although this will work on either bare clauses or RestrictInfos, it's
* much faster to apply it to RestrictInfos, since it can re-use cost
* information that is cached in RestrictInfos. XXX in the bare-clause
* case, we are also not able to apply security considerations. That is
* all right for the moment, because the bare-clause case doesn't occur
* anywhere that barrier quals could be present, but it would be better to
* get rid of it.
*
* Note: some callers pass lists that contain entries that will later be
* removed; this is the easiest way to let this routine see RestrictInfos
* instead of bare clauses. This is another reason why trying to consider
* selectivity in the ordering would likely do the wrong thing.
*/
static List *
order_qual_clauses(PlannerInfo *root, List *clauses)
{
typedef struct
{
Node *clause;
Cost cost;
Index security_level;
} QualItem;
int nitems = list_length(clauses);
QualItem *items;
ListCell *lc;
int i;
List *result;
/* No need to work hard for 0 or 1 clause */
if (nitems <= 1)
return clauses;
/*
* Collect the items and costs into an array. This is to avoid repeated
* cost_qual_eval work if the inputs aren't RestrictInfos.
*/
items = (QualItem *) palloc(nitems * sizeof(QualItem));
i = 0;
foreach(lc, clauses)
{
Node *clause = (Node *) lfirst(lc);
QualCost qcost;
cost_qual_eval_node(&qcost, clause, root);
items[i].clause = clause;
items[i].cost = qcost.per_tuple;
if (IsA(clause, RestrictInfo))
{
RestrictInfo *rinfo = (RestrictInfo *) clause;
/*
* If a clause is leakproof, it doesn't have to be constrained by
* its nominal security level. If it's also reasonably cheap
* (here defined as 10X cpu_operator_cost), pretend it has
* security_level 0, which will allow it to go in front of
* more-expensive quals of lower security levels. Of course, that
* will also force it to go in front of cheaper quals of its own
* security level, which is not so great, but we can alleviate
* that risk by applying the cost limit cutoff.
*/
if (rinfo->leakproof && items[i].cost < 10 * cpu_operator_cost)
items[i].security_level = 0;
else
items[i].security_level = rinfo->security_level;
}
else
items[i].security_level = 0;
i++;
}
/*
* Sort. We don't use qsort() because it's not guaranteed stable for
* equal keys. The expected number of entries is small enough that a
* simple insertion sort should be good enough.
*/
for (i = 1; i < nitems; i++)
{
QualItem newitem = items[i];
int j;
/* insert newitem into the already-sorted subarray */
for (j = i; j > 0; j--)
{
QualItem *olditem = &items[j - 1];
if (newitem.security_level > olditem->security_level ||
(newitem.security_level == olditem->security_level &&
newitem.cost >= olditem->cost))
break;
items[j] = *olditem;
}
items[j] = newitem;
}
/* Convert back to a list */
result = NIL;
for (i = 0; i < nitems; i++)
result = lappend(result, items[i].clause);
return result;
}
/*
* Copy cost and size info from a Path node to the Plan node created from it.
* The executor usually won't use this info, but it's needed by EXPLAIN.
* Also copy the parallel-related flags, which the executor *will* use.
*/
static void
copy_generic_path_info(Plan *dest, Path *src)
{
dest->disabled_nodes = src->disabled_nodes;
dest->startup_cost = src->startup_cost;
dest->total_cost = src->total_cost;
dest->plan_rows = src->rows;
dest->plan_width = src->pathtarget->width;
dest->parallel_aware = src->parallel_aware;
dest->parallel_safe = src->parallel_safe;
}
/*
* Copy cost and size info from a lower plan node to an inserted node.
* (Most callers alter the info after copying it.)
*/
static void
copy_plan_costsize(Plan *dest, Plan *src)
{
dest->disabled_nodes = src->disabled_nodes;
dest->startup_cost = src->startup_cost;
dest->total_cost = src->total_cost;
dest->plan_rows = src->plan_rows;
dest->plan_width = src->plan_width;
/* Assume the inserted node is not parallel-aware. */
dest->parallel_aware = false;
/* Assume the inserted node is parallel-safe, if child plan is. */
dest->parallel_safe = src->parallel_safe;
}
/*
* Some places in this file build Sort nodes that don't have a directly
* corresponding Path node. The cost of the sort is, or should have been,
* included in the cost of the Path node we're working from, but since it's
* not split out, we have to re-figure it using cost_sort(). This is just
* to label the Sort node nicely for EXPLAIN.
*
* limit_tuples is as for cost_sort (in particular, pass -1 if no limit)
*/
static void
label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples)
{
Plan *lefttree = plan->plan.lefttree;
Path sort_path; /* dummy for result of cost_sort */
Assert(IsA(plan, Sort));
cost_sort(&sort_path, root, NIL,
plan->plan.disabled_nodes,
lefttree->total_cost,
lefttree->plan_rows,
lefttree->plan_width,
0.0,
work_mem,
limit_tuples);
plan->plan.startup_cost = sort_path.startup_cost;
plan->plan.total_cost = sort_path.total_cost;
plan->plan.plan_rows = lefttree->plan_rows;
plan->plan.plan_width = lefttree->plan_width;
plan->plan.parallel_aware = false;
plan->plan.parallel_safe = lefttree->parallel_safe;
}
/*
* Same as label_sort_with_costsize, but labels the IncrementalSort node
* instead.
*/
static void
label_incrementalsort_with_costsize(PlannerInfo *root, IncrementalSort *plan,
List *pathkeys, double limit_tuples)
{
Plan *lefttree = plan->sort.plan.lefttree;
Path sort_path; /* dummy for result of cost_incremental_sort */
Assert(IsA(plan, IncrementalSort));
cost_incremental_sort(&sort_path, root, pathkeys,
plan->nPresortedCols,
plan->sort.plan.disabled_nodes,
lefttree->startup_cost,
lefttree->total_cost,
lefttree->plan_rows,
lefttree->plan_width,
0.0,
work_mem,
limit_tuples);
plan->sort.plan.startup_cost = sort_path.startup_cost;
plan->sort.plan.total_cost = sort_path.total_cost;
plan->sort.plan.plan_rows = lefttree->plan_rows;
plan->sort.plan.plan_width = lefttree->plan_width;
plan->sort.plan.parallel_aware = false;
plan->sort.plan.parallel_safe = lefttree->parallel_safe;
}
/*
* bitmap_subplan_mark_shared
* Set isshared flag in bitmap subplan so that it will be created in
* shared memory.
*/
static void
bitmap_subplan_mark_shared(Plan *plan)
{
if (IsA(plan, BitmapAnd))
bitmap_subplan_mark_shared(linitial(((BitmapAnd *) plan)->bitmapplans));
else if (IsA(plan, BitmapOr))
{
((BitmapOr *) plan)->isshared = true;
bitmap_subplan_mark_shared(linitial(((BitmapOr *) plan)->bitmapplans));
}
else if (IsA(plan, BitmapIndexScan))
((BitmapIndexScan *) plan)->isshared = true;
else
elog(ERROR, "unrecognized node type: %d", nodeTag(plan));
}
/*****************************************************************************
*
* PLAN NODE BUILDING ROUTINES
*
* In general, these functions are not passed the original Path and therefore
* leave it to the caller to fill in the cost/width fields from the Path,
* typically by calling copy_generic_path_info(). This convention is
* somewhat historical, but it does support a few places above where we build
* a plan node without having an exactly corresponding Path node. Under no
* circumstances should one of these functions do its own cost calculations,
* as that would be redundant with calculations done while building Paths.
*
*****************************************************************************/
static SeqScan *
make_seqscan(List *qptlist,
List *qpqual,
Index scanrelid)
{
SeqScan *node = makeNode(SeqScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
return node;
}
static SampleScan *
make_samplescan(List *qptlist,
List *qpqual,
Index scanrelid,
TableSampleClause *tsc)
{
SampleScan *node = makeNode(SampleScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->tablesample = tsc;
return node;
}
static IndexScan *
make_indexscan(List *qptlist,
List *qpqual,
Index scanrelid,
Oid indexid,
List *indexqual,
List *indexqualorig,
List *indexorderby,
List *indexorderbyorig,
List *indexorderbyops,
ScanDirection indexscandir)
{
IndexScan *node = makeNode(IndexScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->indexid = indexid;
node->indexqual = indexqual;
node->indexqualorig = indexqualorig;
node->indexorderby = indexorderby;
node->indexorderbyorig = indexorderbyorig;
node->indexorderbyops = indexorderbyops;
node->indexorderdir = indexscandir;
return node;
}
static IndexOnlyScan *
make_indexonlyscan(List *qptlist,
List *qpqual,
Index scanrelid,
Oid indexid,
List *indexqual,
List *recheckqual,
List *indexorderby,
List *indextlist,
ScanDirection indexscandir)
{
IndexOnlyScan *node = makeNode(IndexOnlyScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->indexid = indexid;
node->indexqual = indexqual;
node->recheckqual = recheckqual;
node->indexorderby = indexorderby;
node->indextlist = indextlist;
node->indexorderdir = indexscandir;
return node;
}
static BitmapIndexScan *
make_bitmap_indexscan(Index scanrelid,
Oid indexid,
List *indexqual,
List *indexqualorig)
{
BitmapIndexScan *node = makeNode(BitmapIndexScan);
Plan *plan = &node->scan.plan;
plan->targetlist = NIL; /* not used */
plan->qual = NIL; /* not used */
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->indexid = indexid;
node->indexqual = indexqual;
node->indexqualorig = indexqualorig;
return node;
}
static BitmapHeapScan *
make_bitmap_heapscan(List *qptlist,
List *qpqual,
Plan *lefttree,
List *bitmapqualorig,
Index scanrelid)
{
BitmapHeapScan *node = makeNode(BitmapHeapScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->bitmapqualorig = bitmapqualorig;
return node;
}
static TidScan *
make_tidscan(List *qptlist,
List *qpqual,
Index scanrelid,
List *tidquals)
{
TidScan *node = makeNode(TidScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->tidquals = tidquals;
return node;
}
static TidRangeScan *
make_tidrangescan(List *qptlist,
List *qpqual,
Index scanrelid,
List *tidrangequals)
{
TidRangeScan *node = makeNode(TidRangeScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->tidrangequals = tidrangequals;
return node;
}
static SubqueryScan *
make_subqueryscan(List *qptlist,
List *qpqual,
Index scanrelid,
Plan *subplan)
{
SubqueryScan *node = makeNode(SubqueryScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->subplan = subplan;
node->scanstatus = SUBQUERY_SCAN_UNKNOWN;
return node;
}
static FunctionScan *
make_functionscan(List *qptlist,
List *qpqual,
Index scanrelid,
List *functions,
bool funcordinality)
{
FunctionScan *node = makeNode(FunctionScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->functions = functions;
node->funcordinality = funcordinality;
return node;
}
static TableFuncScan *
make_tablefuncscan(List *qptlist,
List *qpqual,
Index scanrelid,
TableFunc *tablefunc)
{
TableFuncScan *node = makeNode(TableFuncScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->tablefunc = tablefunc;
return node;
}
static ValuesScan *
make_valuesscan(List *qptlist,
List *qpqual,
Index scanrelid,
List *values_lists)
{
ValuesScan *node = makeNode(ValuesScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->values_lists = values_lists;
return node;
}
static CteScan *
make_ctescan(List *qptlist,
List *qpqual,
Index scanrelid,
int ctePlanId,
int cteParam)
{
CteScan *node = makeNode(CteScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->ctePlanId = ctePlanId;
node->cteParam = cteParam;
return node;
}
static NamedTuplestoreScan *
make_namedtuplestorescan(List *qptlist,
List *qpqual,
Index scanrelid,
char *enrname)
{
NamedTuplestoreScan *node = makeNode(NamedTuplestoreScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->enrname = enrname;
return node;
}
static WorkTableScan *
make_worktablescan(List *qptlist,
List *qpqual,
Index scanrelid,
int wtParam)
{
WorkTableScan *node = makeNode(WorkTableScan);
Plan *plan = &node->scan.plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->wtParam = wtParam;
return node;
}
ForeignScan *
make_foreignscan(List *qptlist,
List *qpqual,
Index scanrelid,
List *fdw_exprs,
List *fdw_private,
List *fdw_scan_tlist,
List *fdw_recheck_quals,
Plan *outer_plan)
{
ForeignScan *node = makeNode(ForeignScan);
Plan *plan = &node->scan.plan;
/* cost will be filled in by create_foreignscan_plan */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = outer_plan;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
/* these may be overridden by the FDW's PlanDirectModify callback. */
node->operation = CMD_SELECT;
node->resultRelation = 0;
/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
node->checkAsUser = InvalidOid;
node->fs_server = InvalidOid;
node->fdw_exprs = fdw_exprs;
node->fdw_private = fdw_private;
node->fdw_scan_tlist = fdw_scan_tlist;
node->fdw_recheck_quals = fdw_recheck_quals;
/* fs_relids, fs_base_relids will be filled by create_foreignscan_plan */
node->fs_relids = NULL;
node->fs_base_relids = NULL;
/* fsSystemCol will be filled in by create_foreignscan_plan */
node->fsSystemCol = false;
return node;
}
static RecursiveUnion *
make_recursive_union(List *tlist,
Plan *lefttree,
Plan *righttree,
int wtParam,
List *distinctList,
Cardinality numGroups)
{
RecursiveUnion *node = makeNode(RecursiveUnion);
Plan *plan = &node->plan;
int numCols = list_length(distinctList);
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->wtParam = wtParam;
/*
* convert SortGroupClause list into arrays of attr indexes and equality
* operators, as wanted by executor
*/
node->numCols = numCols;
if (numCols > 0)
{
int keyno = 0;
AttrNumber *dupColIdx;
Oid *dupOperators;
Oid *dupCollations;
ListCell *slitem;
dupColIdx = palloc_array(AttrNumber, numCols);
dupOperators = palloc_array(Oid, numCols);
dupCollations = palloc_array(Oid, numCols);
foreach(slitem, distinctList)
{
SortGroupClause *sortcl = (SortGroupClause *) lfirst(slitem);
TargetEntry *tle = get_sortgroupclause_tle(sortcl,
plan->targetlist);
dupColIdx[keyno] = tle->resno;
dupOperators[keyno] = sortcl->eqop;
dupCollations[keyno] = exprCollation((Node *) tle->expr);
Assert(OidIsValid(dupOperators[keyno]));
keyno++;
}
node->dupColIdx = dupColIdx;
node->dupOperators = dupOperators;
node->dupCollations = dupCollations;
}
node->numGroups = numGroups;
return node;
}
static BitmapAnd *
make_bitmap_and(List *bitmapplans)
{
BitmapAnd *node = makeNode(BitmapAnd);
Plan *plan = &node->plan;
plan->targetlist = NIL;
plan->qual = NIL;
plan->lefttree = NULL;
plan->righttree = NULL;
node->bitmapplans = bitmapplans;
return node;
}
static BitmapOr *
make_bitmap_or(List *bitmapplans)
{
BitmapOr *node = makeNode(BitmapOr);
Plan *plan = &node->plan;
plan->targetlist = NIL;
plan->qual = NIL;
plan->lefttree = NULL;
plan->righttree = NULL;
node->bitmapplans = bitmapplans;
return node;
}
static NestLoop *
make_nestloop(List *tlist,
List *joinclauses,
List *otherclauses,
List *nestParams,
Plan *lefttree,
Plan *righttree,
JoinType jointype,
bool inner_unique)
{
NestLoop *node = makeNode(NestLoop);
Plan *plan = &node->join.plan;
plan->targetlist = tlist;
plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->join.jointype = jointype;
node->join.inner_unique = inner_unique;
node->join.joinqual = joinclauses;
node->nestParams = nestParams;
return node;
}
static HashJoin *
make_hashjoin(List *tlist,
List *joinclauses,
List *otherclauses,
List *hashclauses,
List *hashoperators,
List *hashcollations,
List *hashkeys,
Plan *lefttree,
Plan *righttree,
JoinType jointype,
bool inner_unique)
{
HashJoin *node = makeNode(HashJoin);
Plan *plan = &node->join.plan;
plan->targetlist = tlist;
plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->hashclauses = hashclauses;
node->hashoperators = hashoperators;
node->hashcollations = hashcollations;
node->hashkeys = hashkeys;
node->join.jointype = jointype;
node->join.inner_unique = inner_unique;
node->join.joinqual = joinclauses;
return node;
}
static Hash *
make_hash(Plan *lefttree,
List *hashkeys,
Oid skewTable,
AttrNumber skewColumn,
bool skewInherit)
{
Hash *node = makeNode(Hash);
Plan *plan = &node->plan;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->hashkeys = hashkeys;
node->skewTable = skewTable;
node->skewColumn = skewColumn;
node->skewInherit = skewInherit;
return node;
}
static MergeJoin *
make_mergejoin(List *tlist,
List *joinclauses,
List *otherclauses,
List *mergeclauses,
Oid *mergefamilies,
Oid *mergecollations,
bool *mergereversals,
bool *mergenullsfirst,
Plan *lefttree,
Plan *righttree,
JoinType jointype,
bool inner_unique,
bool skip_mark_restore)
{
MergeJoin *node = makeNode(MergeJoin);
Plan *plan = &node->join.plan;
plan->targetlist = tlist;
plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->skip_mark_restore = skip_mark_restore;
node->mergeclauses = mergeclauses;
node->mergeFamilies = mergefamilies;
node->mergeCollations = mergecollations;
node->mergeReversals = mergereversals;
node->mergeNullsFirst = mergenullsfirst;
node->join.jointype = jointype;
node->join.inner_unique = inner_unique;
node->join.joinqual = joinclauses;
return node;
}
/*
* make_sort --- basic routine to build a Sort plan node
*
* Caller must have built the sortColIdx, sortOperators, collations, and
* nullsFirst arrays already.
*/
static Sort *
make_sort(Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators,
Oid *collations, bool *nullsFirst)
{
Sort *node;
Plan *plan;
node = makeNode(Sort);
plan = &node->plan;
plan->targetlist = lefttree->targetlist;
plan->disabled_nodes = lefttree->disabled_nodes + (enable_sort == false);
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->numCols = numCols;
node->sortColIdx = sortColIdx;
node->sortOperators = sortOperators;
node->collations = collations;
node->nullsFirst = nullsFirst;
return node;
}
/*
* make_incrementalsort --- basic routine to build an IncrementalSort plan node
*
* Caller must have built the sortColIdx, sortOperators, collations, and
* nullsFirst arrays already.
*/
static IncrementalSort *
make_incrementalsort(Plan *lefttree, int numCols, int nPresortedCols,
AttrNumber *sortColIdx, Oid *sortOperators,
Oid *collations, bool *nullsFirst)
{
IncrementalSort *node;
Plan *plan;
node = makeNode(IncrementalSort);
plan = &node->sort.plan;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->nPresortedCols = nPresortedCols;
node->sort.numCols = numCols;
node->sort.sortColIdx = sortColIdx;
node->sort.sortOperators = sortOperators;
node->sort.collations = collations;
node->sort.nullsFirst = nullsFirst;
return node;
}
/*
* prepare_sort_from_pathkeys
* Prepare to sort according to given pathkeys
*
* This is used to set up for Sort, MergeAppend, and Gather Merge nodes. It
* calculates the executor's representation of the sort key information, and
* adjusts the plan targetlist if needed to add resjunk sort columns.
*
* Input parameters:
* 'lefttree' is the plan node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
* 'relids' identifies the child relation being sorted, if any
* 'reqColIdx' is NULL or an array of required sort key column numbers
* 'adjust_tlist_in_place' is true if lefttree must be modified in-place
*
* We must convert the pathkey information into arrays of sort key column
* numbers, sort operator OIDs, collation OIDs, and nulls-first flags,
* which is the representation the executor wants. These are returned into
* the output parameters *p_numsortkeys etc.
*
* When looking for matches to an EquivalenceClass's members, we will only
* consider child EC members if they belong to given 'relids'. This protects
* against possible incorrect matches to child expressions that contain no
* Vars.
*
* If reqColIdx isn't NULL then it contains sort key column numbers that
* we should match. This is used when making child plans for a MergeAppend;
* it's an error if we can't match the columns.
*
* If the pathkeys include expressions that aren't simple Vars, we will
* usually need to add resjunk items to the input plan's targetlist to
* compute these expressions, since a Sort or MergeAppend node itself won't
* do any such calculations. If the input plan type isn't one that can do
* projections, this means adding a Result node just to do the projection.
* However, the caller can pass adjust_tlist_in_place = true to force the
* lefttree tlist to be modified in-place regardless of whether the node type
* can project --- we use this for fixing the tlist of MergeAppend itself.
*
* Returns the node which is to be the input to the Sort (either lefttree,
* or a Result stacked atop lefttree).
*/
static Plan *
prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Relids relids,
const AttrNumber *reqColIdx,
bool adjust_tlist_in_place,
int *p_numsortkeys,
AttrNumber **p_sortColIdx,
Oid **p_sortOperators,
Oid **p_collations,
bool **p_nullsFirst)
{
List *tlist = lefttree->targetlist;
ListCell *i;
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
Oid *collations;
bool *nullsFirst;
/*
* We will need at most list_length(pathkeys) sort columns; possibly less
*/
numsortkeys = list_length(pathkeys);
sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
collations = (Oid *) palloc(numsortkeys * sizeof(Oid));
nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));
numsortkeys = 0;
foreach(i, pathkeys)
{
PathKey *pathkey = (PathKey *) lfirst(i);
EquivalenceClass *ec = pathkey->pk_eclass;
EquivalenceMember *em;
TargetEntry *tle = NULL;
Oid pk_datatype = InvalidOid;
Oid sortop;
ListCell *j;
if (ec->ec_has_volatile)
{
/*
* If the pathkey's EquivalenceClass is volatile, then it must
* have come from an ORDER BY clause, and we have to match it to
* that same targetlist entry.
*/
if (ec->ec_sortref == 0) /* can't happen */
elog(ERROR, "volatile EquivalenceClass has no sortref");
tle = get_sortgroupref_tle(ec->ec_sortref, tlist);
Assert(tle);
Assert(list_length(ec->ec_members) == 1);
pk_datatype = ((EquivalenceMember *) linitial(ec->ec_members))->em_datatype;
}
else if (reqColIdx != NULL)
{
/*
* If we are given a sort column number to match, only consider
* the single TLE at that position. It's possible that there is
* no such TLE, in which case fall through and generate a resjunk
* targetentry (we assume this must have happened in the parent
* plan as well). If there is a TLE but it doesn't match the
* pathkey's EC, we do the same, which is probably the wrong thing
* but we'll leave it to caller to complain about the mismatch.
*/
tle = get_tle_by_resno(tlist, reqColIdx[numsortkeys]);
if (tle)
{
em = find_ec_member_matching_expr(ec, tle->expr, relids);
if (em)
{
/* found expr at right place in tlist */
pk_datatype = em->em_datatype;
}
else
tle = NULL;
}
}
else
{
/*
* Otherwise, we can sort by any non-constant expression listed in
* the pathkey's EquivalenceClass. For now, we take the first
* tlist item found in the EC. If there's no match, we'll generate
* a resjunk entry using the first EC member that is an expression
* in the input's vars.
*
* XXX if we have a choice, is there any way of figuring out which
* might be cheapest to execute? (For example, int4lt is likely
* much cheaper to execute than numericlt, but both might appear
* in the same equivalence class...) Not clear that we ever will
* have an interesting choice in practice, so it may not matter.
*/
foreach(j, tlist)
{
tle = (TargetEntry *) lfirst(j);
em = find_ec_member_matching_expr(ec, tle->expr, relids);
if (em)
{
/* found expr already in tlist */
pk_datatype = em->em_datatype;
break;
}
tle = NULL;
}
}
if (!tle)
{
/*
* No matching tlist item; look for a computable expression.
*/
em = find_computable_ec_member(NULL, ec, tlist, relids, false);
if (!em)
elog(ERROR, "could not find pathkey item to sort");
pk_datatype = em->em_datatype;
/*
* Do we need to insert a Result node?
*/
if (!adjust_tlist_in_place &&
!is_projection_capable_plan(lefttree))
{
/* copy needed so we don't modify input's tlist below */
tlist = copyObject(tlist);
lefttree = inject_projection_plan(lefttree, tlist,
lefttree->parallel_safe);
}
/* Don't bother testing is_projection_capable_plan again */
adjust_tlist_in_place = true;
/*
* Add resjunk entry to input's tlist
*/
tle = makeTargetEntry(copyObject(em->em_expr),
list_length(tlist) + 1,
NULL,
true);
tlist = lappend(tlist, tle);
lefttree->targetlist = tlist; /* just in case NIL before */
}
/*
* Look up the correct sort operator from the PathKey's slightly
* abstracted representation.
*/
sortop = get_opfamily_member_for_cmptype(pathkey->pk_opfamily,
pk_datatype,
pk_datatype,
pathkey->pk_cmptype);
if (!OidIsValid(sortop)) /* should not happen */
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
pathkey->pk_cmptype, pk_datatype, pk_datatype,
pathkey->pk_opfamily);
/* Add the column to the sort arrays */
sortColIdx[numsortkeys] = tle->resno;
sortOperators[numsortkeys] = sortop;
collations[numsortkeys] = ec->ec_collation;
nullsFirst[numsortkeys] = pathkey->pk_nulls_first;
numsortkeys++;
}
/* Return results */
*p_numsortkeys = numsortkeys;
*p_sortColIdx = sortColIdx;
*p_sortOperators = sortOperators;
*p_collations = collations;
*p_nullsFirst = nullsFirst;
return lefttree;
}
/*
* make_sort_from_pathkeys
* Create sort plan to sort according to given pathkeys
*
* 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
* 'relids' is the set of relations required by prepare_sort_from_pathkeys()
*/
static Sort *
make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
{
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
Oid *collations;
bool *nullsFirst;
/* Compute sort column info, and adjust lefttree as needed */
lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
relids,
NULL,
false,
&numsortkeys,
&sortColIdx,
&sortOperators,
&collations,
&nullsFirst);
/* Now build the Sort node */
return make_sort(lefttree, numsortkeys,
sortColIdx, sortOperators,
collations, nullsFirst);
}
/*
* make_incrementalsort_from_pathkeys
* Create sort plan to sort according to given pathkeys
*
* 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
* 'relids' is the set of relations required by prepare_sort_from_pathkeys()
* 'nPresortedCols' is the number of presorted columns in input tuples
*/
static IncrementalSort *
make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
Relids relids, int nPresortedCols)
{
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
Oid *collations;
bool *nullsFirst;
/* Compute sort column info, and adjust lefttree as needed */
lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
relids,
NULL,
false,
&numsortkeys,
&sortColIdx,
&sortOperators,
&collations,
&nullsFirst);
/* Now build the Sort node */
return make_incrementalsort(lefttree, numsortkeys, nPresortedCols,
sortColIdx, sortOperators,
collations, nullsFirst);
}
/*
* make_sort_from_sortclauses
* Create sort plan to sort according to given sortclauses
*
* 'sortcls' is a list of SortGroupClauses
* 'lefttree' is the node which yields input tuples
*/
Sort *
make_sort_from_sortclauses(List *sortcls, Plan *lefttree)
{
List *sub_tlist = lefttree->targetlist;
ListCell *l;
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
Oid *collations;
bool *nullsFirst;
/* Convert list-ish representation to arrays wanted by executor */
numsortkeys = list_length(sortcls);
sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
collations = (Oid *) palloc(numsortkeys * sizeof(Oid));
nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));
numsortkeys = 0;
foreach(l, sortcls)
{
SortGroupClause *sortcl = (SortGroupClause *) lfirst(l);
TargetEntry *tle = get_sortgroupclause_tle(sortcl, sub_tlist);
sortColIdx[numsortkeys] = tle->resno;
sortOperators[numsortkeys] = sortcl->sortop;
collations[numsortkeys] = exprCollation((Node *) tle->expr);
nullsFirst[numsortkeys] = sortcl->nulls_first;
numsortkeys++;
}
return make_sort(lefttree, numsortkeys,
sortColIdx, sortOperators,
collations, nullsFirst);
}
/*
* make_sort_from_groupcols
* Create sort plan to sort based on grouping columns
*
* 'groupcls' is the list of SortGroupClauses
* 'grpColIdx' gives the column numbers to use
*
* This might look like it could be merged with make_sort_from_sortclauses,
* but presently we *must* use the grpColIdx[] array to locate sort columns,
* because the child plan's tlist is not marked with ressortgroupref info
* appropriate to the grouping node. So, only the sort ordering info
* is used from the SortGroupClause entries.
*/
static Sort *
make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree)
{
List *sub_tlist = lefttree->targetlist;
ListCell *l;
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
Oid *collations;
bool *nullsFirst;
/* Convert list-ish representation to arrays wanted by executor */
numsortkeys = list_length(groupcls);
sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
collations = (Oid *) palloc(numsortkeys * sizeof(Oid));
nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));
numsortkeys = 0;
foreach(l, groupcls)
{
SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
TargetEntry *tle = get_tle_by_resno(sub_tlist, grpColIdx[numsortkeys]);
if (!tle)
elog(ERROR, "could not retrieve tle for sort-from-groupcols");
sortColIdx[numsortkeys] = tle->resno;
sortOperators[numsortkeys] = grpcl->sortop;
collations[numsortkeys] = exprCollation((Node *) tle->expr);
nullsFirst[numsortkeys] = grpcl->nulls_first;
numsortkeys++;
}
return make_sort(lefttree, numsortkeys,
sortColIdx, sortOperators,
collations, nullsFirst);
}
static Material *
make_material(Plan *lefttree)
{
Material *node = makeNode(Material);
Plan *plan = &node->plan;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
return node;
}
/*
* materialize_finished_plan: stick a Material node atop a completed plan
*
* There are a couple of places where we want to attach a Material node
* after completion of create_plan(), without any MaterialPath path.
* Those places should probably be refactored someday to do this on the
* Path representation, but it's not worth the trouble yet.
*/
Plan *
materialize_finished_plan(Plan *subplan)
{
Plan *matplan;
Path matpath; /* dummy for cost_material */
Cost initplan_cost;
bool unsafe_initplans;
matplan = (Plan *) make_material(subplan);
/*
* XXX horrid kluge: if there are any initPlans attached to the subplan,
* move them up to the Material node, which is now effectively the top
* plan node in its query level. This prevents failure in
* SS_finalize_plan(), which see for comments.
*/
matplan->initPlan = subplan->initPlan;
subplan->initPlan = NIL;
/* Move the initplans' cost delta, as well */
SS_compute_initplan_cost(matplan->initPlan,
&initplan_cost, &unsafe_initplans);
subplan->startup_cost -= initplan_cost;
subplan->total_cost -= initplan_cost;
/* Set cost data */
cost_material(&matpath,
enable_material,
subplan->disabled_nodes,
subplan->startup_cost,
subplan->total_cost,
subplan->plan_rows,
subplan->plan_width);
matplan->disabled_nodes = subplan->disabled_nodes;
matplan->startup_cost = matpath.startup_cost + initplan_cost;
matplan->total_cost = matpath.total_cost + initplan_cost;
matplan->plan_rows = subplan->plan_rows;
matplan->plan_width = subplan->plan_width;
matplan->parallel_aware = false;
matplan->parallel_safe = subplan->parallel_safe;
return matplan;
}
static Memoize *
make_memoize(Plan *lefttree, Oid *hashoperators, Oid *collations,
List *param_exprs, bool singlerow, bool binary_mode,
uint32 est_entries, Bitmapset *keyparamids,
Cardinality est_calls, Cardinality est_unique_keys,
double est_hit_ratio)
{
Memoize *node = makeNode(Memoize);
Plan *plan = &node->plan;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->numKeys = list_length(param_exprs);
node->hashOperators = hashoperators;
node->collations = collations;
node->param_exprs = param_exprs;
node->singlerow = singlerow;
node->binary_mode = binary_mode;
node->est_entries = est_entries;
node->keyparamids = keyparamids;
node->est_calls = est_calls;
node->est_unique_keys = est_unique_keys;
node->est_hit_ratio = est_hit_ratio;
return node;
}
Agg *
make_agg(List *tlist, List *qual,
AggStrategy aggstrategy, AggSplit aggsplit,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, Oid *grpCollations,
List *groupingSets, List *chain, Cardinality numGroups,
Size transitionSpace, Plan *lefttree)
{
Agg *node = makeNode(Agg);
Plan *plan = &node->plan;
node->aggstrategy = aggstrategy;
node->aggsplit = aggsplit;
node->numCols = numGroupCols;
node->grpColIdx = grpColIdx;
node->grpOperators = grpOperators;
node->grpCollations = grpCollations;
node->numGroups = numGroups;
node->transitionSpace = transitionSpace;
node->aggParams = NULL; /* SS_finalize_plan() will fill this */
node->groupingSets = groupingSets;
node->chain = chain;
plan->qual = qual;
plan->targetlist = tlist;
plan->lefttree = lefttree;
plan->righttree = NULL;
return node;
}
static WindowAgg *
make_windowagg(List *tlist, WindowClause *wc,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators, Oid *partCollations,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, Oid *ordCollations,
List *runCondition, List *qual, bool topWindow, Plan *lefttree)
{
WindowAgg *node = makeNode(WindowAgg);
Plan *plan = &node->plan;
node->winname = wc->name;
node->winref = wc->winref;
node->partNumCols = partNumCols;
node->partColIdx = partColIdx;
node->partOperators = partOperators;
node->partCollations = partCollations;
node->ordNumCols = ordNumCols;
node->ordColIdx = ordColIdx;
node->ordOperators = ordOperators;
node->ordCollations = ordCollations;
node->frameOptions = wc->frameOptions;
node->startOffset = wc->startOffset;
node->endOffset = wc->endOffset;
node->runCondition = runCondition;
/* a duplicate of the above for EXPLAIN */
node->runConditionOrig = runCondition;
node->startInRangeFunc = wc->startInRangeFunc;
node->endInRangeFunc = wc->endInRangeFunc;
node->inRangeColl = wc->inRangeColl;
node->inRangeAsc = wc->inRangeAsc;
node->inRangeNullsFirst = wc->inRangeNullsFirst;
node->topWindow = topWindow;
plan->targetlist = tlist;
plan->lefttree = lefttree;
plan->righttree = NULL;
plan->qual = qual;
return node;
}
static Group *
make_group(List *tlist,
List *qual,
int numGroupCols,
AttrNumber *grpColIdx,
Oid *grpOperators,
Oid *grpCollations,
Plan *lefttree)
{
Group *node = makeNode(Group);
Plan *plan = &node->plan;
node->numCols = numGroupCols;
node->grpColIdx = grpColIdx;
node->grpOperators = grpOperators;
node->grpCollations = grpCollations;
plan->qual = qual;
plan->targetlist = tlist;
plan->lefttree = lefttree;
plan->righttree = NULL;
return node;
}
/*
* pathkeys is a list of PathKeys, identifying the sort columns and semantics.
* The input plan must already be sorted accordingly.
*
* relids identifies the child relation being unique-ified, if any.
*/
static Unique *
make_unique_from_pathkeys(Plan *lefttree, List *pathkeys, int numCols,
Relids relids)
{
Unique *node = makeNode(Unique);
Plan *plan = &node->plan;
int keyno = 0;
AttrNumber *uniqColIdx;
Oid *uniqOperators;
Oid *uniqCollations;
ListCell *lc;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
/*
* Convert pathkeys list into arrays of attr indexes and equality
* operators, as wanted by executor. This has a lot in common with
* prepare_sort_from_pathkeys ... maybe unify sometime?
*/
Assert(numCols >= 0 && numCols <= list_length(pathkeys));
uniqColIdx = palloc_array(AttrNumber, numCols);
uniqOperators = palloc_array(Oid, numCols);
uniqCollations = palloc_array(Oid, numCols);
foreach(lc, pathkeys)
{
PathKey *pathkey = (PathKey *) lfirst(lc);
EquivalenceClass *ec = pathkey->pk_eclass;
EquivalenceMember *em;
TargetEntry *tle = NULL;
Oid pk_datatype = InvalidOid;
Oid eqop;
ListCell *j;
/* Ignore pathkeys beyond the specified number of columns */
if (keyno >= numCols)
break;
if (ec->ec_has_volatile)
{
/*
* If the pathkey's EquivalenceClass is volatile, then it must
* have come from an ORDER BY clause, and we have to match it to
* that same targetlist entry.
*/
if (ec->ec_sortref == 0) /* can't happen */
elog(ERROR, "volatile EquivalenceClass has no sortref");
tle = get_sortgroupref_tle(ec->ec_sortref, plan->targetlist);
Assert(tle);
Assert(list_length(ec->ec_members) == 1);
pk_datatype = ((EquivalenceMember *) linitial(ec->ec_members))->em_datatype;
}
else
{
/*
* Otherwise, we can use any non-constant expression listed in the
* pathkey's EquivalenceClass. For now, we take the first tlist
* item found in the EC.
*/
foreach(j, plan->targetlist)
{
tle = (TargetEntry *) lfirst(j);
em = find_ec_member_matching_expr(ec, tle->expr, relids);
if (em)
{
/* found expr already in tlist */
pk_datatype = em->em_datatype;
break;
}
tle = NULL;
}
}
if (!tle)
elog(ERROR, "could not find pathkey item to sort");
/*
* Look up the correct equality operator from the PathKey's slightly
* abstracted representation.
*/
eqop = get_opfamily_member_for_cmptype(pathkey->pk_opfamily,
pk_datatype,
pk_datatype,
COMPARE_EQ);
if (!OidIsValid(eqop)) /* should not happen */
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
COMPARE_EQ, pk_datatype, pk_datatype,
pathkey->pk_opfamily);
uniqColIdx[keyno] = tle->resno;
uniqOperators[keyno] = eqop;
uniqCollations[keyno] = ec->ec_collation;
keyno++;
}
node->numCols = numCols;
node->uniqColIdx = uniqColIdx;
node->uniqOperators = uniqOperators;
node->uniqCollations = uniqCollations;
return node;
}
static Gather *
make_gather(List *qptlist,
List *qpqual,
int nworkers,
int rescan_param,
bool single_copy,
Plan *subplan)
{
Gather *node = makeNode(Gather);
Plan *plan = &node->plan;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = subplan;
plan->righttree = NULL;
node->num_workers = nworkers;
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
node->initParam = NULL;
return node;
}
/*
* groupList is a list of SortGroupClauses, identifying the targetlist
* items that should be considered by the SetOp filter. The input plans must
* already be sorted accordingly, if we're doing SETOP_SORTED mode.
*/
static SetOp *
make_setop(SetOpCmd cmd, SetOpStrategy strategy,
List *tlist, Plan *lefttree, Plan *righttree,
List *groupList, Cardinality numGroups)
{
SetOp *node = makeNode(SetOp);
Plan *plan = &node->plan;
int numCols = list_length(groupList);
int keyno = 0;
AttrNumber *cmpColIdx;
Oid *cmpOperators;
Oid *cmpCollations;
bool *cmpNullsFirst;
ListCell *slitem;
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = righttree;
/*
* convert SortGroupClause list into arrays of attr indexes and comparison
* operators, as wanted by executor
*/
cmpColIdx = palloc_array(AttrNumber, numCols);
cmpOperators = palloc_array(Oid, numCols);
cmpCollations = palloc_array(Oid, numCols);
cmpNullsFirst = palloc_array(bool, numCols);
foreach(slitem, groupList)
{
SortGroupClause *sortcl = (SortGroupClause *) lfirst(slitem);
TargetEntry *tle = get_sortgroupclause_tle(sortcl, plan->targetlist);
cmpColIdx[keyno] = tle->resno;
if (strategy == SETOP_HASHED)
cmpOperators[keyno] = sortcl->eqop;
else
cmpOperators[keyno] = sortcl->sortop;
Assert(OidIsValid(cmpOperators[keyno]));
cmpCollations[keyno] = exprCollation((Node *) tle->expr);
cmpNullsFirst[keyno] = sortcl->nulls_first;
keyno++;
}
node->cmd = cmd;
node->strategy = strategy;
node->numCols = numCols;
node->cmpColIdx = cmpColIdx;
node->cmpOperators = cmpOperators;
node->cmpCollations = cmpCollations;
node->cmpNullsFirst = cmpNullsFirst;
node->numGroups = numGroups;
return node;
}
/*
* make_lockrows
* Build a LockRows plan node
*/
static LockRows *
make_lockrows(Plan *lefttree, List *rowMarks, int epqParam)
{
LockRows *node = makeNode(LockRows);
Plan *plan = &node->plan;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->rowMarks = rowMarks;
node->epqParam = epqParam;
return node;
}
/*
* make_limit
* Build a Limit plan node
*/
Limit *
make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount,
LimitOption limitOption, int uniqNumCols, AttrNumber *uniqColIdx,
Oid *uniqOperators, Oid *uniqCollations)
{
Limit *node = makeNode(Limit);
Plan *plan = &node->plan;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->limitOffset = limitOffset;
node->limitCount = limitCount;
node->limitOption = limitOption;
node->uniqNumCols = uniqNumCols;
node->uniqColIdx = uniqColIdx;
node->uniqOperators = uniqOperators;
node->uniqCollations = uniqCollations;
return node;
}
/*
* make_gating_result
* Build a Result plan node that performs projection of a subplan, and/or
* applies a one time filter (resconstantqual)
*/
static Result *
make_gating_result(List *tlist,
Node *resconstantqual,
Plan *subplan)
{
Result *node = makeNode(Result);
Plan *plan = &node->plan;
Assert(subplan != NULL);
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = subplan;
plan->righttree = NULL;
node->result_type = RESULT_TYPE_GATING;
node->resconstantqual = resconstantqual;
node->relids = NULL;
return node;
}
/*
* make_one_row_result
* Build a Result plan node that returns a single row (or possibly no rows,
* if the one-time filtered defined by resconstantqual returns false)
*
* 'rel' should be this path's RelOptInfo. In essence, we're saying that this
* Result node generates all the tuples for that RelOptInfo. Note that the same
* consideration can never arise in make_gating_result(), because in that case
* the tuples are always coming from some subordinate node.
*/
static Result *
make_one_row_result(List *tlist,
Node *resconstantqual,
RelOptInfo *rel)
{
Result *node = makeNode(Result);
Plan *plan = &node->plan;
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = NULL;
plan->righttree = NULL;
node->result_type = IS_UPPER_REL(rel) ? RESULT_TYPE_UPPER :
IS_JOIN_REL(rel) ? RESULT_TYPE_JOIN : RESULT_TYPE_SCAN;
node->resconstantqual = resconstantqual;
node->relids = rel->relids;
return node;
}
/*
* make_project_set
* Build a ProjectSet plan node
*/
static ProjectSet *
make_project_set(List *tlist,
Plan *subplan)
{
ProjectSet *node = makeNode(ProjectSet);
Plan *plan = &node->plan;
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = subplan;
plan->righttree = NULL;
return node;
}
/*
* make_modifytable
* Build a ModifyTable plan node
*/
static ModifyTable *
make_modifytable(PlannerInfo *root, Plan *subplan,
CmdType operation, bool canSetTag,
Index nominalRelation, Index rootRelation,
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
List *mergeActionLists, List *mergeJoinConditions,
ForPortionOfExpr *forPortionOf, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
bool returning_old_or_new = false;
bool returning_old_or_new_valid = false;
bool transition_tables = false;
bool transition_tables_valid = false;
List *fdw_private_list;
Bitmapset *direct_modify_plans;
ListCell *lc;
int i;
Assert(operation == CMD_MERGE ||
(operation == CMD_UPDATE ?
list_length(resultRelations) == list_length(updateColnosLists) :
updateColnosLists == NIL));
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
list_length(resultRelations) == list_length(returningLists));
node->plan.lefttree = subplan;
node->plan.righttree = NULL;
node->plan.qual = NIL;
/* setrefs.c will fill in the targetlist, if needed */
node->plan.targetlist = NIL;
node->operation = operation;
node->canSetTag = canSetTag;
node->nominalRelation = nominalRelation;
node->rootRelation = rootRelation;
node->resultRelations = resultRelations;
if (!onconflict)
{
node->onConflictAction = ONCONFLICT_NONE;
node->onConflictLockStrength = LCS_NONE;
node->onConflictSet = NIL;
node->onConflictCols = NIL;
node->onConflictWhere = NULL;
node->arbiterIndexes = NIL;
node->exclRelRTI = 0;
node->exclRelTlist = NIL;
}
else
{
node->onConflictAction = onconflict->action;
/* Lock strength for ON CONFLICT DO SELECT [FOR UPDATE/SHARE] */
node->onConflictLockStrength = onconflict->lockStrength;
/*
* Here we convert the ON CONFLICT UPDATE tlist, if any, to the
* executor's convention of having consecutive resno's. The actual
* target column numbers are saved in node->onConflictCols. (This
* could be done earlier, but there seems no need to.)
*/
node->onConflictSet = onconflict->onConflictSet;
node->onConflictCols =
extract_update_targetlist_colnos(node->onConflictSet);
node->onConflictWhere = onconflict->onConflictWhere;
/*
* If a set of unique index inference elements was provided (an
* INSERT...ON CONFLICT "inference specification"), then infer
* appropriate unique indexes (or throw an error if none are
* available).
*/
node->arbiterIndexes = infer_arbiter_indexes(root);
node->exclRelRTI = onconflict->exclRelIndex;
node->exclRelTlist = onconflict->exclRelTlist;
}
node->updateColnosLists = updateColnosLists;
node->forPortionOf = (Node *) forPortionOf;
node->withCheckOptionLists = withCheckOptionLists;
node->returningOldAlias = root->parse->returningOldAlias;
node->returningNewAlias = root->parse->returningNewAlias;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
node->mergeActionLists = mergeActionLists;
node->mergeJoinConditions = mergeJoinConditions;
node->epqParam = epqParam;
/*
* For each result relation that is a foreign table, allow the FDW to
* construct private plan data, and accumulate it all into a list.
*/
fdw_private_list = NIL;
direct_modify_plans = NULL;
i = 0;
foreach(lc, resultRelations)
{
Index rti = lfirst_int(lc);
FdwRoutine *fdwroutine;
List *fdw_private;
bool direct_modify;
/*
* If possible, we want to get the FdwRoutine from our RelOptInfo for
* the table. But sometimes we don't have a RelOptInfo and must get
* it the hard way. (In INSERT, the target relation is not scanned,
* so it's not a baserel; and there are also corner cases for
* updatable views where the target rel isn't a baserel.)
*/
if (rti < root->simple_rel_array_size &&
root->simple_rel_array[rti] != NULL)
{
RelOptInfo *resultRel = root->simple_rel_array[rti];
fdwroutine = resultRel->fdwroutine;
}
else
{
RangeTblEntry *rte = planner_rt_fetch(rti, root);
if (rte->rtekind == RTE_RELATION &&
rte->relkind == RELKIND_FOREIGN_TABLE)
{
/* Check if the access to foreign tables is restricted */
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
{
/* there must not be built-in foreign tables */
Assert(rte->relid >= FirstNormalObjectId);
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("access to non-system foreign table is restricted")));
}
fdwroutine = GetFdwRoutineByRelId(rte->relid);
}
else
fdwroutine = NULL;
}
/*
* MERGE is not currently supported for foreign tables. We already
* checked that when the table mentioned in the query is foreign; but
* we can still get here if a partitioned table has a foreign table as
* partition. Disallow that now, to avoid an uglier error message
* later.
*/
if (operation == CMD_MERGE && fdwroutine != NULL)
{
RangeTblEntry *rte = planner_rt_fetch(rti, root);
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot execute MERGE on relation \"%s\"",
get_rel_name(rte->relid)),
errdetail_relkind_not_supported(rte->relkind));
}
/*
* Try to modify the foreign table directly if (1) the FDW provides
* callback functions needed for that and (2) there are no local
* structures that need to be run for each modified row: row-level
* triggers on the foreign table, stored generated columns, WITH CHECK
* OPTIONs from parent views, Vars returning OLD/NEW in the RETURNING
* list, or transition tables on the named relation.
*/
direct_modify = false;
if (fdwroutine != NULL &&
fdwroutine->PlanDirectModify != NULL &&
fdwroutine->BeginDirectModify != NULL &&
fdwroutine->IterateDirectModify != NULL &&
fdwroutine->EndDirectModify != NULL &&
withCheckOptionLists == NIL &&
!has_row_triggers(root, rti, operation) &&
!has_stored_generated_columns(root, rti))
{
/*
* returning_old_or_new and transition_tables are the same for all
* result relations, respectively
*/
if (!returning_old_or_new_valid)
{
returning_old_or_new =
contain_vars_returning_old_or_new((Node *)
root->parse->returningList);
returning_old_or_new_valid = true;
}
if (!returning_old_or_new)
{
if (!transition_tables_valid)
{
transition_tables = has_transition_tables(root,
nominalRelation,
operation);
transition_tables_valid = true;
}
if (!transition_tables)
direct_modify = fdwroutine->PlanDirectModify(root, node,
rti, i);
}
}
if (direct_modify)
direct_modify_plans = bms_add_member(direct_modify_plans, i);
if (!direct_modify &&
fdwroutine != NULL &&
fdwroutine->PlanForeignModify != NULL)
fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
else
fdw_private = NIL;
fdw_private_list = lappend(fdw_private_list, fdw_private);
i++;
}
node->fdwPrivLists = fdw_private_list;
node->fdwDirectModifyPlans = direct_modify_plans;
return node;
}
/*
* is_projection_capable_path
* Check whether a given Path node is able to do projection.
*/
bool
is_projection_capable_path(Path *path)
{
/* Most plan types can project, so just list the ones that can't */
switch (path->pathtype)
{
case T_Hash:
case T_Material:
case T_Memoize:
case T_Sort:
case T_IncrementalSort:
case T_Unique:
case T_SetOp:
case T_LockRows:
case T_Limit:
case T_ModifyTable:
case T_MergeAppend:
case T_RecursiveUnion:
return false;
case T_CustomScan:
if (castNode(CustomPath, path)->flags & CUSTOMPATH_SUPPORT_PROJECTION)
return true;
return false;
case T_Append:
/*
* Append can't project, but if an AppendPath is being used to
* represent a dummy path, what will actually be generated is a
* Result which can project.
*/
return IS_DUMMY_APPEND(path);
case T_ProjectSet:
/*
* Although ProjectSet certainly projects, say "no" because we
* don't want the planner to randomly replace its tlist with
* something else; the SRFs have to stay at top level. This might
* get relaxed later.
*/
return false;
default:
break;
}
return true;
}
/*
* is_projection_capable_plan
* Check whether a given Plan node is able to do projection.
*/
bool
is_projection_capable_plan(Plan *plan)
{
/* Most plan types can project, so just list the ones that can't */
switch (nodeTag(plan))
{
case T_Hash:
case T_Material:
case T_Memoize:
case T_Sort:
case T_Unique:
case T_SetOp:
case T_LockRows:
case T_Limit:
case T_ModifyTable:
case T_Append:
case T_MergeAppend:
case T_RecursiveUnion:
return false;
case T_CustomScan:
if (((CustomScan *) plan)->flags & CUSTOMPATH_SUPPORT_PROJECTION)
return true;
return false;
case T_ProjectSet:
/*
* Although ProjectSet certainly projects, say "no" because we
* don't want the planner to randomly replace its tlist with
* something else; the SRFs have to stay at top level. This might
* get relaxed later.
*/
return false;
default:
break;
}
return true;
}
./execIndexing.c 0000664 0001750 0001750 00000114544 15221603750 012475 0 ustar xman xman /*-------------------------------------------------------------------------
*
* execIndexing.c
* routines for inserting index tuples and enforcing unique and
* exclusion constraints.
*
* ExecInsertIndexTuples() is the main entry point. It's called after
* inserting a tuple to the heap, and it inserts corresponding index tuples
* into all indexes. At the same time, it enforces any unique and
* exclusion constraints:
*
* Unique Indexes
* --------------
*
* Enforcing a unique constraint is straightforward. When the index AM
* inserts the tuple to the index, it also checks that there are no
* conflicting tuples in the index already. It does so atomically, so that
* even if two backends try to insert the same key concurrently, only one
* of them will succeed. All the logic to ensure atomicity, and to wait
* for in-progress transactions to finish, is handled by the index AM.
*
* If a unique constraint is deferred, we request the index AM to not
* throw an error if a conflict is found. Instead, we make note that there
* was a conflict and return the list of indexes with conflicts to the
* caller. The caller must re-check them later, by calling index_insert()
* with the UNIQUE_CHECK_EXISTING option.
*
* Exclusion Constraints
* ---------------------
*
* Exclusion constraints are different from unique indexes in that when the
* tuple is inserted to the index, the index AM does not check for
* duplicate keys at the same time. After the insertion, we perform a
* separate scan on the index to check for conflicting tuples, and if one
* is found, we throw an error and the transaction is aborted. If the
* conflicting tuple's inserter or deleter is in-progress, we wait for it
* to finish first.
*
* There is a chance of deadlock, if two backends insert a tuple at the
* same time, and then perform the scan to check for conflicts. They will
* find each other's tuple, and both try to wait for each other. The
* deadlock detector will detect that, and abort one of the transactions.
* That's fairly harmless, as one of them was bound to abort with a
* "duplicate key error" anyway, although you get a different error
* message.
*
* If an exclusion constraint is deferred, we still perform the conflict
* checking scan immediately after inserting the index tuple. But instead
* of throwing an error if a conflict is found, we return that information
* to the caller. The caller must re-check them later by calling
* check_exclusion_constraint().
*
* Speculative insertion
* ---------------------
*
* Speculative insertion is a two-phase mechanism used to implement
* INSERT ... ON CONFLICT. The tuple is first inserted into the heap
* and the indexes are updated as usual, but if a constraint is violated,
* we can still back out of the insertion without aborting the whole
* transaction. In an INSERT ... ON CONFLICT statement, if a conflict is
* detected, the inserted tuple is backed out and the ON CONFLICT action is
* executed instead.
*
* Insertion to a unique index works as usual: the index AM checks for
* duplicate keys atomically with the insertion. But instead of throwing
* an error on a conflict, the speculatively inserted heap tuple is backed
* out.
*
* Exclusion constraints are slightly more complicated. As mentioned
* earlier, there is a risk of deadlock when two backends insert the same
* key concurrently. That was not a problem for regular insertions, when
* one of the transactions has to be aborted anyway, but with a speculative
* insertion we cannot let a deadlock happen, because we only want to back
* out the speculatively inserted tuple on conflict, not abort the whole
* transaction.
*
* When a backend detects that the speculative insertion conflicts with
* another in-progress tuple, it has two options:
*
* 1. back out the speculatively inserted tuple, then wait for the other
* transaction, and retry. Or,
* 2. wait for the other transaction, with the speculatively inserted tuple
* still in place.
*
* If two backends insert at the same time, and both try to wait for each
* other, they will deadlock. So option 2 is not acceptable. Option 1
* avoids the deadlock, but it is prone to a livelock instead. Both
* transactions will wake up immediately as the other transaction backs
* out. Then they both retry, and conflict with each other again, lather,
* rinse, repeat.
*
* To avoid the livelock, one of the backends must back out first, and then
* wait, while the other one waits without backing out. It doesn't matter
* which one backs out, so we employ an arbitrary rule that the transaction
* with the higher XID backs out.
*
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/executor/execIndexing.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/relscan.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/index.h"
#include "executor/executor.h"
#include "nodes/nodeFuncs.h"
#include "storage/lmgr.h"
#include "utils/injection_point.h"
#include "utils/lsyscache.h"
#include "utils/multirangetypes.h"
#include "utils/rangetypes.h"
#include "utils/snapmgr.h"
/* waitMode argument to check_exclusion_or_unique_constraint() */
typedef enum
{
CEOUC_WAIT,
CEOUC_NOWAIT,
CEOUC_LIVELOCK_PREVENTING_WAIT,
} CEOUC_WAIT_MODE;
static bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
IndexInfo *indexInfo,
const ItemPointerData *tupleid,
const Datum *values, const bool *isnull,
EState *estate, bool newIndex,
CEOUC_WAIT_MODE waitMode,
bool violationOK,
ItemPointer conflictTid);
static bool index_recheck_constraint(Relation index, const Oid *constr_procs,
const Datum *existing_values, const bool *existing_isnull,
const Datum *new_values);
static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
EState *estate, IndexInfo *indexInfo,
Relation indexRelation);
static bool index_expression_changed_walker(Node *node,
Bitmapset *allUpdatedCols);
static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
char typtype, Oid atttypid);
/* ----------------------------------------------------------------
* ExecOpenIndices
*
* Find the indices associated with a result relation, open them,
* and save information about them in the result ResultRelInfo.
*
* At entry, caller has already opened and locked
* resultRelInfo->ri_RelationDesc.
* ----------------------------------------------------------------
*/
void
ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
{
Relation resultRelation = resultRelInfo->ri_RelationDesc;
List *indexoidlist;
ListCell *l;
int len,
i;
RelationPtr relationDescs;
IndexInfo **indexInfoArray;
resultRelInfo->ri_NumIndices = 0;
/* fast path if no indexes */
if (!RelationGetForm(resultRelation)->relhasindex)
return;
/*
* Get cached list of index OIDs
*/
indexoidlist = RelationGetIndexList(resultRelation);
len = list_length(indexoidlist);
if (len == 0)
return;
/* This Assert will fail if ExecOpenIndices is called twice */
Assert(resultRelInfo->ri_IndexRelationDescs == NULL);
/*
* allocate space for result arrays
*/
relationDescs = palloc_array(Relation, len);
indexInfoArray = palloc_array(IndexInfo *, len);
resultRelInfo->ri_NumIndices = len;
resultRelInfo->ri_IndexRelationDescs = relationDescs;
resultRelInfo->ri_IndexRelationInfo = indexInfoArray;
/*
* For each index, open the index relation and save pg_index info. We
* acquire RowExclusiveLock, signifying we will update the index.
*
* Note: we do this even if the index is not indisready; it's not worth
* the trouble to optimize for the case where it isn't.
*/
i = 0;
foreach(l, indexoidlist)
{
Oid indexOid = lfirst_oid(l);
Relation indexDesc;
IndexInfo *ii;
indexDesc = index_open(indexOid, RowExclusiveLock);
/* extract index key information from the index's pg_index info */
ii = BuildIndexInfo(indexDesc);
/*
* If the indexes are to be used for speculative insertion, add extra
* information required by unique index entries.
*/
if (speculative && ii->ii_Unique && !indexDesc->rd_index->indisexclusion)
BuildSpeculativeIndexInfo(indexDesc, ii);
relationDescs[i] = indexDesc;
indexInfoArray[i] = ii;
i++;
}
list_free(indexoidlist);
}
/* ----------------------------------------------------------------
* ExecCloseIndices
*
* Close the index relations stored in resultRelInfo
* ----------------------------------------------------------------
*/
void
ExecCloseIndices(ResultRelInfo *resultRelInfo)
{
int i;
int numIndices;
RelationPtr indexDescs;
IndexInfo **indexInfos;
numIndices = resultRelInfo->ri_NumIndices;
indexDescs = resultRelInfo->ri_IndexRelationDescs;
indexInfos = resultRelInfo->ri_IndexRelationInfo;
for (i = 0; i < numIndices; i++)
{
/* This Assert will fail if ExecCloseIndices is called twice */
Assert(indexDescs[i] != NULL);
/* Give the index a chance to do some post-insert cleanup */
index_insert_cleanup(indexDescs[i], indexInfos[i]);
/* Drop lock acquired by ExecOpenIndices */
index_close(indexDescs[i], RowExclusiveLock);
/* Mark the index as closed */
indexDescs[i] = NULL;
}
/*
* We don't attempt to free the IndexInfo data structures or the arrays,
* instead assuming that such stuff will be cleaned up automatically in
* FreeExecutorState.
*/
}
/* ----------------------------------------------------------------
* ExecInsertIndexTuples
*
* This routine takes care of inserting index tuples
* into all the relations indexing the result relation
* when a heap tuple is inserted into the result relation.
*
* When EIIT_IS_UPDATE is set and EIIT_ONLY_SUMMARIZING isn't,
* executor is performing an UPDATE that could not use an
* optimization like heapam's HOT (in more general terms a
* call to table_tuple_update() took place and set
* 'update_indexes' to TU_All). Receiving this hint makes
* us consider if we should pass down the 'indexUnchanged'
* hint in turn. That's something that we figure out for
* each index_insert() call iff EIIT_IS_UPDATE is set.
* (When that flag is not set we already know not to pass the
* hint to any index.)
*
* If EIIT_ONLY_SUMMARIZING is set, an equivalent optimization to
* HOT has been applied and any updated columns are indexed
* only by summarizing indexes (or in more general terms a
* call to table_tuple_update() took place and set
* 'update_indexes' to TU_Summarizing). We can (and must)
* therefore only update the indexes that have
* 'amsummarizing' = true.
*
* Unique and exclusion constraints are enforced at the same
* time. This returns a list of index OIDs for any unique or
* exclusion constraints that are deferred and that had
* potential (unconfirmed) conflicts. (if EIIT_NO_DUPE_ERROR,
* the same is done for non-deferred constraints, but report
* if conflict was speculative or deferred conflict to caller)
*
* If 'arbiterIndexes' is nonempty, EIIT_NO_DUPE_ERROR applies only to
* those indexes. NIL means EIIT_NO_DUPE_ERROR applies to all indexes.
* ----------------------------------------------------------------
*/
List *
ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
EState *estate,
uint32 flags,
TupleTableSlot *slot,
List *arbiterIndexes,
bool *specConflict)
{
ItemPointer tupleid = &slot->tts_tid;
List *result = NIL;
int i;
int numIndices;
RelationPtr relationDescs;
Relation heapRelation;
IndexInfo **indexInfoArray;
ExprContext *econtext;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
Assert(ItemPointerIsValid(tupleid));
/*
* Get information from the result relation info structure.
*/
numIndices = resultRelInfo->ri_NumIndices;
relationDescs = resultRelInfo->ri_IndexRelationDescs;
indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
heapRelation = resultRelInfo->ri_RelationDesc;
/* Sanity check: slot must belong to the same rel as the resultRelInfo. */
Assert(slot->tts_tableOid == RelationGetRelid(heapRelation));
/*
* We will use the EState's per-tuple context for evaluating predicates
* and index expressions (creating it if it's not already there).
*/
econtext = GetPerTupleExprContext(estate);
/* Arrange for econtext's scan tuple to be the tuple under test */
econtext->ecxt_scantuple = slot;
/*
* for each index, form and insert the index tuple
*/
for (i = 0; i < numIndices; i++)
{
Relation indexRelation = relationDescs[i];
IndexInfo *indexInfo;
bool applyNoDupErr;
IndexUniqueCheck checkUnique;
bool indexUnchanged;
bool satisfiesConstraint;
if (indexRelation == NULL)
continue;
indexInfo = indexInfoArray[i];
/* If the index is marked as read-only, ignore it */
if (!indexInfo->ii_ReadyForInserts)
continue;
/*
* Skip processing of non-summarizing indexes if we only update
* summarizing indexes
*/
if ((flags & EIIT_ONLY_SUMMARIZING) && !indexInfo->ii_Summarizing)
continue;
/* Check for partial index */
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
if (!ExecQual(predicateExpand, econtext))
continue;
}
/*
* FormIndexDatum fills in its values and isnull parameters with the
* appropriate values for the column(s) of the index.
*/
FormIndexDatum(indexInfo,
slot,
estate,
values,
isnull);
/* Check whether to apply noDupErr to this index */
applyNoDupErr = (flags & EIIT_NO_DUPE_ERROR) &&
(arbiterIndexes == NIL ||
list_member_oid(arbiterIndexes,
indexRelation->rd_index->indexrelid));
/*
* The index AM does the actual insertion, plus uniqueness checking.
*
* For an immediate-mode unique index, we just tell the index AM to
* throw error if not unique.
*
* For a deferrable unique index, we tell the index AM to just detect
* possible non-uniqueness, and we add the index OID to the result
* list if further checking is needed.
*
* For a speculative insertion (used by INSERT ... ON CONFLICT), do
* the same as for a deferrable unique index.
*/
if (!indexRelation->rd_index->indisunique)
checkUnique = UNIQUE_CHECK_NO;
else if (applyNoDupErr)
checkUnique = UNIQUE_CHECK_PARTIAL;
else if (indexRelation->rd_index->indimmediate)
checkUnique = UNIQUE_CHECK_YES;
else
checkUnique = UNIQUE_CHECK_PARTIAL;
/*
* There's definitely going to be an index_insert() call for this
* index. If we're being called as part of an UPDATE statement,
* consider if the 'indexUnchanged' = true hint should be passed.
*/
indexUnchanged = ((flags & EIIT_IS_UPDATE) &&
index_unchanged_by_update(resultRelInfo,
estate,
indexInfo,
indexRelation));
satisfiesConstraint =
index_insert(indexRelation, /* index relation */
values, /* array of index Datums */
isnull, /* null flags */
tupleid, /* tid of heap tuple */
heapRelation, /* heap relation */
checkUnique, /* type of uniqueness check to do */
indexUnchanged, /* UPDATE without logical change? */
indexInfo); /* index AM may need this */
/*
* If the index has an associated exclusion constraint, check that.
* This is simpler than the process for uniqueness checks since we
* always insert first and then check. If the constraint is deferred,
* we check now anyway, but don't throw error on violation or wait for
* a conclusive outcome from a concurrent insertion; instead we'll
* queue a recheck event. Similarly, noDupErr callers (speculative
* inserters) will recheck later, and wait for a conclusive outcome
* then.
*
* An index for an exclusion constraint can't also be UNIQUE (not an
* essential property, we just don't allow it in the grammar), so no
* need to preserve the prior state of satisfiesConstraint.
*/
if (indexInfo->ii_ExclusionOps != NULL)
{
bool violationOK;
CEOUC_WAIT_MODE waitMode;
if (applyNoDupErr)
{
violationOK = true;
waitMode = CEOUC_LIVELOCK_PREVENTING_WAIT;
}
else if (!indexRelation->rd_index->indimmediate)
{
violationOK = true;
waitMode = CEOUC_NOWAIT;
}
else
{
violationOK = false;
waitMode = CEOUC_WAIT;
}
satisfiesConstraint =
check_exclusion_or_unique_constraint(heapRelation,
indexRelation, indexInfo,
tupleid, values, isnull,
estate, false,
waitMode, violationOK, NULL);
}
if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
indexInfo->ii_ExclusionOps != NULL) &&
!satisfiesConstraint)
{
/*
* The tuple potentially violates the uniqueness or exclusion
* constraint, so make a note of the index so that we can re-check
* it later. Speculative inserters are told if there was a
* speculative conflict, since that always requires a restart.
*/
result = lappend_oid(result, RelationGetRelid(indexRelation));
if (indexRelation->rd_index->indimmediate && specConflict)
*specConflict = true;
}
}
return result;
}
/* ----------------------------------------------------------------
* ExecCheckIndexConstraints
*
* This routine checks if a tuple violates any unique or
* exclusion constraints. Returns true if there is no conflict.
* Otherwise returns false, and the TID of the conflicting
* tuple is returned in *conflictTid.
*
* If 'arbiterIndexes' is given, only those indexes are checked.
* NIL means all indexes.
*
* Note that this doesn't lock the values in any way, so it's
* possible that a conflicting tuple is inserted immediately
* after this returns. This can be used for either a pre-check
* before insertion or a re-check after finding a conflict.
*
* 'tupleid' should be the TID of the tuple that has been recently
* inserted (or can be invalid if we haven't inserted a new tuple yet).
* This tuple will be excluded from conflict checking.
* ----------------------------------------------------------------
*/
bool
ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
EState *estate, ItemPointer conflictTid,
const ItemPointerData *tupleid, List *arbiterIndexes)
{
int i;
int numIndices;
RelationPtr relationDescs;
Relation heapRelation;
IndexInfo **indexInfoArray;
ExprContext *econtext;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
ItemPointerData invalidItemPtr;
bool checkedIndex = false;
ItemPointerSetInvalid(conflictTid);
ItemPointerSetInvalid(&invalidItemPtr);
/*
* Get information from the result relation info structure.
*/
numIndices = resultRelInfo->ri_NumIndices;
relationDescs = resultRelInfo->ri_IndexRelationDescs;
indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
heapRelation = resultRelInfo->ri_RelationDesc;
/*
* We will use the EState's per-tuple context for evaluating predicates
* and index expressions (creating it if it's not already there).
*/
econtext = GetPerTupleExprContext(estate);
/* Arrange for econtext's scan tuple to be the tuple under test */
econtext->ecxt_scantuple = slot;
/*
* For each index, form index tuple and check if it satisfies the
* constraint.
*/
for (i = 0; i < numIndices; i++)
{
Relation indexRelation = relationDescs[i];
IndexInfo *indexInfo;
bool satisfiesConstraint;
if (indexRelation == NULL)
continue;
indexInfo = indexInfoArray[i];
if (!indexInfo->ii_Unique && !indexInfo->ii_ExclusionOps)
continue;
/* If the index is marked as read-only, ignore it */
if (!indexInfo->ii_ReadyForInserts)
continue;
/* When specific arbiter indexes requested, only examine them */
if (arbiterIndexes != NIL &&
!list_member_oid(arbiterIndexes,
indexRelation->rd_index->indexrelid))
continue;
if (!indexRelation->rd_index->indimmediate)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("ON CONFLICT does not support deferrable unique constraints/exclusion constraints as arbiters"),
errtableconstraint(heapRelation,
RelationGetRelationName(indexRelation))));
checkedIndex = true;
/* Check for partial index */
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
if (!ExecQual(predicateExpand, econtext))
continue;
}
/*
* FormIndexDatum fills in its values and isnull parameters with the
* appropriate values for the column(s) of the index.
*/
FormIndexDatum(indexInfo,
slot,
estate,
values,
isnull);
satisfiesConstraint =
check_exclusion_or_unique_constraint(heapRelation, indexRelation,
indexInfo, tupleid,
values, isnull, estate, false,
CEOUC_WAIT, true,
conflictTid);
if (!satisfiesConstraint)
return false;
}
if (arbiterIndexes != NIL && !checkedIndex)
elog(ERROR, "unexpected failure to find arbiter index");
return true;
}
/*
* Check for violation of an exclusion or unique constraint
*
* heap: the table containing the new tuple
* index: the index supporting the constraint
* indexInfo: info about the index, including the exclusion properties
* tupleid: heap TID of the new tuple we have just inserted (invalid if we
* haven't inserted a new tuple yet)
* values, isnull: the *index* column values computed for the new tuple
* estate: an EState we can do evaluation in
* newIndex: if true, we are trying to build a new index (this affects
* only the wording of error messages)
* waitMode: whether to wait for concurrent inserters/deleters
* violationOK: if true, don't throw error for violation
* conflictTid: if not-NULL, the TID of the conflicting tuple is returned here
*
* Returns true if OK, false if actual or potential violation
*
* 'waitMode' determines what happens if a conflict is detected with a tuple
* that was inserted or deleted by a transaction that's still running.
* CEOUC_WAIT means that we wait for the transaction to commit, before
* throwing an error or returning. CEOUC_NOWAIT means that we report the
* violation immediately; so the violation is only potential, and the caller
* must recheck sometime later. This behavior is convenient for deferred
* exclusion checks; we need not bother queuing a deferred event if there is
* definitely no conflict at insertion time.
*
* CEOUC_LIVELOCK_PREVENTING_WAIT is like CEOUC_NOWAIT, but we will sometimes
* wait anyway, to prevent livelocking if two transactions try inserting at
* the same time. This is used with speculative insertions, for INSERT ON
* CONFLICT statements. (See notes in file header)
*
* If violationOK is true, we just report the potential or actual violation to
* the caller by returning 'false'. Otherwise we throw a descriptive error
* message here. When violationOK is false, a false result is impossible.
*
* Note: The indexam is normally responsible for checking unique constraints,
* so this normally only needs to be used for exclusion constraints. But this
* function is also called when doing a "pre-check" for conflicts on a unique
* constraint, when doing speculative insertion. Caller may use the returned
* conflict TID to take further steps.
*/
static bool
check_exclusion_or_unique_constraint(Relation heap, Relation index,
IndexInfo *indexInfo,
const ItemPointerData *tupleid,
const Datum *values, const bool *isnull,
EState *estate, bool newIndex,
CEOUC_WAIT_MODE waitMode,
bool violationOK,
ItemPointer conflictTid)
{
Oid *constr_procs;
uint16 *constr_strats;
Oid *index_collations = index->rd_indcollation;
int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
IndexScanDesc index_scan;
ScanKeyData scankeys[INDEX_MAX_KEYS];
SnapshotData DirtySnapshot;
int i;
bool conflict;
bool found_self;
ExprContext *econtext;
TupleTableSlot *existing_slot;
TupleTableSlot *save_scantuple;
if (indexInfo->ii_ExclusionOps)
{
constr_procs = indexInfo->ii_ExclusionProcs;
constr_strats = indexInfo->ii_ExclusionStrats;
}
else
{
constr_procs = indexInfo->ii_UniqueProcs;
constr_strats = indexInfo->ii_UniqueStrats;
}
/*
* If this is a WITHOUT OVERLAPS constraint, we must also forbid empty
* ranges/multiranges. This must happen before we look for NULLs below, or
* a UNIQUE constraint could insert an empty range along with a NULL
* scalar part.
*/
if (indexInfo->ii_WithoutOverlaps)
{
/*
* Look up the type from the heap tuple, but check the Datum from the
* index tuple.
*/
AttrNumber attno = indexInfo->ii_IndexAttrNumbers[indnkeyatts - 1];
if (!isnull[indnkeyatts - 1])
{
TupleDesc tupdesc = RelationGetDescr(heap);
Form_pg_attribute att = TupleDescAttr(tupdesc, attno - 1);
TypeCacheEntry *typcache = lookup_type_cache(att->atttypid,
TYPECACHE_DOMAIN_BASE_INFO);
char typtype;
if (OidIsValid(typcache->domainBaseType))
typtype = get_typtype(typcache->domainBaseType);
else
typtype = typcache->typtype;
ExecWithoutOverlapsNotEmpty(heap, att->attname,
values[indnkeyatts - 1],
typtype, att->atttypid);
}
}
/*
* If any of the input values are NULL, and the index uses the default
* nulls-are-distinct mode, the constraint check is assumed to pass (i.e.,
* we assume the operators are strict). Otherwise, we interpret the
* constraint as specifying IS NULL for each column whose input value is
* NULL.
*/
if (!indexInfo->ii_NullsNotDistinct)
{
for (i = 0; i < indnkeyatts; i++)
{
if (isnull[i])
return true;
}
}
/*
* Search the tuples that are in the index for any violations, including
* tuples that aren't visible yet.
*/
InitDirtySnapshot(DirtySnapshot);
for (i = 0; i < indnkeyatts; i++)
{
ScanKeyEntryInitialize(&scankeys[i],
isnull[i] ? SK_ISNULL | SK_SEARCHNULL : 0,
i + 1,
constr_strats[i],
InvalidOid,
index_collations[i],
constr_procs[i],
values[i]);
}
/*
* Need a TupleTableSlot to put existing tuples in.
*
* To use FormIndexDatum, we have to make the econtext's scantuple point
* to this slot. Be sure to save and restore caller's value for
* scantuple.
*/
existing_slot = table_slot_create(heap, NULL);
econtext = GetPerTupleExprContext(estate);
save_scantuple = econtext->ecxt_scantuple;
econtext->ecxt_scantuple = existing_slot;
/*
* May have to restart scan from this point if a potential conflict is
* found.
*/
retry:
conflict = false;
found_self = false;
index_scan = index_beginscan(heap, index,
&DirtySnapshot, NULL, indnkeyatts, 0,
SO_NONE);
index_rescan(index_scan, scankeys, indnkeyatts, NULL, 0);
while (index_getnext_slot(index_scan, ForwardScanDirection, existing_slot))
{
TransactionId xwait;
XLTW_Oper reason_wait;
Datum existing_values[INDEX_MAX_KEYS];
bool existing_isnull[INDEX_MAX_KEYS];
char *error_new;
char *error_existing;
/*
* Ignore the entry for the tuple we're trying to check.
*/
if (ItemPointerIsValid(tupleid) &&
ItemPointerEquals(tupleid, &existing_slot->tts_tid))
{
if (found_self) /* should not happen */
elog(ERROR, "found self tuple multiple times in index \"%s\"",
RelationGetRelationName(index));
found_self = true;
continue;
}
/*
* Extract the index column values and isnull flags from the existing
* tuple.
*/
FormIndexDatum(indexInfo, existing_slot, estate,
existing_values, existing_isnull);
/* If lossy indexscan, must recheck the condition */
if (index_scan->xs_recheck)
{
if (!index_recheck_constraint(index,
constr_procs,
existing_values,
existing_isnull,
values))
continue; /* tuple doesn't actually match, so no
* conflict */
}
/*
* At this point we have either a conflict or a potential conflict.
*
* If an in-progress transaction is affecting the visibility of this
* tuple, we need to wait for it to complete and then recheck (unless
* the caller requested not to). For simplicity we do rechecking by
* just restarting the whole scan --- this case probably doesn't
* happen often enough to be worth trying harder, and anyway we don't
* want to hold any index internal locks while waiting.
*/
xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
DirtySnapshot.xmin : DirtySnapshot.xmax;
if (TransactionIdIsValid(xwait) &&
(waitMode == CEOUC_WAIT ||
(waitMode == CEOUC_LIVELOCK_PREVENTING_WAIT &&
DirtySnapshot.speculativeToken &&
TransactionIdPrecedes(GetCurrentTransactionId(), xwait))))
{
reason_wait = indexInfo->ii_ExclusionOps ?
XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
index_endscan(index_scan);
if (DirtySnapshot.speculativeToken)
SpeculativeInsertionWait(DirtySnapshot.xmin,
DirtySnapshot.speculativeToken);
else
XactLockTableWait(xwait, heap,
&existing_slot->tts_tid, reason_wait);
goto retry;
}
/*
* We have a definite conflict (or a potential one, but the caller
* didn't want to wait). Return it to caller, or report it.
*/
if (violationOK)
{
conflict = true;
if (conflictTid)
*conflictTid = existing_slot->tts_tid;
break;
}
error_new = BuildIndexValueDescription(index, values, isnull);
error_existing = BuildIndexValueDescription(index, existing_values,
existing_isnull);
if (newIndex)
ereport(ERROR,
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
error_new && error_existing ?
errdetail("Key %s conflicts with key %s.",
error_new, error_existing) :
errdetail("Key conflicts exist."),
errtableconstraint(heap,
RelationGetRelationName(index))));
else
ereport(ERROR,
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("conflicting key value violates exclusion constraint \"%s\"",
RelationGetRelationName(index)),
error_new && error_existing ?
errdetail("Key %s conflicts with existing key %s.",
error_new, error_existing) :
errdetail("Key conflicts with existing key."),
errtableconstraint(heap,
RelationGetRelationName(index))));
}
index_endscan(index_scan);
/*
* Ordinarily, at this point the search should have found the originally
* inserted tuple (if any), unless we exited the loop early because of
* conflict. However, it is possible to define exclusion constraints for
* which that wouldn't be true --- for instance, if the operator is <>. So
* we no longer complain if found_self is still false.
*/
econtext->ecxt_scantuple = save_scantuple;
ExecDropSingleTupleTableSlot(existing_slot);
#ifdef USE_INJECTION_POINTS
if (!conflict)
INJECTION_POINT("check-exclusion-or-unique-constraint-no-conflict", NULL);
#endif
return !conflict;
}
/*
* Check for violation of an exclusion constraint
*
* This is a dumbed down version of check_exclusion_or_unique_constraint
* for external callers. They don't need all the special modes.
*/
void
check_exclusion_constraint(Relation heap, Relation index,
IndexInfo *indexInfo,
const ItemPointerData *tupleid,
const Datum *values, const bool *isnull,
EState *estate, bool newIndex)
{
(void) check_exclusion_or_unique_constraint(heap, index, indexInfo, tupleid,
values, isnull,
estate, newIndex,
CEOUC_WAIT, false, NULL);
}
/*
* Check existing tuple's index values to see if it really matches the
* exclusion condition against the new_values. Returns true if conflict.
*/
static bool
index_recheck_constraint(Relation index, const Oid *constr_procs,
const Datum *existing_values, const bool *existing_isnull,
const Datum *new_values)
{
int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
int i;
for (i = 0; i < indnkeyatts; i++)
{
/* Assume the exclusion operators are strict */
if (existing_isnull[i])
return false;
if (!DatumGetBool(OidFunctionCall2Coll(constr_procs[i],
index->rd_indcollation[i],
existing_values[i],
new_values[i])))
return false;
}
return true;
}
/*
* Check if ExecInsertIndexTuples() should pass indexUnchanged hint.
*
* When the executor performs an UPDATE that requires a new round of index
* tuples, determine if we should pass 'indexUnchanged' = true hint for one
* single index.
*/
static bool
index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
IndexInfo *indexInfo, Relation indexRelation)
{
Bitmapset *updatedCols;
Bitmapset *extraUpdatedCols;
Bitmapset *allUpdatedCols;
bool hasexpression = false;
List *idxExprs;
/*
* Check cache first
*/
if (indexInfo->ii_CheckedUnchanged)
return indexInfo->ii_IndexUnchanged;
indexInfo->ii_CheckedUnchanged = true;
/*
* Check for indexed attribute overlap with updated columns.
*
* Only do this for key columns. A change to a non-key column within an
* INCLUDE index should not be counted here. Non-key column values are
* opaque payload state to the index AM, a little like an extra table TID.
*
* Note that row-level BEFORE triggers won't affect our behavior, since
* they don't affect the updatedCols bitmaps generally. It doesn't seem
* worth the trouble of checking which attributes were changed directly.
*/
updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
extraUpdatedCols = ExecGetExtraUpdatedCols(resultRelInfo, estate);
for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
{
int keycol = indexInfo->ii_IndexAttrNumbers[attr];
if (keycol <= 0)
{
/*
* Skip expressions for now, but remember to deal with them later
* on
*/
hasexpression = true;
continue;
}
if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
updatedCols) ||
bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
extraUpdatedCols))
{
/* Changed key column -- don't hint for this index */
indexInfo->ii_IndexUnchanged = false;
return false;
}
}
/*
* When we get this far and index has no expressions, return true so that
* index_insert() call will go on to pass 'indexUnchanged' = true hint.
*
* The _absence_ of an indexed key attribute that overlaps with updated
* attributes (in addition to the total absence of indexed expressions)
* shows that the index as a whole is logically unchanged by UPDATE.
*/
if (!hasexpression)
{
indexInfo->ii_IndexUnchanged = true;
return true;
}
/*
* Need to pass only one bms to expression_tree_walker helper function.
* Avoid allocating memory in common case where there are no extra cols.
*/
if (!extraUpdatedCols)
allUpdatedCols = updatedCols;
else
allUpdatedCols = bms_union(updatedCols, extraUpdatedCols);
/*
* We have to work slightly harder in the event of indexed expressions,
* but the principle is the same as before: try to find columns (Vars,
* actually) that overlap with known-updated columns.
*
* If we find any matching Vars, don't pass hint for index. Otherwise
* pass hint.
*/
idxExprs = RelationGetIndexExpressions(indexRelation);
idxExprs = list_concat(idxExprs, RelationGetIndexExpressionsExpand(indexRelation));
hasexpression = index_expression_changed_walker((Node *) idxExprs,
allUpdatedCols);
list_free(idxExprs);
if (extraUpdatedCols)
bms_free(allUpdatedCols);
if (hasexpression)
{
indexInfo->ii_IndexUnchanged = false;
return false;
}
/*
* Deliberately don't consider index predicates. We should even give the
* hint when result rel's "updated tuple" has no corresponding index
* tuple, which is possible with a partial index (provided the usual
* conditions are met).
*/
indexInfo->ii_IndexUnchanged = true;
return true;
}
/*
* Indexed expression helper for index_unchanged_by_update().
*
* Returns true when Var that appears within allUpdatedCols located.
*/
static bool
index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
{
if (node == NULL)
return false;
if (IsA(node, Var))
{
Var *var = (Var *) node;
if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
allUpdatedCols))
{
/* Var was updated -- indicates that we should not hint */
return true;
}
/* Still haven't found a reason to not pass the hint */
return false;
}
return expression_tree_walker(node, index_expression_changed_walker,
allUpdatedCols);
}
/*
* ExecWithoutOverlapsNotEmpty - raise an error if the tuple has an empty
* range or multirange in the given attribute.
*/
static void
ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char typtype, Oid atttypid)
{
bool isempty;
RangeType *r;
MultirangeType *mr;
switch (typtype)
{
case TYPTYPE_RANGE:
r = DatumGetRangeTypeP(attval);
isempty = RangeIsEmpty(r);
break;
case TYPTYPE_MULTIRANGE:
mr = DatumGetMultirangeTypeP(attval);
isempty = MultirangeIsEmpty(mr);
break;
default:
elog(ERROR, "WITHOUT OVERLAPS column \"%s\" is not a range or multirange",
NameStr(attname));
}
/* Report a CHECK_VIOLATION */
if (isempty)
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
NameStr(attname), RelationGetRelationName(rel))));
}
./execnodes.h 0000664 0001750 0001750 00000314567 15221603750 012054 0 ustar xman xman /*-------------------------------------------------------------------------
*
* execnodes.h
* definitions for executor state nodes
*
* Most plan node types declared in plannodes.h have a corresponding
* execution-state node type declared here. An exception is that
* expression nodes (subtypes of Expr) are usually represented by steps
* of an ExprState, and fully handled within execExpr* - but sometimes
* their state needs to be shared with other parts of the executor, as
* for example with SubPlanState, which nodeSubplan.c has to modify.
*
* Node types declared in this file do not have any copy/equal/out/read
* support. (That is currently hard-wired in gen_node_support.pl, rather
* than being explicitly represented by pg_node_attr decorations here.)
* There is no need for copy, equal, or read support for executor trees.
* Output support could be useful for debugging; but there are a lot of
* specialized fields that would require custom code, so for now it's
* not provided.
*
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/nodes/execnodes.h
*
*-------------------------------------------------------------------------
*/
#ifndef EXECNODES_H
#define EXECNODES_H
#include "access/htup.h"
#include "executor/instrument_node.h"
#include "fmgr.h"
#include "lib/ilist.h"
#include "nodes/miscnodes.h"
#include "nodes/params.h"
#include "nodes/plannodes.h"
#include "partitioning/partdefs.h"
#include "storage/buf.h"
#include "utils/reltrigger.h"
#include "utils/typcache.h"
/*
* forward references in this file
*/
typedef struct BufferUsage BufferUsage;
typedef struct ExecRowMark ExecRowMark;
typedef struct ExprState ExprState;
typedef struct ExprContext ExprContext;
typedef struct HTAB HTAB;
typedef struct Instrumentation Instrumentation;
typedef struct pairingheap pairingheap;
typedef struct PlanState PlanState;
typedef struct QueryEnvironment QueryEnvironment;
typedef struct RelationData *Relation;
typedef Relation *RelationPtr;
typedef struct ScanKeyData ScanKeyData;
typedef struct SnapshotData *Snapshot;
typedef struct SortSupportData *SortSupport;
typedef struct TIDBitmap TIDBitmap;
typedef struct NodeInstrumentation NodeInstrumentation;
typedef struct TriggerInstrumentation TriggerInstrumentation;
typedef struct TupleConversionMap TupleConversionMap;
typedef struct TupleDescData *TupleDesc;
typedef struct Tuplesortstate Tuplesortstate;
typedef struct Tuplestorestate Tuplestorestate;
typedef struct TupleTableSlot TupleTableSlot;
typedef struct TupleTableSlotOps TupleTableSlotOps;
typedef struct WalUsage WalUsage;
typedef struct WorkerNodeInstrumentation WorkerNodeInstrumentation;
/* ----------------
* ExprState node
*
* ExprState represents the evaluation state for a whole expression tree.
* It contains instructions (in ->steps) to evaluate the expression.
* ----------------
*/
typedef Datum (*ExprStateEvalFunc) (ExprState *expression,
ExprContext *econtext,
bool *isNull);
/* Bits in ExprState->flags (see also execExpr.h for private flag bits): */
/* expression is for use with ExecQual() */
#define EEO_FLAG_IS_QUAL (1 << 0)
/* expression refers to OLD table columns */
#define EEO_FLAG_HAS_OLD (1 << 1)
/* expression refers to NEW table columns */
#define EEO_FLAG_HAS_NEW (1 << 2)
/* OLD table row is NULL in RETURNING list */
#define EEO_FLAG_OLD_IS_NULL (1 << 3)
/* NEW table row is NULL in RETURNING list */
#define EEO_FLAG_NEW_IS_NULL (1 << 4)
typedef struct ExprState
{
NodeTag type;
#define FIELDNO_EXPRSTATE_FLAGS 1
uint8 flags; /* bitmask of EEO_FLAG_* bits, see above */
/*
* Storage for result value of a scalar expression, or for individual
* column results within expressions built by ExecBuildProjectionInfo().
*/
#define FIELDNO_EXPRSTATE_RESNULL 2
bool resnull;
#define FIELDNO_EXPRSTATE_RESVALUE 3
Datum resvalue;
/*
* If projecting a tuple result, this slot holds the result; else NULL.
*/
#define FIELDNO_EXPRSTATE_RESULTSLOT 4
TupleTableSlot *resultslot;
/*
* Instructions to compute expression's return value.
*/
struct ExprEvalStep *steps;
/*
* Function that actually evaluates the expression. This can be set to
* different values depending on the complexity of the expression.
*/
ExprStateEvalFunc evalfunc;
/* original expression tree, for debugging only */
Expr *expr;
/* private state for an evalfunc */
void *evalfunc_private;
/*
* XXX: following fields only needed during "compilation" (ExecInitExpr);
* could be thrown away afterwards.
*/
int steps_len; /* number of steps currently */
int steps_alloc; /* allocated length of steps array */
#define FIELDNO_EXPRSTATE_PARENT 11
PlanState *parent; /* parent PlanState node, if any */
ParamListInfo ext_params; /* for compiling PARAM_EXTERN nodes */
Datum *innermost_caseval;
bool *innermost_casenull;
Datum *innermost_domainval;
bool *innermost_domainnull;
/*
* For expression nodes that support soft errors. Should be set to NULL if
* the caller wants errors to be thrown. Callers that do not want errors
* thrown should set it to a valid ErrorSaveContext before calling
* ExecInitExprRec().
*/
ErrorSaveContext *escontext;
} ExprState;
/* ----------------
* IndexInfo information
*
* this struct holds the information needed to construct new index
* entries for a particular index. Used for both index_build and
* retail creation of index entries.
*
* ii_Concurrent, ii_BrokenHotChain, and ii_ParallelWorkers are used only
* during index build; they're conventionally zeroed otherwise.
* ----------------
*/
typedef struct IndexInfo
{
NodeTag type;
/* total number of columns in index */
int ii_NumIndexAttrs;
/* number of key columns in index */
int ii_NumIndexKeyAttrs;
/*
* Underlying-rel attribute numbers used as keys (zeroes indicate
* expressions). It also contains info about included columns.
*/
AttrNumber ii_IndexAttrNumbers[INDEX_MAX_KEYS];
/* expr trees for expression entries, or NIL if none */
List *ii_Expressions; /* list of Expr */
List *ii_ExpressionsExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
List *ii_ExpressionsState; /* list of ExprState */
List *ii_ExpressionsExpandState; /* list of ExprState */
/* partial-index predicate, or NIL if none */
List *ii_Predicate; /* list of Expr */
List *ii_PredicateExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
ExprState *ii_PredicateState;
ExprState *ii_PredicateExpandState;
/* Per-column exclusion operators, or NULL if none */
Oid *ii_ExclusionOps; /* array with one entry per column */
/* Underlying function OIDs for ExclusionOps */
Oid *ii_ExclusionProcs; /* array with one entry per column */
/* Opclass strategy numbers for ExclusionOps */
uint16 *ii_ExclusionStrats; /* array with one entry per column */
/* These are like Exclusion*, but for unique indexes */
Oid *ii_UniqueOps; /* array with one entry per column */
Oid *ii_UniqueProcs; /* array with one entry per column */
uint16 *ii_UniqueStrats; /* array with one entry per column */
/* is it a unique index? */
bool ii_Unique;
/* is NULLS NOT DISTINCT? */
bool ii_NullsNotDistinct;
/* is it valid for inserts? */
bool ii_ReadyForInserts;
/* IndexUnchanged status determined yet? */
bool ii_CheckedUnchanged;
/* aminsert hint, cached for retail inserts */
bool ii_IndexUnchanged;
/* are we doing a concurrent index build? */
bool ii_Concurrent;
/* did we detect any broken HOT chains? */
bool ii_BrokenHotChain;
/* is it a summarizing index? */
bool ii_Summarizing;
/* is it a WITHOUT OVERLAPS index? */
bool ii_WithoutOverlaps;
/* # of workers requested (excludes leader) */
int ii_ParallelWorkers;
/* Oid of index AM */
Oid ii_Am;
/* private cache area for index AM */
void *ii_AmCache;
/* memory context holding this IndexInfo */
MemoryContext ii_Context;
} IndexInfo;
/* ----------------
* ExprContext_CB
*
* List of callbacks to be called at ExprContext shutdown.
* ----------------
*/
typedef void (*ExprContextCallbackFunction) (Datum arg);
typedef struct ExprContext_CB
{
struct ExprContext_CB *next;
ExprContextCallbackFunction function;
Datum arg;
} ExprContext_CB;
/* ----------------
* ExprContext
*
* This class holds the "current context" information
* needed to evaluate expressions for doing tuple qualifications
* and tuple projections. For example, if an expression refers
* to an attribute in the current inner tuple then we need to know
* what the current inner tuple is and so we look at the expression
* context.
*
* There are two memory contexts associated with an ExprContext:
* * ecxt_per_query_memory is a query-lifespan context, typically the same
* context the ExprContext node itself is allocated in. This context
* can be used for purposes such as storing function call cache info.
* * ecxt_per_tuple_memory is a short-term context for expression results.
* As the name suggests, it will typically be reset once per tuple,
* before we begin to evaluate expressions for that tuple. Each
* ExprContext normally has its very own per-tuple memory context.
*
* CurrentMemoryContext should be set to ecxt_per_tuple_memory before
* calling ExecEvalExpr() --- see ExecEvalExprSwitchContext().
* ----------------
*/
typedef struct ExprContext
{
NodeTag type;
/* Tuples that Var nodes in expression may refer to */
#define FIELDNO_EXPRCONTEXT_SCANTUPLE 1
TupleTableSlot *ecxt_scantuple;
#define FIELDNO_EXPRCONTEXT_INNERTUPLE 2
TupleTableSlot *ecxt_innertuple;
#define FIELDNO_EXPRCONTEXT_OUTERTUPLE 3
TupleTableSlot *ecxt_outertuple;
/* Memory contexts for expression evaluation --- see notes above */
MemoryContext ecxt_per_query_memory;
MemoryContext ecxt_per_tuple_memory;
/* Values to substitute for Param nodes in expression */
ParamExecData *ecxt_param_exec_vals; /* for PARAM_EXEC params */
ParamListInfo ecxt_param_list_info; /* for other param types */
/*
* Values to substitute for Aggref nodes in the expressions of an Agg
* node, or for WindowFunc nodes within a WindowAgg node.
*/
#define FIELDNO_EXPRCONTEXT_AGGVALUES 8
Datum *ecxt_aggvalues; /* precomputed values for aggs/windowfuncs */
#define FIELDNO_EXPRCONTEXT_AGGNULLS 9
bool *ecxt_aggnulls; /* null flags for aggs/windowfuncs */
/* Value to substitute for CaseTestExpr nodes in expression */
#define FIELDNO_EXPRCONTEXT_CASEDATUM 10
Datum caseValue_datum;
#define FIELDNO_EXPRCONTEXT_CASENULL 11
bool caseValue_isNull;
/* Value to substitute for CoerceToDomainValue nodes in expression */
#define FIELDNO_EXPRCONTEXT_DOMAINDATUM 12
Datum domainValue_datum;
#define FIELDNO_EXPRCONTEXT_DOMAINNULL 13
bool domainValue_isNull;
/* Tuples that OLD/NEW Var nodes in RETURNING may refer to */
#define FIELDNO_EXPRCONTEXT_OLDTUPLE 14
TupleTableSlot *ecxt_oldtuple;
#define FIELDNO_EXPRCONTEXT_NEWTUPLE 15
TupleTableSlot *ecxt_newtuple;
/* Link to containing EState (NULL if a standalone ExprContext) */
struct EState *ecxt_estate;
/* Functions to call back when ExprContext is shut down or rescanned */
ExprContext_CB *ecxt_callbacks;
} ExprContext;
/*
* Set-result status used when evaluating functions potentially returning a
* set.
*/
typedef enum
{
ExprSingleResult, /* expression does not return a set */
ExprMultipleResult, /* this result is an element of a set */
ExprEndResult, /* there are no more elements in the set */
} ExprDoneCond;
/*
* Return modes for functions returning sets. Note values must be chosen
* as separate bits so that a bitmask can be formed to indicate supported
* modes. SFRM_Materialize_Random and SFRM_Materialize_Preferred are
* auxiliary flags about SFRM_Materialize mode, rather than separate modes.
*/
typedef enum
{
SFRM_ValuePerCall = 0x01, /* one value returned per call */
SFRM_Materialize = 0x02, /* result set instantiated in Tuplestore */
SFRM_Materialize_Random = 0x04, /* Tuplestore needs randomAccess */
SFRM_Materialize_Preferred = 0x08, /* caller prefers Tuplestore */
} SetFunctionReturnMode;
/*
* When calling a function that might return a set (multiple rows),
* a node of this type is passed as fcinfo->resultinfo to allow
* return status to be passed back. A function returning set should
* raise an error if no such resultinfo is provided.
*/
typedef struct ReturnSetInfo
{
NodeTag type;
/* values set by caller: */
ExprContext *econtext; /* context function is being called in */
TupleDesc expectedDesc; /* tuple descriptor expected by caller */
int allowedModes; /* bitmask: return modes caller can handle */
/* result status from function (but pre-initialized by caller): */
SetFunctionReturnMode returnMode; /* actual return mode */
ExprDoneCond isDone; /* status for ValuePerCall mode */
/* fields filled by function in Materialize return mode: */
Tuplestorestate *setResult; /* holds the complete returned tuple set */
TupleDesc setDesc; /* actual descriptor for returned tuples */
} ReturnSetInfo;
/* ----------------
* ProjectionInfo node information
*
* This is all the information needed to perform projections ---
* that is, form new tuples by evaluation of targetlist expressions.
* Nodes which need to do projections create one of these.
*
* The target tuple slot is kept in ProjectionInfo->pi_state.resultslot.
* ExecProject() evaluates the tlist, forms a tuple, and stores it
* in the given slot. Note that the result will be a "virtual" tuple
* unless ExecMaterializeSlot() is then called to force it to be
* converted to a physical tuple. The slot must have a tupledesc
* that matches the output of the tlist!
* ----------------
*/
typedef struct ProjectionInfo
{
NodeTag type;
/* instructions to evaluate projection */
ExprState pi_state;
/* expression context in which to evaluate expression */
ExprContext *pi_exprContext;
} ProjectionInfo;
/* ----------------
* JunkFilter
*
* This class is used to store information regarding junk attributes.
* A junk attribute is an attribute in a tuple that is needed only for
* storing intermediate information in the executor, and does not belong
* in emitted tuples. For example, when we do an UPDATE query,
* the planner adds a "junk" entry to the targetlist so that the tuples
* returned to ExecutePlan() contain an extra attribute: the ctid of
* the tuple to be updated. This is needed to do the update, but we
* don't want the ctid to be part of the stored new tuple! So, we
* apply a "junk filter" to remove the junk attributes and form the
* real output tuple. The junkfilter code also provides routines to
* extract the values of the junk attribute(s) from the input tuple.
*
* targetList: the original target list (including junk attributes).
* cleanTupType: the tuple descriptor for the "clean" tuple (with
* junk attributes removed).
* cleanMap: A map with the correspondence between the non-junk
* attribute numbers of the "original" tuple and the
* attribute numbers of the "clean" tuple.
* resultSlot: tuple slot used to hold cleaned tuple.
* ----------------
*/
typedef struct JunkFilter
{
NodeTag type;
List *jf_targetList;
TupleDesc jf_cleanTupType;
AttrNumber *jf_cleanMap;
TupleTableSlot *jf_resultSlot;
} JunkFilter;
/*
* OnConflictActionState
*
* Executor state of an ON CONFLICT DO SELECT/UPDATE operation.
*/
typedef struct OnConflictActionState
{
NodeTag type;
TupleTableSlot *oc_Existing; /* slot to store existing target tuple in */
TupleTableSlot *oc_ProjSlot; /* CONFLICT ... SET ... projection target */
ProjectionInfo *oc_ProjInfo; /* for ON CONFLICT DO UPDATE SET */
LockClauseStrength oc_LockStrength; /* lock strength for DO SELECT */
ExprState *oc_WhereClause; /* state for the WHERE clause */
} OnConflictActionState;
/* ----------------
* MergeActionState information
*
* Executor state for a MERGE action.
* ----------------
*/
typedef struct MergeActionState
{
NodeTag type;
MergeAction *mas_action; /* associated MergeAction node */
ProjectionInfo *mas_proj; /* projection of the action's targetlist for
* this rel */
ExprState *mas_whenqual; /* WHEN [NOT] MATCHED AND conditions */
} MergeActionState;
/*
* ForPortionOfState
*
* Executor state of a FOR PORTION OF operation.
*/
typedef struct ForPortionOfState
{
NodeTag type;
char *fp_rangeName; /* the column named in FOR PORTION OF */
Oid fp_rangeType; /* the base type (not domain) of the FOR
* PORTION OF expression */
int fp_rangeAttno; /* the attno of the range column */
Datum fp_targetRange; /* the range/multirange from FOR PORTION OF */
TypeCacheEntry *fp_leftoverstypcache; /* type cache entry of the range */
TupleTableSlot *fp_Existing; /* slot to store old tuple */
TupleTableSlot *fp_Leftover; /* slot to store leftover */
} ForPortionOfState;
/*
* ResultRelInfo
*
* Whenever we update an existing relation, we have to update indexes on the
* relation, and perhaps also fire triggers. ResultRelInfo holds all the
* information needed about a result relation, including indexes.
*
* Normally, a ResultRelInfo refers to a table that is in the query's range
* table; then ri_RangeTableIndex is the RT index and ri_RelationDesc is
* just a copy of the relevant es_relations[] entry. However, in some
* situations we create ResultRelInfos for relations that are not in the
* range table, namely for targets of tuple routing in a partitioned table,
* and when firing triggers in tables other than the target tables (See
* ExecGetTriggerResultRel). In these situations, ri_RangeTableIndex is 0
* and ri_RelationDesc is a separately-opened relcache pointer that needs to
* be separately closed.
*/
typedef struct ResultRelInfo
{
NodeTag type;
/* result relation's range table index, or 0 if not in range table */
Index ri_RangeTableIndex;
/* relation descriptor for result relation */
Relation ri_RelationDesc;
/* # of indices existing on result relation */
int ri_NumIndices;
/* array of relation descriptors for indices */
RelationPtr ri_IndexRelationDescs;
/* array of key/attr info for indices */
IndexInfo **ri_IndexRelationInfo;
/*
* For UPDATE/DELETE/MERGE result relations, the attribute number of the
* row identity junk attribute in the source plan's output tuples
*/
AttrNumber ri_RowIdAttNo;
/* For UPDATE, attnums of generated columns to be computed */
Bitmapset *ri_extraUpdatedCols;
/* true if the above has been computed */
bool ri_extraUpdatedCols_valid;
/* Projection to generate new tuple in an INSERT/UPDATE */
ProjectionInfo *ri_projectNew;
/* Slot to hold that tuple */
TupleTableSlot *ri_newTupleSlot;
/* Slot to hold the old tuple being updated */
TupleTableSlot *ri_oldTupleSlot;
/* Have the projection and the slots above been initialized? */
bool ri_projectNewInfoValid;
/* updates do LockTuple() before oldtup read; see README.tuplock */
bool ri_needLockTagTuple;
/* triggers to be fired, if any */
TriggerDesc *ri_TrigDesc;
/* cached lookup info for trigger functions */
FmgrInfo *ri_TrigFunctions;
/* array of trigger WHEN expr states */
ExprState **ri_TrigWhenExprs;
/* optional runtime measurements for triggers */
TriggerInstrumentation *ri_TrigInstrument;
/* On-demand created slots for triggers / returning processing */
TupleTableSlot *ri_ReturningSlot; /* for trigger output tuples */
TupleTableSlot *ri_TrigOldSlot; /* for a trigger's old tuple */
TupleTableSlot *ri_TrigNewSlot; /* for a trigger's new tuple */
TupleTableSlot *ri_AllNullSlot; /* for RETURNING OLD/NEW */
/* FDW callback functions, if foreign table */
struct FdwRoutine *ri_FdwRoutine;
/* available to save private state of FDW */
void *ri_FdwState;
/* true when modifying foreign table directly */
bool ri_usesFdwDirectModify;
/* batch insert stuff */
int ri_NumSlots; /* number of slots in the array */
int ri_NumSlotsInitialized; /* number of initialized slots */
int ri_BatchSize; /* max slots inserted in a single batch */
TupleTableSlot **ri_Slots; /* input tuples for batch insert */
TupleTableSlot **ri_PlanSlots;
/* list of WithCheckOption's to be checked */
List *ri_WithCheckOptions;
/* list of WithCheckOption expr states */
List *ri_WithCheckOptionExprs;
/* array of expr states for checking check constraints */
ExprState **ri_CheckConstraintExprs;
/*
* array of expr states for checking not-null constraints on virtual
* generated columns
*/
ExprState **ri_GenVirtualNotNullConstraintExprs;
/*
* Arrays of stored generated columns ExprStates for INSERT/UPDATE/MERGE.
*/
ExprState **ri_GeneratedExprsI;
ExprState **ri_GeneratedExprsU;
/* number of stored generated columns we need to compute */
int ri_NumGeneratedNeededI;
int ri_NumGeneratedNeededU;
/* list of RETURNING expressions */
List *ri_returningList;
/* for computing a RETURNING list */
ProjectionInfo *ri_projectReturning;
/* list of arbiter indexes to use to check conflicts */
List *ri_onConflictArbiterIndexes;
/* ON CONFLICT evaluation state for DO SELECT/UPDATE */
OnConflictActionState *ri_onConflict;
/* for MERGE, lists of MergeActionState (one per MergeMatchKind) */
List *ri_MergeActions[NUM_MERGE_MATCH_KINDS];
/* for MERGE, expr state for checking the join condition */
ExprState *ri_MergeJoinCondition;
/* FOR PORTION OF evaluation state */
ForPortionOfState *ri_forPortionOf;
/* partition check expression state (NULL if not set up yet) */
ExprState *ri_PartitionCheckExpr;
/*
* Map to convert child result relation tuples to the format of the table
* actually mentioned in the query (called "root"). Computed only if
* needed. A NULL map value indicates that no conversion is needed, so we
* must have a separate flag to show if the map has been computed.
*/
TupleConversionMap *ri_ChildToRootMap;
bool ri_ChildToRootMapValid;
/*
* As above, but in the other direction.
*/
TupleConversionMap *ri_RootToChildMap;
bool ri_RootToChildMapValid;
/*
* Other information needed by child result relations
*
* ri_RootResultRelInfo gives the target relation mentioned in the query.
* Used as the root for tuple routing and/or transition capture.
*
* ri_PartitionTupleSlot is non-NULL if the relation is a partition to
* route tuples into and ri_RootToChildMap conversion is needed.
*/
struct ResultRelInfo *ri_RootResultRelInfo;
TupleTableSlot *ri_PartitionTupleSlot;
/* for use by copyfrom.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
/*
* Used when a leaf partition is involved in a cross-partition update of
* one of its ancestors; see ExecCrossPartitionUpdateForeignKey().
*/
List *ri_ancestorResultRels;
} ResultRelInfo;
/* ----------------
* AsyncRequest
*
* State for an asynchronous tuple request.
* ----------------
*/
typedef struct AsyncRequest
{
PlanState *requestor; /* Node that wants a tuple */
PlanState *requestee; /* Node from which a tuple is wanted */
int request_index; /* Scratch space for requestor */
bool callback_pending; /* Callback is needed */
bool request_complete; /* Request complete, result valid */
TupleTableSlot *result; /* Result (NULL or an empty slot if no more
* tuples) */
} AsyncRequest;
/* ----------------
* EState information
*
* Working state for an Executor invocation
* ----------------
*/
typedef struct EState
{
NodeTag type;
/* Basic state for all query types: */
ScanDirection es_direction; /* current scan direction */
Snapshot es_snapshot; /* time qual to use */
Snapshot es_crosscheck_snapshot; /* crosscheck time qual for RI */
List *es_range_table; /* List of RangeTblEntry */
Index es_range_table_size; /* size of the range table arrays */
Relation *es_relations; /* Array of per-range-table-entry Relation
* pointers, or NULL if not yet opened */
ExecRowMark **es_rowmarks; /* Array of per-range-table-entry
* ExecRowMarks, or NULL if none */
List *es_rteperminfos; /* List of RTEPermissionInfo */
PlannedStmt *es_plannedstmt; /* link to top of plan tree */
List *es_part_prune_infos; /* List of PartitionPruneInfo */
List *es_part_prune_states; /* List of PartitionPruneState */
List *es_part_prune_results; /* List of Bitmapset */
Bitmapset *es_unpruned_relids; /* PlannedStmt.unprunableRelids + RT
* indexes of leaf partitions that survive
* initial pruning; see
* ExecDoInitialPruning() */
const char *es_sourceText; /* Source text from QueryDesc */
JunkFilter *es_junkFilter; /* top-level junk filter, if any */
/* If query can insert/delete tuples, the command ID to mark them with */
CommandId es_output_cid;
/* Info about target table(s) for insert/update/delete queries: */
ResultRelInfo **es_result_relations; /* Array of per-range-table-entry
* ResultRelInfo pointers, or NULL
* if not a target table */
List *es_opened_result_relations; /* List of non-NULL entries in
* es_result_relations in no
* specific order */
PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */
/*
* The following list contains ResultRelInfos created by the tuple routing
* code for partitions that aren't found in the es_result_relations array.
*/
List *es_tuple_routing_result_relations;
/* Stuff used for firing triggers: */
List *es_trig_target_relations; /* trigger-only ResultRelInfos */
/* Parameter info: */
ParamListInfo es_param_list_info; /* values of external params */
ParamExecData *es_param_exec_vals; /* values of internal params */
QueryEnvironment *es_queryEnv; /* query environment */
/* Other working state: */
MemoryContext es_query_cxt; /* per-query context in which EState lives */
List *es_tupleTable; /* List of TupleTableSlots */
uint64 es_processed; /* # of tuples processed during one
* ExecutorRun() call. */
uint64 es_total_processed; /* total # of tuples aggregated across all
* ExecutorRun() calls. */
int es_top_eflags; /* eflags passed to ExecutorStart */
int es_instrument; /* OR of InstrumentOption flags */
bool es_finished; /* true when ExecutorFinish is done */
List *es_exprcontexts; /* List of ExprContexts within EState */
List *es_subplanstates; /* List of PlanState for SubPlans */
List *es_auxmodifytables; /* List of secondary ModifyTableStates */
/*
* this ExprContext is for per-output-tuple operations, such as constraint
* checks and index-value computations. It will be reset for each output
* tuple. Note that it will be created only if needed.
*/
ExprContext *es_per_tuple_exprcontext;
/*
* If not NULL, this is an EPQState's EState. This is a field in EState
* both to allow EvalPlanQual aware executor nodes to detect that they
* need to perform EPQ related work, and to provide necessary information
* to do so.
*/
struct EPQState *es_epq_active;
bool es_use_parallel_mode; /* can we use parallel workers? */
int es_parallel_workers_to_launch; /* number of workers to
* launch. */
int es_parallel_workers_launched; /* number of workers actually
* launched. */
/* The per-query shared memory area to use for parallel execution. */
struct dsa_area *es_query_dsa;
/*
* JIT information. es_jit_flags indicates whether JIT should be performed
* and with which options. es_jit is created on-demand when JITing is
* performed.
*
* es_jit_worker_instr is the combined, on demand allocated,
* instrumentation from all workers. The leader's instrumentation is kept
* separate, and is combined on demand by ExplainPrintJITSummary().
*/
int es_jit_flags;
struct JitContext *es_jit;
struct JitInstrumentation *es_jit_worker_instr;
/*
* Lists of ResultRelInfos for foreign tables on which batch-inserts are
* to be executed and owning ModifyTableStates, stored in the same order.
*/
List *es_insert_pending_result_relations;
List *es_insert_pending_modifytables;
} EState;
/*
* ExecRowMark -
* runtime representation of FOR [KEY] UPDATE/SHARE clauses
*
* When doing UPDATE/DELETE/MERGE/SELECT FOR [KEY] UPDATE/SHARE, we will have
* an ExecRowMark for each non-target relation in the query (except inheritance
* parent RTEs, which can be ignored at runtime). Virtual relations such as
* subqueries-in-FROM will have an ExecRowMark with relation == NULL. See
* PlanRowMark for details about most of the fields. In addition to fields
* directly derived from PlanRowMark, we store an activity flag (to denote
* inactive children of inheritance trees), curCtid, which is used by the
* WHERE CURRENT OF code, and ermExtra, which is available for use by the plan
* node that sources the relation (e.g., for a foreign table the FDW can use
* ermExtra to hold information).
*
* EState->es_rowmarks is an array of these structs, indexed by RT index,
* with NULLs for irrelevant RT indexes. es_rowmarks itself is NULL if
* there are no rowmarks.
*/
typedef struct ExecRowMark
{
Relation relation; /* opened and suitably locked relation */
Oid relid; /* its OID (or InvalidOid, if subquery) */
Index rti; /* its range table index */
Index prti; /* parent range table index, if child */
Index rowmarkId; /* unique identifier for resjunk columns */
RowMarkType markType; /* see enum in nodes/plannodes.h */
LockClauseStrength strength; /* LockingClause's strength, or LCS_NONE */
LockWaitPolicy waitPolicy; /* NOWAIT and SKIP LOCKED */
bool ermActive; /* is this mark relevant for current tuple? */
ItemPointerData curCtid; /* ctid of currently locked tuple, if any */
void *ermExtra; /* available for use by relation source node */
} ExecRowMark;
/*
* ExecAuxRowMark -
* additional runtime representation of FOR [KEY] UPDATE/SHARE clauses
*
* Each LockRows and ModifyTable node keeps a list of the rowmarks it needs to
* deal with. In addition to a pointer to the related entry in es_rowmarks,
* this struct carries the column number(s) of the resjunk columns associated
* with the rowmark (see comments for PlanRowMark for more detail).
*/
typedef struct ExecAuxRowMark
{
ExecRowMark *rowmark; /* related entry in es_rowmarks */
AttrNumber ctidAttNo; /* resno of ctid junk attribute, if any */
AttrNumber toidAttNo; /* resno of tableoid junk attribute, if any */
AttrNumber wholeAttNo; /* resno of whole-row junk attribute, if any */
} ExecAuxRowMark;
/* ----------------------------------------------------------------
* Tuple Hash Tables
*
* All-in-memory tuple hash tables are used for a number of purposes.
*
* Note: tab_hash_expr is for hashing the key datatype(s) stored in the table,
* and tab_eq_func is a non-cross-type ExprState for equality checks on those
* types. Normally these are the only ExprStates used, but
* FindTupleHashEntry() supports searching a hashtable using cross-data-type
* hashing. For that, the caller must supply an ExprState to hash the LHS
* datatype as well as the cross-type equality ExprState to use. in_hash_expr
* and cur_eq_func are set to point to the caller's hash and equality
* ExprStates while doing such a search. During LookupTupleHashEntry(), they
* point to tab_hash_expr and tab_eq_func respectively.
* ----------------------------------------------------------------
*/
typedef struct TupleHashEntryData *TupleHashEntry;
typedef struct TupleHashTableData *TupleHashTable;
/*
* TupleHashEntryData is a slot in the tuplehash_hash table. If it's
* populated, it contains a pointer to a MinimalTuple that can also have
* associated "additional data". That's stored in the TupleHashTable's
* tuplescxt.
*/
typedef struct TupleHashEntryData
{
MinimalTuple firstTuple; /* -> copy of first tuple in this group */
uint32 status; /* hash status */
uint32 hash; /* hash value (cached) */
} TupleHashEntryData;
/* define parameters necessary to generate the tuple hash table interface */
#define SH_PREFIX tuplehash
#define SH_ELEMENT_TYPE TupleHashEntryData
#define SH_KEY_TYPE MinimalTuple
#define SH_SCOPE extern
#define SH_DECLARE
#include "lib/simplehash.h"
typedef struct TupleHashTableData
{
tuplehash_hash *hashtab; /* underlying simplehash hash table */
int numCols; /* number of columns in lookup key */
AttrNumber *keyColIdx; /* attr numbers of key columns */
ExprState *tab_hash_expr; /* ExprState for hashing table datatype(s) */
ExprState *tab_eq_func; /* comparator for table datatype(s) */
Oid *tab_collations; /* collations for hash and comparison */
MemoryContext tuplescxt; /* memory context storing hashed tuples */
MemoryContext tempcxt; /* context for function evaluations */
Size additionalsize; /* size of additional data */
TupleTableSlot *tableslot; /* slot for referencing table entries */
/* The following fields are set transiently for each table search: */
TupleTableSlot *inputslot; /* current input tuple's slot */
ExprState *in_hash_expr; /* ExprState for hashing input datatype(s) */
ExprState *cur_eq_func; /* comparator for input vs. table */
ExprContext *exprcontext; /* expression context */
} TupleHashTableData;
typedef tuplehash_iterator TupleHashIterator;
/*
* Use InitTupleHashIterator/TermTupleHashIterator for a read/write scan.
* Use ResetTupleHashIterator if the table can be frozen (in this case no
* explicit scan termination is needed).
*/
#define InitTupleHashIterator(htable, iter) \
tuplehash_start_iterate(htable->hashtab, iter)
#define TermTupleHashIterator(iter) \
((void) 0)
#define ResetTupleHashIterator(htable, iter) \
InitTupleHashIterator(htable, iter)
#define ScanTupleHashTable(htable, iter) \
tuplehash_iterate(htable->hashtab, iter)
/* ----------------------------------------------------------------
* Expression State Nodes
*
* Formerly, there was a separate executor expression state node corresponding
* to each node in a planned expression tree. That's no longer the case; for
* common expression node types, all the execution info is embedded into
* step(s) in a single ExprState node. But we still have a few executor state
* node types for selected expression node types, mostly those in which info
* has to be shared with other parts of the execution state tree.
* ----------------------------------------------------------------
*/
/* ----------------
* WindowFuncExprState node
* ----------------
*/
typedef struct WindowFuncExprState
{
NodeTag type;
WindowFunc *wfunc; /* expression plan node */
List *args; /* ExprStates for argument expressions */
ExprState *aggfilter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
/* ----------------
* SetExprState node
*
* State for evaluating a potentially set-returning expression (like FuncExpr
* or OpExpr). In some cases, like some of the expressions in ROWS FROM(...)
* the expression might not be a SRF, but nonetheless it uses the same
* machinery as SRFs; it will be treated as a SRF returning a single row.
* ----------------
*/
typedef struct SetExprState
{
NodeTag type;
Expr *expr; /* expression plan node */
List *args; /* ExprStates for argument expressions */
/*
* In ROWS FROM, functions can be inlined, removing the FuncExpr normally
* inside. In such a case this is the compiled expression (which cannot
* return a set), which'll be evaluated using regular ExecEvalExpr().
*/
ExprState *elidedFuncState;
/*
* Function manager's lookup info for the target function. If func.fn_oid
* is InvalidOid, we haven't initialized it yet (nor any of the following
* fields, except funcReturnsSet).
*/
FmgrInfo func;
/*
* For a set-returning function (SRF) that returns a tuplestore, we keep
* the tuplestore here and dole out the result rows one at a time. The
* slot holds the row currently being returned.
*/
Tuplestorestate *funcResultStore;
TupleTableSlot *funcResultSlot;
/*
* In some cases we need to compute a tuple descriptor for the function's
* output. If so, it's stored here.
*/
TupleDesc funcResultDesc;
bool funcReturnsTuple; /* valid when funcResultDesc isn't NULL */
/*
* Remember whether the function is declared to return a set. This is set
* by ExecInitExpr, and is valid even before the FmgrInfo is set up.
*/
bool funcReturnsSet;
/*
* setArgsValid is true when we are evaluating a set-returning function
* that uses value-per-call mode and we are in the middle of a call
* series; we want to pass the same argument values to the function again
* (and again, until it returns ExprEndResult). This indicates that
* fcinfo_data already contains valid argument data.
*/
bool setArgsValid;
/*
* Flag to remember whether we have registered a shutdown callback for
* this SetExprState. We do so only if funcResultStore or setArgsValid
* has been set at least once (since all the callback is for is to release
* the tuplestore or clear setArgsValid).
*/
bool shutdown_reg; /* a shutdown callback is registered */
/*
* Call parameter structure for the function. This has been initialized
* (by InitFunctionCallInfoData) if func.fn_oid is valid. It also saves
* argument values between calls, when setArgsValid is true.
*/
FunctionCallInfo fcinfo;
} SetExprState;
/* ----------------
* SubPlanState node
* ----------------
*/
typedef struct SubPlanState
{
NodeTag type;
SubPlan *subplan; /* expression plan node */
PlanState *planstate; /* subselect plan's state tree */
PlanState *parent; /* parent plan node's state tree */
ExprState *testexpr; /* state of combining expression */
HeapTuple curTuple; /* copy of most recent tuple from subplan */
Datum curArray; /* most recent array from ARRAY() subplan */
/* these are used when hashing the subselect's output: */
TupleDesc descRight; /* subselect desc after projection */
ProjectionInfo *projLeft; /* for projecting lefthand exprs */
ProjectionInfo *projRight; /* for projecting subselect output */
TupleHashTable hashtable; /* hash table for no-nulls subselect rows */
TupleHashTable hashnulls; /* hash table for rows with null(s) */
bool havehashrows; /* true if hashtable is not empty */
bool havenullrows; /* true if hashnulls is not empty */
MemoryContext tuplesContext; /* context containing hash tables' tuples */
ExprContext *innerecontext; /* econtext for computing inner tuples */
int numCols; /* number of columns being hashed */
/* each of the remaining fields is an array of length numCols: */
AttrNumber *keyColIdx; /* control data for hash tables */
Oid *tab_eq_funcoids; /* equality func oids for table
* datatype(s) */
Oid *tab_collations; /* collations for hash and comparison */
FmgrInfo *tab_hash_funcs; /* hash functions for table datatype(s) */
ExprState *lhs_hash_expr; /* hash expr for lefthand datatype(s) */
FmgrInfo *cur_eq_funcs; /* equality functions for LHS vs. table */
ExprState *cur_eq_comp; /* equality comparator for LHS vs. table */
} SubPlanState;
/*
* DomainConstraintState - one item to check during CoerceToDomain
*
* Note: we consider this to be part of an ExprState tree, so we give it
* a name following the xxxState convention. But there's no directly
* associated plan-tree node.
*/
typedef enum DomainConstraintType
{
DOM_CONSTRAINT_NOTNULL,
DOM_CONSTRAINT_CHECK,
} DomainConstraintType;
typedef struct DomainConstraintState
{
NodeTag type;
DomainConstraintType constrainttype; /* constraint type */
char *name; /* name of constraint (for error msgs) */
Expr *check_expr; /* for CHECK, a boolean expression */
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
/*
* State for JsonExpr evaluation, too big to inline.
*
* This contains the information going into and coming out of the
* EEOP_JSONEXPR_PATH eval step.
*/
typedef struct JsonExprState
{
/* original expression node */
JsonExpr *jsexpr;
/* value/isnull for formatted_expr */
NullableDatum formatted_expr;
/* value/isnull for pathspec */
NullableDatum pathspec;
/* JsonPathVariable entries for passing_values */
List *args;
/*
* Output variables that drive the EEOP_JUMP_IF_NOT_TRUE steps that are
* added for ON ERROR and ON EMPTY expressions, if any.
*
* Reset for each evaluation of EEOP_JSONEXPR_PATH.
*/
/* Set to true if jsonpath evaluation cause an error. */
NullableDatum error;
/* Set to true if the jsonpath evaluation returned 0 items. */
NullableDatum empty;
/*
* Addresses of steps that implement the non-ERROR variant of ON EMPTY and
* ON ERROR behaviors, respectively.
*/
int jump_empty;
int jump_error;
/*
* Address of the step to coerce the result value of jsonpath evaluation
* to the RETURNING type. -1 if no coercion if JsonExpr.use_io_coercion
* is true.
*/
int jump_eval_coercion;
/*
* Address to jump to when skipping all the steps after performing
* ExecEvalJsonExprPath() so as to return whatever the JsonPath* function
* returned as is, that is, in the cases where there's no error and no
* coercion is necessary.
*/
int jump_end;
/*
* RETURNING type input function invocation info when
* JsonExpr.use_io_coercion is true.
*/
FunctionCallInfo input_fcinfo;
/*
* For error-safe evaluation of coercions. When the ON ERROR behavior is
* not ERROR, a pointer to this is passed to ExecInitExprRec() when
* initializing the coercion expressions or to ExecInitJsonCoercion().
*
* Reset for each evaluation of EEOP_JSONEXPR_PATH.
*/
ErrorSaveContext escontext;
} JsonExprState;
/* ----------------------------------------------------------------
* Executor State Trees
*
* An executing query has a PlanState tree paralleling the Plan tree
* that describes the plan.
* ----------------------------------------------------------------
*/
/* ----------------
* ExecProcNodeMtd
*
* This is the method called by ExecProcNode to return the next tuple
* from an executor node. It returns NULL, or an empty TupleTableSlot,
* if no more tuples are available.
* ----------------
*/
typedef TupleTableSlot *(*ExecProcNodeMtd) (PlanState *pstate);
/* ----------------
* PlanState node
*
* We never actually instantiate any PlanState nodes; this is just the common
* abstract superclass for all PlanState-type nodes.
* ----------------
*/
typedef struct PlanState
{
pg_node_attr(abstract)
NodeTag type;
Plan *plan; /* associated Plan node */
EState *state; /* at execution time, states of individual
* nodes point to one EState for the whole
* top-level plan */
ExecProcNodeMtd ExecProcNode; /* function to return next tuple */
ExecProcNodeMtd ExecProcNodeReal; /* actual function, if above is a
* wrapper */
NodeInstrumentation *instrument; /* Optional runtime stats for this
* node */
WorkerNodeInstrumentation *worker_instrument; /* per-worker
* instrumentation */
/* Per-worker JIT instrumentation */
struct SharedJitInstrumentation *worker_jit_instrument;
/*
* Common structural data for all Plan types. These links to subsidiary
* state trees parallel links in the associated plan tree (except for the
* subPlan list, which does not exist in the plan tree).
*/
ExprState *qual; /* boolean qual condition */
PlanState *lefttree; /* input plan tree(s) */
PlanState *righttree;
List *initPlan; /* Init SubPlanState nodes (un-correlated expr
* subselects) */
List *subPlan; /* SubPlanState nodes in my expressions */
/*
* State for management of parameter-change-driven rescanning
*/
Bitmapset *chgParam; /* set of IDs of changed Params */
/*
* Other run-time state needed by most if not all node types.
*/
TupleDesc ps_ResultTupleDesc; /* node's return type */
TupleTableSlot *ps_ResultTupleSlot; /* slot for my result tuples */
ExprContext *ps_ExprContext; /* node's expression-evaluation context */
ProjectionInfo *ps_ProjInfo; /* info for doing tuple projection */
bool async_capable; /* true if node is async-capable */
/*
* Scanslot's descriptor if known. This is a bit of a hack, but otherwise
* it's hard for expression compilation to optimize based on the
* descriptor, without encoding knowledge about all executor nodes.
*/
TupleDesc scandesc;
/*
* Define the slot types for inner, outer and scanslots for expression
* contexts with this state as a parent. If *opsset is set, then
* *opsfixed indicates whether *ops is guaranteed to be the type of slot
* used. That means that every slot in the corresponding
* ExprContext.ecxt_*tuple will point to a slot of that type, while
* evaluating the expression. If *opsfixed is false, but *ops is set,
* that indicates the most likely type of slot.
*
* The scan* fields are set by ExecInitScanTupleSlot(). If that's not
* called, nodes can initialize the fields themselves.
*
* If outer/inneropsset is false, the information is inferred on-demand
* using ExecGetResultSlotOps() on ->righttree/lefttree, using the
* corresponding node's resultops* fields.
*
* The result* fields are automatically set when ExecInitResultSlot is
* used (be it directly or when the slot is created by
* ExecAssignScanProjectionInfo() /
* ExecConditionalAssignProjectionInfo()). If no projection is necessary
* ExecConditionalAssignProjectionInfo() defaults those fields to the scan
* operations.
*/
const TupleTableSlotOps *scanops;
const TupleTableSlotOps *outerops;
const TupleTableSlotOps *innerops;
const TupleTableSlotOps *resultops;
bool scanopsfixed;
bool outeropsfixed;
bool inneropsfixed;
bool resultopsfixed;
bool scanopsset;
bool outeropsset;
bool inneropsset;
bool resultopsset;
} PlanState;
/* ----------------
* these are defined to avoid confusion problems with "left"
* and "right" and "inner" and "outer". The convention is that
* the "left" plan is the "outer" plan and the "right" plan is
* the inner plan, but these make the code more readable.
* ----------------
*/
#define innerPlanState(node) (((PlanState *)(node))->righttree)
#define outerPlanState(node) (((PlanState *)(node))->lefttree)
/* Macros for inline access to certain instrumentation counters */
#define InstrCountTuples2(node, delta) \
do { \
if (((PlanState *)(node))->instrument) \
((PlanState *)(node))->instrument->ntuples2 += (delta); \
} while (0)
#define InstrCountFiltered1(node, delta) \
do { \
if (((PlanState *)(node))->instrument) \
((PlanState *)(node))->instrument->nfiltered1 += (delta); \
} while(0)
#define InstrCountFiltered2(node, delta) \
do { \
if (((PlanState *)(node))->instrument) \
((PlanState *)(node))->instrument->nfiltered2 += (delta); \
} while(0)
/*
* EPQState is state for executing an EvalPlanQual recheck on a candidate
* tuples e.g. in ModifyTable or LockRows.
*
* To execute EPQ a separate EState is created (stored in ->recheckestate),
* which shares some resources, like the rangetable, with the main query's
* EState (stored in ->parentestate). The (sub-)tree of the plan that needs to
* be rechecked (in ->plan), is separately initialized (into
* ->recheckplanstate), but shares plan nodes with the corresponding nodes in
* the main query. The scan nodes in that separate executor tree are changed
* to return only the current tuple of interest for the respective
* table. Those tuples are either provided by the caller (using
* EvalPlanQualSlot), and/or found using the rowmark mechanism (non-locking
* rowmarks by the EPQ machinery itself, locking ones by the caller).
*
* While the plan to be checked may be changed using EvalPlanQualSetPlan(),
* all such plans need to share the same EState.
*/
typedef struct EPQState
{
/* These are initialized by EvalPlanQualInit() and do not change later: */
EState *parentestate; /* main query's EState */
int epqParam; /* ID of Param to force scan node re-eval */
List *resultRelations; /* integer list of RT indexes, or NIL */
/*
* relsubs_slot[scanrelid - 1] holds the EPQ test tuple to be returned by
* the scan node for the scanrelid'th RT index, in place of performing an
* actual table scan. Callers should use EvalPlanQualSlot() to fetch
* these slots.
*/
List *tuple_table; /* tuple table for relsubs_slot */
TupleTableSlot **relsubs_slot;
/*
* Initialized by EvalPlanQualInit(), may be changed later with
* EvalPlanQualSetPlan():
*/
Plan *plan; /* plan tree to be executed */
List *arowMarks; /* ExecAuxRowMarks (non-locking only) */
/*
* The original output tuple to be rechecked. Set by
* EvalPlanQualSetSlot(), before EvalPlanQualNext() or EvalPlanQual() may
* be called.
*/
TupleTableSlot *origslot;
/* Initialized or reset by EvalPlanQualBegin(): */
EState *recheckestate; /* EState for EPQ execution, see above */
/*
* Rowmarks that can be fetched on-demand using
* EvalPlanQualFetchRowMark(), indexed by scanrelid - 1. Only non-locking
* rowmarks.
*/
ExecAuxRowMark **relsubs_rowmark;
/*
* relsubs_done[scanrelid - 1] is true if there is no EPQ tuple for this
* target relation or it has already been fetched in the current scan of
* this target relation within the current EvalPlanQual test.
*/
bool *relsubs_done;
/*
* relsubs_blocked[scanrelid - 1] is true if there is no EPQ tuple for
* this target relation during the current EvalPlanQual test. We keep
* these flags set for all relids listed in resultRelations, but
* transiently clear the one for the relation whose tuple is actually
* passed to EvalPlanQual().
*/
bool *relsubs_blocked;
PlanState *recheckplanstate; /* EPQ specific exec nodes, for ->plan */
} EPQState;
/* ----------------
* ResultState information
* ----------------
*/
typedef struct ResultState
{
PlanState ps; /* its first field is NodeTag */
ExprState *resconstantqual;
bool rs_done; /* are we done? */
bool rs_checkqual; /* do we need to check the qual? */
} ResultState;
/* ----------------
* ProjectSetState information
*
* Note: at least one of the "elems" will be a SetExprState; the rest are
* regular ExprStates.
* ----------------
*/
typedef struct ProjectSetState
{
PlanState ps; /* its first field is NodeTag */
Node **elems; /* array of expression states */
ExprDoneCond *elemdone; /* array of per-SRF is-done states */
int nelems; /* length of elemdone[] array */
bool pending_srf_tuples; /* still evaluating srfs in tlist? */
MemoryContext argcontext; /* context for SRF arguments */
} ProjectSetState;
/* flags for mt_merge_subcommands */
#define MERGE_INSERT 0x01
#define MERGE_UPDATE 0x02
#define MERGE_DELETE 0x04
/* ----------------
* ModifyTableState information
* ----------------
*/
typedef struct ModifyTableState
{
PlanState ps; /* its first field is NodeTag */
CmdType operation; /* INSERT, UPDATE, DELETE, or MERGE */
bool canSetTag; /* do we set the command tag/es_processed? */
bool mt_done; /* are we done? */
int mt_nrels; /* number of entries in resultRelInfo[] */
ResultRelInfo *resultRelInfo; /* info about target relation(s) */
/*
* Target relation mentioned in the original statement, used to fire
* statement-level triggers and as the root for tuple routing. (This
* might point to one of the resultRelInfo[] entries, but it can also be a
* distinct struct.)
*/
ResultRelInfo *rootResultRelInfo;
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
/*
* These fields are used for inherited UPDATE and DELETE, to track which
* target relation a given tuple is from. If there are a lot of target
* relations, we use a hash table to translate table OIDs to
* resultRelInfo[] indexes; otherwise mt_resultOidHash is NULL.
*/
int mt_resultOidAttno; /* resno of "tableoid" junk attr */
Oid mt_lastResultOid; /* last-seen value of tableoid */
int mt_lastResultIndex; /* corresponding index in resultRelInfo[] */
HTAB *mt_resultOidHash; /* optional hash table to speed lookups */
/*
* Slot for storing tuples in the root partitioned table's rowtype during
* an UPDATE of a partitioned table.
*/
TupleTableSlot *mt_root_tuple_slot;
/* Tuple-routing support info */
struct PartitionTupleRouting *mt_partition_tuple_routing;
/* controls transition table population for specified operation */
struct TransitionCaptureState *mt_transition_capture;
/* controls transition table population for INSERT...ON CONFLICT UPDATE */
struct TransitionCaptureState *mt_oc_transition_capture;
/* Flags showing which subcommands are present INS/UPD/DEL/DO NOTHING */
int mt_merge_subcommands;
/* For MERGE, the action currently being executed */
MergeActionState *mt_merge_action;
/*
* For MERGE, if there is a pending NOT MATCHED [BY TARGET] action to be
* performed, this will be the last tuple read from the subplan; otherwise
* it will be NULL --- see the comments in ExecMerge().
*/
TupleTableSlot *mt_merge_pending_not_matched;
/* tuple counters for MERGE */
double mt_merge_inserted;
double mt_merge_updated;
double mt_merge_deleted;
/*
* Lists of valid updateColnosLists, mergeActionLists, and
* mergeJoinConditions. These contain only entries for unpruned
* relations, filtered from the corresponding lists in ModifyTable.
*/
List *mt_updateColnosLists;
List *mt_mergeActionLists;
List *mt_mergeJoinConditions;
} ModifyTableState;
/* ----------------
* AppendState information
*
* nplans how many plans are in the array
* whichplan which synchronous plan is being executed (0 .. n-1)
* or a special negative value. See nodeAppend.c.
* prune_state details required to allow partitions to be
* eliminated from the scan, or NULL if not possible.
* valid_subplans for runtime pruning, valid synchronous appendplans
* indexes to scan.
* ----------------
*/
struct AppendState;
typedef struct AppendState AppendState;
struct ParallelAppendState;
typedef struct ParallelAppendState ParallelAppendState;
struct PartitionPruneState;
struct AppendState
{
PlanState ps; /* its first field is NodeTag */
PlanState **appendplans; /* array of PlanStates for my inputs */
int as_nplans;
int as_whichplan;
bool as_begun; /* false means need to initialize */
Bitmapset *as_asyncplans; /* asynchronous plans indexes */
int as_nasyncplans; /* # of asynchronous plans */
AsyncRequest **as_asyncrequests; /* array of AsyncRequests */
TupleTableSlot **as_asyncresults; /* unreturned results of async plans */
int as_nasyncresults; /* # of valid entries in as_asyncresults */
bool as_syncdone; /* true if all synchronous plans done in
* asynchronous mode, else false */
int as_nasyncremain; /* # of remaining asynchronous plans */
Bitmapset *as_needrequest; /* asynchronous plans needing a new request */
struct WaitEventSet *as_eventset; /* WaitEventSet used to configure file
* descriptor wait events */
int as_first_partial_plan; /* Index of 'appendplans' containing
* the first partial plan */
ParallelAppendState *as_pstate; /* parallel coordination info */
Size pstate_len; /* size of parallel coordination info */
struct PartitionPruneState *as_prune_state;
bool as_valid_subplans_identified; /* is as_valid_subplans valid? */
Bitmapset *as_valid_subplans;
Bitmapset *as_valid_asyncplans; /* valid asynchronous plans indexes */
bool (*choose_next_subplan) (AppendState *);
};
/* ----------------
* MergeAppendState information
*
* nplans how many plans are in the array
* nkeys number of sort key columns
* sortkeys sort keys in SortSupport representation
* slots current output tuple of each subplan
* heap heap of active tuples
* initialized true if we have fetched first tuple from each subplan
* prune_state details required to allow partitions to be
* eliminated from the scan, or NULL if not possible.
* valid_subplans for runtime pruning, valid mergeplans indexes to
* scan.
* ----------------
*/
typedef struct MergeAppendState
{
PlanState ps; /* its first field is NodeTag */
PlanState **mergeplans; /* array of PlanStates for my inputs */
int ms_nplans;
int ms_nkeys;
SortSupport ms_sortkeys; /* array of length ms_nkeys */
TupleTableSlot **ms_slots; /* array of length ms_nplans */
struct binaryheap *ms_heap; /* binary heap of slot indices */
bool ms_initialized; /* are subplans started? */
struct PartitionPruneState *ms_prune_state;
Bitmapset *ms_valid_subplans;
} MergeAppendState;
/* ----------------
* RecursiveUnionState information
*
* RecursiveUnionState is used for performing a recursive union.
*
* recursing T when we're done scanning the non-recursive term
* intermediate_empty T if intermediate_table is currently empty
* working_table working table (to be scanned by recursive term)
* intermediate_table current recursive output (next generation of WT)
* ----------------
*/
typedef struct RecursiveUnionState
{
PlanState ps; /* its first field is NodeTag */
bool recursing;
bool intermediate_empty;
Tuplestorestate *working_table;
Tuplestorestate *intermediate_table;
/* Remaining fields are unused in UNION ALL case */
Oid *eqfuncoids; /* per-grouping-field equality fns */
FmgrInfo *hashfunctions; /* per-grouping-field hash fns */
MemoryContext tempContext; /* short-term context for comparisons */
TupleHashTable hashtable; /* hash table for tuples already seen */
MemoryContext tuplesContext; /* context containing hash table's tuples */
} RecursiveUnionState;
/* ----------------
* BitmapAndState information
* ----------------
*/
typedef struct BitmapAndState
{
PlanState ps; /* its first field is NodeTag */
PlanState **bitmapplans; /* array of PlanStates for my inputs */
int nplans; /* number of input plans */
} BitmapAndState;
/* ----------------
* BitmapOrState information
* ----------------
*/
typedef struct BitmapOrState
{
PlanState ps; /* its first field is NodeTag */
PlanState **bitmapplans; /* array of PlanStates for my inputs */
int nplans; /* number of input plans */
} BitmapOrState;
/* ----------------------------------------------------------------
* Scan State Information
* ----------------------------------------------------------------
*/
/* ----------------
* ScanState information
*
* ScanState extends PlanState for node types that represent
* scans of an underlying relation. It can also be used for nodes
* that scan the output of an underlying plan node --- in that case,
* only ScanTupleSlot is actually useful, and it refers to the tuple
* retrieved from the subplan.
*
* currentRelation relation being scanned (NULL if none)
* currentScanDesc current scan descriptor for scan (NULL if none)
* ScanTupleSlot pointer to slot in tuple table holding scan tuple
* ----------------
*/
typedef struct ScanState
{
PlanState ps; /* its first field is NodeTag */
Relation ss_currentRelation;
struct TableScanDescData *ss_currentScanDesc;
TupleTableSlot *ss_ScanTupleSlot;
} ScanState;
/* ----------------
* SeqScanState information
* ----------------
*/
typedef struct SeqScanState
{
ScanState ss; /* its first field is NodeTag */
Size pscan_len; /* size of parallel heap scan descriptor */
struct SharedSeqScanInstrumentation *sinstrument;
} SeqScanState;
/* ----------------
* SampleScanState information
* ----------------
*/
typedef struct SampleScanState
{
ScanState ss;
List *args; /* expr states for TABLESAMPLE params */
ExprState *repeatable; /* expr state for REPEATABLE expr */
/* use struct pointer to avoid including tsmapi.h here */
struct TsmRoutine *tsmroutine; /* descriptor for tablesample method */
void *tsm_state; /* tablesample method can keep state here */
bool use_bulkread; /* use bulkread buffer access strategy? */
bool use_pagemode; /* use page-at-a-time visibility checking? */
bool begun; /* false means need to call BeginSampleScan */
uint32 seed; /* random seed */
int64 donetuples; /* number of tuples already returned */
bool haveblock; /* has a block for sampling been determined */
bool done; /* exhausted all tuples? */
} SampleScanState;
/*
* These structs store information about index quals that don't have simple
* constant right-hand sides. See comments for ExecIndexBuildScanKeys()
* for discussion.
*/
typedef struct
{
ScanKeyData *scan_key; /* scankey to put value into */
ExprState *key_expr; /* expr to evaluate to get value */
bool key_toastable; /* is expr's result a toastable datatype? */
} IndexRuntimeKeyInfo;
typedef struct
{
ScanKeyData *scan_key; /* scankey to put value into */
ExprState *array_expr; /* expr to evaluate to get array value */
int next_elem; /* next array element to use */
int num_elems; /* number of elems in current array value */
Datum *elem_values; /* array of num_elems Datums */
bool *elem_nulls; /* array of num_elems is-null flags */
} IndexArrayKeyInfo;
/* ----------------
* IndexScanState information
*
* indexqualorig execution state for indexqualorig expressions
* indexorderbyorig execution state for indexorderbyorig expressions
* ScanKeys Skey structures for index quals
* NumScanKeys number of ScanKeys
* OrderByKeys Skey structures for index ordering operators
* NumOrderByKeys number of OrderByKeys
* RuntimeKeys info about Skeys that must be evaluated at runtime
* NumRuntimeKeys number of RuntimeKeys
* RuntimeKeysReady true if runtime Skeys have been computed
* RuntimeContext expr context for evaling runtime Skeys
* RelationDesc index relation descriptor
* ScanDesc index scan descriptor
* Instrument local index scan instrumentation
* SharedInfo parallel worker instrumentation (no leader entry)
*
* ReorderQueue tuples that need reordering due to re-check
* ReachedEnd have we fetched all tuples from index already?
* OrderByValues values of ORDER BY exprs of last fetched tuple
* OrderByNulls null flags for OrderByValues
* SortSupport for reordering ORDER BY exprs
* OrderByTypByVals is the datatype of order by expression pass-by-value?
* OrderByTypLens typlens of the datatypes of order by expressions
* PscanLen size of parallel index scan descriptor
* ----------------
*/
typedef struct IndexScanState
{
ScanState ss; /* its first field is NodeTag */
ExprState *indexqualorig;
List *indexorderbyorig;
ScanKeyData *iss_ScanKeys;
int iss_NumScanKeys;
ScanKeyData *iss_OrderByKeys;
int iss_NumOrderByKeys;
IndexRuntimeKeyInfo *iss_RuntimeKeys;
int iss_NumRuntimeKeys;
bool iss_RuntimeKeysReady;
ExprContext *iss_RuntimeContext;
Relation iss_RelationDesc;
struct IndexScanDescData *iss_ScanDesc;
IndexScanInstrumentation *iss_Instrument;
SharedIndexScanInstrumentation *iss_SharedInfo;
/* These are needed for re-checking ORDER BY expr ordering */
pairingheap *iss_ReorderQueue;
bool iss_ReachedEnd;
Datum *iss_OrderByValues;
bool *iss_OrderByNulls;
SortSupport iss_SortSupport;
bool *iss_OrderByTypByVals;
int16 *iss_OrderByTypLens;
Size iss_PscanLen;
} IndexScanState;
/* ----------------
* IndexOnlyScanState information
*
* recheckqual execution state for recheckqual expressions
* ScanKeys Skey structures for index quals
* NumScanKeys number of ScanKeys
* OrderByKeys Skey structures for index ordering operators
* NumOrderByKeys number of OrderByKeys
* RuntimeKeys info about Skeys that must be evaluated at runtime
* NumRuntimeKeys number of RuntimeKeys
* RuntimeKeysReady true if runtime Skeys have been computed
* RuntimeContext expr context for evaling runtime Skeys
* RelationDesc index relation descriptor
* ScanDesc index scan descriptor
* Instrument local index scan instrumentation
* SharedInfo parallel worker instrumentation (no leader entry)
* TableSlot slot for holding tuples fetched from the table
* VMBuffer buffer in use for visibility map testing, if any
* PscanLen size of parallel index-only scan descriptor
* NameCStringAttNums attnums of name typed columns to pad to NAMEDATALEN
* NameCStringCount number of elements in the NameCStringAttNums array
* ----------------
*/
typedef struct IndexOnlyScanState
{
ScanState ss; /* its first field is NodeTag */
ExprState *recheckqual;
ScanKeyData *ioss_ScanKeys;
int ioss_NumScanKeys;
ScanKeyData *ioss_OrderByKeys;
int ioss_NumOrderByKeys;
IndexRuntimeKeyInfo *ioss_RuntimeKeys;
int ioss_NumRuntimeKeys;
bool ioss_RuntimeKeysReady;
ExprContext *ioss_RuntimeContext;
Relation ioss_RelationDesc;
struct IndexScanDescData *ioss_ScanDesc;
IndexScanInstrumentation *ioss_Instrument;
SharedIndexScanInstrumentation *ioss_SharedInfo;
TupleTableSlot *ioss_TableSlot;
Buffer ioss_VMBuffer;
Size ioss_PscanLen;
AttrNumber *ioss_NameCStringAttNums;
int ioss_NameCStringCount;
} IndexOnlyScanState;
/* ----------------
* BitmapIndexScanState information
*
* result bitmap to return output into, or NULL
* ScanKeys Skey structures for index quals
* NumScanKeys number of ScanKeys
* RuntimeKeys info about Skeys that must be evaluated at runtime
* NumRuntimeKeys number of RuntimeKeys
* ArrayKeys info about Skeys that come from ScalarArrayOpExprs
* NumArrayKeys number of ArrayKeys
* RuntimeKeysReady true if runtime Skeys have been computed
* RuntimeContext expr context for evaling runtime Skeys
* RelationDesc index relation descriptor
* ScanDesc index scan descriptor
* Instrument local index scan instrumentation
* SharedInfo parallel worker instrumentation (no leader entry)
* ----------------
*/
typedef struct BitmapIndexScanState
{
ScanState ss; /* its first field is NodeTag */
TIDBitmap *biss_result;
ScanKeyData *biss_ScanKeys;
int biss_NumScanKeys;
IndexRuntimeKeyInfo *biss_RuntimeKeys;
int biss_NumRuntimeKeys;
IndexArrayKeyInfo *biss_ArrayKeys;
int biss_NumArrayKeys;
bool biss_RuntimeKeysReady;
ExprContext *biss_RuntimeContext;
Relation biss_RelationDesc;
struct IndexScanDescData *biss_ScanDesc;
IndexScanInstrumentation *biss_Instrument;
SharedIndexScanInstrumentation *biss_SharedInfo;
} BitmapIndexScanState;
/* ----------------
* BitmapHeapScanState information
*
* bitmapqualorig execution state for bitmapqualorig expressions
* tbm bitmap obtained from child index scan(s)
* stats execution statistics
* initialized is node is ready to iterate
* pstate shared state for parallel bitmap scan
* sinstrument statistics for parallel workers
* recheck do current page's tuples need recheck
* ----------------
*/
/* this struct is defined in nodeBitmapHeapscan.c */
typedef struct ParallelBitmapHeapState ParallelBitmapHeapState;
typedef struct BitmapHeapScanState
{
ScanState ss; /* its first field is NodeTag */
ExprState *bitmapqualorig;
TIDBitmap *tbm;
BitmapHeapScanInstrumentation stats;
bool initialized;
ParallelBitmapHeapState *pstate;
SharedBitmapHeapInstrumentation *sinstrument;
bool recheck;
} BitmapHeapScanState;
/* ----------------
* TidScanState information
*
* tidexprs list of TidExpr structs (see nodeTidscan.c)
* isCurrentOf scan has a CurrentOfExpr qual
* NumTids number of tids in this scan
* TidPtr index of currently fetched tid
* TidList evaluated item pointers (array of size NumTids)
* ----------------
*/
typedef struct TidScanState
{
ScanState ss; /* its first field is NodeTag */
List *tss_tidexprs;
bool tss_isCurrentOf;
int tss_NumTids;
int tss_TidPtr;
ItemPointerData *tss_TidList;
} TidScanState;
/* ----------------
* TidRangeScanState information
*
* trss_tidexprs list of TidOpExpr structs (see nodeTidrangescan.c)
* trss_mintid the lowest TID in the scan range
* trss_maxtid the highest TID in the scan range
* trss_inScan is a scan currently in progress?
* trss_pscanlen size of parallel heap scan descriptor
* ----------------
*/
typedef struct TidRangeScanState
{
ScanState ss; /* its first field is NodeTag */
List *trss_tidexprs;
ItemPointerData trss_mintid;
ItemPointerData trss_maxtid;
bool trss_inScan;
Size trss_pscanlen;
struct SharedTidRangeScanInstrumentation *trss_sinstrument;
} TidRangeScanState;
/* ----------------
* SubqueryScanState information
*
* SubqueryScanState is used for scanning a sub-query in the range table.
* ScanTupleSlot references the current output tuple of the sub-query.
* ----------------
*/
typedef struct SubqueryScanState
{
ScanState ss; /* its first field is NodeTag */
PlanState *subplan;
} SubqueryScanState;
/* ----------------
* FunctionScanState information
*
* Function nodes are used to scan the results of a
* function appearing in FROM (typically a function returning set).
*
* eflags node's capability flags
* ordinality is this scan WITH ORDINALITY?
* simple true if we have 1 function and no ordinality
* ordinal current ordinal column value
* nfuncs number of functions being executed
* funcstates per-function execution states (private in
* nodeFunctionscan.c)
* argcontext memory context to evaluate function arguments in
* ----------------
*/
struct FunctionScanPerFuncState;
typedef struct FunctionScanState
{
ScanState ss; /* its first field is NodeTag */
int eflags;
bool ordinality;
bool simple;
int64 ordinal;
int nfuncs;
struct FunctionScanPerFuncState *funcstates; /* array of length nfuncs */
MemoryContext argcontext;
} FunctionScanState;
/* ----------------
* ValuesScanState information
*
* ValuesScan nodes are used to scan the results of a VALUES list
*
* rowcontext per-expression-list context
* exprlists array of expression lists being evaluated
* exprstatelists array of expression state lists, for SubPlans only
* array_len size of above arrays
* curr_idx current array index (0-based)
*
* Note: ss.ps.ps_ExprContext is used to evaluate any qual or projection
* expressions attached to the node. We create a second ExprContext,
* rowcontext, in which to build the executor expression state for each
* Values sublist. Resetting this context lets us get rid of expression
* state for each row, avoiding major memory leakage over a long values list.
* However, that doesn't work for sublists containing SubPlans, because a
* SubPlan has to be connected up to the outer plan tree to work properly.
* Therefore, for only those sublists containing SubPlans, we do expression
* state construction at executor start, and store those pointers in
* exprstatelists[]. NULL entries in that array correspond to simple
* subexpressions that are handled as described above.
* ----------------
*/
typedef struct ValuesScanState
{
ScanState ss; /* its first field is NodeTag */
ExprContext *rowcontext;
List **exprlists;
List **exprstatelists;
int array_len;
int curr_idx;
} ValuesScanState;
/* ----------------
* TableFuncScanState node
*
* Used in table-expression functions like XMLTABLE.
* ----------------
*/
typedef struct TableFuncScanState
{
ScanState ss; /* its first field is NodeTag */
ExprState *docexpr; /* state for document expression */
ExprState *rowexpr; /* state for row-generating expression */
List *colexprs; /* state for column-generating expression */
List *coldefexprs; /* state for column default expressions */
List *colvalexprs; /* state for column value expressions */
List *passingvalexprs; /* state for PASSING argument expressions */
List *ns_names; /* same as TableFunc.ns_names */
List *ns_uris; /* list of states of namespace URI exprs */
Bitmapset *notnulls; /* nullability flag for each output column */
void *opaque; /* table builder private space */
const struct TableFuncRoutine *routine; /* table builder methods */
FmgrInfo *in_functions; /* input function for each column */
Oid *typioparams; /* typioparam for each column */
int64 ordinal; /* row number to be output next */
MemoryContext perTableCxt; /* per-table context */
Tuplestorestate *tupstore; /* output tuple store */
} TableFuncScanState;
/* ----------------
* CteScanState information
*
* CteScan nodes are used to scan a CommonTableExpr query.
*
* Multiple CteScan nodes can read out from the same CTE query. We use
* a tuplestore to hold rows that have been read from the CTE query but
* not yet consumed by all readers.
* ----------------
*/
typedef struct CteScanState
{
ScanState ss; /* its first field is NodeTag */
int eflags; /* capability flags to pass to tuplestore */
int readptr; /* index of my tuplestore read pointer */
PlanState *cteplanstate; /* PlanState for the CTE query itself */
/* Link to the "leader" CteScanState (possibly this same node) */
struct CteScanState *leader;
/* The remaining fields are only valid in the "leader" CteScanState */
Tuplestorestate *cte_table; /* rows already read from the CTE query */
bool eof_cte; /* reached end of CTE query? */
} CteScanState;
/* ----------------
* NamedTuplestoreScanState information
*
* NamedTuplestoreScan nodes are used to scan a Tuplestore created and
* named prior to execution of the query. An example is a transition
* table for an AFTER trigger.
*
* Multiple NamedTuplestoreScan nodes can read out from the same Tuplestore.
* ----------------
*/
typedef struct NamedTuplestoreScanState
{
ScanState ss; /* its first field is NodeTag */
int readptr; /* index of my tuplestore read pointer */
TupleDesc tupdesc; /* format of the tuples in the tuplestore */
Tuplestorestate *relation; /* the rows */
} NamedTuplestoreScanState;
/* ----------------
* WorkTableScanState information
*
* WorkTableScan nodes are used to scan the work table created by
* a RecursiveUnion node. We locate the RecursiveUnion node
* during executor startup.
* ----------------
*/
typedef struct WorkTableScanState
{
ScanState ss; /* its first field is NodeTag */
RecursiveUnionState *rustate;
} WorkTableScanState;
/* ----------------
* ForeignScanState information
*
* ForeignScan nodes are used to scan foreign-data tables.
* ----------------
*/
typedef struct ForeignScanState
{
ScanState ss; /* its first field is NodeTag */
ExprState *fdw_recheck_quals; /* original quals not in ss.ps.qual */
Size pscan_len; /* size of parallel coordination information */
ResultRelInfo *resultRelInfo; /* result rel info, if UPDATE or DELETE */
/* use struct pointer to avoid including fdwapi.h here */
struct FdwRoutine *fdwroutine;
void *fdw_state; /* foreign-data wrapper can keep state here */
} ForeignScanState;
/* ----------------
* CustomScanState information
*
* CustomScan nodes are used to execute custom code within executor.
*
* Core code must avoid assuming that the CustomScanState is only as large as
* the structure declared here; providers are allowed to make it the first
* element in a larger structure, and typically would need to do so. The
* struct is actually allocated by the CreateCustomScanState method associated
* with the plan node. Any additional fields can be initialized there, or in
* the BeginCustomScan method.
* ----------------
*/
struct CustomExecMethods;
typedef struct CustomScanState
{
ScanState ss;
uint32 flags; /* mask of CUSTOMPATH_* flags, see
* nodes/extensible.h */
List *custom_ps; /* list of child PlanState nodes, if any */
Size pscan_len; /* size of parallel coordination information */
const struct CustomExecMethods *methods;
const struct TupleTableSlotOps *slotOps;
} CustomScanState;
/* ----------------------------------------------------------------
* Join State Information
* ----------------------------------------------------------------
*/
/* ----------------
* JoinState information
*
* Superclass for state nodes of join plans.
* ----------------
*/
typedef struct JoinState
{
PlanState ps;
JoinType jointype;
bool single_match; /* True if we should skip to next outer tuple
* after finding one inner match */
ExprState *joinqual; /* JOIN quals (in addition to ps.qual) */
} JoinState;
/* ----------------
* NestLoopState information
*
* NeedNewOuter true if need new outer tuple on next call
* MatchedOuter true if found a join match for current outer tuple
* NullInnerTupleSlot prepared null tuple for left outer joins
* ----------------
*/
typedef struct NestLoopState
{
JoinState js; /* its first field is NodeTag */
bool nl_NeedNewOuter;
bool nl_MatchedOuter;
TupleTableSlot *nl_NullInnerTupleSlot;
} NestLoopState;
/* ----------------
* MergeJoinState information
*
* NumClauses number of mergejoinable join clauses
* Clauses info for each mergejoinable clause
* JoinState current state of ExecMergeJoin state machine
* SkipMarkRestore true if we may skip Mark and Restore operations
* ExtraMarks true to issue extra Mark operations on inner scan
* ConstFalseJoin true if we have a constant-false joinqual
* FillOuter true if should emit unjoined outer tuples anyway
* FillInner true if should emit unjoined inner tuples anyway
* MatchedOuter true if found a join match for current outer tuple
* MatchedInner true if found a join match for current inner tuple
* OuterTupleSlot slot in tuple table for cur outer tuple
* InnerTupleSlot slot in tuple table for cur inner tuple
* MarkedTupleSlot slot in tuple table for marked tuple
* NullOuterTupleSlot prepared null tuple for right outer joins
* NullInnerTupleSlot prepared null tuple for left outer joins
* OuterEContext workspace for computing outer tuple's join values
* InnerEContext workspace for computing inner tuple's join values
* ----------------
*/
/* private in nodeMergejoin.c: */
typedef struct MergeJoinClauseData *MergeJoinClause;
typedef struct MergeJoinState
{
JoinState js; /* its first field is NodeTag */
int mj_NumClauses;
MergeJoinClause mj_Clauses; /* array of length mj_NumClauses */
int mj_JoinState;
bool mj_SkipMarkRestore;
bool mj_ExtraMarks;
bool mj_ConstFalseJoin;
bool mj_FillOuter;
bool mj_FillInner;
bool mj_MatchedOuter;
bool mj_MatchedInner;
TupleTableSlot *mj_OuterTupleSlot;
TupleTableSlot *mj_InnerTupleSlot;
TupleTableSlot *mj_MarkedTupleSlot;
TupleTableSlot *mj_NullOuterTupleSlot;
TupleTableSlot *mj_NullInnerTupleSlot;
ExprContext *mj_OuterEContext;
ExprContext *mj_InnerEContext;
} MergeJoinState;
/* ----------------
* HashJoinState information
*
* hashclauses original form of the hashjoin condition
* hj_OuterHash ExprState for hashing outer keys
* hj_HashTable hash table for the hashjoin
* (NULL if table not built yet)
* hj_CurHashValue hash value for current outer tuple
* hj_CurBucketNo regular bucket# for current outer tuple
* hj_CurSkewBucketNo skew bucket# for current outer tuple
* hj_CurTuple last inner tuple matched to current outer
* tuple, or NULL if starting search
* (hj_CurXXX variables are undefined if
* OuterTupleSlot is empty!)
* hj_OuterTupleSlot tuple slot for outer tuples
* hj_HashTupleSlot tuple slot for inner (hashed) tuples
* hj_NullOuterTupleSlot prepared null tuple for right/right-anti/full
* outer joins
* hj_NullInnerTupleSlot prepared null tuple for left/full outer joins
* hj_NullOuterTupleStore tuplestore holding outer tuples that have
* null join keys (but must be emitted anyway)
* hj_FirstOuterTupleSlot first tuple retrieved from outer plan
* hj_JoinState current state of ExecHashJoin state machine
* hj_KeepNullTuples true to keep outer tuples with null join keys
* hj_MatchedOuter true if found a join match for current outer
* hj_OuterNotEmpty true if outer relation known not empty
* ----------------
*/
/* these structs are defined in executor/hashjoin.h: */
typedef struct HashJoinTupleData *HashJoinTuple;
typedef struct HashJoinTableData *HashJoinTable;
typedef struct HashJoinState
{
JoinState js; /* its first field is NodeTag */
ExprState *hashclauses;
ExprState *hj_OuterHash;
HashJoinTable hj_HashTable;
uint32 hj_CurHashValue;
int hj_CurBucketNo;
int hj_CurSkewBucketNo;
HashJoinTuple hj_CurTuple;
TupleTableSlot *hj_OuterTupleSlot;
TupleTableSlot *hj_HashTupleSlot;
TupleTableSlot *hj_NullOuterTupleSlot;
TupleTableSlot *hj_NullInnerTupleSlot;
Tuplestorestate *hj_NullOuterTupleStore;
TupleTableSlot *hj_FirstOuterTupleSlot;
int hj_JoinState;
bool hj_KeepNullTuples;
bool hj_MatchedOuter;
bool hj_OuterNotEmpty;
} HashJoinState;
/* ----------------------------------------------------------------
* Materialization State Information
* ----------------------------------------------------------------
*/
/* ----------------
* MaterialState information
*
* materialize nodes are used to materialize the results
* of a subplan into a temporary file.
*
* ss.ss_ScanTupleSlot refers to output of underlying plan.
* ----------------
*/
typedef struct MaterialState
{
ScanState ss; /* its first field is NodeTag */
int eflags; /* capability flags to pass to tuplestore */
bool eof_underlying; /* reached end of underlying plan? */
Tuplestorestate *tuplestorestate;
} MaterialState;
struct MemoizeEntry;
struct MemoizeTuple;
struct MemoizeKey;
/* ----------------
* MemoizeState information
*
* memoize nodes are used to cache recent and commonly seen results from
* a parameterized scan.
* ----------------
*/
typedef struct MemoizeState
{
ScanState ss; /* its first field is NodeTag */
int mstatus; /* value of ExecMemoize state machine */
int nkeys; /* number of cache keys */
struct memoize_hash *hashtable; /* hash table for cache entries */
TupleDesc hashkeydesc; /* tuple descriptor for cache keys */
TupleTableSlot *tableslot; /* min tuple slot for existing cache entries */
TupleTableSlot *probeslot; /* virtual slot used for hash lookups */
ExprState *cache_eq_expr; /* Compare exec params to hash key */
ExprState **param_exprs; /* exprs containing the parameters to this
* node */
FmgrInfo *hashfunctions; /* lookup data for hash funcs nkeys in size */
Oid *collations; /* collation for comparisons nkeys in size */
uint64 mem_used; /* bytes of memory used by cache */
uint64 mem_limit; /* memory limit in bytes for the cache */
MemoryContext tableContext; /* memory context to store cache data */
dlist_head lru_list; /* least recently used entry list */
struct MemoizeTuple *last_tuple; /* Used to point to the last tuple
* returned during a cache hit and the
* tuple we last stored when
* populating the cache. */
struct MemoizeEntry *entry; /* the entry that 'last_tuple' belongs to or
* NULL if 'last_tuple' is NULL. */
bool singlerow; /* true if the cache entry is to be marked as
* complete after caching the first tuple. */
bool binary_mode; /* true when cache key should be compared bit
* by bit, false when using hash equality ops */
MemoizeInstrumentation stats; /* execution statistics */
SharedMemoizeInfo *shared_info; /* statistics for parallel workers */
Bitmapset *keyparamids; /* Param->paramids of expressions belonging to
* param_exprs */
} MemoizeState;
/* ----------------
* When performing sorting by multiple keys, it's possible that the input
* dataset is already sorted on a prefix of those keys. We call these
* "presorted keys".
* PresortedKeyData represents information about one such key.
* ----------------
*/
typedef struct PresortedKeyData
{
FmgrInfo flinfo; /* comparison function info */
FunctionCallInfo fcinfo; /* comparison function call info */
OffsetNumber attno; /* attribute number in tuple */
} PresortedKeyData;
/* ----------------
* SortState information
* ----------------
*/
typedef struct SortState
{
ScanState ss; /* its first field is NodeTag */
bool randomAccess; /* need random access to sort output? */
bool bounded; /* is the result set bounded? */
int64 bound; /* if bounded, how many tuples are needed */
bool sort_Done; /* sort completed yet? */
bool bounded_Done; /* value of bounded we did the sort with */
int64 bound_Done; /* value of bound we did the sort with */
void *tuplesortstate; /* private state of tuplesort.c */
bool am_worker; /* are we a worker? */
bool datumSort; /* Datum sort instead of tuple sort? */
SharedSortInfo *shared_info; /* one entry per worker */
} SortState;
typedef enum
{
INCSORT_LOADFULLSORT,
INCSORT_LOADPREFIXSORT,
INCSORT_READFULLSORT,
INCSORT_READPREFIXSORT,
} IncrementalSortExecutionStatus;
typedef struct IncrementalSortState
{
ScanState ss; /* its first field is NodeTag */
bool bounded; /* is the result set bounded? */
int64 bound; /* if bounded, how many tuples are needed */
bool outerNodeDone; /* finished fetching tuples from outer node */
int64 bound_Done; /* value of bound we did the sort with */
IncrementalSortExecutionStatus execution_status;
int64 n_fullsort_remaining;
Tuplesortstate *fullsort_state; /* private state of tuplesort.c */
Tuplesortstate *prefixsort_state; /* private state of tuplesort.c */
/* the keys by which the input path is already sorted */
PresortedKeyData *presorted_keys;
IncrementalSortInfo incsort_info;
/* slot for pivot tuple defining values of presorted keys within group */
TupleTableSlot *group_pivot;
TupleTableSlot *transfer_tuple;
bool am_worker; /* are we a worker? */
SharedIncrementalSortInfo *shared_info; /* one entry per worker */
} IncrementalSortState;
/* ---------------------
* GroupState information
* ---------------------
*/
typedef struct GroupState
{
ScanState ss; /* its first field is NodeTag */
ExprState *eqfunction; /* equality function */
bool grp_done; /* indicates completion of Group scan */
} GroupState;
/* ---------------------
* AggState information
*
* ss.ss_ScanTupleSlot refers to output of underlying plan.
*
* Note: ss.ps.ps_ExprContext contains ecxt_aggvalues and
* ecxt_aggnulls arrays, which hold the computed agg values for the current
* input group during evaluation of an Agg node's output tuple(s). We
* create a second ExprContext, tmpcontext, in which to evaluate input
* expressions and run the aggregate transition functions.
* ---------------------
*/
/* these structs are private in nodeAgg.c: */
typedef struct AggStatePerAggData *AggStatePerAgg;
typedef struct AggStatePerTransData *AggStatePerTrans;
typedef struct AggStatePerGroupData *AggStatePerGroup;
typedef struct AggStatePerPhaseData *AggStatePerPhase;
typedef struct AggStatePerHashData *AggStatePerHash;
typedef struct AggState
{
ScanState ss; /* its first field is NodeTag */
List *aggs; /* all Aggref nodes in targetlist & quals */
int numaggs; /* length of list (could be zero!) */
int numtrans; /* number of pertrans items */
AggStrategy aggstrategy; /* strategy mode */
AggSplit aggsplit; /* agg-splitting mode, see nodes.h */
AggStatePerPhase phase; /* pointer to current phase data */
int numphases; /* number of phases (including phase 0) */
int current_phase; /* current phase number */
AggStatePerAgg peragg; /* per-Aggref information */
AggStatePerTrans pertrans; /* per-Trans state information */
ExprContext *hashcontext; /* econtexts for long-lived data (hashtable) */
ExprContext **aggcontexts; /* econtexts for long-lived data (per GS) */
ExprContext *tmpcontext; /* econtext for input expressions */
#define FIELDNO_AGGSTATE_CURAGGCONTEXT 14
ExprContext *curaggcontext; /* currently active aggcontext */
AggStatePerAgg curperagg; /* currently active aggregate, if any */
#define FIELDNO_AGGSTATE_CURPERTRANS 16
AggStatePerTrans curpertrans; /* currently active trans state, if any */
bool input_done; /* indicates end of input */
bool agg_done; /* indicates completion of Agg scan */
int projected_set; /* The last projected grouping set */
#define FIELDNO_AGGSTATE_CURRENT_SET 20
int current_set; /* The current grouping set being evaluated */
Bitmapset *grouped_cols; /* grouped cols in current projection */
List *all_grouped_cols; /* list of all grouped cols in DESC order */
Bitmapset *colnos_needed; /* all columns needed from the outer plan */
int max_colno_needed; /* highest colno needed from outer plan */
bool all_cols_needed; /* are all cols from outer plan needed? */
/* These fields are for grouping set phase data */
int maxsets; /* The max number of sets in any phase */
AggStatePerPhase phases; /* array of all phases */
Tuplesortstate *sort_in; /* sorted input to phases > 1 */
Tuplesortstate *sort_out; /* input is copied here for next phase */
TupleTableSlot *sort_slot; /* slot for sort results */
/* these fields are used in AGG_PLAIN and AGG_SORTED modes: */
AggStatePerGroup *pergroups; /* grouping set indexed array of per-group
* pointers */
HeapTuple grp_firstTuple; /* copy of first tuple of current group */
/* these fields are used in AGG_HASHED and AGG_MIXED modes: */
bool table_filled; /* hash table filled yet? */
int num_hashes;
MemoryContext hash_metacxt; /* memory for hash table bucket array */
MemoryContext hash_tuplescxt; /* memory for hash table tuples */
struct LogicalTapeSet *hash_tapeset; /* tape set for hash spill tapes */
struct HashAggSpill *hash_spills; /* HashAggSpill for each grouping set,
* exists only during first pass */
TupleTableSlot *hash_spill_rslot; /* for reading spill files */
TupleTableSlot *hash_spill_wslot; /* for writing spill files */
List *hash_batches; /* hash batches remaining to be processed */
bool hash_ever_spilled; /* ever spilled during this execution? */
bool hash_spill_mode; /* we hit a limit during the current batch
* and we must not create new groups */
Size hash_mem_limit; /* limit before spilling hash table */
uint64 hash_ngroups_limit; /* limit before spilling hash table */
int hash_planned_partitions; /* number of partitions planned
* for first pass */
double hashentrysize; /* estimate revised during execution */
Size hash_mem_peak; /* peak hash table memory usage */
uint64 hash_ngroups_current; /* number of groups currently in
* memory in all hash tables */
uint64 hash_disk_used; /* kB of disk space used */
int hash_batches_used; /* batches used during entire execution */
AggStatePerHash perhash; /* array of per-hashtable data */
AggStatePerGroup *hash_pergroup; /* grouping set indexed array of
* per-group pointers */
/* support for evaluation of agg input expressions: */
#define FIELDNO_AGGSTATE_ALL_PERGROUPS 54
AggStatePerGroup *all_pergroups; /* array of first ->pergroups, than
* ->hash_pergroup */
SharedAggInfo *shared_info; /* one entry per worker */
} AggState;
/* ----------------
* WindowAggState information
* ----------------
*/
/* these structs are private in nodeWindowAgg.c: */
typedef struct WindowStatePerFuncData *WindowStatePerFunc;
typedef struct WindowStatePerAggData *WindowStatePerAgg;
/*
* WindowAggStatus -- Used to track the status of WindowAggState
*/
typedef enum WindowAggStatus
{
WINDOWAGG_DONE, /* No more processing to do */
WINDOWAGG_RUN, /* Normal processing of window funcs */
WINDOWAGG_PASSTHROUGH, /* Don't eval window funcs */
WINDOWAGG_PASSTHROUGH_STRICT, /* Pass-through plus don't store new
* tuples during spool */
} WindowAggStatus;
typedef struct WindowAggState
{
ScanState ss; /* its first field is NodeTag */
/* these fields are filled in by ExecInitExpr: */
List *funcs; /* all WindowFunc nodes in targetlist */
int numfuncs; /* total number of window functions */
int numaggs; /* number that are plain aggregates */
WindowStatePerFunc perfunc; /* per-window-function information */
WindowStatePerAgg peragg; /* per-plain-aggregate information */
ExprState *partEqfunction; /* equality funcs for partition columns */
ExprState *ordEqfunction; /* equality funcs for ordering columns */
Tuplestorestate *buffer; /* stores rows of current partition */
int current_ptr; /* read pointer # for current row */
int framehead_ptr; /* read pointer # for frame head, if used */
int frametail_ptr; /* read pointer # for frame tail, if used */
int grouptail_ptr; /* read pointer # for group tail, if used */
int64 spooled_rows; /* total # of rows in buffer */
int64 currentpos; /* position of current row in partition */
int64 frameheadpos; /* current frame head position */
int64 frametailpos; /* current frame tail position (frame end+1) */
/* use struct pointer to avoid including windowapi.h here */
struct WindowObjectData *agg_winobj; /* winobj for aggregate fetches */
int64 aggregatedbase; /* start row for current aggregates */
int64 aggregatedupto; /* rows before this one are aggregated */
WindowAggStatus status; /* run status of WindowAggState */
int frameOptions; /* frame_clause options, see WindowDef */
ExprState *startOffset; /* expression for starting bound offset */
ExprState *endOffset; /* expression for ending bound offset */
Datum startOffsetValue; /* result of startOffset evaluation */
Datum endOffsetValue; /* result of endOffset evaluation */
/* these fields are used with RANGE offset PRECEDING/FOLLOWING: */
FmgrInfo startInRangeFunc; /* in_range function for startOffset */
FmgrInfo endInRangeFunc; /* in_range function for endOffset */
Oid inRangeColl; /* collation for in_range tests */
bool inRangeAsc; /* use ASC sort order for in_range tests? */
bool inRangeNullsFirst; /* nulls sort first for in_range tests? */
/* fields relating to runconditions */
bool use_pass_through; /* When false, stop execution when
* runcondition is no longer true. Else
* just stop evaluating window funcs. */
bool top_window; /* true if this is the top-most WindowAgg or
* the only WindowAgg in this query level */
ExprState *runcondition; /* Condition which must remain true otherwise
* execution of the WindowAgg will finish or
* go into pass-through mode. NULL when there
* is no such condition. */
/* these fields are used in GROUPS mode: */
int64 currentgroup; /* peer group # of current row in partition */
int64 frameheadgroup; /* peer group # of frame head row */
int64 frametailgroup; /* peer group # of frame tail row */
int64 groupheadpos; /* current row's peer group head position */
int64 grouptailpos; /* " " " " tail position (group end+1) */
MemoryContext partcontext; /* context for partition-lifespan data */
MemoryContext aggcontext; /* shared context for aggregate working data */
MemoryContext curaggcontext; /* current aggregate's working data */
ExprContext *tmpcontext; /* short-term evaluation context */
bool all_first; /* true if the scan is starting */
bool partition_spooled; /* true if all tuples in current partition
* have been spooled into tuplestore */
bool next_partition; /* true if begin_partition needs to be called */
bool more_partitions; /* true if there's more partitions after
* this one */
bool framehead_valid; /* true if frameheadpos is known up to
* date for current row */
bool frametail_valid; /* true if frametailpos is known up to
* date for current row */
bool grouptail_valid; /* true if grouptailpos is known up to
* date for current row */
TupleTableSlot *first_part_slot; /* first tuple of current or next
* partition */
TupleTableSlot *framehead_slot; /* first tuple of current frame */
TupleTableSlot *frametail_slot; /* first tuple after current frame */
/* temporary slots for tuples fetched back from tuplestore */
TupleTableSlot *agg_row_slot;
TupleTableSlot *temp_slot_1;
TupleTableSlot *temp_slot_2;
} WindowAggState;
/* ----------------
* UniqueState information
*
* Unique nodes are used "on top of" sort nodes to discard
* duplicate tuples returned from the sort phase. Basically
* all it does is compare the current tuple from the subplan
* with the previously fetched tuple (stored in its result slot).
* If the two are identical in all interesting fields, then
* we just fetch another tuple from the sort and try again.
* ----------------
*/
typedef struct UniqueState
{
PlanState ps; /* its first field is NodeTag */
ExprState *eqfunction; /* tuple equality qual */
} UniqueState;
/* ----------------
* GatherState information
*
* Gather nodes launch 1 or more parallel workers, run a subplan
* in those workers, and collect the results.
* ----------------
*/
typedef struct GatherState
{
PlanState ps; /* its first field is NodeTag */
bool initialized; /* workers launched? */
bool need_to_scan_locally; /* need to read from local plan? */
int64 tuples_needed; /* tuple bound, see ExecSetTupleBound */
/* these fields are set up once: */
TupleTableSlot *funnel_slot;
struct ParallelExecutorInfo *pei;
/* all remaining fields are reinitialized during a rescan: */
int nworkers_launched; /* original number of workers */
int nreaders; /* number of still-active workers */
int nextreader; /* next one to try to read from */
struct TupleQueueReader **reader; /* array with nreaders active entries */
} GatherState;
/* ----------------
* GatherMergeState information
*
* Gather merge nodes launch 1 or more parallel workers, run a
* subplan which produces sorted output in each worker, and then
* merge the results into a single sorted stream.
* ----------------
*/
struct GMReaderTupleBuffer; /* private in nodeGatherMerge.c */
typedef struct GatherMergeState
{
PlanState ps; /* its first field is NodeTag */
bool initialized; /* workers launched? */
bool gm_initialized; /* gather_merge_init() done? */
bool need_to_scan_locally; /* need to read from local plan? */
int64 tuples_needed; /* tuple bound, see ExecSetTupleBound */
/* these fields are set up once: */
TupleDesc tupDesc; /* descriptor for subplan result tuples */
int gm_nkeys; /* number of sort columns */
SortSupport gm_sortkeys; /* array of length gm_nkeys */
struct ParallelExecutorInfo *pei;
/* all remaining fields are reinitialized during a rescan */
/* (but the arrays are not reallocated, just cleared) */
int nworkers_launched; /* original number of workers */
int nreaders; /* number of active workers */
TupleTableSlot **gm_slots; /* array with nreaders+1 entries */
struct TupleQueueReader **reader; /* array with nreaders active entries */
struct GMReaderTupleBuffer *gm_tuple_buffers; /* nreaders tuple buffers */
struct binaryheap *gm_heap; /* binary heap of slot indices */
} GatherMergeState;
/* ----------------
* HashState information
* ----------------
*/
typedef struct HashState
{
PlanState ps; /* its first field is NodeTag */
HashJoinTable hashtable; /* hash table for the hashjoin */
ExprState *hash_expr; /* ExprState to get hash value */
FmgrInfo *skew_hashfunction; /* lookup data for skew hash function */
Oid skew_collation; /* collation to call skew_hashfunction with */
Tuplestorestate *null_tuple_store; /* where to put null-keyed tuples */
bool keep_null_tuples; /* do we need to save such tuples? */
/*
* In a parallelized hash join, the leader retains a pointer to the
* shared-memory stats area in its shared_info field, and then copies the
* shared-memory info back to local storage before DSM shutdown. The
* shared_info field remains NULL in workers, or in non-parallel joins.
*/
SharedHashInfo *shared_info;
/*
* If we are collecting hash stats, this points to an initially-zeroed
* collection area, which could be either local storage or in shared
* memory; either way it's for just one process.
*/
HashInstrumentation *hinstrument;
/* Parallel hash state. */
struct ParallelHashJoinState *parallel_state;
} HashState;
/* ----------------
* SetOpState information
*
* SetOp nodes support either sorted or hashed de-duplication.
* The sorted mode is a bit like MergeJoin, the hashed mode like Agg.
* ----------------
*/
typedef struct SetOpStatePerInput
{
TupleTableSlot *firstTupleSlot; /* first tuple of current group */
int64 numTuples; /* number of tuples in current group */
TupleTableSlot *nextTupleSlot; /* next input tuple, if already read */
bool needGroup; /* do we need to load a new group? */
} SetOpStatePerInput;
typedef struct SetOpState
{
PlanState ps; /* its first field is NodeTag */
bool setop_done; /* indicates completion of output scan */
int64 numOutput; /* number of dups left to output */
int numCols; /* number of grouping columns */
/* these fields are used in SETOP_SORTED mode: */
SortSupport sortKeys; /* per-grouping-field sort data */
SetOpStatePerInput leftInput; /* current outer-relation input state */
SetOpStatePerInput rightInput; /* current inner-relation input state */
bool need_init; /* have we read the first tuples yet? */
/* these fields are used in SETOP_HASHED mode: */
Oid *eqfuncoids; /* per-grouping-field equality fns */
FmgrInfo *hashfunctions; /* per-grouping-field hash fns */
TupleHashTable hashtable; /* hash table with one entry per group */
MemoryContext tuplesContext; /* context containing hash table's tuples */
bool table_filled; /* hash table filled yet? */
TupleHashIterator hashiter; /* for iterating through hash table */
} SetOpState;
/* ----------------
* LockRowsState information
*
* LockRows nodes are used to enforce FOR [KEY] UPDATE/SHARE locking.
* ----------------
*/
typedef struct LockRowsState
{
PlanState ps; /* its first field is NodeTag */
List *lr_arowMarks; /* List of ExecAuxRowMarks */
EPQState lr_epqstate; /* for evaluating EvalPlanQual rechecks */
} LockRowsState;
/* ----------------
* LimitState information
*
* Limit nodes are used to enforce LIMIT/OFFSET clauses.
* They just select the desired subrange of their subplan's output.
*
* offset is the number of initial tuples to skip (0 does nothing).
* count is the number of tuples to return after skipping the offset tuples.
* If no limit count was specified, count is undefined and noCount is true.
* When lstate == LIMIT_INITIAL, offset/count/noCount haven't been set yet.
* ----------------
*/
typedef enum
{
LIMIT_INITIAL, /* initial state for LIMIT node */
LIMIT_RESCAN, /* rescan after recomputing parameters */
LIMIT_EMPTY, /* there are no returnable rows */
LIMIT_INWINDOW, /* have returned a row in the window */
LIMIT_WINDOWEND_TIES, /* have returned a tied row */
LIMIT_SUBPLANEOF, /* at EOF of subplan (within window) */
LIMIT_WINDOWEND, /* stepped off end of window */
LIMIT_WINDOWSTART, /* stepped off beginning of window */
} LimitStateCond;
typedef struct LimitState
{
PlanState ps; /* its first field is NodeTag */
ExprState *limitOffset; /* OFFSET parameter, or NULL if none */
ExprState *limitCount; /* COUNT parameter, or NULL if none */
LimitOption limitOption; /* limit specification type */
int64 offset; /* current OFFSET value */
int64 count; /* current COUNT, if any */
bool noCount; /* if true, ignore count */
LimitStateCond lstate; /* state machine status, as above */
int64 position; /* 1-based index of last tuple returned */
TupleTableSlot *subSlot; /* tuple last obtained from subplan */
ExprState *eqfunction; /* tuple equality qual in case of WITH TIES
* option */
TupleTableSlot *last_slot; /* slot for evaluation of ties */
} LimitState;
#endif /* EXECNODES_H */
./files.txt 0000664 0001750 0001750 00000001117 15222336323 011551 0 ustar xman xman src/backend/access/heap/heapam_handler.c
src/backend/bootstrap/bootstrap.c
src/backend/catalog/index.c
src/backend/catalog/indexing.c
src/backend/catalog/toasting.c
src/backend/commands/analyze.c
src/backend/commands/indexcmds.c
src/backend/executor/execIndexing.c
src/backend/nodes/makefuncs.c
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/util/plancat.c
src/backend/utils/adt/selfuncs.c
src/backend/utils/cache/relcache.c
src/include/nodes/execnodes.h
src/include/nodes/pathnodes.h
src/include/utils/rel.h
src/include/utils/relcache.h
./heapam_handler.c 0000664 0001750 0001750 00000236065 15221604040 013007 0 ustar xman xman /*-------------------------------------------------------------------------
*
* heapam_handler.c
* heap table access method code
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/access/heap/heapam_handler.c
*
*
* NOTES
* This files wires up the lower level heapam.c et al routines with the
* tableam abstraction.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/heaptoast.h"
#include "access/multixact.h"
#include "access/rewriteheap.h"
#include "access/syncscan.h"
#include "access/tableam.h"
#include "access/tsmapi.h"
#include "access/visibilitymap.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "commands/progress.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "storage/bufpage.h"
#include "storage/lmgr.h"
#include "storage/lock.h"
#include "storage/predicate.h"
#include "storage/procarray.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/tuplesort.h"
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
Relation NewHeap, Datum *values, bool *isnull,
BulkInsertState bistate);
static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
OffsetNumber tupoffset);
static BlockNumber heapam_scan_get_blocks_done(HeapScanDesc hscan);
static bool BitmapHeapScanNextBlock(TableScanDesc scan,
bool *recheck,
uint64 *lossy_pages, uint64 *exact_pages);
/* ------------------------------------------------------------------------
* Slot related callbacks for heap AM
* ------------------------------------------------------------------------
*/
static const TupleTableSlotOps *
heapam_slot_callbacks(Relation relation)
{
return &TTSOpsBufferHeapTuple;
}
/* ------------------------------------------------------------------------
* Callbacks for non-modifying operations on individual tuples for heap AM
* ------------------------------------------------------------------------
*/
static bool
heapam_fetch_row_version(Relation relation,
ItemPointer tid,
Snapshot snapshot,
TupleTableSlot *slot)
{
BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
Buffer buffer;
Assert(TTS_IS_BUFFERTUPLE(slot));
bslot->base.tupdata.t_self = *tid;
if (heap_fetch(relation, snapshot, &bslot->base.tupdata, &buffer, false))
{
/* store in slot, transferring existing pin */
ExecStorePinnedBufferHeapTuple(&bslot->base.tupdata, slot, buffer);
slot->tts_tableOid = RelationGetRelid(relation);
return true;
}
return false;
}
static bool
heapam_tuple_tid_valid(TableScanDesc scan, ItemPointer tid)
{
HeapScanDesc hscan = (HeapScanDesc) scan;
return ItemPointerIsValid(tid) &&
ItemPointerGetBlockNumber(tid) < hscan->rs_nblocks;
}
static bool
heapam_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot,
Snapshot snapshot)
{
BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
bool res;
Assert(TTS_IS_BUFFERTUPLE(slot));
Assert(BufferIsValid(bslot->buffer));
/*
* We need buffer pin and lock to call HeapTupleSatisfiesVisibility.
* Caller should be holding pin, but not lock.
*/
LockBuffer(bslot->buffer, BUFFER_LOCK_SHARE);
res = HeapTupleSatisfiesVisibility(bslot->base.tuple, snapshot,
bslot->buffer);
LockBuffer(bslot->buffer, BUFFER_LOCK_UNLOCK);
return res;
}
/* ----------------------------------------------------------------------------
* Functions for manipulations of physical tuples for heap AM.
* ----------------------------------------------------------------------------
*/
static void
heapam_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
uint32 options, BulkInsertState bistate)
{
bool shouldFree = true;
HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
/* Update the tuple with table oid */
slot->tts_tableOid = RelationGetRelid(relation);
tuple->t_tableOid = slot->tts_tableOid;
/* Perform the insertion, and copy the resulting ItemPointer */
heap_insert(relation, tuple, cid, options, bistate);
ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
if (shouldFree)
pfree(tuple);
}
static void
heapam_tuple_insert_speculative(Relation relation, TupleTableSlot *slot,
CommandId cid, uint32 options,
BulkInsertState bistate, uint32 specToken)
{
bool shouldFree = true;
HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
/* Update the tuple with table oid */
slot->tts_tableOid = RelationGetRelid(relation);
tuple->t_tableOid = slot->tts_tableOid;
HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);
options |= HEAP_INSERT_SPECULATIVE;
/* Perform the insertion, and copy the resulting ItemPointer */
heap_insert(relation, tuple, cid, options, bistate);
ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
if (shouldFree)
pfree(tuple);
}
static void
heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
uint32 specToken, bool succeeded)
{
bool shouldFree = true;
HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
/* adjust the tuple's state accordingly */
if (succeeded)
heap_finish_speculative(relation, &slot->tts_tid);
else
heap_abort_speculative(relation, &slot->tts_tid);
if (shouldFree)
pfree(tuple);
}
static TM_Result
heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
uint32 options, Snapshot snapshot, Snapshot crosscheck,
bool wait, TM_FailureData *tmfd)
{
/*
* Currently Deleting of index tuples are handled at vacuum, in case if
* the storage itself is cleaning the dead tuples by itself, it is the
* time to call the index tuple deletion also.
*/
return heap_delete(relation, tid, cid, options, crosscheck, wait,
tmfd);
}
static TM_Result
heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
CommandId cid, uint32 options,
Snapshot snapshot, Snapshot crosscheck,
bool wait, TM_FailureData *tmfd,
LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes)
{
bool shouldFree = true;
HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
TM_Result result;
/* Update the tuple with table oid */
slot->tts_tableOid = RelationGetRelid(relation);
tuple->t_tableOid = slot->tts_tableOid;
result = heap_update(relation, otid, tuple, cid, options,
crosscheck, wait,
tmfd, lockmode, update_indexes);
ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
/*
* Decide whether new index entries are needed for the tuple
*
* Note: heap_update returns the tid (location) of the new tuple in the
* t_self field.
*
* If the update is not HOT, we must update all indexes. If the update is
* HOT, it could be that we updated summarized columns, so we either
* update only summarized indexes, or none at all.
*/
if (result != TM_Ok)
{
Assert(*update_indexes == TU_None);
*update_indexes = TU_None;
}
else if (!HeapTupleIsHeapOnly(tuple))
Assert(*update_indexes == TU_All);
else
Assert((*update_indexes == TU_Summarizing) ||
(*update_indexes == TU_None));
if (shouldFree)
pfree(tuple);
return result;
}
static TM_Result
heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
TupleTableSlot *slot, CommandId cid, LockTupleMode mode,
LockWaitPolicy wait_policy, uint8 flags,
TM_FailureData *tmfd)
{
BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
TM_Result result;
Buffer buffer;
HeapTuple tuple = &bslot->base.tupdata;
bool follow_updates;
follow_updates = (flags & TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS) != 0;
tmfd->traversed = false;
Assert(TTS_IS_BUFFERTUPLE(slot));
tuple_lock_retry:
tuple->t_self = *tid;
result = heap_lock_tuple(relation, tuple, cid, mode, wait_policy,
follow_updates, &buffer, tmfd);
if (result == TM_Updated &&
(flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION))
{
/* Should not encounter speculative tuple on recheck */
Assert(!HeapTupleHeaderIsSpeculative(tuple->t_data));
ReleaseBuffer(buffer);
if (!ItemPointerEquals(&tmfd->ctid, &tuple->t_self))
{
SnapshotData SnapshotDirty;
TransactionId priorXmax;
/* it was updated, so look at the updated version */
*tid = tmfd->ctid;
/* updated row should have xmin matching this xmax */
priorXmax = tmfd->xmax;
/* signal that a tuple later in the chain is getting locked */
tmfd->traversed = true;
/*
* fetch target tuple
*
* Loop here to deal with updated or busy tuples
*/
InitDirtySnapshot(SnapshotDirty);
for (;;)
{
if (ItemPointerIndicatesMovedPartitions(tid))
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
tuple->t_self = *tid;
if (heap_fetch(relation, &SnapshotDirty, tuple, &buffer, true))
{
/*
* If xmin isn't what we're expecting, the slot must have
* been recycled and reused for an unrelated tuple. This
* implies that the latest version of the row was deleted,
* so we need do nothing. (Should be safe to examine xmin
* without getting buffer's content lock. We assume
* reading a TransactionId to be atomic, and Xmin never
* changes in an existing tuple, except to invalid or
* frozen, and neither of those can match priorXmax.)
*/
if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data),
priorXmax))
{
ReleaseBuffer(buffer);
return TM_Deleted;
}
/* otherwise xmin should not be dirty... */
if (TransactionIdIsValid(SnapshotDirty.xmin))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("t_xmin %u is uncommitted in tuple (%u,%u) to be updated in table \"%s\"",
SnapshotDirty.xmin,
ItemPointerGetBlockNumber(&tuple->t_self),
ItemPointerGetOffsetNumber(&tuple->t_self),
RelationGetRelationName(relation))));
/*
* If tuple is being updated by other transaction then we
* have to wait for its commit/abort, or die trying.
*/
if (TransactionIdIsValid(SnapshotDirty.xmax))
{
ReleaseBuffer(buffer);
switch (wait_policy)
{
case LockWaitBlock:
XactLockTableWait(SnapshotDirty.xmax,
relation, &tuple->t_self,
XLTW_FetchUpdated);
break;
case LockWaitSkip:
if (!ConditionalXactLockTableWait(SnapshotDirty.xmax, false))
/* skip instead of waiting */
return TM_WouldBlock;
break;
case LockWaitError:
if (!ConditionalXactLockTableWait(SnapshotDirty.xmax, log_lock_failures))
ereport(ERROR,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on row in relation \"%s\"",
RelationGetRelationName(relation))));
break;
}
continue; /* loop back to repeat heap_fetch */
}
/*
* If tuple was inserted by our own transaction, we have
* to check cmin against cid: cmin >= current CID means
* our command cannot see the tuple, so we should ignore
* it. Otherwise heap_lock_tuple() will throw an error,
* and so would any later attempt to update or delete the
* tuple. (We need not check cmax because
* HeapTupleSatisfiesDirty will consider a tuple deleted
* by our transaction dead, regardless of cmax.) We just
* checked that priorXmax == xmin, so we can test that
* variable instead of doing HeapTupleHeaderGetXmin again.
*/
if (TransactionIdIsCurrentTransactionId(priorXmax) &&
HeapTupleHeaderGetCmin(tuple->t_data) >= cid)
{
tmfd->xmax = priorXmax;
/*
* Cmin is the problematic value, so store that. See
* above.
*/
tmfd->cmax = HeapTupleHeaderGetCmin(tuple->t_data);
ReleaseBuffer(buffer);
return TM_SelfModified;
}
/*
* This is a live tuple, so try to lock it again.
*/
ReleaseBuffer(buffer);
goto tuple_lock_retry;
}
/*
* If the referenced slot was actually empty, the latest
* version of the row must have been deleted, so we need do
* nothing.
*/
if (tuple->t_data == NULL)
{
Assert(!BufferIsValid(buffer));
return TM_Deleted;
}
/*
* As above, if xmin isn't what we're expecting, do nothing.
*/
if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data),
priorXmax))
{
ReleaseBuffer(buffer);
return TM_Deleted;
}
/*
* If we get here, the tuple was found but failed
* SnapshotDirty. Assuming the xmin is either a committed xact
* or our own xact (as it certainly should be if we're trying
* to modify the tuple), this must mean that the row was
* updated or deleted by either a committed xact or our own
* xact. If it was deleted, we can ignore it; if it was
* updated then chain up to the next version and repeat the
* whole process.
*
* As above, it should be safe to examine xmax and t_ctid
* without the buffer content lock, because they can't be
* changing. We'd better hold a buffer pin though.
*/
if (ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid))
{
/* deleted, so forget about it */
ReleaseBuffer(buffer);
return TM_Deleted;
}
/* updated, so look at the updated row */
*tid = tuple->t_data->t_ctid;
/* updated row should have xmin matching this xmax */
priorXmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
ReleaseBuffer(buffer);
/* loop back to fetch next in chain */
}
}
else
{
/* tuple was deleted, so give up */
return TM_Deleted;
}
}
slot->tts_tableOid = RelationGetRelid(relation);
tuple->t_tableOid = slot->tts_tableOid;
/* store in slot, transferring existing pin */
ExecStorePinnedBufferHeapTuple(tuple, slot, buffer);
return result;
}
/* ------------------------------------------------------------------------
* DDL related callbacks for heap AM.
* ------------------------------------------------------------------------
*/
static void
heapam_relation_set_new_filelocator(Relation rel,
const RelFileLocator *newrlocator,
char persistence,
TransactionId *freezeXid,
MultiXactId *minmulti)
{
SMgrRelation srel;
/*
* Initialize to the minimum XID that could put tuples in the table. We
* know that no xacts older than RecentXmin are still running, so that
* will do.
*/
*freezeXid = RecentXmin;
/*
* Similarly, initialize the minimum Multixact to the first value that
* could possibly be stored in tuples in the table. Running transactions
* could reuse values from their local cache, so we are careful to
* consider all currently running multis.
*
* XXX this could be refined further, but is it worth the hassle?
*/
*minmulti = GetOldestMultiXactId();
srel = RelationCreateStorage(*newrlocator, persistence, true);
/*
* If required, set up an init fork for an unlogged table so that it can
* be correctly reinitialized on restart.
*/
if (persistence == RELPERSISTENCE_UNLOGGED)
{
Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
rel->rd_rel->relkind == RELKIND_TOASTVALUE);
smgrcreate(srel, INIT_FORKNUM, false);
log_smgrcreate(newrlocator, INIT_FORKNUM);
}
smgrclose(srel);
}
static void
heapam_relation_nontransactional_truncate(Relation rel)
{
RelationTruncate(rel, 0);
}
static void
heapam_relation_copy_data(Relation rel, const RelFileLocator *newrlocator)
{
SMgrRelation dstrel;
/*
* Since we copy the file directly without looking at the shared buffers,
* we'd better first flush out any pages of the source relation that are
* in shared buffers. We assume no new changes will be made while we are
* holding exclusive lock on the rel.
*/
FlushRelationBuffers(rel);
/*
* Create and copy all forks of the relation, and schedule unlinking of
* old physical files.
*
* NOTE: any conflict in relfilenumber value will be caught in
* RelationCreateStorage().
*/
dstrel = RelationCreateStorage(*newrlocator, rel->rd_rel->relpersistence, true);
/* copy main fork */
RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
rel->rd_rel->relpersistence);
/* copy those extra forks that exist */
for (ForkNumber forkNum = MAIN_FORKNUM + 1;
forkNum <= MAX_FORKNUM; forkNum++)
{
if (smgrexists(RelationGetSmgr(rel), forkNum))
{
smgrcreate(dstrel, forkNum, false);
/*
* WAL log creation if the relation is persistent, or this is the
* init fork of an unlogged relation.
*/
if (RelationIsPermanent(rel) ||
(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
forkNum == INIT_FORKNUM))
log_smgrcreate(newrlocator, forkNum);
RelationCopyStorage(RelationGetSmgr(rel), dstrel, forkNum,
rel->rd_rel->relpersistence);
}
}
/* drop old relation, and close new one */
RelationDropStorage(rel);
smgrclose(dstrel);
}
static void
heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
Relation OldIndex, bool use_sort,
TransactionId OldestXmin,
Snapshot snapshot,
TransactionId *xid_cutoff,
MultiXactId *multi_cutoff,
double *num_tuples,
double *tups_vacuumed,
double *tups_recently_dead)
{
RewriteState rwstate;
BulkInsertState bistate;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
bool is_system_catalog;
Tuplesortstate *tuplesort;
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
TupleTableSlot *slot;
int natts;
Datum *values;
bool *isnull;
BufferHeapTupleTableSlot *hslot;
BlockNumber prev_cblock = InvalidBlockNumber;
bool concurrent = snapshot != NULL;
/* Remember if it's a system catalog */
is_system_catalog = IsSystemRelation(OldHeap);
/*
* Valid smgr_targblock implies something already wrote to the relation.
* This may be harmless, but this function hasn't planned for it.
*/
Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber);
/* Preallocate values/isnull arrays */
natts = newTupDesc->natts;
values = palloc_array(Datum, natts);
isnull = palloc_array(bool, natts);
/*
* In non-concurrent mode, initialize the rewrite operation. This is not
* needed in concurrent mode.
*/
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
else
rwstate = NULL;
/* In concurrent mode, prepare for bulk-insert operation. */
if (concurrent)
bistate = GetBulkInsertState();
else
bistate = NULL;
/* Set up sorting if wanted */
if (use_sort)
tuplesort = tuplesort_begin_cluster(oldTupDesc, OldIndex,
maintenance_work_mem,
NULL, TUPLESORT_NONE);
else
tuplesort = NULL;
/*
* Prepare to scan the OldHeap. To ensure we see recently-dead tuples
* that still need to be copied, we scan with SnapshotAny and use
* HeapTupleSatisfiesVacuum for the visibility test.
*
* In the CONCURRENTLY case, we do regular MVCC visibility tests, using
* the snapshot passed by the caller.
*/
if (OldIndex != NULL && !use_sort)
{
const int ci_index[] = {
PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_INDEX_RELID
};
int64 ci_val[2];
/* Set phase and OIDOldIndex to columns */
ci_val[0] = PROGRESS_REPACK_PHASE_INDEX_SCAN_HEAP;
ci_val[1] = RelationGetRelid(OldIndex);
pgstat_progress_update_multi_param(2, ci_index, ci_val);
tableScan = NULL;
heapScan = NULL;
indexScan = index_beginscan(OldHeap, OldIndex,
snapshot ? snapshot : SnapshotAny,
NULL, 0, 0,
SO_NONE);
index_rescan(indexScan, NULL, 0, NULL, 0);
}
else
{
/* In scan-and-sort mode and also VACUUM FULL, set phase */
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP);
tableScan = table_beginscan(OldHeap,
snapshot ? snapshot : SnapshotAny,
0, (ScanKey) NULL,
SO_NONE);
heapScan = (HeapScanDesc) tableScan;
indexScan = NULL;
/* Set total heap blocks */
pgstat_progress_update_param(PROGRESS_REPACK_TOTAL_HEAP_BLKS,
heapScan->rs_nblocks);
}
slot = table_slot_create(OldHeap, NULL);
hslot = (BufferHeapTupleTableSlot *) slot;
/*
* Scan through the OldHeap, either in OldIndex order or sequentially;
* copy each tuple into the NewHeap, or transiently to the tuplesort
* module. Note that we don't bother sorting dead tuples (they won't get
* to the new table anyway).
*/
for (;;)
{
HeapTuple tuple;
Buffer buf;
bool isdead;
CHECK_FOR_INTERRUPTS();
if (indexScan != NULL)
{
if (!index_getnext_slot(indexScan, ForwardScanDirection, slot))
break;
/* Since we used no scan keys, should never need to recheck */
if (indexScan->xs_recheck)
elog(ERROR, "CLUSTER does not support lossy index conditions");
}
else
{
if (!table_scan_getnextslot(tableScan, ForwardScanDirection, slot))
{
/*
* If the last pages of the scan were empty, we would go to
* the next phase while heap_blks_scanned != heap_blks_total.
* Instead, to ensure that heap_blks_scanned is equivalent to
* heap_blks_total after the table scan phase, this parameter
* is manually updated to the correct value when the table
* scan finishes.
*/
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_BLKS_SCANNED,
heapScan->rs_nblocks);
break;
}
/*
* In scan-and-sort mode and also VACUUM FULL, set heap blocks
* scanned
*
* Note that heapScan may start at an offset and wrap around, i.e.
* rs_startblock may be >0, and rs_cblock may end with a number
* below rs_startblock. To prevent showing this wraparound to the
* user, we offset rs_cblock by rs_startblock (modulo rs_nblocks).
*/
if (prev_cblock != heapScan->rs_cblock)
{
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_BLKS_SCANNED,
(heapScan->rs_cblock +
heapScan->rs_nblocks -
heapScan->rs_startblock
) % heapScan->rs_nblocks + 1);
prev_cblock = heapScan->rs_cblock;
}
}
tuple = ExecFetchSlotHeapTuple(slot, false, NULL);
buf = hslot->buffer;
/*
* In concurrent mode, our table or index scan has used regular MVCC
* visibility test against a snapshot passed by caller; therefore we
* don't need another visibility test. In non-concurrent mode
* however, we must test the visibility of each tuple we read.
*/
if (!concurrent)
{
/*
* To be able to guarantee that we can set the hint bit, acquire
* an exclusive lock on the old buffer. We need the hint bits, set
* in heapam_relation_copy_for_cluster() ->
* HeapTupleSatisfiesVacuum(), to be set, as otherwise
* reform_and_rewrite_tuple() -> rewrite_heap_tuple() will get
* confused. Specifically, rewrite_heap_tuple() checks for
* HEAP_XMAX_INVALID in the old tuple to determine whether to
* check the old-to-new mapping hash table.
*
* It'd be better if we somehow could avoid setting hint bits on
* the old page. One reason to use VACUUM FULL are very bloated
* tables - rewriting most of the old table during VACUUM FULL
* doesn't exactly help...
*/
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
{
case HEAPTUPLE_DEAD:
/* Definitely dead */
isdead = true;
break;
case HEAPTUPLE_RECENTLY_DEAD:
*tups_recently_dead += 1;
pg_fallthrough;
case HEAPTUPLE_LIVE:
/* Live or recently dead, must copy it */
isdead = false;
break;
case HEAPTUPLE_INSERT_IN_PROGRESS:
/*
* As long as we hold exclusive lock on the relation,
* normally the only way to see this is if it was inserted
* earlier in our own transaction. However, it can happen
* in system catalogs, since we tend to release write lock
* before commit there. Give a warning if neither case
* applies; but in any case we had better copy it.
*/
if (!is_system_catalog &&
!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
elog(WARNING, "concurrent insert in progress within table \"%s\"",
RelationGetRelationName(OldHeap));
/* treat as live */
isdead = false;
break;
case HEAPTUPLE_DELETE_IN_PROGRESS:
/*
* Similar situation to INSERT_IN_PROGRESS case.
*/
if (!is_system_catalog &&
!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
elog(WARNING, "concurrent delete in progress within table \"%s\"",
RelationGetRelationName(OldHeap));
/* treat as recently dead */
*tups_recently_dead += 1;
isdead = false;
break;
default:
elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
isdead = false; /* keep compiler quiet */
break;
}
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
if (isdead)
{
*tups_vacuumed += 1;
/* heap rewrite module still needs to see it... */
if (rewrite_heap_dead_tuple(rwstate, tuple))
{
/* A previous recently-dead tuple is now known dead */
*tups_vacuumed += 1;
*tups_recently_dead -= 1;
}
continue;
}
}
*num_tuples += 1;
if (tuplesort != NULL)
{
tuplesort_putheaptuple(tuplesort, tuple);
/*
* In scan-and-sort mode, report increase in number of tuples
* scanned
*/
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_SCANNED,
*num_tuples);
}
else
{
const int ct_index[] = {
PROGRESS_REPACK_HEAP_TUPLES_SCANNED,
PROGRESS_REPACK_HEAP_TUPLES_INSERTED
};
int64 ct_val[2];
if (!concurrent)
reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
values, isnull, rwstate);
else
heap_insert_for_repack(tuple, OldHeap, NewHeap,
values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
* number of tuples scanned and written
*/
ct_val[0] = *num_tuples;
ct_val[1] = *num_tuples;
pgstat_progress_update_multi_param(2, ct_index, ct_val);
}
}
if (indexScan != NULL)
index_endscan(indexScan);
if (tableScan != NULL)
table_endscan(tableScan);
if (slot)
ExecDropSingleTupleTableSlot(slot);
/*
* In scan-and-sort mode, complete the sort, then read out all live tuples
* from the tuplestore and write them to the new relation.
*/
if (tuplesort != NULL)
{
double n_tuples = 0;
/* Report that we are now sorting tuples */
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_SORT_TUPLES);
tuplesort_performsort(tuplesort);
/* Report that we are now writing new heap */
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_WRITE_NEW_HEAP);
for (;;)
{
HeapTuple tuple;
CHECK_FOR_INTERRUPTS();
tuple = tuplesort_getheaptuple(tuplesort, true);
if (tuple == NULL)
break;
n_tuples += 1;
if (!concurrent)
reform_and_rewrite_tuple(tuple,
OldHeap, NewHeap,
values, isnull,
rwstate);
else
heap_insert_for_repack(tuple, OldHeap, NewHeap,
values, isnull, bistate);
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
}
tuplesort_end(tuplesort);
}
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
if (bistate)
FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
pfree(isnull);
}
/*
* Prepare to analyze the next block in the read stream. Returns false if
* the stream is exhausted and true otherwise. The scan must have been started
* with SO_TYPE_ANALYZE option.
*
* This routine holds a buffer pin and lock on the heap page. They are held
* until heapam_scan_analyze_next_tuple() returns false. That is until all the
* items of the heap page are analyzed.
*/
static bool
heapam_scan_analyze_next_block(TableScanDesc scan, ReadStream *stream)
{
HeapScanDesc hscan = (HeapScanDesc) scan;
/*
* We must maintain a pin on the target page's buffer to ensure that
* concurrent activity - e.g. HOT pruning - doesn't delete tuples out from
* under us. It comes from the stream already pinned. We also choose to
* hold sharelock on the buffer throughout --- we could release and
* re-acquire sharelock for each tuple, but since we aren't doing much
* work per tuple, the extra lock traffic is probably better avoided.
*/
hscan->rs_cbuf = read_stream_next_buffer(stream, NULL);
if (!BufferIsValid(hscan->rs_cbuf))
return false;
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
hscan->rs_cblock = BufferGetBlockNumber(hscan->rs_cbuf);
hscan->rs_cindex = FirstOffsetNumber;
return true;
}
static bool
heapam_scan_analyze_next_tuple(TableScanDesc scan,
double *liverows, double *deadrows,
TupleTableSlot *slot)
{
HeapScanDesc hscan = (HeapScanDesc) scan;
Page targpage;
OffsetNumber maxoffset;
BufferHeapTupleTableSlot *hslot;
Assert(TTS_IS_BUFFERTUPLE(slot));
hslot = (BufferHeapTupleTableSlot *) slot;
targpage = BufferGetPage(hscan->rs_cbuf);
maxoffset = PageGetMaxOffsetNumber(targpage);
/* Inner loop over all tuples on the selected page */
for (; hscan->rs_cindex <= maxoffset; hscan->rs_cindex++)
{
ItemId itemid;
HeapTuple targtuple = &hslot->base.tupdata;
bool sample_it = false;
TransactionId dead_after;
itemid = PageGetItemId(targpage, hscan->rs_cindex);
/*
* We ignore unused and redirect line pointers. DEAD line pointers
* should be counted as dead, because we need vacuum to run to get rid
* of them. Note that this rule agrees with the way that
* heap_page_prune_and_freeze() counts things.
*/
if (!ItemIdIsNormal(itemid))
{
if (ItemIdIsDead(itemid))
*deadrows += 1;
continue;
}
ItemPointerSet(&targtuple->t_self, hscan->rs_cblock, hscan->rs_cindex);
targtuple->t_tableOid = RelationGetRelid(scan->rs_rd);
targtuple->t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
targtuple->t_len = ItemIdGetLength(itemid);
switch (HeapTupleSatisfiesVacuumHorizon(targtuple,
hscan->rs_cbuf,
&dead_after))
{
case HEAPTUPLE_LIVE:
sample_it = true;
*liverows += 1;
break;
case HEAPTUPLE_DEAD:
case HEAPTUPLE_RECENTLY_DEAD:
/* Count dead and recently-dead rows */
*deadrows += 1;
break;
case HEAPTUPLE_INSERT_IN_PROGRESS:
/*
* Insert-in-progress rows are not counted. We assume that
* when the inserting transaction commits or aborts, it will
* send a stats message to increment the proper count. This
* works right only if that transaction ends after we finish
* analyzing the table; if things happen in the other order,
* its stats update will be overwritten by ours. However, the
* error will be large only if the other transaction runs long
* enough to insert many tuples, so assuming it will finish
* after us is the safer option.
*
* A special case is that the inserting transaction might be
* our own. In this case we should count and sample the row,
* to accommodate users who load a table and analyze it in one
* transaction. (pgstat_report_analyze has to adjust the
* numbers we report to the cumulative stats system to make
* this come out right.)
*/
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(targtuple->t_data)))
{
sample_it = true;
*liverows += 1;
}
break;
case HEAPTUPLE_DELETE_IN_PROGRESS:
/*
* We count and sample delete-in-progress rows the same as
* live ones, so that the stats counters come out right if the
* deleting transaction commits after us, per the same
* reasoning given above.
*
* If the delete was done by our own transaction, however, we
* must count the row as dead to make pgstat_report_analyze's
* stats adjustments come out right. (Note: this works out
* properly when the row was both inserted and deleted in our
* xact.)
*
* The net effect of these choices is that we act as though an
* IN_PROGRESS transaction hasn't happened yet, except if it
* is our own transaction, which we assume has happened.
*
* This approach ensures that we behave sanely if we see both
* the pre-image and post-image rows for a row being updated
* by a concurrent transaction: we will sample the pre-image
* but not the post-image. We also get sane results if the
* concurrent transaction never commits.
*/
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(targtuple->t_data)))
*deadrows += 1;
else
{
sample_it = true;
*liverows += 1;
}
break;
default:
elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
break;
}
if (sample_it)
{
ExecStoreBufferHeapTuple(targtuple, slot, hscan->rs_cbuf);
hscan->rs_cindex++;
/* note that we leave the buffer locked here! */
return true;
}
}
/* Now release the lock and pin on the page */
UnlockReleaseBuffer(hscan->rs_cbuf);
hscan->rs_cbuf = InvalidBuffer;
/* also prevent old slot contents from having pin on page */
ExecClearTuple(slot);
return false;
}
static double
heapam_index_build_range_scan(Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo,
bool allow_sync,
bool anyvisible,
bool progress,
BlockNumber start_blockno,
BlockNumber numblocks,
IndexBuildCallback callback,
void *callback_state,
TableScanDesc scan)
{
HeapScanDesc hscan;
bool is_system_catalog;
bool checking_uniqueness;
HeapTuple heapTuple;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
double reltuples;
ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
Snapshot snapshot;
bool need_unregister_snapshot = false;
TransactionId OldestXmin;
BlockNumber previous_blkno = InvalidBlockNumber;
BlockNumber root_blkno = InvalidBlockNumber;
OffsetNumber root_offsets[MaxHeapTuplesPerPage];
/*
* sanity checks
*/
Assert(OidIsValid(indexRelation->rd_rel->relam));
/* Remember if it's a system catalog */
is_system_catalog = IsSystemRelation(heapRelation);
/* See whether we're verifying uniqueness/exclusion properties */
checking_uniqueness = (indexInfo->ii_Unique ||
indexInfo->ii_ExclusionOps != NULL);
/*
* "Any visible" mode is not compatible with uniqueness checks; make sure
* only one of those is requested.
*/
Assert(!(anyvisible && checking_uniqueness));
/*
* Need an EState for evaluation of index expressions and partial-index
* predicates. Also a slot to hold the current tuple.
*/
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
slot = table_slot_create(heapRelation, NULL);
/* Arrange for econtext's scan tuple to be the tuple under test */
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. In a normal index build, we use
* SnapshotAny because we must retrieve all tuples and do our own time
* qual checks (because we have to index RECENTLY_DEAD tuples). In a
* concurrent build, or during bootstrap, we take a regular MVCC snapshot
* and index whatever's live according to that.
*/
OldestXmin = InvalidTransactionId;
/* okay to ignore lazy VACUUMs here */
if (!IsBootstrapProcessingMode() && !indexInfo->ii_Concurrent)
OldestXmin = GetOldestNonRemovableTransactionId(heapRelation);
if (!scan)
{
/*
* Serial index build.
*
* Must begin our own heap scan in this case. We may also need to
* register a snapshot whose lifetime is under our direct control.
*/
if (!TransactionIdIsValid(OldestXmin))
{
snapshot = RegisterSnapshot(GetTransactionSnapshot());
need_unregister_snapshot = true;
}
else
snapshot = SnapshotAny;
scan = table_beginscan_strat(heapRelation, /* relation */
snapshot, /* snapshot */
0, /* number of keys */
NULL, /* scan key */
true, /* buffer access strategy OK */
allow_sync); /* syncscan OK? */
}
else
{
/*
* Parallel index build.
*
* Parallel case never registers/unregisters own snapshot. Snapshot
* is taken from parallel heap scan, and is SnapshotAny or an MVCC
* snapshot, based on same criteria as serial case.
*/
Assert(!IsBootstrapProcessingMode());
Assert(allow_sync);
snapshot = scan->rs_snapshot;
}
hscan = (HeapScanDesc) scan;
/*
* Must have called GetOldestNonRemovableTransactionId() if using
* SnapshotAny. Shouldn't have for an MVCC snapshot. (It's especially
* worth checking this for parallel builds, since ambuild routines that
* support parallel builds must work these details out for themselves.)
*/
Assert(snapshot == SnapshotAny || IsMVCCSnapshot(snapshot));
Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
!TransactionIdIsValid(OldestXmin));
Assert(snapshot == SnapshotAny || !anyvisible);
/* Publish number of blocks to scan */
if (progress)
{
BlockNumber nblocks;
if (hscan->rs_base.rs_parallel != NULL)
{
ParallelBlockTableScanDesc pbscan;
pbscan = (ParallelBlockTableScanDesc) hscan->rs_base.rs_parallel;
nblocks = pbscan->phs_nblocks;
}
else
nblocks = hscan->rs_nblocks;
pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL,
nblocks);
}
/* set our scan endpoints */
if (!allow_sync)
heap_setscanlimits(scan, start_blockno, numblocks);
else
{
/* syncscan can only be requested on whole relation */
Assert(start_blockno == 0);
Assert(numblocks == InvalidBlockNumber);
}
reltuples = 0;
/*
* Scan all tuples in the base relation.
*/
while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
bool tupleIsAlive;
CHECK_FOR_INTERRUPTS();
/* Report scan progress, if asked to. */
if (progress)
{
BlockNumber blocks_done = heapam_scan_get_blocks_done(hscan);
if (blocks_done != previous_blkno)
{
pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
blocks_done);
previous_blkno = blocks_done;
}
}
/*
* When dealing with a HOT-chain of updated tuples, we want to index
* the values of the live tuple (if any), but index it under the TID
* of the chain's root tuple. This approach is necessary to preserve
* the HOT-chain structure in the heap. So we need to be able to find
* the root item offset for every tuple that's in a HOT-chain. When
* first reaching a new page of the relation, call
* heap_get_root_tuples() to build a map of root item offsets on the
* page.
*
* It might look unsafe to use this information across buffer
* lock/unlock. However, we hold ShareLock on the table so no
* ordinary insert/update/delete should occur; and we hold pin on the
* buffer continuously while visiting the page, so no pruning
* operation can occur either.
*
* In cases with only ShareUpdateExclusiveLock on the table, it's
* possible for some HOT tuples to appear that we didn't know about
* when we first read the page. To handle that case, we re-obtain the
* list of root offsets when a HOT tuple points to a root item that we
* don't know about.
*
* Also, although our opinions about tuple liveness could change while
* we scan the page (due to concurrent transaction commits/aborts),
* the chain root locations won't, so this info doesn't need to be
* rebuilt after waiting for another transaction.
*
* Note the implied assumption that there is no more than one live
* tuple per HOT-chain --- else we could create more than one index
* entry pointing to the same root tuple.
*/
if (hscan->rs_cblock != root_blkno)
{
Page page = BufferGetPage(hscan->rs_cbuf);
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
heap_get_root_tuples(page, root_offsets);
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
root_blkno = hscan->rs_cblock;
}
if (snapshot == SnapshotAny)
{
/* do our own time qual check */
bool indexIt;
TransactionId xwait;
recheck:
/*
* We could possibly get away with not locking the buffer here,
* since caller should hold ShareLock on the relation, but let's
* be conservative about it. (This remark is still correct even
* with HOT-pruning: our pin on the buffer prevents pruning.)
*/
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
/*
* The criteria for counting a tuple as live in this block need to
* match what analyze.c's heapam_scan_analyze_next_tuple() does,
* otherwise CREATE INDEX and ANALYZE may produce wildly different
* reltuples values, e.g. when there are many recently-dead
* tuples.
*/
switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
hscan->rs_cbuf))
{
case HEAPTUPLE_DEAD:
/* Definitely dead, we can ignore it */
indexIt = false;
tupleIsAlive = false;
break;
case HEAPTUPLE_LIVE:
/* Normal case, index and unique-check it */
indexIt = true;
tupleIsAlive = true;
/* Count it as live, too */
reltuples += 1;
break;
case HEAPTUPLE_RECENTLY_DEAD:
/*
* If tuple is recently deleted then we must index it
* anyway to preserve MVCC semantics. (Pre-existing
* transactions could try to use the index after we finish
* building it, and may need to see such tuples.)
*
* However, if it was HOT-updated then we must only index
* the live tuple at the end of the HOT-chain. Since this
* breaks semantics for pre-existing snapshots, mark the
* index as unusable for them.
*
* We don't count recently-dead tuples in reltuples, even
* if we index them; see heapam_scan_analyze_next_tuple().
*/
if (HeapTupleIsHotUpdated(heapTuple))
{
indexIt = false;
/* mark the index as unsafe for old snapshots */
indexInfo->ii_BrokenHotChain = true;
}
else
indexIt = true;
/* In any case, exclude the tuple from unique-checking */
tupleIsAlive = false;
break;
case HEAPTUPLE_INSERT_IN_PROGRESS:
/*
* In "anyvisible" mode, this tuple is visible and we
* don't need any further checks.
*/
if (anyvisible)
{
indexIt = true;
tupleIsAlive = true;
reltuples += 1;
break;
}
/*
* Since caller should hold ShareLock or better, normally
* the only way to see this is if it was inserted earlier
* in our own transaction. However, it can happen in
* system catalogs, since we tend to release write lock
* before commit there. Give a warning if neither case
* applies.
*/
xwait = HeapTupleHeaderGetXmin(heapTuple->t_data);
if (!TransactionIdIsCurrentTransactionId(xwait))
{
if (!is_system_catalog)
elog(WARNING, "concurrent insert in progress within table \"%s\"",
RelationGetRelationName(heapRelation));
/*
* If we are performing uniqueness checks, indexing
* such a tuple could lead to a bogus uniqueness
* failure. In that case we wait for the inserting
* transaction to finish and check again.
*/
if (checking_uniqueness)
{
/*
* Must drop the lock on the buffer before we wait
*/
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
XactLockTableWait(xwait, heapRelation,
&heapTuple->t_self,
XLTW_InsertIndexUnique);
CHECK_FOR_INTERRUPTS();
goto recheck;
}
}
else
{
/*
* For consistency with
* heapam_scan_analyze_next_tuple(), count
* HEAPTUPLE_INSERT_IN_PROGRESS tuples as live only
* when inserted by our own transaction.
*/
reltuples += 1;
}
/*
* We must index such tuples, since if the index build
* commits then they're good.
*/
indexIt = true;
tupleIsAlive = true;
break;
case HEAPTUPLE_DELETE_IN_PROGRESS:
/*
* As with INSERT_IN_PROGRESS case, this is unexpected
* unless it's our own deletion or a system catalog; but
* in anyvisible mode, this tuple is visible.
*/
if (anyvisible)
{
indexIt = true;
tupleIsAlive = false;
reltuples += 1;
break;
}
xwait = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
if (!TransactionIdIsCurrentTransactionId(xwait))
{
if (!is_system_catalog)
elog(WARNING, "concurrent delete in progress within table \"%s\"",
RelationGetRelationName(heapRelation));
/*
* If we are performing uniqueness checks, assuming
* the tuple is dead could lead to missing a
* uniqueness violation. In that case we wait for the
* deleting transaction to finish and check again.
*
* Also, if it's a HOT-updated tuple, we should not
* index it but rather the live tuple at the end of
* the HOT-chain. However, the deleting transaction
* could abort, possibly leaving this tuple as live
* after all, in which case it has to be indexed. The
* only way to know what to do is to wait for the
* deleting transaction to finish and check again.
*/
if (checking_uniqueness ||
HeapTupleIsHotUpdated(heapTuple))
{
/*
* Must drop the lock on the buffer before we wait
*/
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
XactLockTableWait(xwait, heapRelation,
&heapTuple->t_self,
XLTW_InsertIndexUnique);
CHECK_FOR_INTERRUPTS();
goto recheck;
}
/*
* Otherwise index it but don't check for uniqueness,
* the same as a RECENTLY_DEAD tuple.
*/
indexIt = true;
/*
* Count HEAPTUPLE_DELETE_IN_PROGRESS tuples as live,
* if they were not deleted by the current
* transaction. That's what
* heapam_scan_analyze_next_tuple() does, and we want
* the behavior to be consistent.
*/
reltuples += 1;
}
else if (HeapTupleIsHotUpdated(heapTuple))
{
/*
* It's a HOT-updated tuple deleted by our own xact.
* We can assume the deletion will commit (else the
* index contents don't matter), so treat the same as
* RECENTLY_DEAD HOT-updated tuples.
*/
indexIt = false;
/* mark the index as unsafe for old snapshots */
indexInfo->ii_BrokenHotChain = true;
}
else
{
/*
* It's a regular tuple deleted by our own xact. Index
* it, but don't check for uniqueness nor count in
* reltuples, the same as a RECENTLY_DEAD tuple.
*/
indexIt = true;
}
/* In any case, exclude the tuple from unique-checking */
tupleIsAlive = false;
break;
default:
elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
indexIt = tupleIsAlive = false; /* keep compiler quiet */
break;
}
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
if (!indexIt)
continue;
}
else
{
/* heap_getnext did the time qual check */
tupleIsAlive = true;
reltuples += 1;
}
MemoryContextReset(econtext->ecxt_per_tuple_memory);
/* Set up for predicate or expression evaluation */
ExecStoreBufferHeapTuple(heapTuple, slot, hscan->rs_cbuf);
/*
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
if (predicateExpand != NULL)
{
if (!ExecQual(predicateExpand, econtext))
continue;
}
/*
* For the current heap tuple, extract all the attributes we use in
* this index, and note which are null. This also performs evaluation
* of any expressions needed.
*/
FormIndexDatum(indexInfo,
slot,
estate,
values,
isnull);
/*
* You'd think we should go ahead and build the index tuple here, but
* some index AMs want to do further processing on the data first. So
* pass the values[] and isnull[] arrays, instead.
*/
if (HeapTupleIsHeapOnly(heapTuple))
{
/*
* For a heap-only tuple, pretend its TID is that of the root. See
* src/backend/access/heap/README.HOT for discussion.
*/
ItemPointerData tid;
OffsetNumber offnum;
offnum = ItemPointerGetOffsetNumber(&heapTuple->t_self);
/*
* If a HOT tuple points to a root that we don't know about,
* obtain root items afresh. If that still fails, report it as
* corruption.
*/
if (root_offsets[offnum - 1] == InvalidOffsetNumber)
{
Page page = BufferGetPage(hscan->rs_cbuf);
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
heap_get_root_tuples(page, root_offsets);
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
}
if (!OffsetNumberIsValid(root_offsets[offnum - 1]))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("failed to find parent tuple for heap-only tuple at (%u,%u) in table \"%s\"",
ItemPointerGetBlockNumber(&heapTuple->t_self),
offnum,
RelationGetRelationName(heapRelation))));
ItemPointerSet(&tid, ItemPointerGetBlockNumber(&heapTuple->t_self),
root_offsets[offnum - 1]);
/* Call the AM's callback routine to process the tuple */
callback(indexRelation, &tid, values, isnull, tupleIsAlive,
callback_state);
}
else
{
/* Call the AM's callback routine to process the tuple */
callback(indexRelation, &heapTuple->t_self, values, isnull,
tupleIsAlive, callback_state);
}
}
/* Report scan progress one last time. */
if (progress)
{
BlockNumber blks_done;
if (hscan->rs_base.rs_parallel != NULL)
{
ParallelBlockTableScanDesc pbscan;
pbscan = (ParallelBlockTableScanDesc) hscan->rs_base.rs_parallel;
blks_done = pbscan->phs_nblocks;
}
else
blks_done = hscan->rs_nblocks;
pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
blks_done);
}
table_endscan(scan);
/* we can now forget our snapshot, if set and registered by us */
if (need_unregister_snapshot)
UnregisterSnapshot(snapshot);
ExecDropSingleTupleTableSlot(slot);
FreeExecutorState(estate);
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
indexInfo->ii_PredicateExpandState = NULL;
return reltuples;
}
static void
heapam_index_validate_scan(Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo,
Snapshot snapshot,
ValidateIndexState *state)
{
TableScanDesc scan;
HeapScanDesc hscan;
HeapTuple heapTuple;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
BlockNumber root_blkno = InvalidBlockNumber;
OffsetNumber root_offsets[MaxHeapTuplesPerPage];
bool in_index[MaxHeapTuplesPerPage];
BlockNumber previous_blkno = InvalidBlockNumber;
/* state variables for the merge */
ItemPointer indexcursor = NULL;
ItemPointerData decoded;
bool tuplesort_empty = false;
/*
* sanity checks
*/
Assert(OidIsValid(indexRelation->rd_rel->relam));
/*
* Need an EState for evaluation of index expressions and partial-index
* predicates. Also a slot to hold the current tuple.
*/
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
&TTSOpsHeapTuple);
/* Arrange for econtext's scan tuple to be the tuple under test */
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. We need just those tuples
* satisfying the passed-in reference snapshot. We must disable syncscan
* here, because it's critical that we read from block zero forward to
* match the sorted TIDs.
*/
scan = table_beginscan_strat(heapRelation, /* relation */
snapshot, /* snapshot */
0, /* number of keys */
NULL, /* scan key */
true, /* buffer access strategy OK */
false); /* syncscan not OK */
hscan = (HeapScanDesc) scan;
pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL,
hscan->rs_nblocks);
/*
* Scan all tuples matching the snapshot.
*/
while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
ItemPointer heapcursor = &heapTuple->t_self;
ItemPointerData rootTuple;
OffsetNumber root_offnum;
CHECK_FOR_INTERRUPTS();
state->htups += 1;
if ((previous_blkno == InvalidBlockNumber) ||
(hscan->rs_cblock != previous_blkno))
{
pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
hscan->rs_cblock);
previous_blkno = hscan->rs_cblock;
}
/*
* As commented in table_index_build_scan, we should index heap-only
* tuples under the TIDs of their root tuples; so when we advance onto
* a new heap page, build a map of root item offsets on the page.
*
* This complicates merging against the tuplesort output: we will
* visit the live tuples in order by their offsets, but the root
* offsets that we need to compare against the index contents might be
* ordered differently. So we might have to "look back" within the
* tuplesort output, but only within the current page. We handle that
* by keeping a bool array in_index[] showing all the
* already-passed-over tuplesort output TIDs of the current page. We
* clear that array here, when advancing onto a new heap page.
*/
if (hscan->rs_cblock != root_blkno)
{
Page page = BufferGetPage(hscan->rs_cbuf);
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
heap_get_root_tuples(page, root_offsets);
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
memset(in_index, 0, sizeof(in_index));
root_blkno = hscan->rs_cblock;
}
/* Convert actual tuple TID to root TID */
rootTuple = *heapcursor;
root_offnum = ItemPointerGetOffsetNumber(heapcursor);
if (HeapTupleIsHeapOnly(heapTuple))
{
root_offnum = root_offsets[root_offnum - 1];
if (!OffsetNumberIsValid(root_offnum))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("failed to find parent tuple for heap-only tuple at (%u,%u) in table \"%s\"",
ItemPointerGetBlockNumber(heapcursor),
ItemPointerGetOffsetNumber(heapcursor),
RelationGetRelationName(heapRelation))));
ItemPointerSetOffsetNumber(&rootTuple, root_offnum);
}
/*
* "merge" by skipping through the index tuples until we find or pass
* the current root tuple.
*/
while (!tuplesort_empty &&
(!indexcursor ||
ItemPointerCompare(indexcursor, &rootTuple) < 0))
{
Datum ts_val;
bool ts_isnull;
if (indexcursor)
{
/*
* Remember index items seen earlier on the current heap page
*/
if (ItemPointerGetBlockNumber(indexcursor) == root_blkno)
in_index[ItemPointerGetOffsetNumber(indexcursor) - 1] = true;
}
tuplesort_empty = !tuplesort_getdatum(state->tuplesort, true,
false, &ts_val, &ts_isnull,
NULL);
Assert(tuplesort_empty || !ts_isnull);
if (!tuplesort_empty)
{
itemptr_decode(&decoded, DatumGetInt64(ts_val));
indexcursor = &decoded;
}
else
{
/* Be tidy */
indexcursor = NULL;
}
}
/*
* If the tuplesort has overshot *and* we didn't see a match earlier,
* then this tuple is missing from the index, so insert it.
*/
if ((tuplesort_empty ||
ItemPointerCompare(indexcursor, &rootTuple) > 0) &&
!in_index[root_offnum - 1])
{
MemoryContextReset(econtext->ecxt_per_tuple_memory);
/* Set up for predicate or expression evaluation */
ExecStoreHeapTuple(heapTuple, slot, false);
/*
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
if (predicateExpand != NULL)
{
if (!ExecQual(predicateExpand, econtext))
continue;
}
/*
* For the current heap tuple, extract all the attributes we use
* in this index, and note which are null. This also performs
* evaluation of any expressions needed.
*/
FormIndexDatum(indexInfo,
slot,
estate,
values,
isnull);
/*
* You'd think we should go ahead and build the index tuple here,
* but some index AMs want to do further processing on the data
* first. So pass the values[] and isnull[] arrays, instead.
*/
/*
* If the tuple is already committed dead, you might think we
* could suppress uniqueness checking, but this is no longer true
* in the presence of HOT, because the insert is actually a proxy
* for a uniqueness check on the whole HOT-chain. That is, the
* tuple we have here could be dead because it was already
* HOT-updated, and if so the updating transaction will not have
* thought it should insert index entries. The index AM will
* check the whole HOT-chain and correctly detect a conflict if
* there is one.
*/
index_insert(indexRelation,
values,
isnull,
&rootTuple,
heapRelation,
indexInfo->ii_Unique ?
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
false,
indexInfo);
state->tups_inserted += 1;
}
}
table_endscan(scan);
ExecDropSingleTupleTableSlot(slot);
FreeExecutorState(estate);
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
indexInfo->ii_PredicateExpandState = NULL;
}
/*
* Return the number of blocks that have been read by this scan since
* starting. This is meant for progress reporting rather than be fully
* accurate: in a parallel scan, workers can be concurrently reading blocks
* further ahead than what we report.
*/
static BlockNumber
heapam_scan_get_blocks_done(HeapScanDesc hscan)
{
ParallelBlockTableScanDesc bpscan = NULL;
BlockNumber startblock;
BlockNumber blocks_done;
if (hscan->rs_base.rs_parallel != NULL)
{
bpscan = (ParallelBlockTableScanDesc) hscan->rs_base.rs_parallel;
startblock = bpscan->phs_startblock;
}
else
startblock = hscan->rs_startblock;
/*
* Might have wrapped around the end of the relation, if startblock was
* not zero.
*/
if (hscan->rs_cblock > startblock)
blocks_done = hscan->rs_cblock - startblock;
else
{
BlockNumber nblocks;
nblocks = bpscan != NULL ? bpscan->phs_nblocks : hscan->rs_nblocks;
blocks_done = nblocks - startblock +
hscan->rs_cblock;
}
return blocks_done;
}
/* ------------------------------------------------------------------------
* Miscellaneous callbacks for the heap AM
* ------------------------------------------------------------------------
*/
/*
* Check to see whether the table needs a TOAST table. It does only if
* (1) there are any toastable attributes, and (2) the maximum length
* of a tuple could exceed TOAST_TUPLE_THRESHOLD. (We don't want to
* create a toast table for something like "f1 varchar(20)".)
*/
static bool
heapam_relation_needs_toast_table(Relation rel)
{
int32 data_length = 0;
bool maxlength_unknown = false;
bool has_toastable_attrs = false;
TupleDesc tupdesc = rel->rd_att;
int32 tuple_length;
int i;
for (i = 0; i < tupdesc->natts; i++)
{
Form_pg_attribute att = TupleDescAttr(tupdesc, i);
if (att->attisdropped)
continue;
if (att->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
continue;
data_length = att_align_nominal(data_length, att->attalign);
if (att->attlen > 0)
{
/* Fixed-length types are never toastable */
data_length += att->attlen;
}
else
{
int32 maxlen = type_maximum_size(att->atttypid,
att->atttypmod);
if (maxlen < 0)
maxlength_unknown = true;
else
data_length += maxlen;
if (att->attstorage != TYPSTORAGE_PLAIN)
has_toastable_attrs = true;
}
}
if (!has_toastable_attrs)
return false; /* nothing to toast? */
if (maxlength_unknown)
return true; /* any unlimited-length attrs? */
tuple_length = MAXALIGN(SizeofHeapTupleHeader +
BITMAPLEN(tupdesc->natts)) +
MAXALIGN(data_length);
return (tuple_length > TOAST_TUPLE_THRESHOLD);
}
/*
* TOAST tables for heap relations are just heap relations.
*/
static Oid
heapam_relation_toast_am(Relation rel)
{
return rel->rd_rel->relam;
}
/* ------------------------------------------------------------------------
* Planner related callbacks for the heap AM
* ------------------------------------------------------------------------
*/
#define HEAP_OVERHEAD_BYTES_PER_TUPLE \
(MAXALIGN(SizeofHeapTupleHeader) + sizeof(ItemIdData))
#define HEAP_USABLE_BYTES_PER_PAGE \
(BLCKSZ - SizeOfPageHeaderData)
static void
heapam_estimate_rel_size(Relation rel, int32 *attr_widths,
BlockNumber *pages, double *tuples,
double *allvisfrac)
{
table_block_relation_estimate_size(rel, attr_widths, pages,
tuples, allvisfrac,
HEAP_OVERHEAD_BYTES_PER_TUPLE,
HEAP_USABLE_BYTES_PER_PAGE);
}
/* ------------------------------------------------------------------------
* Executor related callbacks for the heap AM
* ------------------------------------------------------------------------
*/
static bool
heapam_scan_bitmap_next_tuple(TableScanDesc scan,
TupleTableSlot *slot,
bool *recheck,
uint64 *lossy_pages,
uint64 *exact_pages)
{
BitmapHeapScanDesc bscan = (BitmapHeapScanDesc) scan;
HeapScanDesc hscan = (HeapScanDesc) bscan;
OffsetNumber targoffset;
Page page;
ItemId lp;
/*
* Out of range? If so, nothing more to look at on this page
*/
while (hscan->rs_cindex >= hscan->rs_ntuples)
{
/*
* Returns false if the bitmap is exhausted and there are no further
* blocks we need to scan.
*/
if (!BitmapHeapScanNextBlock(scan, recheck, lossy_pages, exact_pages))
return false;
}
targoffset = hscan->rs_vistuples[hscan->rs_cindex];
page = BufferGetPage(hscan->rs_cbuf);
lp = PageGetItemId(page, targoffset);
Assert(ItemIdIsNormal(lp));
hscan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
hscan->rs_ctup.t_len = ItemIdGetLength(lp);
hscan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
ItemPointerSet(&hscan->rs_ctup.t_self, hscan->rs_cblock, targoffset);
pgstat_count_heap_fetch(scan->rs_rd);
/*
* Set up the result slot to point to this tuple. Note that the slot
* acquires a pin on the buffer.
*/
ExecStoreBufferHeapTuple(&hscan->rs_ctup,
slot,
hscan->rs_cbuf);
hscan->rs_cindex++;
return true;
}
static bool
heapam_scan_sample_next_block(TableScanDesc scan, SampleScanState *scanstate)
{
HeapScanDesc hscan = (HeapScanDesc) scan;
TsmRoutine *tsm = scanstate->tsmroutine;
BlockNumber blockno;
/* return false immediately if relation is empty */
if (hscan->rs_nblocks == 0)
return false;
/* release previous scan buffer, if any */
if (BufferIsValid(hscan->rs_cbuf))
{
ReleaseBuffer(hscan->rs_cbuf);
hscan->rs_cbuf = InvalidBuffer;
}
if (tsm->NextSampleBlock)
blockno = tsm->NextSampleBlock(scanstate, hscan->rs_nblocks);
else
{
/* scanning table sequentially */
if (hscan->rs_cblock == InvalidBlockNumber)
{
Assert(!hscan->rs_inited);
blockno = hscan->rs_startblock;
}
else
{
Assert(hscan->rs_inited);
blockno = hscan->rs_cblock + 1;
if (blockno >= hscan->rs_nblocks)
{
/* wrap to beginning of rel, might not have started at 0 */
blockno = 0;
}
/*
* Report our new scan position for synchronization purposes.
*
* Note: we do this before checking for end of scan so that the
* final state of the position hint is back at the start of the
* rel. That's not strictly necessary, but otherwise when you run
* the same query multiple times the starting position would shift
* a little bit backwards on every invocation, which is confusing.
* We don't guarantee any specific ordering in general, though.
*/
if (scan->rs_flags & SO_ALLOW_SYNC)
ss_report_location(scan->rs_rd, blockno);
if (blockno == hscan->rs_startblock)
{
blockno = InvalidBlockNumber;
}
}
}
hscan->rs_cblock = blockno;
if (!BlockNumberIsValid(blockno))
{
hscan->rs_inited = false;
return false;
}
Assert(hscan->rs_cblock < hscan->rs_nblocks);
/*
* Be sure to check for interrupts at least once per page. Checks at
* higher code levels won't be able to stop a sample scan that encounters
* many pages' worth of consecutive dead tuples.
*/
CHECK_FOR_INTERRUPTS();
/* Read page using selected strategy */
hscan->rs_cbuf = ReadBufferExtended(hscan->rs_base.rs_rd, MAIN_FORKNUM,
blockno, RBM_NORMAL, hscan->rs_strategy);
/* in pagemode, prune the page and determine visible tuple offsets */
if (hscan->rs_base.rs_flags & SO_ALLOW_PAGEMODE)
heap_prepare_pagescan(scan);
hscan->rs_inited = true;
return true;
}
static bool
heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
TupleTableSlot *slot)
{
HeapScanDesc hscan = (HeapScanDesc) scan;
TsmRoutine *tsm = scanstate->tsmroutine;
BlockNumber blockno = hscan->rs_cblock;
bool pagemode = (scan->rs_flags & SO_ALLOW_PAGEMODE) != 0;
Page page;
bool all_visible;
OffsetNumber maxoffset;
/*
* When not using pagemode, we must lock the buffer during tuple
* visibility checks.
*/
if (!pagemode)
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
page = BufferGetPage(hscan->rs_cbuf);
all_visible = PageIsAllVisible(page) &&
!scan->rs_snapshot->takenDuringRecovery;
maxoffset = PageGetMaxOffsetNumber(page);
for (;;)
{
OffsetNumber tupoffset;
CHECK_FOR_INTERRUPTS();
/* Ask the tablesample method which tuples to check on this page. */
tupoffset = tsm->NextSampleTuple(scanstate,
blockno,
maxoffset);
if (OffsetNumberIsValid(tupoffset))
{
ItemId itemid;
bool visible;
HeapTuple tuple = &(hscan->rs_ctup);
/* Skip invalid tuple pointers. */
itemid = PageGetItemId(page, tupoffset);
if (!ItemIdIsNormal(itemid))
continue;
tuple->t_data = (HeapTupleHeader) PageGetItem(page, itemid);
tuple->t_len = ItemIdGetLength(itemid);
ItemPointerSet(&(tuple->t_self), blockno, tupoffset);
if (all_visible)
visible = true;
else
visible = SampleHeapTupleVisible(scan, hscan->rs_cbuf,
tuple, tupoffset);
/* in pagemode, heap_prepare_pagescan did this for us */
if (!pagemode)
HeapCheckForSerializableConflictOut(visible, scan->rs_rd, tuple,
hscan->rs_cbuf, scan->rs_snapshot);
/* Try next tuple from same page. */
if (!visible)
continue;
/* Found visible tuple, return it. */
if (!pagemode)
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
ExecStoreBufferHeapTuple(tuple, slot, hscan->rs_cbuf);
/* Count successfully-fetched tuples as heap fetches */
pgstat_count_heap_getnext(scan->rs_rd);
return true;
}
else
{
/*
* If we get here, it means we've exhausted the items on this page
* and it's time to move to the next.
*/
if (!pagemode)
LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
ExecClearTuple(slot);
return false;
}
}
Assert(0);
}
/* ----------------------------------------------------------------------------
* Helper functions for the above.
* ----------------------------------------------------------------------------
*/
/*
* Reconstruct and rewrite the given tuple
*
* We cannot simply copy the tuple as-is, for several reasons:
*
* 1. We'd like to squeeze out the values of any dropped columns, both
* to save space and to ensure we have no corner-case failures. (It's
* possible for example that the new table hasn't got a TOAST table
* and so is unable to store any large values of dropped cols.)
*
* 2. The tuple might not even be legal for the new table; this is
* currently only known to happen as an after-effect of ALTER TABLE
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
{
HeapTuple newtuple;
newtuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
/* The heap rewrite module does the rest */
rewrite_heap_tuple(rwstate, tuple, newtuple);
heap_freetuple(newtuple);
}
/*
* Insert tuple when processing REPACK CONCURRENTLY.
*
* rewriteheap.c is not used in the CONCURRENTLY case because it'd be
* difficult to do the same in the catch-up phase (as the logical
* decoding does not provide us with sufficient visibility
* information). Thus we must use heap_insert() both during the
* catch-up and here.
*
* We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
* decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
* this relation, so no logical replication subscription should need the data.
*
* BulkInsertState is used because many tuples are inserted in the typical
* case.
*/
static void
heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, BulkInsertState bistate)
{
HeapTuple newtuple;
newtuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
heap_insert(NewHeap, newtuple, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, bistate);
heap_freetuple(newtuple);
}
/*
* Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
*
* Deform the given tuple, set values of dropped columns to NULL, and fill in
* any values from attmissingval; then form a new tuple and return it. If no
* attributes need to be changed, a copy of the original tuple is returned.
* Caller is responsible for freeing the returned tuple.
*
* XXX this coding assumes that both relations have the same tupledesc.
*/
static HeapTuple
reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
bool needs_reform = false;
/*
* A short tuple might require values from attmissing val, so activate the
* coding unconditionally in that case. The value might legitimally be
* NULL otherwise, so this is slightly wasteful, but it probably beats
* having to test each attribute for presence of attmissingval each time.
*/
if (HeapTupleHeaderGetNatts(tuple->t_data) < newTupDesc->natts)
needs_reform = true;
/*
* If the column has been dropped but a value is still present, we can
* optimize storage now by getting rid of it.
*/
if (!needs_reform)
{
for (int i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped &&
!heap_attisnull(tuple, i + 1, newTupDesc))
{
needs_reform = true;
break;
}
}
}
/* Skip work if no changes are needed */
if (!needs_reform)
return heap_copytuple(tuple);
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
for (int i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
return heap_form_tuple(newTupDesc, values, isnull);
}
/*
* Check visibility of the tuple.
*/
static bool
SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
OffsetNumber tupoffset)
{
HeapScanDesc hscan = (HeapScanDesc) scan;
if (scan->rs_flags & SO_ALLOW_PAGEMODE)
{
uint32 start = 0,
end = hscan->rs_ntuples;
/*
* In pageatatime mode, heap_prepare_pagescan() already did visibility
* checks, so just look at the info it left in rs_vistuples[].
*
* We use a binary search over the known-sorted array. Note: we could
* save some effort if we insisted that NextSampleTuple select tuples
* in increasing order, but it's not clear that there would be enough
* gain to justify the restriction.
*/
while (start < end)
{
uint32 mid = start + (end - start) / 2;
OffsetNumber curoffset = hscan->rs_vistuples[mid];
if (tupoffset == curoffset)
return true;
else if (tupoffset < curoffset)
end = mid;
else
start = mid + 1;
}
return false;
}
else
{
/* Otherwise, we have to check the tuple individually. */
return HeapTupleSatisfiesVisibility(tuple, scan->rs_snapshot,
buffer);
}
}
/*
* Helper function get the next block of a bitmap heap scan. Returns true when
* it got the next block and saved it in the scan descriptor and false when
* the bitmap and or relation are exhausted.
*/
static bool
BitmapHeapScanNextBlock(TableScanDesc scan,
bool *recheck,
uint64 *lossy_pages, uint64 *exact_pages)
{
BitmapHeapScanDesc bscan = (BitmapHeapScanDesc) scan;
HeapScanDesc hscan = (HeapScanDesc) bscan;
BlockNumber block;
void *per_buffer_data;
Buffer buffer;
Snapshot snapshot;
int ntup;
TBMIterateResult *tbmres;
OffsetNumber offsets[TBM_MAX_TUPLES_PER_PAGE];
int noffsets = -1;
Assert(scan->rs_flags & SO_TYPE_BITMAPSCAN);
Assert(hscan->rs_read_stream);
hscan->rs_cindex = 0;
hscan->rs_ntuples = 0;
/* Release buffer containing previous block. */
if (BufferIsValid(hscan->rs_cbuf))
{
ReleaseBuffer(hscan->rs_cbuf);
hscan->rs_cbuf = InvalidBuffer;
}
hscan->rs_cbuf = read_stream_next_buffer(hscan->rs_read_stream,
&per_buffer_data);
if (BufferIsInvalid(hscan->rs_cbuf))
{
/* the bitmap is exhausted */
return false;
}
Assert(per_buffer_data);
tbmres = per_buffer_data;
Assert(BlockNumberIsValid(tbmres->blockno));
Assert(BufferGetBlockNumber(hscan->rs_cbuf) == tbmres->blockno);
/* Exact pages need their tuple offsets extracted. */
if (!tbmres->lossy)
noffsets = tbm_extract_page_tuple(tbmres, offsets,
TBM_MAX_TUPLES_PER_PAGE);
*recheck = tbmres->recheck;
block = hscan->rs_cblock = tbmres->blockno;
buffer = hscan->rs_cbuf;
snapshot = scan->rs_snapshot;
ntup = 0;
/*
* Prune and repair fragmentation for the whole page, if possible.
*/
heap_page_prune_opt(scan->rs_rd, buffer, &hscan->rs_vmbuffer,
scan->rs_flags & SO_HINT_REL_READ_ONLY);
/*
* We must hold share lock on the buffer content while examining tuple
* visibility. Afterwards, however, the tuples we have found to be
* visible are guaranteed good as long as we hold the buffer pin.
*/
LockBuffer(buffer, BUFFER_LOCK_SHARE);
/*
* We need two separate strategies for lossy and non-lossy cases.
*/
if (!tbmres->lossy)
{
/*
* Bitmap is non-lossy, so we just look through the offsets listed in
* tbmres; but we have to follow any HOT chain starting at each such
* offset.
*/
int curslot;
/* We must have extracted the tuple offsets by now */
Assert(noffsets > -1);
for (curslot = 0; curslot < noffsets; curslot++)
{
OffsetNumber offnum = offsets[curslot];
ItemPointerData tid;
HeapTupleData heapTuple;
ItemPointerSet(&tid, block, offnum);
if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
&heapTuple, NULL, true))
hscan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
}
}
else
{
/*
* Bitmap is lossy, so we must examine each line pointer on the page.
* But we can ignore HOT chains, since we'll check each tuple anyway.
*/
Page page = BufferGetPage(buffer);
OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
OffsetNumber offnum;
for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
{
ItemId lp;
HeapTupleData loctup;
bool valid;
lp = PageGetItemId(page, offnum);
if (!ItemIdIsNormal(lp))
continue;
loctup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
loctup.t_len = ItemIdGetLength(lp);
loctup.t_tableOid = scan->rs_rd->rd_id;
ItemPointerSet(&loctup.t_self, block, offnum);
valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
if (valid)
{
hscan->rs_vistuples[ntup++] = offnum;
PredicateLockTID(scan->rs_rd, &loctup.t_self, snapshot,
HeapTupleHeaderGetXmin(loctup.t_data));
}
HeapCheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
buffer, snapshot);
}
}
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
Assert(ntup <= MaxHeapTuplesPerPage);
hscan->rs_ntuples = ntup;
if (tbmres->lossy)
(*lossy_pages)++;
else
(*exact_pages)++;
/*
* Return true to indicate that a valid block was found and the bitmap is
* not exhausted. If there are no visible tuples on this page,
* hscan->rs_ntuples will be 0 and heapam_scan_bitmap_next_tuple() will
* return false returning control to this function to advance to the next
* block in the bitmap.
*/
return true;
}
/* ------------------------------------------------------------------------
* Definition of the heap table access method.
* ------------------------------------------------------------------------
*/
static const TableAmRoutine heapam_methods = {
.type = T_TableAmRoutine,
.slot_callbacks = heapam_slot_callbacks,
.scan_begin = heap_beginscan,
.scan_end = heap_endscan,
.scan_rescan = heap_rescan,
.scan_getnextslot = heap_getnextslot,
.scan_set_tidrange = heap_set_tidrange,
.scan_getnextslot_tidrange = heap_getnextslot_tidrange,
.parallelscan_estimate = table_block_parallelscan_estimate,
.parallelscan_initialize = table_block_parallelscan_initialize,
.parallelscan_reinitialize = table_block_parallelscan_reinitialize,
.index_fetch_begin = heapam_index_fetch_begin,
.index_fetch_reset = heapam_index_fetch_reset,
.index_fetch_end = heapam_index_fetch_end,
.index_fetch_tuple = heapam_index_fetch_tuple,
.tuple_insert = heapam_tuple_insert,
.tuple_insert_speculative = heapam_tuple_insert_speculative,
.tuple_complete_speculative = heapam_tuple_complete_speculative,
.multi_insert = heap_multi_insert,
.tuple_delete = heapam_tuple_delete,
.tuple_update = heapam_tuple_update,
.tuple_lock = heapam_tuple_lock,
.tuple_fetch_row_version = heapam_fetch_row_version,
.tuple_get_latest_tid = heap_get_latest_tid,
.tuple_tid_valid = heapam_tuple_tid_valid,
.tuple_satisfies_snapshot = heapam_tuple_satisfies_snapshot,
.index_delete_tuples = heap_index_delete_tuples,
.relation_set_new_filelocator = heapam_relation_set_new_filelocator,
.relation_nontransactional_truncate = heapam_relation_nontransactional_truncate,
.relation_copy_data = heapam_relation_copy_data,
.relation_copy_for_cluster = heapam_relation_copy_for_cluster,
.relation_vacuum = heap_vacuum_rel,
.scan_analyze_next_block = heapam_scan_analyze_next_block,
.scan_analyze_next_tuple = heapam_scan_analyze_next_tuple,
.index_build_range_scan = heapam_index_build_range_scan,
.index_validate_scan = heapam_index_validate_scan,
.relation_size = table_block_relation_size,
.relation_needs_toast_table = heapam_relation_needs_toast_table,
.relation_toast_am = heapam_relation_toast_am,
.relation_fetch_toast_slice = heap_fetch_toast_slice,
.relation_estimate_size = heapam_estimate_rel_size,
.scan_bitmap_next_tuple = heapam_scan_bitmap_next_tuple,
.scan_sample_next_block = heapam_scan_sample_next_block,
.scan_sample_next_tuple = heapam_scan_sample_next_tuple
};
const TableAmRoutine *
GetHeapamTableAmRoutine(void)
{
return &heapam_methods;
}
Datum
heap_tableam_handler(PG_FUNCTION_ARGS)
{
PG_RETURN_POINTER(&heapam_methods);
}
./index.c 0000664 0001750 0001750 00000427611 15222105363 011172 0 ustar xman xman /*-------------------------------------------------------------------------
*
* index.c
* code to create and destroy POSTGRES index relations
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/catalog/index.c
*
*
* INTERFACE ROUTINES
* index_create() - Create a cataloged index relation
* index_drop() - Removes index relation from catalogs
* BuildIndexInfo() - Prepare to insert index tuples
* FormIndexDatum() - Construct datum vector for one index tuple
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <unistd.h>
#include "access/amapi.h"
#include "access/attmap.h"
#include "access/heapam.h"
#include "access/multixact.h"
#include "access/relscan.h"
#include "access/tableam.h"
#include "access/toast_compression.h"
#include "access/transam.h"
#include "access/visibilitymap.h"
#include "access/xact.h"
#include "bootstrap/bootstrap.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_description.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "commands/event_trigger.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parser.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "rewrite/rewriteManip.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tuplesort.h"
/* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_index_pg_class_oid = InvalidOid;
RelFileNumber binary_upgrade_next_index_pg_class_relfilenumber =
InvalidRelFileNumber;
/*
* Pointer-free representation of variables used when reindexing system
* catalogs; we use this to propagate those values to parallel workers.
*/
typedef struct
{
Oid currentlyReindexedHeap;
Oid currentlyReindexedIndex;
int numPendingReindexedIndexes;
Oid pendingReindexedIndexes[FLEXIBLE_ARRAY_MEMBER];
} SerializedReindexState;
/* non-export function prototypes */
static bool relationHasPrimaryKey(Relation rel);
static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
const IndexInfo *indexInfo,
const List *indexColNames,
Oid accessMethodId,
const Oid *collationIds,
const Oid *opclassIds);
static void InitializeAttributeOids(Relation indexRelation,
int numatts, Oid indexoid);
static void AppendAttributeTuples(Relation indexRelation, const Datum *attopts, const NullableDatum *stattargets);
static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
Oid parentIndexId,
const IndexInfo *indexInfo,
const Oid *collationOids,
const Oid *opclassOids,
const int16 *coloptions,
bool primary,
bool isexclusion,
bool immediate,
bool isvalid,
bool isready);
static void index_update_stats(Relation rel,
bool hasindex,
double reltuples);
static void IndexCheckExclusion(Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo);
static bool validate_index_callback(ItemPointer itemptr, void *opaque);
static bool ReindexIsCurrentlyProcessingIndex(Oid indexOid);
static void SetReindexProcessing(Oid heapOid, Oid indexOid);
static void ResetReindexProcessing(void);
static void SetReindexPending(List *indexes);
static void RemoveReindexPending(Oid indexOid);
/*
* relationHasPrimaryKey
* See whether an existing relation has a primary key.
*
* Caller must have suitable lock on the relation.
*
* Note: we intentionally do not check indisvalid here; that's because this
* is used to enforce the rule that there can be only one indisprimary index,
* and we want that to be true even if said index is invalid.
*/
static bool
relationHasPrimaryKey(Relation rel)
{
bool result = false;
List *indexoidlist;
ListCell *indexoidscan;
/*
* Get the list of index OIDs for the table from the relcache, and look up
* each one in the pg_index syscache until we find one marked primary key
* (hopefully there isn't more than one such).
*/
indexoidlist = RelationGetIndexList(rel);
foreach(indexoidscan, indexoidlist)
{
Oid indexoid = lfirst_oid(indexoidscan);
HeapTuple indexTuple;
indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
if (!HeapTupleIsValid(indexTuple)) /* should not happen */
elog(ERROR, "cache lookup failed for index %u", indexoid);
result = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary;
ReleaseSysCache(indexTuple);
if (result)
break;
}
list_free(indexoidlist);
return result;
}
/*
* index_check_primary_key
* Apply special checks needed before creating a PRIMARY KEY index
*
* This processing used to be in DefineIndex(), but has been split out
* so that it can be applied during ALTER TABLE ADD PRIMARY KEY USING INDEX.
*
* We check for a pre-existing primary key, and that all columns of the index
* are simple column references (not expressions), and that all those
* columns are marked NOT NULL. If not, fail.
*
* We used to automatically change unmarked columns to NOT NULL here by doing
* our own local ALTER TABLE command. But that doesn't work well if we're
* executing one subcommand of an ALTER TABLE: the operations may not get
* performed in the right order overall. Now we expect that the parser
* inserted any required ALTER TABLE SET NOT NULL operations before trying
* to create a primary-key index.
*
* Caller had better have at least ShareLock on the table, else the not-null
* checking isn't trustworthy.
*/
void
index_check_primary_key(Relation heapRel,
const IndexInfo *indexInfo,
bool is_alter_table,
const IndexStmt *stmt)
{
int i;
/*
* If ALTER TABLE or CREATE TABLE .. PARTITION OF, check that there isn't
* already a PRIMARY KEY. In CREATE TABLE for an ordinary relation, we
* have faith that the parser rejected multiple pkey clauses; and CREATE
* INDEX doesn't have a way to say PRIMARY KEY, so it's no problem either.
*/
if ((is_alter_table || heapRel->rd_rel->relispartition) &&
relationHasPrimaryKey(heapRel))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("multiple primary keys for table \"%s\" are not allowed",
RelationGetRelationName(heapRel))));
}
/*
* Indexes created with NULLS NOT DISTINCT cannot be used for primary key
* constraints. While there is no direct syntax to reach here, it can be
* done by creating a separate index and attaching it via ALTER TABLE ..
* USING INDEX.
*/
if (indexInfo->ii_NullsNotDistinct)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("primary keys cannot use NULLS NOT DISTINCT indexes")));
}
/*
* Check that all of the attributes in a primary key are marked as not
* null. (We don't really expect to see that; it'd mean the parser messed
* up. But it seems wise to check anyway.)
*/
for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
{
AttrNumber attnum = indexInfo->ii_IndexAttrNumbers[i];
HeapTuple atttuple;
Form_pg_attribute attform;
if (attnum == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("primary keys cannot be expressions")));
/* System attributes are never null, so no need to check */
if (attnum < 0)
continue;
atttuple = SearchSysCache2(ATTNUM,
ObjectIdGetDatum(RelationGetRelid(heapRel)),
Int16GetDatum(attnum));
if (!HeapTupleIsValid(atttuple))
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, RelationGetRelid(heapRel));
attform = (Form_pg_attribute) GETSTRUCT(atttuple);
if (!attform->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("primary key column \"%s\" is not marked NOT NULL",
NameStr(attform->attname))));
ReleaseSysCache(atttuple);
}
}
/*
* ConstructTupleDescriptor
*
* Build an index tuple descriptor for a new index
*/
static TupleDesc
ConstructTupleDescriptor(Relation heapRelation,
const IndexInfo *indexInfo,
const List *indexColNames,
Oid accessMethodId,
const Oid *collationIds,
const Oid *opclassIds)
{
int numatts = indexInfo->ii_NumIndexAttrs;
int numkeyatts = indexInfo->ii_NumIndexKeyAttrs;
ListCell *colnames_item = list_head(indexColNames);
ListCell *indexpr_item = list_head(indexInfo->ii_Expressions);
const IndexAmRoutine *amroutine;
TupleDesc heapTupDesc;
TupleDesc indexTupDesc;
int natts; /* #atts in heap rel --- for error checks */
int i;
/* We need access to the index AM's API struct */
amroutine = GetIndexAmRoutineByAmId(accessMethodId, false);
/* ... and to the table's tuple descriptor */
heapTupDesc = RelationGetDescr(heapRelation);
natts = RelationGetForm(heapRelation)->relnatts;
/*
* allocate the new tuple descriptor
*/
indexTupDesc = CreateTemplateTupleDesc(numatts);
/*
* Fill in the pg_attribute row.
*/
for (i = 0; i < numatts; i++)
{
AttrNumber atnum = indexInfo->ii_IndexAttrNumbers[i];
Form_pg_attribute to = TupleDescAttr(indexTupDesc, i);
HeapTuple tuple;
Form_pg_type typeTup;
Form_pg_opclass opclassTup;
Oid keyType;
MemSet(to, 0, ATTRIBUTE_FIXED_PART_SIZE);
to->attnum = i + 1;
to->attislocal = true;
to->attcollation = (i < numkeyatts) ? collationIds[i] : InvalidOid;
/*
* Set the attribute name as specified by caller.
*/
if (colnames_item == NULL) /* shouldn't happen */
elog(ERROR, "too few entries in colnames list");
namestrcpy(&to->attname, (const char *) lfirst(colnames_item));
colnames_item = lnext(indexColNames, colnames_item);
/*
* For simple index columns, we copy some pg_attribute fields from the
* parent relation. For expressions we have to look at the expression
* result.
*/
if (atnum != 0)
{
/* Simple index column */
const FormData_pg_attribute *from;
Assert(atnum > 0); /* should've been caught above */
if (atnum > natts) /* safety check */
elog(ERROR, "invalid column number %d", atnum);
from = TupleDescAttr(heapTupDesc,
AttrNumberGetAttrOffset(atnum));
to->atttypid = from->atttypid;
to->attlen = from->attlen;
to->attndims = from->attndims;
to->atttypmod = from->atttypmod;
to->attbyval = from->attbyval;
to->attalign = from->attalign;
to->attstorage = from->attstorage;
to->attcompression = from->attcompression;
}
else
{
/* Expressional index */
Node *indexkey;
if (indexpr_item == NULL) /* shouldn't happen */
elog(ERROR, "too few entries in indexprs list");
indexkey = (Node *) lfirst(indexpr_item);
indexpr_item = lnext(indexInfo->ii_Expressions, indexpr_item);
/*
* Lookup the expression type in pg_type for the type length etc.
*/
keyType = exprType(indexkey);
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(keyType));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for type %u", keyType);
typeTup = (Form_pg_type) GETSTRUCT(tuple);
/*
* Assign some of the attributes values. Leave the rest.
*/
to->atttypid = keyType;
to->attlen = typeTup->typlen;
to->atttypmod = exprTypmod(indexkey);
to->attbyval = typeTup->typbyval;
to->attalign = typeTup->typalign;
to->attstorage = typeTup->typstorage;
/*
* For expression columns, set attcompression invalid, since
* there's no table column from which to copy the value. Whenever
* we actually need to compress a value, we'll use whatever the
* current value of default_toast_compression is at that point in
* time.
*/
to->attcompression = InvalidCompressionMethod;
ReleaseSysCache(tuple);
/*
* Make sure the expression yields a type that's safe to store in
* an index. We need this defense because we have index opclasses
* for pseudo-types such as "record", and the actually stored type
* had better be safe; eg, a named composite type is okay, an
* anonymous record type is not. The test is the same as for
* whether a table column is of a safe type (which is why we
* needn't check for the non-expression case).
*/
CheckAttributeType(NameStr(to->attname),
to->atttypid, to->attcollation,
NIL, 0);
}
/*
* We do not yet have the correct relation OID for the index, so just
* set it invalid for now. InitializeAttributeOids() will fix it
* later.
*/
to->attrelid = InvalidOid;
/*
* Check the opclass and index AM to see if either provides a keytype
* (overriding the attribute type). Opclass (if exists) takes
* precedence.
*/
keyType = amroutine->amkeytype;
if (i < indexInfo->ii_NumIndexKeyAttrs)
{
tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassIds[i]));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for opclass %u", opclassIds[i]);
opclassTup = (Form_pg_opclass) GETSTRUCT(tuple);
if (OidIsValid(opclassTup->opckeytype))
keyType = opclassTup->opckeytype;
/*
* If keytype is specified as ANYELEMENT, and opcintype is
* ANYARRAY, then the attribute type must be an array (else it'd
* not have matched this opclass); use its element type.
*
* We could also allow ANYCOMPATIBLE/ANYCOMPATIBLEARRAY here, but
* there seems no need to do so; there's no reason to declare an
* opclass as taking ANYCOMPATIBLEARRAY rather than ANYARRAY.
*/
if (keyType == ANYELEMENTOID && opclassTup->opcintype == ANYARRAYOID)
{
keyType = get_base_element_type(to->atttypid);
if (!OidIsValid(keyType))
elog(ERROR, "could not get element type of array type %u",
to->atttypid);
}
ReleaseSysCache(tuple);
}
/*
* If a key type different from the heap value is specified, update
* the type-related fields in the index tupdesc.
*/
if (OidIsValid(keyType) && keyType != to->atttypid)
{
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(keyType));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for type %u", keyType);
typeTup = (Form_pg_type) GETSTRUCT(tuple);
to->atttypid = keyType;
to->atttypmod = -1;
to->attlen = typeTup->typlen;
to->attbyval = typeTup->typbyval;
to->attalign = typeTup->typalign;
to->attstorage = typeTup->typstorage;
/* As above, use the default compression method in this case */
to->attcompression = InvalidCompressionMethod;
ReleaseSysCache(tuple);
}
populate_compact_attribute(indexTupDesc, i);
}
TupleDescFinalize(indexTupDesc);
return indexTupDesc;
}
/* ----------------------------------------------------------------
* InitializeAttributeOids
* ----------------------------------------------------------------
*/
static void
InitializeAttributeOids(Relation indexRelation,
int numatts,
Oid indexoid)
{
TupleDesc tupleDescriptor;
int i;
tupleDescriptor = RelationGetDescr(indexRelation);
for (i = 0; i < numatts; i += 1)
TupleDescAttr(tupleDescriptor, i)->attrelid = indexoid;
}
/* ----------------------------------------------------------------
* AppendAttributeTuples
* ----------------------------------------------------------------
*/
static void
AppendAttributeTuples(Relation indexRelation, const Datum *attopts, const NullableDatum *stattargets)
{
Relation pg_attribute;
CatalogIndexState indstate;
TupleDesc indexTupDesc;
FormExtraData_pg_attribute *attrs_extra = NULL;
if (attopts)
{
attrs_extra = palloc0_array(FormExtraData_pg_attribute, indexRelation->rd_att->natts);
for (int i = 0; i < indexRelation->rd_att->natts; i++)
{
if (attopts[i])
attrs_extra[i].attoptions.value = attopts[i];
else
attrs_extra[i].attoptions.isnull = true;
if (stattargets)
attrs_extra[i].attstattarget = stattargets[i];
else
attrs_extra[i].attstattarget.isnull = true;
}
}
/*
* open the attribute relation and its indexes
*/
pg_attribute = table_open(AttributeRelationId, RowExclusiveLock);
indstate = CatalogOpenIndexes(pg_attribute);
/*
* insert data from new index's tupdesc into pg_attribute
*/
indexTupDesc = RelationGetDescr(indexRelation);
InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, attrs_extra, indstate);
CatalogCloseIndexes(indstate);
table_close(pg_attribute, RowExclusiveLock);
}
/* ----------------------------------------------------------------
* UpdateIndexRelation
*
* Construct and insert a new entry in the pg_index catalog
* ----------------------------------------------------------------
*/
static void
UpdateIndexRelation(Oid indexoid,
Oid heapoid,
Oid parentIndexId,
const IndexInfo *indexInfo,
const Oid *collationOids,
const Oid *opclassOids,
const int16 *coloptions,
bool primary,
bool isexclusion,
bool immediate,
bool isvalid,
bool isready)
{
int2vector *indkey;
oidvector *indcollation;
oidvector *indclass;
int2vector *indoption;
Datum exprsDatum;
Datum predDatum;
Datum values[Natts_pg_index];
bool nulls[Natts_pg_index] = {0};
Relation pg_index;
HeapTuple tuple;
int i;
/*
* Copy the index key, opclass, and indoption info into arrays (should we
* make the caller pass them like this to start with?)
*/
indkey = buildint2vector(NULL, indexInfo->ii_NumIndexAttrs);
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
indkey->values[i] = indexInfo->ii_IndexAttrNumbers[i];
indcollation = buildoidvector(collationOids, indexInfo->ii_NumIndexKeyAttrs);
indclass = buildoidvector(opclassOids, indexInfo->ii_NumIndexKeyAttrs);
indoption = buildint2vector(coloptions, indexInfo->ii_NumIndexKeyAttrs);
/*
* Convert the index expressions (if any) to a text datum
*/
if (indexInfo->ii_Expressions != NIL)
{
char *exprsString;
exprsString = nodeToString(indexInfo->ii_Expressions);
exprsDatum = CStringGetTextDatum(exprsString);
pfree(exprsString);
}
else
exprsDatum = (Datum) 0;
/*
* Convert the index predicate (if any) to a text datum. Note we convert
* implicit-AND format to normal explicit-AND for storage.
*/
if (indexInfo->ii_Predicate != NIL)
{
char *predString;
predString = nodeToString(make_ands_explicit(indexInfo->ii_Predicate));
predDatum = CStringGetTextDatum(predString);
pfree(predString);
}
else
predDatum = (Datum) 0;
/*
* open the system catalog index relation
*/
pg_index = table_open(IndexRelationId, RowExclusiveLock);
/*
* Build a pg_index tuple
*/
values[Anum_pg_index_indexrelid - 1] = ObjectIdGetDatum(indexoid);
values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(heapoid);
values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs);
values[Anum_pg_index_indnkeyatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexKeyAttrs);
values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique);
values[Anum_pg_index_indnullsnotdistinct - 1] = BoolGetDatum(indexInfo->ii_NullsNotDistinct);
values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
values[Anum_pg_index_indisexclusion - 1] = BoolGetDatum(isexclusion);
values[Anum_pg_index_indimmediate - 1] = BoolGetDatum(immediate);
values[Anum_pg_index_indisclustered - 1] = BoolGetDatum(false);
values[Anum_pg_index_indisvalid - 1] = BoolGetDatum(isvalid);
values[Anum_pg_index_indcheckxmin - 1] = BoolGetDatum(false);
values[Anum_pg_index_indisready - 1] = BoolGetDatum(isready);
values[Anum_pg_index_indislive - 1] = BoolGetDatum(true);
values[Anum_pg_index_indisreplident - 1] = BoolGetDatum(false);
values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey);
values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation);
values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass);
values[Anum_pg_index_indoption - 1] = PointerGetDatum(indoption);
values[Anum_pg_index_indexprs - 1] = exprsDatum;
if (exprsDatum == (Datum) 0)
nulls[Anum_pg_index_indexprs - 1] = true;
values[Anum_pg_index_indpred - 1] = predDatum;
if (predDatum == (Datum) 0)
nulls[Anum_pg_index_indpred - 1] = true;
tuple = heap_form_tuple(RelationGetDescr(pg_index), values, nulls);
/*
* insert the tuple into the pg_index catalog
*/
CatalogTupleInsert(pg_index, tuple);
/*
* close the relation and free the tuple
*/
table_close(pg_index, RowExclusiveLock);
heap_freetuple(tuple);
}
/*
* index_create
*
* heapRelation: table to build index on (suitably locked by caller)
* indexRelationName: what it say
* indexRelationId: normally, pass InvalidOid to let this routine
* generate an OID for the index. During bootstrap this may be
* nonzero to specify a preselected OID.
* parentIndexRelid: if creating an index partition, the OID of the
* parent index; otherwise InvalidOid.
* parentConstraintId: if creating a constraint on a partition, the OID
* of the constraint in the parent; otherwise InvalidOid.
* relFileNumber: normally, pass InvalidRelFileNumber to get new storage.
* May be nonzero to attach an existing valid build.
* indexInfo: same info executor uses to insert into the index
* indexColNames: column names to use for index (List of char *)
* accessMethodId: OID of index AM to use
* tableSpaceId: OID of tablespace to use
* collationIds: array of collation OIDs, one per index column
* opclassIds: array of index opclass OIDs, one per index column
* coloptions: array of per-index-column indoption settings
* reloptions: AM-specific options
* flags: bitmask that can include any combination of these bits:
* INDEX_CREATE_IS_PRIMARY
* the index is a primary key
* INDEX_CREATE_ADD_CONSTRAINT:
* invoke index_constraint_create also
* INDEX_CREATE_SKIP_BUILD:
* skip the index_build() step for the moment; caller must do it
* later (typically via reindex_index())
* INDEX_CREATE_CONCURRENT:
* do not lock the table against writers. The index will be
* marked "invalid" and the caller must take additional steps
* to fix it up.
* INDEX_CREATE_IF_NOT_EXISTS:
* do not throw an error if a relation with the same name
* already exists.
* INDEX_CREATE_PARTITIONED:
* create a partitioned index (table must be partitioned)
* INDEX_CREATE_SUPPRESS_PROGRESS:
* don't report progress during the index build.
*
* constr_flags: flags passed to index_constraint_create
* (only if INDEX_CREATE_ADD_CONSTRAINT is set)
* allow_system_table_mods: allow table to be a system catalog
* is_internal: if true, post creation hook for new index
* constraintId: if not NULL, receives OID of created constraint
*
* Returns the OID of the created index.
*/
Oid
index_create(Relation heapRelation,
const char *indexRelationName,
Oid indexRelationId,
Oid parentIndexRelid,
Oid parentConstraintId,
RelFileNumber relFileNumber,
IndexInfo *indexInfo,
const List *indexColNames,
Oid accessMethodId,
Oid tableSpaceId,
const Oid *collationIds,
const Oid *opclassIds,
const Datum *opclassOptions,
const int16 *coloptions,
const NullableDatum *stattargets,
Datum reloptions,
uint16 flags,
uint16 constr_flags,
bool allow_system_table_mods,
bool is_internal,
Oid *constraintId)
{
Oid heapRelationId = RelationGetRelid(heapRelation);
Relation pg_class;
Relation indexRelation;
TupleDesc indexTupDesc;
bool shared_relation;
bool mapped_relation;
bool is_exclusion;
Oid namespaceId;
int i;
char relpersistence;
bool isprimary = (flags & INDEX_CREATE_IS_PRIMARY) != 0;
bool invalid = (flags & INDEX_CREATE_INVALID) != 0;
bool concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0;
bool partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0;
bool progress = (flags & INDEX_CREATE_SUPPRESS_PROGRESS) == 0;
char relkind;
TransactionId relfrozenxid;
MultiXactId relminmxid;
bool create_storage = !RelFileNumberIsValid(relFileNumber);
/* constraint flags can only be set when a constraint is requested */
Assert((constr_flags == 0) ||
((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
/* partitioned indexes must never be "built" by themselves */
Assert(!partitioned || (flags & INDEX_CREATE_SKIP_BUILD));
relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX;
is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
pg_class = table_open(RelationRelationId, RowExclusiveLock);
/*
* The index will be in the same namespace as its parent table, and is
* shared across databases if and only if the parent is. Likewise, it
* will use the relfilenumber map if and only if the parent does; and it
* inherits the parent's relpersistence.
*/
namespaceId = RelationGetNamespace(heapRelation);
shared_relation = heapRelation->rd_rel->relisshared;
mapped_relation = RelationIsMapped(heapRelation);
relpersistence = heapRelation->rd_rel->relpersistence;
/*
* check parameters
*/
if (indexInfo->ii_NumIndexAttrs < 1)
elog(ERROR, "must index at least one column");
if (!allow_system_table_mods &&
IsSystemRelation(heapRelation) &&
IsNormalProcessingMode())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("user-defined indexes on system catalog tables are not supported")));
/*
* Btree text_pattern_ops uses texteq as the equality operator, which is
* fine as long as the collation is deterministic; texteq then reduces to
* bitwise equality and so it is semantically compatible with the other
* operators and functions in that opclass. But with a nondeterministic
* collation, texteq could yield results that are incompatible with the
* actual behavior of the index (which is determined by the opclass's
* comparison function). We prevent such problems by refusing creation of
* an index with that opclass and a nondeterministic collation.
*
* The same applies to varchar_pattern_ops and bpchar_pattern_ops. If we
* find more cases, we might decide to create a real mechanism for marking
* opclasses as incompatible with nondeterminism; but for now, this small
* hack suffices.
*
* Another solution is to use a special operator, not texteq, as the
* equality opclass member; but that is undesirable because it would
* prevent index usage in many queries that work fine today.
*/
for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
{
Oid collation = collationIds[i];
Oid opclass = opclassIds[i];
if (collation)
{
if ((opclass == TEXT_BTREE_PATTERN_OPS_OID ||
opclass == VARCHAR_BTREE_PATTERN_OPS_OID ||
opclass == BPCHAR_BTREE_PATTERN_OPS_OID) &&
!get_collation_isdeterministic(collation))
{
HeapTuple classtup;
classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
if (!HeapTupleIsValid(classtup))
elog(ERROR, "cache lookup failed for operator class %u", opclass);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("nondeterministic collations are not supported for operator class \"%s\"",
NameStr(((Form_pg_opclass) GETSTRUCT(classtup))->opcname))));
ReleaseSysCache(classtup);
}
}
}
/*
* Concurrent index build on a system catalog is unsafe because we tend to
* release locks before committing in catalogs.
*/
if (concurrent &&
IsCatalogRelation(heapRelation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("concurrent index creation on system catalog tables is not supported")));
/*
* This case is currently not supported. There's no way to ask for it in
* the grammar with CREATE INDEX, but it can happen with REINDEX.
*/
if (concurrent && is_exclusion)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("concurrent index creation for exclusion constraints is not supported")));
/*
* We cannot allow indexing a shared relation after initdb (because
* there's no way to make the entry in other databases' pg_class).
*/
if (shared_relation && !IsBootstrapProcessingMode())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("shared indexes cannot be created after initdb")));
/*
* Shared relations must be in pg_global, too (last-ditch check)
*/
if (shared_relation && tableSpaceId != GLOBALTABLESPACE_OID)
elog(ERROR, "shared relations must be placed in pg_global tablespace");
/*
* Check for duplicate name (both as to the index, and as to the
* associated constraint if any). Such cases would fail on the relevant
* catalogs' unique indexes anyway, but we prefer to give a friendlier
* error message.
*/
if (get_relname_relid(indexRelationName, namespaceId))
{
if ((flags & INDEX_CREATE_IF_NOT_EXISTS) != 0)
{
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists, skipping",
indexRelationName)));
table_close(pg_class, RowExclusiveLock);
return InvalidOid;
}
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists",
indexRelationName)));
}
if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0 &&
ConstraintNameIsUsed(CONSTRAINT_RELATION, heapRelationId,
indexRelationName))
{
/*
* INDEX_CREATE_IF_NOT_EXISTS does not apply here, since the
* conflicting constraint is not an index.
*/
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("constraint \"%s\" for relation \"%s\" already exists",
indexRelationName, RelationGetRelationName(heapRelation))));
}
/*
* construct tuple descriptor for index tuples
*/
indexTupDesc = ConstructTupleDescriptor(heapRelation,
indexInfo,
indexColNames,
accessMethodId,
collationIds,
opclassIds);
/*
* Allocate an OID for the index, unless we were told what to use.
*
* The OID will be the relfilenumber as well, so make sure it doesn't
* collide with either pg_class OIDs or existing physical files.
*/
if (!OidIsValid(indexRelationId))
{
/* Use binary-upgrade override for pg_class.oid and relfilenumber */
if (IsBinaryUpgrade)
{
if (!OidIsValid(binary_upgrade_next_index_pg_class_oid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("pg_class index OID value not set when in binary upgrade mode")));
indexRelationId = binary_upgrade_next_index_pg_class_oid;
binary_upgrade_next_index_pg_class_oid = InvalidOid;
/* Override the index relfilenumber */
if ((relkind == RELKIND_INDEX) &&
(!RelFileNumberIsValid(binary_upgrade_next_index_pg_class_relfilenumber)))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("index relfilenumber value not set when in binary upgrade mode")));
relFileNumber = binary_upgrade_next_index_pg_class_relfilenumber;
binary_upgrade_next_index_pg_class_relfilenumber = InvalidRelFileNumber;
/*
* Note that we want create_storage = true for binary upgrade. The
* storage we create here will be replaced later, but we need to
* have something on disk in the meanwhile.
*/
Assert(create_storage);
}
else
{
indexRelationId =
GetNewRelFileNumber(tableSpaceId, pg_class, relpersistence);
}
}
/*
* create the index relation's relcache entry and, if necessary, the
* physical disk file. (If we fail further down, it's the smgr's
* responsibility to remove the disk file again, if any.)
*/
indexRelation = heap_create(indexRelationName,
namespaceId,
tableSpaceId,
indexRelationId,
relFileNumber,
accessMethodId,
indexTupDesc,
relkind,
relpersistence,
shared_relation,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
&relminmxid,
create_storage);
Assert(relfrozenxid == InvalidTransactionId);
Assert(relminmxid == InvalidMultiXactId);
Assert(indexRelationId == RelationGetRelid(indexRelation));
/*
* Obtain exclusive lock on it. Although no other transactions can see it
* until we commit, this prevents deadlock-risk complaints from lock
* manager in cases such as CLUSTER.
*/
LockRelation(indexRelation, AccessExclusiveLock);
/*
* Fill in fields of the index's pg_class entry that are not set correctly
* by heap_create.
*
* XXX should have a cleaner way to create cataloged indexes
*/
indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
indexRelation->rd_rel->relam = accessMethodId;
indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
/*
* store index's pg_class entry
*/
InsertPgClassTuple(pg_class, indexRelation,
RelationGetRelid(indexRelation),
(Datum) 0,
reloptions);
/* done with pg_class */
table_close(pg_class, RowExclusiveLock);
/*
* now update the object id's of all the attribute tuple forms in the
* index relation's tuple descriptor
*/
InitializeAttributeOids(indexRelation,
indexInfo->ii_NumIndexAttrs,
indexRelationId);
/*
* append ATTRIBUTE tuples for the index
*/
AppendAttributeTuples(indexRelation, opclassOptions, stattargets);
/* ----------------
* update pg_index
* (append INDEX tuple)
*
* Note that this stows away a representation of "predicate".
* (Or, could define a rule to maintain the predicate) --Nels, Feb '92
* ----------------
*/
UpdateIndexRelation(indexRelationId, heapRelationId, parentIndexRelid,
indexInfo,
collationIds, opclassIds, coloptions,
isprimary, is_exclusion,
(constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
!concurrent && !invalid,
!concurrent);
/*
* Register relcache invalidation on the indexes' heap relation, to
* maintain consistency of its index list
*/
CacheInvalidateRelcache(heapRelation);
/* update pg_inherits and the parent's relhassubclass, if needed */
if (OidIsValid(parentIndexRelid))
{
StoreSingleInheritance(indexRelationId, parentIndexRelid, 1);
LockRelationOid(parentIndexRelid, ShareUpdateExclusiveLock);
SetRelationHasSubclass(parentIndexRelid, true);
}
/*
* Register constraint and dependencies for the index.
*
* If the index is from a CONSTRAINT clause, construct a pg_constraint
* entry. The index will be linked to the constraint, which in turn is
* linked to the table. If it's not a CONSTRAINT, we need to make a
* dependency directly on the table.
*
* We don't need a dependency on the namespace, because there'll be an
* indirect dependency via our parent table.
*
* During bootstrap we can't register any dependencies, and we don't try
* to make a constraint either.
*/
if (!IsBootstrapProcessingMode())
{
ObjectAddress myself,
referenced;
ObjectAddresses *addrs;
ObjectAddressSet(myself, RelationRelationId, indexRelationId);
if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
{
char constraintType;
ObjectAddress localaddr;
if (isprimary)
constraintType = CONSTRAINT_PRIMARY;
else if (indexInfo->ii_Unique)
constraintType = CONSTRAINT_UNIQUE;
else if (is_exclusion)
constraintType = CONSTRAINT_EXCLUSION;
else
{
elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE");
constraintType = 0; /* keep compiler quiet */
}
localaddr = index_constraint_create(heapRelation,
indexRelationId,
parentConstraintId,
indexInfo,
indexRelationName,
constraintType,
constr_flags,
allow_system_table_mods,
is_internal);
if (constraintId)
*constraintId = localaddr.objectId;
}
else
{
bool have_simple_col = false;
addrs = new_object_addresses();
/* Create auto dependencies on simply-referenced columns */
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
if (indexInfo->ii_IndexAttrNumbers[i] != 0)
{
ObjectAddressSubSet(referenced, RelationRelationId,
heapRelationId,
indexInfo->ii_IndexAttrNumbers[i]);
add_exact_object_address(&referenced, addrs);
have_simple_col = true;
}
}
/*
* If there are no simply-referenced columns, give the index an
* auto dependency on the whole table. In most cases, this will
* be redundant, but it might not be if the index expressions and
* predicate contain no Vars or only whole-row Vars.
*/
if (!have_simple_col)
{
ObjectAddressSet(referenced, RelationRelationId,
heapRelationId);
add_exact_object_address(&referenced, addrs);
}
record_object_address_dependencies(&myself, addrs, DEPENDENCY_AUTO);
free_object_addresses(addrs);
}
/*
* If this is an index partition, create partition dependencies on
* both the parent index and the table. (Note: these must be *in
* addition to*, not instead of, all other dependencies. Otherwise
* we'll be short some dependencies after DETACH PARTITION.)
*/
if (OidIsValid(parentIndexRelid))
{
ObjectAddressSet(referenced, RelationRelationId, parentIndexRelid);
recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI);
ObjectAddressSet(referenced, RelationRelationId, heapRelationId);
recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
}
/* placeholder for normal dependencies */
addrs = new_object_addresses();
/* Store dependency on collations */
/* The default collation is pinned, so don't bother recording it */
for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
{
if (OidIsValid(collationIds[i]) && collationIds[i] != DEFAULT_COLLATION_OID)
{
ObjectAddressSet(referenced, CollationRelationId, collationIds[i]);
add_exact_object_address(&referenced, addrs);
}
}
/* Store dependency on operator classes */
for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
{
ObjectAddressSet(referenced, OperatorClassRelationId, opclassIds[i]);
add_exact_object_address(&referenced, addrs);
}
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
/* Store dependencies on anything mentioned in index expressions */
if (indexInfo->ii_Expressions)
{
recordDependencyOnSingleRelExpr(&myself,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
if (indexInfo->ii_Predicate)
{
recordDependencyOnSingleRelExpr(&myself,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
DEPENDENCY_AUTO, false);
}
}
else
{
/* Bootstrap mode - assert we weren't asked for constraint support */
Assert((flags & INDEX_CREATE_ADD_CONSTRAINT) == 0);
}
/* Post creation hook for new index */
InvokeObjectPostCreateHookArg(RelationRelationId,
indexRelationId, 0, is_internal);
/*
* Advance the command counter so that we can see the newly-entered
* catalog tuples for the index.
*/
CommandCounterIncrement();
/*
* In bootstrap mode, we have to fill in the index strategy structure with
* information from the catalogs. If we aren't bootstrapping, then the
* relcache entry has already been rebuilt thanks to sinval update during
* CommandCounterIncrement.
*/
if (IsBootstrapProcessingMode())
RelationInitIndexAccessInfo(indexRelation);
else
Assert(indexRelation->rd_indexcxt != NULL);
indexRelation->rd_index->indnkeyatts = indexInfo->ii_NumIndexKeyAttrs;
/* Validate opclass-specific options */
if (opclassOptions)
for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
(void) index_opclass_options(indexRelation, i + 1,
opclassOptions[i],
true);
/*
* If this is bootstrap (initdb) time, then we don't actually fill in the
* index yet. We'll be creating more indexes and classes later, so we
* delay filling them in until just before we're done with bootstrapping.
* Similarly, if the caller specified to skip the build then filling the
* index is delayed till later (ALTER TABLE can save work in some cases
* with this). Otherwise, we call the AM routine that constructs the
* index.
*/
if (IsBootstrapProcessingMode())
{
index_register(heapRelationId, indexRelationId, indexInfo);
}
else if ((flags & INDEX_CREATE_SKIP_BUILD) != 0)
{
/*
* Caller is responsible for filling the index later on. However,
* we'd better make sure that the heap relation is correctly marked as
* having an index.
*/
index_update_stats(heapRelation,
true,
-1.0);
/* Make the above update visible */
CommandCounterIncrement();
}
else
{
index_build(heapRelation, indexRelation, indexInfo, false, true,
progress);
}
/*
* Close the index; but we keep the lock that we acquired above until end
* of transaction. Closing the heap is caller's responsibility.
*/
index_close(indexRelation, NoLock);
return indexRelationId;
}
/*
* index_create_copy
*
* Create an index based on the definition of the one provided by caller. The
* index is inserted into catalogs. 'flags' are passed directly to
* index_create.
*
* "tablespaceOid" is the tablespace to use for this index.
*/
Oid
index_create_copy(Relation heapRelation, uint16 flags,
Oid oldIndexId, Oid tablespaceOid, const char *newName)
{
Relation indexRelation;
IndexInfo *oldInfo,
*newInfo;
Oid newIndexId = InvalidOid;
bool concurrently = (flags & INDEX_CREATE_CONCURRENT) != 0;
HeapTuple indexTuple,
classTuple;
Datum indclassDatum,
colOptionDatum,
reloptionsDatum;
Datum *opclassOptions;
oidvector *indclass;
int2vector *indcoloptions;
NullableDatum *stattargets;
bool isnull;
List *indexColNames = NIL;
List *indexExprs = NIL;
List *indexPreds = NIL;
indexRelation = index_open(oldIndexId, RowExclusiveLock);
/* The new index needs some information from the old index */
oldInfo = BuildIndexInfo(indexRelation);
/*
* Concurrent build of an index with exclusion constraints is not
* supported.
*/
if (oldInfo->ii_ExclusionOps != NULL && concurrently)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("concurrent index creation for exclusion constraints is not supported")));
/* Get the array of class and column options IDs from index info */
indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(oldIndexId));
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", oldIndexId);
indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
Anum_pg_index_indclass);
indclass = (oidvector *) DatumGetPointer(indclassDatum);
colOptionDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
Anum_pg_index_indoption);
indcoloptions = (int2vector *) DatumGetPointer(colOptionDatum);
/* Fetch reloptions of index if any */
classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(oldIndexId));
if (!HeapTupleIsValid(classTuple))
elog(ERROR, "cache lookup failed for relation %u", oldIndexId);
reloptionsDatum = SysCacheGetAttr(RELOID, classTuple,
Anum_pg_class_reloptions, &isnull);
/*
* Fetch the list of expressions and predicates directly from the
* catalogs. This cannot rely on the information from IndexInfo of the
* old index as these have been flattened for the planner.
*/
if (oldInfo->ii_Expressions != NIL)
{
Datum exprDatum;
char *exprString;
exprDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
Anum_pg_index_indexprs);
exprString = TextDatumGetCString(exprDatum);
indexExprs = (List *) stringToNode(exprString);
pfree(exprString);
}
if (oldInfo->ii_Predicate != NIL)
{
Datum predDatum;
char *predString;
predDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
Anum_pg_index_indpred);
predString = TextDatumGetCString(predDatum);
indexPreds = (List *) stringToNode(predString);
/* Also convert to implicit-AND format */
indexPreds = make_ands_implicit((Expr *) indexPreds);
pfree(predString);
}
/*
* Build the index information for the new index.
*/
newInfo = makeIndexInfo(oldInfo->ii_NumIndexAttrs,
oldInfo->ii_NumIndexKeyAttrs,
oldInfo->ii_Am,
indexExprs,
indexPreds,
oldInfo->ii_Unique,
oldInfo->ii_NullsNotDistinct,
!concurrently, /* isready */
concurrently, /* concurrent */
indexRelation->rd_indam->amsummarizing,
oldInfo->ii_WithoutOverlaps);
newInfo->ii_ExpressionsExpand =
ExpandVirtualGeneratedColumns(newInfo->ii_ExpressionsExpand, heapRelation, InvalidOid);
newInfo->ii_PredicateExpand =
ExpandVirtualGeneratedColumns(newInfo->ii_PredicateExpand, heapRelation, InvalidOid);
/* fetch exclusion constraint info if any */
if (indexRelation->rd_index->indisexclusion)
{
/*
* XXX Beware: we're making newInfo point to oldInfo-owned memory. It
* would be more orthodox to palloc+memcpy, but we don't need that
* here at present.
*/
newInfo->ii_ExclusionOps = oldInfo->ii_ExclusionOps;
newInfo->ii_ExclusionProcs = oldInfo->ii_ExclusionProcs;
newInfo->ii_ExclusionStrats = oldInfo->ii_ExclusionStrats;
}
/*
* Extract the list of column names and the column numbers for the new
* index information. All this information will be used for the index
* creation.
*/
for (int i = 0; i < oldInfo->ii_NumIndexAttrs; i++)
{
TupleDesc indexTupDesc = RelationGetDescr(indexRelation);
Form_pg_attribute att = TupleDescAttr(indexTupDesc, i);
indexColNames = lappend(indexColNames, NameStr(att->attname));
newInfo->ii_IndexAttrNumbers[i] = oldInfo->ii_IndexAttrNumbers[i];
}
/* Extract opclass options for each attribute */
opclassOptions = palloc0_array(Datum, newInfo->ii_NumIndexAttrs);
for (int i = 0; i < newInfo->ii_NumIndexAttrs; i++)
opclassOptions[i] = get_attoptions(oldIndexId, i + 1);
/* Extract statistic targets for each attribute */
stattargets = palloc0_array(NullableDatum, newInfo->ii_NumIndexAttrs);
for (int i = 0; i < newInfo->ii_NumIndexAttrs; i++)
{
HeapTuple tp;
Datum dat;
tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(oldIndexId), Int16GetDatum(i + 1));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
i + 1, oldIndexId);
dat = SysCacheGetAttr(ATTNUM, tp, Anum_pg_attribute_attstattarget, &isnull);
ReleaseSysCache(tp);
stattargets[i].value = dat;
stattargets[i].isnull = isnull;
}
/*
* Now create the new index.
*
* For a partition index, we adjust the partition dependency later, to
* ensure a consistent state at all times. That is why parentIndexRelid
* is not set here.
*/
newIndexId = index_create(heapRelation,
newName,
InvalidOid, /* indexRelationId */
InvalidOid, /* parentIndexRelid */
InvalidOid, /* parentConstraintId */
InvalidRelFileNumber, /* relFileNumber */
newInfo,
indexColNames,
indexRelation->rd_rel->relam,
tablespaceOid,
indexRelation->rd_indcollation,
indclass->values,
opclassOptions,
indcoloptions->values,
stattargets,
reloptionsDatum,
flags,
0,
true, /* allow table to be a system catalog? */
false, /* is_internal? */
NULL);
/* Close the relations used and clean up */
index_close(indexRelation, NoLock);
ReleaseSysCache(indexTuple);
ReleaseSysCache(classTuple);
return newIndexId;
}
/*
* index_concurrently_build
*
* Build index for a concurrent operation. Low-level locks are taken when
* this operation is performed to prevent only schema changes, but they need
* to be kept until the end of the transaction performing this operation.
* 'indexOid' refers to an index relation OID already created as part of
* previous processing, and 'heapOid' refers to its parent heap relation.
*/
void
index_concurrently_build(Oid heapRelationId,
Oid indexRelationId)
{
Relation heapRel;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
Relation indexRelation;
IndexInfo *indexInfo;
/* This had better make sure that a snapshot is active */
Assert(ActiveSnapshotSet());
/* Open and lock the parent heap relation */
heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
/*
* Switch to the table owner's userid, so that any index functions are run
* as that user. Also lock down security-restricted operations and
* arrange to make GUC variable changes local to this command.
*/
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(heapRel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
indexRelation = index_open(indexRelationId, RowExclusiveLock);
/*
* We have to re-build the IndexInfo struct, since it was lost in the
* commit of the transaction where this concurrent index was created at
* the catalog level.
*/
indexInfo = BuildIndexInfo(indexRelation);
Assert(!indexInfo->ii_ReadyForInserts);
indexInfo->ii_Concurrent = true;
indexInfo->ii_BrokenHotChain = false;
/* Now build the index */
index_build(heapRel, indexRelation, indexInfo, false, true, true);
/* Roll back any GUC changes executed by index functions */
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
/* Close both the relations, but keep the locks */
table_close(heapRel, NoLock);
index_close(indexRelation, NoLock);
/*
* Update the pg_index row to mark the index as ready for inserts. Once we
* commit this transaction, any new transactions that open the table must
* insert new entries into the index for insertions and non-HOT updates.
*/
index_set_state_flags(indexRelationId, INDEX_CREATE_SET_READY);
}
/*
* index_concurrently_swap
*
* Swap name, dependencies, and constraints of the old index over to the new
* index, while marking the old index as invalid and the new as valid.
*/
void
index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
{
Relation pg_class,
pg_index,
pg_constraint,
pg_trigger;
Relation oldClassRel,
newClassRel;
HeapTuple oldClassTuple,
newClassTuple;
Form_pg_class oldClassForm,
newClassForm;
HeapTuple oldIndexTuple,
newIndexTuple;
Form_pg_index oldIndexForm,
newIndexForm;
bool isPartition;
Oid indexConstraintOid;
List *constraintOids = NIL;
ListCell *lc;
/*
* Take a necessary lock on the old and new index before swapping them.
*/
oldClassRel = relation_open(oldIndexId, ShareUpdateExclusiveLock);
newClassRel = relation_open(newIndexId, ShareUpdateExclusiveLock);
/* Now swap names and dependencies of those indexes */
pg_class = table_open(RelationRelationId, RowExclusiveLock);
oldClassTuple = SearchSysCacheCopy1(RELOID,
ObjectIdGetDatum(oldIndexId));
if (!HeapTupleIsValid(oldClassTuple))
elog(ERROR, "could not find tuple for relation %u", oldIndexId);
newClassTuple = SearchSysCacheCopy1(RELOID,
ObjectIdGetDatum(newIndexId));
if (!HeapTupleIsValid(newClassTuple))
elog(ERROR, "could not find tuple for relation %u", newIndexId);
oldClassForm = (Form_pg_class) GETSTRUCT(oldClassTuple);
newClassForm = (Form_pg_class) GETSTRUCT(newClassTuple);
/* Swap the names */
namestrcpy(&newClassForm->relname, NameStr(oldClassForm->relname));
namestrcpy(&oldClassForm->relname, oldName);
/* Swap the partition flags to track inheritance properly */
isPartition = newClassForm->relispartition;
newClassForm->relispartition = oldClassForm->relispartition;
oldClassForm->relispartition = isPartition;
CatalogTupleUpdate(pg_class, &oldClassTuple->t_self, oldClassTuple);
CatalogTupleUpdate(pg_class, &newClassTuple->t_self, newClassTuple);
heap_freetuple(oldClassTuple);
heap_freetuple(newClassTuple);
/* Now swap index info */
pg_index = table_open(IndexRelationId, RowExclusiveLock);
oldIndexTuple = SearchSysCacheCopy1(INDEXRELID,
ObjectIdGetDatum(oldIndexId));
if (!HeapTupleIsValid(oldIndexTuple))
elog(ERROR, "could not find tuple for relation %u", oldIndexId);
newIndexTuple = SearchSysCacheCopy1(INDEXRELID,
ObjectIdGetDatum(newIndexId));
if (!HeapTupleIsValid(newIndexTuple))
elog(ERROR, "could not find tuple for relation %u", newIndexId);
oldIndexForm = (Form_pg_index) GETSTRUCT(oldIndexTuple);
newIndexForm = (Form_pg_index) GETSTRUCT(newIndexTuple);
/*
* Copy constraint flags from the old index. This is safe because the old
* index guaranteed uniqueness.
*/
newIndexForm->indisprimary = oldIndexForm->indisprimary;
oldIndexForm->indisprimary = false;
newIndexForm->indisexclusion = oldIndexForm->indisexclusion;
oldIndexForm->indisexclusion = false;
newIndexForm->indimmediate = oldIndexForm->indimmediate;
oldIndexForm->indimmediate = true;
/* Preserve indisreplident in the new index */
newIndexForm->indisreplident = oldIndexForm->indisreplident;
/* Preserve indisclustered in the new index */
newIndexForm->indisclustered = oldIndexForm->indisclustered;
/*
* Mark the new index as valid, and the old index as invalid similarly to
* what index_set_state_flags() does.
*/
newIndexForm->indisvalid = true;
oldIndexForm->indisvalid = false;
oldIndexForm->indisclustered = false;
oldIndexForm->indisreplident = false;
CatalogTupleUpdate(pg_index, &oldIndexTuple->t_self, oldIndexTuple);
CatalogTupleUpdate(pg_index, &newIndexTuple->t_self, newIndexTuple);
heap_freetuple(oldIndexTuple);
heap_freetuple(newIndexTuple);
/*
* Move constraints and triggers over to the new index
*/
constraintOids = get_index_ref_constraints(oldIndexId);
indexConstraintOid = get_index_constraint(oldIndexId);
if (OidIsValid(indexConstraintOid))
constraintOids = lappend_oid(constraintOids, indexConstraintOid);
pg_constraint = table_open(ConstraintRelationId, RowExclusiveLock);
pg_trigger = table_open(TriggerRelationId, RowExclusiveLock);
foreach(lc, constraintOids)
{
HeapTuple constraintTuple,
triggerTuple;
Form_pg_constraint conForm;
ScanKeyData key[1];
SysScanDesc scan;
Oid constraintOid = lfirst_oid(lc);
/* Move the constraint from the old to the new index */
constraintTuple = SearchSysCacheCopy1(CONSTROID,
ObjectIdGetDatum(constraintOid));
if (!HeapTupleIsValid(constraintTuple))
elog(ERROR, "could not find tuple for constraint %u", constraintOid);
conForm = ((Form_pg_constraint) GETSTRUCT(constraintTuple));
if (conForm->conindid == oldIndexId)
{
conForm->conindid = newIndexId;
CatalogTupleUpdate(pg_constraint, &constraintTuple->t_self, constraintTuple);
}
heap_freetuple(constraintTuple);
/* Search for trigger records */
ScanKeyInit(&key[0],
Anum_pg_trigger_tgconstraint,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(constraintOid));
scan = systable_beginscan(pg_trigger, TriggerConstraintIndexId, true,
NULL, 1, key);
while (HeapTupleIsValid((triggerTuple = systable_getnext(scan))))
{
Form_pg_trigger tgForm = (Form_pg_trigger) GETSTRUCT(triggerTuple);
if (tgForm->tgconstrindid != oldIndexId)
continue;
/* Make a modifiable copy */
triggerTuple = heap_copytuple(triggerTuple);
tgForm = (Form_pg_trigger) GETSTRUCT(triggerTuple);
tgForm->tgconstrindid = newIndexId;
CatalogTupleUpdate(pg_trigger, &triggerTuple->t_self, triggerTuple);
heap_freetuple(triggerTuple);
}
systable_endscan(scan);
}
/*
* Move comment if any
*/
{
Relation description;
ScanKeyData skey[3];
SysScanDesc sd;
HeapTuple tuple;
Datum values[Natts_pg_description] = {0};
bool nulls[Natts_pg_description] = {0};
bool replaces[Natts_pg_description] = {0};
values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(newIndexId);
replaces[Anum_pg_description_objoid - 1] = true;
ScanKeyInit(&skey[0],
Anum_pg_description_objoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(oldIndexId));
ScanKeyInit(&skey[1],
Anum_pg_description_classoid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationRelationId));
ScanKeyInit(&skey[2],
Anum_pg_description_objsubid,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum(0));
description = table_open(DescriptionRelationId, RowExclusiveLock);
sd = systable_beginscan(description, DescriptionObjIndexId, true,
NULL, 3, skey);
while ((tuple = systable_getnext(sd)) != NULL)
{
tuple = heap_modify_tuple(tuple, RelationGetDescr(description),
values, nulls, replaces);
CatalogTupleUpdate(description, &tuple->t_self, tuple);
break; /* Assume there can be only one match */
}
systable_endscan(sd);
table_close(description, NoLock);
}
/*
* Swap inheritance relationship with parent index
*/
if (get_rel_relispartition(oldIndexId))
{
List *ancestors = get_partition_ancestors(oldIndexId);
Oid parentIndexRelid = linitial_oid(ancestors);
DeleteInheritsTuple(oldIndexId, parentIndexRelid, false, NULL);
StoreSingleInheritance(newIndexId, parentIndexRelid, 1);
list_free(ancestors);
}
/*
* Swap all dependencies of and on the old index to the new one, and
* vice-versa. Note that a call to CommandCounterIncrement() would cause
* duplicate entries in pg_depend, so this should not be done.
*/
changeDependenciesOf(RelationRelationId, newIndexId, oldIndexId);
changeDependenciesOn(RelationRelationId, newIndexId, oldIndexId);
changeDependenciesOf(RelationRelationId, oldIndexId, newIndexId);
changeDependenciesOn(RelationRelationId, oldIndexId, newIndexId);
/* copy over statistics from old to new index */
pgstat_copy_relation_stats(newClassRel, oldClassRel);
/* Copy data of pg_statistic from the old index to the new one */
CopyStatistics(oldIndexId, newIndexId);
/* Close relations */
table_close(pg_class, RowExclusiveLock);
table_close(pg_index, RowExclusiveLock);
table_close(pg_constraint, RowExclusiveLock);
table_close(pg_trigger, RowExclusiveLock);
/* The lock taken previously is not released until the end of transaction */
relation_close(oldClassRel, NoLock);
relation_close(newClassRel, NoLock);
}
/*
* index_concurrently_set_dead
*
* Perform the last invalidation stage of DROP INDEX CONCURRENTLY or REINDEX
* CONCURRENTLY before actually dropping the index. After calling this
* function, the index is seen by all the backends as dead. Low-level locks
* taken here are kept until the end of the transaction calling this function.
*/
void
index_concurrently_set_dead(Oid heapId, Oid indexId)
{
Relation userHeapRelation;
Relation userIndexRelation;
/*
* No more predicate locks will be acquired on this index, and we're about
* to stop doing inserts into the index which could show conflicts with
* existing predicate locks, so now is the time to move them to the heap
* relation.
*/
userHeapRelation = table_open(heapId, ShareUpdateExclusiveLock);
userIndexRelation = index_open(indexId, ShareUpdateExclusiveLock);
TransferPredicateLocksToHeapRelation(userIndexRelation);
/*
* Now we are sure that nobody uses the index for queries; they just might
* have it open for updating it. So now we can unset indisready and
* indislive, then wait till nobody could be using it at all anymore.
*/
index_set_state_flags(indexId, INDEX_DROP_SET_DEAD);
/*
* Invalidate the relcache for the table, so that after this commit all
* sessions will refresh the table's index list. Forgetting just the
* index's relcache entry is not enough.
*/
CacheInvalidateRelcache(userHeapRelation);
/*
* Close the relations again, though still holding session lock.
*/
table_close(userHeapRelation, NoLock);
index_close(userIndexRelation, NoLock);
}
/*
* index_constraint_create
*
* Set up a constraint associated with an index. Return the new constraint's
* address.
*
* heapRelation: table owning the index (must be suitably locked by caller)
* indexRelationId: OID of the index
* parentConstraintId: if constraint is on a partition, the OID of the
* constraint in the parent.
* indexInfo: same info executor uses to insert into the index
* constraintName: what it say (generally, should match name of index)
* constraintType: one of CONSTRAINT_PRIMARY, CONSTRAINT_UNIQUE, or
* CONSTRAINT_EXCLUSION
* flags: bitmask that can include any combination of these bits:
* INDEX_CONSTR_CREATE_MARK_AS_PRIMARY: index is a PRIMARY KEY
* INDEX_CONSTR_CREATE_DEFERRABLE: constraint is DEFERRABLE
* INDEX_CONSTR_CREATE_INIT_DEFERRED: constraint is INITIALLY DEFERRED
* INDEX_CONSTR_CREATE_UPDATE_INDEX: update the pg_index row
* INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS: remove existing dependencies
* of index on table's columns
* INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS: constraint uses WITHOUT OVERLAPS
* allow_system_table_mods: allow table to be a system catalog
* is_internal: index is constructed due to internal process
*/
ObjectAddress
index_constraint_create(Relation heapRelation,
Oid indexRelationId,
Oid parentConstraintId,
const IndexInfo *indexInfo,
const char *constraintName,
char constraintType,
uint16 constr_flags,
bool allow_system_table_mods,
bool is_internal)
{
Oid namespaceId = RelationGetNamespace(heapRelation);
ObjectAddress myself,
idxaddr;
Oid conOid;
bool deferrable;
bool initdeferred;
bool mark_as_primary;
bool islocal;
bool noinherit;
bool is_without_overlaps;
int16 inhcount;
deferrable = (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) != 0;
initdeferred = (constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED) != 0;
mark_as_primary = (constr_flags & INDEX_CONSTR_CREATE_MARK_AS_PRIMARY) != 0;
is_without_overlaps = (constr_flags & INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS) != 0;
/* constraint creation support doesn't work while bootstrapping */
Assert(!IsBootstrapProcessingMode());
/* enforce system-table restriction */
if (!allow_system_table_mods &&
IsSystemRelation(heapRelation) &&
IsNormalProcessingMode())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("user-defined indexes on system catalog tables are not supported")));
/* primary/unique constraints shouldn't have any expressions */
if (indexInfo->ii_Expressions &&
constraintType != CONSTRAINT_EXCLUSION)
elog(ERROR, "constraints cannot have index expressions");
/*
* If we're manufacturing a constraint for a pre-existing index, we need
* to get rid of the existing auto dependencies for the index (the ones
* that index_create() would have made instead of calling this function).
*
* Note: this code would not necessarily do the right thing if the index
* has any expressions or predicate, but we'd never be turning such an
* index into a UNIQUE or PRIMARY KEY constraint.
*/
if (constr_flags & INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS)
deleteDependencyRecordsForClass(RelationRelationId, indexRelationId,
RelationRelationId, DEPENDENCY_AUTO);
if (OidIsValid(parentConstraintId))
{
islocal = false;
inhcount = 1;
noinherit = false;
}
else
{
islocal = true;
inhcount = 0;
noinherit = true;
}
/*
* Construct a pg_constraint entry.
*/
conOid = CreateConstraintEntry(constraintName,
namespaceId,
constraintType,
deferrable,
initdeferred,
true, /* Is Enforced */
true,
parentConstraintId,
RelationGetRelid(heapRelation),
indexInfo->ii_IndexAttrNumbers,
indexInfo->ii_NumIndexKeyAttrs,
indexInfo->ii_NumIndexAttrs,
InvalidOid, /* no domain */
indexRelationId, /* index OID */
InvalidOid, /* no foreign key */
NULL,
NULL,
NULL,
NULL,
0,
' ',
' ',
NULL,
0,
' ',
indexInfo->ii_ExclusionOps,
NULL, /* no check constraint */
NULL,
islocal,
inhcount,
noinherit,
is_without_overlaps,
is_internal);
/*
* Register the index as internally dependent on the constraint.
*
* Note that the constraint has a dependency on the table, so we don't
* need (or want) any direct dependency from the index to the table.
*/
ObjectAddressSet(myself, ConstraintRelationId, conOid);
ObjectAddressSet(idxaddr, RelationRelationId, indexRelationId);
recordDependencyOn(&idxaddr, &myself, DEPENDENCY_INTERNAL);
/*
* Also, if this is a constraint on a partition, give it partition-type
* dependencies on the parent constraint as well as the table.
*/
if (OidIsValid(parentConstraintId))
{
ObjectAddress referenced;
ObjectAddressSet(referenced, ConstraintRelationId, parentConstraintId);
recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI);
ObjectAddressSet(referenced, RelationRelationId,
RelationGetRelid(heapRelation));
recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
}
/*
* If the constraint is deferrable, create the deferred uniqueness
* checking trigger. (The trigger will be given an internal dependency on
* the constraint by CreateTrigger.)
*/
if (deferrable)
{
CreateTrigStmt *trigger = makeNode(CreateTrigStmt);
trigger->replace = false;
trigger->isconstraint = true;
trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
"PK_ConstraintTrigger" :
"Unique_ConstraintTrigger";
trigger->relation = NULL;
trigger->funcname = SystemFuncName("unique_key_recheck");
trigger->args = NIL;
trigger->row = true;
trigger->timing = TRIGGER_TYPE_AFTER;
trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
trigger->columns = NIL;
trigger->whenClause = NULL;
trigger->transitionRels = NIL;
trigger->deferrable = true;
trigger->initdeferred = initdeferred;
trigger->constrrel = NULL;
(void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation),
InvalidOid, conOid, indexRelationId, InvalidOid,
InvalidOid, NULL, true, false);
}
/*
* If needed, mark the index as primary and/or deferred in pg_index.
*
* Note: When making an existing index into a constraint, caller must have
* a table lock that prevents concurrent table updates; otherwise, there
* is a risk that concurrent readers of the table will miss seeing this
* index at all.
*/
if ((constr_flags & INDEX_CONSTR_CREATE_UPDATE_INDEX) &&
(mark_as_primary || deferrable))
{
Relation pg_index;
HeapTuple indexTuple;
Form_pg_index indexForm;
bool dirty = false;
bool marked_as_primary = false;
pg_index = table_open(IndexRelationId, RowExclusiveLock);
indexTuple = SearchSysCacheCopy1(INDEXRELID,
ObjectIdGetDatum(indexRelationId));
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexRelationId);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
if (mark_as_primary && !indexForm->indisprimary)
{
indexForm->indisprimary = true;
dirty = true;
marked_as_primary = true;
}
if (deferrable && indexForm->indimmediate)
{
indexForm->indimmediate = false;
dirty = true;
}
if (dirty)
{
CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple);
/*
* When we mark an existing index as primary, force a relcache
* flush on its parent table, so that all sessions will become
* aware that the table now has a primary key. This is important
* because it affects some replication behaviors.
*/
if (marked_as_primary)
CacheInvalidateRelcache(heapRelation);
InvokeObjectPostAlterHookArg(IndexRelationId, indexRelationId, 0,
InvalidOid, is_internal);
}
heap_freetuple(indexTuple);
table_close(pg_index, RowExclusiveLock);
}
return myself;
}
/*
* index_drop
*
* NOTE: this routine should now only be called through performDeletion(),
* else associated dependencies won't be cleaned up.
*
* If concurrent is true, do a DROP INDEX CONCURRENTLY. If concurrent is
* false but concurrent_lock_mode is true, then do a normal DROP INDEX but
* take a lock for CONCURRENTLY processing. That is used as part of REINDEX
* CONCURRENTLY.
*/
void
index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
{
Oid heapId;
Relation userHeapRelation;
Relation userIndexRelation;
Relation indexRelation;
HeapTuple tuple;
bool hasexprs;
LockRelId heaprelid,
indexrelid;
LOCKTAG heaplocktag;
LOCKMODE lockmode;
/*
* A temporary relation uses a non-concurrent DROP. Other backends can't
* access a temporary relation, so there's no harm in grabbing a stronger
* lock (see comments in RemoveRelations), and a non-concurrent DROP is
* more efficient.
*/
Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
(!concurrent && !concurrent_lock_mode));
/*
* To drop an index safely, we must grab exclusive lock on its parent
* table. Exclusive lock on the index alone is insufficient because
* another backend might be about to execute a query on the parent table.
* If it relies on a previously cached list of index OIDs, then it could
* attempt to access the just-dropped index. We must therefore take a
* table lock strong enough to prevent all queries on the table from
* proceeding until we commit and send out a shared-cache-inval notice
* that will make them update their index lists.
*
* In the concurrent case we avoid this requirement by disabling index use
* in multiple steps and waiting out any transactions that might be using
* the index, so we don't need exclusive lock on the parent table. Instead
* we take ShareUpdateExclusiveLock, to ensure that two sessions aren't
* doing CREATE/DROP INDEX CONCURRENTLY on the same index. (We will get
* AccessExclusiveLock on the index below, once we're sure nobody else is
* using it.)
*/
heapId = IndexGetRelation(indexId, false);
lockmode = (concurrent || concurrent_lock_mode) ? ShareUpdateExclusiveLock : AccessExclusiveLock;
userHeapRelation = table_open(heapId, lockmode);
userIndexRelation = index_open(indexId, lockmode);
/*
* We might still have open queries using it in our own session, which the
* above locking won't prevent, so test explicitly.
*/
CheckTableNotInUse(userIndexRelation, "DROP INDEX");
/*
* Drop Index Concurrently is more or less the reverse process of Create
* Index Concurrently.
*
* First we unset indisvalid so queries starting afterwards don't use the
* index to answer queries anymore. We have to keep indisready = true so
* transactions that are still scanning the index can continue to see
* valid index contents. For instance, if they are using READ COMMITTED
* mode, and another transaction makes changes and commits, they need to
* see those new tuples in the index.
*
* After all transactions that could possibly have used the index for
* queries end, we can unset indisready and indislive, then wait till
* nobody could be touching it anymore. (Note: we need indislive because
* this state must be distinct from the initial state during CREATE INDEX
* CONCURRENTLY, which has indislive true while indisready and indisvalid
* are false. That's because in that state, transactions must examine the
* index for HOT-safety decisions, while in this state we don't want them
* to open it at all.)
*
* Since all predicate locks on the index are about to be made invalid, we
* must promote them to predicate locks on the heap. In the
* non-concurrent case we can just do that now. In the concurrent case
* it's a bit trickier. The predicate locks must be moved when there are
* no index scans in progress on the index and no more can subsequently
* start, so that no new predicate locks can be made on the index. Also,
* they must be moved before heap inserts stop maintaining the index, else
* the conflict with the predicate lock on the index gap could be missed
* before the lock on the heap relation is in place to detect a conflict
* based on the heap tuple insert.
*/
if (concurrent)
{
/*
* We must commit our transaction in order to make the first pg_index
* state update visible to other sessions. If the DROP machinery has
* already performed any other actions (removal of other objects,
* pg_depend entries, etc), the commit would make those actions
* permanent, which would leave us with inconsistent catalog state if
* we fail partway through the following sequence. Since DROP INDEX
* CONCURRENTLY is restricted to dropping just one index that has no
* dependencies, we should get here before anything's been done ---
* but let's check that to be sure. We can verify that the current
* transaction has not executed any transactional updates by checking
* that no XID has been assigned.
*/
if (GetTopTransactionIdIfAny() != InvalidTransactionId)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DROP INDEX CONCURRENTLY must be first action in transaction")));
/*
* Mark index invalid by updating its pg_index entry
*/
index_set_state_flags(indexId, INDEX_DROP_CLEAR_VALID);
/*
* Invalidate the relcache for the table, so that after this commit
* all sessions will refresh any cached plans that might reference the
* index.
*/
CacheInvalidateRelcache(userHeapRelation);
/* save lockrelid and locktag for below, then close but keep locks */
heaprelid = userHeapRelation->rd_lockInfo.lockRelId;
SET_LOCKTAG_RELATION(heaplocktag, heaprelid.dbId, heaprelid.relId);
indexrelid = userIndexRelation->rd_lockInfo.lockRelId;
table_close(userHeapRelation, NoLock);
index_close(userIndexRelation, NoLock);
/*
* We must commit our current transaction so that the indisvalid
* update becomes visible to other transactions; then start another.
* Note that any previously-built data structures are lost in the
* commit. The only data we keep past here are the relation IDs.
*
* Before committing, get a session-level lock on the table, to ensure
* that neither it nor the index can be dropped before we finish. This
* cannot block, even if someone else is waiting for access, because
* we already have the same lock within our transaction.
*/
LockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
LockRelationIdForSession(&indexrelid, ShareUpdateExclusiveLock);
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
/*
* Now we must wait until no running transaction could be using the
* index for a query. Use AccessExclusiveLock here to check for
* running transactions that hold locks of any kind on the table. Note
* we do not need to worry about xacts that open the table for reading
* after this point; they will see the index as invalid when they open
* the relation.
*
* Note: the reason we use actual lock acquisition here, rather than
* just checking the ProcArray and sleeping, is that deadlock is
* possible if one of the transactions in question is blocked trying
* to acquire an exclusive lock on our table. The lock code will
* detect deadlock and error out properly.
*
* Note: we report progress through WaitForLockers() unconditionally
* here, even though it will only be used when we're called by REINDEX
* CONCURRENTLY and not when called by DROP INDEX CONCURRENTLY.
*/
WaitForLockers(heaplocktag, AccessExclusiveLock, true);
/*
* Updating pg_index might involve TOAST table access, so ensure we
* have a valid snapshot.
*/
PushActiveSnapshot(GetTransactionSnapshot());
/* Finish invalidation of index and mark it as dead */
index_concurrently_set_dead(heapId, indexId);
PopActiveSnapshot();
/*
* Again, commit the transaction to make the pg_index update visible
* to other sessions.
*/
CommitTransactionCommand();
StartTransactionCommand();
/*
* Wait till every transaction that saw the old index state has
* finished. See above about progress reporting.
*/
WaitForLockers(heaplocktag, AccessExclusiveLock, true);
/*
* Re-open relations to allow us to complete our actions.
*
* At this point, nothing should be accessing the index, but lets
* leave nothing to chance and grab AccessExclusiveLock on the index
* before the physical deletion.
*/
userHeapRelation = table_open(heapId, ShareUpdateExclusiveLock);
userIndexRelation = index_open(indexId, AccessExclusiveLock);
}
else
{
/* Not concurrent, so just transfer predicate locks and we're good */
TransferPredicateLocksToHeapRelation(userIndexRelation);
}
/*
* Schedule physical removal of the files (if any)
*/
if (RELKIND_HAS_STORAGE(userIndexRelation->rd_rel->relkind))
RelationDropStorage(userIndexRelation);
/* ensure that stats are dropped if transaction commits */
pgstat_drop_relation(userIndexRelation);
/*
* Close and flush the index's relcache entry, to ensure relcache doesn't
* try to rebuild it while we're deleting catalog entries. We keep the
* lock though.
*/
index_close(userIndexRelation, NoLock);
RelationForgetRelation(indexId);
/*
* Updating pg_index might involve TOAST table access, so ensure we have a
* valid snapshot.
*/
PushActiveSnapshot(GetTransactionSnapshot());
/*
* fix INDEX relation, and check for expressional index
*/
indexRelation = table_open(IndexRelationId, RowExclusiveLock);
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexId));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for index %u", indexId);
hasexprs = !heap_attisnull(tuple, Anum_pg_index_indexprs,
RelationGetDescr(indexRelation));
CatalogTupleDelete(indexRelation, &tuple->t_self);
ReleaseSysCache(tuple);
table_close(indexRelation, RowExclusiveLock);
PopActiveSnapshot();
/*
* if it has any expression columns, we might have stored statistics about
* them.
*/
if (hasexprs)
RemoveStatistics(indexId, 0);
/*
* fix ATTRIBUTE relation
*/
DeleteAttributeTuples(indexId);
/*
* fix RELATION relation
*/
DeleteRelationTuple(indexId);
/*
* fix INHERITS relation
*/
DeleteInheritsTuple(indexId, InvalidOid, false, NULL);
/*
* We are presently too lazy to attempt to compute the new correct value
* of relhasindex (the next VACUUM will fix it if necessary). So there is
* no need to update the pg_class tuple for the owning relation. But we
* must send out a shared-cache-inval notice on the owning relation to
* ensure other backends update their relcache lists of indexes. (In the
* concurrent case, this is redundant but harmless.)
*/
CacheInvalidateRelcache(userHeapRelation);
/*
* Close owning rel, but keep lock
*/
table_close(userHeapRelation, NoLock);
/*
* Release the session locks before we go.
*/
if (concurrent)
{
UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
UnlockRelationIdForSession(&indexrelid, ShareUpdateExclusiveLock);
}
}
/* ----------------------------------------------------------------
* index_build support
* ----------------------------------------------------------------
*/
/* ----------------
* BuildIndexInfo
* Construct an IndexInfo record for an open index
*
* IndexInfo stores the information about the index that's needed by
* FormIndexDatum, which is used for both index_build() and later insertion
* of individual index tuples. Normally we build an IndexInfo for an index
* just once per command, and then use it for (potentially) many tuples.
* ----------------
*/
IndexInfo *
BuildIndexInfo(Relation index)
{
IndexInfo *ii;
Form_pg_index indexStruct = index->rd_index;
int i;
int numAtts;
/* check the number of keys, and copy attr numbers into the IndexInfo */
numAtts = indexStruct->indnatts;
if (numAtts < 1 || numAtts > INDEX_MAX_KEYS)
elog(ERROR, "invalid indnatts %d for index %u",
numAtts, RelationGetRelid(index));
/*
* Create the node, fetching any expressions needed for expressional
* indexes and index predicate if any.
*/
ii = makeIndexInfo(indexStruct->indnatts,
indexStruct->indnkeyatts,
index->rd_rel->relam,
RelationGetIndexExpressions(index),
RelationGetIndexPredicate(index),
indexStruct->indisunique,
indexStruct->indnullsnotdistinct,
indexStruct->indisready,
false,
index->rd_indam->amsummarizing,
indexStruct->indisexclusion && indexStruct->indisunique);
ii->ii_ExpressionsExpand = (List *)RelationGetIndexExpressionsExpand(index);
ii->ii_PredicateExpand = (List *)RelationGetIndexPredicateExpand(index);
/* fill in attribute numbers */
for (i = 0; i < numAtts; i++)
ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
/* fetch exclusion constraint info if any */
if (indexStruct->indisexclusion)
{
RelationGetExclusionInfo(index,
&ii->ii_ExclusionOps,
&ii->ii_ExclusionProcs,
&ii->ii_ExclusionStrats);
}
return ii;
}
/* ----------------
* BuildDummyIndexInfo
* Construct a dummy IndexInfo record for an open index
*
* This differs from the real BuildIndexInfo in that it will never run any
* user-defined code that might exist in index expressions or predicates.
* Instead of the real index expressions, we return null constants that have
* the right types/typmods/collations. Predicates and exclusion clauses are
* just ignored. This is sufficient for the purpose of truncating an index,
* since we will not need to actually evaluate the expressions or predicates;
* the only thing that's likely to be done with the data is construction of
* a tupdesc describing the index's rowtype.
* ----------------
*/
IndexInfo *
BuildDummyIndexInfo(Relation index)
{
IndexInfo *ii;
Form_pg_index indexStruct = index->rd_index;
int i;
int numAtts;
/* check the number of keys, and copy attr numbers into the IndexInfo */
numAtts = indexStruct->indnatts;
if (numAtts < 1 || numAtts > INDEX_MAX_KEYS)
elog(ERROR, "invalid indnatts %d for index %u",
numAtts, RelationGetRelid(index));
/*
* Create the node, using dummy index expressions, and pretending there is
* no predicate.
*/
ii = makeIndexInfo(indexStruct->indnatts,
indexStruct->indnkeyatts,
index->rd_rel->relam,
RelationGetDummyIndexExpressions(index),
NIL,
indexStruct->indisunique,
indexStruct->indnullsnotdistinct,
indexStruct->indisready,
false,
index->rd_indam->amsummarizing,
indexStruct->indisexclusion && indexStruct->indisunique);
/* fill in attribute numbers */
for (i = 0; i < numAtts; i++)
ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
/* We ignore the exclusion constraint if any */
return ii;
}
/*
* CompareIndexInfo
* Return whether the properties of two indexes (in different tables)
* indicate that they have the "same" definitions.
*
* Note: passing collations and opfamilies separately is a kludge. Adding
* them to IndexInfo may result in better coding here and elsewhere.
*
* Use build_attrmap_by_name(index2, index1) to build the attmap.
*/
bool
CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
const Oid *collations1, const Oid *collations2,
const Oid *opfamilies1, const Oid *opfamilies2,
const AttrMap *attmap)
{
int i;
if (info1->ii_Unique != info2->ii_Unique)
return false;
if (info1->ii_NullsNotDistinct != info2->ii_NullsNotDistinct)
return false;
/* indexes are only equivalent if they have the same access method */
if (info1->ii_Am != info2->ii_Am)
return false;
/* and same number of attributes */
if (info1->ii_NumIndexAttrs != info2->ii_NumIndexAttrs)
return false;
/* and same number of key attributes */
if (info1->ii_NumIndexKeyAttrs != info2->ii_NumIndexKeyAttrs)
return false;
/*
* and columns match through the attribute map (actual attribute numbers
* might differ!) Note that this checks that index columns that are
* expressions appear in the same positions. We will next compare the
* expressions themselves.
*/
for (i = 0; i < info1->ii_NumIndexAttrs; i++)
{
if (attmap->maplen < info2->ii_IndexAttrNumbers[i])
elog(ERROR, "incorrect attribute map");
/* ignore expressions for now (but check their collation/opfamily) */
if (!(info1->ii_IndexAttrNumbers[i] == InvalidAttrNumber &&
info2->ii_IndexAttrNumbers[i] == InvalidAttrNumber))
{
/* fail if just one index has an expression in this column */
if (info1->ii_IndexAttrNumbers[i] == InvalidAttrNumber ||
info2->ii_IndexAttrNumbers[i] == InvalidAttrNumber)
return false;
/* both are columns, so check for match after mapping */
if (attmap->attnums[info2->ii_IndexAttrNumbers[i] - 1] !=
info1->ii_IndexAttrNumbers[i])
return false;
}
/* collation and opfamily are not valid for included columns */
if (i >= info1->ii_NumIndexKeyAttrs)
continue;
if (collations1[i] != collations2[i])
return false;
if (opfamilies1[i] != opfamilies2[i])
return false;
}
/*
* For expression indexes: either both are expression indexes, or neither
* is; if they are, make sure the expressions match.
*/
if ((info1->ii_Expressions != NIL) != (info2->ii_Expressions != NIL))
return false;
if (info1->ii_Expressions != NIL)
{
bool found_whole_row;
Node *mapped;
mapped = map_variable_attnos((Node *) info2->ii_Expressions,
1, 0, attmap,
InvalidOid, &found_whole_row);
if (found_whole_row)
{
/*
* we could throw an error here, but seems out of scope for this
* routine.
*/
return false;
}
if (!equal(info1->ii_Expressions, mapped))
return false;
}
/* Partial index predicates must be identical, if they exist */
if ((info1->ii_Predicate == NULL) != (info2->ii_Predicate == NULL))
return false;
if (info1->ii_Predicate != NULL)
{
bool found_whole_row;
Node *mapped;
mapped = map_variable_attnos((Node *) info2->ii_Predicate,
1, 0, attmap,
InvalidOid, &found_whole_row);
if (found_whole_row)
{
/*
* we could throw an error here, but seems out of scope for this
* routine.
*/
return false;
}
if (!equal(info1->ii_Predicate, mapped))
return false;
}
/* No support currently for comparing exclusion indexes. */
if (info1->ii_ExclusionOps != NULL || info2->ii_ExclusionOps != NULL)
return false;
return true;
}
/* ----------------
* BuildSpeculativeIndexInfo
* Add extra state to IndexInfo record
*
* For unique indexes, we usually don't want to add info to the IndexInfo for
* checking uniqueness, since the B-Tree AM handles that directly. However, in
* the case of speculative insertion and conflict detection in logical
* replication, additional support is required.
*
* Do this processing here rather than in BuildIndexInfo() to not incur the
* overhead in the common non-speculative cases.
* ----------------
*/
void
BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
{
int indnkeyatts;
int i;
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
/*
* fetch info for checking unique indexes
*/
Assert(ii->ii_Unique);
ii->ii_UniqueOps = palloc_array(Oid, indnkeyatts);
ii->ii_UniqueProcs = palloc_array(Oid, indnkeyatts);
ii->ii_UniqueStrats = palloc_array(uint16, indnkeyatts);
/*
* We have to look up the operator's strategy number. This provides a
* cross-check that the operator does match the index.
*/
/* We need the func OIDs and strategy numbers too */
for (i = 0; i < indnkeyatts; i++)
{
ii->ii_UniqueStrats[i] =
IndexAmTranslateCompareType(COMPARE_EQ,
index->rd_rel->relam,
index->rd_opfamily[i],
false);
ii->ii_UniqueOps[i] =
get_opfamily_member(index->rd_opfamily[i],
index->rd_opcintype[i],
index->rd_opcintype[i],
ii->ii_UniqueStrats[i]);
if (!OidIsValid(ii->ii_UniqueOps[i]))
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
ii->ii_UniqueStrats[i], index->rd_opcintype[i],
index->rd_opcintype[i], index->rd_opfamily[i]);
ii->ii_UniqueProcs[i] = get_opcode(ii->ii_UniqueOps[i]);
}
}
/* ----------------
* FormIndexDatum
* Construct values[] and isnull[] arrays for a new index tuple.
*
* indexInfo Info about the index
* slot Heap tuple for which we must prepare an index entry
* estate executor state for evaluating any index expressions
* values Array of index Datums (output area)
* isnull Array of is-null indicators (output area)
*
* When there are no index expressions, estate may be NULL. Otherwise it
* must be supplied, *and* the ecxt_scantuple slot of its per-tuple expr
* context must point to the heap tuple passed in.
*
* Notice we don't actually call index_form_tuple() here; we just prepare
* its input arrays values[] and isnull[]. This is because the index AM
* may wish to alter the data before storage.
* ----------------
*/
void
FormIndexDatum(IndexInfo *indexInfo,
TupleTableSlot *slot,
EState *estate,
Datum *values,
bool *isnull)
{
ListCell *indexpr_item;
int i;
if (indexInfo->ii_Expressions != NIL &&
indexInfo->ii_ExpressionsState == NIL)
{
/* First time through, set up expression evaluation state */
indexInfo->ii_ExpressionsState =
ExecPrepareExprList(indexInfo->ii_Expressions, estate);
indexInfo->ii_ExpressionsExpandState =
ExecPrepareExprList(indexInfo->ii_ExpressionsExpand, estate);
/* Check caller has set up context correctly */
Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
}
indexpr_item = list_head(indexInfo->ii_ExpressionsExpandState);
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
int keycol = indexInfo->ii_IndexAttrNumbers[i];
Datum iDatum;
bool isNull;
if (keycol < 0)
iDatum = slot_getsysattr(slot, keycol, &isNull);
else if (keycol != 0)
{
TupleDesc tupdesc = slot->tts_tupleDescriptor;
Form_pg_attribute att = TupleDescAttr(tupdesc, keycol - 1);
if (att->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
{
/*
* Plain index column; get the value we need directly from the
* heap tuple.
*/
iDatum = slot_getattr(slot, keycol, &isNull);
}
else
{
TupleConstr *constr = tupdesc->constr;
for (int j = 0; j < constr->num_defval; j++)
{
AttrDefault *defval;
Expr *expr = NULL;
ExprState *exprstate;
defval = &constr->defval[j];
if (defval->adnum == keycol)
{
expr = stringToNode(defval->adbin);
exprstate = ExecPrepareExpr(expr, estate);
iDatum = ExecEvalExprSwitchContext(exprstate,
GetPerTupleExprContext(estate),
&isNull);
break;
}
Assert(j + 1 < constr->num_defval);
}
}
}
else
{
/*
* Index expression --- need to evaluate it.
*/
if (indexpr_item == NULL)
elog(ERROR, "wrong number of index expressions");
iDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(indexpr_item),
GetPerTupleExprContext(estate),
&isNull);
indexpr_item = lnext(indexInfo->ii_ExpressionsExpandState, indexpr_item);
}
values[i] = iDatum;
isnull[i] = isNull;
}
if (indexpr_item != NULL)
elog(ERROR, "wrong number of index expressions");
}
/*
* index_update_stats --- update pg_class entry after CREATE INDEX or REINDEX
*
* This routine updates the pg_class row of either an index or its parent
* relation after CREATE INDEX or REINDEX. Its rather bizarre API is designed
* to ensure we can do all the necessary work in just one update.
*
* hasindex: set relhasindex to this value
* reltuples: if >= 0, set reltuples to this value; else no change
*
* If reltuples >= 0, relpages, relallvisible, and relallfrozen are also
* updated (using RelationGetNumberOfBlocks() and visibilitymap_count()).
*
* NOTE: an important side-effect of this operation is that an SI invalidation
* message is sent out to all backends --- including me --- causing relcache
* entries to be flushed or updated with the new data. This must happen even
* if we find that no change is needed in the pg_class row. When updating
* a heap entry, this ensures that other backends find out about the new
* index. When updating an index, it's important because some index AMs
* expect a relcache flush to occur after REINDEX.
*/
static void
index_update_stats(Relation rel,
bool hasindex,
double reltuples)
{
bool update_stats;
BlockNumber relpages = 0; /* keep compiler quiet */
BlockNumber relallvisible = 0;
BlockNumber relallfrozen = 0;
Oid relid = RelationGetRelid(rel);
Relation pg_class;
ScanKeyData key[1];
HeapTuple tuple;
void *state;
Form_pg_class rd_rel;
bool dirty;
/*
* As a special hack, if we are dealing with an empty table and the
* existing reltuples is -1, we leave that alone. This ensures that
* creating an index as part of CREATE TABLE doesn't cause the table to
* prematurely look like it's been vacuumed. The rd_rel we modify may
* differ from rel->rd_rel due to e.g. commit of concurrent GRANT, but the
* commands that change reltuples take locks conflicting with ours. (Even
* if a command changed reltuples under a weaker lock, this affects only
* statistics for an empty table.)
*/
if (reltuples == 0 && rel->rd_rel->reltuples < 0)
reltuples = -1;
/*
* Don't update statistics during binary upgrade, because the indexes are
* created before the data is moved into place.
*/
update_stats = reltuples >= 0 && !IsBinaryUpgrade;
/*
* If autovacuum is off, user may not be expecting table relstats to
* change. This can be important when restoring a dump that includes
* statistics, as the table statistics may be restored before the index is
* created, and we want to preserve the restored table statistics.
*/
if (rel->rd_rel->relkind == RELKIND_RELATION ||
rel->rd_rel->relkind == RELKIND_TOASTVALUE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (AutoVacuumingActive())
{
StdRdOptions *options = (StdRdOptions *) rel->rd_options;
if (options != NULL && !options->autovacuum.enabled)
update_stats = false;
}
else
update_stats = false;
}
/*
* Finish I/O and visibility map buffer locks before
* systable_inplace_update_begin() locks the pg_class buffer. The rd_rel
* we modify may differ from rel->rd_rel due to e.g. commit of concurrent
* GRANT, but no command changes a relkind from non-index to index. (Even
* if one did, relallvisible doesn't break functionality.)
*/
if (update_stats)
{
relpages = RelationGetNumberOfBlocks(rel);
if (rel->rd_rel->relkind != RELKIND_INDEX)
visibilitymap_count(rel, &relallvisible, &relallfrozen);
}
/*
* We always update the pg_class row using a non-transactional,
* overwrite-in-place update. There are several reasons for this:
*
* 1. In bootstrap mode, we have no choice --- UPDATE wouldn't work.
*
* 2. We could be reindexing pg_class itself, in which case we can't move
* its pg_class row because CatalogTupleInsert/CatalogTupleUpdate might
* not know about all the indexes yet (see reindex_relation).
*
* 3. Because we execute CREATE INDEX with just share lock on the parent
* rel (to allow concurrent index creations), an ordinary update could
* suffer a tuple-concurrently-updated failure against another CREATE
* INDEX committing at about the same time. We can avoid that by having
* them both do nontransactional updates (we assume they will both be
* trying to change the pg_class row to the same thing, so it doesn't
* matter which goes first).
*
* It is safe to use a non-transactional update even though our
* transaction could still fail before committing. Setting relhasindex
* true is safe even if there are no indexes (VACUUM will eventually fix
* it). And of course the new relpages and reltuples counts are correct
* regardless. However, we don't want to change relpages (or
* relallvisible) if the caller isn't providing an updated reltuples
* count, because that would bollix the reltuples/relpages ratio which is
* what's really important.
*/
pg_class = table_open(RelationRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
Anum_pg_class_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
systable_inplace_update_begin(pg_class, ClassOidIndexId, true, NULL,
1, key, &tuple, &state);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for relation %u", relid);
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
/* Should this be a more comprehensive test? */
Assert(rd_rel->relkind != RELKIND_PARTITIONED_INDEX);
/* Apply required updates, if any, to copied tuple */
dirty = false;
if (rd_rel->relhasindex != hasindex)
{
rd_rel->relhasindex = hasindex;
dirty = true;
}
if (update_stats)
{
if (rd_rel->relpages != (int32) relpages)
{
rd_rel->relpages = (int32) relpages;
dirty = true;
}
if (rd_rel->reltuples != (float4) reltuples)
{
rd_rel->reltuples = (float4) reltuples;
dirty = true;
}
if (rd_rel->relallvisible != (int32) relallvisible)
{
rd_rel->relallvisible = (int32) relallvisible;
dirty = true;
}
if (rd_rel->relallfrozen != (int32) relallfrozen)
{
rd_rel->relallfrozen = (int32) relallfrozen;
dirty = true;
}
}
/*
* If anything changed, write out the tuple
*/
if (dirty)
{
systable_inplace_update_finish(state, tuple);
/* the above sends transactional and immediate cache inval messages */
}
else
{
systable_inplace_update_cancel(state);
/*
* While we didn't change relhasindex, CREATE INDEX needs a
* transactional inval for when the new index's catalog rows become
* visible. Other CREATE INDEX and REINDEX code happens to also queue
* this inval, but keep this in case rare callers rely on this part of
* our API contract.
*/
CacheInvalidateRelcacheByTuple(tuple);
}
heap_freetuple(tuple);
table_close(pg_class, RowExclusiveLock);
}
/*
* index_build - invoke access-method-specific index build procedure
*
* On entry, the index's catalog entries are valid, and its physical disk
* file has been created but is empty. We call the AM-specific build
* procedure to fill in the index contents. We then update the pg_class
* entries of the index and heap relation as needed, using statistics
* returned by ambuild as well as data passed by the caller.
*
* isreindex indicates we are recreating a previously-existing index.
* parallel indicates if parallelism may be useful.
* progress indicates if the backend should update its progress info.
*
* Note: before Postgres 8.2, the passed-in heap and index Relations
* were automatically closed by this routine. This is no longer the case.
* The caller opened 'em, and the caller should close 'em.
*/
void
index_build(Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo,
bool isreindex,
bool parallel,
bool progress)
{
IndexBuildResult *stats;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
/*
* sanity checks
*/
Assert(RelationIsValid(indexRelation));
Assert(indexRelation->rd_indam);
Assert(indexRelation->rd_indam->ambuild);
Assert(indexRelation->rd_indam->ambuildempty);
/*
* Determine worker process details for parallel CREATE INDEX. Currently,
* only btree, GIN, and BRIN have support for parallel builds.
*
* Note that planner considers parallel safety for us.
*/
if (parallel && IsNormalProcessingMode() &&
indexRelation->rd_indam->amcanbuildparallel)
indexInfo->ii_ParallelWorkers =
plan_create_index_workers(RelationGetRelid(heapRelation),
RelationGetRelid(indexRelation));
if (indexInfo->ii_ParallelWorkers == 0)
ereport(DEBUG1,
(errmsg_internal("building index \"%s\" on table \"%s\" serially",
RelationGetRelationName(indexRelation),
RelationGetRelationName(heapRelation))));
else
ereport(DEBUG1,
(errmsg_internal("building index \"%s\" on table \"%s\" with request for %d parallel workers",
RelationGetRelationName(indexRelation),
RelationGetRelationName(heapRelation),
indexInfo->ii_ParallelWorkers)));
/*
* Switch to the table owner's userid, so that any index functions are run
* as that user. Also lock down security-restricted operations and
* arrange to make GUC variable changes local to this command.
*/
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
/* Set up initial progress report status */
if (progress)
{
const int progress_index[] = {
PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_SUBPHASE,
PROGRESS_CREATEIDX_TUPLES_DONE,
PROGRESS_CREATEIDX_TUPLES_TOTAL,
PROGRESS_SCAN_BLOCKS_DONE,
PROGRESS_SCAN_BLOCKS_TOTAL
};
const int64 progress_vals[] = {
PROGRESS_CREATEIDX_PHASE_BUILD,
PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE,
0, 0, 0, 0
};
pgstat_progress_update_multi_param(6, progress_index, progress_vals);
}
/*
* Call the access method's build procedure
*/
stats = indexRelation->rd_indam->ambuild(heapRelation, indexRelation,
indexInfo);
Assert(stats);
/*
* If this is an unlogged index, we may need to write out an init fork for
* it -- but we must first check whether one already exists. If, for
* example, an unlogged relation is truncated in the transaction that
* created it, or truncated twice in a subsequent transaction, the
* relfilenumber won't change, and nothing needs to be done here.
*/
if (indexRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
!smgrexists(RelationGetSmgr(indexRelation), INIT_FORKNUM))
{
smgrcreate(RelationGetSmgr(indexRelation), INIT_FORKNUM, false);
log_smgrcreate(&indexRelation->rd_locator, INIT_FORKNUM);
indexRelation->rd_indam->ambuildempty(indexRelation);
}
/*
* If we found any potentially broken HOT chains, mark the index as not
* being usable until the current transaction is below the event horizon.
* See src/backend/access/heap/README.HOT for discussion. While it might
* become safe to use the index earlier based on actual cleanup activity
* and other active transactions, the test for that would be much more
* complex and would require some form of blocking, so keep it simple and
* fast by just using the current transaction.
*
* However, when reindexing an existing index, we should do nothing here.
* Any HOT chains that are broken with respect to the index must predate
* the index's original creation, so there is no need to change the
* index's usability horizon. Moreover, we *must not* try to change the
* index's pg_index entry while reindexing pg_index itself, and this
* optimization nicely prevents that. The more complex rules needed for a
* reindex are handled separately after this function returns.
*
* We also need not set indcheckxmin during a concurrent index build,
* because we won't set indisvalid true until all transactions that care
* about the broken HOT chains are gone.
*
* Therefore, this code path can only be taken during non-concurrent
* CREATE INDEX. Thus the fact that heap_update will set the pg_index
* tuple's xmin doesn't matter, because that tuple was created in the
* current transaction anyway. That also means we don't need to worry
* about any concurrent readers of the tuple; no other transaction can see
* it yet.
*/
if (indexInfo->ii_BrokenHotChain &&
!isreindex &&
!indexInfo->ii_Concurrent)
{
Oid indexId = RelationGetRelid(indexRelation);
Relation pg_index;
HeapTuple indexTuple;
Form_pg_index indexForm;
pg_index = table_open(IndexRelationId, RowExclusiveLock);
indexTuple = SearchSysCacheCopy1(INDEXRELID,
ObjectIdGetDatum(indexId));
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexId);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
/* If it's a new index, indcheckxmin shouldn't be set ... */
Assert(!indexForm->indcheckxmin);
indexForm->indcheckxmin = true;
CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple);
heap_freetuple(indexTuple);
table_close(pg_index, RowExclusiveLock);
}
/*
* Update heap and index pg_class rows
*/
index_update_stats(heapRelation,
true,
stats->heap_tuples);
index_update_stats(indexRelation,
false,
stats->index_tuples);
/* Make the updated catalog row versions visible */
CommandCounterIncrement();
/*
* If it's for an exclusion constraint, make a second pass over the heap
* to verify that the constraint is satisfied. We must not do this until
* the index is fully valid. (Broken HOT chains shouldn't matter, though;
* see comments for IndexCheckExclusion.)
*/
if (indexInfo->ii_ExclusionOps != NULL)
IndexCheckExclusion(heapRelation, indexRelation, indexInfo);
/* Roll back any GUC changes executed by index functions */
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
}
/*
* IndexCheckExclusion - verify that a new exclusion constraint is satisfied
*
* When creating an exclusion constraint, we first build the index normally
* and then rescan the heap to check for conflicts. We assume that we only
* need to validate tuples that are live according to an up-to-date snapshot,
* and that these were correctly indexed even in the presence of broken HOT
* chains. This should be OK since we are holding at least ShareLock on the
* table, meaning there can be no uncommitted updates from other transactions.
* (Note: that wouldn't necessarily work for system catalogs, since many
* operations release write lock early on the system catalogs.)
*/
static void
IndexCheckExclusion(Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo)
{
TableScanDesc scan;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
Snapshot snapshot;
/*
* If we are reindexing the target index, mark it as no longer being
* reindexed, to forestall an Assert in index_beginscan when we try to use
* the index for probes. This is OK because the index is now fully valid.
*/
if (ReindexIsCurrentlyProcessingIndex(RelationGetRelid(indexRelation)))
ResetReindexProcessing();
/*
* Need an EState for evaluation of index expressions and partial-index
* predicates. Also a slot to hold the current tuple.
*/
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
slot = table_slot_create(heapRelation, NULL);
/* Arrange for econtext's scan tuple to be the tuple under test */
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Scan all live tuples in the base relation.
*/
snapshot = RegisterSnapshot(GetLatestSnapshot());
scan = table_beginscan_strat(heapRelation, /* relation */
snapshot, /* snapshot */
0, /* number of keys */
NULL, /* scan key */
true, /* buffer access strategy OK */
true); /* syncscan OK */
while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
{
CHECK_FOR_INTERRUPTS();
/*
* In a partial index, ignore tuples that don't satisfy the predicate.
*/
if (predicateExpand != NULL)
{
if (!ExecQual(predicateExpand, econtext))
continue;
}
/*
* Extract index column values, including computing expressions.
*/
FormIndexDatum(indexInfo,
slot,
estate,
values,
isnull);
/*
* Check that this tuple has no conflicts.
*/
check_exclusion_constraint(heapRelation,
indexRelation, indexInfo,
&(slot->tts_tid), values, isnull,
estate, true);
MemoryContextReset(econtext->ecxt_per_tuple_memory);
}
table_endscan(scan);
UnregisterSnapshot(snapshot);
ExecDropSingleTupleTableSlot(slot);
FreeExecutorState(estate);
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
indexInfo->ii_PredicateExpandState = NULL;
}
/*
* validate_index - support code for concurrent index builds
*
* We do a concurrent index build by first inserting the catalog entry for the
* index via index_create(), marking it not indisready and not indisvalid.
* Then we commit our transaction and start a new one, then we wait for all
* transactions that could have been modifying the table to terminate. Now
* we know that any subsequently-started transactions will see the index and
* honor its constraints on HOT updates; so while existing HOT-chains might
* be broken with respect to the index, no currently live tuple will have an
* incompatible HOT update done to it. We now build the index normally via
* index_build(), while holding a weak lock that allows concurrent
* insert/update/delete. Also, we index only tuples that are valid
* as of the start of the scan (see table_index_build_scan), whereas a normal
* build takes care to include recently-dead tuples. This is OK because
* we won't mark the index valid until all transactions that might be able
* to see those tuples are gone. The reason for doing that is to avoid
* bogus unique-index failures due to concurrent UPDATEs (we might see
* different versions of the same row as being valid when we pass over them,
* if we used HeapTupleSatisfiesVacuum). This leaves us with an index that
* does not contain any tuples added to the table while we built the index.
*
* Next, we mark the index "indisready" (but still not "indisvalid") and
* commit the second transaction and start a third. Again we wait for all
* transactions that could have been modifying the table to terminate. Now
* we know that any subsequently-started transactions will see the index and
* insert their new tuples into it. We then take a new reference snapshot
* which is passed to validate_index(). Any tuples that are valid according
* to this snap, but are not in the index, must be added to the index.
* (Any tuples committed live after the snap will be inserted into the
* index by their originating transaction. Any tuples committed dead before
* the snap need not be indexed, because we will wait out all transactions
* that might care about them before we mark the index valid.)
*
* validate_index() works by first gathering all the TIDs currently in the
* index, using a bulkdelete callback that just stores the TIDs and doesn't
* ever say "delete it". (This should be faster than a plain indexscan;
* also, not all index AMs support full-index indexscan.) Then we sort the
* TIDs, and finally scan the table doing a "merge join" against the TID list
* to see which tuples are missing from the index. Thus we will ensure that
* all tuples valid according to the reference snapshot are in the index.
*
* Building a unique index this way is tricky: we might try to insert a
* tuple that is already dead or is in process of being deleted, and we
* mustn't have a uniqueness failure against an updated version of the same
* row. We could try to check the tuple to see if it's already dead and tell
* index_insert() not to do the uniqueness check, but that still leaves us
* with a race condition against an in-progress update. To handle that,
* we expect the index AM to recheck liveness of the to-be-inserted tuple
* before it declares a uniqueness error.
*
* After completing validate_index(), we wait until all transactions that
* were alive at the time of the reference snapshot are gone; this is
* necessary to be sure there are none left with a transaction snapshot
* older than the reference (and hence possibly able to see tuples we did
* not index). Then we mark the index "indisvalid" and commit. Subsequent
* transactions will be able to use it for queries.
*
* Doing two full table scans is a brute-force strategy. We could try to be
* cleverer, eg storing new tuples in a special area of the table (perhaps
* making the table append-only by setting use_fsm). However that would
* add yet more locking issues.
*/
void
validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
{
Relation heapRelation,
indexRelation;
IndexInfo *indexInfo;
IndexVacuumInfo ivinfo;
ValidateIndexState state;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
{
const int progress_index[] = {
PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_TUPLES_DONE,
PROGRESS_CREATEIDX_TUPLES_TOTAL,
PROGRESS_SCAN_BLOCKS_DONE,
PROGRESS_SCAN_BLOCKS_TOTAL
};
const int64 progress_vals[] = {
PROGRESS_CREATEIDX_PHASE_VALIDATE_IDXSCAN,
0, 0, 0, 0
};
pgstat_progress_update_multi_param(5, progress_index, progress_vals);
}
/* Open and lock the parent heap relation */
heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
/*
* Switch to the table owner's userid, so that any index functions are run
* as that user. Also lock down security-restricted operations and
* arrange to make GUC variable changes local to this command.
*/
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
indexRelation = index_open(indexId, RowExclusiveLock);
/*
* Fetch info needed for index_insert. (You might think this should be
* passed in from DefineIndex, but its copy is long gone due to having
* been built in a previous transaction.)
*/
indexInfo = BuildIndexInfo(indexRelation);
/* mark build is concurrent just for consistency */
indexInfo->ii_Concurrent = true;
/*
* Scan the index and gather up all the TIDs into a tuplesort object.
*/
ivinfo.index = indexRelation;
ivinfo.heaprel = heapRelation;
ivinfo.analyze_only = false;
ivinfo.report_progress = true;
ivinfo.estimated_count = true;
ivinfo.message_level = DEBUG2;
ivinfo.num_heap_tuples = heapRelation->rd_rel->reltuples;
ivinfo.strategy = NULL;
/*
* Encode TIDs as int8 values for the sort, rather than directly sorting
* item pointers. This can be significantly faster, primarily because TID
* is a pass-by-reference type on all platforms, whereas int8 is
* pass-by-value on most platforms.
*/
state.tuplesort = tuplesort_begin_datum(INT8OID, Int8LessOperator,
InvalidOid, false,
maintenance_work_mem,
NULL, TUPLESORT_NONE);
state.htups = state.itups = state.tups_inserted = 0;
/* ambulkdelete updates progress metrics */
(void) index_bulk_delete(&ivinfo, NULL,
validate_index_callback, &state);
/* Execute the sort */
{
const int progress_index[] = {
PROGRESS_CREATEIDX_PHASE,
PROGRESS_SCAN_BLOCKS_DONE,
PROGRESS_SCAN_BLOCKS_TOTAL
};
const int64 progress_vals[] = {
PROGRESS_CREATEIDX_PHASE_VALIDATE_SORT,
0, 0
};
pgstat_progress_update_multi_param(3, progress_index, progress_vals);
}
tuplesort_performsort(state.tuplesort);
/*
* Now scan the heap and "merge" it with the index
*/
pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_PHASE_VALIDATE_TABLESCAN);
table_index_validate_scan(heapRelation,
indexRelation,
indexInfo,
snapshot,
&state);
/* Done with tuplesort object */
tuplesort_end(state.tuplesort);
/* Make sure to release resources cached in indexInfo (if needed). */
index_insert_cleanup(indexRelation, indexInfo);
elog(DEBUG2,
"validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
state.htups, state.itups, state.tups_inserted);
/* Roll back any GUC changes executed by index functions */
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
/* Close rels, but keep locks */
index_close(indexRelation, NoLock);
table_close(heapRelation, NoLock);
}
/*
* validate_index_callback - bulkdelete callback to collect the index TIDs
*/
static bool
validate_index_callback(ItemPointer itemptr, void *opaque)
{
ValidateIndexState *state = (ValidateIndexState *) opaque;
int64 encoded = itemptr_encode(itemptr);
tuplesort_putdatum(state->tuplesort, Int64GetDatum(encoded), false);
state->itups += 1;
return false; /* never actually delete anything */
}
/*
* index_set_state_flags - adjust pg_index state flags
*
* This is used during CREATE/DROP INDEX CONCURRENTLY to adjust the pg_index
* flags that denote the index's state.
*
* Note that CatalogTupleUpdate() sends a cache invalidation message for the
* tuple, so other sessions will hear about the update as soon as we commit.
*/
void
index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
{
Relation pg_index;
HeapTuple indexTuple;
Form_pg_index indexForm;
/* Open pg_index and fetch a writable copy of the index's tuple */
pg_index = table_open(IndexRelationId, RowExclusiveLock);
indexTuple = SearchSysCacheCopy1(INDEXRELID,
ObjectIdGetDatum(indexId));
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexId);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
/* Perform the requested state change on the copy */
switch (action)
{
case INDEX_CREATE_SET_READY:
/* Set indisready during a CREATE INDEX CONCURRENTLY sequence */
Assert(indexForm->indislive);
Assert(!indexForm->indisready);
Assert(!indexForm->indisvalid);
indexForm->indisready = true;
break;
case INDEX_CREATE_SET_VALID:
/* Set indisvalid during a CREATE INDEX CONCURRENTLY sequence */
Assert(indexForm->indislive);
Assert(indexForm->indisready);
Assert(!indexForm->indisvalid);
indexForm->indisvalid = true;
break;
case INDEX_DROP_CLEAR_VALID:
/*
* Clear indisvalid during a DROP INDEX CONCURRENTLY sequence
*
* If indisready == true we leave it set so the index still gets
* maintained by active transactions. We only need to ensure that
* indisvalid is false. (We don't assert that either is initially
* true, though, since we want to be able to retry a DROP INDEX
* CONCURRENTLY that failed partway through.)
*
* Note: the CLUSTER logic assumes that indisclustered cannot be
* set on any invalid index, so clear that flag too. For
* cleanliness, also clear indisreplident.
*/
indexForm->indisvalid = false;
indexForm->indisclustered = false;
indexForm->indisreplident = false;
break;
case INDEX_DROP_SET_DEAD:
/*
* Clear indisready/indislive during DROP INDEX CONCURRENTLY
*
* We clear both indisready and indislive, because we not only
* want to stop updates, we want to prevent sessions from touching
* the index at all.
*/
Assert(!indexForm->indisvalid);
Assert(!indexForm->indisclustered);
Assert(!indexForm->indisreplident);
indexForm->indisready = false;
indexForm->indislive = false;
break;
}
/* ... and update it */
CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple);
table_close(pg_index, RowExclusiveLock);
}
/*
* IndexGetRelation: given an index's relation OID, get the OID of the
* relation it is an index on. Uses the system cache.
*/
Oid
IndexGetRelation(Oid indexId, bool missing_ok)
{
HeapTuple tuple;
Form_pg_index index;
Oid result;
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexId));
if (!HeapTupleIsValid(tuple))
{
if (missing_ok)
return InvalidOid;
elog(ERROR, "cache lookup failed for index %u", indexId);
}
index = (Form_pg_index) GETSTRUCT(tuple);
Assert(index->indexrelid == indexId);
result = index->indrelid;
ReleaseSysCache(tuple);
return result;
}
/*
* reindex_index - This routine is used to recreate a single index
*/
void
reindex_index(const ReindexStmt *stmt, Oid indexId,
bool skip_constraint_checks, char persistence,
const ReindexParams *params)
{
Relation iRel,
heapRelation;
Oid heapId;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
IndexInfo *indexInfo;
volatile bool skipped_constraint = false;
PGRUsage ru0;
bool progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
bool set_tablespace = false;
pg_rusage_init(&ru0);
/*
* Open and lock the parent heap relation. ShareLock is sufficient since
* we only need to be sure no schema or data changes are going on.
*/
heapId = IndexGetRelation(indexId,
(params->options & REINDEXOPT_MISSING_OK) != 0);
/* if relation is missing, leave */
if (!OidIsValid(heapId))
return;
if ((params->options & REINDEXOPT_MISSING_OK) != 0)
heapRelation = try_table_open(heapId, ShareLock);
else
heapRelation = table_open(heapId, ShareLock);
/* if relation is gone, leave */
if (!heapRelation)
return;
/*
* Switch to the table owner's userid, so that any index functions are run
* as that user. Also lock down security-restricted operations and
* arrange to make GUC variable changes local to this command.
*/
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
if (progress)
{
const int progress_cols[] = {
PROGRESS_CREATEIDX_COMMAND,
PROGRESS_CREATEIDX_INDEX_OID
};
const int64 progress_vals[] = {
PROGRESS_CREATEIDX_COMMAND_REINDEX,
indexId
};
pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX,
heapId);
pgstat_progress_update_multi_param(2, progress_cols, progress_vals);
}
/*
* Open the target index relation and get an exclusive lock on it, to
* ensure that no one else is touching this particular index.
*/
if ((params->options & REINDEXOPT_MISSING_OK) != 0)
iRel = try_index_open(indexId, AccessExclusiveLock);
else
iRel = index_open(indexId, AccessExclusiveLock);
/* if index relation is gone, leave */
if (!iRel)
{
/* Roll back any GUC changes */
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
/* Close parent heap relation, but keep locks */
table_close(heapRelation, NoLock);
return;
}
if (progress)
pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
iRel->rd_rel->relam);
/*
* If a statement is available, telling that this comes from a REINDEX
* command, collect the index for event triggers.
*/
if (stmt)
{
ObjectAddress address;
ObjectAddressSet(address, RelationRelationId, indexId);
EventTriggerCollectSimpleCommand(address,
InvalidObjectAddress,
(const Node *) stmt);
}
/*
* Partitioned indexes should never get processed here, as they have no
* physical storage.
*/
if (iRel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
elog(ERROR, "cannot reindex partitioned index \"%s.%s\"",
get_namespace_name(RelationGetNamespace(iRel)),
RelationGetRelationName(iRel));
/*
* Don't allow reindex on temp tables of other backends ... their local
* buffer manager is not going to cope.
*/
if (RELATION_IS_OTHER_TEMP(iRel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex temporary tables of other sessions")));
/*
* Don't allow reindex of an invalid index on TOAST table. This is a
* leftover from a failed REINDEX CONCURRENTLY, and if rebuilt it would
* not be possible to drop it anymore.
*/
if (IsToastNamespace(RelationGetNamespace(iRel)) &&
!get_index_isvalid(indexId))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex invalid index on TOAST table")));
/*
* System relations cannot be moved even if allow_system_table_mods is
* enabled to keep things consistent with the concurrent case where all
* the indexes of a relation are processed in series, including indexes of
* toast relations.
*
* Note that this check is not part of CheckRelationTableSpaceMove() as it
* gets used for ALTER TABLE SET TABLESPACE that could cascade across
* toast relations.
*/
if (OidIsValid(params->tablespaceOid) &&
IsSystemRelation(iRel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move system relation \"%s\"",
RelationGetRelationName(iRel))));
/* Check if the tablespace of this index needs to be changed */
if (OidIsValid(params->tablespaceOid) &&
CheckRelationTableSpaceMove(iRel, params->tablespaceOid))
set_tablespace = true;
/*
* Also check for active uses of the index in the current transaction; we
* don't want to reindex underneath an open indexscan.
*/
CheckTableNotInUse(iRel, "REINDEX INDEX");
/* Set new tablespace, if requested */
if (set_tablespace)
{
/* Update its pg_class row */
SetRelationTableSpace(iRel, params->tablespaceOid, InvalidOid);
/*
* Schedule unlinking of the old index storage at transaction commit.
*/
RelationDropStorage(iRel);
RelationAssumeNewRelfilelocator(iRel);
/* Make sure the reltablespace change is visible */
CommandCounterIncrement();
}
/*
* All predicate locks on the index are about to be made invalid. Promote
* them to relation locks on the heap.
*/
TransferPredicateLocksToHeapRelation(iRel);
/* Fetch info needed for index_build */
indexInfo = BuildIndexInfo(iRel);
/* If requested, skip checking uniqueness/exclusion constraints */
if (skip_constraint_checks)
{
if (indexInfo->ii_Unique || indexInfo->ii_ExclusionOps != NULL)
skipped_constraint = true;
indexInfo->ii_Unique = false;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
}
/* Suppress use of the target index while rebuilding it */
SetReindexProcessing(heapId, indexId);
/* Create a new physical relation for the index */
RelationSetNewRelfilenumber(iRel, persistence);
/* Initialize the index and rebuild */
/* Note: we do not need to re-establish pkey setting */
index_build(heapRelation, iRel, indexInfo, true, true, progress);
/* Re-allow use of target index */
ResetReindexProcessing();
/*
* If the index is marked invalid/not-ready/dead (ie, it's from a failed
* CREATE INDEX CONCURRENTLY, or a DROP INDEX CONCURRENTLY failed midway),
* and we didn't skip a uniqueness check, we can now mark it valid. This
* allows REINDEX to be used to clean up in such cases.
*
* We can also reset indcheckxmin, because we have now done a
* non-concurrent index build, *except* in the case where index_build
* found some still-broken HOT chains. If it did, and we don't have to
* change any of the other flags, we just leave indcheckxmin alone (note
* that index_build won't have changed it, because this is a reindex).
* This is okay and desirable because not updating the tuple leaves the
* index's usability horizon (recorded as the tuple's xmin value) the same
* as it was.
*
* But, if the index was invalid/not-ready/dead and there were broken HOT
* chains, we had better force indcheckxmin true, because the normal
* argument that the HOT chains couldn't conflict with the index is
* suspect for an invalid index. (A conflict is definitely possible if
* the index was dead. It probably shouldn't happen otherwise, but let's
* be conservative.) In this case advancing the usability horizon is
* appropriate.
*
* Another reason for avoiding unnecessary updates here is that while
* reindexing pg_index itself, we must not try to update tuples in it.
* pg_index's indexes should always have these flags in their clean state,
* so that won't happen.
*/
if (!skipped_constraint)
{
Relation pg_index;
HeapTuple indexTuple;
Form_pg_index indexForm;
bool index_bad;
pg_index = table_open(IndexRelationId, RowExclusiveLock);
indexTuple = SearchSysCacheCopy1(INDEXRELID,
ObjectIdGetDatum(indexId));
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexId);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
index_bad = (!indexForm->indisvalid ||
!indexForm->indisready ||
!indexForm->indislive);
if (index_bad ||
(indexForm->indcheckxmin && !indexInfo->ii_BrokenHotChain))
{
if (!indexInfo->ii_BrokenHotChain)
indexForm->indcheckxmin = false;
else if (index_bad)
indexForm->indcheckxmin = true;
indexForm->indisvalid = true;
indexForm->indisready = true;
indexForm->indislive = true;
CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple);
/*
* Invalidate the relcache for the table, so that after we commit
* all sessions will refresh the table's index list. This ensures
* that if anyone misses seeing the pg_index row during this
* update, they'll refresh their list before attempting any update
* on the table.
*/
CacheInvalidateRelcache(heapRelation);
}
table_close(pg_index, RowExclusiveLock);
}
/* Log what we did */
if ((params->options & REINDEXOPT_VERBOSE) != 0)
ereport(INFO,
(errmsg("index \"%s\" was reindexed",
get_rel_name(indexId)),
errdetail_internal("%s",
pg_rusage_show(&ru0))));
/* Roll back any GUC changes executed by index functions */
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
/* Close rels, but keep locks */
index_close(iRel, NoLock);
table_close(heapRelation, NoLock);
if (progress)
pgstat_progress_end_command();
}
/*
* reindex_relation - This routine is used to recreate all indexes
* of a relation (and optionally its toast relation too, if any).
*
* "flags" is a bitmask that can include any combination of these bits:
*
* REINDEX_REL_PROCESS_TOAST: if true, process the toast table too (if any).
*
* REINDEX_REL_SUPPRESS_INDEX_USE: if true, the relation was just completely
* rebuilt by an operation such as VACUUM FULL or CLUSTER, and therefore its
* indexes are inconsistent with it. This makes things tricky if the relation
* is a system catalog that we might consult during the reindexing. To deal
* with that case, we mark all of the indexes as pending rebuild so that they
* won't be trusted until rebuilt. The caller is required to call us *without*
* having made the rebuilt table visible by doing CommandCounterIncrement;
* we'll do CCI after having collected the index list. (This way we can still
* use catalog indexes while collecting the list.)
*
* REINDEX_REL_CHECK_CONSTRAINTS: if true, recheck unique and exclusion
* constraint conditions, else don't. To avoid deadlocks, VACUUM FULL or
* CLUSTER on a system catalog must omit this flag. REINDEX should be used to
* rebuild an index if constraint inconsistency is suspected. For optimal
* performance, other callers should include the flag only after transforming
* the data in a manner that risks a change in constraint validity.
*
* REINDEX_REL_FORCE_INDEXES_UNLOGGED: if true, set the persistence of the
* rebuilt indexes to unlogged.
*
* REINDEX_REL_FORCE_INDEXES_PERMANENT: if true, set the persistence of the
* rebuilt indexes to permanent.
*
* Returns true if any indexes were rebuilt (including toast table's index
* when relevant). Note that a CommandCounterIncrement will occur after each
* index rebuild.
*/
bool
reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
const ReindexParams *params)
{
Relation rel;
Oid toast_relid;
List *indexIds;
char persistence;
bool result = false;
ListCell *indexId;
int i;
/*
* Open and lock the relation. ShareLock is sufficient since we only need
* to prevent schema and data changes in it. The lock level used here
* should match ReindexTable().
*/
if ((params->options & REINDEXOPT_MISSING_OK) != 0)
rel = try_table_open(relid, ShareLock);
else
rel = table_open(relid, ShareLock);
/* if relation is gone, leave */
if (!rel)
return false;
/*
* Partitioned tables should never get processed here, as they have no
* physical storage.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
elog(ERROR, "cannot reindex partitioned table \"%s.%s\"",
get_namespace_name(RelationGetNamespace(rel)),
RelationGetRelationName(rel));
toast_relid = rel->rd_rel->reltoastrelid;
/*
* Get the list of index OIDs for this relation. (We trust the relcache
* to get this with a sequential scan if ignoring system indexes.)
*/
indexIds = RelationGetIndexList(rel);
if (flags & REINDEX_REL_SUPPRESS_INDEX_USE)
{
/* Suppress use of all the indexes until they are rebuilt */
SetReindexPending(indexIds);
/*
* Make the new heap contents visible --- now things might be
* inconsistent!
*/
CommandCounterIncrement();
}
/*
* Reindex the toast table, if any, before the main table.
*
* This helps in cases where a corruption in the toast table's index would
* otherwise error and stop REINDEX TABLE command when it tries to fetch a
* toasted datum. This way. the toast table's index is rebuilt and fixed
* before it is used for reindexing the main table.
*
* It is critical to call reindex_relation() *after* the call to
* RelationGetIndexList() returning the list of indexes on the relation,
* because reindex_relation() will call CommandCounterIncrement() after
* every reindex_index(). See REINDEX_REL_SUPPRESS_INDEX_USE for more
* details.
*/
if ((flags & REINDEX_REL_PROCESS_TOAST) && OidIsValid(toast_relid))
{
/*
* Note that this should fail if the toast relation is missing, so
* reset REINDEXOPT_MISSING_OK. Even if a new tablespace is set for
* the parent relation, the indexes on its toast table are not moved.
* This rule is enforced by setting tablespaceOid to InvalidOid.
*/
ReindexParams newparams = *params;
newparams.options &= ~(REINDEXOPT_MISSING_OK);
newparams.tablespaceOid = InvalidOid;
result |= reindex_relation(stmt, toast_relid, flags, &newparams);
}
/*
* Compute persistence of indexes: same as that of owning rel, unless
* caller specified otherwise.
*/
if (flags & REINDEX_REL_FORCE_INDEXES_UNLOGGED)
persistence = RELPERSISTENCE_UNLOGGED;
else if (flags & REINDEX_REL_FORCE_INDEXES_PERMANENT)
persistence = RELPERSISTENCE_PERMANENT;
else
persistence = rel->rd_rel->relpersistence;
/* Reindex all the indexes. */
i = 1;
foreach(indexId, indexIds)
{
Oid indexOid = lfirst_oid(indexId);
Oid indexNamespaceId = get_rel_namespace(indexOid);
/*
* Skip any invalid indexes on a TOAST table. These can only be
* duplicate leftovers from a failed REINDEX CONCURRENTLY, and if
* rebuilt it would not be possible to drop them anymore.
*/
if (IsToastNamespace(indexNamespaceId) &&
!get_index_isvalid(indexOid))
{
ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex invalid index \"%s.%s\" on TOAST table, skipping",
get_namespace_name(indexNamespaceId),
get_rel_name(indexOid))));
/*
* Remove this invalid toast index from the reindex pending list,
* as it is skipped here due to the hard failure that would happen
* in reindex_index(), should we try to process it.
*/
if (flags & REINDEX_REL_SUPPRESS_INDEX_USE)
RemoveReindexPending(indexOid);
continue;
}
reindex_index(stmt, indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS),
persistence, params);
CommandCounterIncrement();
/* Index should no longer be in the pending list */
Assert(!ReindexIsProcessingIndex(indexOid));
/* Set index rebuild count */
pgstat_progress_update_param(PROGRESS_REPACK_INDEX_REBUILD_COUNT,
i);
i++;
}
/*
* Close rel, but continue to hold the lock.
*/
table_close(rel, NoLock);
result |= (indexIds != NIL);
return result;
}
/* ----------------------------------------------------------------
* System index reindexing support
*
* When we are busy reindexing a system index, this code provides support
* for preventing catalog lookups from using that index. We also make use
* of this to catch attempted uses of user indexes during reindexing of
* those indexes. This information is propagated to parallel workers;
* attempting to change it during a parallel operation is not permitted.
* ----------------------------------------------------------------
*/
static Oid currentlyReindexedHeap = InvalidOid;
static Oid currentlyReindexedIndex = InvalidOid;
static List *pendingReindexedIndexes = NIL;
static int reindexingNestLevel = 0;
/*
* ReindexIsProcessingHeap
* True if heap specified by OID is currently being reindexed.
*/
bool
ReindexIsProcessingHeap(Oid heapOid)
{
return heapOid == currentlyReindexedHeap;
}
/*
* ReindexIsCurrentlyProcessingIndex
* True if index specified by OID is currently being reindexed.
*/
static bool
ReindexIsCurrentlyProcessingIndex(Oid indexOid)
{
return indexOid == currentlyReindexedIndex;
}
/*
* ReindexIsProcessingIndex
* True if index specified by OID is currently being reindexed,
* or should be treated as invalid because it is awaiting reindex.
*/
bool
ReindexIsProcessingIndex(Oid indexOid)
{
return indexOid == currentlyReindexedIndex ||
list_member_oid(pendingReindexedIndexes, indexOid);
}
/*
* SetReindexProcessing
* Set flag that specified heap/index are being reindexed.
*/
static void
SetReindexProcessing(Oid heapOid, Oid indexOid)
{
Assert(OidIsValid(heapOid) && OidIsValid(indexOid));
/* Reindexing is not re-entrant. */
if (OidIsValid(currentlyReindexedHeap))
elog(ERROR, "cannot reindex while reindexing");
currentlyReindexedHeap = heapOid;
currentlyReindexedIndex = indexOid;
/* Index is no longer "pending" reindex. */
RemoveReindexPending(indexOid);
/* This may have been set already, but in case it isn't, do so now. */
reindexingNestLevel = GetCurrentTransactionNestLevel();
}
/*
* ResetReindexProcessing
* Unset reindexing status.
*/
static void
ResetReindexProcessing(void)
{
currentlyReindexedHeap = InvalidOid;
currentlyReindexedIndex = InvalidOid;
/* reindexingNestLevel remains set till end of (sub)transaction */
}
/*
* SetReindexPending
* Mark the given indexes as pending reindex.
*
* NB: we assume that the current memory context stays valid throughout.
*/
static void
SetReindexPending(List *indexes)
{
/* Reindexing is not re-entrant. */
if (pendingReindexedIndexes)
elog(ERROR, "cannot reindex while reindexing");
if (IsInParallelMode())
elog(ERROR, "cannot modify reindex state during a parallel operation");
pendingReindexedIndexes = list_copy(indexes);
reindexingNestLevel = GetCurrentTransactionNestLevel();
}
/*
* RemoveReindexPending
* Remove the given index from the pending list.
*/
static void
RemoveReindexPending(Oid indexOid)
{
if (IsInParallelMode())
elog(ERROR, "cannot modify reindex state during a parallel operation");
pendingReindexedIndexes = list_delete_oid(pendingReindexedIndexes,
indexOid);
}
/*
* ResetReindexState
* Clear all reindexing state during (sub)transaction abort.
*/
void
ResetReindexState(int nestLevel)
{
/*
* Because reindexing is not re-entrant, we don't need to cope with nested
* reindexing states. We just need to avoid messing up the outer-level
* state in case a subtransaction fails within a REINDEX. So checking the
* current nest level against that of the reindex operation is sufficient.
*/
if (reindexingNestLevel >= nestLevel)
{
currentlyReindexedHeap = InvalidOid;
currentlyReindexedIndex = InvalidOid;
/*
* We needn't try to release the contents of pendingReindexedIndexes;
* that list should be in a transaction-lifespan context, so it will
* go away automatically.
*/
pendingReindexedIndexes = NIL;
reindexingNestLevel = 0;
}
}
/*
* EstimateReindexStateSpace
* Estimate space needed to pass reindex state to parallel workers.
*/
Size
EstimateReindexStateSpace(void)
{
return offsetof(SerializedReindexState, pendingReindexedIndexes)
+ mul_size(sizeof(Oid), list_length(pendingReindexedIndexes));
}
/*
* SerializeReindexState
* Serialize reindex state for parallel workers.
*/
void
SerializeReindexState(Size maxsize, char *start_address)
{
SerializedReindexState *sistate = (SerializedReindexState *) start_address;
int c = 0;
ListCell *lc;
sistate->currentlyReindexedHeap = currentlyReindexedHeap;
sistate->currentlyReindexedIndex = currentlyReindexedIndex;
sistate->numPendingReindexedIndexes = list_length(pendingReindexedIndexes);
foreach(lc, pendingReindexedIndexes)
sistate->pendingReindexedIndexes[c++] = lfirst_oid(lc);
}
/*
* RestoreReindexState
* Restore reindex state in a parallel worker.
*/
void
RestoreReindexState(const void *reindexstate)
{
const SerializedReindexState *sistate = (const SerializedReindexState *) reindexstate;
int c = 0;
MemoryContext oldcontext;
currentlyReindexedHeap = sistate->currentlyReindexedHeap;
currentlyReindexedIndex = sistate->currentlyReindexedIndex;
Assert(pendingReindexedIndexes == NIL);
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
for (c = 0; c < sistate->numPendingReindexedIndexes; ++c)
pendingReindexedIndexes =
lappend_oid(pendingReindexedIndexes,
sistate->pendingReindexedIndexes[c]);
MemoryContextSwitchTo(oldcontext);
/* Note the worker has its own transaction nesting level */
reindexingNestLevel = GetCurrentTransactionNestLevel();
}
./indexcmds.c 0000664 0001750 0001750 00000440403 15222105474 012036 0 ustar xman xman /*-------------------------------------------------------------------------
*
* indexcmds.c
* POSTGRES define and remove index code.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/commands/indexcmds.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/amapi.h"
#include "access/attmap.h"
#include "access/gist.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_coerce.h"
#include "parser/parse_oper.h"
#include "parser/parse_utilcmd.h"
#include "partitioning/partdesc.h"
#include "pgstat.h"
#include "rewrite/rewriteManip.h"
#include "storage/lmgr.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/injection_point.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/partcache.h"
#include "utils/pg_rusage.h"
#include "utils/regproc.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
/* non-export function prototypes */
static bool CompareOpclassOptions(const Datum *opts1, const Datum *opts2, int natts);
static void CheckPredicate(Expr *predicate);
static void ComputeIndexAttrs(ParseState *pstate,
IndexInfo *indexInfo,
Oid *typeOids,
Oid *collationOids,
Oid *opclassOids,
Datum *opclassOptions,
int16 *colOptions,
const List *attList,
const List *exclusionOpNames,
Oid relId,
const char *accessMethodName,
Oid accessMethodId,
bool amcanorder,
bool isconstraint,
bool iswithoutoverlaps,
Oid ddl_userid,
int ddl_sec_context,
int *ddl_save_nestlevel);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
const List *colnames, const List *exclusionOpNames,
bool primary, bool isconstraint);
static char *ChooseIndexNameAddition(const List *colnames);
static List *ChooseIndexColumnNames(const List *indexElems);
static void ReindexIndex(const ReindexStmt *stmt, const ReindexParams *params,
bool isTopLevel);
static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg);
static Oid ReindexTable(const ReindexStmt *stmt, const ReindexParams *params,
bool isTopLevel);
static void ReindexMultipleTables(const ReindexStmt *stmt,
const ReindexParams *params);
static void reindex_error_callback(void *arg);
static void ReindexPartitions(const ReindexStmt *stmt, Oid relid,
const ReindexParams *params, bool isTopLevel);
static void ReindexMultipleInternal(const ReindexStmt *stmt, const List *relids,
const ReindexParams *params);
static bool ReindexRelationConcurrently(const ReindexStmt *stmt,
Oid relationOid,
const ReindexParams *params);
static void update_relispartition(Oid relationId, bool newval);
static inline void set_indexsafe_procflags(void);
/*
* callback argument type for RangeVarCallbackForReindexIndex()
*/
struct ReindexIndexCallbackState
{
ReindexParams params; /* options from statement */
Oid locked_table_oid; /* tracks previously locked table */
};
/*
* callback arguments for reindex_error_callback()
*/
typedef struct ReindexErrorInfo
{
char *relname;
char *relnamespace;
char relkind;
} ReindexErrorInfo;
/*
* CheckIndexCompatible
* Determine whether an existing index definition is compatible with a
* prospective index definition, such that the existing index storage
* could become the storage of the new index, avoiding a rebuild.
*
* 'oldId': the OID of the existing index
* 'accessMethodName': name of the AM to use.
* 'attributeList': a list of IndexElem specifying columns and expressions
* to index on.
* 'exclusionOpNames': list of names of exclusion-constraint operators,
* or NIL if not an exclusion constraint.
* 'isWithoutOverlaps': true iff this index has a WITHOUT OVERLAPS clause.
*
* This is tailored to the needs of ALTER TABLE ALTER TYPE, which recreates
* any indexes that depended on a changing column from their pg_get_indexdef
* or pg_get_constraintdef definitions. We omit some of the sanity checks of
* DefineIndex. We assume that the old and new indexes have the same number
* of columns and that if one has an expression column or predicate, both do.
* Errors arising from the attribute list still apply.
*
* Most column type changes that can skip a table rewrite do not invalidate
* indexes. We acknowledge this when all operator classes, collations and
* exclusion operators match. Though we could further permit intra-opfamily
* changes for btree and hash indexes, that adds subtle complexity with no
* concrete benefit for core types. Note, that INCLUDE columns aren't
* checked by this function, for them it's enough that table rewrite is
* skipped.
*
* When a comparison or exclusion operator has a polymorphic input type, the
* actual input types must also match. This defends against the possibility
* that operators could vary behavior in response to get_fn_expr_argtype().
* At present, this hazard is theoretical: check_exclusion_constraint() and
* all core index access methods decline to set fn_expr for such calls.
*
* We do not yet implement a test to verify compatibility of expression
* columns or predicates, so assume any such index is incompatible.
*/
bool
CheckIndexCompatible(Oid oldId,
const char *accessMethodName,
const List *attributeList,
const List *exclusionOpNames,
bool isWithoutOverlaps)
{
bool isconstraint;
Oid *typeIds;
Oid *collationIds;
Oid *opclassIds;
Datum *opclassOptions;
Oid accessMethodId;
Oid relationId;
HeapTuple tuple;
Form_pg_index indexForm;
Form_pg_am accessMethodForm;
const IndexAmRoutine *amRoutine;
bool amcanorder;
bool amsummarizing;
int16 *coloptions;
IndexInfo *indexInfo;
int numberOfAttributes;
int old_natts;
bool ret = true;
oidvector *old_indclass;
oidvector *old_indcollation;
Relation irel;
int i;
Datum d;
/* Caller should already have the relation locked in some way. */
relationId = IndexGetRelation(oldId, false);
/*
* We can pretend isconstraint = false unconditionally. It only serves to
* decide the text of an error message that should never happen for us.
*/
isconstraint = false;
numberOfAttributes = list_length(attributeList);
Assert(numberOfAttributes > 0);
Assert(numberOfAttributes <= INDEX_MAX_KEYS);
/* look up the access method */
tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("access method \"%s\" does not exist",
accessMethodName)));
accessMethodForm = (Form_pg_am) GETSTRUCT(tuple);
accessMethodId = accessMethodForm->oid;
amRoutine = GetIndexAmRoutine(accessMethodForm->amhandler);
ReleaseSysCache(tuple);
amcanorder = amRoutine->amcanorder;
amsummarizing = amRoutine->amsummarizing;
/*
* Compute the operator classes, collations, and exclusion operators for
* the new index, so we can test whether it's compatible with the existing
* one. Note that ComputeIndexAttrs might fail here, but that's OK:
* DefineIndex would have failed later. Our attributeList contains only
* key attributes, thus we're filling ii_NumIndexAttrs and
* ii_NumIndexKeyAttrs with same value.
*/
indexInfo = makeIndexInfo(numberOfAttributes, numberOfAttributes,
accessMethodId, NIL, NIL, false, false,
false, false, amsummarizing, isWithoutOverlaps);
typeIds = palloc_array(Oid, numberOfAttributes);
collationIds = palloc_array(Oid, numberOfAttributes);
opclassIds = palloc_array(Oid, numberOfAttributes);
opclassOptions = palloc_array(Datum, numberOfAttributes);
coloptions = palloc_array(int16, numberOfAttributes);
ComputeIndexAttrs(NULL, indexInfo,
typeIds, collationIds, opclassIds, opclassOptions,
coloptions, attributeList,
exclusionOpNames, relationId,
accessMethodName, accessMethodId,
amcanorder, isconstraint, isWithoutOverlaps, InvalidOid,
0, NULL);
/* Get the soon-obsolete pg_index tuple. */
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(oldId));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for index %u", oldId);
indexForm = (Form_pg_index) GETSTRUCT(tuple);
/*
* We don't assess expressions or predicates; assume incompatibility.
* Also, if the index is invalid for any reason, treat it as incompatible.
*/
if (!(heap_attisnull(tuple, Anum_pg_index_indpred, NULL) &&
heap_attisnull(tuple, Anum_pg_index_indexprs, NULL) &&
indexForm->indisvalid))
{
ReleaseSysCache(tuple);
return false;
}
/* Any change in operator class or collation breaks compatibility. */
old_natts = indexForm->indnkeyatts;
Assert(old_natts == numberOfAttributes);
d = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indcollation);
old_indcollation = (oidvector *) DatumGetPointer(d);
d = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indclass);
old_indclass = (oidvector *) DatumGetPointer(d);
ret = (memcmp(old_indclass->values, opclassIds, old_natts * sizeof(Oid)) == 0 &&
memcmp(old_indcollation->values, collationIds, old_natts * sizeof(Oid)) == 0);
ReleaseSysCache(tuple);
if (!ret)
return false;
/* For polymorphic opcintype, column type changes break compatibility. */
irel = index_open(oldId, AccessShareLock); /* caller probably has a lock */
for (i = 0; i < old_natts; i++)
{
if (IsPolymorphicType(get_opclass_input_type(opclassIds[i])) &&
TupleDescAttr(irel->rd_att, i)->atttypid != typeIds[i])
{
ret = false;
break;
}
}
/* Any change in opclass options break compatibility. */
if (ret)
{
Datum *oldOpclassOptions = palloc_array(Datum, old_natts);
for (i = 0; i < old_natts; i++)
oldOpclassOptions[i] = get_attoptions(oldId, i + 1);
ret = CompareOpclassOptions(oldOpclassOptions, opclassOptions, old_natts);
pfree(oldOpclassOptions);
}
/* Any change in exclusion operator selections breaks compatibility. */
if (ret && indexInfo->ii_ExclusionOps != NULL)
{
Oid *old_operators,
*old_procs;
uint16 *old_strats;
RelationGetExclusionInfo(irel, &old_operators, &old_procs, &old_strats);
ret = memcmp(old_operators, indexInfo->ii_ExclusionOps,
old_natts * sizeof(Oid)) == 0;
/* Require an exact input type match for polymorphic operators. */
if (ret)
{
for (i = 0; i < old_natts && ret; i++)
{
Oid left,
right;
op_input_types(indexInfo->ii_ExclusionOps[i], &left, &right);
if ((IsPolymorphicType(left) || IsPolymorphicType(right)) &&
TupleDescAttr(irel->rd_att, i)->atttypid != typeIds[i])
{
ret = false;
break;
}
}
}
}
index_close(irel, NoLock);
return ret;
}
/*
* CompareOpclassOptions
*
* Compare per-column opclass options which are represented by arrays of text[]
* datums. Both elements of arrays and array themselves can be NULL.
*/
static bool
CompareOpclassOptions(const Datum *opts1, const Datum *opts2, int natts)
{
int i;
FmgrInfo fm;
if (!opts1 && !opts2)
return true;
fmgr_info(F_ARRAY_EQ, &fm);
for (i = 0; i < natts; i++)
{
Datum opt1 = opts1 ? opts1[i] : (Datum) 0;
Datum opt2 = opts2 ? opts2[i] : (Datum) 0;
if (opt1 == (Datum) 0)
{
if (opt2 == (Datum) 0)
continue;
else
return false;
}
else if (opt2 == (Datum) 0)
return false;
/*
* Compare non-NULL text[] datums. Use C collation to enforce binary
* equivalence of texts, because we don't know anything about the
* semantics of opclass options.
*/
if (!DatumGetBool(FunctionCall2Coll(&fm, C_COLLATION_OID, opt1, opt2)))
return false;
}
return true;
}
/*
* WaitForOlderSnapshots
*
* Wait for transactions that might have an older snapshot than the given xmin
* limit, because it might not contain tuples deleted just before it has
* been taken. Obtain a list of VXIDs of such transactions, and wait for them
* individually. This is used when building an index concurrently.
*
* We can exclude any running transactions that have xmin > the xmin given;
* their oldest snapshot must be newer than our xmin limit.
* We can also exclude any transactions that have xmin = zero, since they
* evidently have no live snapshot at all (and any one they might be in
* process of taking is certainly newer than ours). Transactions in other
* DBs can be ignored too, since they'll never even be able to see the
* index being worked on.
*
* We can also exclude autovacuum processes and processes running manual
* lazy VACUUMs, because they won't be fazed by missing index entries
* either. (Manual ANALYZEs, however, can't be excluded because they
* might be within transactions that are going to do arbitrary operations
* later.) Processes running CREATE INDEX CONCURRENTLY or REINDEX CONCURRENTLY
* on indexes that are neither expressional nor partial are also safe to
* ignore, since we know that those processes won't examine any data
* outside the table they're indexing.
*
* Also, GetCurrentVirtualXIDs never reports our own vxid, so we need not
* check for that.
*
* If a process goes idle-in-transaction with xmin zero, we do not need to
* wait for it anymore, per the above argument. We do not have the
* infrastructure right now to stop waiting if that happens, but we can at
* least avoid the folly of waiting when it is idle at the time we would
* begin to wait. We do this by repeatedly rechecking the output of
* GetCurrentVirtualXIDs. If, during any iteration, a particular vxid
* doesn't show up in the output, we know we can forget about it.
*/
void
WaitForOlderSnapshots(TransactionId limitXmin, bool progress)
{
int n_old_snapshots;
int i;
VirtualTransactionId *old_snapshots;
old_snapshots = GetCurrentVirtualXIDs(limitXmin, true, false,
PROC_IS_AUTOVACUUM | PROC_IN_VACUUM
| PROC_IN_SAFE_IC,
&n_old_snapshots);
if (progress)
pgstat_progress_update_param(PROGRESS_WAITFOR_TOTAL, n_old_snapshots);
for (i = 0; i < n_old_snapshots; i++)
{
if (!VirtualTransactionIdIsValid(old_snapshots[i]))
continue; /* found uninteresting in previous cycle */
if (i > 0)
{
/* see if anything's changed ... */
VirtualTransactionId *newer_snapshots;
int n_newer_snapshots;
int j;
int k;
newer_snapshots = GetCurrentVirtualXIDs(limitXmin,
true, false,
PROC_IS_AUTOVACUUM | PROC_IN_VACUUM
| PROC_IN_SAFE_IC,
&n_newer_snapshots);
for (j = i; j < n_old_snapshots; j++)
{
if (!VirtualTransactionIdIsValid(old_snapshots[j]))
continue; /* found uninteresting in previous cycle */
for (k = 0; k < n_newer_snapshots; k++)
{
if (VirtualTransactionIdEquals(old_snapshots[j],
newer_snapshots[k]))
break;
}
if (k >= n_newer_snapshots) /* not there anymore */
SetInvalidVirtualTransactionId(old_snapshots[j]);
}
pfree(newer_snapshots);
}
if (VirtualTransactionIdIsValid(old_snapshots[i]))
{
/* If requested, publish who we're going to wait for. */
if (progress)
{
PGPROC *holder = ProcNumberGetProc(old_snapshots[i].procNumber);
if (holder)
pgstat_progress_update_param(PROGRESS_WAITFOR_CURRENT_PID,
holder->pid);
}
VirtualXactLock(old_snapshots[i], true);
}
if (progress)
pgstat_progress_update_param(PROGRESS_WAITFOR_DONE, i + 1);
}
}
/*
* DefineIndex
* Creates a new index.
*
* This function manages the current userid according to the needs of pg_dump.
* Recreating old-database catalog entries in new-database is fine, regardless
* of which users would have permission to recreate those entries now. That's
* just preservation of state. Running opaque expressions, like calling a
* function named in a catalog entry or evaluating a pg_node_tree in a catalog
* entry, as anyone other than the object owner, is not fine. To adhere to
* those principles and to remain fail-safe, use the table owner userid for
* most ACL checks. Use the original userid for ACL checks reached without
* traversing opaque expressions. (pg_dump can predict such ACL checks from
* catalogs.) Overall, this is a mess. Future DDL development should
* consider offering one DDL command for catalog setup and a separate DDL
* command for steps that run opaque expressions.
*
* 'pstate': ParseState struct (used only for error reports; pass NULL if
* not available)
* 'tableId': the OID of the table relation on which the index is to be
* created
* 'stmt': IndexStmt describing the properties of the new index.
* 'indexRelationId': normally InvalidOid, but during bootstrap can be
* nonzero to specify a preselected OID for the index.
* 'parentIndexId': the OID of the parent index; InvalidOid if not the child
* of a partitioned index.
* 'parentConstraintId': the OID of the parent constraint; InvalidOid if not
* the child of a constraint (only used when recursing)
* 'total_parts': total number of direct and indirect partitions of relation;
* pass -1 if not known or rel is not partitioned.
* 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
* 'check_rights': check for CREATE rights in namespace and tablespace. (This
* should be true except when ALTER is deleting/recreating an index.)
* 'check_not_in_use': check for table not already in use in current session.
* This should be true unless caller is holding the table open, in which
* case the caller had better have checked it earlier.
* 'skip_build': make the catalog entries but don't create the index files
* 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
*
* Returns the object address of the created index.
*/
ObjectAddress
DefineIndex(ParseState *pstate,
Oid tableId,
const IndexStmt *stmt,
Oid indexRelationId,
Oid parentIndexId,
Oid parentConstraintId,
int total_parts,
bool is_alter_table,
bool check_rights,
bool check_not_in_use,
bool skip_build,
bool quiet)
{
bool concurrent;
char *indexRelationName;
char *accessMethodName;
Oid *typeIds;
Oid *collationIds;
Oid *opclassIds;
Datum *opclassOptions;
Oid accessMethodId;
Oid namespaceId;
Oid tablespaceId;
Oid createdConstraintId = InvalidOid;
List *indexColNames;
List *allIndexParams;
Relation rel;
HeapTuple tuple;
Form_pg_am accessMethodForm;
const IndexAmRoutine *amRoutine;
bool amcanorder;
bool amissummarizing;
amoptions_function amoptions;
bool exclusion;
bool partitioned;
bool safe_index;
Datum reloptions;
int16 *coloptions;
IndexInfo *indexInfo;
uint16 flags;
uint16 constr_flags;
int numberOfAttributes;
int numberOfKeyAttributes;
TransactionId limitXmin;
ObjectAddress address;
LockRelId heaprelid;
LOCKTAG heaplocktag;
LOCKMODE lockmode;
Snapshot snapshot;
Oid root_save_userid;
int root_save_sec_context;
int root_save_nestlevel;
root_save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
/*
* Some callers need us to run with an empty default_tablespace; this is a
* necessary hack to be able to reproduce catalog state accurately when
* recreating indexes after table-rewriting ALTER TABLE.
*/
if (stmt->reset_default_tblspc)
(void) set_config_option("default_tablespace", "",
PGC_USERSET, PGC_S_SESSION,
GUC_ACTION_SAVE, true, 0, false);
/*
* Force non-concurrent build on temporary relations, even if CONCURRENTLY
* was requested. Other backends can't access a temporary relation, so
* there's no harm in grabbing a stronger lock, and a non-concurrent DROP
* is more efficient. Do this before any use of the concurrent option is
* done.
*/
if (stmt->concurrent && get_rel_persistence(tableId) != RELPERSISTENCE_TEMP)
concurrent = true;
else
concurrent = false;
/*
* Start progress report. If we're building a partition, this was already
* done.
*/
if (!OidIsValid(parentIndexId))
{
pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, tableId);
pgstat_progress_update_param(PROGRESS_CREATEIDX_COMMAND,
concurrent ?
PROGRESS_CREATEIDX_COMMAND_CREATE_CONCURRENTLY :
PROGRESS_CREATEIDX_COMMAND_CREATE);
}
/*
* No index OID to report yet
*/
pgstat_progress_update_param(PROGRESS_CREATEIDX_INDEX_OID,
InvalidOid);
/*
* count key attributes in index
*/
numberOfKeyAttributes = list_length(stmt->indexParams);
/*
* Calculate the new list of index columns including both key columns and
* INCLUDE columns. Later we can determine which of these are key
* columns, and which are just part of the INCLUDE list by checking the
* list position. A list item in a position less than ii_NumIndexKeyAttrs
* is part of the key columns, and anything equal to and over is part of
* the INCLUDE columns.
*/
allIndexParams = list_concat_copy(stmt->indexParams,
stmt->indexIncludingParams);
numberOfAttributes = list_length(allIndexParams);
if (numberOfKeyAttributes <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("must specify at least one column")));
if (numberOfAttributes > INDEX_MAX_KEYS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("cannot use more than %d columns in an index",
INDEX_MAX_KEYS)));
/*
* Only SELECT ... FOR UPDATE/SHARE are allowed while doing a standard
* index build; but for concurrent builds we allow INSERT/UPDATE/DELETE
* (but not VACUUM).
*
* NB: Caller is responsible for making sure that tableId refers to the
* relation on which the index should be built; except in bootstrap mode,
* this will typically require the caller to have already locked the
* relation. To avoid lock upgrade hazards, that lock should be at least
* as strong as the one we take here.
*
* NB: If the lock strength here ever changes, code that is run by
* parallel workers under the control of certain particular ambuild
* functions will need to be updated, too.
*/
lockmode = concurrent ? ShareUpdateExclusiveLock : ShareLock;
rel = table_open(tableId, lockmode);
/*
* Switch to the table owner's userid, so that any index functions are run
* as that user. Also lock down security-restricted operations. We
* already arranged to make GUC variable changes local to this command.
*/
GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
SetUserIdAndSecContext(rel->rd_rel->relowner,
root_save_sec_context | SECURITY_RESTRICTED_OPERATION);
namespaceId = RelationGetNamespace(rel);
/*
* It has exclusion constraint behavior if it's an EXCLUDE constraint or a
* temporal PRIMARY KEY/UNIQUE constraint
*/
exclusion = stmt->excludeOpNames || stmt->iswithoutoverlaps;
/* Ensure that it makes sense to index this kind of relation */
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on relation \"%s\"",
RelationGetRelationName(rel)),
errdetail_relkind_not_supported(rel->rd_rel->relkind)));
break;
}
/*
* Establish behavior for partitioned tables, and verify sanity of
* parameters.
*
* We do not build an actual index in this case; we only create a few
* catalog entries. The actual indexes are built by recursing for each
* partition.
*/
partitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
if (partitioned)
{
/*
* Note: we check 'stmt->concurrent' rather than 'concurrent', so that
* the error is thrown also for temporary tables. Seems better to be
* consistent, even though we could do it on temporary table because
* we're not actually doing it concurrently.
*/
if (stmt->concurrent)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create index on partitioned table \"%s\" concurrently",
RelationGetRelationName(rel))));
}
/*
* Don't try to CREATE INDEX on temp tables of other backends.
*/
if (RELATION_IS_OTHER_TEMP(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create indexes on temporary tables of other sessions")));
/*
* Unless our caller vouches for having checked this already, insist that
* the table not be in use by our own session, either. Otherwise we might
* fail to make entries in the new index (for instance, if an INSERT or
* UPDATE is in progress and has already made its list of target indexes).
*/
if (check_not_in_use)
CheckTableNotInUse(rel, "CREATE INDEX");
/*
* Verify we (still) have CREATE rights in the rel's namespace.
* (Presumably we did when the rel was created, but maybe not anymore.)
* Skip check if caller doesn't want it. Also skip check if
* bootstrapping, since permissions machinery may not be working yet.
*/
if (check_rights && !IsBootstrapProcessingMode())
{
AclResult aclresult;
aclresult = object_aclcheck(NamespaceRelationId, namespaceId, root_save_userid,
ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_SCHEMA,
get_namespace_name(namespaceId));
}
/*
* Select tablespace to use. If not specified, use default tablespace
* (which may in turn default to database's default).
*/
if (stmt->tableSpace)
{
tablespaceId = get_tablespace_oid(stmt->tableSpace, false);
if (partitioned && tablespaceId == MyDatabaseTableSpace)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot specify default tablespace for partitioned relations")));
}
else
{
tablespaceId = GetDefaultTablespace(rel->rd_rel->relpersistence,
partitioned);
/* note InvalidOid is OK in this case */
}
/* Check tablespace permissions */
if (check_rights &&
OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
{
AclResult aclresult;
aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, root_save_userid,
ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_TABLESPACE,
get_tablespace_name(tablespaceId));
}
/*
* Force shared indexes into the pg_global tablespace. This is a bit of a
* hack but seems simpler than marking them in the BKI commands. On the
* other hand, if it's not shared, don't allow it to be placed there.
*/
if (rel->rd_rel->relisshared)
tablespaceId = GLOBALTABLESPACE_OID;
else if (tablespaceId == GLOBALTABLESPACE_OID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("only shared relations can be placed in pg_global tablespace")));
/*
* Choose the index column names.
*/
indexColNames = ChooseIndexColumnNames(allIndexParams);
/*
* Select name for index if caller didn't specify
*/
indexRelationName = stmt->idxname;
if (indexRelationName == NULL)
indexRelationName = ChooseIndexName(RelationGetRelationName(rel),
namespaceId,
indexColNames,
stmt->excludeOpNames,
stmt->primary,
stmt->isconstraint);
/*
* look up the access method, verify it can handle the requested features
*/
accessMethodName = stmt->accessMethod;
tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName));
if (!HeapTupleIsValid(tuple))
{
/*
* Hack to provide more-or-less-transparent updating of old RTREE
* indexes to GiST: if RTREE is requested and not found, use GIST.
*/
if (strcmp(accessMethodName, "rtree") == 0)
{
ereport(NOTICE,
(errmsg("substituting access method \"gist\" for obsolete method \"rtree\"")));
accessMethodName = "gist";
tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName));
}
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("access method \"%s\" does not exist",
accessMethodName)));
}
accessMethodForm = (Form_pg_am) GETSTRUCT(tuple);
accessMethodId = accessMethodForm->oid;
amRoutine = GetIndexAmRoutine(accessMethodForm->amhandler);
pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
accessMethodId);
if (stmt->unique && !stmt->iswithoutoverlaps && !amRoutine->amcanunique)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support unique indexes",
accessMethodName)));
if (stmt->indexIncludingParams != NIL && !amRoutine->amcaninclude)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support included columns",
accessMethodName)));
if (numberOfKeyAttributes > 1 && !amRoutine->amcanmulticol)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support multicolumn indexes",
accessMethodName)));
if (exclusion && amRoutine->amgettuple == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support exclusion constraints",
accessMethodName)));
if (stmt->iswithoutoverlaps && strcmp(accessMethodName, "gist") != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support WITHOUT OVERLAPS constraints",
accessMethodName)));
amcanorder = amRoutine->amcanorder;
amoptions = amRoutine->amoptions;
amissummarizing = amRoutine->amsummarizing;
ReleaseSysCache(tuple);
/*
* Validate predicate, if given
*/
if (stmt->whereClause)
CheckPredicate((Expr *) stmt->whereClause);
/*
* Parse AM-specific options, convert to text array form, validate.
*/
reloptions = transformRelOptions((Datum) 0, stmt->options,
NULL, NULL, false, false);
(void) index_reloptions(amoptions, reloptions, true);
/*
* Prepare arguments for index_create, primarily an IndexInfo structure.
* Note that predicates must be in implicit-AND format. In a concurrent
* build, mark it not-ready-for-inserts.
*/
indexInfo = makeIndexInfo(numberOfAttributes,
numberOfKeyAttributes,
accessMethodId,
NIL, /* expressions, NIL for now */
make_ands_implicit((Expr *) stmt->whereClause),
stmt->unique,
stmt->nulls_not_distinct,
!concurrent,
concurrent,
amissummarizing,
stmt->iswithoutoverlaps);
indexInfo->ii_PredicateExpand =
ExpandVirtualGeneratedColumns(indexInfo->ii_PredicateExpand, rel, InvalidOid);
typeIds = palloc_array(Oid, numberOfAttributes);
collationIds = palloc_array(Oid, numberOfAttributes);
opclassIds = palloc_array(Oid, numberOfAttributes);
opclassOptions = palloc_array(Datum, numberOfAttributes);
coloptions = palloc_array(int16, numberOfAttributes);
ComputeIndexAttrs(pstate,
indexInfo,
typeIds, collationIds, opclassIds, opclassOptions,
coloptions, allIndexParams,
stmt->excludeOpNames, tableId,
accessMethodName, accessMethodId,
amcanorder, stmt->isconstraint, stmt->iswithoutoverlaps,
root_save_userid, root_save_sec_context,
&root_save_nestlevel);
indexInfo->ii_ExpressionsExpand =
ExpandVirtualGeneratedColumns(indexInfo->ii_ExpressionsExpand, rel, InvalidOid);
/*
* Extra checks when creating a PRIMARY KEY index.
*/
if (stmt->primary)
index_check_primary_key(rel, indexInfo, is_alter_table, stmt);
/*
* If this table is partitioned and we're creating a unique index, primary
* key, or exclusion constraint, make sure that the partition key is a
* subset of the index's columns. Otherwise it would be possible to
* violate uniqueness by putting values that ought to be unique in
* different partitions.
*
* We could lift this limitation if we had global indexes, but those have
* their own problems, so this is a useful feature combination.
*/
if (partitioned && (stmt->unique || exclusion))
{
PartitionKey key = RelationGetPartitionKey(rel);
const char *constraint_type;
int i;
if (stmt->primary)
constraint_type = "PRIMARY KEY";
else if (stmt->unique)
constraint_type = "UNIQUE";
else if (stmt->excludeOpNames)
constraint_type = "EXCLUDE";
else
{
elog(ERROR, "unknown constraint type");
constraint_type = NULL; /* keep compiler quiet */
}
/*
* Verify that all the columns in the partition key appear in the
* unique key definition, with the same notion of equality.
*/
for (i = 0; i < key->partnatts; i++)
{
bool found = false;
int eq_strategy;
Oid ptkey_eqop;
int j;
/*
* Identify the equality operator associated with this partkey
* column. For list and range partitioning, partkeys use btree
* operator classes; hash partitioning uses hash operator classes.
* (Keep this in sync with ComputePartitionAttrs!)
*/
if (key->strategy == PARTITION_STRATEGY_HASH)
eq_strategy = HTEqualStrategyNumber;
else
eq_strategy = BTEqualStrategyNumber;
ptkey_eqop = get_opfamily_member(key->partopfamily[i],
key->partopcintype[i],
key->partopcintype[i],
eq_strategy);
if (!OidIsValid(ptkey_eqop))
elog(ERROR, "missing operator %d(%u,%u) in partition opfamily %u",
eq_strategy, key->partopcintype[i], key->partopcintype[i],
key->partopfamily[i]);
/*
* It may be possible to support UNIQUE constraints when partition
* keys are expressions, but is it worth it? Give up for now.
*/
if (key->partattrs[i] == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported %s constraint with partition key definition",
constraint_type),
errdetail("%s constraints cannot be used when partition keys include expressions.",
constraint_type)));
/* Search the index column(s) for a match */
for (j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
{
if (key->partattrs[i] == indexInfo->ii_IndexAttrNumbers[j])
{
/*
* Matched the column, now what about the collation and
* equality op?
*/
Oid idx_opfamily;
Oid idx_opcintype;
if (key->partcollation[i] != collationIds[j])
continue;
if (get_opclass_opfamily_and_input_type(opclassIds[j],
&idx_opfamily,
&idx_opcintype))
{
Oid idx_eqop = InvalidOid;
if (stmt->unique && !stmt->iswithoutoverlaps)
idx_eqop = get_opfamily_member_for_cmptype(idx_opfamily,
idx_opcintype,
idx_opcintype,
COMPARE_EQ);
else if (exclusion)
idx_eqop = indexInfo->ii_ExclusionOps[j];
if (!idx_eqop)
ereport(ERROR,
errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("could not identify an equality operator for type %s", format_type_be(idx_opcintype)),
errdetail("There is no suitable operator in operator family \"%s\" for access method \"%s\".",
get_opfamily_name(idx_opfamily, false), get_am_name(get_opfamily_method(idx_opfamily))));
if (ptkey_eqop == idx_eqop)
{
found = true;
break;
}
else if (exclusion)
{
/*
* We found a match, but it's not an equality
* operator. Instead of failing below with an
* error message about a missing column, fail now
* and explain that the operator is wrong.
*/
Form_pg_attribute att = TupleDescAttr(RelationGetDescr(rel), key->partattrs[i] - 1);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot match partition key to index on column \"%s\" using non-equal operator \"%s\"",
NameStr(att->attname),
get_opname(indexInfo->ii_ExclusionOps[j]))));
}
}
}
}
if (!found)
{
Form_pg_attribute att;
att = TupleDescAttr(RelationGetDescr(rel),
key->partattrs[i] - 1);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is UNIQUE, PRIMARY KEY, etc */
errmsg("%s constraint on partitioned table must include all partitioning columns",
constraint_type),
/* translator: first %s is UNIQUE, PRIMARY KEY, etc */
errdetail("%s constraint on table \"%s\" lacks column \"%s\" which is part of the partition key.",
constraint_type, RelationGetRelationName(rel),
NameStr(att->attname))));
}
}
}
/*
* We disallow indexes on system columns. They would not necessarily get
* updated correctly, and they don't seem useful anyway.
*
* Aisallow virtual generated columns in indexes (include using expression
* index instead).
*/
for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
AttrNumber attno = indexInfo->ii_IndexAttrNumbers[i];
if (attno < 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
/*if (attno > 0 && i < numberOfKeyAttributes &&
TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
stmt->primary ?
errmsg("primary keys on virtual generated columns are not supported") :
stmt->isconstraint ?
errmsg("unique constraints on virtual generated columns are not supported") :
errmsg("indexes on virtual generated columns are not supported"));*/
}
/*
* Also check for system columns used in expressions or predicates.
*/
if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
{
Bitmapset *indexattrs = NULL;
pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
for (int i = FirstLowInvalidHeapAttributeNumber + 1; i < 0; i++)
{
if (bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
indexattrs))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
}
}
/* Is index safe for others to ignore? See set_indexsafe_procflags() */
safe_index = indexInfo->ii_Expressions == NIL &&
indexInfo->ii_Predicate == NIL;
/*
* Report index creation if appropriate (delay this till after most of the
* error checks)
*/
if (stmt->isconstraint && !quiet)
{
const char *constraint_type;
if (stmt->primary)
constraint_type = "PRIMARY KEY";
else if (stmt->unique)
constraint_type = "UNIQUE";
else if (stmt->excludeOpNames)
constraint_type = "EXCLUDE";
else
{
elog(ERROR, "unknown constraint type");
constraint_type = NULL; /* keep compiler quiet */
}
ereport(DEBUG1,
(errmsg_internal("%s %s will create implicit index \"%s\" for table \"%s\"",
is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
constraint_type,
indexRelationName, RelationGetRelationName(rel))));
}
/*
* A valid stmt->oldNumber implies that we already have a built form of
* the index. The caller should also decline any index build.
*/
Assert(!RelFileNumberIsValid(stmt->oldNumber) || (skip_build && !concurrent));
/*
* Make the catalog entries for the index, including constraints. This
* step also actually builds the index, except if caller requested not to
* or in concurrent mode, in which case it'll be done later, or doing a
* partitioned index (because those don't have storage).
*/
flags = constr_flags = 0;
if (stmt->isconstraint)
flags |= INDEX_CREATE_ADD_CONSTRAINT;
if (skip_build || concurrent || partitioned)
flags |= INDEX_CREATE_SKIP_BUILD;
if (stmt->if_not_exists)
flags |= INDEX_CREATE_IF_NOT_EXISTS;
if (concurrent)
flags |= INDEX_CREATE_CONCURRENT;
if (partitioned)
flags |= INDEX_CREATE_PARTITIONED;
if (stmt->primary)
flags |= INDEX_CREATE_IS_PRIMARY;
/*
* If the table is partitioned, and recursion was declined but partitions
* exist, mark the index as invalid.
*/
if (partitioned && stmt->relation && !stmt->relation->inh)
{
PartitionDesc pd = RelationGetPartitionDesc(rel, true);
if (pd->nparts != 0)
flags |= INDEX_CREATE_INVALID;
}
if (stmt->deferrable)
constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;
if (stmt->initdeferred)
constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;
if (stmt->iswithoutoverlaps)
constr_flags |= INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS;
indexRelationId =
index_create(rel, indexRelationName, indexRelationId, parentIndexId,
parentConstraintId,
stmt->oldNumber, indexInfo, indexColNames,
accessMethodId, tablespaceId,
collationIds, opclassIds, opclassOptions,
coloptions, NULL, reloptions,
flags, constr_flags,
allowSystemTableMods, !check_rights,
&createdConstraintId);
ObjectAddressSet(address, RelationRelationId, indexRelationId);
if (!OidIsValid(indexRelationId))
{
/*
* Roll back any GUC changes executed by index functions. Also revert
* to original default_tablespace if we changed it above.
*/
AtEOXact_GUC(false, root_save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(root_save_userid, root_save_sec_context);
table_close(rel, NoLock);
/* If this is the top-level index, we're done */
if (!OidIsValid(parentIndexId))
pgstat_progress_end_command();
return address;
}
/*
* Roll back any GUC changes executed by index functions, and keep
* subsequent changes local to this command. This is essential if some
* index function changed a behavior-affecting GUC, e.g. search_path.
*/
AtEOXact_GUC(false, root_save_nestlevel);
root_save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
/* Add any requested comment */
if (stmt->idxcomment != NULL)
CreateComments(indexRelationId, RelationRelationId, 0,
stmt->idxcomment);
if (partitioned)
{
PartitionDesc partdesc;
/*
* Unless caller specified to skip this step (via ONLY), process each
* partition to make sure they all contain a corresponding index.
*
* If we're called internally (no stmt->relation), recurse always.
*/
partdesc = RelationGetPartitionDesc(rel, true);
if ((!stmt->relation || stmt->relation->inh) && partdesc->nparts > 0)
{
int nparts = partdesc->nparts;
Oid *part_oids = palloc_array(Oid, nparts);
bool invalidate_parent = false;
Relation parentIndex;
TupleDesc parentDesc;
/*
* Report the total number of partitions at the start of the
* command; don't update it when being called recursively.
*/
if (!OidIsValid(parentIndexId))
{
/*
* When called by ProcessUtilitySlow, the number of partitions
* is passed in as an optimization; but other callers pass -1
* since they don't have the value handy. This should count
* partitions the same way, ie one less than the number of
* relations find_all_inheritors reports.
*
* We assume we needn't ask find_all_inheritors to take locks,
* because that should have happened already for all callers.
* Even if it did not, this is safe as long as we don't try to
* touch the partitions here; the worst consequence would be a
* bogus progress-reporting total.
*/
if (total_parts < 0)
{
List *children = find_all_inheritors(tableId, NoLock, NULL);
total_parts = list_length(children) - 1;
list_free(children);
}
pgstat_progress_update_param(PROGRESS_CREATEIDX_PARTITIONS_TOTAL,
total_parts);
}
/* Make a local copy of partdesc->oids[], just for safety */
memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
/*
* We'll need an IndexInfo describing the parent index. The one
* built above is almost good enough, but not quite, because (for
* example) its predicate expression if any hasn't been through
* expression preprocessing. The most reliable way to get an
* IndexInfo that will match those for child indexes is to build
* it the same way, using BuildIndexInfo().
*/
parentIndex = index_open(indexRelationId, lockmode);
indexInfo = BuildIndexInfo(parentIndex);
parentDesc = RelationGetDescr(rel);
/*
* For each partition, scan all existing indexes; if one matches
* our index definition and is not already attached to some other
* parent index, attach it to the one we just created.
*
* If none matches, build a new index by calling ourselves
* recursively with the same options (except for the index name).
*/
for (int i = 0; i < nparts; i++)
{
Oid childRelid = part_oids[i];
Relation childrel;
Oid child_save_userid;
int child_save_sec_context;
int child_save_nestlevel;
List *childidxs;
ListCell *cell;
AttrMap *attmap;
bool found = false;
childrel = table_open(childRelid, lockmode);
GetUserIdAndSecContext(&child_save_userid,
&child_save_sec_context);
SetUserIdAndSecContext(childrel->rd_rel->relowner,
child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
child_save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
/*
* Don't try to create indexes on foreign tables, though. Skip
* those if a regular index, or fail if trying to create a
* constraint index.
*/
if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
{
if (stmt->unique || stmt->primary)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create unique index on partitioned table \"%s\"",
RelationGetRelationName(rel)),
errdetail("Table \"%s\" contains partitions that are foreign tables.",
RelationGetRelationName(rel))));
AtEOXact_GUC(false, child_save_nestlevel);
SetUserIdAndSecContext(child_save_userid,
child_save_sec_context);
table_close(childrel, lockmode);
continue;
}
childidxs = RelationGetIndexList(childrel);
attmap =
build_attrmap_by_name(RelationGetDescr(childrel),
parentDesc,
false);
foreach(cell, childidxs)
{
Oid cldidxid = lfirst_oid(cell);
Relation cldidx;
IndexInfo *cldIdxInfo;
/* this index is already partition of another one */
if (has_superclass(cldidxid))
continue;
cldidx = index_open(cldidxid, lockmode);
cldIdxInfo = BuildIndexInfo(cldidx);
if (CompareIndexInfo(cldIdxInfo, indexInfo,
cldidx->rd_indcollation,
parentIndex->rd_indcollation,
cldidx->rd_opfamily,
parentIndex->rd_opfamily,
attmap))
{
Oid cldConstrOid = InvalidOid;
/*
* Found a match.
*
* If this index is being created in the parent
* because of a constraint, then the child needs to
* have a constraint also, so look for one. If there
* is no such constraint, this index is no good, so
* keep looking.
*/
if (createdConstraintId != InvalidOid)
{
cldConstrOid =
get_relation_idx_constraint_oid(childRelid,
cldidxid);
if (cldConstrOid == InvalidOid)
{
index_close(cldidx, lockmode);
continue;
}
}
/* Attach index to parent and we're done. */
IndexSetParentIndex(cldidx, indexRelationId);
if (createdConstraintId != InvalidOid)
ConstraintSetParentConstraint(cldConstrOid,
createdConstraintId,
childRelid);
if (!cldidx->rd_index->indisvalid)
invalidate_parent = true;
found = true;
/*
* Report this partition as processed. Note that if
* the partition has children itself, we'd ideally
* count the children and update the progress report
* for all of them; but that seems unduly expensive.
* Instead, the progress report will act like all such
* indirect children were processed in zero time at
* the end of the command.
*/
pgstat_progress_incr_param(PROGRESS_CREATEIDX_PARTITIONS_DONE, 1);
/* keep lock till commit */
index_close(cldidx, NoLock);
break;
}
index_close(cldidx, lockmode);
}
list_free(childidxs);
AtEOXact_GUC(false, child_save_nestlevel);
SetUserIdAndSecContext(child_save_userid,
child_save_sec_context);
table_close(childrel, NoLock);
/*
* If no matching index was found, create our own.
*/
if (!found)
{
IndexStmt *childStmt;
ObjectAddress childAddr;
/*
* Build an IndexStmt describing the desired child index
* in the same way that we do during ATTACH PARTITION.
* Notably, we rely on generateClonedIndexStmt to produce
* a search-path-independent representation, which the
* original IndexStmt might not be.
*/
childStmt = generateClonedIndexStmt(NULL,
parentIndex,
attmap,
NULL);
/*
* Recurse as the starting user ID. Callee will use that
* for permission checks, then switch again.
*/
Assert(GetUserId() == child_save_userid);
SetUserIdAndSecContext(root_save_userid,
root_save_sec_context);
childAddr =
DefineIndex(NULL, /* original pstate not applicable */
childRelid, childStmt,
InvalidOid, /* no predefined OID */
indexRelationId, /* this is our child */
createdConstraintId,
-1,
is_alter_table, check_rights,
check_not_in_use,
skip_build, quiet);
SetUserIdAndSecContext(child_save_userid,
child_save_sec_context);
/*
* Check if the index just created is valid or not, as it
* could be possible that it has been switched as invalid
* when recursing across multiple partition levels.
*/
if (!get_index_isvalid(childAddr.objectId))
invalidate_parent = true;
}
free_attrmap(attmap);
}
index_close(parentIndex, lockmode);
/*
* The pg_index row we inserted for this index was marked
* indisvalid=true. But if we attached an existing index that is
* invalid, this is incorrect, so update our row to invalid too.
*/
if (invalidate_parent)
{
Relation pg_index = table_open(IndexRelationId, RowExclusiveLock);
HeapTuple tup,
newtup;
tup = SearchSysCache1(INDEXRELID,
ObjectIdGetDatum(indexRelationId));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for index %u",
indexRelationId);
newtup = heap_copytuple(tup);
((Form_pg_index) GETSTRUCT(newtup))->indisvalid = false;
CatalogTupleUpdate(pg_index, &tup->t_self, newtup);
ReleaseSysCache(tup);
table_close(pg_index, RowExclusiveLock);
heap_freetuple(newtup);
/*
* CCI here to make this update visible, in case this recurses
* across multiple partition levels.
*/
CommandCounterIncrement();
}
}
/*
* Indexes on partitioned tables are not themselves built, so we're
* done here.
*/
AtEOXact_GUC(false, root_save_nestlevel);
SetUserIdAndSecContext(root_save_userid, root_save_sec_context);
table_close(rel, NoLock);
if (!OidIsValid(parentIndexId))
pgstat_progress_end_command();
else
{
/* Update progress for an intermediate partitioned index itself */
pgstat_progress_incr_param(PROGRESS_CREATEIDX_PARTITIONS_DONE, 1);
}
return address;
}
AtEOXact_GUC(false, root_save_nestlevel);
SetUserIdAndSecContext(root_save_userid, root_save_sec_context);
if (!concurrent)
{
/* Close the heap and we're done, in the non-concurrent case */
table_close(rel, NoLock);
/*
* If this is the top-level index, the command is done overall;
* otherwise, increment progress to report one child index is done.
*/
if (!OidIsValid(parentIndexId))
pgstat_progress_end_command();
else
pgstat_progress_incr_param(PROGRESS_CREATEIDX_PARTITIONS_DONE, 1);
return address;
}
/* save lockrelid and locktag for below, then close rel */
heaprelid = rel->rd_lockInfo.lockRelId;
SET_LOCKTAG_RELATION(heaplocktag, heaprelid.dbId, heaprelid.relId);
table_close(rel, NoLock);
/*
* For a concurrent build, it's important to make the catalog entries
* visible to other transactions before we start to build the index. That
* will prevent them from making incompatible HOT updates. The new index
* will be marked not indisready and not indisvalid, so that no one else
* tries to either insert into it or use it for queries.
*
* We must commit our current transaction so that the index becomes
* visible; then start another. Note that all the data structures we just
* built are lost in the commit. The only data we keep past here are the
* relation IDs.
*
* Before committing, get a session-level lock on the table, to ensure
* that neither it nor the index can be dropped before we finish. This
* cannot block, even if someone else is waiting for access, because we
* already have the same lock within our transaction.
*
* Note: we don't currently bother with a session lock on the index,
* because there are no operations that could change its state while we
* hold lock on the parent table. This might need to change later.
*/
LockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
/* Tell concurrent index builds to ignore us, if index qualifies */
if (safe_index)
set_indexsafe_procflags();
/*
* The index is now visible, so we can report the OID. While on it,
* include the report for the beginning of phase 2.
*/
{
const int progress_cols[] = {
PROGRESS_CREATEIDX_INDEX_OID,
PROGRESS_CREATEIDX_PHASE
};
const int64 progress_vals[] = {
indexRelationId,
PROGRESS_CREATEIDX_PHASE_WAIT_1
};
pgstat_progress_update_multi_param(2, progress_cols, progress_vals);
}
/*
* Phase 2 of concurrent index build (see comments for validate_index()
* for an overview of how this works)
*
* Now we must wait until no running transaction could have the table open
* with the old list of indexes. Use ShareLock to consider running
* transactions that hold locks that permit writing to the table. Note we
* do not need to worry about xacts that open the table for writing after
* this point; they will see the new index when they open it.
*
* Note: the reason we use actual lock acquisition here, rather than just
* checking the ProcArray and sleeping, is that deadlock is possible if
* one of the transactions in question is blocked trying to acquire an
* exclusive lock on our table. The lock code will detect deadlock and
* error out properly.
*/
WaitForLockers(heaplocktag, ShareLock, true);
/*
* At this moment we are sure that there are no transactions with the
* table open for write that don't have this new index in their list of
* indexes. We have waited out all the existing transactions and any new
* transaction will have the new index in its list, but the index is still
* marked as "not-ready-for-inserts". The index is consulted while
* deciding HOT-safety though. This arrangement ensures that no new HOT
* chains can be created where the new tuple and the old tuple in the
* chain have different index keys.
*
* We now take a new snapshot, and build the index using all tuples that
* are visible in this snapshot. We can be sure that any HOT updates to
* these tuples will be compatible with the index, since any updates made
* by transactions that didn't know about the index are now committed or
* rolled back. Thus, each visible tuple is either the end of its
* HOT-chain or the extension of the chain is HOT-safe for this index.
*/
/* Set ActiveSnapshot since functions in the indexes may need it */
PushActiveSnapshot(GetTransactionSnapshot());
/* Perform concurrent build of index */
index_concurrently_build(tableId, indexRelationId);
/* we can do away with our snapshot */
PopActiveSnapshot();
/*
* Commit this transaction to make the indisready update visible.
*/
CommitTransactionCommand();
StartTransactionCommand();
/* Tell concurrent index builds to ignore us, if index qualifies */
if (safe_index)
set_indexsafe_procflags();
/*
* Phase 3 of concurrent index build
*
* We once again wait until no transaction can have the table open with
* the index marked as read-only for updates.
*/
pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_PHASE_WAIT_2);
WaitForLockers(heaplocktag, ShareLock, true);
/*
* Now take the "reference snapshot" that will be used by validate_index()
* to filter candidate tuples. Beware! There might still be snapshots in
* use that treat some transaction as in-progress that our reference
* snapshot treats as committed. If such a recently-committed transaction
* deleted tuples in the table, we will not include them in the index; yet
* those transactions which see the deleting one as still-in-progress will
* expect such tuples to be there once we mark the index as valid.
*
* We solve this by waiting for all endangered transactions to exit before
* we mark the index as valid.
*
* We also set ActiveSnapshot to this snap, since functions in indexes may
* need a snapshot.
*/
snapshot = RegisterSnapshot(GetTransactionSnapshot());
PushActiveSnapshot(snapshot);
/*
* Scan the index and the heap, insert any missing index entries.
*/
validate_index(tableId, indexRelationId, snapshot);
/*
* Drop the reference snapshot. We must do this before waiting out other
* snapshot holders, else we will deadlock against other processes also
* doing CREATE INDEX CONCURRENTLY, which would see our snapshot as one
* they must wait for. But first, save the snapshot's xmin to use as
* limitXmin for GetCurrentVirtualXIDs().
*/
limitXmin = snapshot->xmin;
PopActiveSnapshot();
UnregisterSnapshot(snapshot);
/*
* The snapshot subsystem could still contain registered snapshots that
* are holding back our process's advertised xmin; in particular, if
* default_transaction_isolation = serializable, there is a transaction
* snapshot that is still active. The CatalogSnapshot is likewise a
* hazard. To ensure no deadlocks, we must commit and start yet another
* transaction, and do our wait before any snapshot has been taken in it.
*/
CommitTransactionCommand();
StartTransactionCommand();
/* Tell concurrent index builds to ignore us, if index qualifies */
if (safe_index)
set_indexsafe_procflags();
/* We should now definitely not be advertising any xmin. */
Assert(MyProc->xmin == InvalidTransactionId);
/*
* The index is now valid in the sense that it contains all currently
* interesting tuples. But since it might not contain tuples deleted just
* before the reference snap was taken, we have to wait out any
* transactions that might have older snapshots.
*/
INJECTION_POINT("define-index-before-set-valid", NULL);
pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_PHASE_WAIT_3);
WaitForOlderSnapshots(limitXmin, true);
/*
* Updating pg_index might involve TOAST table access, so ensure we have a
* valid snapshot.
*/
PushActiveSnapshot(GetTransactionSnapshot());
/*
* Index can now be marked valid -- update its pg_index entry
*/
index_set_state_flags(indexRelationId, INDEX_CREATE_SET_VALID);
PopActiveSnapshot();
/*
* The pg_index update will cause backends (including this one) to update
* relcache entries for the index itself, but we should also send a
* relcache inval on the parent table to force replanning of cached plans.
* Otherwise existing sessions might fail to use the new index where it
* would be useful. (Note that our earlier commits did not create reasons
* to replan; so relcache flush on the index itself was sufficient.)
*/
CacheInvalidateRelcacheByRelid(heaprelid.relId);
/*
* Last thing to do is release the session-level lock on the parent table.
*/
UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
pgstat_progress_end_command();
return address;
}
/*
* CheckPredicate
* Checks that the given partial-index predicate is valid.
*
* This used to also constrain the form of the predicate to forms that
* indxpath.c could do something with. However, that seems overly
* restrictive. One useful application of partial indexes is to apply
* a UNIQUE constraint across a subset of a table, and in that scenario
* any evaluable predicate will work. So accept any predicate here
* (except ones requiring a plan), and let indxpath.c fend for itself.
*/
static void
CheckPredicate(Expr *predicate)
{
/*
* transformExpr() should have already rejected subqueries, aggregates,
* and window functions, based on the EXPR_KIND_ for a predicate.
*/
/*
* A predicate using mutable functions is probably wrong, for the same
* reasons that we don't allow an index expression to use one.
*/
if (contain_mutable_functions_after_planning(predicate))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("functions in index predicate must be marked IMMUTABLE")));
}
/*
* Compute per-index-column information, including indexed column numbers
* or index expressions, opclasses and their options. Note, all output vectors
* should be allocated for all columns, including "including" ones.
*
* If the caller switched to the table owner, ddl_userid is the role for ACL
* checks reached without traversing opaque expressions. Otherwise, it's
* InvalidOid, and other ddl_* arguments are undefined.
*
* Upon returning from this function, callers must apply
* ExpandVirtualGeneratedColumns() to ii_ExpressionsExpand
* when necessary for actual expansion if ii_ExpressionsExpand
* is not NIL or dummy.
*/
static void
ComputeIndexAttrs(ParseState *pstate,
IndexInfo *indexInfo,
Oid *typeOids,
Oid *collationOids,
Oid *opclassOids,
Datum *opclassOptions,
int16 *colOptions,
const List *attList, /* list of IndexElem's */
const List *exclusionOpNames,
Oid relId,
const char *accessMethodName,
Oid accessMethodId,
bool amcanorder,
bool isconstraint,
bool iswithoutoverlaps,
Oid ddl_userid,
int ddl_sec_context,
int *ddl_save_nestlevel)
{
ListCell *nextExclOp;
ListCell *lc;
int attn;
int nkeycols = indexInfo->ii_NumIndexKeyAttrs;
Oid save_userid;
int save_sec_context;
/* Allocate space for exclusion operator info, if needed */
if (exclusionOpNames)
{
Assert(list_length(exclusionOpNames) == nkeycols);
indexInfo->ii_ExclusionOps = palloc_array(Oid, nkeycols);
indexInfo->ii_ExclusionProcs = palloc_array(Oid, nkeycols);
indexInfo->ii_ExclusionStrats = palloc_array(uint16, nkeycols);
nextExclOp = list_head(exclusionOpNames);
}
else
nextExclOp = NULL;
/*
* If this is a WITHOUT OVERLAPS constraint, we need space for exclusion
* ops, but we don't need to parse anything, so we can let nextExclOp be
* NULL. Note that for partitions/inheriting/LIKE, exclusionOpNames will
* be set, so we already allocated above.
*/
if (iswithoutoverlaps)
{
if (exclusionOpNames == NIL)
{
indexInfo->ii_ExclusionOps = palloc_array(Oid, nkeycols);
indexInfo->ii_ExclusionProcs = palloc_array(Oid, nkeycols);
indexInfo->ii_ExclusionStrats = palloc_array(uint16, nkeycols);
}
nextExclOp = NULL;
}
if (OidIsValid(ddl_userid))
GetUserIdAndSecContext(&save_userid, &save_sec_context);
/*
* process attributeList
*/
attn = 0;
foreach(lc, attList)
{
IndexElem *attribute = (IndexElem *) lfirst(lc);
Oid atttype;
Oid attcollation;
/*
* Process the column-or-expression to be indexed.
*/
if (attribute->name != NULL)
{
/* Simple index attribute */
HeapTuple atttuple;
Form_pg_attribute attform;
Assert(attribute->expr == NULL);
atttuple = SearchSysCacheAttName(relId, attribute->name);
if (!HeapTupleIsValid(atttuple))
{
/* difference in error message spellings is historical */
if (isconstraint)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" named in key does not exist",
attribute->name),
parser_errposition(pstate, attribute->location)));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist",
attribute->name),
parser_errposition(pstate, attribute->location)));
}
attform = (Form_pg_attribute) GETSTRUCT(atttuple);
indexInfo->ii_IndexAttrNumbers[attn] = attform->attnum;
atttype = attform->atttypid;
attcollation = attform->attcollation;
ReleaseSysCache(atttuple);
}
else
{
/* Index expression */
Node *expr = attribute->expr;
Assert(expr != NULL);
if (attn >= nkeycols)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("expressions are not supported in included columns"),
parser_errposition(pstate, attribute->location)));
atttype = exprType(expr);
attcollation = exprCollation(expr);
/*
* Strip any top-level COLLATE clause. This ensures that we treat
* "x COLLATE y" and "(x COLLATE y)" alike.
*/
while (IsA(expr, CollateExpr))
expr = (Node *) ((CollateExpr *) expr)->arg;
if (IsA(expr, Var) &&
((Var *) expr)->varattno != InvalidAttrNumber)
{
/*
* User wrote "(column)" or "(column COLLATE something)".
* Treat it like simple attribute anyway.
*/
indexInfo->ii_IndexAttrNumbers[attn] = ((Var *) expr)->varattno;
}
else
{
indexInfo->ii_IndexAttrNumbers[attn] = 0; /* marks expression */
indexInfo->ii_Expressions = lappend(indexInfo->ii_Expressions,
expr);
/*
* transformExpr() should have already rejected subqueries,
* aggregates, and window functions, based on the EXPR_KIND_
* for an index expression.
*/
/*
* An expression using mutable functions is probably wrong,
* since if you aren't going to get the same result for the
* same data every time, it's not clear what the index entries
* mean at all.
*/
if (contain_mutable_functions_after_planning((Expr *) expr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("functions in index expression must be marked IMMUTABLE"),
parser_errposition(pstate, attribute->location)));
}
}
typeOids[attn] = atttype;
/*
* Included columns have no collation, no opclass and no ordering
* options.
*/
if (attn >= nkeycols)
{
if (attribute->collation)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("including column does not support a collation"),
parser_errposition(pstate, attribute->location)));
if (attribute->opclass)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("including column does not support an operator class"),
parser_errposition(pstate, attribute->location)));
if (attribute->ordering != SORTBY_DEFAULT)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("including column does not support ASC/DESC options"),
parser_errposition(pstate, attribute->location)));
if (attribute->nulls_ordering != SORTBY_NULLS_DEFAULT)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("including column does not support NULLS FIRST/LAST options"),
parser_errposition(pstate, attribute->location)));
opclassOids[attn] = InvalidOid;
opclassOptions[attn] = (Datum) 0;
colOptions[attn] = 0;
collationOids[attn] = InvalidOid;
attn++;
continue;
}
/*
* Apply collation override if any. Use of ddl_userid is necessary
* due to ACL checks therein, and it's safe because collations don't
* contain opaque expressions (or non-opaque expressions).
*/
if (attribute->collation)
{
if (OidIsValid(ddl_userid))
{
AtEOXact_GUC(false, *ddl_save_nestlevel);
SetUserIdAndSecContext(ddl_userid, ddl_sec_context);
}
attcollation = get_collation_oid(attribute->collation, false);
if (OidIsValid(ddl_userid))
{
SetUserIdAndSecContext(save_userid, save_sec_context);
*ddl_save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
}
}
/*
* Check we have a collation iff it's a collatable type. The only
* expected failures here are (1) COLLATE applied to a noncollatable
* type, or (2) index expression had an unresolved collation. But we
* might as well code this to be a complete consistency check.
*/
if (type_is_collatable(atttype))
{
if (!OidIsValid(attcollation))
ereport(ERROR,
(errcode(ERRCODE_INDETERMINATE_COLLATION),
errmsg("could not determine which collation to use for index expression"),
errhint("Use the COLLATE clause to set the collation explicitly."),
parser_errposition(pstate, attribute->location)));
}
else
{
if (OidIsValid(attcollation))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("collations are not supported by type %s",
format_type_be(atttype)),
parser_errposition(pstate, attribute->location)));
}
collationOids[attn] = attcollation;
/*
* Identify the opclass to use. Use of ddl_userid is necessary due to
* ACL checks therein. This is safe despite opclasses containing
* opaque expressions (specifically, functions), because only
* superusers can define opclasses.
*/
if (OidIsValid(ddl_userid))
{
AtEOXact_GUC(false, *ddl_save_nestlevel);
SetUserIdAndSecContext(ddl_userid, ddl_sec_context);
}
opclassOids[attn] = ResolveOpClass(attribute->opclass,
atttype,
accessMethodName,
accessMethodId);
if (OidIsValid(ddl_userid))
{
SetUserIdAndSecContext(save_userid, save_sec_context);
*ddl_save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
}
/*
* Identify the exclusion operator, if any.
*/
if (nextExclOp)
{
List *opname = (List *) lfirst(nextExclOp);
Oid opid;
Oid opfamily;
int strat;
/*
* Find the operator --- it must accept the column datatype
* without runtime coercion (but binary compatibility is OK).
* Operators contain opaque expressions (specifically, functions).
* compatible_oper_opid() boils down to oper() and
* IsBinaryCoercible(). PostgreSQL would have security problems
* elsewhere if oper() started calling opaque expressions.
*/
if (OidIsValid(ddl_userid))
{
AtEOXact_GUC(false, *ddl_save_nestlevel);
SetUserIdAndSecContext(ddl_userid, ddl_sec_context);
}
opid = compatible_oper_opid(opname, atttype, atttype, false);
if (OidIsValid(ddl_userid))
{
SetUserIdAndSecContext(save_userid, save_sec_context);
*ddl_save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
}
/*
* Only allow commutative operators to be used in exclusion
* constraints. If X conflicts with Y, but Y does not conflict
* with X, bad things will happen.
*/
if (get_commutator(opid) != opid)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("operator %s is not commutative",
format_operator(opid)),
errdetail("Only commutative operators can be used in exclusion constraints."),
parser_errposition(pstate, attribute->location)));
/*
* Operator must be a member of the right opfamily, too
*/
opfamily = get_opclass_family(opclassOids[attn]);
strat = get_op_opfamily_strategy(opid, opfamily);
if (strat == 0)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("operator %s is not a member of operator family \"%s\"",
format_operator(opid),
get_opfamily_name(opfamily, false)),
errdetail("The exclusion operator must be related to the index operator class for the constraint."),
parser_errposition(pstate, attribute->location)));
indexInfo->ii_ExclusionOps[attn] = opid;
indexInfo->ii_ExclusionProcs[attn] = get_opcode(opid);
indexInfo->ii_ExclusionStrats[attn] = strat;
nextExclOp = lnext(exclusionOpNames, nextExclOp);
}
else if (iswithoutoverlaps)
{
CompareType cmptype;
StrategyNumber strat;
Oid opid;
if (attn == nkeycols - 1)
cmptype = COMPARE_OVERLAP;
else
cmptype = COMPARE_EQ;
GetOperatorFromCompareType(opclassOids[attn], InvalidOid, cmptype, &opid, &strat);
indexInfo->ii_ExclusionOps[attn] = opid;
indexInfo->ii_ExclusionProcs[attn] = get_opcode(opid);
indexInfo->ii_ExclusionStrats[attn] = strat;
}
/*
* Set up the per-column options (indoption field). For now, this is
* zero for any un-ordered index, while ordered indexes have DESC and
* NULLS FIRST/LAST options.
*/
colOptions[attn] = 0;
if (amcanorder)
{
/* default ordering is ASC */
if (attribute->ordering == SORTBY_DESC)
colOptions[attn] |= INDOPTION_DESC;
/* default null ordering is LAST for ASC, FIRST for DESC */
if (attribute->nulls_ordering == SORTBY_NULLS_DEFAULT)
{
if (attribute->ordering == SORTBY_DESC)
colOptions[attn] |= INDOPTION_NULLS_FIRST;
}
else if (attribute->nulls_ordering == SORTBY_NULLS_FIRST)
colOptions[attn] |= INDOPTION_NULLS_FIRST;
}
else
{
/* index AM does not support ordering */
if (attribute->ordering != SORTBY_DEFAULT)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support ASC/DESC options",
accessMethodName),
parser_errposition(pstate, attribute->location)));
if (attribute->nulls_ordering != SORTBY_NULLS_DEFAULT)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support NULLS FIRST/LAST options",
accessMethodName),
parser_errposition(pstate, attribute->location)));
}
/* Set up the per-column opclass options (attoptions field). */
if (attribute->opclassopts)
{
Assert(attn < nkeycols);
opclassOptions[attn] =
transformRelOptions((Datum) 0, attribute->opclassopts,
NULL, NULL, false, false);
}
else
opclassOptions[attn] = (Datum) 0;
attn++;
}
indexInfo->ii_ExpressionsExpand =
ExpandVirtualGeneratedColumns(copyObject(indexInfo->ii_Expressions),
NULL, relId);
}
/*
* Resolve possibly-defaulted operator class specification
*
* Note: This is used to resolve operator class specifications in index and
* partition key definitions.
*/
Oid
ResolveOpClass(const List *opclass, Oid attrType,
const char *accessMethodName, Oid accessMethodId)
{
char *schemaname;
char *opcname;
HeapTuple tuple;
Form_pg_opclass opform;
Oid opClassId,
opInputType;
if (opclass == NIL)
{
/* no operator class specified, so find the default */
opClassId = GetDefaultOpClass(attrType, accessMethodId);
if (!OidIsValid(opClassId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("data type %s has no default operator class for access method \"%s\"",
format_type_be(attrType), accessMethodName),
errhint("You must specify an operator class for the index or define a default operator class for the data type.")));
return opClassId;
}
/*
* Specific opclass name given, so look up the opclass.
*/
/* deconstruct the name list */
DeconstructQualifiedName(opclass, &schemaname, &opcname);
if (schemaname)
{
/* Look in specific schema only */
Oid namespaceId;
namespaceId = LookupExplicitNamespace(schemaname, false);
tuple = SearchSysCache3(CLAAMNAMENSP,
ObjectIdGetDatum(accessMethodId),
PointerGetDatum(opcname),
ObjectIdGetDatum(namespaceId));
}
else
{
/* Unqualified opclass name, so search the search path */
opClassId = OpclassnameGetOpcid(accessMethodId, opcname);
if (!OidIsValid(opClassId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
opcname, accessMethodName)));
tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opClassId));
}
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
NameListToString(opclass), accessMethodName)));
/*
* Verify that the index operator class accepts this datatype. Note we
* will accept binary compatibility.
*/
opform = (Form_pg_opclass) GETSTRUCT(tuple);
opClassId = opform->oid;
opInputType = opform->opcintype;
if (!IsBinaryCoercible(attrType, opInputType))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("operator class \"%s\" does not accept data type %s",
NameListToString(opclass), format_type_be(attrType))));
ReleaseSysCache(tuple);
return opClassId;
}
/*
* GetDefaultOpClass
*
* Given the OIDs of a datatype and an access method, find the default
* operator class, if any. Returns InvalidOid if there is none.
*/
Oid
GetDefaultOpClass(Oid type_id, Oid am_id)
{
Oid result = InvalidOid;
int nexact = 0;
int ncompatible = 0;
int ncompatiblepreferred = 0;
Relation rel;
ScanKeyData skey[1];
SysScanDesc scan;
HeapTuple tup;
TYPCATEGORY tcategory;
/* If it's a domain, look at the base type instead */
type_id = getBaseType(type_id);
tcategory = TypeCategory(type_id);
/*
* We scan through all the opclasses available for the access method,
* looking for one that is marked default and matches the target type
* (either exactly or binary-compatibly, but prefer an exact match).
*
* We could find more than one binary-compatible match. If just one is
* for a preferred type, use that one; otherwise we fail, forcing the user
* to specify which one he wants. (The preferred-type special case is a
* kluge for varchar: it's binary-compatible to both text and bpchar, so
* we need a tiebreaker.) If we find more than one exact match, then
* someone put bogus entries in pg_opclass.
*/
rel = table_open(OperatorClassRelationId, AccessShareLock);
ScanKeyInit(&skey[0],
Anum_pg_opclass_opcmethod,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(am_id));
scan = systable_beginscan(rel, OpclassAmNameNspIndexId, true,
NULL, 1, skey);
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
Form_pg_opclass opclass = (Form_pg_opclass) GETSTRUCT(tup);
/* ignore altogether if not a default opclass */
if (!opclass->opcdefault)
continue;
if (opclass->opcintype == type_id)
{
nexact++;
result = opclass->oid;
}
else if (nexact == 0 &&
IsBinaryCoercible(type_id, opclass->opcintype))
{
if (IsPreferredType(tcategory, opclass->opcintype))
{
ncompatiblepreferred++;
result = opclass->oid;
}
else if (ncompatiblepreferred == 0)
{
ncompatible++;
result = opclass->oid;
}
}
}
systable_endscan(scan);
table_close(rel, AccessShareLock);
/* raise error if pg_opclass contains inconsistent data */
if (nexact > 1)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("there are multiple default operator classes for data type %s",
format_type_be(type_id))));
if (nexact == 1 ||
ncompatiblepreferred == 1 ||
(ncompatiblepreferred == 0 && ncompatible == 1))
return result;
return InvalidOid;
}
/*
* GetOperatorFromCompareType
*
* opclass - the opclass to use
* rhstype - the type for the right-hand side, or InvalidOid to use the type of the given opclass.
* cmptype - kind of operator to find
* opid - holds the operator we found
* strat - holds the output strategy number
*
* Finds an operator from a CompareType. This is used for temporal index
* constraints (and other temporal features) to look up equality and overlaps
* operators. We ask an opclass support function to translate from the
* compare type to the internal strategy numbers. Raises ERROR on search
* failure.
*/
void
GetOperatorFromCompareType(Oid opclass, Oid rhstype, CompareType cmptype,
Oid *opid, StrategyNumber *strat)
{
Oid amid;
Oid opfamily;
Oid opcintype;
Assert(cmptype == COMPARE_EQ || cmptype == COMPARE_OVERLAP || cmptype == COMPARE_CONTAINED_BY);
/*
* Use the opclass to get the opfamily, opcintype, and access method. If
* any of this fails, quit early.
*/
if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
elog(ERROR, "cache lookup failed for opclass %u", opclass);
amid = get_opclass_method(opclass);
/*
* Ask the index AM to translate to its internal stratnum
*/
*strat = IndexAmTranslateCompareType(cmptype, amid, opfamily, true);
if (*strat == InvalidStrategy)
ereport(ERROR,
errcode(ERRCODE_UNDEFINED_OBJECT),
cmptype == COMPARE_EQ ? errmsg("could not identify an equality operator for type %s", format_type_be(opcintype)) :
cmptype == COMPARE_OVERLAP ? errmsg("could not identify an overlaps operator for type %s", format_type_be(opcintype)) :
cmptype == COMPARE_CONTAINED_BY ? errmsg("could not identify a contained-by operator for type %s", format_type_be(opcintype)) : 0,
errdetail("Could not translate compare type %d for operator family \"%s\" of access method \"%s\".",
cmptype, get_opfamily_name(opfamily, false), get_am_name(amid)));
/*
* We parameterize rhstype so foreign keys can ask for a <@ operator whose
* rhs matches the aggregate function. For example range_agg returns
* anymultirange.
*/
if (!OidIsValid(rhstype))
rhstype = opcintype;
*opid = get_opfamily_member(opfamily, opcintype, rhstype, *strat);
if (!OidIsValid(*opid))
ereport(ERROR,
errcode(ERRCODE_UNDEFINED_OBJECT),
cmptype == COMPARE_EQ ? errmsg("could not identify an equality operator for type %s", format_type_be(opcintype)) :
cmptype == COMPARE_OVERLAP ? errmsg("could not identify an overlaps operator for type %s", format_type_be(opcintype)) :
cmptype == COMPARE_CONTAINED_BY ? errmsg("could not identify a contained-by operator for type %s", format_type_be(opcintype)) : 0,
errdetail("There is no suitable operator in operator family \"%s\" for access method \"%s\".",
get_opfamily_name(opfamily, false), get_am_name(amid)));
}
/*
* makeObjectName()
*
* Create a name for an implicitly created index, sequence, constraint,
* extended statistics, etc.
*
* The parameters are typically: the original table name, the original field
* name, and a "type" string (such as "seq" or "pkey"). The field name
* and/or type can be NULL if not relevant.
*
* The result is a palloc'd string.
*
* The basic result we want is "name1_name2_label", omitting "_name2" or
* "_label" when those parameters are NULL. However, we must generate
* a name with less than NAMEDATALEN characters! So, we truncate one or
* both names if necessary to make a short-enough string. The label part
* is never truncated (so it had better be reasonably short).
*
* The caller is responsible for checking uniqueness of the generated
* name and retrying as needed; retrying will be done by altering the
* "label" string (which is why we never truncate that part).
*/
char *
makeObjectName(const char *name1, const char *name2, const char *label)
{
char *name;
int overhead = 0; /* chars needed for label and underscores */
int availchars; /* chars available for name(s) */
int name1chars; /* chars allocated to name1 */
int name2chars; /* chars allocated to name2 */
int ndx;
name1chars = strlen(name1);
if (name2)
{
name2chars = strlen(name2);
overhead++; /* allow for separating underscore */
}
else
name2chars = 0;
if (label)
overhead += strlen(label) + 1;
availchars = NAMEDATALEN - 1 - overhead;
Assert(availchars > 0); /* else caller chose a bad label */
/*
* If we must truncate, preferentially truncate the longer name. This
* logic could be expressed without a loop, but it's simple and obvious as
* a loop.
*/
while (name1chars + name2chars > availchars)
{
if (name1chars > name2chars)
name1chars--;
else
name2chars--;
}
name1chars = pg_mbcliplen(name1, name1chars, name1chars);
if (name2)
name2chars = pg_mbcliplen(name2, name2chars, name2chars);
/* Now construct the string using the chosen lengths */
name = palloc(name1chars + name2chars + overhead + 1);
memcpy(name, name1, name1chars);
ndx = name1chars;
if (name2)
{
name[ndx++] = '_';
memcpy(name + ndx, name2, name2chars);
ndx += name2chars;
}
if (label)
{
name[ndx++] = '_';
strcpy(name + ndx, label);
}
else
name[ndx] = '\0';
return name;
}
/*
* Select a nonconflicting name for a new relation. This is ordinarily
* used to choose index names (which is why it's here) but it can also
* be used for sequences, or any autogenerated relation kind.
*
* name1, name2, and label are used the same way as for makeObjectName(),
* except that the label can't be NULL; digits will be appended to the label
* if needed to create a name that is unique within the specified namespace.
*
* If isconstraint is true, we also avoid choosing a name matching any
* existing constraint in the same namespace. (This is stricter than what
* Postgres itself requires, but the SQL standard says that constraint names
* should be unique within schemas, so we follow that for autogenerated
* constraint names.)
*
* Note: it is theoretically possible to get a collision anyway, if someone
* else chooses the same name concurrently. We shorten the race condition
* window by checking for conflicting relations using SnapshotDirty, but
* that doesn't close the window entirely. This is fairly unlikely to be
* a problem in practice, especially if one is holding an exclusive lock on
* the relation identified by name1. However, if choosing multiple names
* within a single command, you'd better create the new object and do
* CommandCounterIncrement before choosing the next one!
*
* Returns a palloc'd string.
*/
char *
ChooseRelationName(const char *name1, const char *name2,
const char *label, Oid namespaceid,
bool isconstraint)
{
int pass = 0;
char *relname = NULL;
char modlabel[NAMEDATALEN];
SnapshotData SnapshotDirty;
Relation pgclassrel;
/* prepare to search pg_class with a dirty snapshot */
InitDirtySnapshot(SnapshotDirty);
pgclassrel = table_open(RelationRelationId, AccessShareLock);
/* try the unmodified label first */
strlcpy(modlabel, label, sizeof(modlabel));
for (;;)
{
ScanKeyData key[2];
SysScanDesc scan;
bool collides;
relname = makeObjectName(name1, name2, modlabel);
/* is there any conflicting relation name? */
ScanKeyInit(&key[0],
Anum_pg_class_relname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(relname));
ScanKeyInit(&key[1],
Anum_pg_class_relnamespace,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(namespaceid));
scan = systable_beginscan(pgclassrel, ClassNameNspIndexId,
true /* indexOK */ ,
&SnapshotDirty,
2, key);
collides = HeapTupleIsValid(systable_getnext(scan));
systable_endscan(scan);
/* break out of loop if no conflict */
if (!collides)
{
if (!isconstraint ||
!ConstraintNameExists(relname, namespaceid))
break;
}
/* found a conflict, so try a new name component */
pfree(relname);
snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
}
table_close(pgclassrel, AccessShareLock);
return relname;
}
/*
* Select the name to be used for an index.
*
* The argument list is pretty ad-hoc :-(
*/
static char *
ChooseIndexName(const char *tabname, Oid namespaceId,
const List *colnames, const List *exclusionOpNames,
bool primary, bool isconstraint)
{
char *indexname;
if (primary)
{
/* the primary key's name does not depend on the specific column(s) */
indexname = ChooseRelationName(tabname,
NULL,
"pkey",
namespaceId,
true);
}
else if (exclusionOpNames != NIL)
{
indexname = ChooseRelationName(tabname,
ChooseIndexNameAddition(colnames),
"excl",
namespaceId,
true);
}
else if (isconstraint)
{
indexname = ChooseRelationName(tabname,
ChooseIndexNameAddition(colnames),
"key",
namespaceId,
true);
}
else
{
indexname = ChooseRelationName(tabname,
ChooseIndexNameAddition(colnames),
"idx",
namespaceId,
false);
}
return indexname;
}
/*
* Generate "name2" for a new index given the list of column names for it
* (as produced by ChooseIndexColumnNames). This will be passed to
* ChooseRelationName along with the parent table name and a suitable label.
*
* We know that less than NAMEDATALEN characters will actually be used,
* so we can truncate the result once we've generated that many.
*
* XXX See also ChooseForeignKeyConstraintNameAddition and
* ChooseExtendedStatisticNameAddition.
*/
static char *
ChooseIndexNameAddition(const List *colnames)
{
char buf[NAMEDATALEN * 2];
int buflen = 0;
ListCell *lc;
buf[0] = '\0';
foreach(lc, colnames)
{
const char *name = (const char *) lfirst(lc);
if (buflen > 0)
buf[buflen++] = '_'; /* insert _ between names */
/*
* At this point we have buflen <= NAMEDATALEN. name should be less
* than NAMEDATALEN already, but use strlcpy for paranoia.
*/
strlcpy(buf + buflen, name, NAMEDATALEN);
buflen += strlen(buf + buflen);
if (buflen >= NAMEDATALEN)
break;
}
return pstrdup(buf);
}
/*
* Select the actual names to be used for the columns of an index, given the
* list of IndexElems for the columns. This is mostly about ensuring the
* names are unique so we don't get a conflicting-attribute-names error.
*
* Returns a List of plain strings (char *, not String nodes).
*/
static List *
ChooseIndexColumnNames(const List *indexElems)
{
List *result = NIL;
ListCell *lc;
foreach(lc, indexElems)
{
IndexElem *ielem = (IndexElem *) lfirst(lc);
const char *origname;
const char *curname;
int i;
char buf[NAMEDATALEN];
/* Get the preliminary name from the IndexElem */
if (ielem->indexcolname)
origname = ielem->indexcolname; /* caller-specified name */
else if (ielem->name)
origname = ielem->name; /* simple column reference */
else
origname = "expr"; /* default name for expression */
/* If it conflicts with any previous column, tweak it */
curname = origname;
for (i = 1;; i++)
{
ListCell *lc2;
char nbuf[32];
int nlen;
foreach(lc2, result)
{
if (strcmp(curname, (char *) lfirst(lc2)) == 0)
break;
}
if (lc2 == NULL)
break; /* found nonconflicting name */
sprintf(nbuf, "%d", i);
/* Ensure generated names are shorter than NAMEDATALEN */
nlen = pg_mbcliplen(origname, strlen(origname),
NAMEDATALEN - 1 - strlen(nbuf));
memcpy(buf, origname, nlen);
strcpy(buf + nlen, nbuf);
curname = buf;
}
/* And attach to the result list */
result = lappend(result, pstrdup(curname));
}
return result;
}
/*
* ExecReindex
*
* Primary entry point for manual REINDEX commands. This is mainly a
* preparation wrapper for the real operations that will happen in
* each subroutine of REINDEX.
*/
void
ExecReindex(ParseState *pstate, const ReindexStmt *stmt, bool isTopLevel)
{
ReindexParams params = {0};
ListCell *lc;
bool concurrently = false;
bool verbose = false;
char *tablespacename = NULL;
/* Parse option list */
foreach(lc, stmt->params)
{
DefElem *opt = (DefElem *) lfirst(lc);
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "concurrently") == 0)
concurrently = defGetBoolean(opt);
else if (strcmp(opt->defname, "tablespace") == 0)
tablespacename = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized %s option \"%s\"",
"REINDEX", opt->defname),
parser_errposition(pstate, opt->location)));
}
if (concurrently)
PreventInTransactionBlock(isTopLevel,
"REINDEX CONCURRENTLY");
params.options =
(verbose ? REINDEXOPT_VERBOSE : 0) |
(concurrently ? REINDEXOPT_CONCURRENTLY : 0);
/*
* Assign the tablespace OID to move indexes to, with InvalidOid to do
* nothing.
*/
if (tablespacename != NULL)
{
params.tablespaceOid = get_tablespace_oid(tablespacename, false);
/* Check permissions except when moving to database's default */
if (OidIsValid(params.tablespaceOid) &&
params.tablespaceOid != MyDatabaseTableSpace)
{
AclResult aclresult;
aclresult = object_aclcheck(TableSpaceRelationId, params.tablespaceOid,
GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_TABLESPACE,
get_tablespace_name(params.tablespaceOid));
}
}
else
params.tablespaceOid = InvalidOid;
switch (stmt->kind)
{
case REINDEX_OBJECT_INDEX:
ReindexIndex(stmt, ¶ms, isTopLevel);
break;
case REINDEX_OBJECT_TABLE:
ReindexTable(stmt, ¶ms, isTopLevel);
break;
case REINDEX_OBJECT_SCHEMA:
case REINDEX_OBJECT_SYSTEM:
case REINDEX_OBJECT_DATABASE:
/*
* This cannot run inside a user transaction block; if we were
* inside a transaction, then its commit- and
* start-transaction-command calls would not have the intended
* effect!
*/
PreventInTransactionBlock(isTopLevel,
(stmt->kind == REINDEX_OBJECT_SCHEMA) ? "REINDEX SCHEMA" :
(stmt->kind == REINDEX_OBJECT_SYSTEM) ? "REINDEX SYSTEM" :
"REINDEX DATABASE");
ReindexMultipleTables(stmt, ¶ms);
break;
default:
elog(ERROR, "unrecognized object type: %d",
(int) stmt->kind);
break;
}
}
/*
* ReindexIndex
* Recreate a specific index.
*/
static void
ReindexIndex(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLevel)
{
const RangeVar *indexRelation = stmt->relation;
struct ReindexIndexCallbackState state;
Oid indOid;
char persistence;
char relkind;
/*
* Find and lock index, and check permissions on table; use callback to
* obtain lock on table first, to avoid deadlock hazard. The lock level
* used here must match the index lock obtained in reindex_index().
*
* If it's a temporary index, we will perform a non-concurrent reindex,
* even if CONCURRENTLY was requested. In that case, reindex_index() will
* upgrade the lock, but that's OK, because other sessions can't hold
* locks on our temporary table.
*/
state.params = *params;
state.locked_table_oid = InvalidOid;
indOid = RangeVarGetRelidExtended(indexRelation,
(params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
ShareUpdateExclusiveLock : AccessExclusiveLock,
0,
RangeVarCallbackForReindexIndex,
&state);
/*
* Obtain the current persistence and kind of the existing index. We
* already hold a lock on the index.
*/
persistence = get_rel_persistence(indOid);
relkind = get_rel_relkind(indOid);
if (relkind == RELKIND_PARTITIONED_INDEX)
ReindexPartitions(stmt, indOid, params, isTopLevel);
else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
persistence != RELPERSISTENCE_TEMP)
ReindexRelationConcurrently(stmt, indOid, params);
else
{
ReindexParams newparams = *params;
newparams.options |= REINDEXOPT_REPORT_PROGRESS;
reindex_index(stmt, indOid, false, persistence, &newparams);
}
}
/*
* Check permissions on table before acquiring relation lock; also lock
* the heap before the RangeVarGetRelidExtended takes the index lock, to avoid
* deadlocks.
*/
static void
RangeVarCallbackForReindexIndex(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg)
{
char relkind;
struct ReindexIndexCallbackState *state = arg;
LOCKMODE table_lockmode;
Oid table_oid;
AclResult aclresult;
/*
* Lock level here should match table lock in reindex_index() for
* non-concurrent case and table locks used by index_concurrently_*() for
* concurrent case.
*/
table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
ShareUpdateExclusiveLock : ShareLock;
/*
* If we previously locked some other index's heap, and the name we're
* looking up no longer refers to that relation, release the now-useless
* lock.
*/
if (relId != oldRelId && OidIsValid(oldRelId))
{
UnlockRelationOid(state->locked_table_oid, table_lockmode);
state->locked_table_oid = InvalidOid;
}
/* If the relation does not exist, there's nothing more to do. */
if (!OidIsValid(relId))
return;
/* If the relation does exist, check whether it's an index. */
relkind = get_rel_relkind(relId);
if (relkind != RELKIND_INDEX &&
relkind != RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not an index", relation->relname)));
/* Look up the index's table. */
table_oid = IndexGetRelation(relId, false);
/*
* In the unlikely event that, upon retry, we get the same index OID with
* a different table OID, fail. RangeVarGetRelidExtended() will have
* already locked the index in this case, and it won't retry again, so we
* can't lock the newly discovered table OID without risking deadlock.
* Also, while this corner case is indeed possible, it is extremely
* unlikely to happen in practice, so it's probably not worth any more
* effort than this.
*/
if (relId == oldRelId && table_oid != state->locked_table_oid)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("index \"%s\" was concurrently dropped",
relation->relname)));
/* Check permissions. */
aclresult = pg_class_aclcheck(table_oid, GetUserId(), ACL_MAINTAIN);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_INDEX, relation->relname);
/* Lock heap before index to avoid deadlock. */
if (relId != oldRelId)
{
LockRelationOid(table_oid, table_lockmode);
state->locked_table_oid = table_oid;
}
}
/*
* ReindexTable
* Recreate all indexes of a table (and of its toast table, if any)
*/
static Oid
ReindexTable(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLevel)
{
Oid heapOid;
bool result;
const RangeVar *relation = stmt->relation;
/*
* The lock level used here should match reindex_relation().
*
* If it's a temporary table, we will perform a non-concurrent reindex,
* even if CONCURRENTLY was requested. In that case, reindex_relation()
* will upgrade the lock, but that's OK, because other sessions can't hold
* locks on our temporary table.
*/
heapOid = RangeVarGetRelidExtended(relation,
(params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
ShareUpdateExclusiveLock : ShareLock,
0,
RangeVarCallbackMaintainsTable, NULL);
if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
ReindexPartitions(stmt, heapOid, params, isTopLevel);
else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
{
result = ReindexRelationConcurrently(stmt, heapOid, params);
if (!result)
ereport(NOTICE,
(errmsg("table \"%s\" has no indexes that can be reindexed concurrently",
relation->relname)));
}
else
{
ReindexParams newparams = *params;
newparams.options |= REINDEXOPT_REPORT_PROGRESS;
result = reindex_relation(stmt, heapOid,
REINDEX_REL_PROCESS_TOAST |
REINDEX_REL_CHECK_CONSTRAINTS,
&newparams);
if (!result)
ereport(NOTICE,
(errmsg("table \"%s\" has no indexes to reindex",
relation->relname)));
}
return heapOid;
}
/*
* ReindexMultipleTables
* Recreate indexes of tables selected by objectName/objectKind.
*
* To reduce the probability of deadlocks, each table is reindexed in a
* separate transaction, so we can release the lock on it right away.
* That means this must not be called within a user transaction block!
*/
static void
ReindexMultipleTables(const ReindexStmt *stmt, const ReindexParams *params)
{
Oid objectOid;
Relation relationRelation;
TableScanDesc scan;
ScanKeyData scan_keys[1];
HeapTuple tuple;
MemoryContext private_context;
MemoryContext old;
List *relids = NIL;
int num_keys;
bool concurrent_warning = false;
bool tablespace_warning = false;
const char *objectName = stmt->name;
const ReindexObjectType objectKind = stmt->kind;
Assert(objectKind == REINDEX_OBJECT_SCHEMA ||
objectKind == REINDEX_OBJECT_SYSTEM ||
objectKind == REINDEX_OBJECT_DATABASE);
/*
* This matches the options enforced by the grammar, where the object name
* is optional for DATABASE and SYSTEM.
*/
Assert(objectName || objectKind != REINDEX_OBJECT_SCHEMA);
if (objectKind == REINDEX_OBJECT_SYSTEM &&
(params->options & REINDEXOPT_CONCURRENTLY) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex system catalogs concurrently")));
/*
* Get OID of object to reindex, being the database currently being used
* by session for a database or for system catalogs, or the schema defined
* by caller. At the same time do permission checks that need different
* processing depending on the object type.
*/
if (objectKind == REINDEX_OBJECT_SCHEMA)
{
objectOid = get_namespace_oid(objectName, false);
if (!object_ownercheck(NamespaceRelationId, objectOid, GetUserId()) &&
!has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
objectName);
}
else
{
objectOid = MyDatabaseId;
if (objectName && strcmp(objectName, get_database_name(objectOid)) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("can only reindex the currently open database")));
if (!object_ownercheck(DatabaseRelationId, objectOid, GetUserId()) &&
!has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
get_database_name(objectOid));
}
/*
* Create a memory context that will survive forced transaction commits we
* do below. Since it is a child of PortalContext, it will go away
* eventually even if we suffer an error; there's no need for special
* abort cleanup logic.
*/
private_context = AllocSetContextCreate(PortalContext,
"ReindexMultipleTables",
ALLOCSET_SMALL_SIZES);
/*
* Define the search keys to find the objects to reindex. For a schema, we
* select target relations using relnamespace, something not necessary for
* a database-wide operation.
*/
if (objectKind == REINDEX_OBJECT_SCHEMA)
{
num_keys = 1;
ScanKeyInit(&scan_keys[0],
Anum_pg_class_relnamespace,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(objectOid));
}
else
num_keys = 0;
/*
* Scan pg_class to build a list of the relations we need to reindex.
*
* We only consider plain relations and materialized views here (toast
* rels will be processed indirectly by reindex_relation).
*/
relationRelation = table_open(RelationRelationId, AccessShareLock);
scan = table_beginscan_catalog(relationRelation, num_keys, scan_keys);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
Oid relid = classtuple->oid;
/*
* Only regular tables and matviews can have indexes, so ignore any
* other kind of relation.
*
* Partitioned tables/indexes are skipped but matching leaf partitions
* are processed.
*/
if (classtuple->relkind != RELKIND_RELATION &&
classtuple->relkind != RELKIND_MATVIEW)
continue;
/* Skip temp tables of other backends; we can't reindex them at all */
if (classtuple->relpersistence == RELPERSISTENCE_TEMP &&
!isTempNamespace(classtuple->relnamespace))
continue;
/*
* Check user/system classification. SYSTEM processes all the
* catalogs, and DATABASE processes everything that's not a catalog.
*/
if (objectKind == REINDEX_OBJECT_SYSTEM &&
!IsCatalogRelationOid(relid))
continue;
else if (objectKind == REINDEX_OBJECT_DATABASE &&
IsCatalogRelationOid(relid))
continue;
/*
* We already checked privileges on the database or schema, but we
* further restrict reindexing shared catalogs to roles with the
* MAINTAIN privilege on the relation.
*/
if (classtuple->relisshared &&
pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) != ACLCHECK_OK)
continue;
/*
* Skip system tables, since index_create() would reject indexing them
* concurrently (and it would likely fail if we tried).
*/
if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
IsCatalogRelationOid(relid))
{
if (!concurrent_warning)
ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex system catalogs concurrently, skipping all")));
concurrent_warning = true;
continue;
}
/*
* If a new tablespace is set, check if this relation has to be
* skipped.
*/
if (OidIsValid(params->tablespaceOid))
{
bool skip_rel = false;
/*
* Mapped relations cannot be moved to different tablespaces (in
* particular this eliminates all shared catalogs.).
*/
if (RELKIND_HAS_STORAGE(classtuple->relkind) &&
!RelFileNumberIsValid(classtuple->relfilenode))
skip_rel = true;
/*
* A system relation is always skipped, even with
* allow_system_table_mods enabled.
*/
if (IsSystemClass(relid, classtuple))
skip_rel = true;
if (skip_rel)
{
if (!tablespace_warning)
ereport(WARNING,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("cannot move system relations, skipping all")));
tablespace_warning = true;
continue;
}
}
/* Save the list of relation OIDs in private context */
old = MemoryContextSwitchTo(private_context);
/*
* We always want to reindex pg_class first if it's selected to be
* reindexed. This ensures that if there is any corruption in
* pg_class' indexes, they will be fixed before we process any other
* tables. This is critical because reindexing itself will try to
* update pg_class.
*/
if (relid == RelationRelationId)
relids = lcons_oid(relid, relids);
else
relids = lappend_oid(relids, relid);
MemoryContextSwitchTo(old);
}
table_endscan(scan);
table_close(relationRelation, AccessShareLock);
/*
* Process each relation listed in a separate transaction. Note that this
* commits and then starts a new transaction immediately.
*/
ReindexMultipleInternal(stmt, relids, params);
MemoryContextDelete(private_context);
}
/*
* Error callback specific to ReindexPartitions().
*/
static void
reindex_error_callback(void *arg)
{
ReindexErrorInfo *errinfo = (ReindexErrorInfo *) arg;
Assert(RELKIND_HAS_PARTITIONS(errinfo->relkind));
if (errinfo->relkind == RELKIND_PARTITIONED_TABLE)
errcontext("while reindexing partitioned table \"%s.%s\"",
errinfo->relnamespace, errinfo->relname);
else if (errinfo->relkind == RELKIND_PARTITIONED_INDEX)
errcontext("while reindexing partitioned index \"%s.%s\"",
errinfo->relnamespace, errinfo->relname);
}
/*
* ReindexPartitions
*
* Reindex a set of partitions, per the partitioned index or table given
* by the caller.
*/
static void
ReindexPartitions(const ReindexStmt *stmt, Oid relid, const ReindexParams *params, bool isTopLevel)
{
List *partitions = NIL;
char relkind = get_rel_relkind(relid);
char *relname = get_rel_name(relid);
char *relnamespace = get_namespace_name(get_rel_namespace(relid));
MemoryContext reindex_context;
List *inhoids;
ListCell *lc;
ErrorContextCallback errcallback;
ReindexErrorInfo errinfo;
Assert(RELKIND_HAS_PARTITIONS(relkind));
/*
* Check if this runs in a transaction block, with an error callback to
* provide more context under which a problem happens.
*/
errinfo.relname = pstrdup(relname);
errinfo.relnamespace = pstrdup(relnamespace);
errinfo.relkind = relkind;
errcallback.callback = reindex_error_callback;
errcallback.arg = &errinfo;
errcallback.previous = error_context_stack;
error_context_stack = &errcallback;
PreventInTransactionBlock(isTopLevel,
relkind == RELKIND_PARTITIONED_TABLE ?
"REINDEX TABLE" : "REINDEX INDEX");
/* Pop the error context stack */
error_context_stack = errcallback.previous;
/*
* Create special memory context for cross-transaction storage.
*
* Since it is a child of PortalContext, it will go away eventually even
* if we suffer an error so there is no need for special abort cleanup
* logic.
*/
reindex_context = AllocSetContextCreate(PortalContext, "Reindex",
ALLOCSET_DEFAULT_SIZES);
/* ShareLock is enough to prevent schema modifications */
inhoids = find_all_inheritors(relid, ShareLock, NULL);
/*
* The list of relations to reindex are the physical partitions of the
* tree so discard any partitioned table or index.
*/
foreach(lc, inhoids)
{
Oid partoid = lfirst_oid(lc);
char partkind = get_rel_relkind(partoid);
MemoryContext old_context;
/*
* This discards partitioned tables, partitioned indexes and foreign
* tables.
*/
if (!RELKIND_HAS_STORAGE(partkind))
continue;
Assert(partkind == RELKIND_INDEX ||
partkind == RELKIND_RELATION);
/* Save partition OID */
old_context = MemoryContextSwitchTo(reindex_context);
partitions = lappend_oid(partitions, partoid);
MemoryContextSwitchTo(old_context);
}
/*
* Process each partition listed in a separate transaction. Note that
* this commits and then starts a new transaction immediately.
*/
ReindexMultipleInternal(stmt, partitions, params);
/*
* Clean up working storage --- note we must do this after
* StartTransactionCommand, else we might be trying to delete the active
* context!
*/
MemoryContextDelete(reindex_context);
}
/*
* ReindexMultipleInternal
*
* Reindex a list of relations, each one being processed in its own
* transaction. This commits the existing transaction immediately,
* and starts a new transaction when finished.
*/
static void
ReindexMultipleInternal(const ReindexStmt *stmt, const List *relids, const ReindexParams *params)
{
ListCell *l;
PopActiveSnapshot();
CommitTransactionCommand();
foreach(l, relids)
{
Oid relid = lfirst_oid(l);
char relkind;
char relpersistence;
StartTransactionCommand();
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* check if the relation still exists */
if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
{
PopActiveSnapshot();
CommitTransactionCommand();
continue;
}
/*
* Check permissions except when moving to database's default if a new
* tablespace is chosen. Note that this check also happens in
* ExecReindex(), but we do an extra check here as this runs across
* multiple transactions.
*/
if (OidIsValid(params->tablespaceOid) &&
params->tablespaceOid != MyDatabaseTableSpace)
{
AclResult aclresult;
aclresult = object_aclcheck(TableSpaceRelationId, params->tablespaceOid,
GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_TABLESPACE,
get_tablespace_name(params->tablespaceOid));
}
relkind = get_rel_relkind(relid);
relpersistence = get_rel_persistence(relid);
/*
* Partitioned tables and indexes can never be processed directly, and
* a list of their leaves should be built first.
*/
Assert(!RELKIND_HAS_PARTITIONS(relkind));
if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
relpersistence != RELPERSISTENCE_TEMP)
{
ReindexParams newparams = *params;
newparams.options |= REINDEXOPT_MISSING_OK;
(void) ReindexRelationConcurrently(stmt, relid, &newparams);
if (ActiveSnapshotSet())
PopActiveSnapshot();
/* ReindexRelationConcurrently() does the verbose output */
}
else if (relkind == RELKIND_INDEX)
{
ReindexParams newparams = *params;
newparams.options |=
REINDEXOPT_REPORT_PROGRESS | REINDEXOPT_MISSING_OK;
reindex_index(stmt, relid, false, relpersistence, &newparams);
PopActiveSnapshot();
/* reindex_index() does the verbose output */
}
else
{
bool result;
ReindexParams newparams = *params;
newparams.options |=
REINDEXOPT_REPORT_PROGRESS | REINDEXOPT_MISSING_OK;
result = reindex_relation(stmt, relid,
REINDEX_REL_PROCESS_TOAST |
REINDEX_REL_CHECK_CONSTRAINTS,
&newparams);
if (result && (params->options & REINDEXOPT_VERBOSE) != 0)
ereport(INFO,
(errmsg("table \"%s.%s\" was reindexed",
get_namespace_name(get_rel_namespace(relid)),
get_rel_name(relid))));
PopActiveSnapshot();
}
CommitTransactionCommand();
}
StartTransactionCommand();
}
/*
* ReindexRelationConcurrently - process REINDEX CONCURRENTLY for given
* relation OID
*
* 'relationOid' can either belong to an index, a table or a materialized
* view. For tables and materialized views, all its indexes will be rebuilt,
* excluding invalid indexes and any indexes used in exclusion constraints,
* but including its associated toast table indexes. For indexes, the index
* itself will be rebuilt.
*
* The locks taken on parent tables and involved indexes are kept until the
* transaction is committed, at which point a session lock is taken on each
* relation. Both of these protect against concurrent schema changes.
*
* Returns true if any indexes have been rebuilt (including toast table's
* indexes, when relevant), otherwise returns false.
*
* NOTE: This cannot be used on temporary relations. A concurrent build would
* cause issues with ON COMMIT actions triggered by the transactions of the
* concurrent build. Temporary relations are not subject to concurrent
* concerns, so there's no need for the more complicated concurrent build,
* anyway, and a non-concurrent reindex is more efficient.
*/
static bool
ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const ReindexParams *params)
{
typedef struct ReindexIndexInfo
{
Oid indexId;
Oid tableId;
Oid amId;
bool safe; /* for set_indexsafe_procflags */
} ReindexIndexInfo;
List *heapRelationIds = NIL;
List *indexIds = NIL;
List *newIndexIds = NIL;
List *relationLocks = NIL;
List *lockTags = NIL;
ListCell *lc,
*lc2;
MemoryContext private_context;
MemoryContext oldcontext;
char relkind;
char *relationName = NULL;
char *relationNamespace = NULL;
PGRUsage ru0;
const int progress_index[] = {
PROGRESS_CREATEIDX_COMMAND,
PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_INDEX_OID,
PROGRESS_CREATEIDX_ACCESS_METHOD_OID
};
int64 progress_vals[4];
/*
* Create a memory context that will survive forced transaction commits we
* do below. Since it is a child of PortalContext, it will go away
* eventually even if we suffer an error; there's no need for special
* abort cleanup logic.
*/
private_context = AllocSetContextCreate(PortalContext,
"ReindexConcurrent",
ALLOCSET_SMALL_SIZES);
if ((params->options & REINDEXOPT_VERBOSE) != 0)
{
/* Save data needed by REINDEX VERBOSE in private context */
oldcontext = MemoryContextSwitchTo(private_context);
relationName = get_rel_name(relationOid);
relationNamespace = get_namespace_name(get_rel_namespace(relationOid));
pg_rusage_init(&ru0);
MemoryContextSwitchTo(oldcontext);
}
relkind = get_rel_relkind(relationOid);
/*
* Extract the list of indexes that are going to be rebuilt based on the
* relation Oid given by caller.
*/
switch (relkind)
{
case RELKIND_RELATION:
case RELKIND_MATVIEW:
case RELKIND_TOASTVALUE:
{
/*
* In the case of a relation, find all its indexes including
* toast indexes.
*/
Relation heapRelation;
/* Save the list of relation OIDs in private context */
oldcontext = MemoryContextSwitchTo(private_context);
/* Track this relation for session locks */
heapRelationIds = lappend_oid(heapRelationIds, relationOid);
MemoryContextSwitchTo(oldcontext);
if (IsCatalogRelationOid(relationOid))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex system catalogs concurrently")));
/* Open relation to get its indexes */
if ((params->options & REINDEXOPT_MISSING_OK) != 0)
{
heapRelation = try_table_open(relationOid,
ShareUpdateExclusiveLock);
/* leave if relation does not exist */
if (!heapRelation)
break;
}
else
heapRelation = table_open(relationOid,
ShareUpdateExclusiveLock);
if (OidIsValid(params->tablespaceOid) &&
IsSystemRelation(heapRelation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move system relation \"%s\"",
RelationGetRelationName(heapRelation))));
/* Add all the valid indexes of relation to list */
foreach(lc, RelationGetIndexList(heapRelation))
{
Oid cellOid = lfirst_oid(lc);
Relation indexRelation = index_open(cellOid,
ShareUpdateExclusiveLock);
if (!indexRelation->rd_index->indisvalid)
ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("skipping reindex of invalid index \"%s.%s\"",
get_namespace_name(get_rel_namespace(cellOid)),
get_rel_name(cellOid)),
errhint("Use DROP INDEX or REINDEX INDEX.")));
else if (indexRelation->rd_index->indisexclusion)
ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex exclusion constraint index \"%s.%s\" concurrently, skipping",
get_namespace_name(get_rel_namespace(cellOid)),
get_rel_name(cellOid))));
else
{
ReindexIndexInfo *idx;
/* Save the list of relation OIDs in private context */
oldcontext = MemoryContextSwitchTo(private_context);
idx = palloc_object(ReindexIndexInfo);
idx->indexId = cellOid;
/* other fields set later */
indexIds = lappend(indexIds, idx);
MemoryContextSwitchTo(oldcontext);
}
index_close(indexRelation, NoLock);
}
/* Also add the toast indexes */
if (OidIsValid(heapRelation->rd_rel->reltoastrelid))
{
Oid toastOid = heapRelation->rd_rel->reltoastrelid;
Relation toastRelation = table_open(toastOid,
ShareUpdateExclusiveLock);
/* Save the list of relation OIDs in private context */
oldcontext = MemoryContextSwitchTo(private_context);
/* Track this relation for session locks */
heapRelationIds = lappend_oid(heapRelationIds, toastOid);
MemoryContextSwitchTo(oldcontext);
foreach(lc2, RelationGetIndexList(toastRelation))
{
Oid cellOid = lfirst_oid(lc2);
Relation indexRelation = index_open(cellOid,
ShareUpdateExclusiveLock);
if (!indexRelation->rd_index->indisvalid)
ereport(WARNING,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("skipping reindex of invalid index \"%s.%s\"",
get_namespace_name(get_rel_namespace(cellOid)),
get_rel_name(cellOid)),
errhint("Use DROP INDEX or REINDEX INDEX.")));
else
{
ReindexIndexInfo *idx;
/*
* Save the list of relation OIDs in private
* context
*/
oldcontext = MemoryContextSwitchTo(private_context);
idx = palloc_object(ReindexIndexInfo);
idx->indexId = cellOid;
indexIds = lappend(indexIds, idx);
/* other fields set later */
MemoryContextSwitchTo(oldcontext);
}
index_close(indexRelation, NoLock);
}
table_close(toastRelation, NoLock);
}
table_close(heapRelation, NoLock);
break;
}
case RELKIND_INDEX:
{
Oid heapId = IndexGetRelation(relationOid,
(params->options & REINDEXOPT_MISSING_OK) != 0);
Relation heapRelation;
ReindexIndexInfo *idx;
/* if relation is missing, leave */
if (!OidIsValid(heapId))
break;
if (IsCatalogRelationOid(heapId))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex system catalogs concurrently")));
/*
* Don't allow reindex for an invalid index on TOAST table, as
* if rebuilt it would not be possible to drop it. Match
* error message in reindex_index().
*/
if (IsToastNamespace(get_rel_namespace(relationOid)) &&
!get_index_isvalid(relationOid))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex invalid index on TOAST table")));
/*
* Check if parent relation can be locked and if it exists,
* this needs to be done at this stage as the list of indexes
* to rebuild is not complete yet, and REINDEXOPT_MISSING_OK
* should not be used once all the session locks are taken.
*/
if ((params->options & REINDEXOPT_MISSING_OK) != 0)
{
heapRelation = try_table_open(heapId,
ShareUpdateExclusiveLock);
/* leave if relation does not exist */
if (!heapRelation)
break;
}
else
heapRelation = table_open(heapId,
ShareUpdateExclusiveLock);
if (OidIsValid(params->tablespaceOid) &&
IsSystemRelation(heapRelation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move system relation \"%s\"",
get_rel_name(relationOid))));
table_close(heapRelation, NoLock);
/* Save the list of relation OIDs in private context */
oldcontext = MemoryContextSwitchTo(private_context);
/* Track the heap relation of this index for session locks */
heapRelationIds = list_make1_oid(heapId);
/*
* Save the list of relation OIDs in private context. Note
* that invalid indexes are allowed here.
*/
idx = palloc_object(ReindexIndexInfo);
idx->indexId = relationOid;
indexIds = lappend(indexIds, idx);
/* other fields set later */
MemoryContextSwitchTo(oldcontext);
break;
}
case RELKIND_PARTITIONED_TABLE:
case RELKIND_PARTITIONED_INDEX:
default:
/* Return error if type of relation is not supported */
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot reindex this type of relation concurrently")));
break;
}
/*
* Definitely no indexes, so leave. Any checks based on
* REINDEXOPT_MISSING_OK should be done only while the list of indexes to
* work on is built as the session locks taken before this transaction
* commits will make sure that they cannot be dropped by a concurrent
* session until this operation completes.
*/
if (indexIds == NIL)
return false;
/* It's not a shared catalog, so refuse to move it to shared tablespace */
if (params->tablespaceOid == GLOBALTABLESPACE_OID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move non-shared relation to tablespace \"%s\"",
get_tablespace_name(params->tablespaceOid))));
Assert(heapRelationIds != NIL);
/*-----
* Now we have all the indexes we want to process in indexIds.
*
* The phases now are:
*
* 1. create new indexes in the catalog
* 2. build new indexes
* 3. let new indexes catch up with tuples inserted in the meantime
* 4. swap index names
* 5. mark old indexes as dead
* 6. drop old indexes
*
* We process each phase for all indexes before moving to the next phase,
* for efficiency.
*/
/*
* Phase 1 of REINDEX CONCURRENTLY
*
* Create a new index with the same properties as the old one, but it is
* only registered in catalogs and will be built later. Then get session
* locks on all involved tables. See analogous code in DefineIndex() for
* more detailed comments.
*/
foreach(lc, indexIds)
{
char *concurrentName;
ReindexIndexInfo *idx = lfirst(lc);
ReindexIndexInfo *newidx;
Oid newIndexId;
Relation indexRel;
Relation heapRel;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
Relation newIndexRel;
LockRelId *lockrelid;
Oid tablespaceid;
indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
heapRel = table_open(indexRel->rd_index->indrelid,
ShareUpdateExclusiveLock);
/*
* Switch to the table owner's userid, so that any index functions are
* run as that user. Also lock down security-restricted operations
* and arrange to make GUC variable changes local to this command.
*/
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(heapRel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
RestrictSearchPath();
/* determine safety of this index for set_indexsafe_procflags */
idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
RelationGetIndexPredicate(indexRel) == NIL);
#ifdef USE_INJECTION_POINTS
if (idx->safe)
INJECTION_POINT("reindex-conc-index-safe", NULL);
else
INJECTION_POINT("reindex-conc-index-not-safe", NULL);
#endif
idx->tableId = RelationGetRelid(heapRel);
idx->amId = indexRel->rd_rel->relam;
/* This function shouldn't be called for temporary relations. */
if (indexRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
elog(ERROR, "cannot reindex a temporary table concurrently");
pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, idx->tableId);
progress_vals[0] = PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY;
progress_vals[1] = 0; /* initializing */
progress_vals[2] = idx->indexId;
progress_vals[3] = idx->amId;
pgstat_progress_update_multi_param(4, progress_index, progress_vals);
/* Choose a temporary relation name for the new index */
concurrentName = ChooseRelationName(get_rel_name(idx->indexId),
NULL,
"ccnew",
get_rel_namespace(indexRel->rd_index->indrelid),
false);
/* Choose the new tablespace, indexes of toast tables are not moved */
if (OidIsValid(params->tablespaceOid) &&
heapRel->rd_rel->relkind != RELKIND_TOASTVALUE)
tablespaceid = params->tablespaceOid;
else
tablespaceid = indexRel->rd_rel->reltablespace;
/* Create new index definition based on given index */
newIndexId = index_create_copy(heapRel,
INDEX_CREATE_CONCURRENT |
INDEX_CREATE_SKIP_BUILD |
INDEX_CREATE_SUPPRESS_PROGRESS,
idx->indexId,
tablespaceid,
concurrentName);
/*
* Now open the relation of the new index, a session-level lock is
* also needed on it.
*/
newIndexRel = index_open(newIndexId, ShareUpdateExclusiveLock);
/*
* Save the list of OIDs and locks in private context
*/
oldcontext = MemoryContextSwitchTo(private_context);
newidx = palloc_object(ReindexIndexInfo);
newidx->indexId = newIndexId;
newidx->safe = idx->safe;
newidx->tableId = idx->tableId;
newidx->amId = idx->amId;
newIndexIds = lappend(newIndexIds, newidx);
/*
* Save lockrelid to protect each relation from drop then close
* relations. The lockrelid on parent relation is not taken here to
* avoid multiple locks taken on the same relation, instead we rely on
* parentRelationIds built earlier.
*/
lockrelid = palloc_object(LockRelId);
*lockrelid = indexRel->rd_lockInfo.lockRelId;
relationLocks = lappend(relationLocks, lockrelid);
lockrelid = palloc_object(LockRelId);
*lockrelid = newIndexRel->rd_lockInfo.lockRelId;
relationLocks = lappend(relationLocks, lockrelid);
MemoryContextSwitchTo(oldcontext);
index_close(indexRel, NoLock);
index_close(newIndexRel, NoLock);
/* Roll back any GUC changes executed by index functions */
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
table_close(heapRel, NoLock);
/*
* If a statement is available, telling that this comes from a REINDEX
* command, collect the new index for event triggers.
*/
if (stmt)
{
ObjectAddress address;
ObjectAddressSet(address, RelationRelationId, newIndexId);
EventTriggerCollectSimpleCommand(address,
InvalidObjectAddress,
(const Node *) stmt);
}
}
/*
* Save the heap lock for following visibility checks with other backends
* might conflict with this session.
*/
foreach(lc, heapRelationIds)
{
Relation heapRelation = table_open(lfirst_oid(lc), ShareUpdateExclusiveLock);
LockRelId *lockrelid;
LOCKTAG *heaplocktag;
/* Save the list of locks in private context */
oldcontext = MemoryContextSwitchTo(private_context);
/* Add lockrelid of heap relation to the list of locked relations */
lockrelid = palloc_object(LockRelId);
*lockrelid = heapRelation->rd_lockInfo.lockRelId;
relationLocks = lappend(relationLocks, lockrelid);
heaplocktag = palloc_object(LOCKTAG);
/* Save the LOCKTAG for this parent relation for the wait phase */
SET_LOCKTAG_RELATION(*heaplocktag, lockrelid->dbId, lockrelid->relId);
lockTags = lappend(lockTags, heaplocktag);
MemoryContextSwitchTo(oldcontext);
/* Close heap relation */
table_close(heapRelation, NoLock);
}
/* Get a session-level lock on each table. */
foreach(lc, relationLocks)
{
LockRelId *lockrelid = (LockRelId *) lfirst(lc);
LockRelationIdForSession(lockrelid, ShareUpdateExclusiveLock);
}
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
/*
* Because we don't take a snapshot in this transaction, there's no need
* to set the PROC_IN_SAFE_IC flag here.
*/
/*
* Phase 2 of REINDEX CONCURRENTLY
*
* Build the new indexes in a separate transaction for each index to avoid
* having open transactions for an unnecessary long time. But before
* doing that, wait until no running transactions could have the table of
* the index open with the old list of indexes. See "phase 2" in
* DefineIndex() for more details.
*/
pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_PHASE_WAIT_1);
WaitForLockersMultiple(lockTags, ShareLock, true);
CommitTransactionCommand();
foreach(lc, newIndexIds)
{
ReindexIndexInfo *newidx = lfirst(lc);
/* Start new transaction for this index's concurrent build */
StartTransactionCommand();
/*
* Check for user-requested abort. This is inside a transaction so as
* xact.c does not issue a useless WARNING, and ensures that
* session-level locks are cleaned up on abort.
*/
CHECK_FOR_INTERRUPTS();
/* Tell concurrent indexing to ignore us, if index qualifies */
if (newidx->safe)
set_indexsafe_procflags();
/* Set ActiveSnapshot since functions in the indexes may need it */
PushActiveSnapshot(GetTransactionSnapshot());
/*
* Update progress for the index to build, with the correct parent
* table involved.
*/
pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, newidx->tableId);
progress_vals[0] = PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY;
progress_vals[1] = PROGRESS_CREATEIDX_PHASE_BUILD;
progress_vals[2] = newidx->indexId;
progress_vals[3] = newidx->amId;
pgstat_progress_update_multi_param(4, progress_index, progress_vals);
/* Perform concurrent build of new index */
index_concurrently_build(newidx->tableId, newidx->indexId);
PopActiveSnapshot();
CommitTransactionCommand();
}
StartTransactionCommand();
/*
* Because we don't take a snapshot or Xid in this transaction, there's no
* need to set the PROC_IN_SAFE_IC flag here.
*/
/*
* Phase 3 of REINDEX CONCURRENTLY
*
* During this phase the old indexes catch up with any new tuples that
* were created during the previous phase. See "phase 3" in DefineIndex()
* for more details.
*/
pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_PHASE_WAIT_2);
WaitForLockersMultiple(lockTags, ShareLock, true);
CommitTransactionCommand();
foreach(lc, newIndexIds)
{
ReindexIndexInfo *newidx = lfirst(lc);
TransactionId limitXmin;
Snapshot snapshot;
StartTransactionCommand();
/*
* Check for user-requested abort. This is inside a transaction so as
* xact.c does not issue a useless WARNING, and ensures that
* session-level locks are cleaned up on abort.
*/
CHECK_FOR_INTERRUPTS();
/* Tell concurrent indexing to ignore us, if index qualifies */
if (newidx->safe)
set_indexsafe_procflags();
/*
* Take the "reference snapshot" that will be used by validate_index()
* to filter candidate tuples.
*/
snapshot = RegisterSnapshot(GetTransactionSnapshot());
PushActiveSnapshot(snapshot);
/*
* Update progress for the index to build, with the correct parent
* table involved.
*/
pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, newidx->tableId);
progress_vals[0] = PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY;
progress_vals[1] = PROGRESS_CREATEIDX_PHASE_VALIDATE_IDXSCAN;
progress_vals[2] = newidx->indexId;
progress_vals[3] = newidx->amId;
pgstat_progress_update_multi_param(4, progress_index, progress_vals);
validate_index(newidx->tableId, newidx->indexId, snapshot);
/*
* We can now do away with our active snapshot, we still need to save
* the xmin limit to wait for older snapshots.
*/
limitXmin = snapshot->xmin;
PopActiveSnapshot();
UnregisterSnapshot(snapshot);
/*
* To ensure no deadlocks, we must commit and start yet another
* transaction, and do our wait before any snapshot has been taken in
* it.
*/
CommitTransactionCommand();
StartTransactionCommand();
/*
* The index is now valid in the sense that it contains all currently
* interesting tuples. But since it might not contain tuples deleted
* just before the reference snap was taken, we have to wait out any
* transactions that might have older snapshots.
*
* Because we don't take a snapshot or Xid in this transaction,
* there's no need to set the PROC_IN_SAFE_IC flag here.
*/
pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_PHASE_WAIT_3);
WaitForOlderSnapshots(limitXmin, true);
CommitTransactionCommand();
}
/*
* Phase 4 of REINDEX CONCURRENTLY
*
* Now that the new indexes have been validated, swap each new index with
* its corresponding old index.
*
* We mark the new indexes as valid and the old indexes as not valid at
* the same time to make sure we only get constraint violations from the
* indexes with the correct names.
*/
INJECTION_POINT("reindex-relation-concurrently-before-swap", NULL);
StartTransactionCommand();
/*
* Because this transaction only does catalog manipulations and doesn't do
* any index operations, we can set the PROC_IN_SAFE_IC flag here
* unconditionally.
*/
set_indexsafe_procflags();
forboth(lc, indexIds, lc2, newIndexIds)
{
ReindexIndexInfo *oldidx = lfirst(lc);
ReindexIndexInfo *newidx = lfirst(lc2);
char *oldName;
/*
* Check for user-requested abort. This is inside a transaction so as
* xact.c does not issue a useless WARNING, and ensures that
* session-level locks are cleaned up on abort.
*/
CHECK_FOR_INTERRUPTS();
/* Choose a relation name for old index */
oldName = ChooseRelationName(get_rel_name(oldidx->indexId),
NULL,
"ccold",
get_rel_namespace(oldidx->tableId),
false);
/*
* Swapping the indexes might involve TOAST table access, so ensure we
* have a valid snapshot.
*/
PushActiveSnapshot(GetTransactionSnapshot());
/*
* Swap old index with the new one. This also marks the new one as
* valid and the old one as not valid.
*/
index_concurrently_swap(newidx->indexId, oldidx->indexId, oldName);
PopActiveSnapshot();
/*
* Invalidate the relcache for the table, so that after this commit
* all sessions will refresh any cached plans that might reference the
* index.
*/
CacheInvalidateRelcacheByRelid(oldidx->tableId);
/*
* CCI here so that subsequent iterations see the oldName in the
* catalog and can choose a nonconflicting name for their oldName.
* Otherwise, this could lead to conflicts if a table has two indexes
* whose names are equal for the first NAMEDATALEN-minus-a-few
* characters.
*/
CommandCounterIncrement();
}
/* Commit this transaction and make index swaps visible */
CommitTransactionCommand();
StartTransactionCommand();
/*
* While we could set PROC_IN_SAFE_IC if all indexes qualified, there's no
* real need for that, because we only acquire an Xid after the wait is
* done, and that lasts for a very short period.
*/
/*
* Phase 5 of REINDEX CONCURRENTLY
*
* Mark the old indexes as dead. First we must wait until no running
* transaction could be using the index for a query. See also
* index_drop() for more details.
*/
INJECTION_POINT("reindex-relation-concurrently-before-set-dead", NULL);
pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_PHASE_WAIT_4);
WaitForLockersMultiple(lockTags, AccessExclusiveLock, true);
foreach(lc, indexIds)
{
ReindexIndexInfo *oldidx = lfirst(lc);
/*
* Check for user-requested abort. This is inside a transaction so as
* xact.c does not issue a useless WARNING, and ensures that
* session-level locks are cleaned up on abort.
*/
CHECK_FOR_INTERRUPTS();
/*
* Updating pg_index might involve TOAST table access, so ensure we
* have a valid snapshot.
*/
PushActiveSnapshot(GetTransactionSnapshot());
index_concurrently_set_dead(oldidx->tableId, oldidx->indexId);
PopActiveSnapshot();
}
/* Commit this transaction to make the updates visible. */
CommitTransactionCommand();
StartTransactionCommand();
/*
* While we could set PROC_IN_SAFE_IC if all indexes qualified, there's no
* real need for that, because we only acquire an Xid after the wait is
* done, and that lasts for a very short period.
*/
/*
* Phase 6 of REINDEX CONCURRENTLY
*
* Drop the old indexes.
*/
pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
PROGRESS_CREATEIDX_PHASE_WAIT_5);
WaitForLockersMultiple(lockTags, AccessExclusiveLock, true);
PushActiveSnapshot(GetTransactionSnapshot());
{
ObjectAddresses *objects = new_object_addresses();
foreach(lc, indexIds)
{
ReindexIndexInfo *idx = lfirst(lc);
ObjectAddress object;
object.classId = RelationRelationId;
object.objectId = idx->indexId;
object.objectSubId = 0;
add_exact_object_address(&object, objects);
}
/*
* Use PERFORM_DELETION_CONCURRENT_LOCK so that index_drop() uses the
* right lock level.
*/
performMultipleDeletions(objects, DROP_RESTRICT,
PERFORM_DELETION_CONCURRENT_LOCK | PERFORM_DELETION_INTERNAL);
}
PopActiveSnapshot();
CommitTransactionCommand();
/*
* Finally, release the session-level lock on the table.
*/
foreach(lc, relationLocks)
{
LockRelId *lockrelid = (LockRelId *) lfirst(lc);
UnlockRelationIdForSession(lockrelid, ShareUpdateExclusiveLock);
}
/* Start a new transaction to finish process properly */
StartTransactionCommand();
/* Log what we did */
if ((params->options & REINDEXOPT_VERBOSE) != 0)
{
if (relkind == RELKIND_INDEX)
ereport(INFO,
(errmsg("index \"%s.%s\" was reindexed",
relationNamespace, relationName),
errdetail("%s.",
pg_rusage_show(&ru0))));
else
{
foreach(lc, newIndexIds)
{
ReindexIndexInfo *idx = lfirst(lc);
Oid indOid = idx->indexId;
ereport(INFO,
(errmsg("index \"%s.%s\" was reindexed",
get_namespace_name(get_rel_namespace(indOid)),
get_rel_name(indOid))));
/* Don't show rusage here, since it's not per index. */
}
ereport(INFO,
(errmsg("table \"%s.%s\" was reindexed",
relationNamespace, relationName),
errdetail("%s.",
pg_rusage_show(&ru0))));
}
}
MemoryContextDelete(private_context);
pgstat_progress_end_command();
return true;
}
/*
* Insert or delete an appropriate pg_inherits tuple to make the given index
* be a partition of the indicated parent index.
*
* This also corrects the pg_depend information for the affected index.
*/
void
IndexSetParentIndex(Relation partitionIdx, Oid parentOid)
{
Relation pg_inherits;
ScanKeyData key[2];
SysScanDesc scan;
Oid partRelid = RelationGetRelid(partitionIdx);
HeapTuple tuple;
bool fix_dependencies;
/* Make sure this is an index */
Assert(partitionIdx->rd_rel->relkind == RELKIND_INDEX ||
partitionIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
/*
* Scan pg_inherits for rows linking our index to some parent.
*/
pg_inherits = relation_open(InheritsRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(partRelid));
ScanKeyInit(&key[1],
Anum_pg_inherits_inhseqno,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum(1));
scan = systable_beginscan(pg_inherits, InheritsRelidSeqnoIndexId, true,
NULL, 2, key);
tuple = systable_getnext(scan);
if (!HeapTupleIsValid(tuple))
{
if (parentOid == InvalidOid)
{
/*
* No pg_inherits row, and no parent wanted: nothing to do in this
* case.
*/
fix_dependencies = false;
}
else
{
StoreSingleInheritance(partRelid, parentOid, 1);
fix_dependencies = true;
}
}
else
{
Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(tuple);
if (parentOid == InvalidOid)
{
/*
* There exists a pg_inherits row, which we want to clear; do so.
*/
CatalogTupleDelete(pg_inherits, &tuple->t_self);
fix_dependencies = true;
}
else
{
/*
* A pg_inherits row exists. If it's the same we want, then we're
* good; if it differs, that amounts to a corrupt catalog and
* should not happen.
*/
if (inhForm->inhparent != parentOid)
{
/* unexpected: we should not get called in this case */
elog(ERROR, "bogus pg_inherit row: inhrelid %u inhparent %u",
inhForm->inhrelid, inhForm->inhparent);
}
/* already in the right state */
fix_dependencies = false;
}
}
/* done with pg_inherits */
systable_endscan(scan);
relation_close(pg_inherits, RowExclusiveLock);
/* set relhassubclass if an index partition has been added to the parent */
if (OidIsValid(parentOid))
{
LockRelationOid(parentOid, ShareUpdateExclusiveLock);
SetRelationHasSubclass(parentOid, true);
}
/* set relispartition correctly on the partition */
update_relispartition(partRelid, OidIsValid(parentOid));
if (fix_dependencies)
{
/*
* Insert/delete pg_depend rows. If setting a parent, add PARTITION
* dependencies on the parent index and the table; if removing a
* parent, delete PARTITION dependencies.
*/
if (OidIsValid(parentOid))
{
ObjectAddress partIdx;
ObjectAddress parentIdx;
ObjectAddress partitionTbl;
ObjectAddressSet(partIdx, RelationRelationId, partRelid);
ObjectAddressSet(parentIdx, RelationRelationId, parentOid);
ObjectAddressSet(partitionTbl, RelationRelationId,
partitionIdx->rd_index->indrelid);
recordDependencyOn(&partIdx, &parentIdx,
DEPENDENCY_PARTITION_PRI);
recordDependencyOn(&partIdx, &partitionTbl,
DEPENDENCY_PARTITION_SEC);
}
else
{
deleteDependencyRecordsForClass(RelationRelationId, partRelid,
RelationRelationId,
DEPENDENCY_PARTITION_PRI);
deleteDependencyRecordsForClass(RelationRelationId, partRelid,
RelationRelationId,
DEPENDENCY_PARTITION_SEC);
}
/* make our updates visible */
CommandCounterIncrement();
}
}
/*
* Subroutine of IndexSetParentIndex to update the relispartition flag of the
* given index to the given value.
*/
static void
update_relispartition(Oid relationId, bool newval)
{
HeapTuple tup;
Relation classRel;
ItemPointerData otid;
classRel = table_open(RelationRelationId, RowExclusiveLock);
tup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(relationId));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for relation %u", relationId);
otid = tup->t_self;
Assert(((Form_pg_class) GETSTRUCT(tup))->relispartition != newval);
((Form_pg_class) GETSTRUCT(tup))->relispartition = newval;
CatalogTupleUpdate(classRel, &otid, tup);
UnlockTuple(classRel, &otid, InplaceUpdateTupleLock);
heap_freetuple(tup);
table_close(classRel, RowExclusiveLock);
}
/*
* Set the PROC_IN_SAFE_IC flag in MyProc->statusFlags.
*
* When doing concurrent index builds, we can set this flag
* to tell other processes concurrently running CREATE
* INDEX CONCURRENTLY or REINDEX CONCURRENTLY to ignore us when
* doing their waits for concurrent snapshots. On one hand it
* avoids pointlessly waiting for a process that's not interesting
* anyway; but more importantly it avoids deadlocks in some cases.
*
* This can be done safely only for indexes that don't execute any
* expressions that could access other tables, so index must not be
* expressional nor partial. Caller is responsible for only calling
* this routine when that assumption holds true.
*
* (The flag is reset automatically at transaction end, so it must be
* set for each transaction.)
*/
static inline void
set_indexsafe_procflags(void)
{
/*
* This should only be called before installing xid or xmin in MyProc;
* otherwise, concurrent processes could see an Xmin that moves backwards.
*/
Assert(MyProc->xid == InvalidTransactionId &&
MyProc->xmin == InvalidTransactionId);
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_SAFE_IC;
ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
./indexing.c 0000664 0001750 0001750 00000025511 15221144670 011664 0 ustar xman xman /*-------------------------------------------------------------------------
*
* indexing.c
* This file contains routines to support indexes defined on system
* catalogs.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/catalog/indexing.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "executor/executor.h"
#include "utils/rel.h"
/*
* CatalogOpenIndexes - open the indexes on a system catalog.
*
* When inserting or updating tuples in a system catalog, call this
* to prepare to update the indexes for the catalog.
*
* In the current implementation, we share code for opening/closing the
* indexes with execUtils.c. But we do not use ExecInsertIndexTuples,
* because we don't want to create an EState. This implies that we
* do not support partial or expressional indexes on system catalogs,
* nor can we support generalized exclusion constraints.
* This could be fixed with localized changes here if we wanted to pay
* the extra overhead of building an EState.
*/
CatalogIndexState
CatalogOpenIndexes(Relation heapRel)
{
ResultRelInfo *resultRelInfo;
resultRelInfo = makeNode(ResultRelInfo);
resultRelInfo->ri_RangeTableIndex = 0; /* dummy */
resultRelInfo->ri_RelationDesc = heapRel;
resultRelInfo->ri_TrigDesc = NULL; /* we don't fire triggers */
ExecOpenIndices(resultRelInfo, false);
return resultRelInfo;
}
/*
* CatalogCloseIndexes - clean up resources allocated by CatalogOpenIndexes
*/
void
CatalogCloseIndexes(CatalogIndexState indstate)
{
ExecCloseIndices(indstate);
pfree(indstate);
}
/*
* CatalogIndexInsert - insert index entries for one catalog tuple
*
* This should be called for each inserted or updated catalog tuple.
*
* This is effectively a cut-down version of ExecInsertIndexTuples.
*/
static void
CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
TU_UpdateIndexes updateIndexes)
{
int i;
int numIndexes;
RelationPtr relationDescs;
Relation heapRelation;
TupleTableSlot *slot;
IndexInfo **indexInfoArray;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
bool onlySummarized = (updateIndexes == TU_Summarizing);
/*
* HOT update does not require index inserts. But with asserts enabled we
* want to check that it'd be legal to currently insert into the
* table/index.
*/
#ifndef USE_ASSERT_CHECKING
if (HeapTupleIsHeapOnly(heapTuple) && !onlySummarized)
return;
#endif
/* When only updating summarized indexes, the tuple has to be HOT. */
Assert((!onlySummarized) || HeapTupleIsHeapOnly(heapTuple));
/*
* Get information from the state structure. Fall out if nothing to do.
*/
numIndexes = indstate->ri_NumIndices;
if (numIndexes == 0)
return;
relationDescs = indstate->ri_IndexRelationDescs;
indexInfoArray = indstate->ri_IndexRelationInfo;
heapRelation = indstate->ri_RelationDesc;
/* Need a slot to hold the tuple being examined */
slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
&TTSOpsHeapTuple);
ExecStoreHeapTuple(heapTuple, slot, false);
/*
* for each index, form and insert the index tuple
*/
for (i = 0; i < numIndexes; i++)
{
IndexInfo *indexInfo;
Relation index;
indexInfo = indexInfoArray[i];
index = relationDescs[i];
/* If the index is marked as read-only, ignore it */
if (!indexInfo->ii_ReadyForInserts)
continue;
/*
* Expressional and partial indexes on system catalogs are not
* supported, nor exclusion constraints, nor deferred uniqueness
*/
Assert(indexInfo->ii_Expressions == NIL);
Assert(indexInfo->ii_ExpressionsExpand == NIL);
Assert(indexInfo->ii_Predicate == NIL);
Assert(indexInfo->ii_PredicateExpand == NIL);
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(index->rd_index->indimmediate);
Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
/* see earlier check above */
#ifdef USE_ASSERT_CHECKING
if (HeapTupleIsHeapOnly(heapTuple) && !onlySummarized)
{
Assert(!ReindexIsProcessingIndex(RelationGetRelid(index)));
continue;
}
#endif /* USE_ASSERT_CHECKING */
/*
* Skip insertions into non-summarizing indexes if we only need to
* update summarizing indexes.
*/
if (onlySummarized && !indexInfo->ii_Summarizing)
continue;
/*
* FormIndexDatum fills in its values and isnull parameters with the
* appropriate values for the column(s) of the index.
*/
FormIndexDatum(indexInfo,
slot,
NULL, /* no expression eval to do */
values,
isnull);
/*
* The index AM does the rest.
*/
index_insert(index, /* index relation */
values, /* array of index Datums */
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
index->rd_index->indisunique ?
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
false,
indexInfo);
}
ExecDropSingleTupleTableSlot(slot);
}
/*
* Subroutine to verify that catalog constraints are honored.
*
* Tuples inserted via CatalogTupleInsert/CatalogTupleUpdate are generally
* "hand made", so that it's possible that they fail to satisfy constraints
* that would be checked if they were being inserted by the executor. That's
* a coding error, so we only bother to check for it in assert-enabled builds.
*/
#ifdef USE_ASSERT_CHECKING
static void
CatalogTupleCheckConstraints(Relation heapRel, HeapTuple tup)
{
/*
* Currently, the only constraints implemented for system catalogs are
* attnotnull constraints.
*/
if (HeapTupleHasNulls(tup))
{
TupleDesc tupdesc = RelationGetDescr(heapRel);
uint8 *bp = tup->t_data->t_bits;
for (int attnum = 0; attnum < tupdesc->natts; attnum++)
{
Form_pg_attribute thisatt = TupleDescAttr(tupdesc, attnum);
Assert(!(thisatt->attnotnull && att_isnull(attnum, bp)));
}
}
}
#else /* !USE_ASSERT_CHECKING */
#define CatalogTupleCheckConstraints(heapRel, tup) ((void) 0)
#endif /* USE_ASSERT_CHECKING */
/*
* CatalogTupleInsert - do heap and indexing work for a new catalog tuple
*
* Insert the tuple data in "tup" into the specified catalog relation.
*
* This is a convenience routine for the common case of inserting a single
* tuple in a system catalog; it inserts a new heap tuple, keeping indexes
* current. Avoid using it for multiple tuples, since opening the indexes
* and building the index info structures is moderately expensive.
* (Use CatalogTupleInsertWithInfo in such cases.)
*/
void
CatalogTupleInsert(Relation heapRel, HeapTuple tup)
{
CatalogIndexState indstate;
CatalogTupleCheckConstraints(heapRel, tup);
indstate = CatalogOpenIndexes(heapRel);
simple_heap_insert(heapRel, tup);
CatalogIndexInsert(indstate, tup, TU_All);
CatalogCloseIndexes(indstate);
}
/*
* CatalogTupleInsertWithInfo - as above, but with caller-supplied index info
*
* This should be used when it's important to amortize CatalogOpenIndexes/
* CatalogCloseIndexes work across multiple insertions. At some point we
* might cache the CatalogIndexState data somewhere (perhaps in the relcache)
* so that callers needn't trouble over this ... but we don't do so today.
*/
void
CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
CatalogIndexState indstate)
{
CatalogTupleCheckConstraints(heapRel, tup);
simple_heap_insert(heapRel, tup);
CatalogIndexInsert(indstate, tup, TU_All);
}
/*
* CatalogTuplesMultiInsertWithInfo - as above, but for multiple tuples
*
* Insert multiple tuples into the given catalog relation at once, with an
* amortized cost of CatalogOpenIndexes.
*/
void
CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
int ntuples, CatalogIndexState indstate)
{
/* Nothing to do */
if (ntuples <= 0)
return;
heap_multi_insert(heapRel, slot, ntuples,
GetCurrentCommandId(true), 0, NULL);
/*
* There is no equivalent to heap_multi_insert for the catalog indexes, so
* we must loop over and insert individually.
*/
for (int i = 0; i < ntuples; i++)
{
bool should_free;
HeapTuple tuple;
tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
tuple->t_tableOid = slot[i]->tts_tableOid;
CatalogIndexInsert(indstate, tuple, TU_All);
if (should_free)
heap_freetuple(tuple);
}
}
/*
* CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
*
* Update the tuple identified by "otid", replacing it with the data in "tup".
*
* This is a convenience routine for the common case of updating a single
* tuple in a system catalog; it updates one heap tuple, keeping indexes
* current. Avoid using it for multiple tuples, since opening the indexes
* and building the index info structures is moderately expensive.
* (Use CatalogTupleUpdateWithInfo in such cases.)
*/
void
CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
{
CatalogIndexState indstate;
TU_UpdateIndexes updateIndexes = TU_All;
CatalogTupleCheckConstraints(heapRel, tup);
indstate = CatalogOpenIndexes(heapRel);
simple_heap_update(heapRel, otid, tup, &updateIndexes);
CatalogIndexInsert(indstate, tup, updateIndexes);
CatalogCloseIndexes(indstate);
}
/*
* CatalogTupleUpdateWithInfo - as above, but with caller-supplied index info
*
* This should be used when it's important to amortize CatalogOpenIndexes/
* CatalogCloseIndexes work across multiple updates. At some point we
* might cache the CatalogIndexState data somewhere (perhaps in the relcache)
* so that callers needn't trouble over this ... but we don't do so today.
*/
void
CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTuple tup,
CatalogIndexState indstate)
{
TU_UpdateIndexes updateIndexes = TU_All;
CatalogTupleCheckConstraints(heapRel, tup);
simple_heap_update(heapRel, otid, tup, &updateIndexes);
CatalogIndexInsert(indstate, tup, updateIndexes);
}
/*
* CatalogTupleDelete - do heap and indexing work for deleting a catalog tuple
*
* Delete the tuple identified by "tid" in the specified catalog.
*
* With Postgres heaps, there is no index work to do at deletion time;
* cleanup will be done later by VACUUM. However, callers of this function
* shouldn't have to know that; we'd like a uniform abstraction for all
* catalog tuple changes. Hence, provide this currently-trivial wrapper.
*
* The abstraction is a bit leaky in that we don't provide an optimized
* CatalogTupleDeleteWithInfo version, because there is currently nothing to
* optimize. If we ever need that, rather than touching a lot of call sites,
* it might be better to do something about caching CatalogIndexState.
*/
void
CatalogTupleDelete(Relation heapRel, const ItemPointerData *tid)
{
simple_heap_delete(heapRel, tid);
}
./indxpath.c 0000664 0001750 0001750 00000425363 15221714454 011712 0 ustar xman xman /*-------------------------------------------------------------------------
*
* indxpath.c
* Routines to determine which indexes are usable for scanning a
* given relation, and create Paths accordingly.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/optimizer/path/indxpath.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/stratnum.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "utils/lsyscache.h"
#include "utils/selfuncs.h"
/* XXX see PartCollMatchesExprColl */
#define IndexCollMatchesExprColl(idxcollation, exprcollation) \
((idxcollation) == InvalidOid || (idxcollation) == (exprcollation))
/* Whether we are looking for plain indexscan, bitmap scan, or either */
typedef enum
{
ST_INDEXSCAN, /* must support amgettuple */
ST_BITMAPSCAN, /* must support amgetbitmap */
ST_ANYSCAN, /* either is okay */
} ScanTypeControl;
/* Data structure for collecting qual clauses that match an index */
typedef struct
{
bool nonempty; /* True if lists are not all empty */
/* Lists of IndexClause nodes, one list per index column */
List *indexclauses[INDEX_MAX_KEYS];
} IndexClauseSet;
/* Per-path data used within choose_bitmap_and() */
typedef struct
{
Path *path; /* IndexPath, BitmapAndPath, or BitmapOrPath */
List *quals; /* the WHERE clauses it uses */
List *preds; /* predicates of its partial index(es) */
Bitmapset *clauseids; /* quals+preds represented as a bitmapset */
bool unclassifiable; /* has too many quals+preds to process? */
} PathClauseUsage;
/* Callback argument for ec_member_matches_indexcol */
typedef struct
{
IndexOptInfo *index; /* index we're considering */
int indexcol; /* index column we want to match to */
} ec_member_matches_arg;
static void consider_index_join_clauses(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index,
IndexClauseSet *rclauseset,
IndexClauseSet *jclauseset,
IndexClauseSet *eclauseset,
List **bitindexpaths);
static void consider_index_join_outer_rels(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index,
IndexClauseSet *rclauseset,
IndexClauseSet *jclauseset,
IndexClauseSet *eclauseset,
List **bitindexpaths,
List *indexjoinclauses,
int considered_clauses,
List **considered_relids);
static void get_join_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index,
IndexClauseSet *rclauseset,
IndexClauseSet *jclauseset,
IndexClauseSet *eclauseset,
List **bitindexpaths,
Relids relids,
List **considered_relids);
static bool eclass_already_used(EquivalenceClass *parent_ec, Relids oldrelids,
List *indexjoinclauses);
static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
List **bitindexpaths);
static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop);
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses);
static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses);
static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel,
List *paths);
static int path_usage_comparator(const void *a, const void *b);
static Cost bitmap_scan_cost_est(PlannerInfo *root, RelOptInfo *rel,
Path *ipath);
static Cost bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel,
List *paths);
static PathClauseUsage *classify_index_clause_usage(Path *path,
List **clauselist);
static void find_indexpath_quals(Path *bitmapqual, List **quals, List **preds);
static int find_list_position(Node *node, List **nodelist);
static bool check_index_only(RelOptInfo *rel, IndexOptInfo *index);
static double get_loop_count(PlannerInfo *root, Index cur_relid, Relids outer_relids);
static double adjust_rowcount_for_semijoins(PlannerInfo *root,
Index cur_relid,
Index outer_relid,
double rowcount);
static double approximate_joinrel_size(PlannerInfo *root, Relids relids);
static void match_restriction_clauses_to_index(PlannerInfo *root,
IndexOptInfo *index,
IndexClauseSet *clauseset);
static void match_join_clauses_to_index(PlannerInfo *root,
RelOptInfo *rel, IndexOptInfo *index,
IndexClauseSet *clauseset,
List **joinorclauses);
static void match_eclass_clauses_to_index(PlannerInfo *root,
IndexOptInfo *index,
IndexClauseSet *clauseset);
static void match_clauses_to_index(PlannerInfo *root,
List *clauses,
IndexOptInfo *index,
IndexClauseSet *clauseset);
static void match_clause_to_index(PlannerInfo *root,
RestrictInfo *rinfo,
IndexOptInfo *index,
IndexClauseSet *clauseset);
static IndexClause *match_clause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index);
static bool IsBooleanOpfamily(Oid opfamily);
static IndexClause *match_boolean_index_clause(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol, IndexOptInfo *index);
static IndexClause *match_opclause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index);
static IndexClause *match_funcclause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index);
static IndexClause *get_index_clause_from_support(PlannerInfo *root,
RestrictInfo *rinfo,
Oid funcid,
int indexarg,
int indexcol,
IndexOptInfo *index);
static IndexClause *match_saopclause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index);
static IndexClause *match_rowcompare_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index);
static IndexClause *match_orclause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index);
static IndexClause *expand_indexqual_rowcompare(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index,
Oid expr_op,
bool var_on_left);
static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
List **orderby_clauses_p,
List **clause_columns_p);
static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
int indexcol, Expr *clause, Oid pk_opfamily);
static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
EquivalenceClass *ec, EquivalenceMember *em,
void *arg);
/*
* create_index_paths()
* Generate all interesting index paths for the given relation.
* Candidate paths are added to the rel's pathlist (using add_path).
*
* To be considered for an index scan, an index must match one or more
* restriction clauses or join clauses from the query's qual condition,
* or match the query's ORDER BY condition, or have a predicate that
* matches the query's qual condition.
*
* There are two basic kinds of index scans. A "plain" index scan uses
* only restriction clauses (possibly none at all) in its indexqual,
* so it can be applied in any context. A "parameterized" index scan uses
* join clauses (plus restriction clauses, if available) in its indexqual.
* When joining such a scan to one of the relations supplying the other
* variables used in its indexqual, the parameterized scan must appear as
* the inner relation of a nestloop join; it can't be used on the outer side,
* nor in a merge or hash join. In that context, values for the other rels'
* attributes are available and fixed during any one scan of the indexpath.
*
* An IndexPath is generated and submitted to add_path() for each plain or
* parameterized index scan this routine deems potentially interesting for
* the current query.
*
* 'rel' is the relation for which we want to generate index paths
*
* Note: check_index_predicates() must have been run previously for this rel.
*
* Note: in cases involving LATERAL references in the relation's tlist, it's
* possible that rel->lateral_relids is nonempty. Currently, we include
* lateral_relids into the parameterization reported for each path, but don't
* take it into account otherwise. The fact that any such rels *must* be
* available as parameter sources perhaps should influence our choices of
* index quals ... but for now, it doesn't seem worth troubling over.
* In particular, comments below about "unparameterized" paths should be read
* as meaning "unparameterized so far as the indexquals are concerned".
*/
void
create_index_paths(PlannerInfo *root, RelOptInfo *rel)
{
List *indexpaths;
List *bitindexpaths;
List *bitjoinpaths;
List *joinorclauses;
IndexClauseSet rclauseset;
IndexClauseSet jclauseset;
IndexClauseSet eclauseset;
ListCell *lc;
/* Skip the whole mess if no indexes */
if (rel->indexlist == NIL)
return;
/* Bitmap paths are collected and then dealt with at the end */
bitindexpaths = bitjoinpaths = joinorclauses = NIL;
/* Examine each index in turn */
foreach(lc, rel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
/* Protect limited-size array in IndexClauseSets */
Assert(index->nkeycolumns <= INDEX_MAX_KEYS);
/*
* Ignore partial indexes that do not match the query.
* (generate_bitmap_or_paths() might be able to do something with
* them, but that's of no concern here.)
*/
if (index->indpred != NIL && !index->predOK)
continue;
/*
* Identify the restriction clauses that can match the index.
*/
MemSet(&rclauseset, 0, sizeof(rclauseset));
match_restriction_clauses_to_index(root, index, &rclauseset);
/*
* Build index paths from the restriction clauses. These will be
* non-parameterized paths. Plain paths go directly to add_path(),
* bitmap paths are added to bitindexpaths to be handled below.
*/
get_index_paths(root, rel, index, &rclauseset,
&bitindexpaths);
/*
* Identify the join clauses that can match the index. For the moment
* we keep them separate from the restriction clauses. Note that this
* step finds only "loose" join clauses that have not been merged into
* EquivalenceClasses. Also, collect join OR clauses for later.
*/
MemSet(&jclauseset, 0, sizeof(jclauseset));
match_join_clauses_to_index(root, rel, index,
&jclauseset, &joinorclauses);
/*
* Look for EquivalenceClasses that can generate joinclauses matching
* the index.
*/
MemSet(&eclauseset, 0, sizeof(eclauseset));
match_eclass_clauses_to_index(root, index,
&eclauseset);
/*
* If we found any plain or eclass join clauses, build parameterized
* index paths using them.
*/
if (jclauseset.nonempty || eclauseset.nonempty)
consider_index_join_clauses(root, rel, index,
&rclauseset,
&jclauseset,
&eclauseset,
&bitjoinpaths);
}
/*
* Generate BitmapOrPaths for any suitable OR-clauses present in the
* restriction list. Add these to bitindexpaths.
*/
indexpaths = generate_bitmap_or_paths(root, rel,
rel->baserestrictinfo, NIL);
bitindexpaths = list_concat(bitindexpaths, indexpaths);
/*
* Likewise, generate BitmapOrPaths for any suitable OR-clauses present in
* the joinclause list. Add these to bitjoinpaths.
*/
indexpaths = generate_bitmap_or_paths(root, rel,
joinorclauses, rel->baserestrictinfo);
bitjoinpaths = list_concat(bitjoinpaths, indexpaths);
/*
* If we found anything usable, generate a BitmapHeapPath for the most
* promising combination of restriction bitmap index paths. Note there
* will be only one such path no matter how many indexes exist. This
* should be sufficient since there's basically only one figure of merit
* (total cost) for such a path.
*/
if (bitindexpaths != NIL)
{
Path *bitmapqual;
BitmapHeapPath *bpath;
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0, 0);
add_path(rel, (Path *) bpath);
/* create a partial bitmap heap path */
if (rel->consider_parallel && rel->lateral_relids == NULL)
create_partial_bitmap_paths(root, rel, bitmapqual);
}
/*
* Likewise, if we found anything usable, generate BitmapHeapPaths for the
* most promising combinations of join bitmap index paths. Our strategy
* is to generate one such path for each distinct parameterization seen
* among the available bitmap index paths. This may look pretty
* expensive, but usually there won't be very many distinct
* parameterizations. (This logic is quite similar to that in
* consider_index_join_clauses, but we're working with whole paths not
* individual clauses.)
*/
if (bitjoinpaths != NIL)
{
List *all_path_outers;
/* Identify each distinct parameterization seen in bitjoinpaths */
all_path_outers = NIL;
foreach(lc, bitjoinpaths)
{
Path *path = (Path *) lfirst(lc);
Relids required_outer = PATH_REQ_OUTER(path);
all_path_outers = list_append_unique(all_path_outers,
required_outer);
}
/* Now, for each distinct parameterization set ... */
foreach(lc, all_path_outers)
{
Relids max_outers = (Relids) lfirst(lc);
List *this_path_set;
Path *bitmapqual;
Relids required_outer;
double loop_count;
BitmapHeapPath *bpath;
ListCell *lcp;
/* Identify all the bitmap join paths needing no more than that */
this_path_set = NIL;
foreach(lcp, bitjoinpaths)
{
Path *path = (Path *) lfirst(lcp);
if (bms_is_subset(PATH_REQ_OUTER(path), max_outers))
this_path_set = lappend(this_path_set, path);
}
/*
* Add in restriction bitmap paths, since they can be used
* together with any join paths.
*/
this_path_set = list_concat(this_path_set, bitindexpaths);
/* Select best AND combination for this parameterization */
bitmapqual = choose_bitmap_and(root, rel, this_path_set);
/* And push that path into the mix */
required_outer = PATH_REQ_OUTER(bitmapqual);
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count, 0);
add_path(rel, (Path *) bpath);
}
}
}
/*
* consider_index_join_clauses
* Given sets of join clauses for an index, decide which parameterized
* index paths to build.
*
* Plain indexpaths are sent directly to add_path, while potential
* bitmap indexpaths are added to *bitindexpaths for later processing.
*
* 'rel' is the index's heap relation
* 'index' is the index for which we want to generate paths
* 'rclauseset' is the collection of indexable restriction clauses
* 'jclauseset' is the collection of indexable simple join clauses
* 'eclauseset' is the collection of indexable clauses from EquivalenceClasses
* '*bitindexpaths' is the list to add bitmap paths to
*/
static void
consider_index_join_clauses(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index,
IndexClauseSet *rclauseset,
IndexClauseSet *jclauseset,
IndexClauseSet *eclauseset,
List **bitindexpaths)
{
int considered_clauses = 0;
List *considered_relids = NIL;
int indexcol;
/*
* The strategy here is to identify every potentially useful set of outer
* rels that can provide indexable join clauses. For each such set,
* select all the join clauses available from those outer rels, add on all
* the indexable restriction clauses, and generate plain and/or bitmap
* index paths for that set of clauses. This is based on the assumption
* that it's always better to apply a clause as an indexqual than as a
* filter (qpqual); which is where an available clause would end up being
* applied if we omit it from the indexquals.
*
* This looks expensive, but in most practical cases there won't be very
* many distinct sets of outer rels to consider. As a safety valve when
* that's not true, we use a heuristic: limit the number of outer rel sets
* considered to a multiple of the number of clauses considered. (We'll
* always consider using each individual join clause, though.)
*
* For simplicity in selecting relevant clauses, we represent each set of
* outer rels as a maximum set of clause_relids --- that is, the indexed
* relation itself is also included in the relids set. considered_relids
* lists all relids sets we've already tried.
*/
for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
{
/* Consider each applicable simple join clause */
considered_clauses += list_length(jclauseset->indexclauses[indexcol]);
consider_index_join_outer_rels(root, rel, index,
rclauseset, jclauseset, eclauseset,
bitindexpaths,
jclauseset->indexclauses[indexcol],
considered_clauses,
&considered_relids);
/* Consider each applicable eclass join clause */
considered_clauses += list_length(eclauseset->indexclauses[indexcol]);
consider_index_join_outer_rels(root, rel, index,
rclauseset, jclauseset, eclauseset,
bitindexpaths,
eclauseset->indexclauses[indexcol],
considered_clauses,
&considered_relids);
}
}
/*
* consider_index_join_outer_rels
* Generate parameterized paths based on clause relids in the clause list.
*
* Workhorse for consider_index_join_clauses; see notes therein for rationale.
*
* 'rel', 'index', 'rclauseset', 'jclauseset', 'eclauseset', and
* 'bitindexpaths' as above
* 'indexjoinclauses' is a list of IndexClauses for join clauses
* 'considered_clauses' is the total number of clauses considered (so far)
* '*considered_relids' is a list of all relids sets already considered
*/
static void
consider_index_join_outer_rels(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index,
IndexClauseSet *rclauseset,
IndexClauseSet *jclauseset,
IndexClauseSet *eclauseset,
List **bitindexpaths,
List *indexjoinclauses,
int considered_clauses,
List **considered_relids)
{
ListCell *lc;
/* Examine relids of each joinclause in the given list */
foreach(lc, indexjoinclauses)
{
IndexClause *iclause = (IndexClause *) lfirst(lc);
Relids clause_relids = iclause->rinfo->clause_relids;
EquivalenceClass *parent_ec = iclause->rinfo->parent_ec;
int num_considered_relids;
/* If we already tried its relids set, no need to do so again */
if (list_member(*considered_relids, clause_relids))
continue;
/*
* Generate the union of this clause's relids set with each
* previously-tried set. This ensures we try this clause along with
* every interesting subset of previous clauses. However, to avoid
* exponential growth of planning time when there are many clauses,
* limit the number of relid sets accepted to 10 * considered_clauses.
*
* Note: get_join_index_paths appends entries to *considered_relids,
* but we do not need to visit such newly-added entries within this
* loop, so we don't use foreach() here. No real harm would be done
* if we did visit them, since the subset check would reject them; but
* it would waste some cycles.
*/
num_considered_relids = list_length(*considered_relids);
for (int pos = 0; pos < num_considered_relids; pos++)
{
Relids oldrelids = (Relids) list_nth(*considered_relids, pos);
/*
* If either is a subset of the other, no new set is possible.
* This isn't a complete test for redundancy, but it's easy and
* cheap. get_join_index_paths will check more carefully if we
* already generated the same relids set.
*/
if (bms_subset_compare(clause_relids, oldrelids) != BMS_DIFFERENT)
continue;
/*
* If this clause was derived from an equivalence class, the
* clause list may contain other clauses derived from the same
* eclass. We should not consider that combining this clause with
* one of those clauses generates a usefully different
* parameterization; so skip if any clause derived from the same
* eclass would already have been included when using oldrelids.
*/
if (parent_ec &&
eclass_already_used(parent_ec, oldrelids,
indexjoinclauses))
continue;
/*
* If the number of relid sets considered exceeds our heuristic
* limit, stop considering combinations of clauses. We'll still
* consider the current clause alone, though (below this loop).
*/
if (list_length(*considered_relids) >= 10 * considered_clauses)
break;
/* OK, try the union set */
get_join_index_paths(root, rel, index,
rclauseset, jclauseset, eclauseset,
bitindexpaths,
bms_union(clause_relids, oldrelids),
considered_relids);
}
/* Also try this set of relids by itself */
get_join_index_paths(root, rel, index,
rclauseset, jclauseset, eclauseset,
bitindexpaths,
clause_relids,
considered_relids);
}
}
/*
* get_join_index_paths
* Generate index paths using clauses from the specified outer relations.
* In addition to generating paths, relids is added to *considered_relids
* if not already present.
*
* Workhorse for consider_index_join_clauses; see notes therein for rationale.
*
* 'rel', 'index', 'rclauseset', 'jclauseset', 'eclauseset',
* 'bitindexpaths', 'considered_relids' as above
* 'relids' is the current set of relids to consider (the target rel plus
* one or more outer rels)
*/
static void
get_join_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index,
IndexClauseSet *rclauseset,
IndexClauseSet *jclauseset,
IndexClauseSet *eclauseset,
List **bitindexpaths,
Relids relids,
List **considered_relids)
{
IndexClauseSet clauseset;
int indexcol;
/* If we already considered this relids set, don't repeat the work */
if (list_member(*considered_relids, relids))
return;
/* Identify indexclauses usable with this relids set */
MemSet(&clauseset, 0, sizeof(clauseset));
for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
{
ListCell *lc;
/* First find applicable simple join clauses */
foreach(lc, jclauseset->indexclauses[indexcol])
{
IndexClause *iclause = (IndexClause *) lfirst(lc);
if (bms_is_subset(iclause->rinfo->clause_relids, relids))
clauseset.indexclauses[indexcol] =
lappend(clauseset.indexclauses[indexcol], iclause);
}
/*
* Add applicable eclass join clauses. The clauses generated for each
* column are redundant (cf generate_implied_equalities_for_column),
* so we need at most one. This is the only exception to the general
* rule of using all available index clauses.
*/
foreach(lc, eclauseset->indexclauses[indexcol])
{
IndexClause *iclause = (IndexClause *) lfirst(lc);
if (bms_is_subset(iclause->rinfo->clause_relids, relids))
{
clauseset.indexclauses[indexcol] =
lappend(clauseset.indexclauses[indexcol], iclause);
break;
}
}
/* Add restriction clauses */
clauseset.indexclauses[indexcol] =
list_concat(clauseset.indexclauses[indexcol],
rclauseset->indexclauses[indexcol]);
if (clauseset.indexclauses[indexcol] != NIL)
clauseset.nonempty = true;
}
/* We should have found something, else caller passed silly relids */
Assert(clauseset.nonempty);
/* Build index path(s) using the collected set of clauses */
get_index_paths(root, rel, index, &clauseset, bitindexpaths);
/*
* Remember we considered paths for this set of relids.
*/
*considered_relids = lappend(*considered_relids, relids);
}
/*
* eclass_already_used
* True if any join clause usable with oldrelids was generated from
* the specified equivalence class.
*/
static bool
eclass_already_used(EquivalenceClass *parent_ec, Relids oldrelids,
List *indexjoinclauses)
{
ListCell *lc;
foreach(lc, indexjoinclauses)
{
IndexClause *iclause = (IndexClause *) lfirst(lc);
RestrictInfo *rinfo = iclause->rinfo;
if (rinfo->parent_ec == parent_ec &&
bms_is_subset(rinfo->clause_relids, oldrelids))
return true;
}
return false;
}
/*
* get_index_paths
* Given an index and a set of index clauses for it, construct IndexPaths.
*
* Plain indexpaths are sent directly to add_path, while potential
* bitmap indexpaths are added to *bitindexpaths for later processing.
*
* This is a fairly simple frontend to build_index_paths(). Its reason for
* existence is mainly to handle ScalarArrayOpExpr quals properly. If the
* index AM supports them natively, we should just include them in simple
* index paths. If not, we should exclude them while building simple index
* paths, and then make a separate attempt to include them in bitmap paths.
*/
static void
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
List **bitindexpaths)
{
List *indexpaths;
bool skip_nonnative_saop = false;
ListCell *lc;
/*
* Build simple index paths using the clauses. Allow ScalarArrayOpExpr
* clauses only if the index AM supports them natively.
*/
indexpaths = build_index_paths(root, rel,
index, clauses,
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop);
/*
* Submit all the ones that can form plain IndexScan plans to add_path. (A
* plain IndexPath can represent either a plain IndexScan or an
* IndexOnlyScan, but for our purposes here that distinction does not
* matter. However, some of the indexes might support only bitmap scans,
* and those we mustn't submit to add_path here.)
*
* Also, pick out the ones that are usable as bitmap scans. For that, we
* must discard indexes that don't support bitmap scans, and we also are
* only interested in paths that have some selectivity; we should discard
* anything that was generated solely for ordering purposes.
*/
foreach(lc, indexpaths)
{
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
add_path(rel, (Path *) ipath);
if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
ipath->indexselectivity < 1.0))
*bitindexpaths = lappend(*bitindexpaths, ipath);
}
/*
* If there were ScalarArrayOpExpr clauses that the index can't handle
* natively, generate bitmap scan paths relying on executor-managed
* ScalarArrayOpExpr.
*/
if (skip_nonnative_saop)
{
indexpaths = build_index_paths(root, rel,
index, clauses,
false,
ST_BITMAPSCAN,
NULL);
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
}
}
/*
* build_index_paths
* Given an index and a set of index clauses for it, construct zero
* or more IndexPaths. It also constructs zero or more partial IndexPaths.
*
* We return a list of paths because (1) this routine checks some cases
* that should cause us to not generate any IndexPath, and (2) in some
* cases we want to consider both a forward and a backward scan, so as
* to obtain both sort orders. Note that the paths are just returned
* to the caller and not immediately fed to add_path().
*
* At top level, useful_predicate should be exactly the index's predOK flag
* (ie, true if it has a predicate that was proven from the restriction
* clauses). When working on an arm of an OR clause, useful_predicate
* should be true if the predicate required the current OR list to be proven.
* Note that this routine should never be called at all if the index has an
* unprovable predicate.
*
* scantype indicates whether we want to create plain indexscans, bitmap
* indexscans, or both. When it's ST_BITMAPSCAN, we will not consider
* index ordering while deciding if a Path is worth generating.
*
* If skip_nonnative_saop is non-NULL, we ignore ScalarArrayOpExpr clauses
* unless the index AM supports them directly, and we set *skip_nonnative_saop
* to true if we found any such clauses (caller must initialize the variable
* to false). If it's NULL, we do not ignore ScalarArrayOpExpr clauses.
*
* 'rel' is the index's heap relation
* 'index' is the index for which we want to generate paths
* 'clauses' is the collection of indexable clauses (IndexClause nodes)
* 'useful_predicate' indicates whether the index has a useful predicate
* 'scantype' indicates whether we need plain or bitmap scan support
* 'skip_nonnative_saop' indicates whether to accept SAOP if index AM doesn't
*/
static List *
build_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop)
{
List *result = NIL;
IndexPath *ipath;
List *index_clauses;
Relids outer_relids;
double loop_count;
List *orderbyclauses;
List *orderbyclausecols;
List *index_pathkeys;
List *useful_pathkeys;
bool pathkeys_possibly_useful;
bool index_is_ordered;
bool index_only_scan;
int indexcol;
Assert(skip_nonnative_saop != NULL || scantype == ST_BITMAPSCAN);
/*
* Check that index supports the desired scan type(s)
*/
switch (scantype)
{
case ST_INDEXSCAN:
if (!index->amhasgettuple)
return NIL;
break;
case ST_BITMAPSCAN:
if (!index->amhasgetbitmap)
return NIL;
break;
case ST_ANYSCAN:
/* either or both are OK */
break;
}
/*
* 1. Combine the per-column IndexClause lists into an overall list.
*
* In the resulting list, clauses are ordered by index key, so that the
* column numbers form a nondecreasing sequence. (This order is depended
* on by btree and possibly other places.) The list can be empty, if the
* index AM allows that.
*
* We also build a Relids set showing which outer rels are required by the
* selected clauses. Any lateral_relids are included in that, but not
* otherwise accounted for.
*/
index_clauses = NIL;
outer_relids = bms_copy(rel->lateral_relids);
for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
{
ListCell *lc;
foreach(lc, clauses->indexclauses[indexcol])
{
IndexClause *iclause = (IndexClause *) lfirst(lc);
RestrictInfo *rinfo = iclause->rinfo;
if (skip_nonnative_saop && !index->amsearcharray &&
IsA(rinfo->clause, ScalarArrayOpExpr))
{
/*
* Caller asked us to generate IndexPaths that omit any
* ScalarArrayOpExpr clauses when the underlying index AM
* lacks native support.
*
* We must omit this clause (and tell caller about it).
*/
*skip_nonnative_saop = true;
continue;
}
/* OK to include this clause */
index_clauses = lappend(index_clauses, iclause);
outer_relids = bms_add_members(outer_relids,
rinfo->clause_relids);
}
/*
* If no clauses match the first index column, check for amoptionalkey
* restriction. We can't generate a scan over an index with
* amoptionalkey = false unless there's at least one index clause.
* (When working on columns after the first, this test cannot fail. It
* is always okay for columns after the first to not have any
* clauses.)
*/
if (index_clauses == NIL && !index->amoptionalkey)
return NIL;
}
/* We do not want the index's rel itself listed in outer_relids */
outer_relids = bms_del_member(outer_relids, rel->relid);
/* Compute loop_count for cost estimation purposes */
loop_count = get_loop_count(root, rel->relid, outer_relids);
/*
* 2. Compute pathkeys describing index's ordering, if any, then see how
* many of them are actually useful for this query. This is not relevant
* if we are only trying to build bitmap indexscans.
*/
pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
has_useful_pathkeys(root, rel));
index_is_ordered = (index->sortopfamily != NULL);
if (index_is_ordered && pathkeys_possibly_useful)
{
index_pathkeys = build_index_pathkeys(root, index,
ForwardScanDirection);
useful_pathkeys = truncate_useless_pathkeys(root, rel,
index_pathkeys);
orderbyclauses = NIL;
orderbyclausecols = NIL;
}
else if (index->amcanorderbyop && pathkeys_possibly_useful)
{
/*
* See if we can generate ordering operators for query_pathkeys or at
* least some prefix thereof. Matching to just a prefix of the
* query_pathkeys will allow an incremental sort to be considered on
* the index's partially sorted results.
*/
match_pathkeys_to_index(index, root->query_pathkeys,
&orderbyclauses,
&orderbyclausecols);
if (list_length(root->query_pathkeys) == list_length(orderbyclauses))
useful_pathkeys = root->query_pathkeys;
else
useful_pathkeys = list_copy_head(root->query_pathkeys,
list_length(orderbyclauses));
}
else
{
useful_pathkeys = NIL;
orderbyclauses = NIL;
orderbyclausecols = NIL;
}
/*
* 3. Check if an index-only scan is possible. If we're not building
* plain indexscans, this isn't relevant since bitmap scans don't support
* index data retrieval anyway.
*/
index_only_scan = (scantype != ST_BITMAPSCAN &&
check_index_only(rel, index));
/*
* 4. Generate an indexscan path if there are relevant restriction clauses
* in the current clauses, OR the index ordering is potentially useful for
* later merging or final output ordering, OR the index has a useful
* predicate, OR an index-only scan is possible.
*/
if (index_clauses != NIL || useful_pathkeys != NIL || useful_predicate ||
index_only_scan)
{
ipath = create_index_path(root, index,
index_clauses,
orderbyclauses,
orderbyclausecols,
useful_pathkeys,
ForwardScanDirection,
index_only_scan,
outer_relids,
loop_count,
false);
result = lappend(result, ipath);
/*
* If appropriate, consider parallel index scan. We don't allow
* parallel index scan for bitmap index scans.
*/
if (index->amcanparallel &&
rel->consider_parallel && outer_relids == NULL &&
scantype != ST_BITMAPSCAN)
{
ipath = create_index_path(root, index,
index_clauses,
orderbyclauses,
orderbyclausecols,
useful_pathkeys,
ForwardScanDirection,
index_only_scan,
outer_relids,
loop_count,
true);
/*
* if, after costing the path, we find that it's not worth using
* parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
add_partial_path(rel, (Path *) ipath);
else
pfree(ipath);
}
}
/*
* 5. If the index is ordered, a backwards scan might be interesting.
*/
if (index_is_ordered && pathkeys_possibly_useful)
{
index_pathkeys = build_index_pathkeys(root, index,
BackwardScanDirection);
useful_pathkeys = truncate_useless_pathkeys(root, rel,
index_pathkeys);
if (useful_pathkeys != NIL)
{
ipath = create_index_path(root, index,
index_clauses,
NIL,
NIL,
useful_pathkeys,
BackwardScanDirection,
index_only_scan,
outer_relids,
loop_count,
false);
result = lappend(result, ipath);
/* If appropriate, consider parallel index scan */
if (index->amcanparallel &&
rel->consider_parallel && outer_relids == NULL &&
scantype != ST_BITMAPSCAN)
{
ipath = create_index_path(root, index,
index_clauses,
NIL,
NIL,
useful_pathkeys,
BackwardScanDirection,
index_only_scan,
outer_relids,
loop_count,
true);
/*
* if, after costing the path, we find that it's not worth
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
add_partial_path(rel, (Path *) ipath);
else
pfree(ipath);
}
}
}
return result;
}
/*
* build_paths_for_OR
* Given a list of restriction clauses from one arm of an OR clause,
* construct all matching IndexPaths for the relation.
*
* Here we must scan all indexes of the relation, since a bitmap OR tree
* can use multiple indexes.
*
* The caller actually supplies two lists of restriction clauses: some
* "current" ones and some "other" ones. Both lists can be used freely
* to match keys of the index, but an index must use at least one of the
* "current" clauses to be considered usable. The motivation for this is
* examples like
* WHERE (x = 42) AND (... OR (y = 52 AND z = 77) OR ....)
* While we are considering the y/z subclause of the OR, we can use "x = 42"
* as one of the available index conditions; but we shouldn't match the
* subclause to any index on x alone, because such a Path would already have
* been generated at the upper level. So we could use an index on x,y,z
* or an index on x,y for the OR subclause, but not an index on just x.
* When dealing with a partial index, a match of the index predicate to
* one of the "current" clauses also makes the index usable.
*
* 'rel' is the relation for which we want to generate index paths
* 'clauses' is the current list of clauses (RestrictInfo nodes)
* 'other_clauses' is the list of additional upper-level clauses
*/
static List *
build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses)
{
List *result = NIL;
List *all_clauses = NIL; /* not computed till needed */
ListCell *lc;
foreach(lc, rel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
IndexClauseSet clauseset;
List *indexpaths;
bool useful_predicate;
/* Ignore index if it doesn't support bitmap scans */
if (!index->amhasgetbitmap)
continue;
/*
* Ignore partial indexes that do not match the query. If a partial
* index is marked predOK then we know it's OK. Otherwise, we have to
* test whether the added clauses are sufficient to imply the
* predicate. If so, we can use the index in the current context.
*
* We set useful_predicate to true iff the predicate was proven using
* the current set of clauses. This is needed to prevent matching a
* predOK index to an arm of an OR, which would be a legal but
* pointlessly inefficient plan. (A better plan will be generated by
* just scanning the predOK index alone, no OR.)
*/
useful_predicate = false;
if (index->indpredExpand != NIL)
{
if (index->predOK)
{
/* Usable, but don't set useful_predicate */
}
else
{
/* Form all_clauses if not done already */
if (all_clauses == NIL)
all_clauses = list_concat_copy(clauses, other_clauses);
if (!predicate_implied_by(index->indpredExpand, all_clauses, false))
continue; /* can't use it at all */
if (!predicate_implied_by(index->indpredExpand, other_clauses, false))
useful_predicate = true;
}
}
/*
* Identify the restriction clauses that can match the index.
*/
MemSet(&clauseset, 0, sizeof(clauseset));
match_clauses_to_index(root, clauses, index, &clauseset);
/*
* If no matches so far, and the index predicate isn't useful, we
* don't want it.
*/
if (!clauseset.nonempty && !useful_predicate)
continue;
/*
* Add "other" restriction clauses to the clauseset.
*/
match_clauses_to_index(root, other_clauses, index, &clauseset);
/*
* Construct paths if possible.
*/
indexpaths = build_index_paths(root, rel,
index, &clauseset,
useful_predicate,
ST_BITMAPSCAN,
NULL);
result = list_concat(result, indexpaths);
}
return result;
}
/*
* Utility structure used to group similar OR-clause arguments in
* group_similar_or_args(). It represents information about the OR-clause
* argument and its matching index key.
*/
typedef struct
{
int indexnum; /* index of the matching index, or -1 if no
* matching index */
int colnum; /* index of the matching column, or -1 if no
* matching index */
Oid opno; /* OID of the OpClause operator, or InvalidOid
* if not an OpExpr */
Oid inputcollid; /* OID of the OpClause input collation */
int argindex; /* index of the clause in the list of
* arguments */
int groupindex; /* value of argindex for the first clause in
* the group of similar clauses */
} OrArgIndexMatch;
/*
* Comparison function for OrArgIndexMatch which provides sort order placing
* similar OR-clause arguments together.
*/
static int
or_arg_index_match_cmp(const void *a, const void *b)
{
const OrArgIndexMatch *match_a = (const OrArgIndexMatch *) a;
const OrArgIndexMatch *match_b = (const OrArgIndexMatch *) b;
if (match_a->indexnum < match_b->indexnum)
return -1;
else if (match_a->indexnum > match_b->indexnum)
return 1;
if (match_a->colnum < match_b->colnum)
return -1;
else if (match_a->colnum > match_b->colnum)
return 1;
if (match_a->opno < match_b->opno)
return -1;
else if (match_a->opno > match_b->opno)
return 1;
if (match_a->inputcollid < match_b->inputcollid)
return -1;
else if (match_a->inputcollid > match_b->inputcollid)
return 1;
if (match_a->argindex < match_b->argindex)
return -1;
else if (match_a->argindex > match_b->argindex)
return 1;
return 0;
}
/*
* Another comparison function for OrArgIndexMatch. It sorts groups together
* using groupindex. The group items are then sorted by argindex.
*/
static int
or_arg_index_match_cmp_group(const void *a, const void *b)
{
const OrArgIndexMatch *match_a = (const OrArgIndexMatch *) a;
const OrArgIndexMatch *match_b = (const OrArgIndexMatch *) b;
if (match_a->groupindex < match_b->groupindex)
return -1;
else if (match_a->groupindex > match_b->groupindex)
return 1;
if (match_a->argindex < match_b->argindex)
return -1;
else if (match_a->argindex > match_b->argindex)
return 1;
return 0;
}
/*
* group_similar_or_args
* Transform incoming OR-restrictinfo into a list of sub-restrictinfos,
* each of them containing a subset of similar OR-clause arguments from
* the source rinfo.
*
* Similar OR-clause arguments are of the form "indexkey op constant" having
* the same indexkey, operator, and collation. Constant may comprise either
* Const or Param. It may be employed later, during the
* match_clause_to_indexcol() to transform the whole OR-sub-rinfo to an SAOP
* clause.
*
* Returns the processed list of OR-clause arguments.
*/
static List *
group_similar_or_args(PlannerInfo *root, RelOptInfo *rel, RestrictInfo *rinfo)
{
int n;
int i;
int group_start;
OrArgIndexMatch *matches;
bool matched = false;
ListCell *lc;
ListCell *lc2;
List *orargs;
List *result = NIL;
Index relid = rel->relid;
Assert(IsA(rinfo->orclause, BoolExpr));
orargs = ((BoolExpr *) rinfo->orclause)->args;
n = list_length(orargs);
/*
* To avoid N^2 behavior, take utility pass along the list of OR-clause
* arguments. For each argument, fill the OrArgIndexMatch structure,
* which will be used to sort these arguments at the next step.
*/
i = -1;
matches = palloc_array(OrArgIndexMatch, n);
foreach(lc, orargs)
{
Node *arg = lfirst(lc);
RestrictInfo *argrinfo;
OpExpr *clause;
Oid opno;
Node *leftop,
*rightop;
Node *nonConstExpr;
int indexnum;
int colnum;
i++;
matches[i].argindex = i;
matches[i].groupindex = i;
matches[i].indexnum = -1;
matches[i].colnum = -1;
matches[i].opno = InvalidOid;
matches[i].inputcollid = InvalidOid;
if (!IsA(arg, RestrictInfo))
continue;
argrinfo = castNode(RestrictInfo, arg);
/* Only operator clauses can match */
if (!IsA(argrinfo->clause, OpExpr))
continue;
clause = (OpExpr *) argrinfo->clause;
opno = clause->opno;
/* Only binary operators can match */
if (list_length(clause->args) != 2)
continue;
/*
* Ignore any RelabelType node above the operands. This is needed to
* be able to apply indexscanning in binary-compatible-operator cases.
* Note: we can assume there is at most one RelabelType node;
* eval_const_expressions() will have simplified if more than one.
*/
leftop = get_leftop(clause);
if (IsA(leftop, RelabelType))
leftop = (Node *) ((RelabelType *) leftop)->arg;
rightop = get_rightop(clause);
if (IsA(rightop, RelabelType))
rightop = (Node *) ((RelabelType *) rightop)->arg;
/*
* Check for clauses of the form: (indexkey operator constant) or
* (constant operator indexkey). But we don't know a particular index
* yet. Therefore, we try to distinguish the potential index key and
* constant first, then search for a matching index key among all
* indexes.
*/
if (bms_is_member(relid, argrinfo->right_relids) &&
!bms_is_member(relid, argrinfo->left_relids) &&
!contain_volatile_functions(leftop))
{
opno = get_commutator(opno);
if (!OidIsValid(opno))
{
/* commutator doesn't exist, we can't reverse the order */
continue;
}
nonConstExpr = rightop;
}
else if (bms_is_member(relid, argrinfo->left_relids) &&
!bms_is_member(relid, argrinfo->right_relids) &&
!contain_volatile_functions(rightop))
{
nonConstExpr = leftop;
}
else
{
continue;
}
/*
* Match non-constant part to the index key. It's possible that a
* single non-constant part matches multiple index keys. It's OK, we
* just stop with first matching index key. Given that this choice is
* determined the same for every clause, we will group similar clauses
* together anyway.
*/
indexnum = 0;
foreach(lc2, rel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc2);
/*
* Ignore index if it doesn't support bitmap scans or SAOP
* clauses.
*/
if (!index->amhasgetbitmap || !index->amsearcharray)
continue;
for (colnum = 0; colnum < index->nkeycolumns; colnum++)
{
if (match_index_to_operand(nonConstExpr, colnum, index))
{
matches[i].indexnum = indexnum;
matches[i].colnum = colnum;
matches[i].opno = opno;
matches[i].inputcollid = clause->inputcollid;
matched = true;
break;
}
}
/*
* Stop looping through the indexes, if we managed to match
* nonConstExpr to any index column.
*/
if (matches[i].indexnum >= 0)
break;
indexnum++;
}
}
/*
* Fast-path check: if no clause is matching to the index column, we can
* just give up at this stage and return the clause list as-is.
*/
if (!matched)
{
pfree(matches);
return orargs;
}
/*
* Sort clauses to make similar clauses go together. But at the same
* time, we would like to change the order of clauses as little as
* possible. To do so, we reorder each group of similar clauses so that
* the first item of the group stays in place, and all the other items are
* moved after it. So, if there are no similar clauses, the order of
* clauses stays the same. When there are some groups, required
* reordering happens while the rest of the clauses remain in their
* places. That is achieved by assigning a 'groupindex' to each clause:
* the number of the first item in the group in the original clause list.
*/
qsort(matches, n, sizeof(OrArgIndexMatch), or_arg_index_match_cmp);
/* Assign groupindex to the sorted clauses */
for (i = 1; i < n; i++)
{
/*
* When two clauses are similar and should belong to the same group,
* copy the 'groupindex' from the previous clause. Given we are
* considering clauses in direct order, all the clauses would have a
* 'groupindex' equal to the 'groupindex' of the first clause in the
* group.
*/
if (matches[i].indexnum == matches[i - 1].indexnum &&
matches[i].colnum == matches[i - 1].colnum &&
matches[i].opno == matches[i - 1].opno &&
matches[i].inputcollid == matches[i - 1].inputcollid &&
matches[i].indexnum != -1)
matches[i].groupindex = matches[i - 1].groupindex;
}
/* Re-sort clauses first by groupindex then by argindex */
qsort(matches, n, sizeof(OrArgIndexMatch), or_arg_index_match_cmp_group);
/*
* Group similar clauses into single sub-restrictinfo. Side effect: the
* resulting list of restrictions will be sorted by indexnum and colnum.
*/
group_start = 0;
for (i = 1; i <= n; i++)
{
/* Check if it's a group boundary */
if (group_start >= 0 &&
(i == n ||
matches[i].indexnum != matches[group_start].indexnum ||
matches[i].colnum != matches[group_start].colnum ||
matches[i].opno != matches[group_start].opno ||
matches[i].inputcollid != matches[group_start].inputcollid ||
matches[i].indexnum == -1))
{
/*
* One clause in group: add it "as is" to the upper-level OR.
*/
if (i - group_start == 1)
{
result = lappend(result,
list_nth(orargs,
matches[group_start].argindex));
}
else
{
/*
* Two or more clauses in a group: create a nested OR.
*/
List *args = NIL;
List *rargs = NIL;
RestrictInfo *subrinfo;
int j;
Assert(i - group_start >= 2);
/* Construct the list of nested OR arguments */
for (j = group_start; j < i; j++)
{
Node *arg = list_nth(orargs, matches[j].argindex);
rargs = lappend(rargs, arg);
if (IsA(arg, RestrictInfo))
args = lappend(args, ((RestrictInfo *) arg)->clause);
else
args = lappend(args, arg);
}
/* Construct the nested OR and wrap it with RestrictInfo */
subrinfo = make_plain_restrictinfo(root,
make_orclause(args),
make_orclause(rargs),
rinfo->is_pushed_down,
rinfo->has_clone,
rinfo->is_clone,
rinfo->pseudoconstant,
rinfo->security_level,
rinfo->required_relids,
rinfo->incompatible_relids,
rinfo->outer_relids);
result = lappend(result, subrinfo);
}
group_start = i;
}
}
pfree(matches);
return result;
}
/*
* make_bitmap_paths_for_or_group
* Generate bitmap paths for a group of similar OR-clause arguments
* produced by group_similar_or_args().
*
* This function considers two cases: (1) matching a group of clauses to
* the index as a whole, and (2) matching the individual clauses one-by-one.
* (1) typically comprises an optimal solution. If not, (2) typically
* comprises fair alternative.
*
* Ideally, we could consider all arbitrary splits of arguments into
* subgroups, but that could lead to unacceptable computational complexity.
* This is why we only consider two cases of above.
*/
static List *
make_bitmap_paths_for_or_group(PlannerInfo *root, RelOptInfo *rel,
RestrictInfo *ri, List *other_clauses)
{
List *jointlist = NIL;
List *splitlist = NIL;
ListCell *lc;
List *orargs;
List *args = ((BoolExpr *) ri->orclause)->args;
Cost jointcost = 0.0,
splitcost = 0.0;
Path *bitmapqual;
List *indlist;
/*
* First, try to match the whole group to the one index.
*/
orargs = list_make1(ri);
indlist = build_paths_for_OR(root, rel,
orargs,
other_clauses);
if (indlist != NIL)
{
bitmapqual = choose_bitmap_and(root, rel, indlist);
jointcost = bitmapqual->total_cost;
jointlist = list_make1(bitmapqual);
}
/*
* If we manage to find a bitmap scan, which uses the group of OR-clause
* arguments as a whole, we can skip matching OR-clause arguments
* one-by-one as long as there are no other clauses, which can bring more
* efficiency to one-by-one case.
*/
if (jointlist != NIL && other_clauses == NIL)
return jointlist;
/*
* Also try to match all containing clauses one-by-one.
*/
foreach(lc, args)
{
orargs = list_make1(lfirst(lc));
indlist = build_paths_for_OR(root, rel,
orargs,
other_clauses);
if (indlist == NIL)
{
splitlist = NIL;
break;
}
bitmapqual = choose_bitmap_and(root, rel, indlist);
splitcost += bitmapqual->total_cost;
splitlist = lappend(splitlist, bitmapqual);
}
/*
* Pick the best option.
*/
if (splitlist == NIL)
return jointlist;
else if (jointlist == NIL)
return splitlist;
else
return (jointcost < splitcost) ? jointlist : splitlist;
}
/*
* generate_bitmap_or_paths
* Look through the list of clauses to find OR clauses, and generate
* a BitmapOrPath for each one we can handle that way. Return a list
* of the generated BitmapOrPaths.
*
* other_clauses is a list of additional clauses that can be assumed true
* for the purpose of generating indexquals, but are not to be searched for
* ORs. (See build_paths_for_OR() for motivation.)
*/
static List *
generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses)
{
List *result = NIL;
List *all_clauses;
ListCell *lc;
/*
* We can use both the current and other clauses as context for
* build_paths_for_OR; no need to remove ORs from the lists.
*/
all_clauses = list_concat_copy(clauses, other_clauses);
foreach(lc, clauses)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
List *pathlist;
Path *bitmapqual;
ListCell *j;
List *groupedArgs;
List *inner_other_clauses = NIL;
/* Ignore RestrictInfos that aren't ORs */
if (!restriction_is_or_clause(rinfo))
continue;
/*
* We must be able to match at least one index to each of the arms of
* the OR, else we can't use it.
*/
pathlist = NIL;
/*
* Group the similar OR-clause arguments into dedicated RestrictInfos,
* because each of those RestrictInfos has a chance to match the index
* as a whole.
*/
groupedArgs = group_similar_or_args(root, rel, rinfo);
if (groupedArgs != ((BoolExpr *) rinfo->orclause)->args)
{
/*
* Some parts of the rinfo were probably grouped. In this case,
* we have a set of sub-rinfos that together are an exact
* duplicate of rinfo. Thus, we need to remove the rinfo from
* other clauses. match_clauses_to_index detects duplicated
* iclauses by comparing pointers to original rinfos that would be
* different. So, we must delete rinfo to avoid de-facto
* duplicated clauses in the index clauses list.
*/
inner_other_clauses = list_delete(list_copy(all_clauses), rinfo);
}
foreach(j, groupedArgs)
{
Node *orarg = (Node *) lfirst(j);
List *indlist;
/* OR arguments should be ANDs or sub-RestrictInfos */
if (is_andclause(orarg))
{
List *andargs = ((BoolExpr *) orarg)->args;
indlist = build_paths_for_OR(root, rel,
andargs,
all_clauses);
/* Recurse in case there are sub-ORs */
indlist = list_concat(indlist,
generate_bitmap_or_paths(root, rel,
andargs,
all_clauses));
}
else if (restriction_is_or_clause(castNode(RestrictInfo, orarg)))
{
RestrictInfo *ri = castNode(RestrictInfo, orarg);
/*
* Generate bitmap paths for the group of similar OR-clause
* arguments.
*/
indlist = make_bitmap_paths_for_or_group(root,
rel, ri,
inner_other_clauses);
if (indlist == NIL)
{
pathlist = NIL;
break;
}
else
{
pathlist = list_concat(pathlist, indlist);
continue;
}
}
else
{
RestrictInfo *ri = castNode(RestrictInfo, orarg);
List *orargs;
orargs = list_make1(ri);
indlist = build_paths_for_OR(root, rel,
orargs,
all_clauses);
}
/*
* If nothing matched this arm, we can't do anything with this OR
* clause.
*/
if (indlist == NIL)
{
pathlist = NIL;
break;
}
/*
* OK, pick the most promising AND combination, and add it to
* pathlist.
*/
bitmapqual = choose_bitmap_and(root, rel, indlist);
pathlist = lappend(pathlist, bitmapqual);
}
if (inner_other_clauses != NIL)
list_free(inner_other_clauses);
/*
* If we have a match for every arm, then turn them into a
* BitmapOrPath, and add to result list.
*/
if (pathlist != NIL)
{
bitmapqual = (Path *) create_bitmap_or_path(root, rel, pathlist);
result = lappend(result, bitmapqual);
}
}
return result;
}
/*
* choose_bitmap_and
* Given a nonempty list of bitmap paths, AND them into one path.
*
* This is a nontrivial decision since we can legally use any subset of the
* given path set. We want to choose a good tradeoff between selectivity
* and cost of computing the bitmap.
*
* The result is either a single one of the inputs, or a BitmapAndPath
* combining multiple inputs.
*/
static Path *
choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths)
{
int npaths = list_length(paths);
PathClauseUsage **pathinfoarray;
PathClauseUsage *pathinfo;
List *clauselist;
List *bestpaths = NIL;
Cost bestcost = 0;
int i,
j;
ListCell *l;
Assert(npaths > 0); /* else caller error */
if (npaths == 1)
return (Path *) linitial(paths); /* easy case */
/*
* In theory we should consider every nonempty subset of the given paths.
* In practice that seems like overkill, given the crude nature of the
* estimates, not to mention the possible effects of higher-level AND and
* OR clauses. Moreover, it's completely impractical if there are a large
* number of paths, since the work would grow as O(2^N).
*
* As a heuristic, we first check for paths using exactly the same sets of
* WHERE clauses + index predicate conditions, and reject all but the
* cheapest-to-scan in any such group. This primarily gets rid of indexes
* that include the interesting columns but also irrelevant columns. (In
* situations where the DBA has gone overboard on creating variant
* indexes, this can make for a very large reduction in the number of
* paths considered further.)
*
* We then sort the surviving paths with the cheapest-to-scan first, and
* for each path, consider using that path alone as the basis for a bitmap
* scan. Then we consider bitmap AND scans formed from that path plus
* each subsequent (higher-cost) path, adding on a subsequent path if it
* results in a reduction in the estimated total scan cost. This means we
* consider about O(N^2) rather than O(2^N) path combinations, which is
* quite tolerable, especially given than N is usually reasonably small
* because of the prefiltering step. The cheapest of these is returned.
*
* We will only consider AND combinations in which no two indexes use the
* same WHERE clause. This is a bit of a kluge: it's needed because
* costsize.c and clausesel.c aren't very smart about redundant clauses.
* They will usually double-count the redundant clauses, producing a
* too-small selectivity that makes a redundant AND step look like it
* reduces the total cost. Perhaps someday that code will be smarter and
* we can remove this limitation. (But note that this also defends
* against flat-out duplicate input paths, which can happen because
* match_join_clauses_to_index will find the same OR join clauses that
* extract_restriction_or_clauses has pulled OR restriction clauses out
* of.)
*
* For the same reason, we reject AND combinations in which an index
* predicate clause duplicates another clause. Here we find it necessary
* to be even stricter: we'll reject a partial index if any of its
* predicate clauses are implied by the set of WHERE clauses and predicate
* clauses used so far. This covers cases such as a condition "x = 42"
* used with a plain index, followed by a clauseless scan of a partial
* index "WHERE x >= 40 AND x < 50". The partial index has been accepted
* only because "x = 42" was present, and so allowing it would partially
* double-count selectivity. (We could use predicate_implied_by on
* regular qual clauses too, to have a more intelligent, but much more
* expensive, check for redundancy --- but in most cases simple equality
* seems to suffice.)
*/
/*
* Extract clause usage info and detect any paths that use exactly the
* same set of clauses; keep only the cheapest-to-scan of any such groups.
* The surviving paths are put into an array for qsort'ing.
*/
pathinfoarray = palloc_array(PathClauseUsage *, npaths);
clauselist = NIL;
npaths = 0;
foreach(l, paths)
{
Path *ipath = (Path *) lfirst(l);
pathinfo = classify_index_clause_usage(ipath, &clauselist);
/* If it's unclassifiable, treat it as distinct from all others */
if (pathinfo->unclassifiable)
{
pathinfoarray[npaths++] = pathinfo;
continue;
}
for (i = 0; i < npaths; i++)
{
if (!pathinfoarray[i]->unclassifiable &&
bms_equal(pathinfo->clauseids, pathinfoarray[i]->clauseids))
break;
}
if (i < npaths)
{
/* duplicate clauseids, keep the cheaper one */
Cost ncost;
Cost ocost;
Selectivity nselec;
Selectivity oselec;
cost_bitmap_tree_node(pathinfo->path, &ncost, &nselec);
cost_bitmap_tree_node(pathinfoarray[i]->path, &ocost, &oselec);
if (ncost < ocost)
pathinfoarray[i] = pathinfo;
}
else
{
/* not duplicate clauseids, add to array */
pathinfoarray[npaths++] = pathinfo;
}
}
/* If only one surviving path, we're done */
if (npaths == 1)
return pathinfoarray[0]->path;
/* Sort the surviving paths by index access cost */
qsort(pathinfoarray, npaths, sizeof(PathClauseUsage *),
path_usage_comparator);
/*
* For each surviving index, consider it as an "AND group leader", and see
* whether adding on any of the later indexes results in an AND path with
* cheaper total cost than before. Then take the cheapest AND group.
*
* Note: paths that are either clauseless or unclassifiable will have
* empty clauseids, so that they will not be rejected by the clauseids
* filter here, nor will they cause later paths to be rejected by it.
*/
for (i = 0; i < npaths; i++)
{
Cost costsofar;
List *qualsofar;
Bitmapset *clauseidsofar;
pathinfo = pathinfoarray[i];
paths = list_make1(pathinfo->path);
costsofar = bitmap_scan_cost_est(root, rel, pathinfo->path);
qualsofar = list_concat_copy(pathinfo->quals, pathinfo->preds);
clauseidsofar = bms_copy(pathinfo->clauseids);
for (j = i + 1; j < npaths; j++)
{
Cost newcost;
pathinfo = pathinfoarray[j];
/* Check for redundancy */
if (bms_overlap(pathinfo->clauseids, clauseidsofar))
continue; /* consider it redundant */
if (pathinfo->preds)
{
bool redundant = false;
/* we check each predicate clause separately */
foreach(l, pathinfo->preds)
{
Node *np = (Node *) lfirst(l);
if (predicate_implied_by(list_make1(np), qualsofar, false))
{
redundant = true;
break; /* out of inner foreach loop */
}
}
if (redundant)
continue;
}
/* tentatively add new path to paths, so we can estimate cost */
paths = lappend(paths, pathinfo->path);
newcost = bitmap_and_cost_est(root, rel, paths);
if (newcost < costsofar)
{
/* keep new path in paths, update subsidiary variables */
costsofar = newcost;
qualsofar = list_concat(qualsofar, pathinfo->quals);
qualsofar = list_concat(qualsofar, pathinfo->preds);
clauseidsofar = bms_add_members(clauseidsofar,
pathinfo->clauseids);
}
else
{
/* reject new path, remove it from paths list */
paths = list_truncate(paths, list_length(paths) - 1);
}
}
/* Keep the cheapest AND-group (or singleton) */
if (i == 0 || costsofar < bestcost)
{
bestpaths = paths;
bestcost = costsofar;
}
/* some easy cleanup (we don't try real hard though) */
list_free(qualsofar);
}
if (list_length(bestpaths) == 1)
return (Path *) linitial(bestpaths); /* no need for AND */
return (Path *) create_bitmap_and_path(root, rel, bestpaths);
}
/* qsort comparator to sort in increasing index access cost order */
static int
path_usage_comparator(const void *a, const void *b)
{
PathClauseUsage *pa = *(PathClauseUsage *const *) a;
PathClauseUsage *pb = *(PathClauseUsage *const *) b;
Cost acost;
Cost bcost;
Selectivity aselec;
Selectivity bselec;
cost_bitmap_tree_node(pa->path, &acost, &aselec);
cost_bitmap_tree_node(pb->path, &bcost, &bselec);
/*
* If costs are the same, sort by selectivity.
*/
if (acost < bcost)
return -1;
if (acost > bcost)
return 1;
if (aselec < bselec)
return -1;
if (aselec > bselec)
return 1;
return 0;
}
/*
* Estimate the cost of actually executing a bitmap scan with a single
* index path (which could be a BitmapAnd or BitmapOr node).
*/
static Cost
bitmap_scan_cost_est(PlannerInfo *root, RelOptInfo *rel, Path *ipath)
{
BitmapHeapPath bpath;
/* Set up a dummy BitmapHeapPath */
bpath.path.type = T_BitmapHeapPath;
bpath.path.pathtype = T_BitmapHeapScan;
bpath.path.parent = rel;
bpath.path.pathtarget = rel->reltarget;
bpath.path.param_info = ipath->param_info;
bpath.path.pathkeys = NIL;
bpath.bitmapqual = ipath;
/*
* Check the cost of temporary path without considering parallelism.
* Parallel bitmap heap path will be considered at later stage.
*/
bpath.path.parallel_workers = 0;
/* Now we can do cost_bitmap_heap_scan */
cost_bitmap_heap_scan(&bpath.path, root, rel,
bpath.path.param_info,
ipath,
get_loop_count(root, rel->relid,
PATH_REQ_OUTER(ipath)));
return bpath.path.total_cost;
}
/*
* Estimate the cost of actually executing a BitmapAnd scan with the given
* inputs.
*/
static Cost
bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel, List *paths)
{
BitmapAndPath *apath;
/*
* Might as well build a real BitmapAndPath here, as the work is slightly
* too complicated to be worth repeating just to save one palloc.
*/
apath = create_bitmap_and_path(root, rel, paths);
return bitmap_scan_cost_est(root, rel, (Path *) apath);
}
/*
* classify_index_clause_usage
* Construct a PathClauseUsage struct describing the WHERE clauses and
* index predicate clauses used by the given indexscan path.
* We consider two clauses the same if they are equal().
*
* At some point we might want to migrate this info into the Path data
* structure proper, but for the moment it's only needed within
* choose_bitmap_and().
*
* *clauselist is used and expanded as needed to identify all the distinct
* clauses seen across successive calls. Caller must initialize it to NIL
* before first call of a set.
*/
static PathClauseUsage *
classify_index_clause_usage(Path *path, List **clauselist)
{
PathClauseUsage *result;
Bitmapset *clauseids;
ListCell *lc;
result = palloc_object(PathClauseUsage);
result->path = path;
/* Recursively find the quals and preds used by the path */
result->quals = NIL;
result->preds = NIL;
find_indexpath_quals(path, &result->quals, &result->preds);
/*
* Some machine-generated queries have outlandish numbers of qual clauses.
* To avoid getting into O(N^2) behavior even in this preliminary
* classification step, we want to limit the number of entries we can
* accumulate in *clauselist. Treat any path with more than 100 quals +
* preds as unclassifiable, which will cause calling code to consider it
* distinct from all other paths.
*/
if (list_length(result->quals) + list_length(result->preds) > 100)
{
result->clauseids = NULL;
result->unclassifiable = true;
return result;
}
/* Build up a bitmapset representing the quals and preds */
clauseids = NULL;
foreach(lc, result->quals)
{
Node *node = (Node *) lfirst(lc);
clauseids = bms_add_member(clauseids,
find_list_position(node, clauselist));
}
foreach(lc, result->preds)
{
Node *node = (Node *) lfirst(lc);
clauseids = bms_add_member(clauseids,
find_list_position(node, clauselist));
}
result->clauseids = clauseids;
result->unclassifiable = false;
return result;
}
/*
* find_indexpath_quals
*
* Given the Path structure for a plain or bitmap indexscan, extract lists
* of all the index clauses and index predicate conditions used in the Path.
* These are appended to the initial contents of *quals and *preds (hence
* caller should initialize those to NIL).
*
* Note we are not trying to produce an accurate representation of the AND/OR
* semantics of the Path, but just find out all the base conditions used.
*
* The result lists contain pointers to the expressions used in the Path,
* but all the list cells are freshly built, so it's safe to destructively
* modify the lists (eg, by concat'ing with other lists).
*/
static void
find_indexpath_quals(Path *bitmapqual, List **quals, List **preds)
{
if (IsA(bitmapqual, BitmapAndPath))
{
BitmapAndPath *apath = (BitmapAndPath *) bitmapqual;
ListCell *l;
foreach(l, apath->bitmapquals)
{
find_indexpath_quals((Path *) lfirst(l), quals, preds);
}
}
else if (IsA(bitmapqual, BitmapOrPath))
{
BitmapOrPath *opath = (BitmapOrPath *) bitmapqual;
ListCell *l;
foreach(l, opath->bitmapquals)
{
find_indexpath_quals((Path *) lfirst(l), quals, preds);
}
}
else if (IsA(bitmapqual, IndexPath))
{
IndexPath *ipath = (IndexPath *) bitmapqual;
ListCell *l;
foreach(l, ipath->indexclauses)
{
IndexClause *iclause = (IndexClause *) lfirst(l);
*quals = lappend(*quals, iclause->rinfo->clause);
}
*preds = list_concat(*preds, ipath->indexinfo->indpredExpand);
}
else
elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
}
/*
* find_list_position
* Return the given node's position (counting from 0) in the given
* list of nodes. If it's not equal() to any existing list member,
* add it at the end, and return that position.
*/
static int
find_list_position(Node *node, List **nodelist)
{
int i;
ListCell *lc;
i = 0;
foreach(lc, *nodelist)
{
Node *oldnode = (Node *) lfirst(lc);
if (equal(node, oldnode))
return i;
i++;
}
*nodelist = lappend(*nodelist, node);
return i;
}
/*
* check_index_only
* Determine whether an index-only scan is possible for this index.
*/
static bool
check_index_only(RelOptInfo *rel, IndexOptInfo *index)
{
bool result;
Bitmapset *attrs_used = NULL;
Bitmapset *index_canreturn_attrs = NULL;
ListCell *lc;
int i;
/* If we're not allowed to consider index-only scans, give up now */
if ((rel->pgs_mask & PGS_CONSIDER_INDEXONLY) == 0)
return false;
/*
* Check that all needed attributes of the relation are available from the
* index.
*/
/*
* First, identify all the attributes needed for joins or final output.
* Note: we must look at rel's targetlist, not the attr_needed data,
* because attr_needed isn't computed for inheritance child rels.
*/
pull_varattnos((Node *) rel->reltarget->exprs, rel->relid, &attrs_used);
/*
* Add all the attributes used by restriction clauses; but consider only
* those clauses not implied by the index predicate, since ones that are
* so implied don't need to be checked explicitly in the plan.
*
* Note: attributes used only in index quals would not be needed at
* runtime either, if we are certain that the index is not lossy. However
* it'd be complicated to account for that accurately, and it doesn't
* matter in most cases, since we'd conclude that such attributes are
* available from the index anyway.
*/
foreach(lc, index->indrestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
pull_varattnos((Node *) rinfo->clause, rel->relid, &attrs_used);
}
/*
* Construct a bitmapset of columns that the index can return back in an
* index-only scan.
*/
for (i = 0; i < index->ncolumns; i++)
{
int attno = index->indexkeys[i];
/*
* For the moment, we just ignore index expressions. It might be nice
* to do something with them, later.
*/
if (attno == 0)
continue;
if (index->canreturn[i])
index_canreturn_attrs =
bms_add_member(index_canreturn_attrs,
attno - FirstLowInvalidHeapAttributeNumber);
}
/* Do we have all the necessary attributes? */
result = bms_is_subset(attrs_used, index_canreturn_attrs);
bms_free(attrs_used);
bms_free(index_canreturn_attrs);
return result;
}
/*
* get_loop_count
* Choose the loop count estimate to use for costing a parameterized path
* with the given set of outer relids.
*
* Since we produce parameterized paths before we've begun to generate join
* relations, it's impossible to predict exactly how many times a parameterized
* path will be iterated; we don't know the size of the relation that will be
* on the outside of the nestloop. However, we should try to account for
* multiple iterations somehow in costing the path. The heuristic embodied
* here is to use the rowcount of the smallest other base relation needed in
* the join clauses used by the path. (We could alternatively consider the
* largest one, but that seems too optimistic.) This is of course the right
* answer for single-other-relation cases, and it seems like a reasonable
* zero-order approximation for multiway-join cases.
*
* In addition, we check to see if the other side of each join clause is on
* the inside of some semijoin that the current relation is on the outside of.
* If so, the only way that a parameterized path could be used is if the
* semijoin RHS has been unique-ified, so we should use the number of unique
* RHS rows rather than using the relation's raw rowcount.
*
* Note: for this to work, allpaths.c must establish all baserel size
* estimates before it begins to compute paths, or at least before it
* calls create_index_paths().
*/
static double
get_loop_count(PlannerInfo *root, Index cur_relid, Relids outer_relids)
{
double result;
int outer_relid;
/* For a non-parameterized path, just return 1.0 quickly */
if (outer_relids == NULL)
return 1.0;
result = 0.0;
outer_relid = -1;
while ((outer_relid = bms_next_member(outer_relids, outer_relid)) >= 0)
{
RelOptInfo *outer_rel;
double rowcount;
/* Paranoia: ignore bogus relid indexes */
if (outer_relid >= root->simple_rel_array_size)
continue;
outer_rel = root->simple_rel_array[outer_relid];
if (outer_rel == NULL)
continue;
Assert(outer_rel->relid == outer_relid); /* sanity check on array */
/* Other relation could be proven empty, if so ignore */
if (IS_DUMMY_REL(outer_rel))
continue;
/* Otherwise, rel's rows estimate should be valid by now */
Assert(outer_rel->rows > 0);
/* Check to see if rel is on the inside of any semijoins */
rowcount = adjust_rowcount_for_semijoins(root,
cur_relid,
outer_relid,
outer_rel->rows);
/* Remember smallest row count estimate among the outer rels */
if (result == 0.0 || result > rowcount)
result = rowcount;
}
/* Return 1.0 if we found no valid relations (shouldn't happen) */
return (result > 0.0) ? result : 1.0;
}
/*
* Check to see if outer_relid is on the inside of any semijoin that cur_relid
* is on the outside of. If so, replace rowcount with the estimated number of
* unique rows from the semijoin RHS (assuming that's smaller, which it might
* not be). The estimate is crude but it's the best we can do at this stage
* of the proceedings.
*/
static double
adjust_rowcount_for_semijoins(PlannerInfo *root,
Index cur_relid,
Index outer_relid,
double rowcount)
{
ListCell *lc;
foreach(lc, root->join_info_list)
{
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
if (sjinfo->jointype == JOIN_SEMI &&
bms_is_member(cur_relid, sjinfo->syn_lefthand) &&
bms_is_member(outer_relid, sjinfo->syn_righthand))
{
/* Estimate number of unique-ified rows */
double nraw;
double nunique;
nraw = approximate_joinrel_size(root, sjinfo->syn_righthand);
nunique = estimate_num_groups(root,
sjinfo->semi_rhs_exprs,
nraw,
NULL,
NULL);
if (rowcount > nunique)
rowcount = nunique;
}
}
return rowcount;
}
/*
* Make an approximate estimate of the size of a joinrel.
*
* We don't have enough info at this point to get a good estimate, so we
* just multiply the base relation sizes together. Fortunately, this is
* the right answer anyway for the most common case with a single relation
* on the RHS of a semijoin. Also, estimate_num_groups() has only a weak
* dependency on its input_rows argument (it basically uses it as a clamp).
* So we might be able to get a fairly decent end result even with a severe
* overestimate of the RHS's raw size.
*/
static double
approximate_joinrel_size(PlannerInfo *root, Relids relids)
{
double rowcount = 1.0;
int relid;
relid = -1;
while ((relid = bms_next_member(relids, relid)) >= 0)
{
RelOptInfo *rel;
/* Paranoia: ignore bogus relid indexes */
if (relid >= root->simple_rel_array_size)
continue;
rel = root->simple_rel_array[relid];
if (rel == NULL)
continue;
Assert(rel->relid == relid); /* sanity check on array */
/* Relation could be proven empty, if so ignore */
if (IS_DUMMY_REL(rel))
continue;
/* Otherwise, rel's rows estimate should be valid by now */
Assert(rel->rows > 0);
/* Accumulate product */
rowcount *= rel->rows;
}
return rowcount;
}
/****************************************************************************
* ---- ROUTINES TO CHECK QUERY CLAUSES ----
****************************************************************************/
/*
* match_restriction_clauses_to_index
* Identify restriction clauses for the rel that match the index.
* Matching clauses are added to *clauseset.
*/
static void
match_restriction_clauses_to_index(PlannerInfo *root,
IndexOptInfo *index,
IndexClauseSet *clauseset)
{
/* We can ignore clauses that are implied by the index predicate */
match_clauses_to_index(root, index->indrestrictinfo, index, clauseset);
}
/*
* match_join_clauses_to_index
* Identify join clauses for the rel that match the index.
* Matching clauses are added to *clauseset.
* Also, add any potentially usable join OR clauses to *joinorclauses.
* They also might be processed by match_clause_to_index() as a whole.
*/
static void
match_join_clauses_to_index(PlannerInfo *root,
RelOptInfo *rel, IndexOptInfo *index,
IndexClauseSet *clauseset,
List **joinorclauses)
{
ListCell *lc;
/* Scan the rel's join clauses */
foreach(lc, rel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */
if (!join_clause_is_movable_to(rinfo, rel))
continue;
/*
* Potentially usable, so see if it matches the index or is an OR. Use
* list_append_unique_ptr() here to avoid possible duplicates when
* processing the same clauses with different indexes.
*/
if (restriction_is_or_clause(rinfo))
*joinorclauses = list_append_unique_ptr(*joinorclauses, rinfo);
match_clause_to_index(root, rinfo, index, clauseset);
}
}
/*
* match_eclass_clauses_to_index
* Identify EquivalenceClass join clauses for the rel that match the index.
* Matching clauses are added to *clauseset.
*/
static void
match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index,
IndexClauseSet *clauseset)
{
int indexcol;
/* No work if rel is not in any such ECs */
if (!index->rel->has_eclass_joins)
return;
for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
{
ec_member_matches_arg arg;
List *clauses;
/* Generate clauses, skipping any that join to lateral_referencers */
arg.index = index;
arg.indexcol = indexcol;
clauses = generate_implied_equalities_for_column(root,
index->rel,
ec_member_matches_indexcol,
&arg,
index->rel->lateral_referencers);
/*
* We have to check whether the results actually do match the index,
* since for non-btree indexes the EC's equality operators might not
* be in the index opclass (cf ec_member_matches_indexcol).
*/
match_clauses_to_index(root, clauses, index, clauseset);
}
}
/*
* match_clauses_to_index
* Perform match_clause_to_index() for each clause in a list.
* Matching clauses are added to *clauseset.
*/
static void
match_clauses_to_index(PlannerInfo *root,
List *clauses,
IndexOptInfo *index,
IndexClauseSet *clauseset)
{
ListCell *lc;
foreach(lc, clauses)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
match_clause_to_index(root, rinfo, index, clauseset);
}
}
/*
* match_clause_to_index
* Test whether a qual clause can be used with an index.
*
* If the clause is usable, add an IndexClause entry for it to the appropriate
* list in *clauseset. (*clauseset must be initialized to zeroes before first
* call.)
*
* Note: in some circumstances we may find the same RestrictInfos coming from
* multiple places. Defend against redundant outputs by refusing to add a
* clause twice (pointer equality should be a good enough check for this).
*
* Note: it's possible that a badly-defined index could have multiple matching
* columns. We always select the first match if so; this avoids scenarios
* wherein we get an inflated idea of the index's selectivity by using the
* same clause multiple times with different index columns.
*/
static void
match_clause_to_index(PlannerInfo *root,
RestrictInfo *rinfo,
IndexOptInfo *index,
IndexClauseSet *clauseset)
{
int indexcol;
/*
* Never match pseudoconstants to indexes. (Normally a match could not
* happen anyway, since a pseudoconstant clause couldn't contain a Var,
* but what if someone builds an expression index on a constant? It's not
* totally unreasonable to do so with a partial index, either.)
*/
if (rinfo->pseudoconstant)
return;
/*
* If clause can't be used as an indexqual because it must wait till after
* some lower-security-level restriction clause, reject it.
*/
if (!restriction_is_securely_promotable(rinfo, index->rel))
return;
/* OK, check each index key column for a match */
for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
{
IndexClause *iclause;
ListCell *lc;
/* Ignore duplicates */
foreach(lc, clauseset->indexclauses[indexcol])
{
iclause = (IndexClause *) lfirst(lc);
if (iclause->rinfo == rinfo)
return;
}
/* OK, try to match the clause to the index column */
iclause = match_clause_to_indexcol(root,
rinfo,
indexcol,
index);
if (iclause)
{
/* Success, so record it */
clauseset->indexclauses[indexcol] =
lappend(clauseset->indexclauses[indexcol], iclause);
clauseset->nonempty = true;
return;
}
}
}
/*
* match_clause_to_indexcol()
* Determine whether a restriction clause matches a column of an index,
* and if so, build an IndexClause node describing the details.
*
* To match an index normally, an operator clause:
*
* (1) must be in the form (indexkey op const) or (const op indexkey);
* and
* (2) must contain an operator which is in the index's operator family
* for this column; and
* (3) must match the collation of the index, if collation is relevant.
*
* Our definition of "const" is exceedingly liberal: we allow anything that
* doesn't involve a volatile function or a Var of the index's relation.
* In particular, Vars belonging to other relations of the query are
* accepted here, since a clause of that form can be used in a
* parameterized indexscan. It's the responsibility of higher code levels
* to manage restriction and join clauses appropriately.
*
* Note: we do need to check for Vars of the index's relation on the
* "const" side of the clause, since clauses like (a.f1 OP (b.f2 OP a.f3))
* are not processable by a parameterized indexscan on a.f1, whereas
* something like (a.f1 OP (b.f2 OP c.f3)) is.
*
* Presently, the executor can only deal with indexquals that have the
* indexkey on the left, so we can only use clauses that have the indexkey
* on the right if we can commute the clause to put the key on the left.
* We handle that by generating an IndexClause with the correctly-commuted
* opclause as a derived indexqual.
*
* If the index has a collation, the clause must have the same collation.
* For collation-less indexes, we assume it doesn't matter; this is
* necessary for cases like "hstore ? text", wherein hstore's operators
* don't care about collation but the clause will get marked with a
* collation anyway because of the text argument. (This logic is
* embodied in the macro IndexCollMatchesExprColl.)
*
* It is also possible to match RowCompareExpr clauses to indexes (but
* currently, only btree indexes handle this).
*
* It is also possible to match ScalarArrayOpExpr clauses to indexes, when
* the clause is of the form "indexkey op ANY (arrayconst)".
*
* It is also possible to match a list of OR clauses if it might be
* transformed into a single ScalarArrayOpExpr clause. On success,
* the returning index clause will contain a transformed clause.
*
* For boolean indexes, it is also possible to match the clause directly
* to the indexkey; or perhaps the clause is (NOT indexkey).
*
* And, last but not least, some operators and functions can be processed
* to derive (typically lossy) indexquals from a clause that isn't in
* itself indexable. If we see that any operand of an OpExpr or FuncExpr
* matches the index key, and the function has a planner support function
* attached to it, we'll invoke the support function to see if such an
* indexqual can be built.
*
* 'rinfo' is the clause to be tested (as a RestrictInfo node).
* 'indexcol' is a column number of 'index' (counting from 0).
* 'index' is the index of interest.
*
* Returns an IndexClause if the clause can be used with this index key,
* or NULL if not.
*
* NOTE: This routine always returns NULL if the clause is an AND clause.
* Higher-level routines deal with OR and AND clauses. OR clause can be
* matched as a whole by match_orclause_to_indexcol() though.
*/
static IndexClause *
match_clause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index)
{
IndexClause *iclause;
Expr *clause = rinfo->clause;
Oid opfamily;
Assert(indexcol < index->nkeycolumns);
/*
* Historically this code has coped with NULL clauses. That's probably
* not possible anymore, but we might as well continue to cope.
*/
if (clause == NULL)
return NULL;
/* First check for boolean-index cases. */
opfamily = index->opfamily[indexcol];
if (IsBooleanOpfamily(opfamily))
{
iclause = match_boolean_index_clause(root, rinfo, indexcol, index);
if (iclause)
return iclause;
}
/*
* Clause must be an opclause, funcclause, ScalarArrayOpExpr,
* RowCompareExpr, or OR-clause that could be converted to SAOP. Or, if
* the index supports it, we can handle IS NULL/NOT NULL clauses.
*/
if (IsA(clause, OpExpr))
{
return match_opclause_to_indexcol(root, rinfo, indexcol, index);
}
else if (IsA(clause, FuncExpr))
{
return match_funcclause_to_indexcol(root, rinfo, indexcol, index);
}
else if (IsA(clause, ScalarArrayOpExpr))
{
return match_saopclause_to_indexcol(root, rinfo, indexcol, index);
}
else if (IsA(clause, RowCompareExpr))
{
return match_rowcompare_to_indexcol(root, rinfo, indexcol, index);
}
else if (restriction_is_or_clause(rinfo))
{
return match_orclause_to_indexcol(root, rinfo, indexcol, index);
}
else if (index->amsearchnulls && IsA(clause, NullTest))
{
NullTest *nt = (NullTest *) clause;
if (!nt->argisrow &&
match_index_to_operand((Node *) nt->arg, indexcol, index))
{
iclause = makeNode(IndexClause);
iclause->rinfo = rinfo;
iclause->indexquals = list_make1(rinfo);
iclause->lossy = false;
iclause->indexcol = indexcol;
iclause->indexcols = NIL;
return iclause;
}
}
return NULL;
}
/*
* IsBooleanOpfamily
* Detect whether an opfamily supports boolean equality as an operator.
*
* If the opfamily OID is in the range of built-in objects, we can rely
* on hard-wired knowledge of which built-in opfamilies support this.
* For extension opfamilies, there's no choice but to do a catcache lookup.
*/
static bool
IsBooleanOpfamily(Oid opfamily)
{
if (opfamily < FirstNormalObjectId)
return IsBuiltinBooleanOpfamily(opfamily);
else
return op_in_opfamily(BooleanEqualOperator, opfamily);
}
/*
* match_boolean_index_clause
* Recognize restriction clauses that can be matched to a boolean index.
*
* The idea here is that, for an index on a boolean column that supports the
* BooleanEqualOperator, we can transform a plain reference to the indexkey
* into "indexkey = true", or "NOT indexkey" into "indexkey = false", etc,
* so as to make the expression indexable using the index's "=" operator.
* Since Postgres 8.1, we must do this because constant simplification does
* the reverse transformation; without this code there'd be no way to use
* such an index at all.
*
* This should be called only when IsBooleanOpfamily() recognizes the
* index's operator family. We check to see if the clause matches the
* index's key, and if so, build a suitable IndexClause.
*/
static IndexClause *
match_boolean_index_clause(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index)
{
Node *clause = (Node *) rinfo->clause;
Expr *op = NULL;
/* Direct match? */
if (match_index_to_operand(clause, indexcol, index))
{
/* convert to indexkey = TRUE */
op = make_opclause(BooleanEqualOperator, BOOLOID, false,
(Expr *) clause,
(Expr *) makeBoolConst(true, false),
InvalidOid, InvalidOid);
}
/* NOT clause? */
else if (is_notclause(clause))
{
Node *arg = (Node *) get_notclausearg((Expr *) clause);
if (match_index_to_operand(arg, indexcol, index))
{
/* convert to indexkey = FALSE */
op = make_opclause(BooleanEqualOperator, BOOLOID, false,
(Expr *) arg,
(Expr *) makeBoolConst(false, false),
InvalidOid, InvalidOid);
}
}
/*
* Since we only consider clauses at top level of WHERE, we can convert
* indexkey IS TRUE and indexkey IS FALSE to index searches as well. The
* different meaning for NULL isn't important.
*/
else if (clause && IsA(clause, BooleanTest))
{
BooleanTest *btest = (BooleanTest *) clause;
Node *arg = (Node *) btest->arg;
if (btest->booltesttype == IS_TRUE &&
match_index_to_operand(arg, indexcol, index))
{
/* convert to indexkey = TRUE */
op = make_opclause(BooleanEqualOperator, BOOLOID, false,
(Expr *) arg,
(Expr *) makeBoolConst(true, false),
InvalidOid, InvalidOid);
}
else if (btest->booltesttype == IS_FALSE &&
match_index_to_operand(arg, indexcol, index))
{
/* convert to indexkey = FALSE */
op = make_opclause(BooleanEqualOperator, BOOLOID, false,
(Expr *) arg,
(Expr *) makeBoolConst(false, false),
InvalidOid, InvalidOid);
}
}
/*
* If we successfully made an operator clause from the given qual, we must
* wrap it in an IndexClause. It's not lossy.
*/
if (op)
{
IndexClause *iclause = makeNode(IndexClause);
iclause->rinfo = rinfo;
iclause->indexquals = list_make1(make_simple_restrictinfo(root, op));
iclause->lossy = false;
iclause->indexcol = indexcol;
iclause->indexcols = NIL;
return iclause;
}
return NULL;
}
/*
* match_opclause_to_indexcol()
* Handles the OpExpr case for match_clause_to_indexcol(),
* which see for comments.
*/
static IndexClause *
match_opclause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index)
{
IndexClause *iclause;
OpExpr *clause = (OpExpr *) rinfo->clause;
Node *leftop,
*rightop;
Oid expr_op;
Oid expr_coll;
Index index_relid;
Oid opfamily;
Oid idxcollation;
/*
* Only binary operators need apply. (In theory, a planner support
* function could do something with a unary operator, but it seems
* unlikely to be worth the cycles to check.)
*/
if (list_length(clause->args) != 2)
return NULL;
leftop = (Node *) linitial(clause->args);
rightop = (Node *) lsecond(clause->args);
expr_op = clause->opno;
expr_coll = clause->inputcollid;
index_relid = index->rel->relid;
opfamily = index->opfamily[indexcol];
idxcollation = index->indexcollations[indexcol];
/*
* Check for clauses of the form: (indexkey operator constant) or
* (constant operator indexkey). See match_clause_to_indexcol's notes
* about const-ness.
*
* Note that we don't ask the support function about clauses that don't
* have one of these forms. Again, in principle it might be possible to
* do something, but it seems unlikely to be worth the cycles to check.
*/
if (match_index_to_operand(leftop, indexcol, index) &&
!bms_is_member(index_relid, rinfo->right_relids) &&
!contain_volatile_functions(rightop))
{
if (IndexCollMatchesExprColl(idxcollation, expr_coll) &&
op_in_opfamily(expr_op, opfamily))
{
iclause = makeNode(IndexClause);
iclause->rinfo = rinfo;
iclause->indexquals = list_make1(rinfo);
iclause->lossy = false;
iclause->indexcol = indexcol;
iclause->indexcols = NIL;
return iclause;
}
/*
* If we didn't find a member of the index's opfamily, try the support
* function for the operator's underlying function.
*/
set_opfuncid(clause); /* make sure we have opfuncid */
return get_index_clause_from_support(root,
rinfo,
clause->opfuncid,
0, /* indexarg on left */
indexcol,
index);
}
if (match_index_to_operand(rightop, indexcol, index) &&
!bms_is_member(index_relid, rinfo->left_relids) &&
!contain_volatile_functions(leftop))
{
if (IndexCollMatchesExprColl(idxcollation, expr_coll))
{
Oid comm_op = get_commutator(expr_op);
if (OidIsValid(comm_op) &&
op_in_opfamily(comm_op, opfamily))
{
RestrictInfo *commrinfo;
/* Build a commuted OpExpr and RestrictInfo */
commrinfo = commute_restrictinfo(rinfo, comm_op);
/* Make an IndexClause showing that as a derived qual */
iclause = makeNode(IndexClause);
iclause->rinfo = rinfo;
iclause->indexquals = list_make1(commrinfo);
iclause->lossy = false;
iclause->indexcol = indexcol;
iclause->indexcols = NIL;
return iclause;
}
}
/*
* If we didn't find a member of the index's opfamily, try the support
* function for the operator's underlying function.
*/
set_opfuncid(clause); /* make sure we have opfuncid */
return get_index_clause_from_support(root,
rinfo,
clause->opfuncid,
1, /* indexarg on right */
indexcol,
index);
}
return NULL;
}
/*
* match_funcclause_to_indexcol()
* Handles the FuncExpr case for match_clause_to_indexcol(),
* which see for comments.
*/
static IndexClause *
match_funcclause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index)
{
FuncExpr *clause = (FuncExpr *) rinfo->clause;
int indexarg;
ListCell *lc;
/*
* We have no built-in intelligence about function clauses, but if there's
* a planner support function, it might be able to do something. But, to
* cut down on wasted planning cycles, only call the support function if
* at least one argument matches the target index column.
*
* Note that we don't insist on the other arguments being pseudoconstants;
* the support function has to check that. This is to allow cases where
* only some of the other arguments need to be included in the indexqual.
*/
indexarg = 0;
foreach(lc, clause->args)
{
Node *op = (Node *) lfirst(lc);
if (match_index_to_operand(op, indexcol, index))
{
return get_index_clause_from_support(root,
rinfo,
clause->funcid,
indexarg,
indexcol,
index);
}
indexarg++;
}
return NULL;
}
/*
* get_index_clause_from_support()
* If the function has a planner support function, try to construct
* an IndexClause using indexquals created by the support function.
*/
static IndexClause *
get_index_clause_from_support(PlannerInfo *root,
RestrictInfo *rinfo,
Oid funcid,
int indexarg,
int indexcol,
IndexOptInfo *index)
{
Oid prosupport = get_func_support(funcid);
SupportRequestIndexCondition req;
List *sresult;
if (!OidIsValid(prosupport))
return NULL;
req.type = T_SupportRequestIndexCondition;
req.root = root;
req.funcid = funcid;
req.node = (Node *) rinfo->clause;
req.indexarg = indexarg;
req.index = index;
req.indexcol = indexcol;
req.opfamily = index->opfamily[indexcol];
req.indexcollation = index->indexcollations[indexcol];
req.lossy = true; /* default assumption */
sresult = (List *)
DatumGetPointer(OidFunctionCall1(prosupport,
PointerGetDatum(&req)));
if (sresult != NIL)
{
IndexClause *iclause = makeNode(IndexClause);
List *indexquals = NIL;
ListCell *lc;
/*
* The support function API says it should just give back bare
* clauses, so here we must wrap each one in a RestrictInfo.
*/
foreach(lc, sresult)
{
Expr *clause = (Expr *) lfirst(lc);
indexquals = lappend(indexquals,
make_simple_restrictinfo(root, clause));
}
iclause->rinfo = rinfo;
iclause->indexquals = indexquals;
iclause->lossy = req.lossy;
iclause->indexcol = indexcol;
iclause->indexcols = NIL;
return iclause;
}
return NULL;
}
/*
* match_saopclause_to_indexcol()
* Handles the ScalarArrayOpExpr case for match_clause_to_indexcol(),
* which see for comments.
*/
static IndexClause *
match_saopclause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index)
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause;
Node *leftop,
*rightop;
Relids right_relids;
Oid expr_op;
Oid expr_coll;
Index index_relid;
Oid opfamily;
Oid idxcollation;
/* We only accept ANY clauses, not ALL */
if (!saop->useOr)
return NULL;
leftop = (Node *) linitial(saop->args);
rightop = (Node *) lsecond(saop->args);
right_relids = pull_varnos(root, rightop);
expr_op = saop->opno;
expr_coll = saop->inputcollid;
index_relid = index->rel->relid;
opfamily = index->opfamily[indexcol];
idxcollation = index->indexcollations[indexcol];
/*
* We must have indexkey on the left and a pseudo-constant array argument.
*/
if (match_index_to_operand(leftop, indexcol, index) &&
!bms_is_member(index_relid, right_relids) &&
!contain_volatile_functions(rightop))
{
if (IndexCollMatchesExprColl(idxcollation, expr_coll) &&
op_in_opfamily(expr_op, opfamily))
{
IndexClause *iclause = makeNode(IndexClause);
iclause->rinfo = rinfo;
iclause->indexquals = list_make1(rinfo);
iclause->lossy = false;
iclause->indexcol = indexcol;
iclause->indexcols = NIL;
return iclause;
}
/*
* We do not currently ask support functions about ScalarArrayOpExprs,
* though in principle we could.
*/
}
return NULL;
}
/*
* match_rowcompare_to_indexcol()
* Handles the RowCompareExpr case for match_clause_to_indexcol(),
* which see for comments.
*
* In this routine we check whether the first column of the row comparison
* matches the target index column. This is sufficient to guarantee that some
* index condition can be constructed from the RowCompareExpr --- the rest
* is handled by expand_indexqual_rowcompare().
*/
static IndexClause *
match_rowcompare_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index)
{
RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause;
Index index_relid;
Oid opfamily;
Oid idxcollation;
Node *leftop,
*rightop;
bool var_on_left;
Oid expr_op;
Oid expr_coll;
/* Forget it if we're not dealing with a btree index */
if (index->relam != BTREE_AM_OID)
return NULL;
index_relid = index->rel->relid;
opfamily = index->opfamily[indexcol];
idxcollation = index->indexcollations[indexcol];
/*
* We could do the matching on the basis of insisting that the opfamily
* shown in the RowCompareExpr be the same as the index column's opfamily,
* but that could fail in the presence of reverse-sort opfamilies: it'd be
* a matter of chance whether RowCompareExpr had picked the forward or
* reverse-sort family. So look only at the operator, and match if it is
* a member of the index's opfamily (after commutation, if the indexkey is
* on the right). We'll worry later about whether any additional
* operators are matchable to the index.
*/
leftop = (Node *) linitial(clause->largs);
rightop = (Node *) linitial(clause->rargs);
expr_op = linitial_oid(clause->opnos);
expr_coll = linitial_oid(clause->inputcollids);
/* Collations must match, if relevant */
if (!IndexCollMatchesExprColl(idxcollation, expr_coll))
return NULL;
/*
* These syntactic tests are the same as in match_opclause_to_indexcol()
*/
if (match_index_to_operand(leftop, indexcol, index) &&
!bms_is_member(index_relid, pull_varnos(root, rightop)) &&
!contain_volatile_functions(rightop))
{
/* OK, indexkey is on left */
var_on_left = true;
}
else if (match_index_to_operand(rightop, indexcol, index) &&
!bms_is_member(index_relid, pull_varnos(root, leftop)) &&
!contain_volatile_functions(leftop))
{
/* indexkey is on right, so commute the operator */
expr_op = get_commutator(expr_op);
if (expr_op == InvalidOid)
return NULL;
var_on_left = false;
}
else
return NULL;
/* We're good if the operator is the right type of opfamily member */
switch (get_op_opfamily_strategy(expr_op, opfamily))
{
case BTLessStrategyNumber:
case BTLessEqualStrategyNumber:
case BTGreaterEqualStrategyNumber:
case BTGreaterStrategyNumber:
return expand_indexqual_rowcompare(root,
rinfo,
indexcol,
index,
expr_op,
var_on_left);
}
return NULL;
}
/*
* match_orclause_to_indexcol()
* Handles the OR-expr case for match_clause_to_indexcol() in the case
* when it could be transformed to ScalarArrayOpExpr.
*
* In this routine, we attempt to transform a list of OR-clause args into a
* single SAOP expression matching the target index column. On success,
* return an IndexClause containing the transformed expression.
* Return NULL if the transformation fails.
*/
static IndexClause *
match_orclause_to_indexcol(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index)
{
BoolExpr *orclause = (BoolExpr *) rinfo->orclause;
List *consts = NIL;
Node *indexExpr = NULL;
Oid matchOpno = InvalidOid;
Oid consttype = InvalidOid;
Oid arraytype = InvalidOid;
Oid inputcollid = InvalidOid;
bool firstTime = true;
bool haveNonConst = false;
Index indexRelid = index->rel->relid;
ScalarArrayOpExpr *saopexpr;
IndexClause *iclause;
ListCell *lc;
/* Forget it if index doesn't support SAOP clauses */
if (!index->amsearcharray)
return NULL;
/*
* Try to convert a list of OR-clauses to a single SAOP expression. Each
* OR entry must be in the form: (indexkey operator constant) or (constant
* operator indexkey). Operators of all the entries must match. On
* discovery of anything unsupported, we give up by breaking out of the
* loop immediately and returning NULL.
*/
foreach(lc, orclause->args)
{
RestrictInfo *subRinfo = (RestrictInfo *) lfirst(lc);
OpExpr *subClause;
Oid opno;
Node *leftop,
*rightop;
Node *constExpr;
/* If it's not a RestrictInfo (i.e. it's a sub-AND), we can't use it */
if (!IsA(subRinfo, RestrictInfo))
break;
/* Only operator clauses can match */
if (!IsA(subRinfo->clause, OpExpr))
break;
subClause = (OpExpr *) subRinfo->clause;
opno = subClause->opno;
/* Only binary operators can match */
if (list_length(subClause->args) != 2)
break;
/*
* Check for clauses of the form: (indexkey operator constant) or
* (constant operator indexkey). These tests should agree with
* match_opclause_to_indexcol.
*/
leftop = (Node *) linitial(subClause->args);
rightop = (Node *) lsecond(subClause->args);
if (match_index_to_operand(leftop, indexcol, index) &&
!bms_is_member(indexRelid, subRinfo->right_relids) &&
!contain_volatile_functions(rightop))
{
indexExpr = leftop;
constExpr = rightop;
}
else if (match_index_to_operand(rightop, indexcol, index) &&
!bms_is_member(indexRelid, subRinfo->left_relids) &&
!contain_volatile_functions(leftop))
{
opno = get_commutator(opno);
if (!OidIsValid(opno))
{
/* commutator doesn't exist, we can't reverse the order */
break;
}
indexExpr = rightop;
constExpr = leftop;
}
else
{
break;
}
/*
* Save information about the operator, type, and collation for the
* first matching qual. Then, check that subsequent quals match the
* first.
*/
if (firstTime)
{
matchOpno = opno;
consttype = exprType(constExpr);
arraytype = get_array_type(consttype);
inputcollid = subClause->inputcollid;
/*
* Check that the operator is presented in the opfamily and that
* the expression collation matches the index collation. Also,
* there must be an array type to construct an array later.
*/
if (!IndexCollMatchesExprColl(index->indexcollations[indexcol],
inputcollid) ||
!op_in_opfamily(matchOpno, index->opfamily[indexcol]) ||
!OidIsValid(arraytype))
break;
/*
* Disallow if either type is RECORD, mainly because we can't be
* positive that all the RHS expressions are the same record type.
*/
if (consttype == RECORDOID || exprType(indexExpr) == RECORDOID)
break;
firstTime = false;
}
else
{
if (matchOpno != opno ||
inputcollid != subClause->inputcollid ||
consttype != exprType(constExpr))
break;
}
/*
* The righthand inputs don't necessarily have to be plain Consts, but
* make_SAOP_expr needs to know if any are not.
*/
if (!IsA(constExpr, Const))
haveNonConst = true;
consts = lappend(consts, constExpr);
}
/*
* Handle failed conversion from breaking out of the loop because of an
* unsupported qual. Also check that we have an indexExpr, just in case
* the OR list was somehow empty (it shouldn't be). Return NULL to
* indicate the conversion failed.
*/
if (lc != NULL || indexExpr == NULL)
{
list_free(consts); /* might as well */
return NULL;
}
/*
* Build the new SAOP node. We use the indexExpr from the last OR arm;
* since all the arms passed match_index_to_operand, it shouldn't matter
* which one we use. But using "inputcollid" twice is a bit of a cheat:
* we might end up with an array Const node that is labeled with a
* collation despite its elements being of a noncollatable type. But
* nothing is likely to complain about that, so we don't bother being more
* accurate.
*/
saopexpr = make_SAOP_expr(matchOpno, indexExpr, consttype, inputcollid,
inputcollid, consts, haveNonConst);
Assert(saopexpr != NULL);
/*
* Finally, build an IndexClause based on the SAOP node. It's not lossy.
*/
iclause = makeNode(IndexClause);
iclause->rinfo = rinfo;
iclause->indexquals = list_make1(make_simple_restrictinfo(root,
(Expr *) saopexpr));
iclause->lossy = false;
iclause->indexcol = indexcol;
iclause->indexcols = NIL;
return iclause;
}
/*
* expand_indexqual_rowcompare --- expand a single indexqual condition
* that is a RowCompareExpr
*
* It's already known that the first column of the row comparison matches
* the specified column of the index. We can use additional columns of the
* row comparison as index qualifications, so long as they match the index
* in the "same direction", ie, the indexkeys are all on the same side of the
* clause and the operators are all the same-type members of the opfamilies.
*
* If all the columns of the RowCompareExpr match in this way, we just use it
* as-is, except for possibly commuting it to put the indexkeys on the left.
*
* Otherwise, we build a shortened RowCompareExpr (if more than one
* column matches) or a simple OpExpr (if the first-column match is all
* there is). In these cases the modified clause is always "<=" or ">="
* even when the original was "<" or ">" --- this is necessary to match all
* the rows that could match the original. (We are building a lossy version
* of the row comparison when we do this, so we set lossy = true.)
*
* Note: this is really just the last half of match_rowcompare_to_indexcol,
* but we split it out for comprehensibility.
*/
static IndexClause *
expand_indexqual_rowcompare(PlannerInfo *root,
RestrictInfo *rinfo,
int indexcol,
IndexOptInfo *index,
Oid expr_op,
bool var_on_left)
{
IndexClause *iclause = makeNode(IndexClause);
RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause;
int op_strategy;
Oid op_lefttype;
Oid op_righttype;
int matching_cols;
List *expr_ops;
List *opfamilies;
List *lefttypes;
List *righttypes;
List *new_ops;
List *var_args;
List *non_var_args;
iclause->rinfo = rinfo;
iclause->indexcol = indexcol;
if (var_on_left)
{
var_args = clause->largs;
non_var_args = clause->rargs;
}
else
{
var_args = clause->rargs;
non_var_args = clause->largs;
}
get_op_opfamily_properties(expr_op, index->opfamily[indexcol], false,
&op_strategy,
&op_lefttype,
&op_righttype);
/* Initialize returned list of which index columns are used */
iclause->indexcols = list_make1_int(indexcol);
/* Build lists of ops, opfamilies and operator datatypes in case needed */
expr_ops = list_make1_oid(expr_op);
opfamilies = list_make1_oid(index->opfamily[indexcol]);
lefttypes = list_make1_oid(op_lefttype);
righttypes = list_make1_oid(op_righttype);
/*
* See how many of the remaining columns match some index column in the
* same way. As in match_clause_to_indexcol(), the "other" side of any
* potential index condition is OK as long as it doesn't use Vars from the
* indexed relation.
*/
matching_cols = 1;
while (matching_cols < list_length(var_args))
{
Node *varop = (Node *) list_nth(var_args, matching_cols);
Node *constop = (Node *) list_nth(non_var_args, matching_cols);
int i;
expr_op = list_nth_oid(clause->opnos, matching_cols);
if (!var_on_left)
{
/* indexkey is on right, so commute the operator */
expr_op = get_commutator(expr_op);
if (expr_op == InvalidOid)
break; /* operator is not usable */
}
if (bms_is_member(index->rel->relid, pull_varnos(root, constop)))
break; /* no good, Var on wrong side */
if (contain_volatile_functions(constop))
break; /* no good, volatile comparison value */
/*
* The Var side can match any key column of the index.
*/
for (i = 0; i < index->nkeycolumns; i++)
{
if (match_index_to_operand(varop, i, index) &&
get_op_opfamily_strategy(expr_op,
index->opfamily[i]) == op_strategy &&
IndexCollMatchesExprColl(index->indexcollations[i],
list_nth_oid(clause->inputcollids,
matching_cols)))
break;
}
if (i >= index->nkeycolumns)
break; /* no match found */
/* Add column number to returned list */
iclause->indexcols = lappend_int(iclause->indexcols, i);
/* Add operator info to lists */
get_op_opfamily_properties(expr_op, index->opfamily[i], false,
&op_strategy,
&op_lefttype,
&op_righttype);
expr_ops = lappend_oid(expr_ops, expr_op);
opfamilies = lappend_oid(opfamilies, index->opfamily[i]);
lefttypes = lappend_oid(lefttypes, op_lefttype);
righttypes = lappend_oid(righttypes, op_righttype);
/* This column matches, keep scanning */
matching_cols++;
}
/* Result is non-lossy if all columns are usable as index quals */
iclause->lossy = (matching_cols != list_length(clause->opnos));
/*
* We can use rinfo->clause as-is if we have var on left and it's all
* usable as index quals.
*/
if (var_on_left && !iclause->lossy)
iclause->indexquals = list_make1(rinfo);
else
{
/*
* We have to generate a modified rowcompare (possibly just one
* OpExpr). The painful part of this is changing < to <= or > to >=,
* so deal with that first.
*/
if (!iclause->lossy)
{
/* very easy, just use the commuted operators */
new_ops = expr_ops;
}
else if (op_strategy == BTLessEqualStrategyNumber ||
op_strategy == BTGreaterEqualStrategyNumber)
{
/* easy, just use the same (possibly commuted) operators */
new_ops = list_truncate(expr_ops, matching_cols);
}
else
{
ListCell *opfamilies_cell;
ListCell *lefttypes_cell;
ListCell *righttypes_cell;
if (op_strategy == BTLessStrategyNumber)
op_strategy = BTLessEqualStrategyNumber;
else if (op_strategy == BTGreaterStrategyNumber)
op_strategy = BTGreaterEqualStrategyNumber;
else
elog(ERROR, "unexpected strategy number %d", op_strategy);
new_ops = NIL;
forthree(opfamilies_cell, opfamilies,
lefttypes_cell, lefttypes,
righttypes_cell, righttypes)
{
Oid opfam = lfirst_oid(opfamilies_cell);
Oid lefttype = lfirst_oid(lefttypes_cell);
Oid righttype = lfirst_oid(righttypes_cell);
expr_op = get_opfamily_member(opfam, lefttype, righttype,
op_strategy);
if (!OidIsValid(expr_op)) /* should not happen */
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
op_strategy, lefttype, righttype, opfam);
new_ops = lappend_oid(new_ops, expr_op);
}
}
/* If we have more than one matching col, create a subset rowcompare */
if (matching_cols > 1)
{
RowCompareExpr *rc = makeNode(RowCompareExpr);
rc->cmptype = (CompareType) op_strategy;
rc->opnos = new_ops;
rc->opfamilies = list_copy_head(clause->opfamilies,
matching_cols);
rc->inputcollids = list_copy_head(clause->inputcollids,
matching_cols);
rc->largs = list_copy_head(var_args, matching_cols);
rc->rargs = list_copy_head(non_var_args, matching_cols);
iclause->indexquals = list_make1(make_simple_restrictinfo(root,
(Expr *) rc));
}
else
{
Expr *op;
/* We don't report an index column list in this case */
iclause->indexcols = NIL;
op = make_opclause(linitial_oid(new_ops), BOOLOID, false,
copyObject(linitial(var_args)),
copyObject(linitial(non_var_args)),
InvalidOid,
linitial_oid(clause->inputcollids));
iclause->indexquals = list_make1(make_simple_restrictinfo(root, op));
}
}
return iclause;
}
/****************************************************************************
* ---- ROUTINES TO CHECK ORDERING OPERATORS ----
****************************************************************************/
/*
* match_pathkeys_to_index
* For the given 'index' and 'pathkeys', output a list of suitable ORDER
* BY expressions, each of the form "indexedcol operator pseudoconstant",
* along with an integer list of the index column numbers (zero based)
* that each clause would be used with.
*
* This attempts to find an ORDER BY and index column number for all items in
* the pathkey list, however, if we're unable to match any given pathkey to an
* index column, we return just the ones matched by the function so far. This
* allows callers who are interested in partial matches to get them. Callers
* can determine a partial match vs a full match by checking the outputted
* list lengths. A full match will have one item in the output lists for each
* item in the given 'pathkeys' list.
*/
static void
match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
List **orderby_clauses_p,
List **clause_columns_p)
{
ListCell *lc1;
*orderby_clauses_p = NIL; /* set default results */
*clause_columns_p = NIL;
/* Only indexes with the amcanorderbyop property are interesting here */
if (!index->amcanorderbyop)
return;
foreach(lc1, pathkeys)
{
PathKey *pathkey = (PathKey *) lfirst(lc1);
bool found = false;
EquivalenceMemberIterator it;
EquivalenceMember *member;
/* Pathkey must request default sort order for the target opfamily */
if (pathkey->pk_cmptype != COMPARE_LT || pathkey->pk_nulls_first)
return;
/* If eclass is volatile, no hope of using an indexscan */
if (pathkey->pk_eclass->ec_has_volatile)
return;
/*
* Try to match eclass member expression(s) to index. Note that child
* EC members are considered, but only when they belong to the target
* relation. (Unlike regular members, the same expression could be a
* child member of more than one EC. Therefore, the same index could
* be considered to match more than one pathkey list, which is OK
* here. See also get_eclass_for_sort_expr.)
*/
setup_eclass_member_iterator(&it, pathkey->pk_eclass,
index->rel->relids);
while ((member = eclass_member_iterator_next(&it)) != NULL)
{
int indexcol;
/* No possibility of match if it references other relations */
if (!bms_equal(member->em_relids, index->rel->relids))
continue;
/*
* We allow any column of the index to match each pathkey; they
* don't have to match left-to-right as you might expect. This is
* correct for GiST, and it doesn't matter for SP-GiST because
* that doesn't handle multiple columns anyway, and no other
* existing AMs support amcanorderbyop. We might need different
* logic in future for other implementations.
*/
for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
{
Expr *expr;
expr = match_clause_to_ordering_op(index,
indexcol,
member->em_expr,
pathkey->pk_opfamily);
if (expr)
{
*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
*clause_columns_p = lappend_int(*clause_columns_p, indexcol);
found = true;
break;
}
}
if (found) /* don't want to look at remaining members */
break;
}
/*
* Return the matches found so far when this pathkey couldn't be
* matched to the index.
*/
if (!found)
return;
}
}
/*
* match_clause_to_ordering_op
* Determines whether an ordering operator expression matches an
* index column.
*
* This is similar to, but simpler than, match_clause_to_indexcol.
* We only care about simple OpExpr cases. The input is a bare
* expression that is being ordered by, which must be of the form
* (indexkey op const) or (const op indexkey) where op is an ordering
* operator for the column's opfamily.
*
* 'index' is the index of interest.
* 'indexcol' is a column number of 'index' (counting from 0).
* 'clause' is the ordering expression to be tested.
* 'pk_opfamily' is the btree opfamily describing the required sort order.
*
* Note that we currently do not consider the collation of the ordering
* operator's result. In practical cases the result type will be numeric
* and thus have no collation, and it's not very clear what to match to
* if it did have a collation. The index's collation should match the
* ordering operator's input collation, not its result.
*
* If successful, return 'clause' as-is if the indexkey is on the left,
* otherwise a commuted copy of 'clause'. If no match, return NULL.
*/
static Expr *
match_clause_to_ordering_op(IndexOptInfo *index,
int indexcol,
Expr *clause,
Oid pk_opfamily)
{
Oid opfamily;
Oid idxcollation;
Node *leftop,
*rightop;
Oid expr_op;
Oid expr_coll;
Oid sortfamily;
bool commuted;
Assert(indexcol < index->nkeycolumns);
opfamily = index->opfamily[indexcol];
idxcollation = index->indexcollations[indexcol];
/*
* Clause must be a binary opclause.
*/
if (!is_opclause(clause))
return NULL;
leftop = get_leftop(clause);
rightop = get_rightop(clause);
if (!leftop || !rightop)
return NULL;
expr_op = ((OpExpr *) clause)->opno;
expr_coll = ((OpExpr *) clause)->inputcollid;
/*
* We can forget the whole thing right away if wrong collation.
*/
if (!IndexCollMatchesExprColl(idxcollation, expr_coll))
return NULL;
/*
* Check for clauses of the form: (indexkey operator constant) or
* (constant operator indexkey).
*/
if (match_index_to_operand(leftop, indexcol, index) &&
!contain_var_clause(rightop) &&
!contain_volatile_functions(rightop))
{
commuted = false;
}
else if (match_index_to_operand(rightop, indexcol, index) &&
!contain_var_clause(leftop) &&
!contain_volatile_functions(leftop))
{
/* Might match, but we need a commuted operator */
expr_op = get_commutator(expr_op);
if (expr_op == InvalidOid)
return NULL;
commuted = true;
}
else
return NULL;
/*
* Is the (commuted) operator an ordering operator for the opfamily? And
* if so, does it yield the right sorting semantics?
*/
sortfamily = get_op_opfamily_sortfamily(expr_op, opfamily);
if (sortfamily != pk_opfamily)
return NULL;
/* We have a match. Return clause or a commuted version thereof. */
if (commuted)
{
OpExpr *newclause = makeNode(OpExpr);
/* flat-copy all the fields of clause */
memcpy(newclause, clause, sizeof(OpExpr));
/* commute it */
newclause->opno = expr_op;
newclause->opfuncid = InvalidOid;
newclause->args = list_make2(rightop, leftop);
clause = (Expr *) newclause;
}
return clause;
}
/****************************************************************************
* ---- ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS ----
****************************************************************************/
/*
* check_index_predicates
* Set the predicate-derived IndexOptInfo fields for each index
* of the specified relation.
*
* predOK is set true if the index is partial and its predicate is satisfied
* for this query, ie the query's WHERE clauses imply the predicate.
*
* indrestrictinfo is set to the relation's baserestrictinfo list less any
* conditions that are implied by the index's predicate. (Obviously, for a
* non-partial index, this is the same as baserestrictinfo.) Such conditions
* can be dropped from the plan when using the index, in certain cases.
*
* At one time it was possible for this to get re-run after adding more
* restrictions to the rel, thus possibly letting us prove more indexes OK.
* That doesn't happen any more (at least not in the core code's usage),
* but this code still supports it in case extensions want to mess with the
* baserestrictinfo list. We assume that adding more restrictions can't make
* an index not predOK. We must recompute indrestrictinfo each time, though,
* to make sure any newly-added restrictions get into it if needed.
*/
void
check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
{
List *clauselist;
bool have_partial;
bool is_target_rel;
Relids otherrels;
ListCell *lc;
/* Indexes are available only on base or "other" member relations. */
Assert(IS_SIMPLE_REL(rel));
/*
* Initialize the indrestrictinfo lists to be identical to
* baserestrictinfo, and check whether there are any partial indexes. If
* not, this is all we need to do.
*/
have_partial = false;
foreach(lc, rel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
index->indrestrictinfo = rel->baserestrictinfo;
if (index->indpred)
have_partial = true;
}
if (!have_partial)
return;
/*
* Construct a list of clauses that we can assume true for the purpose of
* proving the index(es) usable. Restriction clauses for the rel are
* always usable, and so are any join clauses that are "movable to" this
* rel. Also, we can consider any EC-derivable join clauses (which must
* be "movable to" this rel, by definition).
*/
clauselist = list_copy(rel->baserestrictinfo);
/* Scan the rel's join clauses */
foreach(lc, rel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/* Check if clause can be moved to this rel */
if (!join_clause_is_movable_to(rinfo, rel))
continue;
clauselist = lappend(clauselist, rinfo);
}
/*
* Add on any equivalence-derivable join clauses. Computing the correct
* relid sets for generate_join_implied_equalities is slightly tricky
* because the rel could be a child rel rather than a true baserel, and in
* that case we must subtract its parents' relid(s) from all_query_rels.
* Additionally, we mustn't consider clauses that are only computable
* after outer joins that can null the rel.
*/
if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
otherrels = bms_difference(root->all_query_rels,
find_childrel_parents(root, rel));
else
otherrels = bms_difference(root->all_query_rels, rel->relids);
otherrels = bms_del_members(otherrels, rel->nulling_relids);
if (!bms_is_empty(otherrels))
clauselist =
list_concat(clauselist,
generate_join_implied_equalities(root,
bms_union(rel->relids,
otherrels),
otherrels,
rel,
NULL));
/*
* Normally we remove quals that are implied by a partial index's
* predicate from indrestrictinfo, indicating that they need not be
* checked explicitly by an indexscan plan using this index. However, if
* the rel is a target relation of UPDATE/DELETE/MERGE/SELECT FOR UPDATE,
* we cannot remove such quals from the plan, because they need to be in
* the plan so that they will be properly rechecked by EvalPlanQual
* testing. Some day we might want to remove such quals from the main
* plan anyway and pass them through to EvalPlanQual via a side channel;
* but for now, we just don't remove implied quals at all for target
* relations.
*/
is_target_rel = (bms_is_member(rel->relid, root->all_result_relids) ||
get_plan_rowmark(root->rowMarks, rel->relid) != NULL);
/*
* Now try to prove each index predicate true, and compute the
* indrestrictinfo lists for partial indexes. Note that we compute the
* indrestrictinfo list even for non-predOK indexes; this might seem
* wasteful, but we may be able to use such indexes in OR clauses, cf
* generate_bitmap_or_paths().
*/
foreach(lc, rel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
ListCell *lcr;
if (index->indpredExpand == NIL)
continue; /* ignore non-partial indexes here */
if (!index->predOK) /* don't repeat work if already proven OK */
index->predOK = predicate_implied_by(index->indpredExpand, clauselist,
false);
/* If rel is an update target, leave indrestrictinfo as set above */
if (is_target_rel)
continue;
/*
* If index is !amoptionalkey, also leave indrestrictinfo as set
* above. Otherwise we risk removing all quals for the first index
* key and then not being able to generate an indexscan at all. It
* would be better to be more selective, but we've not yet identified
* which if any of the quals match the first index key.
*/
if (!index->amoptionalkey)
continue;
/* Else compute indrestrictinfo as the non-implied quals */
index->indrestrictinfo = NIL;
foreach(lcr, rel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lcr);
/* predicate_implied_by() assumes first arg is immutable */
if (contain_mutable_functions((Node *) rinfo->clause) ||
!predicate_implied_by(list_make1(rinfo->clause),
index->indpredExpand, false))
index->indrestrictinfo = lappend(index->indrestrictinfo, rinfo);
}
}
}
/****************************************************************************
* ---- ROUTINES TO CHECK EXTERNALLY-VISIBLE CONDITIONS ----
****************************************************************************/
/*
* ec_member_matches_indexcol
* Test whether an EquivalenceClass member matches an index column.
*
* This is a callback for use by generate_implied_equalities_for_column.
*/
static bool
ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
EquivalenceClass *ec, EquivalenceMember *em,
void *arg)
{
IndexOptInfo *index = ((ec_member_matches_arg *) arg)->index;
int indexcol = ((ec_member_matches_arg *) arg)->indexcol;
Oid curFamily;
Oid curCollation;
Assert(indexcol < index->nkeycolumns);
curFamily = index->opfamily[indexcol];
curCollation = index->indexcollations[indexcol];
/*
* If it's a btree index, we can reject it if its opfamily isn't
* compatible with the EC, since no clause generated from the EC could be
* used with the index. For non-btree indexes, we can't easily tell
* whether clauses generated from the EC could be used with the index, so
* don't check the opfamily. This might mean we return "true" for a
* useless EC, so we have to recheck the results of
* generate_implied_equalities_for_column; see
* match_eclass_clauses_to_index.
*/
if (index->relam == BTREE_AM_OID &&
!list_member_oid(ec->ec_opfamilies, curFamily))
return false;
/* We insist on collation match for all index types, though */
if (!IndexCollMatchesExprColl(curCollation, ec->ec_collation))
return false;
return match_index_to_operand((Node *) em->em_expr, indexcol, index);
}
/*
* relation_has_unique_index_for
* Determine whether the relation provably has at most one row satisfying
* a set of equality conditions, because the conditions constrain all
* columns of some unique index.
*
* The conditions are provided as a list of RestrictInfo nodes, where the
* caller has already determined that each condition is a mergejoinable
* equality with an expression in this relation on one side, and an
* expression not involving this relation on the other. The transient
* outer_is_left flag is used to identify which side we should look at:
* left side if outer_is_left is false, right side if it is true.
*
* The caller need only supply equality conditions arising from joins;
* this routine automatically adds in any usable baserestrictinfo clauses.
* (Note that the passed-in restrictlist will be destructively modified!)
*
* If extra_clauses isn't NULL, return baserestrictinfo clauses which were used
* to derive uniqueness.
*/
bool
relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
List *restrictlist, List **extra_clauses)
{
ListCell *ic;
/* Short-circuit if no indexes... */
if (rel->indexlist == NIL)
return false;
/*
* Examine the rel's restriction clauses for usable var = const clauses
* that we can add to the restrictlist.
*/
foreach(ic, rel->baserestrictinfo)
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(ic);
/*
* Note: can_join won't be set for a restriction clause, but
* mergeopfamilies will be if it has a mergejoinable operator and
* doesn't contain volatile functions.
*/
if (restrictinfo->mergeopfamilies == NIL)
continue; /* not mergejoinable */
/*
* The clause certainly doesn't refer to anything but the given rel.
* If either side is pseudoconstant then we can use it.
*/
if (bms_is_empty(restrictinfo->left_relids))
{
/* righthand side is inner */
restrictinfo->outer_is_left = true;
}
else if (bms_is_empty(restrictinfo->right_relids))
{
/* lefthand side is inner */
restrictinfo->outer_is_left = false;
}
else
continue;
/* OK, add to list */
restrictlist = lappend(restrictlist, restrictinfo);
}
/* Short-circuit the easy case */
if (restrictlist == NIL)
return false;
/* Examine each index of the relation ... */
foreach(ic, rel->indexlist)
{
IndexOptInfo *ind = (IndexOptInfo *) lfirst(ic);
int c;
List *exprs = NIL;
/*
* If the index is not unique, or not immediately enforced, or if it's
* a partial index, it's useless here. We're unable to make use of
* predOK partial unique indexes due to the fact that
* check_index_predicates() also makes use of join predicates to
* determine if the partial index is usable. Here we need proofs that
* hold true before any joins are evaluated.
*/
if (!ind->unique || !ind->immediate || ind->indpred != NIL)
continue;
/*
* Try to find each index column in the list of conditions. This is
* O(N^2) or worse, but we expect all the lists to be short.
*/
for (c = 0; c < ind->nkeycolumns; c++)
{
ListCell *lc;
foreach(lc, restrictlist)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
Node *rexpr;
/*
* The condition's equality operator must be a member of the
* index opfamily, else it is not asserting the right kind of
* equality behavior for this index. We check this first
* since it's probably the cheapest test.
*/
if (!list_member_oid(rinfo->mergeopfamilies, ind->opfamily[c]))
continue;
/*
* The index's collation must agree with the clause's input
* collation on equality, else the index's uniqueness does not
* imply uniqueness under the clause's equality semantics.
*/
if (!collations_agree_on_equality(ind->indexcollations[c],
exprInputCollation((Node *) rinfo->clause)))
continue;
/* OK, see if the condition operand matches the index key */
if (rinfo->outer_is_left)
rexpr = get_rightop(rinfo->clause);
else
rexpr = get_leftop(rinfo->clause);
if (match_index_to_operand(rexpr, c, ind))
{
if (bms_membership(rinfo->clause_relids) == BMS_SINGLETON)
{
MemoryContext oldMemCtx =
MemoryContextSwitchTo(root->planner_cxt);
/*
* Add filter clause into a list allowing caller to
* know if uniqueness have made not only by join
* clauses.
*/
Assert(bms_is_empty(rinfo->left_relids) ||
bms_is_empty(rinfo->right_relids));
if (extra_clauses)
exprs = lappend(exprs, rinfo);
MemoryContextSwitchTo(oldMemCtx);
}
break; /* found a match; column is unique */
}
}
if (lc == NULL)
break; /* no match; this index doesn't help us */
}
/* Matched all key columns of this index? */
if (c == ind->nkeycolumns)
{
if (extra_clauses)
*extra_clauses = exprs;
return true;
}
}
return false;
}
/*
* indexcol_is_bool_constant_for_query
*
* If an index column is constrained to have a constant value by the query's
* WHERE conditions, then it's irrelevant for sort-order considerations.
* Usually that means we have a restriction clause WHERE indexcol = constant,
* which gets turned into an EquivalenceClass containing a constant, which
* is recognized as redundant by build_index_pathkeys(). But if the index
* column is a boolean variable (or expression), then we are not going to
* see WHERE indexcol = constant, because expression preprocessing will have
* simplified that to "WHERE indexcol" or "WHERE NOT indexcol". So we are not
* going to have a matching EquivalenceClass (unless the query also contains
* "ORDER BY indexcol"). To allow such cases to work the same as they would
* for non-boolean values, this function is provided to detect whether the
* specified index column matches a boolean restriction clause.
*/
bool
indexcol_is_bool_constant_for_query(PlannerInfo *root,
IndexOptInfo *index,
int indexcol)
{
ListCell *lc;
/* If the index isn't boolean, we can't possibly get a match */
if (!IsBooleanOpfamily(index->opfamily[indexcol]))
return false;
/* Check each restriction clause for the index's rel */
foreach(lc, index->rel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/*
* As in match_clause_to_indexcol, never match pseudoconstants to
* indexes. (It might be semantically okay to do so here, but the
* odds of getting a match are negligible, so don't waste the cycles.)
*/
if (rinfo->pseudoconstant)
continue;
/* See if we can match the clause's expression to the index column */
if (match_boolean_index_clause(root, rinfo, indexcol, index))
return true;
}
return false;
}
/****************************************************************************
* ---- ROUTINES TO CHECK OPERANDS ----
****************************************************************************/
/*
* match_index_to_operand()
* Generalized test for a match between an index's key
* and the operand on one side of a restriction or join clause.
*
* operand: the nodetree to be compared to the index
* indexcol: the column number of the index (counting from 0)
* index: the index of interest
*
* Note that we aren't interested in collations here; the caller must check
* for a collation match, if it's dealing with an operator where that matters.
*
* This is exported for use in selfuncs.c.
*/
bool
match_index_to_operand(Node *operand,
int indexcol,
IndexOptInfo *index)
{
int indkey;
/*
* Ignore any PlaceHolderVar node contained in the operand. This is
* needed to be able to apply indexscanning in cases where the operand (or
* a subtree) has been wrapped in PlaceHolderVars to enforce separate
* identity or as a result of outer joins.
*/
operand = strip_noop_phvs(operand);
/*
* Ignore any RelabelType node above the operand. This is needed to be
* able to apply indexscanning in binary-compatible-operator cases.
*
* Note: we must handle nested RelabelType nodes here. While
* eval_const_expressions() will have simplified them to at most one
* layer, our prior stripping of PlaceHolderVars may have brought separate
* RelabelTypes into adjacency.
*/
while (operand && IsA(operand, RelabelType))
operand = (Node *) ((RelabelType *) operand)->arg;
indkey = index->indexkeys[indexcol];
if (indkey != 0)
{
/*
* Simple index column; operand must be a matching Var.
*/
if (operand && IsA(operand, Var) &&
index->rel->relid == ((Var *) operand)->varno &&
indkey == ((Var *) operand)->varattno &&
((Var *) operand)->varnullingrels == NULL)
return true;
}
else
{
/*
* Index expression; find the correct expression. (This search could
* be avoided, at the cost of complicating all the callers of this
* routine; doesn't seem worth it.)
*/
ListCell *indexpr_item;
int i;
Node *indexkey;
indexpr_item = list_head(index->indexprsExpand);
for (i = 0; i < indexcol; i++)
{
if (index->indexkeys[i] == 0)
{
if (indexpr_item == NULL)
elog(ERROR, "wrong number of index expressions");
indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
if (indexpr_item == NULL)
elog(ERROR, "wrong number of index expressions");
indexkey = (Node *) lfirst(indexpr_item);
/*
* Does it match the operand? Again, strip any relabeling.
*/
if (indexkey && IsA(indexkey, RelabelType))
indexkey = (Node *) ((RelabelType *) indexkey)->arg;
if (equal(indexkey, operand))
return true;
}
return false;
}
/*
* is_pseudo_constant_for_index()
* Test whether the given expression can be used as an indexscan
* comparison value.
*
* An indexscan comparison value must not contain any volatile functions,
* and it can't contain any Vars of the index's own table. Vars of
* other tables are okay, though; in that case we'd be producing an
* indexqual usable in a parameterized indexscan. This is, therefore,
* a weaker condition than is_pseudo_constant_clause().
*
* This function is exported for use by planner support functions,
* which will have available the IndexOptInfo, but not any RestrictInfo
* infrastructure. It is making the same test made by functions above
* such as match_opclause_to_indexcol(), but those rely where possible
* on RestrictInfo information about variable membership.
*
* expr: the nodetree to be checked
* index: the index of interest
*/
bool
is_pseudo_constant_for_index(PlannerInfo *root, Node *expr, IndexOptInfo *index)
{
/* pull_varnos is cheaper than volatility check, so do that first */
if (bms_is_member(index->rel->relid, pull_varnos(root, expr)))
return false; /* no good, contains Var of table */
if (contain_volatile_functions(expr))
return false; /* no good, volatile comparison value */
return true;
}
./makefuncs.c 0000664 0001750 0001750 00000057017 15221615341 012037 0 ustar xman xman /*-------------------------------------------------------------------------
*
* makefuncs.c
* creator functions for various nodes. The functions here are for the
* most frequently created nodes.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/nodes/makefuncs.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "utils/lsyscache.h"
/*
* makeA_Expr -
* makes an A_Expr node
*/
A_Expr *
makeA_Expr(A_Expr_Kind kind, List *name,
Node *lexpr, Node *rexpr, int location)
{
A_Expr *a = makeNode(A_Expr);
a->kind = kind;
a->name = name;
a->lexpr = lexpr;
a->rexpr = rexpr;
a->location = location;
return a;
}
/*
* makeSimpleA_Expr -
* As above, given a simple (unqualified) operator name
*/
A_Expr *
makeSimpleA_Expr(A_Expr_Kind kind, char *name,
Node *lexpr, Node *rexpr, int location)
{
A_Expr *a = makeNode(A_Expr);
a->kind = kind;
a->name = list_make1(makeString(name));
a->lexpr = lexpr;
a->rexpr = rexpr;
a->location = location;
return a;
}
/*
* makeVar -
* creates a Var node
*/
Var *
makeVar(int varno,
AttrNumber varattno,
Oid vartype,
int32 vartypmod,
Oid varcollid,
Index varlevelsup)
{
Var *var = makeNode(Var);
var->varno = varno;
var->varattno = varattno;
var->vartype = vartype;
var->vartypmod = vartypmod;
var->varcollid = varcollid;
var->varlevelsup = varlevelsup;
/*
* Only a few callers need to make Var nodes with varreturningtype
* different from VAR_RETURNING_DEFAULT, non-null varnullingrels, or with
* varnosyn/varattnosyn different from varno/varattno. We don't provide
* separate arguments for them, but just initialize them to sensible
* default values. This reduces code clutter and chance of error for most
* callers.
*/
var->varreturningtype = VAR_RETURNING_DEFAULT;
var->varnullingrels = NULL;
var->varnosyn = (Index) varno;
var->varattnosyn = varattno;
/* Likewise, we just set location to "unknown" here */
var->location = -1;
return var;
}
/*
* makeVarFromTargetEntry -
* convenience function to create a same-level Var node from a
* TargetEntry
*/
Var *
makeVarFromTargetEntry(int varno,
TargetEntry *tle)
{
return makeVar(varno,
tle->resno,
exprType((Node *) tle->expr),
exprTypmod((Node *) tle->expr),
exprCollation((Node *) tle->expr),
0);
}
/*
* makeWholeRowVar -
* creates a Var node representing a whole row of the specified RTE
*
* A whole-row reference is a Var with varno set to the correct range
* table entry, and varattno == 0 to signal that it references the whole
* tuple. (Use of zero here is unclean, since it could easily be confused
* with error cases, but it's not worth changing now.) The vartype indicates
* a rowtype; either a named composite type, or a domain over a named
* composite type (only possible if the RTE is a function returning that),
* or RECORD. This function encapsulates the logic for determining the
* correct rowtype OID to use.
*
* If allowScalar is true, then for the case where the RTE is a single function
* returning a non-composite result type, we produce a normal Var referencing
* the function's result directly, instead of the single-column composite
* value that the whole-row notation might otherwise suggest.
*/
Var *
makeWholeRowVar(RangeTblEntry *rte,
int varno,
Index varlevelsup,
bool allowScalar)
{
Var *result;
Oid toid;
Node *fexpr;
switch (rte->rtekind)
{
case RTE_RELATION:
/* relation: the rowtype is a named composite type */
toid = get_rel_type_id(rte->relid);
if (!OidIsValid(toid))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("relation \"%s\" does not have a composite type",
get_rel_name(rte->relid))));
result = makeVar(varno,
InvalidAttrNumber,
toid,
-1,
InvalidOid,
varlevelsup);
break;
case RTE_SUBQUERY:
/*
* For a standard subquery, the Var should be of RECORD type.
* However, if we're looking at a subquery that was expanded from
* a view or SRF (only possible during planning), we must use the
* appropriate rowtype, so that the resulting Var has the same
* type that we would have produced from the original RTE.
*/
if (OidIsValid(rte->relid))
{
/* Subquery was expanded from a view */
toid = get_rel_type_id(rte->relid);
if (!OidIsValid(toid))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("relation \"%s\" does not have a composite type",
get_rel_name(rte->relid))));
}
else if (rte->functions)
{
/*
* Subquery was expanded from a set-returning function. That
* would not have happened if there's more than one function
* or ordinality was requested. We also needn't worry about
* the allowScalar case, since the planner doesn't use that.
* Otherwise this must match the RTE_FUNCTION code below.
*/
Assert(!allowScalar);
fexpr = ((RangeTblFunction *) linitial(rte->functions))->funcexpr;
toid = exprType(fexpr);
if (!type_is_rowtype(toid))
toid = RECORDOID;
}
else
{
/* Normal subquery-in-FROM */
toid = RECORDOID;
}
result = makeVar(varno,
InvalidAttrNumber,
toid,
-1,
InvalidOid,
varlevelsup);
break;
case RTE_FUNCTION:
/*
* If there's more than one function, or ordinality is requested,
* force a RECORD result, since there's certainly more than one
* column involved and it can't be a known named type.
*/
if (rte->funcordinality || list_length(rte->functions) != 1)
{
/* always produces an anonymous RECORD result */
result = makeVar(varno,
InvalidAttrNumber,
RECORDOID,
-1,
InvalidOid,
varlevelsup);
break;
}
fexpr = ((RangeTblFunction *) linitial(rte->functions))->funcexpr;
toid = exprType(fexpr);
if (type_is_rowtype(toid))
{
/* func returns composite; same as relation case */
result = makeVar(varno,
InvalidAttrNumber,
toid,
-1,
InvalidOid,
varlevelsup);
}
else if (allowScalar)
{
/* func returns scalar; just return its output as-is */
result = makeVar(varno,
1,
toid,
-1,
exprCollation(fexpr),
varlevelsup);
}
else
{
/* func returns scalar, but we want a composite result */
result = makeVar(varno,
InvalidAttrNumber,
RECORDOID,
-1,
InvalidOid,
varlevelsup);
}
break;
default:
/*
* RTE is a join, tablefunc, VALUES, CTE, etc. We represent these
* cases as a whole-row Var of RECORD type. (Note that in most
* cases the Var will be expanded to a RowExpr during planning,
* but that is not our concern here.)
*/
result = makeVar(varno,
InvalidAttrNumber,
RECORDOID,
-1,
InvalidOid,
varlevelsup);
break;
}
return result;
}
/*
* makeTargetEntry -
* creates a TargetEntry node
*/
TargetEntry *
makeTargetEntry(Expr *expr,
AttrNumber resno,
char *resname,
bool resjunk)
{
TargetEntry *tle = makeNode(TargetEntry);
tle->expr = expr;
tle->resno = resno;
tle->resname = resname;
/*
* We always set these fields to 0. If the caller wants to change them he
* must do so explicitly. Few callers do that, so omitting these
* arguments reduces the chance of error.
*/
tle->ressortgroupref = 0;
tle->resorigtbl = InvalidOid;
tle->resorigcol = 0;
tle->resjunk = resjunk;
return tle;
}
/*
* flatCopyTargetEntry -
* duplicate a TargetEntry, but don't copy substructure
*
* This is commonly used when we just want to modify the resno or substitute
* a new expression.
*/
TargetEntry *
flatCopyTargetEntry(TargetEntry *src_tle)
{
TargetEntry *tle = makeNode(TargetEntry);
Assert(IsA(src_tle, TargetEntry));
memcpy(tle, src_tle, sizeof(TargetEntry));
return tle;
}
/*
* makeFromExpr -
* creates a FromExpr node
*/
FromExpr *
makeFromExpr(List *fromlist, Node *quals)
{
FromExpr *f = makeNode(FromExpr);
f->fromlist = fromlist;
f->quals = quals;
return f;
}
/*
* makeConst -
* creates a Const node
*/
Const *
makeConst(Oid consttype,
int32 consttypmod,
Oid constcollid,
int constlen,
Datum constvalue,
bool constisnull,
bool constbyval)
{
Const *cnst = makeNode(Const);
/*
* If it's a varlena value, force it to be in non-expanded (non-toasted)
* format; this avoids any possible dependency on external values and
* improves consistency of representation, which is important for equal().
*/
if (!constisnull && constlen == -1)
constvalue = PointerGetDatum(PG_DETOAST_DATUM(constvalue));
cnst->consttype = consttype;
cnst->consttypmod = consttypmod;
cnst->constcollid = constcollid;
cnst->constlen = constlen;
cnst->constvalue = constvalue;
cnst->constisnull = constisnull;
cnst->constbyval = constbyval;
cnst->location = -1; /* "unknown" */
return cnst;
}
/*
* makeNullConst -
* creates a Const node representing a NULL of the specified type/typmod
*
* This is a convenience routine that just saves a lookup of the type's
* storage properties.
*/
Const *
makeNullConst(Oid consttype, int32 consttypmod, Oid constcollid)
{
int16 typLen;
bool typByVal;
get_typlenbyval(consttype, &typLen, &typByVal);
return makeConst(consttype,
consttypmod,
constcollid,
(int) typLen,
(Datum) 0,
true,
typByVal);
}
/*
* makeBoolConst -
* creates a Const node representing a boolean value (can be NULL too)
*/
Node *
makeBoolConst(bool value, bool isnull)
{
/* note that pg_type.h hardwires size of bool as 1 ... duplicate it */
return (Node *) makeConst(BOOLOID, -1, InvalidOid, 1,
BoolGetDatum(value), isnull, true);
}
/*
* makeBoolExpr -
* creates a BoolExpr node
*/
Expr *
makeBoolExpr(BoolExprType boolop, List *args, int location)
{
BoolExpr *b = makeNode(BoolExpr);
b->boolop = boolop;
b->args = args;
b->location = location;
return (Expr *) b;
}
/*
* makeAlias -
* creates an Alias node
*
* NOTE: the given name is copied, but the colnames list (if any) isn't.
*/
Alias *
makeAlias(const char *aliasname, List *colnames)
{
Alias *a = makeNode(Alias);
a->aliasname = pstrdup(aliasname);
a->colnames = colnames;
return a;
}
/*
* makeRelabelType -
* creates a RelabelType node
*/
RelabelType *
makeRelabelType(Expr *arg, Oid rtype, int32 rtypmod, Oid rcollid,
CoercionForm rformat)
{
RelabelType *r = makeNode(RelabelType);
r->arg = arg;
r->resulttype = rtype;
r->resulttypmod = rtypmod;
r->resultcollid = rcollid;
r->relabelformat = rformat;
r->location = -1;
return r;
}
/*
* makeRangeVar -
* creates a RangeVar node (rather oversimplified case)
*/
RangeVar *
makeRangeVar(char *schemaname, char *relname, int location)
{
RangeVar *r = makeNode(RangeVar);
r->catalogname = NULL;
r->schemaname = schemaname;
r->relname = relname;
r->inh = true;
r->relpersistence = RELPERSISTENCE_PERMANENT;
r->alias = NULL;
r->location = location;
return r;
}
/*
* makeNotNullConstraint -
* creates a Constraint node for NOT NULL constraints
*/
Constraint *
makeNotNullConstraint(String *colname)
{
Constraint *notnull;
notnull = makeNode(Constraint);
notnull->contype = CONSTR_NOTNULL;
notnull->conname = NULL;
notnull->is_no_inherit = false;
notnull->deferrable = false;
notnull->initdeferred = false;
notnull->location = -1;
notnull->keys = list_make1(colname);
notnull->is_enforced = true;
notnull->skip_validation = false;
notnull->initially_valid = true;
return notnull;
}
/*
* makeTypeName -
* build a TypeName node for an unqualified name.
*
* typmod is defaulted, but can be changed later by caller.
*/
TypeName *
makeTypeName(char *typnam)
{
return makeTypeNameFromNameList(list_make1(makeString(typnam)));
}
/*
* makeTypeNameFromNameList -
* build a TypeName node for a String list representing a qualified name.
*
* typmod is defaulted, but can be changed later by caller.
*/
TypeName *
makeTypeNameFromNameList(List *names)
{
TypeName *n = makeNode(TypeName);
n->names = names;
n->typmods = NIL;
n->typemod = -1;
n->location = -1;
return n;
}
/*
* makeTypeNameFromOid -
* build a TypeName node to represent a type already known by OID/typmod.
*/
TypeName *
makeTypeNameFromOid(Oid typeOid, int32 typmod)
{
TypeName *n = makeNode(TypeName);
n->typeOid = typeOid;
n->typemod = typmod;
n->location = -1;
return n;
}
/*
* makeColumnDef -
* build a ColumnDef node to represent a simple column definition.
*
* Type and collation are specified by OID.
* Other properties are all basic to start with.
*/
ColumnDef *
makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
{
ColumnDef *n = makeNode(ColumnDef);
n->colname = pstrdup(colname);
n->typeName = makeTypeNameFromOid(typeOid, typmod);
n->inhcount = 0;
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
n->collClause = NULL;
n->collOid = collOid;
n->constraints = NIL;
n->fdwoptions = NIL;
n->location = -1;
return n;
}
/*
* makeFuncExpr -
* build an expression tree representing a function call.
*
* The argument expressions must have been transformed already.
*/
FuncExpr *
makeFuncExpr(Oid funcid, Oid rettype, List *args,
Oid funccollid, Oid inputcollid, CoercionForm fformat)
{
FuncExpr *funcexpr;
funcexpr = makeNode(FuncExpr);
funcexpr->funcid = funcid;
funcexpr->funcresulttype = rettype;
funcexpr->funcretset = false; /* only allowed case here */
funcexpr->funcvariadic = false; /* only allowed case here */
funcexpr->funcformat = fformat;
funcexpr->funccollid = funccollid;
funcexpr->inputcollid = inputcollid;
funcexpr->args = args;
funcexpr->location = -1;
return funcexpr;
}
/*
* makeStringConst -
* build a A_Const node of type T_String for given string
*/
Node *
makeStringConst(char *str, int location)
{
A_Const *n = makeNode(A_Const);
n->val.sval.type = T_String;
n->val.sval.sval = str;
n->location = location;
return (Node *) n;
}
/*
* makeDefElem -
* build a DefElem node
*
* This is sufficient for the "typical" case with an unqualified option name
* and no special action.
*/
DefElem *
makeDefElem(char *name, Node *arg, int location)
{
DefElem *res = makeNode(DefElem);
res->defnamespace = NULL;
res->defname = name;
res->arg = arg;
res->defaction = DEFELEM_UNSPEC;
res->location = location;
return res;
}
/*
* makeDefElemExtended -
* build a DefElem node with all fields available to be specified
*/
DefElem *
makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction, int location)
{
DefElem *res = makeNode(DefElem);
res->defnamespace = nameSpace;
res->defname = name;
res->arg = arg;
res->defaction = defaction;
res->location = location;
return res;
}
/*
* makeFuncCall -
*
* Initialize a FuncCall struct with the information every caller must
* supply. Any non-default parameters have to be inserted by the caller.
*/
FuncCall *
makeFuncCall(List *name, List *args, CoercionForm funcformat, int location)
{
FuncCall *n = makeNode(FuncCall);
n->funcname = name;
n->args = args;
n->agg_order = NIL;
n->agg_filter = NULL;
n->over = NULL;
n->agg_within_group = false;
n->agg_star = false;
n->agg_distinct = false;
n->func_variadic = false;
n->funcformat = funcformat;
n->location = location;
return n;
}
/*
* make_opclause
* Creates an operator clause given its operator info, left operand
* and right operand (pass NULL to create single-operand clause),
* and collation info.
*/
Expr *
make_opclause(Oid opno, Oid opresulttype, bool opretset,
Expr *leftop, Expr *rightop,
Oid opcollid, Oid inputcollid)
{
OpExpr *expr = makeNode(OpExpr);
expr->opno = opno;
expr->opfuncid = InvalidOid;
expr->opresulttype = opresulttype;
expr->opretset = opretset;
expr->opcollid = opcollid;
expr->inputcollid = inputcollid;
if (rightop)
expr->args = list_make2(leftop, rightop);
else
expr->args = list_make1(leftop);
expr->location = -1;
return (Expr *) expr;
}
/*
* make_andclause
*
* Creates an 'and' clause given a list of its subclauses.
*/
Expr *
make_andclause(List *andclauses)
{
BoolExpr *expr = makeNode(BoolExpr);
expr->boolop = AND_EXPR;
expr->args = andclauses;
expr->location = -1;
return (Expr *) expr;
}
/*
* make_orclause
*
* Creates an 'or' clause given a list of its subclauses.
*/
Expr *
make_orclause(List *orclauses)
{
BoolExpr *expr = makeNode(BoolExpr);
expr->boolop = OR_EXPR;
expr->args = orclauses;
expr->location = -1;
return (Expr *) expr;
}
/*
* make_notclause
*
* Create a 'not' clause given the expression to be negated.
*/
Expr *
make_notclause(Expr *notclause)
{
BoolExpr *expr = makeNode(BoolExpr);
expr->boolop = NOT_EXPR;
expr->args = list_make1(notclause);
expr->location = -1;
return (Expr *) expr;
}
/*
* make_and_qual
*
* Variant of make_andclause for ANDing two qual conditions together.
* Qual conditions have the property that a NULL nodetree is interpreted
* as 'true'.
*
* NB: this makes no attempt to preserve AND/OR flatness; so it should not
* be used on a qual that has already been run through prepqual.c.
*/
Node *
make_and_qual(Node *qual1, Node *qual2)
{
if (qual1 == NULL)
return qual2;
if (qual2 == NULL)
return qual1;
return (Node *) make_andclause(list_make2(qual1, qual2));
}
/*
* The planner and executor usually represent qualification expressions
* as lists of boolean expressions with implicit AND semantics.
*
* These functions convert between an AND-semantics expression list and the
* ordinary representation of a boolean expression.
*
* Note that an empty list is considered equivalent to TRUE.
*/
Expr *
make_ands_explicit(List *andclauses)
{
if (andclauses == NIL)
return (Expr *) makeBoolConst(true, false);
else if (list_length(andclauses) == 1)
return (Expr *) linitial(andclauses);
else
return make_andclause(andclauses);
}
List *
make_ands_implicit(Expr *clause)
{
/*
* NB: because the parser sets the qual field to NULL in a query that has
* no WHERE clause, we must consider a NULL input clause as TRUE, even
* though one might more reasonably think it FALSE.
*/
if (clause == NULL)
return NIL; /* NULL -> NIL list == TRUE */
else if (is_andclause(clause))
return ((BoolExpr *) clause)->args;
else if (IsA(clause, Const) &&
!((Const *) clause)->constisnull &&
DatumGetBool(((Const *) clause)->constvalue))
return NIL; /* constant TRUE input -> NIL list */
else
return list_make1(clause);
}
/*
* makeIndexInfo
* create an IndexInfo node. Upon returning from this function,
* callers must apply ExpandVirtualGeneratedColumns()
* or RelationGetIndexExpressionsExpand or RelationGetIndexPredicateExpand() to
* ii_ExpressionsExpand and ii_PredicateExpand as needed for actual
* expansion when they are not NIL or dummy.
*/
IndexInfo *
makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
List *predicates, bool unique, bool nulls_not_distinct,
bool isready, bool concurrent, bool summarizing,
bool withoutoverlaps)
{
IndexInfo *n = makeNode(IndexInfo);
n->ii_NumIndexAttrs = numattrs;
n->ii_NumIndexKeyAttrs = numkeyattrs;
Assert(n->ii_NumIndexKeyAttrs != 0);
Assert(n->ii_NumIndexKeyAttrs <= n->ii_NumIndexAttrs);
n->ii_Unique = unique;
n->ii_NullsNotDistinct = nulls_not_distinct;
n->ii_ReadyForInserts = isready;
n->ii_CheckedUnchanged = false;
n->ii_IndexUnchanged = false;
n->ii_Concurrent = concurrent;
n->ii_Summarizing = summarizing;
n->ii_WithoutOverlaps = withoutoverlaps;
/* summarizing indexes cannot contain non-key attributes */
Assert(!summarizing || (numkeyattrs == numattrs));
/* expressions */
n->ii_Expressions = expressions;
n->ii_ExpressionsExpand = copyObject(expressions);
n->ii_ExpressionsState = NIL;
n->ii_ExpressionsExpandState = NIL;
/* predicates */
n->ii_Predicate = predicates;
n->ii_PredicateExpand = copyObject(predicates);
n->ii_PredicateState = NULL;
n->ii_PredicateExpandState = NULL;
/* exclusion constraints */
n->ii_ExclusionOps = NULL;
n->ii_ExclusionProcs = NULL;
n->ii_ExclusionStrats = NULL;
/* speculative inserts */
n->ii_UniqueOps = NULL;
n->ii_UniqueProcs = NULL;
n->ii_UniqueStrats = NULL;
/* initialize index-build state to default */
n->ii_BrokenHotChain = false;
n->ii_ParallelWorkers = 0;
/* set up for possible use by index AM */
n->ii_Am = amoid;
n->ii_AmCache = NULL;
n->ii_Context = CurrentMemoryContext;
return n;
}
/*
* makeGroupingSet
*
*/
GroupingSet *
makeGroupingSet(GroupingSetKind kind, List *content, int location)
{
GroupingSet *n = makeNode(GroupingSet);
n->kind = kind;
n->content = content;
n->location = location;
return n;
}
/*
* makeVacuumRelation -
* create a VacuumRelation node
*/
VacuumRelation *
makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
{
VacuumRelation *v = makeNode(VacuumRelation);
v->relation = relation;
v->oid = oid;
v->va_cols = va_cols;
return v;
}
/*
* makeJsonFormat -
* creates a JsonFormat node
*/
JsonFormat *
makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location)
{
JsonFormat *jf = makeNode(JsonFormat);
jf->format_type = type;
jf->encoding = encoding;
jf->location = location;
return jf;
}
/*
* makeJsonValueExpr -
* creates a JsonValueExpr node
*/
JsonValueExpr *
makeJsonValueExpr(Expr *raw_expr, Expr *formatted_expr,
JsonFormat *format)
{
JsonValueExpr *jve = makeNode(JsonValueExpr);
jve->raw_expr = raw_expr;
jve->formatted_expr = formatted_expr;
jve->format = format;
return jve;
}
/*
* makeJsonBehavior -
* creates a JsonBehavior node
*/
JsonBehavior *
makeJsonBehavior(JsonBehaviorType btype, Node *expr, int location)
{
JsonBehavior *behavior = makeNode(JsonBehavior);
behavior->btype = btype;
behavior->expr = expr;
behavior->location = location;
return behavior;
}
/*
* makeJsonKeyValue -
* creates a JsonKeyValue node
*/
Node *
makeJsonKeyValue(Node *key, Node *value)
{
JsonKeyValue *n = makeNode(JsonKeyValue);
n->key = (Expr *) key;
n->value = castNode(JsonValueExpr, value);
return (Node *) n;
}
/*
* makeJsonIsPredicate -
* creates a JsonIsPredicate node
*/
Node *
makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType item_type,
bool unique_keys, Oid exprBaseType, int location)
{
JsonIsPredicate *n = makeNode(JsonIsPredicate);
Assert(expr != NULL);
n->expr = expr;
n->format = format;
n->item_type = item_type;
n->unique_keys = unique_keys;
n->exprBaseType = exprBaseType;
n->location = location;
return (Node *) n;
}
/*
* makeJsonTablePathSpec -
* Make JsonTablePathSpec node from given path string and name (if any)
*/
JsonTablePathSpec *
makeJsonTablePathSpec(char *string, char *name, int string_location,
int name_location)
{
JsonTablePathSpec *pathspec = makeNode(JsonTablePathSpec);
Assert(string != NULL);
pathspec->string = makeStringConst(string, string_location);
if (name != NULL)
pathspec->name = pstrdup(name);
pathspec->name_location = name_location;
pathspec->location = string_location;
return pathspec;
}
/*
* makeJsonTablePath -
* Make JsonTablePath node for given path string and name
*/
JsonTablePath *
makeJsonTablePath(Const *pathvalue, char *pathname)
{
JsonTablePath *path = makeNode(JsonTablePath);
Assert(IsA(pathvalue, Const));
path->value = pathvalue;
path->name = pathname;
return path;
}
./pathnodes.h 0000664 0001750 0001750 00000451131 15221466421 012053 0 ustar xman xman /*-------------------------------------------------------------------------
*
* pathnodes.h
* Definitions for planner's internal data structures, especially Paths.
*
* We don't support copying RelOptInfo, IndexOptInfo, or Path nodes.
* There are some subsidiary structs that are useful to copy, though.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/nodes/pathnodes.h
*
*-------------------------------------------------------------------------
*/
#ifndef PATHNODES_H
#define PATHNODES_H
#include "access/sdir.h"
#include "lib/stringinfo.h"
#include "nodes/params.h"
#include "nodes/parsenodes.h"
#include "storage/block.h"
/*
* Path generation strategies.
*
* These constants are used to specify the set of strategies that the planner
* should use, either for the query as a whole or for a specific baserel or
* joinrel. The various planner-related enable_* GUCs are used to set the
* PlannerGlobal's default_pgs_mask, and that in turn is used to set each
* RelOptInfo's pgs_mask. In both cases, extensions can use hooks to modify the
* default value. Not every strategy listed here has a corresponding enable_*
* GUC; those that don't are always allowed unless disabled by an extension.
* Not all strategies are relevant for every RelOptInfo; e.g. PGS_SEQSCAN
* doesn't affect joinrels one way or the other.
*
* In most cases, disabling a path generation strategy merely means that any
* paths generated using that strategy are marked as disabled, but in some
* cases, path generation is skipped altogether. The latter strategy is only
* permissible when it can't result in planner failure -- for instance, we
* couldn't do this for sequential scans on a plain rel, because there might
* not be any other possible path. Nevertheless, the behaviors in each
* individual case are to some extent the result of historical accident,
* chosen to match the preexisting behaviors of the enable_* GUCs.
*
* In a few cases, we have more than one bit for the same strategy, controlling
* different aspects of the planner behavior. When PGS_CONSIDER_INDEXONLY is
* unset, we don't even consider index-only scans, and any such scans that
* would have been generated become index scans instead. On the other hand,
* unsetting PGS_INDEXSCAN or PGS_INDEXONLYSCAN causes generated paths of the
* corresponding types to be marked as disabled. Similarly, unsetting
* PGS_CONSIDER_PARTITIONWISE prevents any sort of thinking about partitionwise
* joins for the current rel, which incidentally will preclude higher-level
* joinrels from building partitionwise paths using paths taken from the
* current rel's children. On the other hand, unsetting PGS_APPEND or
* PGS_MERGE_APPEND will only arrange to disable paths of the corresponding
* types if they are generated at the level of the current rel.
*
* Finally, unsetting PGS_CONSIDER_NONPARTIAL disables all non-partial paths
* except those that use Gather or Gather Merge. In most other cases, a
* plugin can nudge the planner toward a particular strategy by disabling
* all of the others, but that doesn't work here: unsetting PGS_SEQSCAN,
* for instance, would disable both partial and non-partial sequential scans.
*/
#define PGS_SEQSCAN 0x00000001
#define PGS_INDEXSCAN 0x00000002
#define PGS_INDEXONLYSCAN 0x00000004
#define PGS_BITMAPSCAN 0x00000008
#define PGS_TIDSCAN 0x00000010
#define PGS_FOREIGNJOIN 0x00000020
#define PGS_MERGEJOIN_PLAIN 0x00000040
#define PGS_MERGEJOIN_MATERIALIZE 0x00000080
#define PGS_NESTLOOP_PLAIN 0x00000100
#define PGS_NESTLOOP_MATERIALIZE 0x00000200
#define PGS_NESTLOOP_MEMOIZE 0x00000400
#define PGS_HASHJOIN 0x00000800
#define PGS_APPEND 0x00001000
#define PGS_MERGE_APPEND 0x00002000
#define PGS_GATHER 0x00004000
#define PGS_GATHER_MERGE 0x00008000
#define PGS_CONSIDER_INDEXONLY 0x00010000
#define PGS_CONSIDER_PARTITIONWISE 0x00020000
#define PGS_CONSIDER_NONPARTIAL 0x00040000
/*
* Convenience macros for useful combination of the bits defined above.
*/
#define PGS_SCAN_ANY \
(PGS_SEQSCAN | PGS_INDEXSCAN | PGS_INDEXONLYSCAN | PGS_BITMAPSCAN | \
PGS_TIDSCAN)
#define PGS_MERGEJOIN_ANY \
(PGS_MERGEJOIN_PLAIN | PGS_MERGEJOIN_MATERIALIZE)
#define PGS_NESTLOOP_ANY \
(PGS_NESTLOOP_PLAIN | PGS_NESTLOOP_MATERIALIZE | PGS_NESTLOOP_MEMOIZE)
#define PGS_JOIN_ANY \
(PGS_FOREIGNJOIN | PGS_MERGEJOIN_ANY | PGS_NESTLOOP_ANY | PGS_HASHJOIN)
/*
* Relids
* Set of relation identifiers (indexes into the rangetable).
*/
typedef Bitmapset *Relids;
/*
* When looking for a "cheapest path", this enum specifies whether we want
* cheapest startup cost or cheapest total cost.
*/
typedef enum CostSelector
{
STARTUP_COST, TOTAL_COST
} CostSelector;
/*
* The cost estimate produced by cost_qual_eval() includes both a one-time
* (startup) cost, and a per-tuple cost.
*/
typedef struct QualCost
{
Cost startup; /* one-time cost */
Cost per_tuple; /* per-evaluation cost */
} QualCost;
/*
* Costing aggregate function execution requires these statistics about
* the aggregates to be executed by a given Agg node. Note that the costs
* include the execution costs of the aggregates' argument expressions as
* well as the aggregate functions themselves. Also, the fields must be
* defined so that initializing the struct to zeroes with memset is correct.
*/
typedef struct AggClauseCosts
{
QualCost transCost; /* total per-input-row execution costs */
QualCost finalCost; /* total per-aggregated-row costs */
Size transitionSpace; /* space for pass-by-ref transition data */
} AggClauseCosts;
/*
* This enum identifies the different types of "upper" (post-scan/join)
* relations that we might deal with during planning.
*/
typedef enum UpperRelationKind
{
UPPERREL_SETOP, /* result of UNION/INTERSECT/EXCEPT, if any */
UPPERREL_PARTIAL_GROUP_AGG, /* result of partial grouping/aggregation, if
* any */
UPPERREL_GROUP_AGG, /* result of grouping/aggregation, if any */
UPPERREL_WINDOW, /* result of window functions, if any */
UPPERREL_PARTIAL_DISTINCT, /* result of partial "SELECT DISTINCT", if any */
UPPERREL_DISTINCT, /* result of "SELECT DISTINCT", if any */
UPPERREL_ORDERED, /* result of ORDER BY, if any */
UPPERREL_FINAL, /* result of any remaining top-level actions */
/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
} UpperRelationKind;
/*----------
* PlannerGlobal
* Global information for planning/optimization
*
* PlannerGlobal holds state for an entire planner invocation; this state
* is shared across all levels of sub-Queries that exist in the command being
* planned.
*
* Not all fields are printed. (In some cases, there is no print support for
* the field type; in others, doing so would lead to infinite recursion.)
*----------
*/
typedef struct PlannerGlobal
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/* Param values provided to planner() */
ParamListInfo boundParams pg_node_attr(read_write_ignore);
/* Plans for SubPlan nodes */
List *subplans;
/* Paths from which the SubPlan Plans were made */
List *subpaths;
/* PlannerInfos for SubPlan nodes */
List *subroots pg_node_attr(read_write_ignore);
/* names already used for subplans (list of C strings) */
List *subplanNames pg_node_attr(read_write_ignore);
/* indices of subplans that require REWIND */
Bitmapset *rewindPlanIDs;
/* "flat" rangetable for executor */
List *finalrtable;
/*
* RT indexes of all relation RTEs in finalrtable (RTE_RELATION and
* RTE_SUBQUERY RTEs of views)
*/
Bitmapset *allRelids;
/*
* RT indexes of all leaf partitions in nodes that support pruning and are
* subject to runtime pruning at plan initialization time ("initial"
* pruning).
*/
Bitmapset *prunableRelids;
/* "flat" list of RTEPermissionInfos */
List *finalrteperminfos;
/* list of SubPlanRTInfo nodes */
List *subrtinfos;
/* "flat" list of PlanRowMarks */
List *finalrowmarks;
/* "flat" list of integer RT indexes */
List *resultRelations;
/* "flat" list of AppendRelInfos */
List *appendRelations;
/* "flat" list of PartitionPruneInfos */
List *partPruneInfos;
/* OIDs of relations the plan depends on */
List *relationOids;
/* other dependencies, as PlanInvalItems */
List *invalItems;
/* type OIDs for PARAM_EXEC Params */
List *paramExecTypes;
/* info about nodes elided from the plan during setrefs processing */
List *elidedNodes;
/* highest PlaceHolderVar ID assigned */
Index lastPHId;
/* highest PlanRowMark ID assigned */
Index lastRowMarkId;
/* highest plan node ID assigned */
int lastPlanNodeId;
/* redo plan when TransactionXmin changes? */
bool transientPlan;
/* is plan specific to current role? */
bool dependsOnRole;
/* parallel mode potentially OK? */
bool parallelModeOK;
/* parallel mode actually required? */
bool parallelModeNeeded;
/* worst PROPARALLEL hazard level */
char maxParallelHazard;
/* mask of allowed path generation strategies */
uint64 default_pgs_mask;
/* partition descriptors */
PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
/* hash table for NOT NULL attnums of relations */
struct HTAB *rel_notnullatts_hash pg_node_attr(read_write_ignore);
/* extension state */
void **extension_state pg_node_attr(read_write_ignore);
int extension_state_allocated;
} PlannerGlobal;
/* macro for fetching the Plan associated with a SubPlan node */
#define planner_subplan_get_plan(root, subplan) \
((Plan *) list_nth((root)->glob->subplans, (subplan)->plan_id - 1))
/*----------
* PlannerInfo
* Per-query information for planning/optimization
*
* This struct is conventionally called "root" in all the planner routines.
* It holds links to all of the planner's working state, in addition to the
* original Query. Note that at present the planner extensively modifies
* the passed-in Query data structure; someday that should stop.
*
* Not all fields are printed. (In some cases, there is no print support for
* the field type; in others, doing so would lead to infinite recursion or
* bloat dump output more than seems useful.)
*
* NOTE: When adding new entries containing relids and relid bitmapsets,
* remember to check that they will be correctly processed by
* the remove_self_join_rel function - relid of removing relation will be
* correctly replaced with the keeping one.
*----------
*/
typedef struct PlannerInfo PlannerInfo;
struct PlannerInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/* the Query being planned */
Query *parse;
/* global info for current planner run */
PlannerGlobal *glob;
/* 1 at the outermost Query */
Index query_level;
/* NULL at outermost Query */
PlannerInfo *parent_root pg_node_attr(read_write_ignore);
/* Subplan name for EXPLAIN and debugging purposes (NULL at top level) */
char *plan_name;
/*
* If this PlannerInfo exists to consider an alternative implementation
* strategy for a portion of the query that could also be implemented by
* some other PlannerInfo, this is the plan_name for that other
* PlannerInfo. When we are considering the first or only alternative, it
* is the same as plan_name.
*
* Currently, we set this to a value other than plan_name only when
* considering a MinMaxAggPath or a hashed SubPlan.
*/
char *alternative_plan_name;
/*
* plan_params contains the expressions that this query level needs to
* make available to a lower query level that is currently being planned.
* outer_params contains the paramIds of PARAM_EXEC Params that outer
* query levels will make available to this query level.
*/
/* list of PlannerParamItems, see below */
List *plan_params;
Bitmapset *outer_params;
/*
* simple_rel_array holds pointers to "base rels" and "other rels" (see
* comments for RelOptInfo for more info). It is indexed by rangetable
* index (so entry 0 is always wasted). Entries can be NULL when an RTE
* does not correspond to a base relation, such as a join RTE or an
* unreferenced view RTE; or if the RelOptInfo hasn't been made yet.
*/
struct RelOptInfo **simple_rel_array pg_node_attr(array_size(simple_rel_array_size));
/* allocated size of array */
int simple_rel_array_size;
/*
* simple_rte_array is the same length as simple_rel_array and holds
* pointers to the associated rangetable entries. Using this is a shade
* faster than using rt_fetch(), mostly due to fewer indirections. (Not
* printed because it'd be redundant with parse->rtable.)
*/
RangeTblEntry **simple_rte_array pg_node_attr(read_write_ignore);
/*
* append_rel_array is the same length as the above arrays, and holds
* pointers to the corresponding AppendRelInfo entry indexed by
* child_relid, or NULL if the rel is not an appendrel child. The array
* itself is not allocated if append_rel_list is empty. (Not printed
* because it'd be redundant with append_rel_list.)
*/
struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
/*
* all_baserels is a Relids set of all base relids (but not joins or
* "other" rels) in the query. This is computed in deconstruct_jointree.
*/
Relids all_baserels;
/*
* outer_join_rels is a Relids set of all outer-join relids in the query.
* This is computed in deconstruct_jointree.
*/
Relids outer_join_rels;
/*
* all_query_rels is a Relids set of all base relids and outer join relids
* (but not "other" relids) in the query. This is the Relids identifier
* of the final join we need to form. This is computed in
* deconstruct_jointree.
*/
Relids all_query_rels;
/*
* join_rel_list is a list of all join-relation RelOptInfos we have
* considered in this planning run. For small problems we just scan the
* list to do lookups, but when there are many join relations we build a
* hash table for faster lookups. The hash table is present and valid
* when join_rel_hash is not NULL. Note that we still maintain the list
* even when using the hash table for lookups; this simplifies life for
* GEQO.
*/
List *join_rel_list;
struct HTAB *join_rel_hash pg_node_attr(read_write_ignore);
/*
* When doing a dynamic-programming-style join search, join_rel_level[k]
* is a list of all join-relation RelOptInfos of level k, and
* join_cur_level is the current level. New join-relation RelOptInfos are
* automatically added to the join_rel_level[join_cur_level] list.
* join_rel_level is NULL if not in use.
*
* Note: we've already printed all baserel and joinrel RelOptInfos above,
* so we don't dump join_rel_level or other lists of RelOptInfos.
*/
/* lists of join-relation RelOptInfos */
List **join_rel_level pg_node_attr(read_write_ignore);
/* index of list being extended */
int join_cur_level;
/* init SubPlans for query */
List *init_plans;
/*
* per-CTE-item list of subplan IDs (or -1 if no subplan was made for that
* CTE)
*/
List *cte_plan_ids;
/* List of Lists of Params for MULTIEXPR subquery outputs */
List *multiexpr_params;
/* list of JoinDomains used in the query (higher ones first) */
List *join_domains;
/* list of active EquivalenceClasses */
List *eq_classes;
/* set true once ECs are canonical */
bool ec_merging_done;
/* list of "canonical" PathKeys */
List *canon_pathkeys;
/*
* list of OuterJoinClauseInfos for mergejoinable outer join clauses
* w/nonnullable var on left
*/
List *left_join_clauses;
/*
* list of OuterJoinClauseInfos for mergejoinable outer join clauses
* w/nonnullable var on right
*/
List *right_join_clauses;
/*
* list of OuterJoinClauseInfos for mergejoinable full join clauses
*/
List *full_join_clauses;
/* list of SpecialJoinInfos */
List *join_info_list;
/* counter for assigning RestrictInfo serial numbers */
int last_rinfo_serial;
/*
* all_result_relids is empty for SELECT, otherwise it contains at least
* parse->resultRelation. For UPDATE/DELETE/MERGE across an inheritance
* or partitioning tree, the result rel's child relids are added. When
* using multi-level partitioning, intermediate partitioned rels are
* included. leaf_result_relids is similar except that only actual result
* tables, not partitioned tables, are included in it.
*/
/* set of all result relids */
Relids all_result_relids;
/* set of all leaf relids */
Relids leaf_result_relids;
/*
* list of AppendRelInfos
*
* Note: for AppendRelInfos describing partitions of a partitioned table,
* we guarantee that partitions that come earlier in the partitioned
* table's PartitionDesc will appear earlier in append_rel_list.
*/
List *append_rel_list;
/* list of RowIdentityVarInfos */
List *row_identity_vars;
/* list of PlanRowMarks */
List *rowMarks;
/* list of PlaceHolderInfos */
List *placeholder_list;
/* list of AggClauseInfos */
List *agg_clause_list;
/* list of GroupingExprInfos */
List *group_expr_list;
/* list of plain Vars contained in targetlist and havingQual */
List *tlist_vars;
/* array of PlaceHolderInfos indexed by phid */
struct PlaceHolderInfo **placeholder_array pg_node_attr(read_write_ignore, array_size(placeholder_array_size));
/* allocated size of array */
int placeholder_array_size pg_node_attr(read_write_ignore);
/* list of ForeignKeyOptInfos */
List *fkey_list;
/* desired pathkeys for query_planner() */
List *query_pathkeys;
/* groupClause pathkeys, if any */
List *group_pathkeys;
/*
* The number of elements in the group_pathkeys list which belong to the
* GROUP BY clause. Additional ones belong to ORDER BY / DISTINCT
* aggregates.
*/
int num_groupby_pathkeys;
/* pathkeys of bottom window, if any */
List *window_pathkeys;
/* distinctClause pathkeys, if any */
List *distinct_pathkeys;
/* sortClause pathkeys, if any */
List *sort_pathkeys;
/* set operator pathkeys, if any */
List *setop_pathkeys;
/* Canonicalised partition schemes used in the query. */
List *part_schemes pg_node_attr(read_write_ignore);
/* RelOptInfos we are now trying to join */
List *initial_rels pg_node_attr(read_write_ignore);
/*
* Upper-rel RelOptInfos. Use fetch_upper_rel() to get any particular
* upper rel.
*/
List *upper_rels[UPPERREL_FINAL + 1] pg_node_attr(read_write_ignore);
/* Result tlists chosen by grouping_planner for upper-stage processing */
struct PathTarget *upper_targets[UPPERREL_FINAL + 1] pg_node_attr(read_write_ignore);
/*
* The fully-processed groupClause is kept here. It differs from
* parse->groupClause in that we remove any items that we can prove
* redundant, so that only the columns named here actually need to be
* compared to determine grouping. Note that it's possible for *all* the
* items to be proven redundant, implying that there is only one group
* containing all the query's rows. Hence, if you want to check whether
* GROUP BY was specified, test for nonempty parse->groupClause, not for
* nonempty processed_groupClause. Optimizer chooses specific order of
* group-by clauses during the upper paths generation process, attempting
* to use different strategies to minimize number of sorts or engage
* incremental sort. See preprocess_groupclause() and
* get_useful_group_keys_orderings() for details.
*
* Currently, when grouping sets are specified we do not attempt to
* optimize the groupClause, so that processed_groupClause will be
* identical to parse->groupClause.
*/
List *processed_groupClause;
/*
* The fully-processed distinctClause is kept here. It differs from
* parse->distinctClause in that we remove any items that we can prove
* redundant, so that only the columns named here actually need to be
* compared to determine uniqueness. Note that it's possible for *all*
* the items to be proven redundant, implying that there should be only
* one output row. Hence, if you want to check whether DISTINCT was
* specified, test for nonempty parse->distinctClause, not for nonempty
* processed_distinctClause.
*/
List *processed_distinctClause;
/*
* The fully-processed targetlist is kept here. It differs from
* parse->targetList in that (for INSERT) it's been reordered to match the
* target table, and defaults have been filled in. Also, additional
* resjunk targets may be present. preprocess_targetlist() does most of
* that work, but note that more resjunk targets can get added during
* appendrel expansion. (Hence, upper_targets mustn't get set up till
* after that.)
*/
List *processed_tlist;
/*
* For UPDATE, this list contains the target table's attribute numbers to
* which the first N entries of processed_tlist are to be assigned. (Any
* additional entries in processed_tlist must be resjunk.) DO NOT use the
* resnos in processed_tlist to identify the UPDATE target columns.
*/
List *update_colnos;
/*
* Fields filled during create_plan() for use in setrefs.c
*/
/* for GroupingFunc fixup (can't print: array length not known here) */
AttrNumber *grouping_map pg_node_attr(read_write_ignore);
/* List of MinMaxAggInfos */
List *minmax_aggs;
/* context holding PlannerInfo */
MemoryContext planner_cxt pg_node_attr(read_write_ignore);
/* # of pages in all non-dummy tables of query */
Cardinality total_table_pages;
/* tuple_fraction passed to query_planner */
Selectivity tuple_fraction;
/* limit_tuples passed to query_planner */
Cardinality limit_tuples;
/*
* Minimum security_level for quals. Note: qual_security_level is zero if
* there are no securityQuals.
*/
Index qual_security_level;
/* true if any RTEs are RTE_JOIN kind */
bool hasJoinRTEs;
/* true if any RTEs are marked LATERAL */
bool hasLateralRTEs;
/* true if havingQual was non-null */
bool hasHavingQual;
/* true if any RestrictInfo has pseudoconstant = true */
bool hasPseudoConstantQuals;
/* true if we've made any of those */
bool hasAlternativeSubPlans;
/* true once we're no longer allowed to add PlaceHolderInfos */
bool placeholdersFrozen;
/* true if planning a recursive WITH item */
bool hasRecursion;
/* true if a planner extension may replan this subquery */
bool assumeReplanning;
/*
* The rangetable index for the RTE_GROUP RTE, or 0 if there is no
* RTE_GROUP RTE.
*/
int group_rtindex;
/*
* Information about aggregates. Filled by preprocess_aggrefs().
*/
/* AggInfo structs */
List *agginfos;
/* AggTransInfo structs */
List *aggtransinfos;
/* number of aggs with DISTINCT/ORDER BY/WITHIN GROUP */
int numOrderedAggs;
/* does any agg not support partial mode? */
bool hasNonPartialAggs;
/* is any partial agg non-serializable? */
bool hasNonSerialAggs;
/*
* These fields are used only when hasRecursion is true:
*/
/* PARAM_EXEC ID for the work table */
int wt_param_id;
/* a path for non-recursive term */
struct Path *non_recursive_path;
/*
* These fields are workspace for createplan.c
*/
/* outer rels above current node */
Relids curOuterRels;
/* not-yet-assigned NestLoopParams */
List *curOuterParams;
/*
* These fields are workspace for setrefs.c. Each is an array
* corresponding to glob->subplans. (We could probably teach
* gen_node_support.pl how to determine the array length, but it doesn't
* seem worth the trouble, so just mark them read_write_ignore.)
*/
bool *isAltSubplan pg_node_attr(read_write_ignore);
bool *isUsedSubplan pg_node_attr(read_write_ignore);
/* PartitionPruneInfos added in this query's plan. */
List *partPruneInfos;
/* extension state */
void **extension_state pg_node_attr(read_write_ignore);
int extension_state_allocated;
};
/*
* In places where it's known that simple_rte_array[] must have been prepared
* already, we just index into it to fetch RTEs. In code that might be
* executed before or after entering query_planner(), use this macro.
*/
#define planner_rt_fetch(rti, root) \
((root)->simple_rte_array ? (root)->simple_rte_array[rti] : \
rt_fetch(rti, (root)->parse->rtable))
/*
* If multiple relations are partitioned the same way, all such partitions
* will have a pointer to the same PartitionScheme. A list of PartitionScheme
* objects is attached to the PlannerInfo. By design, the partition scheme
* incorporates only the general properties of the partition method (LIST vs.
* RANGE, number of partitioning columns and the type information for each)
* and not the specific bounds.
*
* We store the opclass-declared input data types instead of the partition key
* datatypes since the former rather than the latter are used to compare
* partition bounds. Since partition key data types and the opclass declared
* input data types are expected to be binary compatible (per ResolveOpClass),
* both of those should have same byval and length properties.
*/
typedef struct PartitionSchemeData
{
char strategy; /* partition strategy */
int16 partnatts; /* number of partition attributes */
Oid *partopfamily; /* OIDs of operator families */
Oid *partopcintype; /* OIDs of opclass declared input data types */
Oid *partcollation; /* OIDs of partitioning collations */
/* Cached information about partition key data types. */
int16 *parttyplen;
bool *parttypbyval;
/* Cached information about partition comparison functions. */
struct FmgrInfo *partsupfunc;
} PartitionSchemeData;
typedef struct PartitionSchemeData *PartitionScheme;
/*----------
* RelOptInfo
* Per-relation information for planning/optimization
*
* For planning purposes, a "base rel" is either a plain relation (a table)
* or the output of a sub-SELECT or function that appears in the range table.
* In either case it is uniquely identified by an RT index. A "joinrel"
* is the joining of two or more base rels. A joinrel is identified by
* the set of RT indexes for its component baserels, along with RT indexes
* for any outer joins it has computed. We create RelOptInfo nodes for each
* baserel and joinrel, and store them in the PlannerInfo's simple_rel_array
* and join_rel_list respectively.
*
* Note that there is only one joinrel for any given set of component
* baserels, no matter what order we assemble them in; so an unordered
* set is the right datatype to identify it with.
*
* We also have "other rels", which are like base rels in that they refer to
* single RT indexes; but they are not part of the join tree, and are given
* a different RelOptKind to identify them.
* Currently the only kind of otherrels are those made for member relations
* of an "append relation", that is an inheritance set or UNION ALL subquery.
* An append relation has a parent RTE that is a base rel, which represents
* the entire append relation. The member RTEs are otherrels. The parent
* is present in the query join tree but the members are not. The member
* RTEs and otherrels are used to plan the scans of the individual tables or
* subqueries of the append set; then the parent baserel is given Append
* and/or MergeAppend paths comprising the best paths for the individual
* member rels. (See comments for AppendRelInfo for more information.)
*
* At one time we also made otherrels to represent join RTEs, for use in
* handling join alias Vars. Currently this is not needed because all join
* alias Vars are expanded to non-aliased form during preprocess_expression.
*
* We also have relations representing joins between child relations of
* different partitioned tables. These relations are not added to
* join_rel_level lists as they are not joined directly by the dynamic
* programming algorithm.
*
* There is also a RelOptKind for "upper" relations, which are RelOptInfos
* that describe post-scan/join processing steps, such as aggregation.
* Many of the fields in these RelOptInfos are meaningless, but their Path
* fields always hold Paths showing ways to do that processing step.
*
* Parts of this data structure are specific to various scan and join
* mechanisms. It didn't seem worth creating new node types for them.
*
* relids - Set of relation identifiers (RT indexes). This is a base
* relation if there is just one, a join relation if more;
* in the join case, RT indexes of any outer joins formed
* at or below this join are included along with baserels
* rows - estimated number of tuples in the relation after restriction
* clauses have been applied (ie, output rows of a plan for it)
* consider_startup - true if there is any value in keeping plain paths for
* this rel on the basis of having cheap startup cost
* consider_param_startup - the same for parameterized paths
* reltarget - Default Path output tlist for this rel; normally contains
* Var and PlaceHolderVar nodes for the values we need to
* output from this relation.
* List is in no particular order, but all rels of an
* appendrel set must use corresponding orders.
* NOTE: in an appendrel child relation, may contain
* arbitrary expressions pulled up from a subquery!
* pathlist - List of Path nodes, one for each potentially useful
* method of generating the relation
* ppilist - ParamPathInfo nodes for parameterized Paths, if any
* cheapest_startup_path - the pathlist member with lowest startup cost
* (regardless of ordering) among the unparameterized paths;
* or NULL if there is no unparameterized path
* cheapest_total_path - the pathlist member with lowest total cost
* (regardless of ordering) among the unparameterized paths;
* or if there is no unparameterized path, the path with lowest
* total cost among the paths with minimum parameterization
* cheapest_parameterized_paths - best paths for their parameterizations;
* always includes cheapest_total_path, even if that's unparameterized
* direct_lateral_relids - rels this rel has direct LATERAL references to
* lateral_relids - required outer rels for LATERAL, as a Relids set
* (includes both direct and indirect lateral references)
*
* If the relation is a base relation it will have these fields set:
*
* relid - RTE index (this is redundant with the relids field, but
* is provided for convenience of access)
* rtekind - copy of RTE's rtekind field
* min_attr, max_attr - range of valid AttrNumbers for rel
* attr_needed - array of bitmapsets indicating the highest joinrel
* in which each attribute is needed; if bit 0 is set then
* the attribute is needed as part of final targetlist
* attr_widths - cache space for per-attribute width estimates;
* zero means not computed yet
* notnullattnums - zero-based set containing attnums of NOT NULL
* columns (not populated for rels corresponding to
* non-partitioned inh==true RTEs)
* nulling_relids - relids of outer joins that can null this rel
* lateral_vars - lateral cross-references of rel, if any (list of
* Vars and PlaceHolderVars)
* lateral_referencers - relids of rels that reference this one laterally
* (includes both direct and indirect lateral references)
* indexlist - list of IndexOptInfo nodes for relation's indexes
* (always NIL if it's not a table or partitioned table)
* pages - number of disk pages in relation (zero if not a table)
* tuples - number of tuples in relation (not considering restrictions)
* allvisfrac - fraction of disk pages that are marked all-visible
* eclass_indexes - EquivalenceClasses that mention this rel (filled
* only after EC merging is complete)
* subroot - PlannerInfo for subquery (NULL if it's not a subquery)
* subplan_params - list of PlannerParamItems to be passed to subquery
*
* Note: for a subquery, tuples and subroot are not set immediately
* upon creation of the RelOptInfo object; they are filled in when
* set_subquery_pathlist processes the object.
*
* For otherrels that are appendrel members, these fields are filled
* in just as for a baserel, except we don't bother with lateral_vars.
*
* If the relation is either a foreign table or a join of foreign tables that
* all belong to the same foreign server and are assigned to the same user to
* check access permissions as (cf checkAsUser), these fields will be set:
*
* serverid - OID of foreign server, if foreign table (else InvalidOid)
* userid - OID of user to check access as (InvalidOid means current user)
* useridiscurrent - we've assumed that userid equals current user
* fdwroutine - function hooks for FDW, if foreign table (else NULL)
* fdw_private - private state for FDW, if foreign table (else NULL)
*
* Two fields are used to cache knowledge acquired during the join search
* about whether this rel is provably unique when being joined to given other
* relation(s), ie, it can have at most one row matching any given row from
* that join relation. Currently we only attempt such proofs, and thus only
* populate these fields, for base rels; but someday they might be used for
* join rels too:
*
* unique_for_rels - list of UniqueRelInfo, each one being a set of other
* rels for which this one has been proven unique
* non_unique_for_rels - list of Relid sets, each one being a set of
* other rels for which we have tried and failed to prove
* this one unique
*
* Three fields are used to cache information about unique-ification of this
* relation. This is used to support semijoins where the relation appears on
* the RHS: the relation is first unique-ified, and then a regular join is
* performed:
*
* unique_rel - the unique-ified version of the relation, containing paths
* that produce unique (no duplicates) output from relation;
* NULL if not yet requested
* unique_pathkeys - pathkeys that represent the ordering requirements for
* the relation's output in sort-based unique-ification
* implementations
* unique_groupclause - a list of SortGroupClause nodes that represent the
* columns to be grouped on in hash-based unique-ification
* implementations
*
* The presence of the following fields depends on the restrictions
* and joins that the relation participates in:
*
* baserestrictinfo - List of RestrictInfo nodes, containing info about
* each non-join qualification clause in which this relation
* participates (only used for base rels)
* baserestrictcost - Estimated cost of evaluating the baserestrictinfo
* clauses at a single tuple (only used for base rels)
* baserestrict_min_security - Smallest security_level found among
* clauses in baserestrictinfo
* joininfo - List of RestrictInfo nodes, containing info about each
* join clause in which this relation participates (but
* note this excludes clauses that might be derivable from
* EquivalenceClasses)
* has_eclass_joins - flag that EquivalenceClass joins are possible
*
* Note: Keeping a restrictinfo list in the RelOptInfo is useful only for
* base rels, because for a join rel the set of clauses that are treated as
* restrict clauses varies depending on which sub-relations we choose to join.
* (For example, in a 3-base-rel join, a clause relating rels 1 and 2 must be
* treated as a restrictclause if we join {1} and {2 3} to make {1 2 3}; but
* if we join {1 2} and {3} then that clause will be a restrictclause in {1 2}
* and should not be processed again at the level of {1 2 3}.) Therefore,
* the restrictinfo list in the join case appears in individual JoinPaths
* (field joinrestrictinfo), not in the parent relation. But it's OK for
* the RelOptInfo to store the joininfo list, because that is the same
* for a given rel no matter how we form it.
*
* We store baserestrictcost in the RelOptInfo (for base relations) because
* we know we will need it at least once (to price the sequential scan)
* and may need it multiple times to price index scans.
*
* A join relation is considered to be partitioned if it is formed from a
* join of two relations that are partitioned, have matching partitioning
* schemes, and are joined on an equijoin of the partitioning columns.
* Under those conditions we can consider the join relation to be partitioned
* by either relation's partitioning keys, though some care is needed if
* either relation can be forced to null by outer-joining. For example, an
* outer join like (A LEFT JOIN B ON A.a = B.b) may produce rows with B.b
* NULL. These rows may not fit the partitioning conditions imposed on B.
* Hence, strictly speaking, the join is not partitioned by B.b and thus
* partition keys of an outer join should include partition key expressions
* from the non-nullable side only. However, if a subsequent join uses
* strict comparison operators (and all commonly-used equijoin operators are
* strict), the presence of nulls doesn't cause a problem: such rows couldn't
* match anything on the other side and thus they don't create a need to do
* any cross-partition sub-joins. Hence we can treat such values as still
* partitioning the join output for the purpose of additional partitionwise
* joining, so long as a strict join operator is used by the next join.
*
* If the relation is partitioned, these fields will be set:
*
* part_scheme - Partitioning scheme of the relation
* nparts - Number of partitions
* boundinfo - Partition bounds
* partbounds_merged - true if partition bounds are merged ones
* partition_qual - Partition constraint if not the root
* part_rels - RelOptInfos for each partition
* all_partrels - Relids set of all partition relids
* partexprs, nullable_partexprs - Partition key expressions
*
* The partexprs and nullable_partexprs arrays each contain
* part_scheme->partnatts elements. Each of the elements is a list of
* partition key expressions. For partitioned base relations, there is one
* expression in each partexprs element, and nullable_partexprs is empty.
* For partitioned join relations, each base relation within the join
* contributes one partition key expression per partitioning column;
* that expression goes in the partexprs[i] list if the base relation
* is not nullable by this join or any lower outer join, or in the
* nullable_partexprs[i] list if the base relation is nullable.
* Furthermore, FULL JOINs add extra nullable_partexprs expressions
* corresponding to COALESCE expressions of the left and right join columns,
* to simplify matching join clauses to those lists.
*
* Not all fields are printed. (In some cases, there is no print support for
* the field type.)
*----------
*/
/* Bitmask of flags supported by table AMs */
#define AMFLAG_HAS_TID_RANGE (1 << 0)
typedef enum RelOptKind
{
RELOPT_BASEREL,
RELOPT_JOINREL,
RELOPT_OTHER_MEMBER_REL,
RELOPT_OTHER_JOINREL,
RELOPT_UPPER_REL,
RELOPT_OTHER_UPPER_REL,
} RelOptKind;
/*
* Is the given relation a simple relation i.e a base or "other" member
* relation?
*/
#define IS_SIMPLE_REL(rel) \
((rel)->reloptkind == RELOPT_BASEREL || \
(rel)->reloptkind == RELOPT_OTHER_MEMBER_REL)
/* Is the given relation a join relation? */
#define IS_JOIN_REL(rel) \
((rel)->reloptkind == RELOPT_JOINREL || \
(rel)->reloptkind == RELOPT_OTHER_JOINREL)
/* Is the given relation an upper relation? */
#define IS_UPPER_REL(rel) \
((rel)->reloptkind == RELOPT_UPPER_REL || \
(rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
/* Is the given relation an "other" relation? */
#define IS_OTHER_REL(rel) \
((rel)->reloptkind == RELOPT_OTHER_MEMBER_REL || \
(rel)->reloptkind == RELOPT_OTHER_JOINREL || \
(rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
typedef struct RelOptInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
RelOptKind reloptkind;
/*
* all relations included in this RelOptInfo; set of base + OJ relids
* (rangetable indexes)
*/
Relids relids;
/*
* size estimates generated by planner
*/
/* estimated number of result tuples */
Cardinality rows;
/*
* per-relation planner control
*/
/* keep cheap-startup-cost paths? */
bool consider_startup;
/* ditto, for parameterized paths? */
bool consider_param_startup;
/* consider parallel paths? */
bool consider_parallel;
/* path generation strategy mask */
uint64 pgs_mask;
/*
* default result targetlist for Paths scanning this relation; list of
* Vars/Exprs, cost, width
*/
struct PathTarget *reltarget;
/*
* materialization information
*/
List *pathlist; /* Path structures */
List *ppilist; /* ParamPathInfos used in pathlist */
List *partial_pathlist; /* partial Paths */
struct Path *cheapest_startup_path;
struct Path *cheapest_total_path;
List *cheapest_parameterized_paths;
/*
* parameterization information needed for both base rels and join rels
* (see also lateral_vars and lateral_referencers)
*/
/* rels directly laterally referenced */
Relids direct_lateral_relids;
/* minimum parameterization of rel */
Relids lateral_relids;
/*
* information about a base rel (not set for join rels!)
*/
Index relid;
/* containing tablespace */
Oid reltablespace;
/* RELATION, SUBQUERY, FUNCTION, etc */
RTEKind rtekind;
/* smallest attrno of rel (often <0) */
AttrNumber min_attr;
/* largest attrno of rel */
AttrNumber max_attr;
/* array indexed [min_attr .. max_attr] */
Relids *attr_needed pg_node_attr(read_write_ignore);
/* array indexed [min_attr .. max_attr] */
int32 *attr_widths pg_node_attr(read_write_ignore);
/* zero-based set containing attnums of NOT NULL columns */
Bitmapset *notnullattnums;
/* relids of outer joins that can null this baserel */
Relids nulling_relids;
/* LATERAL Vars and PHVs referenced by rel */
List *lateral_vars;
/* rels that reference this baserel laterally */
Relids lateral_referencers;
/* list of IndexOptInfo */
List *indexlist;
/* list of StatisticExtInfo */
List *statlist;
/* size estimates derived from pg_class */
BlockNumber pages;
Cardinality tuples;
double allvisfrac;
/* indexes in PlannerInfo's eq_classes list of ECs that mention this rel */
Bitmapset *eclass_indexes;
PlannerInfo *subroot; /* if subquery */
List *subplan_params; /* if subquery */
/* wanted number of parallel workers */
int rel_parallel_workers;
/* Bitmask of optional features supported by the table AM */
uint32 amflags;
/*
* Information about foreign tables and foreign joins
*/
/* identifies server for the table or join */
Oid serverid;
/* identifies user to check access as; 0 means to check as current user */
Oid userid;
/* join is only valid for current user */
bool useridiscurrent;
/* use "struct FdwRoutine" to avoid including fdwapi.h here */
struct FdwRoutine *fdwroutine pg_node_attr(read_write_ignore);
void *fdw_private pg_node_attr(read_write_ignore);
/*
* cache space for remembering if we have proven this relation unique
*/
/* known unique for these other relid set(s) given in UniqueRelInfo(s) */
List *unique_for_rels;
/* known not unique for these set(s) */
List *non_unique_for_rels;
/*
* information about unique-ification of this relation
*/
/* the unique-ified version of the relation */
struct RelOptInfo *unique_rel;
/* pathkeys for sort-based unique-ification implementations */
List *unique_pathkeys;
/* SortGroupClause nodes for hash-based unique-ification implementations */
List *unique_groupclause;
/*
* used by various scans and joins:
*/
/* RestrictInfo structures (if base rel) */
List *baserestrictinfo;
/* cost of evaluating the above */
QualCost baserestrictcost;
/* min security_level found in baserestrictinfo */
Index baserestrict_min_security;
/* RestrictInfo structures for join clauses involving this rel */
List *joininfo;
/* T means joininfo is incomplete */
bool has_eclass_joins;
/*
* used by partitionwise joins:
*/
/* consider partitionwise join paths? (if partitioned rel) */
bool consider_partitionwise_join;
/*
* used by eager aggregation:
*/
/* information needed to create grouped paths */
struct RelAggInfo *agg_info;
/* the partially-aggregated version of the relation */
struct RelOptInfo *grouped_rel;
/*
* inheritance links, if this is an otherrel (otherwise NULL):
*/
/* Immediate parent relation (dumping it would be too verbose) */
struct RelOptInfo *parent pg_node_attr(read_write_ignore);
/* Topmost parent relation (dumping it would be too verbose) */
struct RelOptInfo *top_parent pg_node_attr(read_write_ignore);
/* Relids of topmost parent (redundant, but handy) */
Relids top_parent_relids;
/*
* used for partitioned relations:
*/
/* Partitioning scheme */
PartitionScheme part_scheme pg_node_attr(read_write_ignore);
/*
* Number of partitions; -1 if not yet set; in case of a join relation 0
* means it's considered unpartitioned
*/
int nparts;
/* Partition bounds */
struct PartitionBoundInfoData *boundinfo pg_node_attr(read_write_ignore);
/* True if partition bounds were created by partition_bounds_merge() */
bool partbounds_merged;
/* Partition constraint, if not the root */
List *partition_qual;
/*
* Array of RelOptInfos of partitions, stored in the same order as bounds
* (don't print, too bulky and duplicative)
*/
struct RelOptInfo **part_rels pg_node_attr(read_write_ignore);
/*
* Bitmap with members acting as indexes into the part_rels[] array to
* indicate which partitions survived partition pruning.
*/
Bitmapset *live_parts;
/* Relids set of all partition relids */
Relids all_partrels;
/*
* These arrays are of length partkey->partnatts, which we don't have at
* hand, so don't try to print
*/
/* Non-nullable partition key expressions */
List **partexprs pg_node_attr(read_write_ignore);
/* Nullable partition key expressions */
List **nullable_partexprs pg_node_attr(read_write_ignore);
/* extension state */
void **extension_state pg_node_attr(read_write_ignore);
int extension_state_allocated;
} RelOptInfo;
/*
* Is given relation partitioned?
*
* It's not enough to test whether rel->part_scheme is set, because it might
* be that the basic partitioning properties of the input relations matched
* but the partition bounds did not. Also, if we are able to prove a rel
* dummy (empty), we should henceforth treat it as unpartitioned.
*/
#define IS_PARTITIONED_REL(rel) \
((rel)->part_scheme && (rel)->boundinfo && (rel)->nparts > 0 && \
(rel)->part_rels && !IS_DUMMY_REL(rel))
/*
* Convenience macro to make sure that a partitioned relation has all the
* required members set.
*/
#define REL_HAS_ALL_PART_PROPS(rel) \
((rel)->part_scheme && (rel)->boundinfo && (rel)->nparts > 0 && \
(rel)->part_rels && (rel)->partexprs && (rel)->nullable_partexprs)
/*
* Is given relation unique-ified?
*
* When the nominal jointype is JOIN_INNER, sjinfo->jointype is JOIN_SEMI, and
* the given rel is exactly the RHS of the semijoin, it indicates that the rel
* has been unique-ified.
*/
#define RELATION_WAS_MADE_UNIQUE(rel, sjinfo, nominal_jointype) \
((nominal_jointype) == JOIN_INNER && (sjinfo)->jointype == JOIN_SEMI && \
bms_equal((sjinfo)->syn_righthand, (rel)->relids))
/*
* Is the given relation a grouped relation?
*/
#define IS_GROUPED_REL(rel) \
((rel)->agg_info != NULL)
/*
* RelAggInfo
* Information needed to create paths for a grouped relation.
*
* "target" is the default result targetlist for Paths scanning this grouped
* relation; list of Vars/Exprs, cost, width.
*
* "agg_input" is the output tlist for the paths that provide input to the
* grouped paths. One difference from the reltarget of the non-grouped
* relation is that agg_input has its sortgrouprefs[] initialized.
*
* "group_clauses" and "group_exprs" are lists of SortGroupClauses and the
* corresponding grouping expressions.
*
* "apply_agg_at" tracks the set of relids at which partial aggregation is
* applied in the paths of this grouped relation.
*
* "grouped_rows" is the estimated number of result tuples of the grouped
* relation.
*
* "agg_useful" is a flag to indicate whether the grouped paths are considered
* useful. It is set true if the average partial group size is no less than
* min_eager_agg_group_size, suggesting a significant row count reduction.
*/
typedef struct RelAggInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/* the output tlist for the grouped paths */
struct PathTarget *target;
/* the output tlist for the input paths */
struct PathTarget *agg_input;
/* a list of SortGroupClauses */
List *group_clauses;
/* a list of grouping expressions */
List *group_exprs;
/* the set of relids partial aggregation is applied at */
Relids apply_agg_at;
/* estimated number of result tuples */
Cardinality grouped_rows;
/* the grouped paths are considered useful? */
bool agg_useful;
} RelAggInfo;
/*
* IndexOptInfo
* Per-index information for planning/optimization
*
* indexkeys[] and canreturn[] each have ncolumns entries.
*
* indexcollations[], opfamily[], and opcintype[] each have nkeycolumns
* entries. These don't contain any information about INCLUDE columns.
*
* sortopfamily[], reverse_sort[], and nulls_first[] have
* nkeycolumns entries, if the index is ordered; but if it is unordered,
* those pointers are NULL.
*
* Zeroes in the indexkeys[] array indicate index columns that are
* expressions; there is one element in indexprs for each such column.
*
* For an ordered index, reverse_sort[] and nulls_first[] describe the
* sort ordering of a forward indexscan; we can also consider a backward
* indexscan, which will generate the reverse ordering.
*
* The indexprs and indpred expressions have been run through
* prepqual.c and eval_const_expressions() for ease of matching to
* WHERE clauses. indpred is in implicit-AND form.
*
* indextlist is a TargetEntry list representing the index columns.
* It provides an equivalent base-relation Var for each simple column,
* and links to the matching indexprs element for each expression column.
*
* While most of these fields are filled when the IndexOptInfo is created
* (by plancat.c), indrestrictinfo and predOK are set later, in
* check_index_predicates().
*/
struct IndexPath; /* forward declaration */
typedef struct IndexOptInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/* OID of the index relation */
Oid indexoid;
/* tablespace of index (not table) */
Oid reltablespace;
/* back-link to index's table; don't print, else infinite recursion */
RelOptInfo *rel pg_node_attr(read_write_ignore);
/*
* index-size statistics (from pg_class and elsewhere)
*/
/* number of disk pages in index */
BlockNumber pages;
/* number of index tuples in index */
Cardinality tuples;
/* index tree height, or -1 if unknown */
int tree_height;
/*
* index descriptor information
*/
/* number of columns in index */
int ncolumns;
/* number of key columns in index */
int nkeycolumns;
/*
* table column numbers of index's columns (both key and included
* columns), or 0 for expression columns
*/
int *indexkeys pg_node_attr(array_size(ncolumns));
/* OIDs of collations of index columns */
Oid *indexcollations pg_node_attr(array_size(nkeycolumns));
/* OIDs of operator families for columns */
Oid *opfamily pg_node_attr(array_size(nkeycolumns));
/* OIDs of opclass declared input data types */
Oid *opcintype pg_node_attr(array_size(nkeycolumns));
/* OIDs of btree opfamilies, if orderable. NULL if partitioned index */
Oid *sortopfamily pg_node_attr(array_size(nkeycolumns));
/* is sort order descending? or NULL if partitioned index */
bool *reverse_sort pg_node_attr(array_size(nkeycolumns));
/* do NULLs come first in the sort order? or NULL if partitioned index */
bool *nulls_first pg_node_attr(array_size(nkeycolumns));
/* opclass-specific options for columns */
bytea **opclassoptions pg_node_attr(read_write_ignore);
/* which index cols can be returned in an index-only scan? */
bool *canreturn pg_node_attr(array_size(ncolumns));
/* OID of the access method (in pg_am) */
Oid relam;
/*
* expressions for non-simple index columns; redundant to print since we
* print indextlist
*/
List *indexprs pg_node_attr(read_write_ignore);
List *indexprsExpand pg_node_attr(read_write_ignore);
/* predicate if a partial index, else NIL */
List *indpred;
List *indpredExpand;
/* targetlist representing index columns */
List *indextlist;
/*
* parent relation's baserestrictinfo list, less any conditions implied by
* the index's predicate (unless it's a target rel, see comments in
* check_index_predicates())
*/
List *indrestrictinfo;
/* true if index predicate matches query */
bool predOK;
/* true if a unique index */
bool unique;
/* true if the index was defined with NULLS NOT DISTINCT */
bool nullsnotdistinct;
/* is uniqueness enforced immediately? */
bool immediate;
/* true if paths using this index should be marked disabled */
bool disabled;
/* true if index doesn't really exist */
bool hypothetical;
/*
* Remaining fields are copied from the index AM's API struct
* (IndexAmRoutine). These fields are not set for partitioned indexes.
*/
bool amcanorderbyop;
bool amoptionalkey;
bool amsearcharray;
bool amsearchnulls;
/* does AM have amgettuple interface? */
bool amhasgettuple;
/* does AM have amgetbitmap interface? */
bool amhasgetbitmap;
bool amcanparallel;
/* does AM have ammarkpos interface? */
bool amcanmarkpos;
/* AM's cost estimator */
/* Rather than include amapi.h here, we declare amcostestimate like this */
void (*amcostestimate) (struct PlannerInfo *, struct IndexPath *, double, Cost *, Cost *, Selectivity *, double *, double *) pg_node_attr(read_write_ignore);
} IndexOptInfo;
/*
* ForeignKeyOptInfo
* Per-foreign-key information for planning/optimization
*
* The per-FK-column arrays can be fixed-size because we allow at most
* INDEX_MAX_KEYS columns in a foreign key constraint. Each array has
* nkeys valid entries.
*/
typedef struct ForeignKeyOptInfo
{
pg_node_attr(custom_read_write, no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/*
* Basic data about the foreign key (fetched from catalogs):
*/
/* RT index of the referencing table */
Index con_relid;
/* RT index of the referenced table */
Index ref_relid;
/* number of columns in the foreign key */
int nkeys;
/* cols in referencing table */
AttrNumber conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
/* cols in referenced table */
AttrNumber confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
/* PK = FK operator OIDs */
Oid conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
/*
* Derived info about whether FK's equality conditions match the query:
*/
/* # of FK cols matched by ECs */
int nmatched_ec;
/* # of these ECs that are ec_has_const */
int nconst_ec;
/* # of FK cols matched by non-EC rinfos */
int nmatched_rcols;
/* total # of non-EC rinfos matched to FK */
int nmatched_ri;
/* Pointer to eclass matching each column's condition, if there is one */
struct EquivalenceClass *eclass[INDEX_MAX_KEYS];
/* Pointer to eclass member for the referencing Var, if there is one */
struct EquivalenceMember *fk_eclass_member[INDEX_MAX_KEYS];
/* List of non-EC RestrictInfos matching each column's condition */
List *rinfos[INDEX_MAX_KEYS];
} ForeignKeyOptInfo;
/*
* StatisticExtInfo
* Information about extended statistics for planning/optimization
*
* Each pg_statistic_ext row is represented by one or more nodes of this
* type, or even zero if ANALYZE has not computed them.
*/
typedef struct StatisticExtInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/* OID of the statistics row */
Oid statOid;
/* includes child relations */
bool inherit;
/* back-link to statistic's table; don't print, else infinite recursion */
RelOptInfo *rel pg_node_attr(read_write_ignore);
/* statistics kind of this entry */
char kind;
/* attnums of the columns covered */
Bitmapset *keys;
/* expressions */
List *exprs;
} StatisticExtInfo;
/*
* JoinDomains
*
* A "join domain" defines the scope of applicability of deductions made via
* the EquivalenceClass mechanism. Roughly speaking, a join domain is a set
* of base+OJ relations that are inner-joined together. More precisely, it is
* the set of relations at which equalities deduced from an EquivalenceClass
* can be enforced or should be expected to hold. The topmost JoinDomain
* covers the whole query (so its jd_relids should equal all_query_rels).
* An outer join creates a new JoinDomain that includes all base+OJ relids
* within its nullable side, but (by convention) not the OJ's own relid.
* A FULL join creates two new JoinDomains, one for each side.
*
* Notice that a rel that is below outer join(s) will thus appear to belong
* to multiple join domains. However, any of its Vars that appear in
* EquivalenceClasses belonging to higher join domains will have nullingrel
* bits preventing them from being evaluated at the rel's scan level, so that
* we will not be able to derive enforceable-at-the-rel-scan-level clauses
* from such ECs. We define the join domain relid sets this way so that
* domains can be said to be "higher" or "lower" when one domain relid set
* includes another.
*
* The JoinDomains for a query are computed in deconstruct_jointree.
* We do not copy JoinDomain structs once made, so they can be compared
* for equality by simple pointer equality.
*/
typedef struct JoinDomain
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
Relids jd_relids; /* all relids contained within the domain */
} JoinDomain;
/*
* EquivalenceClasses
*
* Whenever we identify a mergejoinable equality clause A = B that is
* not an outer-join clause, we create an EquivalenceClass containing
* the expressions A and B to record this knowledge. If we later find another
* equivalence B = C, we add C to the existing EquivalenceClass; this may
* require merging two existing EquivalenceClasses. At the end of the qual
* distribution process, we have sets of values that are known all transitively
* equal to each other, where "equal" is according to the rules of the btree
* operator family(s) shown in ec_opfamilies, as well as the collation shown
* by ec_collation. (We restrict an EC to contain only equalities whose
* operators belong to the same set of opfamilies. This could probably be
* relaxed, but for now it's not worth the trouble, since nearly all equality
* operators belong to only one btree opclass anyway. Similarly, we suppose
* that all or none of the input datatypes are collatable, so that a single
* collation value is sufficient.)
*
* Strictly speaking, deductions from an EquivalenceClass hold only within
* a "join domain", that is a set of relations that are innerjoined together
* (see JoinDomain above). For the most part we don't need to account for
* this explicitly, because equality clauses from different join domains
* will contain Vars that are not equal() because they have different
* nullingrel sets, and thus we will never falsely merge ECs from different
* join domains. But Var-free (pseudoconstant) expressions lack that safety
* feature. We handle that by marking "const" EC members with the JoinDomain
* of the clause they came from; two nominally-equal const members will be
* considered different if they came from different JoinDomains. This ensures
* no false EquivalenceClass merges will occur.
*
* We also use EquivalenceClasses as the base structure for PathKeys, letting
* us represent knowledge about different sort orderings being equivalent.
* Since every PathKey must reference an EquivalenceClass, we will end up
* with single-member EquivalenceClasses whenever a sort key expression has
* not been equivalenced to anything else. It is also possible that such an
* EquivalenceClass will contain a volatile expression ("ORDER BY random()"),
* which is a case that can't arise otherwise since clauses containing
* volatile functions are never considered mergejoinable. We mark such
* EquivalenceClasses specially to prevent them from being merged with
* ordinary EquivalenceClasses. Also, for volatile expressions we have
* to be careful to match the EquivalenceClass to the correct targetlist
* entry: consider SELECT random() AS a, random() AS b ... ORDER BY b,a.
* So we record the SortGroupRef of the originating sort clause.
*
* Derived equality clauses are stored in ec_derives_list. For small queries,
* this list is scanned directly during lookup. For larger queries -- e.g.,
* with many partitions or joins -- a hash table (ec_derives_hash) is built
* when the list grows beyond a threshold, for faster lookup. When present,
* the hash table contains the same RestrictInfos and is maintained alongside
* the list. We retain the list even when the hash is used to simplify
* serialization (e.g., in _outEquivalenceClass()) and support
* EquivalenceClass merging.
*
* In contrast, ec_sources holds equality clauses that appear directly in the
* query. These are typically few and do not require a hash table for lookup.
*
* 'ec_members' is a List of all !em_is_child EquivalenceMembers in the class.
* EquivalenceMembers for any RELOPT_OTHER_MEMBER_REL and RELOPT_OTHER_JOINREL
* relations are stored in the 'ec_childmembers' array in the index
* corresponding to the relid, or first component relid in the case of
* RELOPT_OTHER_JOINRELs. 'ec_childmembers' is NULL if the class has no child
* EquivalenceMembers.
*
* For code wishing to look at EquivalenceMembers, if only parent-level
* members are needed, then a simple foreach loop over ec_members is
* sufficient. When child members are also required, it is best to use the
* functionality provided by EquivalenceMemberIterator. This visits all
* parent members and only the relevant child members. The reason for this
* is that large numbers of child EquivalenceMembers can exist in queries to
* partitioned tables with many partitions. The functionality provided by
* EquivalenceMemberIterator allows efficient access to EquivalenceMembers
* which belong to specific child relids. See the header comments for
* EquivalenceMemberIterator below for further details.
*
* NB: if ec_merged isn't NULL, this class has been merged into another, and
* should be ignored in favor of using the pointed-to class.
*
* NB: EquivalenceClasses are never copied after creation. Therefore,
* copyObject() copies pointers to them as pointers, and equal() compares
* pointers to EquivalenceClasses via pointer equality. This is implemented
* by putting copy_as_scalar and equal_as_scalar attributes on fields that
* are pointers to EquivalenceClasses. The same goes for EquivalenceMembers.
*/
typedef struct EquivalenceClass
{
pg_node_attr(custom_read_write, no_copy_equal, no_read, no_query_jumble)
NodeTag type;
List *ec_opfamilies; /* btree operator family OIDs */
Oid ec_collation; /* collation, if datatypes are collatable */
int ec_childmembers_size; /* # elements in ec_childmembers */
List *ec_members; /* list of EquivalenceMembers */
List **ec_childmembers; /* array of Lists of child members */
List *ec_sources; /* list of generating RestrictInfos */
List *ec_derives_list; /* list of derived RestrictInfos */
struct derives_hash *ec_derives_hash; /* optional hash table for fast
* lookup; contains same
* RestrictInfos as list */
Relids ec_relids; /* all relids appearing in ec_members, except
* for child members (see below) */
bool ec_has_const; /* any pseudoconstants in ec_members? */
bool ec_has_volatile; /* the (sole) member is a volatile expr */
bool ec_broken; /* failed to generate needed clauses? */
Index ec_sortref; /* originating sortclause label, or 0 */
Index ec_min_security; /* minimum security_level in ec_sources */
Index ec_max_security; /* maximum security_level in ec_sources */
struct EquivalenceClass *ec_merged; /* set if merged into another EC */
} EquivalenceClass;
/*
* If an EC contains a constant, any PathKey depending on it must be
* redundant, since there's only one possible value of the key.
*/
#define EC_MUST_BE_REDUNDANT(eclass) \
((eclass)->ec_has_const)
/*
* EquivalenceMember - one member expression of an EquivalenceClass
*
* em_is_child signifies that this element was built by transposing a member
* for an appendrel parent relation to represent the corresponding expression
* for an appendrel child. These members are used for determining the
* pathkeys of scans on the child relation and for explicitly sorting the
* child when necessary to build a MergeAppend path for the whole appendrel
* tree. An em_is_child member has no impact on the properties of the EC as a
* whole; in particular the EC's ec_relids field does NOT include the child
* relation. em_is_child members aren't stored in the ec_members List of the
* EC and instead they're stored and indexed by the relids of the child
* relation they represent in ec_childmembers. An em_is_child member
* should never be marked em_is_const nor cause ec_has_const or
* ec_has_volatile to be set, either. Thus, em_is_child members are not
* really full-fledged members of the EC, but just reflections or
* doppelgangers of real members. Most operations on EquivalenceClasses
* should ignore em_is_child members by only inspecting members in the
* ec_members list. Callers that require inspecting child members should do
* so using an EquivalenceMemberIterator and should test em_relids to make
* sure they only consider relevant members.
*
* em_datatype is usually the same as exprType(em_expr), but can be
* different when dealing with a binary-compatible opfamily; in particular
* anyarray_ops would never work without this. Use em_datatype when
* looking up a specific btree operator to work with this expression.
*/
typedef struct EquivalenceMember
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
Expr *em_expr; /* the expression represented */
Relids em_relids; /* all relids appearing in em_expr */
bool em_is_const; /* expression is pseudoconstant? */
bool em_is_child; /* derived version for a child relation? */
Oid em_datatype; /* the "nominal type" used by the opfamily */
JoinDomain *em_jdomain; /* join domain containing the source clause */
/* if em_is_child is true, this links to corresponding EM for top parent */
struct EquivalenceMember *em_parent pg_node_attr(read_write_ignore);
} EquivalenceMember;
/*
* EquivalenceMemberIterator
*
* EquivalenceMemberIterator allows efficient access to sets of
* EquivalenceMembers for callers which require access to child members.
* Because partitioning workloads can result in large numbers of child
* members, the child members are not stored in the EquivalenceClass's
* ec_members List. Instead, these are stored in the EquivalenceClass's
* ec_childmembers array of Lists. The functionality provided by
* EquivalenceMemberIterator aims to provide efficient access to parent
* members and child members belonging to specific child relids.
*
* Currently, there is only one way to initialize and iterate over an
* EquivalenceMemberIterator and that is via the setup_eclass_member_iterator
* and eclass_member_iterator_next functions. The iterator object is
* generally a local variable which is passed by address to
* setup_eclass_member_iterator. The calling function defines which
* EquivalenceClass the iterator should be looking at and which child
* relids to also return members for. child_relids can be passed as NULL, but
* the caller may as well just perform a foreach loop over ec_members as only
* parent-level members will be returned in that case.
*
* When calling the next function on an EquivalenceMemberIterator, all
* parent-level EquivalenceMembers are returned first, followed by all child
* members for the specified 'child_relids' for all child members which were
* indexed by any of the specified 'child_relids' in add_child_eq_member().
*
* Code using the iterator method of finding EquivalenceMembers will generally
* always want to ensure the returned member matches their search criteria
* rather than relying on the filtering to be done for them as all parent
* members are returned and for members belonging to RELOPT_OTHER_JOINREL
* rels, the member's em_relids may be a superset of the specified
* 'child_relids', which might not be what the caller wants.
*
* The most common way to use this iterator is as follows:
* -----
* EquivalenceMemberIterator it;
* EquivalenceMember *em;
*
* setup_eclass_member_iterator(&it, ec, child_relids);
* while ((em = eclass_member_iterator_next(&it)) != NULL)
* {
* ...
* }
* -----
* It is not valid to call eclass_member_iterator_next() after it has returned
* NULL for any given EquivalenceMemberIterator. Individual fields within
* the EquivalenceMemberIterator struct must not be accessed by callers.
*/
typedef struct
{
EquivalenceClass *ec; /* The EquivalenceClass to iterate over */
int current_relid; /* Current relid position within 'relids'. -1
* when still looping over ec_members and -2
* at the end of iteration */
Relids child_relids; /* Relids of child relations of interest.
* Non-child rels are ignored */
ListCell *current_cell; /* Next cell to return within current_list */
List *current_list; /* Current list of members being returned */
} EquivalenceMemberIterator;
/*
* PathKeys
*
* The sort ordering of a path is represented by a list of PathKey nodes.
* An empty list implies no known ordering. Otherwise the first item
* represents the primary sort key, the second the first secondary sort key,
* etc. The value being sorted is represented by linking to an
* EquivalenceClass containing that value and including pk_opfamily among its
* ec_opfamilies. The EquivalenceClass tells which collation to use, too.
* This is a convenient method because it makes it trivial to detect
* equivalent and closely-related orderings. (See optimizer/README for more
* information.)
*
* Note: pk_cmptype is either COMPARE_LT (for ASC) or COMPARE_GT (for DESC).
*/
typedef struct PathKey
{
pg_node_attr(no_read, no_query_jumble)
NodeTag type;
/* the value that is ordered */
EquivalenceClass *pk_eclass pg_node_attr(copy_as_scalar, equal_as_scalar);
Oid pk_opfamily; /* index opfamily defining the ordering */
CompareType pk_cmptype; /* sort direction (ASC or DESC) */
bool pk_nulls_first; /* do NULLs come before normal values? */
} PathKey;
/*
* Contains an order of group-by clauses and the corresponding list of
* pathkeys.
*
* The elements of 'clauses' list should have the same order as the head of
* 'pathkeys' list. The tleSortGroupRef of the clause should be equal to
* ec_sortref of the pathkey equivalence class. If there are redundant
* clauses with the same tleSortGroupRef, they must be grouped together.
*/
typedef struct GroupByOrdering
{
NodeTag type;
List *pathkeys;
List *clauses;
} GroupByOrdering;
/*
* VolatileFunctionStatus -- allows nodes to cache their
* contain_volatile_functions properties. VOLATILITY_UNKNOWN means not yet
* determined.
*/
typedef enum VolatileFunctionStatus
{
VOLATILITY_UNKNOWN = 0,
VOLATILITY_VOLATILE,
VOLATILITY_NOVOLATILE,
} VolatileFunctionStatus;
/*
* PathTarget
*
* This struct contains what we need to know during planning about the
* targetlist (output columns) that a Path will compute. Each RelOptInfo
* includes a default PathTarget, which its individual Paths may simply
* reference. However, in some cases a Path may compute outputs different
* from other Paths, and in that case we make a custom PathTarget for it.
* For example, an indexscan might return index expressions that would
* otherwise need to be explicitly calculated. (Note also that "upper"
* relations generally don't have useful default PathTargets.)
*
* exprs contains bare expressions; they do not have TargetEntry nodes on top,
* though those will appear in finished Plans.
*
* sortgrouprefs[] is an array of the same length as exprs, containing the
* corresponding sort/group refnos, or zeroes for expressions not referenced
* by sort/group clauses. If sortgrouprefs is NULL (which it generally is in
* RelOptInfo.reltarget targets; only upper-level Paths contain this info),
* we have not identified sort/group columns in this tlist. This allows us to
* deal with sort/group refnos when needed with less expense than including
* TargetEntry nodes in the exprs list.
*/
typedef struct PathTarget
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/* list of expressions to be computed */
List *exprs;
/* corresponding sort/group refnos, or 0 */
Index *sortgrouprefs pg_node_attr(array_size(exprs));
/* cost of evaluating the expressions */
QualCost cost;
/* estimated avg width of result tuples */
int width;
/* indicates if exprs contain any volatile functions */
VolatileFunctionStatus has_volatile_expr;
} PathTarget;
/* Convenience macro to get a sort/group refno from a PathTarget */
#define get_pathtarget_sortgroupref(target, colno) \
((target)->sortgrouprefs ? (target)->sortgrouprefs[colno] : (Index) 0)
/*
* ParamPathInfo
*
* All parameterized paths for a given relation with given required outer rels
* link to a single ParamPathInfo, which stores common information such as
* the estimated rowcount for this parameterization. We do this partly to
* avoid recalculations, but mostly to ensure that the estimated rowcount
* is in fact the same for every such path.
*
* Note: ppi_clauses is only used in ParamPathInfos for base relation paths;
* in join cases it's NIL because the set of relevant clauses varies depending
* on how the join is formed. The relevant clauses will appear in each
* parameterized join path's joinrestrictinfo list, instead. ParamPathInfos
* for append relations don't bother with this, either.
*
* ppi_serials is the set of rinfo_serial numbers for quals that are enforced
* by this path. As with ppi_clauses, it's only maintained for baserels.
* (We could construct it on-the-fly from ppi_clauses, but it seems better
* to materialize a copy.)
*/
typedef struct ParamPathInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
Relids ppi_req_outer; /* rels supplying parameters used by path */
Cardinality ppi_rows; /* estimated number of result tuples */
List *ppi_clauses; /* join clauses available from outer rels */
Bitmapset *ppi_serials; /* set of rinfo_serial for enforced quals */
} ParamPathInfo;
/*
* Type "Path" is used as-is for sequential-scan paths, as well as some other
* simple plan types that we don't need any extra information in the path for.
* For other path types it is the first component of a larger struct.
*
* "pathtype" is the NodeTag of the Plan node we could build from this Path.
* It is partially redundant with the Path's NodeTag, but allows us to use
* the same Path type for multiple Plan types when there is no need to
* distinguish the Plan type during path processing.
*
* "parent" identifies the relation this Path scans, and "pathtarget"
* describes the precise set of output columns the Path would compute.
* In simple cases all Paths for a given rel share the same targetlist,
* which we represent by having path->pathtarget equal to parent->reltarget.
*
* "param_info", if not NULL, links to a ParamPathInfo that identifies outer
* relation(s) that provide parameter values to each scan of this path.
* That means this path can only be joined to those rels by means of nestloop
* joins with this path on the inside. Also note that a parameterized path
* is responsible for testing all "movable" joinclauses involving this rel
* and the specified outer rel(s).
*
* "rows" is the same as parent->rows in simple paths, but in parameterized
* paths it can be less than parent->rows, reflecting the fact that we've
* filtered by extra join conditions.
*
* "pathkeys" is a List of PathKey nodes (see above), describing the sort
* ordering of the path's output rows.
*
* We do not support copying Path trees, mainly because the circular linkages
* between RelOptInfo and Path nodes can't be handled easily in a simple
* depth-first traversal. We also don't have read support at the moment.
*/
typedef struct Path
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/* tag identifying scan/join method */
NodeTag pathtype;
/*
* the relation this path can build
*
* We do NOT print the parent, else we'd be in infinite recursion. We can
* print the parent's relids for identification purposes, though.
*/
RelOptInfo *parent pg_node_attr(write_only_relids);
/*
* list of Vars/Exprs, cost, width
*
* We print the pathtarget only if it's not the default one for the rel.
*/
PathTarget *pathtarget pg_node_attr(write_only_nondefault_pathtarget);
/*
* parameterization info, or NULL if none
*
* We do not print the whole of param_info, since it's printed via
* RelOptInfo; it's sufficient and less cluttering to print just the
* required outer relids.
*/
ParamPathInfo *param_info pg_node_attr(write_only_req_outer);
/* engage parallel-aware logic? */
bool parallel_aware;
/* OK to use as part of parallel plan? */
bool parallel_safe;
/* desired # of workers; 0 = not parallel */
int parallel_workers;
/* estimated size/costs for path (see costsize.c for more info) */
Cardinality rows; /* estimated number of result tuples */
int disabled_nodes; /* count of disabled nodes */
Cost startup_cost; /* cost expended before fetching any tuples */
Cost total_cost; /* total cost (assuming all tuples fetched) */
/* sort ordering of path's output; a List of PathKey nodes; see above */
List *pathkeys;
} Path;
/* Macro for extracting a path's parameterization relids; beware double eval */
#define PATH_REQ_OUTER(path) \
((path)->param_info ? (path)->param_info->ppi_req_outer : (Relids) NULL)
/*----------
* IndexPath represents an index scan over a single index.
*
* This struct is used for both regular indexscans and index-only scans;
* path.pathtype is T_IndexScan or T_IndexOnlyScan to show which is meant.
*
* 'indexinfo' is the index to be scanned.
*
* 'indexclauses' is a list of IndexClause nodes, each representing one
* index-checkable restriction, with implicit AND semantics across the list.
* An empty list implies a full index scan.
*
* 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have
* been found to be usable as ordering operators for an amcanorderbyop index.
* The list must match the path's pathkeys, ie, one expression per pathkey
* in the same order. These are not RestrictInfos, just bare expressions,
* since they generally won't yield booleans. It's guaranteed that each
* expression has the index key on the left side of the operator.
*
* 'indexorderbycols' is an integer list of index column numbers (zero-based)
* of the same length as 'indexorderbys', showing which index column each
* ORDER BY expression is meant to be used with. (There is no restriction
* on which index column each ORDER BY can be used with.)
*
* 'indexscandir' is one of:
* ForwardScanDirection: forward scan of an index
* BackwardScanDirection: backward scan of an ordered index
* Unordered indexes will always have an indexscandir of ForwardScanDirection.
*
* 'indextotalcost' and 'indexselectivity' are saved in the IndexPath so that
* we need not recompute them when considering using the same index in a
* bitmap index/heap scan (see BitmapHeapPath). The costs of the IndexPath
* itself represent the costs of an IndexScan or IndexOnlyScan plan type.
*----------
*/
typedef struct IndexPath
{
Path path;
IndexOptInfo *indexinfo;
List *indexclauses;
List *indexorderbys;
List *indexorderbycols;
ScanDirection indexscandir;
Cost indextotalcost;
Selectivity indexselectivity;
} IndexPath;
/*
* Each IndexClause references a RestrictInfo node from the query's WHERE
* or JOIN conditions, and shows how that restriction can be applied to
* the particular index. We support both indexclauses that are directly
* usable by the index machinery, which are typically of the form
* "indexcol OP pseudoconstant", and those from which an indexable qual
* can be derived. The simplest such transformation is that a clause
* of the form "pseudoconstant OP indexcol" can be commuted to produce an
* indexable qual (the index machinery expects the indexcol to be on the
* left always). Another example is that we might be able to extract an
* indexable range condition from a LIKE condition, as in "x LIKE 'foo%bar'"
* giving rise to "x >= 'foo' AND x < 'fop'". Derivation of such lossy
* conditions is done by a planner support function attached to the
* indexclause's top-level function or operator.
*
* indexquals is a list of RestrictInfos for the directly-usable index
* conditions associated with this IndexClause. In the simplest case
* it's a one-element list whose member is iclause->rinfo. Otherwise,
* it contains one or more directly-usable indexqual conditions extracted
* from the given clause. The 'lossy' flag indicates whether the
* indexquals are semantically equivalent to the original clause, or
* represent a weaker condition.
*
* Normally, indexcol is the index of the single index column the clause
* works on, and indexcols is NIL. But if the clause is a RowCompareExpr,
* indexcol is the index of the leading column, and indexcols is a list of
* all the affected columns. (Note that indexcols matches up with the
* columns of the actual indexable RowCompareExpr in indexquals, which
* might be different from the original in rinfo.)
*
* An IndexPath's IndexClause list is required to be ordered by index
* column, i.e. the indexcol values must form a nondecreasing sequence.
* (The order of multiple clauses for the same index column is unspecified.)
*/
typedef struct IndexClause
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
struct RestrictInfo *rinfo; /* original restriction or join clause */
List *indexquals; /* indexqual(s) derived from it */
bool lossy; /* are indexquals a lossy version of clause? */
AttrNumber indexcol; /* index column the clause uses (zero-based) */
List *indexcols; /* multiple index columns, if RowCompare */
} IndexClause;
/*
* BitmapHeapPath represents one or more indexscans that generate TID bitmaps
* instead of directly accessing the heap, followed by AND/OR combinations
* to produce a single bitmap, followed by a heap scan that uses the bitmap.
* Note that the output is always considered unordered, since it will come
* out in physical heap order no matter what the underlying indexes did.
*
* The individual indexscans are represented by IndexPath nodes, and any
* logic on top of them is represented by a tree of BitmapAndPath and
* BitmapOrPath nodes. Notice that we can use the same IndexPath node both
* to represent a regular (or index-only) index scan plan, and as the child
* of a BitmapHeapPath that represents scanning the same index using a
* BitmapIndexScan. The startup_cost and total_cost figures of an IndexPath
* always represent the costs to use it as a regular (or index-only)
* IndexScan. The costs of a BitmapIndexScan can be computed using the
* IndexPath's indextotalcost and indexselectivity.
*/
typedef struct BitmapHeapPath
{
Path path;
Path *bitmapqual; /* IndexPath, BitmapAndPath, BitmapOrPath */
} BitmapHeapPath;
/*
* BitmapAndPath represents a BitmapAnd plan node; it can only appear as
* part of the substructure of a BitmapHeapPath. The Path structure is
* a bit more heavyweight than we really need for this, but for simplicity
* we make it a derivative of Path anyway.
*/
typedef struct BitmapAndPath
{
Path path;
List *bitmapquals; /* IndexPaths and BitmapOrPaths */
Selectivity bitmapselectivity;
} BitmapAndPath;
/*
* BitmapOrPath represents a BitmapOr plan node; it can only appear as
* part of the substructure of a BitmapHeapPath. The Path structure is
* a bit more heavyweight than we really need for this, but for simplicity
* we make it a derivative of Path anyway.
*/
typedef struct BitmapOrPath
{
Path path;
List *bitmapquals; /* IndexPaths and BitmapAndPaths */
Selectivity bitmapselectivity;
} BitmapOrPath;
/*
* TidPath represents a scan by TID
*
* tidquals is an implicitly OR'ed list of qual expressions of the form
* "CTID = pseudoconstant", or "CTID = ANY(pseudoconstant_array)",
* or a CurrentOfExpr for the relation.
*/
typedef struct TidPath
{
Path path;
List *tidquals; /* qual(s) involving CTID = something */
} TidPath;
/*
* TidRangePath represents a scan by a contiguous range of TIDs
*
* tidrangequals is an implicitly AND'ed list of qual expressions of the form
* "CTID relop pseudoconstant", where relop is one of >,>=,<,<=.
*/
typedef struct TidRangePath
{
Path path;
List *tidrangequals;
} TidRangePath;
/*
* SubqueryScanPath represents a scan of an unflattened subquery-in-FROM
*
* Note that the subpath comes from a different planning domain; for example
* RTE indexes within it mean something different from those known to the
* SubqueryScanPath. path.parent->subroot is the planning context needed to
* interpret the subpath.
*/
typedef struct SubqueryScanPath
{
Path path;
Path *subpath; /* path representing subquery execution */
} SubqueryScanPath;
/*
* ForeignPath represents a potential scan of a foreign table, foreign join
* or foreign upper-relation.
*
* In the case of a foreign join, fdw_restrictinfo stores the RestrictInfos to
* apply to the join, which are used by createplan.c to get pseudoconstant
* clauses evaluated as one-time quals in a gating Result plan node.
*
* fdw_private stores FDW private data about the scan. While fdw_private is
* not actually touched by the core code during normal operations, it's
* generally a good idea to use a representation that can be dumped by
* nodeToString(), so that you can examine the structure during debugging
* with tools like pprint().
*/
typedef struct ForeignPath
{
Path path;
Path *fdw_outerpath;
List *fdw_restrictinfo;
List *fdw_private;
} ForeignPath;
/*
* CustomPath represents a table scan or a table join done by some out-of-core
* extension.
*
* We provide a set of hooks here - which the provider must take care to set
* up correctly - to allow extensions to supply their own methods of scanning
* a relation or join relations. For example, a provider might provide GPU
* acceleration, a cache-based scan, or some other kind of logic we haven't
* dreamed up yet.
*
* CustomPaths can be injected into the planning process for a base or join
* relation by set_rel_pathlist_hook or set_join_pathlist_hook functions,
* respectively.
*
* In the case of a table join, custom_restrictinfo stores the RestrictInfos
* to apply to the join, which are used by createplan.c to get pseudoconstant
* clauses evaluated as one-time quals in a gating Result plan node.
*
* Core code must avoid assuming that the CustomPath is only as large as
* the structure declared here; providers are allowed to make it the first
* element in a larger structure. (Since the planner never copies Paths,
* this doesn't add any complication.) However, for consistency with the
* FDW case, we provide a "custom_private" field in CustomPath; providers
* may prefer to use that rather than define another struct type.
*/
struct CustomPathMethods;
typedef struct CustomPath
{
Path path;
uint32 flags; /* mask of CUSTOMPATH_* flags, see
* nodes/extensible.h */
List *custom_paths; /* list of child Path nodes, if any */
List *custom_restrictinfo;
List *custom_private;
const struct CustomPathMethods *methods;
} CustomPath;
/*
* AppendPath represents an Append plan, ie, successive execution of
* several member plans.
*
* For partial Append, 'subpaths' contains non-partial subpaths followed by
* partial subpaths.
*
* Whenever accumulate_append_subpath() allows us to consolidate multiple
* levels of Append paths down to one, we store the RTI sets for the omitted
* paths in child_append_relid_sets. This is not necessary for planning or
* execution; we do it for the benefit of code that wants to inspect the
* final plan and understand how it came to be.
*
* Note: it is possible for "subpaths" to contain only one, or even no,
* elements. These cases are optimized during create_append_plan.
* In particular, an AppendPath with no subpaths is a "dummy" path that
* is created to represent the case that a relation is provably empty.
* (This is a convenient representation because it means that when we build
* an appendrel and find that all its children have been excluded, no extra
* action is needed to recognize the relation as dummy.)
*/
typedef struct AppendPath
{
Path path;
List *subpaths; /* list of component Paths */
/* Index of first partial path in subpaths; list_length(subpaths) if none */
int first_partial_path;
Cardinality limit_tuples; /* hard limit on output tuples, or -1 */
List *child_append_relid_sets;
} AppendPath;
#define IS_DUMMY_APPEND(p) \
(IsA((p), AppendPath) && ((AppendPath *) (p))->subpaths == NIL)
/*
* A relation that's been proven empty will have one path that is dummy
* (but might have projection paths on top). For historical reasons,
* this is provided as a macro that wraps is_dummy_rel().
*/
#define IS_DUMMY_REL(r) is_dummy_rel(r)
extern bool is_dummy_rel(RelOptInfo *rel);
/*
* MergeAppendPath represents a MergeAppend plan, ie, the merging of sorted
* results from several member plans to produce similarly-sorted output.
*
* child_append_relid_sets has the same meaning here as for AppendPath.
*/
typedef struct MergeAppendPath
{
Path path;
List *subpaths; /* list of component Paths */
Cardinality limit_tuples; /* hard limit on output tuples, or -1 */
List *child_append_relid_sets;
} MergeAppendPath;
/*
* GroupResultPath represents use of a Result plan node to compute the
* output of a degenerate GROUP BY case, wherein we know we should produce
* exactly one row, which might then be filtered by a HAVING qual.
*
* Note that quals is a list of bare clauses, not RestrictInfos.
*/
typedef struct GroupResultPath
{
Path path;
List *quals;
} GroupResultPath;
/*
* MaterialPath represents use of a Material plan node, i.e., caching of
* the output of its subpath. This is used when the subpath is expensive
* and needs to be scanned repeatedly, or when we need mark/restore ability
* and the subpath doesn't have it.
*/
typedef struct MaterialPath
{
Path path;
Path *subpath;
} MaterialPath;
/*
* MemoizePath represents a Memoize plan node, i.e., a cache that caches
* tuples from parameterized paths to save the underlying node from having to
* be rescanned for parameter values which are already cached.
*/
typedef struct MemoizePath
{
Path path;
Path *subpath; /* outerpath to cache tuples from */
List *hash_operators; /* OIDs of hash equality ops for cache keys */
List *param_exprs; /* expressions that are cache keys */
bool singlerow; /* true if the cache entry is to be marked as
* complete after caching the first record. */
bool binary_mode; /* true when cache key should be compared bit
* by bit, false when using hash equality ops */
uint32 est_entries; /* The maximum number of entries that the
* planner expects will fit in the cache, or 0
* if unknown */
Cardinality est_calls; /* expected number of rescans */
Cardinality est_unique_keys; /* estimated unique keys, for EXPLAIN */
double est_hit_ratio; /* estimated cache hit ratio, for EXPLAIN */
} MemoizePath;
/*
* GatherPath runs several copies of a plan in parallel and collects the
* results. The parallel leader may also execute the plan, unless the
* single_copy flag is set.
*/
typedef struct GatherPath
{
Path path;
Path *subpath; /* path for each worker */
bool single_copy; /* don't execute path more than once */
int num_workers; /* number of workers sought to help */
} GatherPath;
/*
* GatherMergePath runs several copies of a plan in parallel and collects
* the results, preserving their common sort order.
*/
typedef struct GatherMergePath
{
Path path;
Path *subpath; /* path for each worker */
int num_workers; /* number of workers sought to help */
} GatherMergePath;
/*
* All join-type paths share these fields.
*/
typedef struct JoinPath
{
pg_node_attr(abstract)
Path path;
JoinType jointype;
bool inner_unique; /* each outer tuple provably matches no more
* than one inner tuple */
Path *outerjoinpath; /* path for the outer side of the join */
Path *innerjoinpath; /* path for the inner side of the join */
List *joinrestrictinfo; /* RestrictInfos to apply to join */
/*
* See the notes for RelOptInfo and ParamPathInfo to understand why
* joinrestrictinfo is needed in JoinPath, and can't be merged into the
* parent RelOptInfo.
*/
} JoinPath;
/*
* A nested-loop path needs no special fields.
*/
typedef struct NestPath
{
JoinPath jpath;
} NestPath;
/*
* A mergejoin path has these fields.
*
* Unlike other path types, a MergePath node doesn't represent just a single
* run-time plan node: it can represent up to four. Aside from the MergeJoin
* node itself, there can be a Sort node for the outer input, a Sort node
* for the inner input, and/or a Material node for the inner input. We could
* represent these nodes by separate path nodes, but considering how many
* different merge paths are investigated during a complex join problem,
* it seems better to avoid unnecessary palloc overhead.
*
* path_mergeclauses lists the clauses (in the form of RestrictInfos)
* that will be used in the merge.
*
* Note that the mergeclauses are a subset of the parent relation's
* restriction-clause list. Any join clauses that are not mergejoinable
* appear only in the parent's restrict list, and must be checked by a
* qpqual at execution time.
*
* outersortkeys (resp. innersortkeys) is NIL if the outer path
* (resp. inner path) is already ordered appropriately for the
* mergejoin. If it is not NIL then it is a PathKeys list describing
* the ordering that must be created by an explicit Sort node.
*
* outer_presorted_keys is the number of presorted keys of the outer
* path that match outersortkeys. It is used to determine whether
* explicit incremental sort can be applied when outersortkeys is not
* NIL. We do not track the number of presorted keys of the inner
* path, as incremental sort currently does not support mark/restore.
*
* skip_mark_restore is true if the executor need not do mark/restore calls.
* Mark/restore overhead is usually required, but can be skipped if we know
* that the executor need find only one match per outer tuple, and that the
* mergeclauses are sufficient to identify a match. In such cases the
* executor can immediately advance the outer relation after processing a
* match, and therefore it need never back up the inner relation.
*
* materialize_inner is true if a Material node should be placed atop the
* inner input. This may appear with or without an inner Sort step.
*/
typedef struct MergePath
{
JoinPath jpath;
List *path_mergeclauses; /* join clauses to be used for merge */
List *outersortkeys; /* keys for explicit sort, if any */
List *innersortkeys; /* keys for explicit sort, if any */
int outer_presorted_keys; /* number of presorted keys of the
* outer path */
bool skip_mark_restore; /* can executor skip mark/restore? */
bool materialize_inner; /* add Materialize to inner? */
} MergePath;
/*
* A hashjoin path has these fields.
*
* The remarks above for mergeclauses apply for hashclauses as well.
*
* Hashjoin does not care what order its inputs appear in, so we have
* no need for sortkeys.
*/
typedef struct HashPath
{
JoinPath jpath;
List *path_hashclauses; /* join clauses used for hashing */
int num_batches; /* number of batches expected */
Cardinality inner_rows_total; /* total inner rows expected */
} HashPath;
/*
* ProjectionPath represents a projection (that is, targetlist computation)
*
* Nominally, this path node represents using a Result plan node to do a
* projection step. However, if the input plan node supports projection,
* we can just modify its output targetlist to do the required calculations
* directly, and not need a Result. In some places in the planner we can just
* jam the desired PathTarget into the input path node (and adjust its cost
* accordingly), so we don't need a ProjectionPath. But in other places
* it's necessary to not modify the input path node, so we need a separate
* ProjectionPath node, which is marked dummy to indicate that we intend to
* assign the work to the input plan node. The estimated cost for the
* ProjectionPath node will account for whether a Result will be used or not.
*/
typedef struct ProjectionPath
{
Path path;
Path *subpath; /* path representing input source */
bool dummypp; /* true if no separate Result is needed */
} ProjectionPath;
/*
* ProjectSetPath represents evaluation of a targetlist that includes
* set-returning function(s), which will need to be implemented by a
* ProjectSet plan node.
*/
typedef struct ProjectSetPath
{
Path path;
Path *subpath; /* path representing input source */
} ProjectSetPath;
/*
* SortPath represents an explicit sort step
*
* The sort keys are, by definition, the same as path.pathkeys.
*
* Note: the Sort plan node cannot project, so path.pathtarget must be the
* same as the input's pathtarget.
*/
typedef struct SortPath
{
Path path;
Path *subpath; /* path representing input source */
} SortPath;
/*
* IncrementalSortPath represents an incremental sort step
*
* This is like a regular sort, except some leading key columns are assumed
* to be ordered already.
*/
typedef struct IncrementalSortPath
{
SortPath spath;
int nPresortedCols; /* number of presorted columns */
} IncrementalSortPath;
/*
* GroupPath represents grouping (of presorted input)
*
* groupClause represents the columns to be grouped on; the input path
* must be at least that well sorted.
*
* We can also apply a qual to the grouped rows (equivalent of HAVING)
*/
typedef struct GroupPath
{
Path path;
Path *subpath; /* path representing input source */
List *groupClause; /* a list of SortGroupClause's */
List *qual; /* quals (HAVING quals), if any */
} GroupPath;
/*
* UniquePath represents adjacent-duplicate removal (in presorted input)
*
* The columns to be compared are the first numkeys columns of the path's
* pathkeys. The input is presumed already sorted that way.
*/
typedef struct UniquePath
{
Path path;
Path *subpath; /* path representing input source */
int numkeys; /* number of pathkey columns to compare */
} UniquePath;
/*
* AggPath represents generic computation of aggregate functions
*
* This may involve plain grouping (but not grouping sets), using either
* sorted or hashed grouping; for the AGG_SORTED case, the input must be
* appropriately presorted.
*/
typedef struct AggPath
{
Path path;
Path *subpath; /* path representing input source */
AggStrategy aggstrategy; /* basic strategy, see nodes.h */
AggSplit aggsplit; /* agg-splitting mode, see nodes.h */
Cardinality numGroups; /* estimated number of groups in input */
uint64 transitionSpace; /* for pass-by-ref transition data */
List *groupClause; /* a list of SortGroupClause's */
List *qual; /* quals (HAVING quals), if any */
} AggPath;
/*
* Various annotations used for grouping sets in the planner.
*/
typedef struct GroupingSetData
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
List *set; /* grouping set as list of sortgrouprefs */
Cardinality numGroups; /* est. number of result groups */
} GroupingSetData;
typedef struct RollupData
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
List *groupClause; /* applicable subset of parse->groupClause */
List *gsets; /* lists of integer indexes into groupClause */
List *gsets_data; /* list of GroupingSetData */
Cardinality numGroups; /* est. number of result groups */
bool hashable; /* can be hashed */
bool is_hashed; /* to be implemented as a hashagg */
} RollupData;
/*
* GroupingSetsPath represents a GROUPING SETS aggregation
*/
typedef struct GroupingSetsPath
{
Path path;
Path *subpath; /* path representing input source */
AggStrategy aggstrategy; /* basic strategy */
List *rollups; /* list of RollupData */
List *qual; /* quals (HAVING quals), if any */
uint64 transitionSpace; /* for pass-by-ref transition data */
} GroupingSetsPath;
/*
* MinMaxAggPath represents computation of MIN/MAX aggregates from indexes
*/
typedef struct MinMaxAggPath
{
Path path;
List *mmaggregates; /* list of MinMaxAggInfo */
List *quals; /* HAVING quals, if any */
} MinMaxAggPath;
/*
* WindowAggPath represents generic computation of window functions
*/
typedef struct WindowAggPath
{
Path path;
Path *subpath; /* path representing input source */
WindowClause *winclause; /* WindowClause we'll be using */
List *qual; /* lower-level WindowAgg runconditions */
List *runCondition; /* OpExpr List to short-circuit execution */
bool topwindow; /* false for all apart from the WindowAgg
* that's closest to the root of the plan */
} WindowAggPath;
/*
* SetOpPath represents a set-operation, that is INTERSECT or EXCEPT
*/
typedef struct SetOpPath
{
Path path;
Path *leftpath; /* paths representing input sources */
Path *rightpath;
SetOpCmd cmd; /* what to do, see nodes.h */
SetOpStrategy strategy; /* how to do it, see nodes.h */
List *groupList; /* SortGroupClauses identifying target cols */
Cardinality numGroups; /* estimated number of groups in left input */
} SetOpPath;
/*
* RecursiveUnionPath represents a recursive UNION node
*/
typedef struct RecursiveUnionPath
{
Path path;
Path *leftpath; /* paths representing input sources */
Path *rightpath;
List *distinctList; /* SortGroupClauses identifying target cols */
int wtParam; /* ID of Param representing work table */
Cardinality numGroups; /* estimated number of groups in input */
} RecursiveUnionPath;
/*
* LockRowsPath represents acquiring row locks for SELECT FOR UPDATE/SHARE
*/
typedef struct LockRowsPath
{
Path path;
Path *subpath; /* path representing input source */
List *rowMarks; /* a list of PlanRowMark's */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
} LockRowsPath;
/*
* ModifyTablePath represents performing INSERT/UPDATE/DELETE/MERGE
*
* We represent most things that will be in the ModifyTable plan node
* literally, except we have a child Path not Plan. But analysis of the
* OnConflictExpr is deferred to createplan.c, as is collection of FDW data.
*/
typedef struct ModifyTablePath
{
Path path;
Path *subpath; /* Path producing source data */
CmdType operation; /* INSERT, UPDATE, DELETE, or MERGE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
Index rootRelation; /* Root RT index, if partitioned/inherited */
List *resultRelations; /* integer list of RT indexes */
List *updateColnosLists; /* per-target-table update_colnos lists */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
OnConflictExpr *onconflict; /* ON CONFLICT clause, or NULL */
ForPortionOfExpr *forPortionOf; /* FOR PORTION OF clause for UPDATE/DELETE */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
List *mergeActionLists; /* per-target-table lists of actions for
* MERGE */
List *mergeJoinConditions; /* per-target-table join conditions
* for MERGE */
} ModifyTablePath;
/*
* LimitPath represents applying LIMIT/OFFSET restrictions
*/
typedef struct LimitPath
{
Path path;
Path *subpath; /* path representing input source */
Node *limitOffset; /* OFFSET parameter, or NULL if none */
Node *limitCount; /* COUNT parameter, or NULL if none */
LimitOption limitOption; /* FETCH FIRST with ties or exact number */
} LimitPath;
/*
* Restriction clause info.
*
* We create one of these for each AND sub-clause of a restriction condition
* (WHERE or JOIN/ON clause). Since the restriction clauses are logically
* ANDed, we can use any one of them or any subset of them to filter out
* tuples, without having to evaluate the rest. The RestrictInfo node itself
* stores data used by the optimizer while choosing the best query plan.
*
* If a restriction clause references a single base relation, it will appear
* in the baserestrictinfo list of the RelOptInfo for that base rel.
*
* If a restriction clause references more than one base+OJ relation, it will
* appear in the joininfo list of every RelOptInfo that describes a strict
* subset of the relations mentioned in the clause. The joininfo lists are
* used to drive join tree building by selecting plausible join candidates.
* The clause cannot actually be applied until we have built a join rel
* containing all the relations it references, however.
*
* When we construct a join rel that includes all the relations referenced
* in a multi-relation restriction clause, we place that clause into the
* joinrestrictinfo lists of paths for the join rel, if neither left nor
* right sub-path includes all relations referenced in the clause. The clause
* will be applied at that join level, and will not propagate any further up
* the join tree. (Note: the "predicate migration" code was once intended to
* push restriction clauses up and down the plan tree based on evaluation
* costs, but it's dead code and is unlikely to be resurrected in the
* foreseeable future.)
*
* Note that in the presence of more than two rels, a multi-rel restriction
* might reach different heights in the join tree depending on the join
* sequence we use. So, these clauses cannot be associated directly with
* the join RelOptInfo, but must be kept track of on a per-join-path basis.
*
* RestrictInfos that represent equivalence conditions (i.e., mergejoinable
* equalities that are not outerjoin-delayed) are handled a bit differently.
* Initially we attach them to the EquivalenceClasses that are derived from
* them. When we construct a scan or join path, we look through all the
* EquivalenceClasses and generate derived RestrictInfos representing the
* minimal set of conditions that need to be checked for this particular scan
* or join to enforce that all members of each EquivalenceClass are in fact
* equal in all rows emitted by the scan or join.
*
* The clause_relids field lists the base plus outer-join RT indexes that
* actually appear in the clause. required_relids lists the minimum set of
* relids needed to evaluate the clause; while this is often equal to
* clause_relids, it can be more. We will add relids to required_relids when
* we need to force an outer join ON clause to be evaluated exactly at the
* level of the outer join, which is true except when it is a "degenerate"
* condition that references only Vars from the nullable side of the join.
*
* RestrictInfo nodes contain a flag to indicate whether a qual has been
* pushed down to a lower level than its original syntactic placement in the
* join tree would suggest. If an outer join prevents us from pushing a qual
* down to its "natural" semantic level (the level associated with just the
* base rels used in the qual) then we mark the qual with a "required_relids"
* value including more than just the base rels it actually uses. By
* pretending that the qual references all the rels required to form the outer
* join, we prevent it from being evaluated below the outer join's joinrel.
* When we do form the outer join's joinrel, we still need to distinguish
* those quals that are actually in that join's JOIN/ON condition from those
* that appeared elsewhere in the tree and were pushed down to the join rel
* because they used no other rels. That's what the is_pushed_down flag is
* for; it tells us that a qual is not an OUTER JOIN qual for the set of base
* rels listed in required_relids. A clause that originally came from WHERE
* or an INNER JOIN condition will *always* have its is_pushed_down flag set.
* It's possible for an OUTER JOIN clause to be marked is_pushed_down too,
* if we decide that it can be pushed down into the nullable side of the join.
* In that case it acts as a plain filter qual for wherever it gets evaluated.
* (In short, is_pushed_down is only false for non-degenerate outer join
* conditions. Possibly we should rename it to reflect that meaning? But
* see also the comments for RINFO_IS_PUSHED_DOWN, below.)
*
* There is also an incompatible_relids field, which is a set of outer-join
* relids above which we cannot evaluate the clause (because they might null
* Vars it uses that should not be nulled yet). In principle this could be
* filled in any RestrictInfo as the set of OJ relids that appear above the
* clause and null Vars that it uses. In practice we only bother to populate
* it for "clone" clauses, as it's currently only needed to prevent multiple
* clones of the same clause from being accepted for evaluation at the same
* join level.
*
* There is also an outer_relids field, which is NULL except for outer join
* clauses; for those, it is the set of relids on the outer side of the
* clause's outer join. (These are rels that the clause cannot be applied to
* in parameterized scans, since pushing it into the join's outer side would
* lead to wrong answers.)
*
* To handle security-barrier conditions efficiently, we mark RestrictInfo
* nodes with a security_level field, in which higher values identify clauses
* coming from less-trusted sources. The exact semantics are that a clause
* cannot be evaluated before another clause with a lower security_level value
* unless the first clause is leakproof. As with outer-join clauses, this
* creates a reason for clauses to sometimes need to be evaluated higher in
* the join tree than their contents would suggest; and even at a single plan
* node, this rule constrains the order of application of clauses.
*
* In general, the referenced clause might be arbitrarily complex. The
* kinds of clauses we can handle as indexscan quals, mergejoin clauses,
* or hashjoin clauses are limited (e.g., no volatile functions). The code
* for each kind of path is responsible for identifying the restrict clauses
* it can use and ignoring the rest. Clauses not implemented by an indexscan,
* mergejoin, or hashjoin will be placed in the plan qual or joinqual field
* of the finished Plan node, where they will be enforced by general-purpose
* qual-expression-evaluation code. (But we are still entitled to count
* their selectivity when estimating the result tuple count, if we
* can guess what it is...)
*
* When the referenced clause is an OR clause, we generate a modified copy
* in which additional RestrictInfo nodes are inserted below the top-level
* OR/AND structure. This is a convenience for OR indexscan processing:
* indexquals taken from either the top level or an OR subclause will have
* associated RestrictInfo nodes.
*
* The can_join flag is set true if the clause looks potentially useful as
* a merge or hash join clause, that is if it is a binary opclause with
* nonoverlapping sets of relids referenced in the left and right sides.
* (Whether the operator is actually merge or hash joinable isn't checked,
* however.)
*
* The pseudoconstant flag is set true if the clause contains no Vars of
* the current query level and no volatile functions. Such a clause can be
* pulled out and used as a one-time qual in a gating Result node. We keep
* pseudoconstant clauses in the same lists as other RestrictInfos so that
* the regular clause-pushing machinery can assign them to the correct join
* level, but they need to be treated specially for cost and selectivity
* estimates. Note that a pseudoconstant clause can never be an indexqual
* or merge or hash join clause, so it's of no interest to large parts of
* the planner.
*
* When we generate multiple versions of a clause so as to have versions
* that will work after commuting some left joins per outer join identity 3,
* we mark the one with the fewest nullingrels bits with has_clone = true,
* and the rest with is_clone = true. This allows proper filtering of
* these redundant clauses, so that we apply only one version of them.
*
* When join clauses are generated from EquivalenceClasses, there may be
* several equally valid ways to enforce join equivalence, of which we need
* apply only one. We mark clauses of this kind by setting parent_ec to
* point to the generating EquivalenceClass. Multiple clauses with the same
* parent_ec in the same join are redundant.
*
* Most fields are ignored for equality, since they may not be set yet, and
* should be derivable from the clause anyway.
*
* parent_ec, left_ec, right_ec are not printed, lest it lead to infinite
* recursion in plan tree dump.
*/
typedef struct RestrictInfo
{
pg_node_attr(no_read, no_query_jumble)
NodeTag type;
/* the represented clause of WHERE or JOIN */
Expr *clause;
/* true if clause was pushed down in level */
bool is_pushed_down;
/* see comment above */
bool can_join pg_node_attr(equal_ignore);
/* see comment above */
bool pseudoconstant pg_node_attr(equal_ignore);
/* see comment above */
bool has_clone;
bool is_clone;
/* true if known to contain no leaked Vars */
bool leakproof pg_node_attr(equal_ignore);
/* indicates if clause contains any volatile functions */
VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);
/* see comment above */
Index security_level;
/* number of base rels in clause_relids */
int num_base_rels pg_node_attr(equal_ignore);
/* The relids (varnos+varnullingrels) actually referenced in the clause: */
Relids clause_relids pg_node_attr(equal_ignore);
/* The set of relids required to evaluate the clause: */
Relids required_relids;
/* Relids above which we cannot evaluate the clause (see comment above) */
Relids incompatible_relids;
/* If an outer-join clause, the outer-side relations, else NULL: */
Relids outer_relids;
/*
* Relids in the left/right side of the clause. These fields are set for
* any binary opclause.
*/
Relids left_relids pg_node_attr(equal_ignore);
Relids right_relids pg_node_attr(equal_ignore);
/*
* Modified clause with RestrictInfos. This field is NULL unless clause
* is an OR clause.
*/
Expr *orclause pg_node_attr(equal_ignore);
/*----------
* Serial number of this RestrictInfo. This is unique within the current
* PlannerInfo context, with a few critical exceptions:
* 1. When we generate multiple clones of the same qual condition to
* cope with outer join identity 3, all the clones get the same serial
* number. This reflects that we only want to apply one of them in any
* given plan.
* 2. If we manufacture a commuted version of a qual to use as an index
* condition, it copies the original's rinfo_serial, since it is in
* practice the same condition.
* 3. If we reduce a qual to constant-FALSE, the new constant-FALSE qual
* copies the original's rinfo_serial, since it is in practice the same
* condition.
* 4. RestrictInfos made for a child relation copy their parent's
* rinfo_serial. Likewise, when an EquivalenceClass makes a derived
* equality clause for a child relation, it copies the rinfo_serial of
* the matching equality clause for the parent. This allows detection
* of redundant pushed-down equality clauses.
*----------
*/
int rinfo_serial;
/*
* Generating EquivalenceClass. This field is NULL unless clause is
* potentially redundant.
*/
EquivalenceClass *parent_ec pg_node_attr(copy_as_scalar, equal_ignore, read_write_ignore);
/*
* cache space for cost and selectivity
*/
/* eval cost of clause; -1 if not yet set */
QualCost eval_cost pg_node_attr(equal_ignore);
/* selectivity for "normal" (JOIN_INNER) semantics; -1 if not yet set */
Selectivity norm_selec pg_node_attr(equal_ignore);
/* selectivity for outer join semantics; -1 if not yet set */
Selectivity outer_selec pg_node_attr(equal_ignore);
/*
* opfamilies containing clause operator; valid if clause is
* mergejoinable, else NIL
*/
List *mergeopfamilies pg_node_attr(equal_ignore);
/*
* cache space for mergeclause processing; NULL if not yet set
*/
/* EquivalenceClass containing lefthand */
EquivalenceClass *left_ec pg_node_attr(copy_as_scalar, equal_ignore, read_write_ignore);
/* EquivalenceClass containing righthand */
EquivalenceClass *right_ec pg_node_attr(copy_as_scalar, equal_ignore, read_write_ignore);
/* EquivalenceMember for lefthand */
EquivalenceMember *left_em pg_node_attr(copy_as_scalar, equal_ignore);
/* EquivalenceMember for righthand */
EquivalenceMember *right_em pg_node_attr(copy_as_scalar, equal_ignore);
/*
* List of MergeScanSelCache structs. Those aren't Nodes, so hard to
* copy; instead replace with NIL. That has the effect that copying will
* just reset the cache. Likewise, can't compare or print them.
*/
List *scansel_cache pg_node_attr(copy_as(NIL), equal_ignore, read_write_ignore);
/*
* transient workspace for use while considering a specific join path; T =
* outer var on left, F = on right
*/
bool outer_is_left pg_node_attr(equal_ignore);
/*
* copy of clause operator; valid if clause is hashjoinable, else
* InvalidOid
*/
Oid hashjoinoperator pg_node_attr(equal_ignore);
/*
* cache space for hashclause processing; -1 if not yet set
*/
/* avg bucketsize of left side */
Selectivity left_bucketsize pg_node_attr(equal_ignore);
/* avg bucketsize of right side */
Selectivity right_bucketsize pg_node_attr(equal_ignore);
/* left side's most common val's freq */
Selectivity left_mcvfreq pg_node_attr(equal_ignore);
/* right side's most common val's freq */
Selectivity right_mcvfreq pg_node_attr(equal_ignore);
/* hash equality operators used for memoize nodes, else InvalidOid */
Oid left_hasheqoperator pg_node_attr(equal_ignore);
Oid right_hasheqoperator pg_node_attr(equal_ignore);
} RestrictInfo;
/*
* This macro embodies the correct way to test whether a RestrictInfo is
* "pushed down" to a given outer join, that is, should be treated as a filter
* clause rather than a join clause at that outer join. This is certainly so
* if is_pushed_down is true; but examining that is not sufficient anymore,
* because outer-join clauses will get pushed down to lower outer joins when
* we generate a path for the lower outer join that is parameterized by the
* LHS of the upper one. We can detect such a clause by noting that its
* required_relids exceed the scope of the join.
*/
#define RINFO_IS_PUSHED_DOWN(rinfo, joinrelids) \
((rinfo)->is_pushed_down || \
!bms_is_subset((rinfo)->required_relids, joinrelids))
/*
* Since mergejoinscansel() is a relatively expensive function, and would
* otherwise be invoked many times while planning a large join tree,
* we go out of our way to cache its results. Each mergejoinable
* RestrictInfo carries a list of the specific sort orderings that have
* been considered for use with it, and the resulting selectivities.
*/
typedef struct MergeScanSelCache
{
/* Ordering details (cache lookup key) */
Oid opfamily; /* index opfamily defining the ordering */
Oid collation; /* collation for the ordering */
CompareType cmptype; /* sort direction (ASC or DESC) */
bool nulls_first; /* do NULLs come before normal values? */
/* Results */
Selectivity leftstartsel; /* first-join fraction for clause left side */
Selectivity leftendsel; /* last-join fraction for clause left side */
Selectivity rightstartsel; /* first-join fraction for clause right side */
Selectivity rightendsel; /* last-join fraction for clause right side */
} MergeScanSelCache;
/*
* Placeholder node for an expression to be evaluated below the top level
* of a plan tree. This is used during planning to represent the contained
* expression. At the end of the planning process it is replaced by either
* the contained expression or a Var referring to a lower-level evaluation of
* the contained expression. Generally the evaluation occurs below an outer
* join, and Var references above the outer join might thereby yield NULL
* instead of the expression value.
*
* phrels and phlevelsup correspond to the varno/varlevelsup fields of a
* plain Var, except that phrels has to be a relid set since the evaluation
* level of a PlaceHolderVar might be a join rather than a base relation.
* Likewise, phnullingrels corresponds to varnullingrels.
*
* Although the planner treats this as an expression node type, it is not
* recognized by the parser or executor, so we declare it here rather than
* in primnodes.h.
*
* We intentionally do not compare phexpr. Two PlaceHolderVars with the
* same ID and levelsup should be considered equal even if the contained
* expressions have managed to mutate to different states. This will
* happen during final plan construction when there are nested PHVs, since
* the inner PHV will get replaced by a Param in some copies of the outer
* PHV. Another way in which it can happen is that initplan sublinks
* could get replaced by differently-numbered Params when sublink folding
* is done. (The end result of such a situation would be some
* unreferenced initplans, which is annoying but not really a problem.)
* On the same reasoning, there is no need to examine phrels. But we do
* need to compare phnullingrels, as that represents effects that are
* external to the original value of the PHV.
*/
typedef struct PlaceHolderVar
{
pg_node_attr(no_query_jumble)
Expr xpr;
/* the represented expression */
Expr *phexpr pg_node_attr(equal_ignore);
/* base+OJ relids syntactically within expr src */
Relids phrels pg_node_attr(equal_ignore);
/* RT indexes of outer joins that can null PHV's value */
Relids phnullingrels;
/* ID for PHV (unique within planner run) */
Index phid;
/* > 0 if PHV belongs to outer query */
Index phlevelsup;
} PlaceHolderVar;
/*
* "Special join" info.
*
* One-sided outer joins constrain the order of joining partially but not
* completely. We flatten such joins into the planner's top-level list of
* relations to join, but record information about each outer join in a
* SpecialJoinInfo struct. These structs are kept in the PlannerInfo node's
* join_info_list.
*
* Similarly, semijoins and antijoins created by flattening IN (subselect)
* and EXISTS(subselect) clauses create partial constraints on join order.
* These are likewise recorded in SpecialJoinInfo structs.
*
* We make SpecialJoinInfos for FULL JOINs even though there is no flexibility
* of planning for them, because this simplifies make_join_rel()'s API.
*
* min_lefthand and min_righthand are the sets of base+OJ relids that must be
* available on each side when performing the special join.
* It is not valid for either min_lefthand or min_righthand to be empty sets;
* if they were, this would break the logic that enforces join order.
*
* syn_lefthand and syn_righthand are the sets of base+OJ relids that are
* syntactically below this special join. (These are needed to help compute
* min_lefthand and min_righthand for higher joins.)
*
* jointype is never JOIN_RIGHT; a RIGHT JOIN is handled by switching
* the inputs to make it a LEFT JOIN. It's never JOIN_RIGHT_SEMI or
* JOIN_RIGHT_ANTI either. So the allowed values of jointype in a
* join_info_list member are only LEFT, FULL, SEMI, or ANTI.
*
* ojrelid is the RT index of the join RTE representing this outer join,
* if there is one. It is zero when jointype is INNER or SEMI, and can be
* zero for jointype ANTI (if the join was transformed from a SEMI join).
* One use for this field is that when constructing the output targetlist of a
* join relation that implements this OJ, we add ojrelid to the varnullingrels
* and phnullingrels fields of nullable (RHS) output columns, so that the
* output Vars and PlaceHolderVars correctly reflect the nulling that has
* potentially happened to them.
*
* commute_above_l is filled with the relids of syntactically-higher outer
* joins that have been found to commute with this one per outer join identity
* 3 (see optimizer/README), when this join is in the LHS of the upper join
* (so, this is the lower join in the first form of the identity).
*
* commute_above_r is filled with the relids of syntactically-higher outer
* joins that have been found to commute with this one per outer join identity
* 3, when this join is in the RHS of the upper join (so, this is the lower
* join in the second form of the identity).
*
* commute_below_l is filled with the relids of syntactically-lower outer
* joins that have been found to commute with this one per outer join identity
* 3 and are in the LHS of this join (so, this is the upper join in the first
* form of the identity).
*
* commute_below_r is filled with the relids of syntactically-lower outer
* joins that have been found to commute with this one per outer join identity
* 3 and are in the RHS of this join (so, this is the upper join in the second
* form of the identity).
*
* lhs_strict is true if the special join's condition cannot succeed when the
* LHS variables are all NULL (this means that an outer join can commute with
* upper-level outer joins even if it appears in their RHS). We don't bother
* to set lhs_strict for FULL JOINs, however.
*
* For a semijoin, we also extract the join operators and their RHS arguments
* and set semi_operators, semi_rhs_exprs, semi_can_btree, and semi_can_hash.
* This is done in support of possibly unique-ifying the RHS, so we don't
* bother unless at least one of semi_can_btree and semi_can_hash can be set
* true. (You might expect that this information would be computed during
* join planning; but it's helpful to have it available during planning of
* parameterized table scans, so we store it in the SpecialJoinInfo structs.)
*
* For purposes of join selectivity estimation, we create transient
* SpecialJoinInfo structures for regular inner joins; so it is possible
* to have jointype == JOIN_INNER in such a structure, even though this is
* not allowed within join_info_list. We also create transient
* SpecialJoinInfos with jointype == JOIN_INNER for outer joins, since for
* cost estimation purposes it is sometimes useful to know the join size under
* plain innerjoin semantics. Note that lhs_strict and the semi_xxx fields
* are not set meaningfully within such structs.
*
* We also create transient SpecialJoinInfos for child joins during
* partitionwise join planning, which are also not present in join_info_list.
*/
typedef struct SpecialJoinInfo
{
pg_node_attr(no_read, no_query_jumble)
NodeTag type;
Relids min_lefthand; /* base+OJ relids in minimum LHS for join */
Relids min_righthand; /* base+OJ relids in minimum RHS for join */
Relids syn_lefthand; /* base+OJ relids syntactically within LHS */
Relids syn_righthand; /* base+OJ relids syntactically within RHS */
JoinType jointype; /* always INNER, LEFT, FULL, SEMI, or ANTI */
Index ojrelid; /* outer join's RT index; 0 if none */
Relids commute_above_l; /* commuting OJs above this one, if LHS */
Relids commute_above_r; /* commuting OJs above this one, if RHS */
Relids commute_below_l; /* commuting OJs in this one's LHS */
Relids commute_below_r; /* commuting OJs in this one's RHS */
bool lhs_strict; /* joinclause is strict for some LHS rel */
/* Remaining fields are set only for JOIN_SEMI jointype: */
bool semi_can_btree; /* true if semi_operators are all btree */
bool semi_can_hash; /* true if semi_operators are all hash */
List *semi_operators; /* OIDs of equality join operators */
List *semi_rhs_exprs; /* righthand-side expressions of these ops */
} SpecialJoinInfo;
/*
* Transient outer-join clause info.
*
* We set aside every outer join ON clause that looks mergejoinable,
* and process it specially at the end of qual distribution.
*/
typedef struct OuterJoinClauseInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
RestrictInfo *rinfo; /* a mergejoinable outer-join clause */
SpecialJoinInfo *sjinfo; /* the outer join's SpecialJoinInfo */
} OuterJoinClauseInfo;
/*
* Append-relation info.
*
* When we expand an inheritable table or a UNION-ALL subselect into an
* "append relation" (essentially, a list of child RTEs), we build an
* AppendRelInfo for each child RTE. The list of AppendRelInfos indicates
* which child RTEs must be included when expanding the parent, and each node
* carries information needed to translate between columns of the parent and
* columns of the child.
*
* These structs are kept in the PlannerInfo node's append_rel_list, with
* append_rel_array[] providing a convenient lookup method for the struct
* associated with a particular child relid (there can be only one, though
* parent rels may have many entries in append_rel_list).
*
* Note: after completion of the planner prep phase, any given RTE is an
* append parent having entries in append_rel_list if and only if its
* "inh" flag is set. We clear "inh" for plain tables that turn out not
* to have inheritance children, and (in an abuse of the original meaning
* of the flag) we set "inh" for subquery RTEs that turn out to be
* flattenable UNION ALL queries. This lets us avoid useless searches
* of append_rel_list.
*
* Note: the data structure assumes that append-rel members are single
* baserels. This is OK for inheritance, but it prevents us from pulling
* up a UNION ALL member subquery if it contains a join. While that could
* be fixed with a more complex data structure, at present there's not much
* point because no improvement in the plan could result.
*/
typedef struct AppendRelInfo
{
pg_node_attr(no_query_jumble)
NodeTag type;
/*
* These fields uniquely identify this append relationship. There can be
* (in fact, always should be) multiple AppendRelInfos for the same
* parent_relid, but never more than one per child_relid, since a given
* RTE cannot be a child of more than one append parent.
*/
Index parent_relid; /* RT index of append parent rel */
Index child_relid; /* RT index of append child rel */
/*
* For an inheritance appendrel, the parent and child are both regular
* relations, and we store their rowtype OIDs here for use in translating
* whole-row Vars. For a UNION-ALL appendrel, the parent and child are
* both subqueries with no named rowtype, and we store InvalidOid here.
*/
Oid parent_reltype; /* OID of parent's composite type */
Oid child_reltype; /* OID of child's composite type */
/*
* The N'th element of this list is a Var or expression representing the
* child column corresponding to the N'th column of the parent. This is
* used to translate Vars referencing the parent rel into references to
* the child. A list element is NULL if it corresponds to a dropped
* column of the parent (this is only possible for inheritance cases, not
* UNION ALL). The list elements are always simple Vars for inheritance
* cases, but can be arbitrary expressions in UNION ALL cases.
*
* Notice we only store entries for user columns (attno > 0). Whole-row
* Vars are special-cased, and system columns (attno < 0) need no special
* translation since their attnos are the same for all tables.
*
* Caution: the Vars have varlevelsup = 0. Be careful to adjust as needed
* when copying into a subquery.
*/
List *translated_vars; /* Expressions in the child's Vars */
/*
* This array simplifies translations in the reverse direction, from
* child's column numbers to parent's. The entry at [ccolno - 1] is the
* 1-based parent column number for child column ccolno, or zero if that
* child column is dropped or doesn't exist in the parent.
*/
int num_child_cols; /* length of array */
AttrNumber *parent_colnos pg_node_attr(array_size(num_child_cols));
/*
* We store the parent table's OID here for inheritance, or InvalidOid for
* UNION ALL. This is only needed to help in generating error messages if
* an attempt is made to reference a dropped parent column.
*/
Oid parent_reloid; /* OID of parent relation */
} AppendRelInfo;
/*
* Information about a row-identity "resjunk" column in UPDATE/DELETE/MERGE.
*
* In partitioned UPDATE/DELETE/MERGE it's important for child partitions to
* share row-identity columns whenever possible, so as not to chew up too many
* targetlist columns. We use these structs to track which identity columns
* have been requested. In the finished plan, each of these will give rise
* to one resjunk entry in the targetlist of the ModifyTable's subplan node.
*
* All the Vars stored in RowIdentityVarInfos must have varno ROWID_VAR, for
* convenience of detecting duplicate requests. We'll replace that, in the
* final plan, with the varno of the generating rel.
*
* Outside this list, a Var with varno ROWID_VAR and varattno k is a reference
* to the k-th element of the row_identity_vars list (k counting from 1).
* We add such a reference to root->processed_tlist when creating the entry,
* and it propagates into the plan tree from there.
*/
typedef struct RowIdentityVarInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
Var *rowidvar; /* Var to be evaluated (but varno=ROWID_VAR) */
int32 rowidwidth; /* estimated average width */
char *rowidname; /* name of the resjunk column */
Relids rowidrels; /* RTE indexes of target rels using this */
} RowIdentityVarInfo;
/*
* One element of the list passed to query_is_distinct_for(). Each entry
* names a subquery output column that the caller needs to be distinct over,
* plus the upper-level equality operator and its input collation, so that
* the subquery's own DISTINCT/GROUP BY/set-op clauses can be compared for
* compatibility.
*/
typedef struct DistinctColInfo
{
int colno; /* subquery output column resno */
Oid opid; /* upper-level equality operator */
Oid collid; /* input collation of opid */
} DistinctColInfo;
/*
* For each distinct placeholder expression generated during planning, we
* store a PlaceHolderInfo node in the PlannerInfo node's placeholder_list.
* This stores info that is needed centrally rather than in each copy of the
* PlaceHolderVar. The phid fields identify which PlaceHolderInfo goes with
* each PlaceHolderVar. Note that phid is unique throughout a planner run,
* not just within a query level --- this is so that we need not reassign ID's
* when pulling a subquery into its parent.
*
* The idea is to evaluate the expression at (only) the ph_eval_at join level,
* then allow it to bubble up like a Var until the ph_needed join level.
* ph_needed has the same definition as attr_needed for a regular Var.
*
* The PlaceHolderVar's expression might contain LATERAL references to vars
* coming from outside its syntactic scope. If so, those rels are *not*
* included in ph_eval_at, but they are recorded in ph_lateral.
*
* Notice that when ph_eval_at is a join rather than a single baserel, the
* PlaceHolderInfo may create constraints on join order: the ph_eval_at join
* has to be formed below any outer joins that should null the PlaceHolderVar.
*
* We create a PlaceHolderInfo only after determining that the PlaceHolderVar
* is actually referenced in the plan tree, so that unreferenced placeholders
* don't result in unnecessary constraints on join order.
*/
typedef struct PlaceHolderInfo
{
pg_node_attr(no_read, no_query_jumble)
NodeTag type;
/* ID for PH (unique within planner run) */
Index phid;
/*
* copy of PlaceHolderVar tree (should be redundant for comparison, could
* be ignored)
*/
PlaceHolderVar *ph_var;
/* lowest level we can evaluate value at */
Relids ph_eval_at;
/* relids of contained lateral refs, if any */
Relids ph_lateral;
/* highest level the value is needed at */
Relids ph_needed;
/* estimated attribute width */
int32 ph_width;
} PlaceHolderInfo;
/*
* This struct describes one potentially index-optimizable MIN/MAX aggregate
* function. MinMaxAggPath contains a list of these, and if we accept that
* path, the list is stored into root->minmax_aggs for use during setrefs.c.
*/
typedef struct MinMaxAggInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/* pg_proc Oid of the aggregate */
Oid aggfnoid;
/* Oid of its sort operator */
Oid aggsortop;
/* expression we are aggregating on */
Expr *target;
/*
* modified "root" for planning the subquery; not printed, too large, not
* interesting enough
*/
PlannerInfo *subroot pg_node_attr(read_write_ignore);
/* access path for subquery */
Path *path;
/* estimated cost to fetch first row */
Cost pathcost;
/* param for subplan's output */
Param *param;
} MinMaxAggInfo;
/*
* For each distinct Aggref node that appears in the targetlist and HAVING
* clauses, we store an AggClauseInfo node in the PlannerInfo node's
* agg_clause_list. Each AggClauseInfo records the set of relations referenced
* by the aggregate expression. This information is used to determine how far
* the aggregate can be safely pushed down in the join tree.
*/
typedef struct AggClauseInfo
{
pg_node_attr(no_read, no_query_jumble)
NodeTag type;
/* the Aggref expr */
Aggref *aggref;
/* lowest level we can evaluate this aggregate at */
Relids agg_eval_at;
} AggClauseInfo;
/*
* For each grouping expression that appears in grouping clauses, we store a
* GroupingExprInfo node in the PlannerInfo node's group_expr_list. Each
* GroupingExprInfo records the expression being grouped on, its sortgroupref,
* and the EquivalenceClass it belongs to. This information is necessary to
* reproduce correct grouping semantics at different levels of the join tree.
*/
typedef struct GroupingExprInfo
{
pg_node_attr(no_read, no_query_jumble)
NodeTag type;
/* the represented expression */
Expr *expr;
/* the tleSortGroupRef of the corresponding SortGroupClause */
Index sortgroupref;
/* the equivalence class the expression belongs to */
EquivalenceClass *ec pg_node_attr(copy_as_scalar, equal_as_scalar);
} GroupingExprInfo;
/*
* At runtime, PARAM_EXEC slots are used to pass values around from one plan
* node to another. They can be used to pass values down into subqueries (for
* outer references in subqueries), or up out of subqueries (for the results
* of a subplan), or from a NestLoop plan node into its inner relation (when
* the inner scan is parameterized with values from the outer relation).
* The planner is responsible for assigning nonconflicting PARAM_EXEC IDs to
* the PARAM_EXEC Params it generates.
*
* Outer references are managed via root->plan_params, which is a list of
* PlannerParamItems. While planning a subquery, each parent query level's
* plan_params contains the values required from it by the current subquery.
* During create_plan(), we use plan_params to track values that must be
* passed from outer to inner sides of NestLoop plan nodes.
*
* The item a PlannerParamItem represents can be one of three kinds:
*
* A Var: the slot represents a variable of this level that must be passed
* down because subqueries have outer references to it, or must be passed
* from a NestLoop node to its inner scan. The varlevelsup value in the Var
* will always be zero.
*
* A PlaceHolderVar: this works much like the Var case, except that the
* entry is a PlaceHolderVar node with a contained expression. The PHV
* will have phlevelsup = 0, and the contained expression is adjusted
* to match in level.
*
* An Aggref (with an expression tree representing its argument): the slot
* represents an aggregate expression that is an outer reference for some
* subquery. The Aggref itself has agglevelsup = 0, and its argument tree
* is adjusted to match in level.
*
* Note: we detect duplicate Var and PlaceHolderVar parameters and coalesce
* them into one slot, but we do not bother to do that for Aggrefs.
* The scope of duplicate-elimination only extends across the set of
* parameters passed from one query level into a single subquery, or for
* nestloop parameters across the set of nestloop parameters used in a single
* query level. So there is no possibility of a PARAM_EXEC slot being used
* for conflicting purposes.
*
* In addition, PARAM_EXEC slots are assigned for Params representing outputs
* from subplans (values that are setParam items for those subplans). These
* IDs need not be tracked via PlannerParamItems, since we do not need any
* duplicate-elimination nor later processing of the represented expressions.
* Instead, we just record the assignment of the slot number by appending to
* root->glob->paramExecTypes.
*/
typedef struct PlannerParamItem
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
Node *item; /* the Var, PlaceHolderVar, or Aggref */
int paramId; /* its assigned PARAM_EXEC slot number */
} PlannerParamItem;
/*
* When making cost estimates for a SEMI/ANTI/inner_unique join, there are
* some correction factors that are needed in both nestloop and hash joins
* to account for the fact that the executor can stop scanning inner rows
* as soon as it finds a match to the current outer row. These numbers
* depend only on the selected outer and inner join relations, not on the
* particular paths used for them, so it's worthwhile to calculate them
* just once per relation pair not once per considered path. This struct
* is filled by compute_semi_anti_join_factors and must be passed along
* to the join cost estimation functions.
*
* outer_match_frac is the fraction of the outer tuples that are
* expected to have at least one match.
* match_count is the average number of matches expected for
* outer tuples that have at least one match.
*/
typedef struct SemiAntiJoinFactors
{
Selectivity outer_match_frac;
Selectivity match_count;
} SemiAntiJoinFactors;
/*
* Struct for extra information passed to subroutines of add_paths_to_joinrel
*
* restrictlist contains all of the RestrictInfo nodes for restriction
* clauses that apply to this join
* mergeclause_list is a list of RestrictInfo nodes for available
* mergejoin clauses in this join
* inner_unique is true if each outer tuple provably matches no more
* than one inner tuple
* sjinfo is extra info about special joins for selectivity estimation
* semifactors is as shown above (only valid for SEMI/ANTI/inner_unique joins)
* param_source_rels are OK targets for parameterization of result paths
* pgs_mask is a bitmask of PGS_* constants to limit the join strategy
*/
typedef struct JoinPathExtraData
{
List *restrictlist;
List *mergeclause_list;
bool inner_unique;
SpecialJoinInfo *sjinfo;
SemiAntiJoinFactors semifactors;
Relids param_source_rels;
uint64 pgs_mask;
} JoinPathExtraData;
/*
* Various flags indicating what kinds of grouping are possible.
*
* GROUPING_CAN_USE_SORT should be set if it's possible to perform
* sort-based implementations of grouping. When grouping sets are in use,
* this will be true if sorting is potentially usable for any of the grouping
* sets, even if it's not usable for all of them.
*
* GROUPING_CAN_USE_HASH should be set if it's possible to perform
* hash-based implementations of grouping.
*
* GROUPING_CAN_PARTIAL_AGG should be set if the aggregation is of a type
* for which we support partial aggregation (not, for example, grouping sets).
* It says nothing about parallel-safety or the availability of suitable paths.
*/
#define GROUPING_CAN_USE_SORT 0x0001
#define GROUPING_CAN_USE_HASH 0x0002
#define GROUPING_CAN_PARTIAL_AGG 0x0004
/*
* What kind of partitionwise aggregation is in use?
*
* PARTITIONWISE_AGGREGATE_NONE: Not used.
*
* PARTITIONWISE_AGGREGATE_FULL: Aggregate each partition separately, and
* append the results.
*
* PARTITIONWISE_AGGREGATE_PARTIAL: Partially aggregate each partition
* separately, append the results, and then finalize aggregation.
*/
typedef enum
{
PARTITIONWISE_AGGREGATE_NONE,
PARTITIONWISE_AGGREGATE_FULL,
PARTITIONWISE_AGGREGATE_PARTIAL,
} PartitionwiseAggregateType;
/*
* Struct for extra information passed to subroutines of create_grouping_paths
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
* have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
* havingQual gives list of quals to be applied after aggregation.
* targetList gives list of columns to be projected.
* patype is the type of partitionwise aggregation that is being performed.
*/
typedef struct
{
/* Data which remains constant once set. */
int flags;
bool partial_costs_set;
AggClauseCosts agg_partial_costs;
AggClauseCosts agg_final_costs;
/* Data which may differ across partitions. */
bool target_parallel_safe;
Node *havingQual;
List *targetList;
PartitionwiseAggregateType patype;
} GroupPathExtraData;
/*
* Struct for extra information passed to subroutines of grouping_planner
*
* limit_needed is true if we actually need a Limit plan node.
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
* expressions computed by preprocess_limit() (see comments for
* preprocess_limit() for more information).
*/
typedef struct
{
bool limit_needed;
Cardinality limit_tuples;
int64 count_est;
int64 offset_est;
} FinalPathExtraData;
/*
* For speed reasons, cost estimation for join paths is performed in two
* phases: the first phase tries to quickly derive a lower bound for the
* join cost, and then we check if that's sufficient to reject the path.
* If not, we come back for a more refined cost estimate. The first phase
* fills a JoinCostWorkspace struct with its preliminary cost estimates
* and possibly additional intermediate values. The second phase takes
* these values as inputs to avoid repeating work.
*
* (Ideally we'd declare this in cost.h, but it's also needed in pathnode.h,
* so seems best to put it here.)
*/
typedef struct JoinCostWorkspace
{
/* Preliminary cost estimates --- must not be larger than final ones! */
int disabled_nodes;
Cost startup_cost; /* cost expended before fetching any tuples */
Cost total_cost; /* total cost (assuming all tuples fetched) */
/* Fields below here should be treated as private to costsize.c */
Cost run_cost; /* non-startup cost components */
/* private for cost_nestloop code */
Cost inner_run_cost; /* also used by cost_mergejoin code */
Cost inner_rescan_run_cost;
/* private for cost_mergejoin code */
Cardinality outer_rows;
Cardinality inner_rows;
Cardinality outer_skip_rows;
Cardinality inner_skip_rows;
/* private for cost_hashjoin code */
int numbuckets;
int numbatches;
Cardinality inner_rows_total;
} JoinCostWorkspace;
/*
* AggInfo holds information about an aggregate that needs to be computed.
* Multiple Aggrefs in a query can refer to the same AggInfo by having the
* same 'aggno' value, so that the aggregate is computed only once.
*/
typedef struct AggInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/*
* List of Aggref exprs that this state value is for.
*
* There will always be at least one, but there can be multiple identical
* Aggref's sharing the same per-agg.
*/
List *aggrefs;
/* Transition state number for this aggregate */
int transno;
/*
* "shareable" is false if this agg cannot share state values with other
* aggregates because the final function is read-write.
*/
bool shareable;
/* Oid of the final function, or InvalidOid if none */
Oid finalfn_oid;
} AggInfo;
/*
* AggTransInfo holds information about transition state that is used by one
* or more aggregates in the query. Multiple aggregates can share the same
* transition state, if they have the same inputs and the same transition
* function. Aggrefs that share the same transition info have the same
* 'aggtransno' value.
*/
typedef struct AggTransInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/* Inputs for this transition state */
List *args;
Expr *aggfilter;
/* Oid of the state transition function */
Oid transfn_oid;
/* Oid of the serialization function, or InvalidOid if none */
Oid serialfn_oid;
/* Oid of the deserialization function, or InvalidOid if none */
Oid deserialfn_oid;
/* Oid of the combine function, or InvalidOid if none */
Oid combinefn_oid;
/* Oid of state value's datatype */
Oid aggtranstype;
/* Additional data about transtype */
int32 aggtranstypmod;
int transtypeLen;
bool transtypeByVal;
/* Space-consumption estimate */
int32 aggtransspace;
/* Initial value from pg_aggregate entry */
Datum initValue pg_node_attr(read_write_ignore);
bool initValueIsNull;
} AggTransInfo;
/*
* UniqueRelInfo caches a fact that a relation is unique when being joined
* to other relation(s).
*/
typedef struct UniqueRelInfo
{
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
NodeTag type;
/*
* The relation in consideration is unique when being joined with this set
* of other relation(s).
*/
Relids outerrelids;
/*
* The relation in consideration is unique when considering only clauses
* suitable for self-join (passed split_selfjoin_quals()).
*/
bool self_join;
/*
* Additional clauses from a baserestrictinfo list that were used to prove
* the uniqueness. We cache it for the self-join checking procedure: a
* self-join can be removed if the outer relation contains strictly the
* same set of clauses.
*/
List *extra_clauses;
} UniqueRelInfo;
#endif /* PATHNODES_H */
./plancat.c 0000664 0001750 0001750 00000255132 15221505613 011503 0 ustar xman xman /*-------------------------------------------------------------------------
*
* plancat.c
* routines for accessing the system catalogs
*
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/optimizer/util/plancat.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <math.h>
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_ext_data.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/plancat.h"
#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "partitioning/partdesc.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "statistics/statistics.h"
#include "storage/bufmgr.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
/* GUC parameter */
int constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
typedef struct NotnullHashEntry
{
Oid relid; /* OID of the relation */
Bitmapset *notnullattnums; /* attnums of NOT NULL columns */
} NotnullHashEntry;
static void get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
Relation relation, bool inhparent);
static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
List *idxExprs);
static List *get_relation_constraints(PlannerInfo *root,
Oid relationObjectId, RelOptInfo *rel,
bool include_noinherit,
bool include_notnull,
bool include_partition);
static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
Relation heapRelation);
static List *get_relation_statistics(PlannerInfo *root, RelOptInfo *rel,
Relation relation);
static void set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
Relation relation);
static PartitionScheme find_partition_scheme(PlannerInfo *root,
Relation relation);
static void set_baserel_partition_key_exprs(Relation relation,
RelOptInfo *rel);
static void set_baserel_partition_constraint(Relation relation,
RelOptInfo *rel);
/*
* get_relation_info -
* Retrieves catalog information for a given relation.
*
* Given the Oid of the relation, return the following info into fields
* of the RelOptInfo struct:
*
* min_attr lowest valid AttrNumber
* max_attr highest valid AttrNumber
* indexlist list of IndexOptInfos for relation's indexes
* statlist list of StatisticExtInfo for relation's statistic objects
* serverid if it's a foreign table, the server OID
* fdwroutine if it's a foreign table, the FDW function pointers
* pages number of pages
* tuples number of tuples
* rel_parallel_workers user-defined number of parallel workers
*
* Also, add information about the relation's foreign keys to root->fkey_list.
*
* Also, initialize the attr_needed[] and attr_widths[] arrays. In most
* cases these are left as zeroes, but sometimes we need to compute attr
* widths here, and we may as well cache the results for costsize.c.
*
* If inhparent is true, all we need to do is set up the attr arrays:
* the RelOptInfo actually represents the appendrel formed by an inheritance
* tree, and so the parent rel's physical size and index information isn't
* important for it, however, for partitioned tables, we do populate the
* indexlist as the planner uses unique indexes as unique proofs for certain
* optimizations.
*/
void
get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
RelOptInfo *rel)
{
Index varno = rel->relid;
Relation relation;
bool hasindex;
List *indexinfos = NIL;
/*
* We need not lock the relation since it was already locked, either by
* the rewriter or when expand_inherited_rtentry() added it to the query's
* rangetable.
*/
relation = table_open(relationObjectId, NoLock);
/*
* Relations without a table AM can be used in a query only if they are of
* special-cased relkinds. This check prevents us from crashing later if,
* for example, a view's ON SELECT rule has gone missing. Note that
* table_open() already rejected indexes and composite types; spell the
* error the same way it does.
*/
if (!relation->rd_tableam)
{
if (!(relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot open relation \"%s\"",
RelationGetRelationName(relation)),
errdetail_relkind_not_supported(relation->rd_rel->relkind)));
}
/* Temporary and unlogged relations are inaccessible during recovery. */
if (!RelationIsPermanent(relation) && RecoveryInProgress())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot access temporary or unlogged relations during recovery")));
rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1;
rel->max_attr = RelationGetNumberOfAttributes(relation);
rel->reltablespace = RelationGetForm(relation)->reltablespace;
Assert(rel->max_attr >= rel->min_attr);
rel->attr_needed = (Relids *)
palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
rel->attr_widths = (int32 *)
palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32));
/*
* Record which columns are defined as NOT NULL. We leave this
* unpopulated for non-partitioned inheritance parent relations as it's
* ambiguous as to what it means. Some child tables may have a NOT NULL
* constraint for a column while others may not. We could work harder and
* build a unioned set of all child relations notnullattnums, but there's
* currently no need. The RelOptInfo corresponding to the !inh
* RangeTblEntry does get populated.
*/
if (!inhparent || relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
rel->notnullattnums = find_relation_notnullatts(root, relationObjectId);
/*
* Estimate relation size --- unless it's an inheritance parent, in which
* case the size we want is not the rel's own size but the size of its
* inheritance tree. That will be computed in set_append_rel_size().
*/
if (!inhparent)
estimate_rel_size(relation, rel->attr_widths - rel->min_attr,
&rel->pages, &rel->tuples, &rel->allvisfrac);
/* Retrieve the parallel_workers reloption, or -1 if not set. */
rel->rel_parallel_workers = RelationGetParallelWorkers(relation, -1);
/*
* Make list of indexes. Ignore indexes on system catalogs if told to.
* Don't bother with indexes from traditional inheritance parents. For
* partitioned tables, we need a list of at least unique indexes as these
* serve as unique proofs for certain planner optimizations. However,
* let's not discriminate here and just record all partitioned indexes
* whether they're unique indexes or not.
*/
if ((inhparent && relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|| (IgnoreSystemIndexes && IsSystemRelation(relation)))
hasindex = false;
else
hasindex = relation->rd_rel->relhasindex;
if (hasindex)
{
List *indexoidlist;
LOCKMODE lmode;
ListCell *l;
indexoidlist = RelationGetIndexList(relation);
/*
* For each index, we get the same type of lock that the executor will
* need, and do not release it. This saves a couple of trips to the
* shared lock manager while not creating any real loss of
* concurrency, because no schema changes could be happening on the
* index while we hold lock on the parent rel, and no lock type used
* for queries blocks any other kind of index operation.
*/
lmode = root->simple_rte_array[varno]->rellockmode;
foreach(l, indexoidlist)
{
Oid indexoid = lfirst_oid(l);
Relation indexRelation;
Form_pg_index index;
const IndexAmRoutine *amroutine = NULL;
IndexOptInfo *info;
int ncolumns,
nkeycolumns;
int i;
/*
* Extract info from the relation descriptor for the index.
*/
indexRelation = index_open(indexoid, lmode);
index = indexRelation->rd_index;
/*
* Ignore invalid indexes, since they can't safely be used for
* queries. Note that this is OK because the data structure we
* are constructing is only used by the planner --- the executor
* still needs to insert into "invalid" indexes, if they're marked
* indisready.
*/
if (!index->indisvalid)
{
index_close(indexRelation, NoLock);
continue;
}
/*
* If the index is valid, but cannot yet be used, ignore it; but
* mark the plan we are generating as transient. See
* src/backend/access/heap/README.HOT for discussion.
*/
if (index->indcheckxmin &&
!TransactionIdPrecedes(HeapTupleHeaderGetXmin(indexRelation->rd_indextuple->t_data),
TransactionXmin))
{
root->glob->transientPlan = true;
index_close(indexRelation, NoLock);
continue;
}
info = makeNode(IndexOptInfo);
info->indexoid = index->indexrelid;
info->reltablespace =
RelationGetForm(indexRelation)->reltablespace;
info->rel = rel;
info->ncolumns = ncolumns = index->indnatts;
info->nkeycolumns = nkeycolumns = index->indnkeyatts;
info->indexkeys = palloc_array(int, ncolumns);
info->indexcollations = palloc_array(Oid, nkeycolumns);
info->opfamily = palloc_array(Oid, nkeycolumns);
info->opcintype = palloc_array(Oid, nkeycolumns);
info->canreturn = palloc_array(bool, ncolumns);
for (i = 0; i < ncolumns; i++)
{
info->indexkeys[i] = index->indkey.values[i];
info->canreturn[i] = index_can_return(indexRelation, i + 1);
}
for (i = 0; i < nkeycolumns; i++)
{
info->opfamily[i] = indexRelation->rd_opfamily[i];
info->opcintype[i] = indexRelation->rd_opcintype[i];
info->indexcollations[i] = indexRelation->rd_indcollation[i];
}
info->relam = indexRelation->rd_rel->relam;
/*
* We don't have an AM for partitioned indexes, so we'll just
* NULLify the AM related fields for those.
*/
if (indexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
{
/* We copy just the fields we need, not all of rd_indam */
amroutine = indexRelation->rd_indam;
info->amcanorderbyop = amroutine->amcanorderbyop;
info->amoptionalkey = amroutine->amoptionalkey;
info->amsearcharray = amroutine->amsearcharray;
info->amsearchnulls = amroutine->amsearchnulls;
info->amcanparallel = amroutine->amcanparallel;
info->amhasgettuple = (amroutine->amgettuple != NULL);
info->amhasgetbitmap = amroutine->amgetbitmap != NULL &&
relation->rd_tableam->scan_bitmap_next_tuple != NULL;
info->amcanmarkpos = (amroutine->ammarkpos != NULL &&
amroutine->amrestrpos != NULL);
info->amcostestimate = amroutine->amcostestimate;
Assert(info->amcostestimate != NULL);
/* Fetch index opclass options */
info->opclassoptions = RelationGetIndexAttOptions(indexRelation, true);
/*
* Fetch the ordering information for the index, if any.
*/
if (info->relam == BTREE_AM_OID)
{
/*
* If it's a btree index, we can use its opfamily OIDs
* directly as the sort ordering opfamily OIDs.
*/
Assert(amroutine->amcanorder);
info->sortopfamily = info->opfamily;
info->reverse_sort = palloc_array(bool, nkeycolumns);
info->nulls_first = palloc_array(bool, nkeycolumns);
for (i = 0; i < nkeycolumns; i++)
{
int16 opt = indexRelation->rd_indoption[i];
info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0;
info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0;
}
}
else if (amroutine->amcanorder)
{
/*
* Otherwise, identify the corresponding btree opfamilies
* by trying to map this index's "<" operators into btree.
* Since "<" uniquely defines the behavior of a sort
* order, this is a sufficient test.
*
* XXX This method is rather slow and complicated. It'd
* be better to have a way to explicitly declare the
* corresponding btree opfamily for each opfamily of the
* other index type.
*/
info->sortopfamily = palloc_array(Oid, nkeycolumns);
info->reverse_sort = palloc_array(bool, nkeycolumns);
info->nulls_first = palloc_array(bool, nkeycolumns);
for (i = 0; i < nkeycolumns; i++)
{
int16 opt = indexRelation->rd_indoption[i];
Oid ltopr;
Oid opfamily;
Oid opcintype;
CompareType cmptype;
info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0;
info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0;
ltopr = get_opfamily_member_for_cmptype(info->opfamily[i],
info->opcintype[i],
info->opcintype[i],
COMPARE_LT);
if (OidIsValid(ltopr) &&
get_ordering_op_properties(ltopr,
&opfamily,
&opcintype,
&cmptype) &&
opcintype == info->opcintype[i] &&
cmptype == COMPARE_LT)
{
/* Successful mapping */
info->sortopfamily[i] = opfamily;
}
else
{
/* Fail ... quietly treat index as unordered */
info->sortopfamily = NULL;
info->reverse_sort = NULL;
info->nulls_first = NULL;
break;
}
}
}
else
{
info->sortopfamily = NULL;
info->reverse_sort = NULL;
info->nulls_first = NULL;
}
}
else
{
info->amcanorderbyop = false;
info->amoptionalkey = false;
info->amsearcharray = false;
info->amsearchnulls = false;
info->amcanparallel = false;
info->amhasgettuple = false;
info->amhasgetbitmap = false;
info->amcanmarkpos = false;
info->amcostestimate = NULL;
info->sortopfamily = NULL;
info->reverse_sort = NULL;
info->nulls_first = NULL;
}
/*
* Fetch the index expressions and predicate, if any. We must
* modify the copies we obtain from the relcache to have the
* correct varno for the parent relation, so that they match up
* correctly against qual clauses.
*
* After fixing the varnos, we need to run the index expressions
* and predicate through const-simplification again, using a valid
* "root". This ensures that NullTest quals for Vars can be
* properly reduced.
*/
info->indexprs = RelationGetIndexExpressions(indexRelation);
info->indexprsExpand = RelationGetIndexExpressionsExpand(indexRelation);
info->indpred = RelationGetIndexPredicate(indexRelation);
info->indpredExpand = RelationGetIndexPredicateExpand(indexRelation);
if (info->indexprs)
{
if (varno != 1)
{
ChangeVarNodes((Node *) info->indexprs, 1, varno, 0);
ChangeVarNodes((Node *) info->indexprsExpand, 1, varno, 0);
}
info->indexprs = (List *)
eval_const_expressions(root, (Node *) info->indexprs);
info->indexprsExpand = (List *)
eval_const_expressions(root, (Node *) info->indexprsExpand);
}
if (info->indpred)
{
if (varno != 1)
{
ChangeVarNodes((Node *) info->indpred, 1, varno, 0);
ChangeVarNodes((Node *) info->indpredExpand, 1, varno, 0);
}
info->indpred = (List *)
eval_const_expressions(root,
(Node *) make_ands_explicit(info->indpred));
info->indpredExpand = (List *)
eval_const_expressions(root,
(Node *) make_ands_explicit(info->indpredExpand));
info->indpred = make_ands_implicit((Expr *) info->indpred);
info->indpredExpand = make_ands_implicit((Expr *) info->indpredExpand);
}
/* Build targetlist using the completed indexprs data */
info->indextlist = build_index_tlist(root, info, relation);
info->indrestrictinfo = NIL; /* set later, in indxpath.c */
info->predOK = false; /* set later, in indxpath.c */
info->unique = index->indisunique;
info->nullsnotdistinct = index->indnullsnotdistinct;
info->immediate = index->indimmediate;
info->hypothetical = false;
/*
* Estimate the index size. If it's not a partial index, we lock
* the number-of-tuples estimate to equal the parent table; if it
* is partial then we have to use the same methods as we would for
* a table, except we can be sure that the index is not larger
* than the table. We must ignore partitioned indexes here as
* there are not physical indexes.
*/
if (indexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
{
if (info->indpred == NIL)
{
info->pages = RelationGetNumberOfBlocks(indexRelation);
info->tuples = rel->tuples;
}
else
{
double allvisfrac; /* dummy */
estimate_rel_size(indexRelation, NULL,
&info->pages, &info->tuples, &allvisfrac);
if (info->tuples > rel->tuples)
info->tuples = rel->tuples;
}
/*
* Get tree height while we have the index open
*/
if (amroutine->amgettreeheight)
{
info->tree_height = amroutine->amgettreeheight(indexRelation);
}
else
{
/* For other index types, just set it to "unknown" for now */
info->tree_height = -1;
}
}
else
{
/* Zero these out for partitioned indexes */
info->pages = 0;
info->tuples = 0.0;
info->tree_height = -1;
}
index_close(indexRelation, NoLock);
/*
* We've historically used lcons() here. It'd make more sense to
* use lappend(), but that causes the planner to change behavior
* in cases where two indexes seem equally attractive. For now,
* stick with lcons() --- few tables should have so many indexes
* that the O(N^2) behavior of lcons() is really a problem.
*/
indexinfos = lcons(info, indexinfos);
}
list_free(indexoidlist);
}
rel->indexlist = indexinfos;
rel->statlist = get_relation_statistics(root, rel, relation);
/* Grab foreign-table info using the relcache, while we have it */
if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
{
/* Check if the access to foreign tables is restricted */
if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
{
/* there must not be built-in foreign tables */
Assert(RelationGetRelid(relation) >= FirstNormalObjectId);
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("access to non-system foreign table is restricted")));
}
rel->serverid = GetForeignServerIdByRelId(RelationGetRelid(relation));
rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
}
else
{
rel->serverid = InvalidOid;
rel->fdwroutine = NULL;
}
/* Collect info about relation's foreign keys, if relevant */
get_relation_foreign_keys(root, rel, relation, inhparent);
/* Collect info about functions implemented by the rel's table AM. */
if (relation->rd_tableam &&
relation->rd_tableam->scan_set_tidrange != NULL &&
relation->rd_tableam->scan_getnextslot_tidrange != NULL)
rel->amflags |= AMFLAG_HAS_TID_RANGE;
/*
* Collect info about relation's partitioning scheme, if any. Only
* inheritance parents may be partitioned.
*/
if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
set_relation_partition_info(root, rel, relation);
table_close(relation, NoLock);
}
/*
* get_relation_foreign_keys -
* Retrieves foreign key information for a given relation.
*
* ForeignKeyOptInfos for relevant foreign keys are created and added to
* root->fkey_list. We do this now while we have the relcache entry open.
* We could sometimes avoid making useless ForeignKeyOptInfos if we waited
* until all RelOptInfos have been built, but the cost of re-opening the
* relcache entries would probably exceed any savings.
*/
static void
get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
Relation relation, bool inhparent)
{
List *rtable = root->parse->rtable;
List *cachedfkeys;
ListCell *lc;
/*
* If it's not a baserel, we don't care about its FKs. Also, if the query
* references only a single relation, we can skip the lookup since no FKs
* could satisfy the requirements below.
*/
if (rel->reloptkind != RELOPT_BASEREL ||
list_length(rtable) < 2)
return;
/*
* If it's the parent of an inheritance tree, ignore its FKs. We could
* make useful FK-based deductions if we found that all members of the
* inheritance tree have equivalent FK constraints, but detecting that
* would require code that hasn't been written.
*/
if (inhparent)
return;
/*
* Extract data about relation's FKs from the relcache. Note that this
* list belongs to the relcache and might disappear in a cache flush, so
* we must not do any further catalog access within this function.
*/
cachedfkeys = RelationGetFKeyList(relation);
/*
* Figure out which FKs are of interest for this query, and create
* ForeignKeyOptInfos for them. We want only FKs that reference some
* other RTE of the current query. In queries containing self-joins,
* there might be more than one other RTE for a referenced table, and we
* should make a ForeignKeyOptInfo for each occurrence.
*
* Ideally, we would ignore RTEs that correspond to non-baserels, but it's
* too hard to identify those here, so we might end up making some useless
* ForeignKeyOptInfos. If so, match_foreign_keys_to_quals() will remove
* them again.
*/
foreach(lc, cachedfkeys)
{
ForeignKeyCacheInfo *cachedfk = (ForeignKeyCacheInfo *) lfirst(lc);
Index rti;
ListCell *lc2;
/* conrelid should always be that of the table we're considering */
Assert(cachedfk->conrelid == RelationGetRelid(relation));
/* skip constraints currently not enforced */
if (!cachedfk->conenforced)
continue;
/* Scan to find other RTEs matching confrelid */
rti = 0;
foreach(lc2, rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
ForeignKeyOptInfo *info;
rti++;
/* Ignore if not the correct table */
if (rte->rtekind != RTE_RELATION ||
rte->relid != cachedfk->confrelid)
continue;
/* Ignore if it's an inheritance parent; doesn't really match */
if (rte->inh)
continue;
/* Ignore self-referential FKs; we only care about joins */
if (rti == rel->relid)
continue;
/* OK, let's make an entry */
info = makeNode(ForeignKeyOptInfo);
info->con_relid = rel->relid;
info->ref_relid = rti;
info->nkeys = cachedfk->nkeys;
memcpy(info->conkey, cachedfk->conkey, sizeof(info->conkey));
memcpy(info->confkey, cachedfk->confkey, sizeof(info->confkey));
memcpy(info->conpfeqop, cachedfk->conpfeqop, sizeof(info->conpfeqop));
/* zero out fields to be filled by match_foreign_keys_to_quals */
info->nmatched_ec = 0;
info->nconst_ec = 0;
info->nmatched_rcols = 0;
info->nmatched_ri = 0;
memset(info->eclass, 0, sizeof(info->eclass));
memset(info->fk_eclass_member, 0, sizeof(info->fk_eclass_member));
memset(info->rinfos, 0, sizeof(info->rinfos));
root->fkey_list = lappend(root->fkey_list, info);
}
}
}
/*
* get_relation_notnullatts -
* Retrieves column not-null constraint information for a given relation.
*
* We do this while we have the relcache entry open, and store the column
* not-null constraint information in a hash table based on the relation OID.
*/
void
get_relation_notnullatts(PlannerInfo *root, Relation relation)
{
Oid relid = RelationGetRelid(relation);
NotnullHashEntry *hentry;
bool found;
Bitmapset *notnullattnums = NULL;
/* bail out if the relation has no not-null constraints */
if (relation->rd_att->constr == NULL ||
!relation->rd_att->constr->has_not_null)
return;
/* create the hash table if it hasn't been created yet */
if (root->glob->rel_notnullatts_hash == NULL)
{
HTAB *hashtab;
HASHCTL hash_ctl;
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(NotnullHashEntry);
hash_ctl.hcxt = CurrentMemoryContext;
hashtab = hash_create("Relation NOT NULL attnums",
64L, /* arbitrary initial size */
&hash_ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
root->glob->rel_notnullatts_hash = hashtab;
}
/*
* Create a hash entry for this relation OID, if we don't have one
* already.
*/
hentry = (NotnullHashEntry *) hash_search(root->glob->rel_notnullatts_hash,
&relid,
HASH_ENTER,
&found);
/* bail out if a hash entry already exists for this relation OID */
if (found)
return;
/* collect the column not-null constraint information for this relation */
for (int i = 0; i < relation->rd_att->natts; i++)
{
CompactAttribute *attr = TupleDescCompactAttr(relation->rd_att, i);
Assert(attr->attnullability != ATTNULLABLE_UNKNOWN);
if (attr->attnullability == ATTNULLABLE_VALID)
{
notnullattnums = bms_add_member(notnullattnums, i + 1);
/*
* Per RemoveAttributeById(), dropped columns will have their
* attnotnull unset, so we needn't check for dropped columns in
* the above condition.
*/
Assert(!attr->attisdropped);
}
}
/* ... and initialize the new hash entry */
hentry->notnullattnums = notnullattnums;
}
/*
* find_relation_notnullatts -
* Searches the hash table and returns the column not-null constraint
* information for a given relation.
*/
Bitmapset *
find_relation_notnullatts(PlannerInfo *root, Oid relid)
{
NotnullHashEntry *hentry;
bool found;
if (root->glob->rel_notnullatts_hash == NULL)
return NULL;
hentry = (NotnullHashEntry *) hash_search(root->glob->rel_notnullatts_hash,
&relid,
HASH_FIND,
&found);
if (!found)
return NULL;
return hentry->notnullattnums;
}
/*
* infer_arbiter_indexes -
* Determine the unique indexes used to arbitrate speculative insertion.
*
* Uses user-supplied inference clause expressions and predicate to match a
* unique index from those defined and ready on the heap relation (target).
* An exact match is required on columns/expressions (although they can appear
* in any order). However, the predicate given by the user need only restrict
* insertion to a subset of some part of the table covered by some particular
* unique index (in particular, a partial unique index) in order to be
* inferred.
*
* The implementation does not consider which B-Tree operator class any
* particular available unique index attribute uses, unless one was specified
* in the inference specification. The same is true of collations. In
* particular, there is no system dependency on the default operator class for
* the purposes of inference. If no opclass (or collation) is specified, then
* all matching indexes (that may or may not match the default in terms of
* each attribute opclass/collation) are used for inference.
*/
List *
infer_arbiter_indexes(PlannerInfo *root)
{
OnConflictExpr *onconflict = root->parse->onConflict;
/* Iteration state */
Index varno;
RangeTblEntry *rte;
Relation relation;
Oid indexOidFromConstraint = InvalidOid;
List *indexList;
List *indexRelList = NIL;
/*
* Required attributes and expressions used to match indexes to the clause
* given by the user. In the ON CONFLICT ON CONSTRAINT case, we compute
* these from that constraint's index to match all other indexes, to
* account for the case where that index is being concurrently reindexed.
*/
List *inferIndexExprs = (List *) onconflict->arbiterWhere;
Bitmapset *inferAttrs = NULL;
List *inferElems = NIL;
/* Results */
List *results = NIL;
bool foundValid = false;
/*
* Quickly return NIL for ON CONFLICT DO NOTHING without an inference
* specification or named constraint. ON CONFLICT DO SELECT/UPDATE
* statements must always provide one or the other (but parser ought to
* have caught that already).
*/
if (onconflict->arbiterElems == NIL &&
onconflict->constraint == InvalidOid)
return NIL;
/*
* We need not lock the relation since it was already locked, either by
* the rewriter or when expand_inherited_rtentry() added it to the query's
* rangetable.
*/
varno = root->parse->resultRelation;
rte = rt_fetch(varno, root->parse->rtable);
relation = table_open(rte->relid, NoLock);
/*
* Build normalized/BMS representation of plain indexed attributes, as
* well as a separate list of expression items. This simplifies matching
* the cataloged definition of indexes.
*/
foreach_ptr(InferenceElem, elem, onconflict->arbiterElems)
{
Var *var;
int attno;
/* we cannot also have a constraint name, per grammar */
Assert(!OidIsValid(onconflict->constraint));
if (!IsA(elem->expr, Var))
{
/* If not a plain Var, just shove it in inferElems for now */
inferElems = lappend(inferElems, elem->expr);
continue;
}
var = (Var *) elem->expr;
attno = var->varattno;
if (attno == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("whole row unique index inference specifications are not supported")));
inferAttrs = bms_add_member(inferAttrs,
attno - FirstLowInvalidHeapAttributeNumber);
}
/*
* Next, open all the indexes. We need this list for two things: first,
* if an ON CONSTRAINT clause was given, and that constraint's index is
* undergoing REINDEX CONCURRENTLY, then we need to consider all matches
* for that index. Second, if an attribute list was specified in the ON
* CONFLICT clause, we use the list to find the indexes whose attributes
* match that list.
*/
indexList = RelationGetIndexList(relation);
foreach_oid(indexoid, indexList)
{
Relation idxRel;
/* obtain the same lock type that the executor will ultimately use */
idxRel = index_open(indexoid, rte->rellockmode);
indexRelList = lappend(indexRelList, idxRel);
}
/*
* If a constraint was named in the command, look up its index. We don't
* return it immediately because we need some additional sanity checks,
* and also because we need to include other indexes as arbiters to
* account for REINDEX CONCURRENTLY processing it.
*/
if (onconflict->constraint != InvalidOid)
{
/* we cannot also have an explicit list of elements, per grammar */
Assert(onconflict->arbiterElems == NIL);
indexOidFromConstraint = get_constraint_index(onconflict->constraint);
if (indexOidFromConstraint == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint in ON CONFLICT clause has no associated index")));
/*
* Find the named constraint index to extract its attributes and
* predicates.
*/
foreach_ptr(RelationData, idxRel, indexRelList)
{
Form_pg_index idxForm = idxRel->rd_index;
if (indexOidFromConstraint == idxForm->indexrelid)
{
/* Found it. */
Assert(idxForm->indisready);
/*
* Set up inferElems and inferIndexExprs to match the
* constraint index, so that we can match them in the loop
* below.
*/
for (int natt = 0; natt < idxForm->indnkeyatts; natt++)
{
int attno;
attno = idxRel->rd_index->indkey.values[natt];
if (attno != InvalidAttrNumber)
inferAttrs =
bms_add_member(inferAttrs,
attno - FirstLowInvalidHeapAttributeNumber);
}
inferElems = RelationGetIndexExpressionsExpand(idxRel);
inferIndexExprs = RelationGetIndexPredicateExpand(idxRel);
break;
}
}
}
/*
* Using that representation, iterate through the list of indexes on the
* target relation to find matches.
*/
foreach_ptr(RelationData, idxRel, indexRelList)
{
Form_pg_index idxForm;
Bitmapset *indexedAttrs;
List *idxExprs;
List *predExprs;
AttrNumber natt;
bool match;
/*
* Extract info from the relation descriptor for the index.
*
* Let executor complain about !indimmediate case directly, because
* enforcement needs to occur there anyway when an inference clause is
* omitted.
*/
idxForm = idxRel->rd_index;
/*
* Ignore indexes that aren't indisready, because we cannot trust
* their catalog structure yet. However, if any indexes are marked
* indisready but not yet indisvalid, we still consider them, because
* they might turn valid while we're running. Doing it this way
* allows a concurrent transaction with a slightly later catalog
* snapshot infer the same set of indexes, which is critical to
* prevent spurious 'duplicate key' errors.
*
* However, another critical aspect is that a unique index that isn't
* yet marked indisvalid=true might not be complete yet, meaning it
* wouldn't detect possible duplicate rows. In order to prevent false
* negatives, we require that we include in the set of inferred
* indexes at least one index that is marked valid.
*/
if (!idxForm->indisready)
continue;
/*
* Ignore invalid indexes for partitioned tables. It's possible that
* some partitions don't have the index (yet), and then we would not
* find a match during ExecInitPartitionInfo.
*/
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
!idxForm->indisvalid)
continue;
/*
* Note that we do not perform a check against indcheckxmin (like e.g.
* get_relation_info()) here to eliminate candidates, because
* uniqueness checking only cares about the most recently committed
* tuple versions.
*/
/*
* Look for match for "ON constraint_name" variant, which may not be a
* unique constraint. This can only be a constraint name.
*/
if (indexOidFromConstraint == idxForm->indexrelid)
{
/*
* ON CONFLICT DO UPDATE and ON CONFLICT DO SELECT are not
* supported with exclusion constraints.
*/
if (idxForm->indisexclusion &&
(onconflict->action == ONCONFLICT_UPDATE ||
onconflict->action == ONCONFLICT_SELECT))
ereport(ERROR,
errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("ON CONFLICT DO %s not supported with exclusion constraints",
onconflict->action == ONCONFLICT_UPDATE ? "UPDATE" : "SELECT"));
/* Consider this one a match already */
results = lappend_oid(results, idxForm->indexrelid);
foundValid |= idxForm->indisvalid;
continue;
}
else if (indexOidFromConstraint != InvalidOid)
{
/*
* In the case of "ON constraint_name DO SELECT/UPDATE" we need to
* skip non-unique candidates.
*/
if (!idxForm->indisunique &&
(onconflict->action == ONCONFLICT_UPDATE ||
onconflict->action == ONCONFLICT_SELECT))
continue;
}
else
{
/*
* Only considering conventional inference at this point (not
* named constraints), so index under consideration can be
* immediately skipped if it's not unique.
*/
if (!idxForm->indisunique)
continue;
}
/*
* So-called unique constraints with WITHOUT OVERLAPS are really
* exclusion constraints, so skip those too.
*/
if (idxForm->indisexclusion)
continue;
/* Build BMS representation of plain (non expression) index attrs */
indexedAttrs = NULL;
for (natt = 0; natt < idxForm->indnkeyatts; natt++)
{
int attno = idxRel->rd_index->indkey.values[natt];
if (attno != 0)
indexedAttrs = bms_add_member(indexedAttrs,
attno - FirstLowInvalidHeapAttributeNumber);
}
/* Non-expression attributes (if any) must match */
if (!bms_equal(indexedAttrs, inferAttrs))
continue;
/* Expression attributes (if any) must match */
idxExprs = RelationGetIndexExpressionsExpand(idxRel);
if (idxExprs)
{
if (varno != 1)
ChangeVarNodes((Node *) idxExprs, 1, varno, 0);
idxExprs = (List *) eval_const_expressions(root, (Node *) idxExprs);
}
/*
* If arbiterElems are present, check them. (Note that if a
* constraint name was given in the command line, this list is NIL.)
*/
match = true;
foreach_ptr(InferenceElem, elem, onconflict->arbiterElems)
{
/*
* Ensure that collation/opclass aspects of inference expression
* element match. Even though this loop is primarily concerned
* with matching expressions, it is a convenient point to check
* this for both expressions and ordinary (non-expression)
* attributes appearing as inference elements.
*/
if (!infer_collation_opclass_match(elem, idxRel, idxExprs))
{
match = false;
break;
}
/*
* Plain Vars don't factor into count of expression elements, and
* the question of whether or not they satisfy the index
* definition has already been considered (they must).
*/
if (IsA(elem->expr, Var))
continue;
/*
* Might as well avoid redundant check in the rare cases where
* infer_collation_opclass_match() is required to do real work.
* Otherwise, check that element expression appears in cataloged
* index definition.
*/
if (elem->infercollid != InvalidOid ||
elem->inferopclass != InvalidOid ||
list_member(idxExprs, elem->expr))
continue;
match = false;
break;
}
if (!match)
continue;
/*
* In case of inference from an attribute list, ensure that the
* expression elements from inference clause are not missing any
* cataloged expressions. This does the right thing when unique
* indexes redundantly repeat the same attribute, or if attributes
* redundantly appear multiple times within an inference clause.
*
* In case a constraint was named, ensure the candidate has an equal
* set of expressions as the named constraint's index.
*/
if (list_difference(idxExprs, inferElems) != NIL)
continue;
predExprs = RelationGetIndexPredicateExpand(idxRel);
if (predExprs)
{
if (varno != 1)
ChangeVarNodes((Node *) predExprs, 1, varno, 0);
predExprs = (List *)
eval_const_expressions(root,
(Node *) make_ands_explicit(predExprs));
predExprs = make_ands_implicit((Expr *) predExprs);
}
/*
* Partial indexes affect each form of ON CONFLICT differently: if a
* constraint was named, then the predicates must be identical. In
* conventional inference, the index's predicate must be implied by
* the WHERE clause.
*/
if (OidIsValid(indexOidFromConstraint))
{
if (list_difference(predExprs, inferIndexExprs) != NIL)
continue;
}
else
{
if (!predicate_implied_by(predExprs, inferIndexExprs, false))
continue;
}
/* All good -- consider this index a match */
results = lappend_oid(results, idxForm->indexrelid);
foundValid |= idxForm->indisvalid;
}
/* Close all indexes */
foreach_ptr(RelationData, idxRel, indexRelList)
{
index_close(idxRel, NoLock);
}
list_free(indexList);
list_free(indexRelList);
table_close(relation, NoLock);
/* We require at least one indisvalid index */
if (results == NIL || !foundValid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("there is no unique or exclusion constraint matching the ON CONFLICT specification")));
return results;
}
/*
* infer_collation_opclass_match - ensure infer element opclass/collation match
*
* Given unique index inference element from inference specification, if
* collation was specified, or if opclass was specified, verify that there is
* at least one matching indexed attribute (occasionally, there may be more).
* Skip this in the common case where inference specification does not include
* collation or opclass (instead matching everything, regardless of cataloged
* collation/opclass of indexed attribute).
*
* At least historically, Postgres has not offered collations or opclasses
* with alternative-to-default notions of equality, so these additional
* criteria should only be required infrequently.
*
* Don't give up immediately when an inference element matches some attribute
* cataloged as indexed but not matching additional opclass/collation
* criteria. This is done so that the implementation is as forgiving as
* possible of redundancy within cataloged index attributes (or, less
* usefully, within inference specification elements). If collations actually
* differ between apparently redundantly indexed attributes (redundant within
* or across indexes), then there really is no redundancy as such.
*
* Note that if an inference element specifies an opclass and a collation at
* once, both must match in at least one particular attribute within index
* catalog definition in order for that inference element to be considered
* inferred/satisfied.
*/
static bool
infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
List *idxExprs)
{
AttrNumber natt;
Oid inferopfamily = InvalidOid; /* OID of opclass opfamily */
Oid inferopcinputtype = InvalidOid; /* OID of opclass input type */
int nplain = 0; /* # plain attrs observed */
/*
* If inference specification element lacks collation/opclass, then no
* need to check for exact match.
*/
if (elem->infercollid == InvalidOid && elem->inferopclass == InvalidOid)
return true;
/*
* Lookup opfamily and input type, for matching indexes
*/
if (elem->inferopclass)
{
inferopfamily = get_opclass_family(elem->inferopclass);
inferopcinputtype = get_opclass_input_type(elem->inferopclass);
}
for (natt = 1; natt <= idxRel->rd_att->natts; natt++)
{
Oid opfamily = idxRel->rd_opfamily[natt - 1];
Oid opcinputtype = idxRel->rd_opcintype[natt - 1];
Oid collation = idxRel->rd_indcollation[natt - 1];
int attno = idxRel->rd_index->indkey.values[natt - 1];
if (attno != 0)
nplain++;
if (elem->inferopclass != InvalidOid &&
(inferopfamily != opfamily || inferopcinputtype != opcinputtype))
{
/* Attribute needed to match opclass, but didn't */
continue;
}
if (elem->infercollid != InvalidOid &&
elem->infercollid != collation)
{
/* Attribute needed to match collation, but didn't */
continue;
}
/* If one matching index att found, good enough -- return true */
if (IsA(elem->expr, Var))
{
if (((Var *) elem->expr)->varattno == attno)
return true;
}
else if (attno == 0)
{
Node *nattExpr = list_nth(idxExprs, (natt - 1) - nplain);
/*
* Note that unlike routines like match_index_to_operand() we
* don't need to care about RelabelType. Neither the index
* definition nor the inference clause should contain them.
*/
if (equal(elem->expr, nattExpr))
return true;
}
}
return false;
}
/*
* estimate_rel_size - estimate # pages and # tuples in a table or index
*
* We also estimate the fraction of the pages that are marked all-visible in
* the visibility map, for use in estimation of index-only scans.
*
* If attr_widths isn't NULL, it points to the zero-index entry of the
* relation's attr_widths[] cache; we fill this in if we have need to compute
* the attribute widths for estimation purposes.
*/
void
estimate_rel_size(Relation rel, int32 *attr_widths,
BlockNumber *pages, double *tuples, double *allvisfrac)
{
BlockNumber curpages;
BlockNumber relpages;
double reltuples;
BlockNumber relallvisible;
double density;
if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind))
{
table_relation_estimate_size(rel, attr_widths, pages, tuples,
allvisfrac);
}
else if (rel->rd_rel->relkind == RELKIND_INDEX)
{
/*
* XXX: It'd probably be good to move this into a callback, individual
* index types e.g. know if they have a metapage.
*/
/* it has storage, ok to call the smgr */
curpages = RelationGetNumberOfBlocks(rel);
/* report estimated # pages */
*pages = curpages;
/* quick exit if rel is clearly empty */
if (curpages == 0)
{
*tuples = 0;
*allvisfrac = 0;
return;
}
/* coerce values in pg_class to more desirable types */
relpages = (BlockNumber) rel->rd_rel->relpages;
reltuples = (double) rel->rd_rel->reltuples;
relallvisible = (BlockNumber) rel->rd_rel->relallvisible;
/*
* Discount the metapage while estimating the number of tuples. This
* is a kluge because it assumes more than it ought to about index
* structure. Currently it's OK for btree, hash, and GIN indexes but
* suspect for GiST indexes.
*/
if (relpages > 0)
{
curpages--;
relpages--;
}
/* estimate number of tuples from previous tuple density */
if (reltuples >= 0 && relpages > 0)
density = reltuples / (double) relpages;
else
{
/*
* If we have no data because the relation was never vacuumed,
* estimate tuple width from attribute datatypes. We assume here
* that the pages are completely full, which is OK for tables
* (since they've presumably not been VACUUMed yet) but is
* probably an overestimate for indexes. Fortunately
* get_relation_info() can clamp the overestimate to the parent
* table's size.
*
* Note: this code intentionally disregards alignment
* considerations, because (a) that would be gilding the lily
* considering how crude the estimate is, and (b) it creates
* platform dependencies in the default plans which are kind of a
* headache for regression testing.
*
* XXX: Should this logic be more index specific?
*/
int32 tuple_width;
tuple_width = get_rel_data_width(rel, attr_widths);
tuple_width += MAXALIGN(SizeofHeapTupleHeader);
tuple_width += sizeof(ItemIdData);
/* note: integer division is intentional here */
density = (BLCKSZ - SizeOfPageHeaderData) / tuple_width;
}
*tuples = rint(density * (double) curpages);
/*
* We use relallvisible as-is, rather than scaling it up like we do
* for the pages and tuples counts, on the theory that any pages added
* since the last VACUUM are most likely not marked all-visible. But
* costsize.c wants it converted to a fraction.
*/
if (relallvisible == 0 || curpages <= 0)
*allvisfrac = 0;
else if ((double) relallvisible >= curpages)
*allvisfrac = 1;
else
*allvisfrac = (double) relallvisible / curpages;
}
else
{
/*
* Just use whatever's in pg_class. This covers foreign tables,
* sequences, and also relkinds without storage (shouldn't get here?);
* see initializations in AddNewRelationTuple(). Note that FDW must
* cope if reltuples is -1!
*/
*pages = rel->rd_rel->relpages;
*tuples = rel->rd_rel->reltuples;
*allvisfrac = 0;
}
}
/*
* get_rel_data_width
*
* Estimate the average width of (the data part of) the relation's tuples.
*
* If attr_widths isn't NULL, it points to the zero-index entry of the
* relation's attr_widths[] cache; use and update that cache as appropriate.
*
* Currently we ignore dropped columns. Ideally those should be included
* in the result, but we haven't got any way to get info about them; and
* since they might be mostly NULLs, treating them as zero-width is not
* necessarily the wrong thing anyway.
*/
int32
get_rel_data_width(Relation rel, int32 *attr_widths)
{
int64 tuple_width = 0;
int i;
for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++)
{
Form_pg_attribute att = TupleDescAttr(rel->rd_att, i - 1);
int32 item_width;
if (att->attisdropped)
continue;
/* use previously cached data, if any */
if (attr_widths != NULL && attr_widths[i] > 0)
{
tuple_width += attr_widths[i];
continue;
}
/* This should match set_rel_width() in costsize.c */
item_width = get_attavgwidth(RelationGetRelid(rel), i);
if (item_width <= 0)
{
item_width = get_typavgwidth(att->atttypid, att->atttypmod);
Assert(item_width > 0);
}
if (attr_widths != NULL)
attr_widths[i] = item_width;
tuple_width += item_width;
}
return clamp_width_est(tuple_width);
}
/*
* get_relation_data_width
*
* External API for get_rel_data_width: same behavior except we have to
* open the relcache entry.
*/
int32
get_relation_data_width(Oid relid, int32 *attr_widths)
{
int32 result;
Relation relation;
/* As above, assume relation is already locked */
relation = table_open(relid, NoLock);
result = get_rel_data_width(relation, attr_widths);
table_close(relation, NoLock);
return result;
}
/*
* get_relation_constraints
*
* Retrieve the applicable constraint expressions of the given relation.
* Only constraints that have been validated are considered.
*
* Returns a List (possibly empty) of constraint expressions. Each one
* has been canonicalized, and its Vars are changed to have the varno
* indicated by rel->relid. This allows the expressions to be easily
* compared to expressions taken from WHERE.
*
* If include_noinherit is true, it's okay to include constraints that
* are marked NO INHERIT.
*
* If include_notnull is true, "col IS NOT NULL" expressions are generated
* and added to the result for each column that's marked attnotnull.
*
* If include_partition is true, and the relation is a partition,
* also include the partitioning constraints.
*
* Note: at present this is invoked at most once per relation per planner
* run, and in many cases it won't be invoked at all, so there seems no
* point in caching the data in RelOptInfo.
*/
static List *
get_relation_constraints(PlannerInfo *root,
Oid relationObjectId, RelOptInfo *rel,
bool include_noinherit,
bool include_notnull,
bool include_partition)
{
List *result = NIL;
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
/*
* We assume the relation has already been safely locked.
*/
relation = table_open(relationObjectId, NoLock);
constr = relation->rd_att->constr;
if (constr != NULL)
{
int num_check = constr->num_check;
int i;
for (i = 0; i < num_check; i++)
{
Node *cexpr;
/*
* If this constraint hasn't been fully validated yet, we must
* ignore it here.
*/
if (!constr->check[i].ccvalid)
continue;
/*
* NOT ENFORCED constraints are always marked as invalid, which
* should have been ignored.
*/
Assert(constr->check[i].ccenforced);
/*
* Also ignore if NO INHERIT and we weren't told that that's safe.
*/
if (constr->check[i].ccnoinherit && !include_noinherit)
continue;
cexpr = stringToNode(constr->check[i].ccbin);
/*
* Fix Vars to have the desired varno. This must be done before
* const-simplification because eval_const_expressions reduces
* NullTest for Vars based on varno.
*/
if (varno != 1)
ChangeVarNodes(cexpr, 1, varno, 0);
/*
* Run each expression through const-simplification and
* canonicalization. This is not just an optimization, but is
* necessary, because we will be comparing it to
* similarly-processed qual clauses, and may fail to detect valid
* matches without this. This must match the processing done to
* qual clauses in preprocess_expression()! (We can skip the
* stuff involving subqueries, however, since we don't allow any
* in check constraints.)
*/
cexpr = eval_const_expressions(root, cexpr);
cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
/*
* Finally, convert to implicit-AND format (that is, a List) and
* append the resulting item(s) to our output list.
*/
result = list_concat(result,
make_ands_implicit((Expr *) cexpr));
}
/* Add NOT NULL constraints in expression form, if requested */
if (include_notnull && constr->has_not_null)
{
int natts = relation->rd_att->natts;
for (i = 1; i <= natts; i++)
{
CompactAttribute *att = TupleDescCompactAttr(relation->rd_att, i - 1);
if (att->attnullability == ATTNULLABLE_VALID && !att->attisdropped)
{
Form_pg_attribute wholeatt = TupleDescAttr(relation->rd_att, i - 1);
NullTest *ntest = makeNode(NullTest);
ntest->arg = (Expr *) makeVar(varno,
i,
wholeatt->atttypid,
wholeatt->atttypmod,
wholeatt->attcollation,
0);
ntest->nulltesttype = IS_NOT_NULL;
/*
* argisrow=false is correct even for a composite column,
* because attnotnull does not represent a SQL-spec IS NOT
* NULL test in such a case, just IS DISTINCT FROM NULL.
*/
ntest->argisrow = false;
ntest->location = -1;
result = lappend(result, ntest);
}
}
}
}
/*
* Add partitioning constraints, if requested.
*/
if (include_partition && relation->rd_rel->relispartition)
{
/* make sure rel->partition_qual is set */
set_baserel_partition_constraint(relation, rel);
result = list_concat(result, rel->partition_qual);
}
/*
* Expand virtual generated columns in the constraint expressions.
*/
if (result)
result = (List *) expand_generated_columns_in_expr((Node *) result,
relation,
varno);
table_close(relation, NoLock);
return result;
}
/*
* Try loading data for the statistics object.
*
* We don't know if the data (specified by statOid and inh value) exist.
* The result is stored in stainfos list.
*/
static void
get_relation_statistics_worker(List **stainfos, RelOptInfo *rel,
Oid statOid, bool inh,
Bitmapset *keys, List *exprs)
{
Form_pg_statistic_ext_data dataForm;
HeapTuple dtup;
dtup = SearchSysCache2(STATEXTDATASTXOID,
ObjectIdGetDatum(statOid), BoolGetDatum(inh));
if (!HeapTupleIsValid(dtup))
return;
dataForm = (Form_pg_statistic_ext_data) GETSTRUCT(dtup);
/* add one StatisticExtInfo for each kind built */
if (statext_is_kind_built(dtup, STATS_EXT_NDISTINCT))
{
StatisticExtInfo *info = makeNode(StatisticExtInfo);
info->statOid = statOid;
info->inherit = dataForm->stxdinherit;
info->rel = rel;
info->kind = STATS_EXT_NDISTINCT;
info->keys = bms_copy(keys);
info->exprs = exprs;
*stainfos = lappend(*stainfos, info);
}
if (statext_is_kind_built(dtup, STATS_EXT_DEPENDENCIES))
{
StatisticExtInfo *info = makeNode(StatisticExtInfo);
info->statOid = statOid;
info->inherit = dataForm->stxdinherit;
info->rel = rel;
info->kind = STATS_EXT_DEPENDENCIES;
info->keys = bms_copy(keys);
info->exprs = exprs;
*stainfos = lappend(*stainfos, info);
}
if (statext_is_kind_built(dtup, STATS_EXT_MCV))
{
StatisticExtInfo *info = makeNode(StatisticExtInfo);
info->statOid = statOid;
info->inherit = dataForm->stxdinherit;
info->rel = rel;
info->kind = STATS_EXT_MCV;
info->keys = bms_copy(keys);
info->exprs = exprs;
*stainfos = lappend(*stainfos, info);
}
if (statext_is_kind_built(dtup, STATS_EXT_EXPRESSIONS))
{
StatisticExtInfo *info = makeNode(StatisticExtInfo);
info->statOid = statOid;
info->inherit = dataForm->stxdinherit;
info->rel = rel;
info->kind = STATS_EXT_EXPRESSIONS;
info->keys = bms_copy(keys);
info->exprs = exprs;
*stainfos = lappend(*stainfos, info);
}
ReleaseSysCache(dtup);
}
/*
* get_relation_statistics
* Retrieve extended statistics defined on the table.
*
* Returns a List (possibly empty) of StatisticExtInfo objects describing
* the statistics. Note that this doesn't load the actual statistics data,
* just the identifying metadata. Only stats actually built are considered.
*/
static List *
get_relation_statistics(PlannerInfo *root, RelOptInfo *rel,
Relation relation)
{
Index varno = rel->relid;
List *statoidlist;
List *stainfos = NIL;
ListCell *l;
statoidlist = RelationGetStatExtList(relation);
foreach(l, statoidlist)
{
Oid statOid = lfirst_oid(l);
Form_pg_statistic_ext staForm;
HeapTuple htup;
Bitmapset *keys = NULL;
List *exprs = NIL;
int i;
htup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statOid));
if (!HeapTupleIsValid(htup))
elog(ERROR, "cache lookup failed for statistics object %u", statOid);
staForm = (Form_pg_statistic_ext) GETSTRUCT(htup);
/*
* First, build the array of columns covered. This is ultimately
* wasted if no stats within the object have actually been built, but
* it doesn't seem worth troubling over that case.
*/
for (i = 0; i < staForm->stxkeys.dim1; i++)
keys = bms_add_member(keys, staForm->stxkeys.values[i]);
/*
* Preprocess expressions (if any). We read the expressions, fix the
* varnos, and run them through eval_const_expressions.
*
* XXX We don't know yet if there are any data for this stats object,
* with either stxdinherit value. But it's reasonable to assume there
* is at least one of those, possibly both. So it's better to process
* keys and expressions here.
*/
{
bool isnull;
Datum datum;
/* decode expression (if any) */
datum = SysCacheGetAttr(STATEXTOID, htup,
Anum_pg_statistic_ext_stxexprs, &isnull);
if (!isnull)
{
char *exprsString;
exprsString = TextDatumGetCString(datum);
exprs = (List *) stringToNode(exprsString);
pfree(exprsString);
/* Expand virtual generated columns in the expressions */
exprs = (List *) expand_generated_columns_in_expr((Node *) exprs, relation, 1);
/*
* Modify the copies we obtain from the relcache to have the
* correct varno for the parent relation, so that they match
* up correctly against qual clauses.
*
* This must be done before const-simplification because
* eval_const_expressions reduces NullTest for Vars based on
* varno.
*/
if (varno != 1)
ChangeVarNodes((Node *) exprs, 1, varno, 0);
/*
* Run the expressions through eval_const_expressions. This is
* not just an optimization, but is necessary, because the
* planner will be comparing them to similarly-processed qual
* clauses, and may fail to detect valid matches without this.
* We must not use canonicalize_qual, however, since these
* aren't qual expressions.
*/
exprs = (List *) eval_const_expressions(root, (Node *) exprs);
/* May as well fix opfuncids too */
fix_opfuncids((Node *) exprs);
}
}
/* extract statistics for possible values of stxdinherit flag */
get_relation_statistics_worker(&stainfos, rel, statOid, true, keys, exprs);
get_relation_statistics_worker(&stainfos, rel, statOid, false, keys, exprs);
ReleaseSysCache(htup);
bms_free(keys);
}
list_free(statoidlist);
return stainfos;
}
/*
* relation_excluded_by_constraints
*
* Detect whether the relation need not be scanned because it has either
* self-inconsistent restrictions, or restrictions inconsistent with the
* relation's applicable constraints.
*
* Note: this examines only rel->relid, rel->reloptkind, and
* rel->baserestrictinfo; therefore it can be called before filling in
* other fields of the RelOptInfo.
*/
bool
relation_excluded_by_constraints(PlannerInfo *root,
RelOptInfo *rel, RangeTblEntry *rte)
{
bool include_noinherit;
bool include_notnull;
bool include_partition = false;
List *safe_restrictions;
List *constraint_pred;
List *safe_constraints;
ListCell *lc;
/* As of now, constraint exclusion works only with simple relations. */
Assert(IS_SIMPLE_REL(rel));
/*
* If there are no base restriction clauses, we have no hope of proving
* anything below, so fall out quickly.
*/
if (rel->baserestrictinfo == NIL)
return false;
/*
* Regardless of the setting of constraint_exclusion, detect
* constant-FALSE-or-NULL restriction clauses. Although const-folding
* will reduce "anything AND FALSE" to just "FALSE", the baserestrictinfo
* list can still have other members besides the FALSE constant, due to
* qual pushdown and other mechanisms; so check them all. This doesn't
* fire very often, but it seems cheap enough to be worth doing anyway.
* (Without this, we'd miss some optimizations that 9.5 and earlier found
* via much more roundabout methods.)
*/
foreach(lc, rel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
Expr *clause = rinfo->clause;
if (clause && IsA(clause, Const) &&
(((Const *) clause)->constisnull ||
!DatumGetBool(((Const *) clause)->constvalue)))
return true;
}
/*
* Skip further tests, depending on constraint_exclusion.
*/
switch (constraint_exclusion)
{
case CONSTRAINT_EXCLUSION_OFF:
/* In 'off' mode, never make any further tests */
return false;
case CONSTRAINT_EXCLUSION_PARTITION:
/*
* When constraint_exclusion is set to 'partition' we only handle
* appendrel members. Partition pruning has already been applied,
* so there is no need to consider the rel's partition constraints
* here.
*/
if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
break; /* appendrel member, so process it */
return false;
case CONSTRAINT_EXCLUSION_ON:
/*
* In 'on' mode, always apply constraint exclusion. If we are
* considering a baserel that is a partition (i.e., it was
* directly named rather than expanded from a parent table), then
* its partition constraints haven't been considered yet, so
* include them in the processing here.
*/
if (rel->reloptkind == RELOPT_BASEREL)
include_partition = true;
break; /* always try to exclude */
}
/*
* Check for self-contradictory restriction clauses. We dare not make
* deductions with non-immutable functions, but any immutable clauses that
* are self-contradictory allow us to conclude the scan is unnecessary.
*
* Note: strip off RestrictInfo because predicate_refuted_by() isn't
* expecting to see any in its predicate argument.
*/
safe_restrictions = NIL;
foreach(lc, rel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
if (!contain_mutable_functions((Node *) rinfo->clause))
safe_restrictions = lappend(safe_restrictions, rinfo->clause);
}
/*
* We can use weak refutation here, since we're comparing restriction
* clauses with restriction clauses.
*/
if (predicate_refuted_by(safe_restrictions, safe_restrictions, true))
return true;
/*
* Only plain relations have constraints, so stop here for other rtekinds.
*/
if (rte->rtekind != RTE_RELATION)
return false;
/*
* If we are scanning just this table, we can use NO INHERIT constraints,
* but not if we're scanning its children too. (Note that partitioned
* tables should never have NO INHERIT constraints; but it's not necessary
* for us to assume that here.)
*/
include_noinherit = !rte->inh;
/*
* Currently, attnotnull constraints must be treated as NO INHERIT unless
* this is a partitioned table. In future we might track their
* inheritance status more accurately, allowing this to be refined.
*
* XXX do we need/want to change this?
*/
include_notnull = (!rte->inh || rte->relkind == RELKIND_PARTITIONED_TABLE);
/*
* Fetch the appropriate set of constraint expressions.
*/
constraint_pred = get_relation_constraints(root, rte->relid, rel,
include_noinherit,
include_notnull,
include_partition);
/*
* We do not currently enforce that CHECK constraints contain only
* immutable functions, so it's necessary to check here. We daren't draw
* conclusions from plan-time evaluation of non-immutable functions. Since
* they're ANDed, we can just ignore any mutable constraints in the list,
* and reason about the rest.
*/
safe_constraints = NIL;
foreach(lc, constraint_pred)
{
Node *pred = (Node *) lfirst(lc);
if (!contain_mutable_functions(pred))
safe_constraints = lappend(safe_constraints, pred);
}
/*
* The constraints are effectively ANDed together, so we can just try to
* refute the entire collection at once. This may allow us to make proofs
* that would fail if we took them individually.
*
* Note: we use rel->baserestrictinfo, not safe_restrictions as might seem
* an obvious optimization. Some of the clauses might be OR clauses that
* have volatile and nonvolatile subclauses, and it's OK to make
* deductions with the nonvolatile parts.
*
* We need strong refutation because we have to prove that the constraints
* would yield false, not just NULL.
*/
if (predicate_refuted_by(safe_constraints, rel->baserestrictinfo, false))
return true;
return false;
}
/*
* build_physical_tlist
*
* Build a targetlist consisting of exactly the relation's user attributes,
* in order. The executor can special-case such tlists to avoid a projection
* step at runtime, so we use such tlists preferentially for scan nodes.
*
* Exception: if there are any dropped or missing columns, we punt and return
* NIL. Ideally we would like to handle these cases too. However this
* creates problems for ExecTypeFromTL, which may be asked to build a tupdesc
* for a tlist that includes vars of no-longer-existent types. In theory we
* could dig out the required info from the pg_attribute entries of the
* relation, but that data is not readily available to ExecTypeFromTL.
* For now, we don't apply the physical-tlist optimization when there are
* dropped cols.
*
* We also support building a "physical" tlist for subqueries, functions,
* values lists, table expressions, and CTEs, since the same optimization can
* occur in SubqueryScan, FunctionScan, ValuesScan, CteScan, TableFunc,
* NamedTuplestoreScan, and WorkTableScan nodes.
*/
List *
build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
{
List *tlist = NIL;
Index varno = rel->relid;
RangeTblEntry *rte = planner_rt_fetch(varno, root);
Relation relation;
Query *subquery;
Var *var;
ListCell *l;
int attrno,
numattrs;
List *colvars;
switch (rte->rtekind)
{
case RTE_RELATION:
/* Assume we already have adequate lock */
relation = table_open(rte->relid, NoLock);
numattrs = RelationGetNumberOfAttributes(relation);
for (attrno = 1; attrno <= numattrs; attrno++)
{
Form_pg_attribute att_tup = TupleDescAttr(relation->rd_att,
attrno - 1);
if (att_tup->attisdropped || att_tup->atthasmissing)
{
/* found a dropped or missing col, so punt */
tlist = NIL;
break;
}
var = makeVar(varno,
attrno,
att_tup->atttypid,
att_tup->atttypmod,
att_tup->attcollation,
0);
tlist = lappend(tlist,
makeTargetEntry((Expr *) var,
attrno,
NULL,
false));
}
table_close(relation, NoLock);
break;
case RTE_SUBQUERY:
subquery = rte->subquery;
foreach(l, subquery->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
/*
* A resjunk column of the subquery can be reflected as
* resjunk in the physical tlist; we need not punt.
*/
var = makeVarFromTargetEntry(varno, tle);
tlist = lappend(tlist,
makeTargetEntry((Expr *) var,
tle->resno,
NULL,
tle->resjunk));
}
break;
case RTE_FUNCTION:
case RTE_TABLEFUNC:
case RTE_VALUES:
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* Not all of these can have dropped cols, but share code anyway */
expandRTE(rte, varno, 0, VAR_RETURNING_DEFAULT, -1,
true /* include dropped */ , NULL, &colvars);
foreach(l, colvars)
{
var = (Var *) lfirst(l);
/*
* A non-Var in expandRTE's output means a dropped column;
* must punt.
*/
if (!IsA(var, Var))
{
tlist = NIL;
break;
}
tlist = lappend(tlist,
makeTargetEntry((Expr *) var,
var->varattno,
NULL,
false));
}
break;
default:
/* caller error */
elog(ERROR, "unsupported RTE kind %d in build_physical_tlist",
(int) rte->rtekind);
break;
}
return tlist;
}
/*
* build_index_tlist
*
* Build a targetlist representing the columns of the specified index.
* Each column is represented by a Var for the corresponding base-relation
* column, or an expression in base-relation Vars, as appropriate.
*
* There are never any dropped columns in indexes, so unlike
* build_physical_tlist, we need no failure case.
*/
static List *
build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
Relation heapRelation)
{
List *tlist = NIL;
Index varno = index->rel->relid;
ListCell *indexpr_item;
int i;
indexpr_item = list_head(index->indexprs);
for (i = 0; i < index->ncolumns; i++)
{
int indexkey = index->indexkeys[i];
Expr *indexvar;
if (indexkey != 0)
{
/* simple column */
const FormData_pg_attribute *att_tup;
if (indexkey < 0)
att_tup = SystemAttributeDefinition(indexkey);
else
att_tup = TupleDescAttr(heapRelation->rd_att, indexkey - 1);
indexvar = (Expr *) makeVar(varno,
indexkey,
att_tup->atttypid,
att_tup->atttypmod,
att_tup->attcollation,
0);
}
else
{
/* expression column */
if (indexpr_item == NULL)
elog(ERROR, "wrong number of index expressions");
indexvar = (Expr *) lfirst(indexpr_item);
indexpr_item = lnext(index->indexprs, indexpr_item);
}
tlist = lappend(tlist,
makeTargetEntry(indexvar,
i + 1,
NULL,
false));
}
if (indexpr_item != NULL)
elog(ERROR, "wrong number of index expressions");
return tlist;
}
/*
* restriction_selectivity
*
* Returns the selectivity of a specified restriction operator clause.
* This code executes registered procedures stored in the
* operator relation, by calling the function manager.
*
* See clause_selectivity() for the meaning of the additional parameters.
*/
Selectivity
restriction_selectivity(PlannerInfo *root,
Oid operatorid,
List *args,
Oid inputcollid,
int varRelid)
{
RegProcedure oprrest = get_oprrest(operatorid);
float8 result;
/*
* if the oprrest procedure is missing for whatever reason, use a
* selectivity of 0.5
*/
if (!oprrest)
return (Selectivity) 0.5;
result = DatumGetFloat8(OidFunctionCall4Coll(oprrest,
inputcollid,
PointerGetDatum(root),
ObjectIdGetDatum(operatorid),
PointerGetDatum(args),
Int32GetDatum(varRelid)));
if (result < 0.0 || result > 1.0)
elog(ERROR, "invalid restriction selectivity: %f", result);
return (Selectivity) result;
}
/*
* join_selectivity
*
* Returns the selectivity of a specified join operator clause.
* This code executes registered procedures stored in the
* operator relation, by calling the function manager.
*
* See clause_selectivity() for the meaning of the additional parameters.
*/
Selectivity
join_selectivity(PlannerInfo *root,
Oid operatorid,
List *args,
Oid inputcollid,
JoinType jointype,
SpecialJoinInfo *sjinfo)
{
RegProcedure oprjoin = get_oprjoin(operatorid);
float8 result;
/*
* if the oprjoin procedure is missing for whatever reason, use a
* selectivity of 0.5
*/
if (!oprjoin)
return (Selectivity) 0.5;
result = DatumGetFloat8(OidFunctionCall5Coll(oprjoin,
inputcollid,
PointerGetDatum(root),
ObjectIdGetDatum(operatorid),
PointerGetDatum(args),
Int16GetDatum(jointype),
PointerGetDatum(sjinfo)));
if (result < 0.0 || result > 1.0)
elog(ERROR, "invalid join selectivity: %f", result);
return (Selectivity) result;
}
/*
* function_selectivity
*
* Attempt to estimate the selectivity of a specified boolean function clause
* by asking its support function. If the function lacks support, return -1.
*
* See clause_selectivity() for the meaning of the additional parameters.
*/
Selectivity
function_selectivity(PlannerInfo *root,
Oid funcid,
List *args,
Oid inputcollid,
bool is_join,
int varRelid,
JoinType jointype,
SpecialJoinInfo *sjinfo)
{
RegProcedure prosupport = get_func_support(funcid);
SupportRequestSelectivity req;
SupportRequestSelectivity *sresult;
if (!prosupport)
return (Selectivity) -1; /* no support function */
req.type = T_SupportRequestSelectivity;
req.root = root;
req.funcid = funcid;
req.args = args;
req.inputcollid = inputcollid;
req.is_join = is_join;
req.varRelid = varRelid;
req.jointype = jointype;
req.sjinfo = sjinfo;
req.selectivity = -1; /* to catch failure to set the value */
sresult = (SupportRequestSelectivity *)
DatumGetPointer(OidFunctionCall1(prosupport,
PointerGetDatum(&req)));
if (sresult != &req)
return (Selectivity) -1; /* function did not honor request */
if (req.selectivity < 0.0 || req.selectivity > 1.0)
elog(ERROR, "invalid function selectivity: %f", req.selectivity);
return (Selectivity) req.selectivity;
}
/*
* add_function_cost
*
* Get an estimate of the execution cost of a function, and *add* it to
* the contents of *cost. The estimate may include both one-time and
* per-tuple components, since QualCost does.
*
* The funcid must always be supplied. If it is being called as the
* implementation of a specific parsetree node (FuncExpr, OpExpr,
* WindowFunc, etc), pass that as "node", else pass NULL.
*
* In some usages root might be NULL, too.
*/
void
add_function_cost(PlannerInfo *root, Oid funcid, Node *node,
QualCost *cost)
{
HeapTuple proctup;
Form_pg_proc procform;
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", funcid);
procform = (Form_pg_proc) GETSTRUCT(proctup);
if (OidIsValid(procform->prosupport))
{
SupportRequestCost req;
SupportRequestCost *sresult;
req.type = T_SupportRequestCost;
req.root = root;
req.funcid = funcid;
req.node = node;
/* Initialize cost fields so that support function doesn't have to */
req.startup = 0;
req.per_tuple = 0;
sresult = (SupportRequestCost *)
DatumGetPointer(OidFunctionCall1(procform->prosupport,
PointerGetDatum(&req)));
if (sresult == &req)
{
/* Success, so accumulate support function's estimate into *cost */
cost->startup += req.startup;
cost->per_tuple += req.per_tuple;
ReleaseSysCache(proctup);
return;
}
}
/* No support function, or it failed, so rely on procost */
cost->per_tuple += procform->procost * cpu_operator_cost;
ReleaseSysCache(proctup);
}
/*
* get_function_rows
*
* Get an estimate of the number of rows returned by a set-returning function.
*
* The funcid must always be supplied. In current usage, the calling node
* will always be supplied, and will be either a FuncExpr or OpExpr.
* But it's a good idea to not fail if it's NULL.
*
* In some usages root might be NULL, too.
*
* Note: this returns the unfiltered result of the support function, if any.
* It's usually a good idea to apply clamp_row_est() to the result, but we
* leave it to the caller to do so.
*/
double
get_function_rows(PlannerInfo *root, Oid funcid, Node *node)
{
HeapTuple proctup;
Form_pg_proc procform;
double result;
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", funcid);
procform = (Form_pg_proc) GETSTRUCT(proctup);
Assert(procform->proretset); /* else caller error */
if (OidIsValid(procform->prosupport))
{
SupportRequestRows req;
SupportRequestRows *sresult;
req.type = T_SupportRequestRows;
req.root = root;
req.funcid = funcid;
req.node = node;
req.rows = 0; /* just for sanity */
sresult = (SupportRequestRows *)
DatumGetPointer(OidFunctionCall1(procform->prosupport,
PointerGetDatum(&req)));
if (sresult == &req)
{
/* Success */
ReleaseSysCache(proctup);
return req.rows;
}
}
/* No support function, or it failed, so rely on prorows */
result = procform->prorows;
ReleaseSysCache(proctup);
return result;
}
/*
* has_unique_index
*
* Detect whether there is a unique index on the specified attribute
* of the specified relation, thus allowing us to conclude that all
* the (non-null) values of the attribute are distinct.
*
* This function does not check the index's indimmediate property, which
* means that uniqueness may transiently fail to hold intra-transaction.
* That's appropriate when we are making statistical estimates, but beware
* of using this for any correctness proofs.
*/
bool
has_unique_index(RelOptInfo *rel, AttrNumber attno)
{
ListCell *ilist;
foreach(ilist, rel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
/*
* Note: ignore partial indexes, since they don't allow us to conclude
* that all attr values are distinct, *unless* they are marked predOK
* which means we know the index's predicate is satisfied by the
* query. We don't take any interest in expressional indexes either.
* Also, a multicolumn unique index doesn't allow us to conclude that
* just the specified attr is unique.
*/
if (index->unique &&
index->nkeycolumns == 1 &&
index->indexkeys[0] == attno &&
(index->indpred == NIL || index->predOK))
return true;
}
return false;
}
/*
* has_row_triggers
*
* Detect whether the specified relation has any row-level triggers for event.
*/
bool
has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
{
RangeTblEntry *rte = planner_rt_fetch(rti, root);
Relation relation;
TriggerDesc *trigDesc;
bool result = false;
/* Assume we already have adequate lock */
relation = table_open(rte->relid, NoLock);
trigDesc = relation->trigdesc;
switch (event)
{
case CMD_INSERT:
if (trigDesc &&
(trigDesc->trig_insert_after_row ||
trigDesc->trig_insert_before_row))
result = true;
break;
case CMD_UPDATE:
if (trigDesc &&
(trigDesc->trig_update_after_row ||
trigDesc->trig_update_before_row))
result = true;
break;
case CMD_DELETE:
if (trigDesc &&
(trigDesc->trig_delete_after_row ||
trigDesc->trig_delete_before_row))
result = true;
break;
/* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
case CMD_MERGE:
result = false;
break;
default:
elog(ERROR, "unrecognized CmdType: %d", (int) event);
break;
}
table_close(relation, NoLock);
return result;
}
/*
* has_transition_tables
*
* Detect whether the specified relation has any transition tables for event.
*/
bool
has_transition_tables(PlannerInfo *root, Index rti, CmdType event)
{
RangeTblEntry *rte = planner_rt_fetch(rti, root);
Relation relation;
TriggerDesc *trigDesc;
bool result = false;
Assert(rte->rtekind == RTE_RELATION);
/* Currently foreign tables cannot have transition tables */
if (rte->relkind == RELKIND_FOREIGN_TABLE)
return result;
/* Assume we already have adequate lock */
relation = table_open(rte->relid, NoLock);
trigDesc = relation->trigdesc;
switch (event)
{
case CMD_INSERT:
if (trigDesc &&
trigDesc->trig_insert_new_table)
result = true;
break;
case CMD_UPDATE:
if (trigDesc &&
(trigDesc->trig_update_old_table ||
trigDesc->trig_update_new_table))
result = true;
break;
case CMD_DELETE:
if (trigDesc &&
trigDesc->trig_delete_old_table)
result = true;
break;
/* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
case CMD_MERGE:
result = false;
break;
default:
elog(ERROR, "unrecognized CmdType: %d", (int) event);
break;
}
table_close(relation, NoLock);
return result;
}
/*
* has_stored_generated_columns
*
* Does table identified by RTI have any STORED GENERATED columns?
*/
bool
has_stored_generated_columns(PlannerInfo *root, Index rti)
{
RangeTblEntry *rte = planner_rt_fetch(rti, root);
Relation relation;
TupleDesc tupdesc;
bool result = false;
/* Assume we already have adequate lock */
relation = table_open(rte->relid, NoLock);
tupdesc = RelationGetDescr(relation);
result = tupdesc->constr && tupdesc->constr->has_generated_stored;
table_close(relation, NoLock);
return result;
}
/*
* get_dependent_generated_columns
*
* Get the column numbers of any STORED GENERATED columns of the relation
* that depend on any column listed in target_cols. Both the input and
* result bitmapsets contain column numbers offset by
* FirstLowInvalidHeapAttributeNumber.
*/
Bitmapset *
get_dependent_generated_columns(PlannerInfo *root, Index rti,
Bitmapset *target_cols)
{
Bitmapset *dependentCols = NULL;
RangeTblEntry *rte = planner_rt_fetch(rti, root);
Relation relation;
TupleDesc tupdesc;
TupleConstr *constr;
/* Assume we already have adequate lock */
relation = table_open(rte->relid, NoLock);
tupdesc = RelationGetDescr(relation);
constr = tupdesc->constr;
if (constr && constr->has_generated_stored)
{
for (int i = 0; i < constr->num_defval; i++)
{
AttrDefault *defval = &constr->defval[i];
Node *expr;
Bitmapset *attrs_used = NULL;
/* skip if not generated column */
if (!TupleDescCompactAttr(tupdesc, defval->adnum - 1)->attgenerated)
continue;
/* identify columns this generated column depends on */
expr = stringToNode(defval->adbin);
pull_varattnos(expr, 1, &attrs_used);
if (bms_overlap(target_cols, attrs_used))
dependentCols = bms_add_member(dependentCols,
defval->adnum - FirstLowInvalidHeapAttributeNumber);
}
}
table_close(relation, NoLock);
return dependentCols;
}
/*
* set_relation_partition_info
*
* Set partitioning scheme and related information for a partitioned table.
*/
static void
set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
Relation relation)
{
PartitionDesc partdesc;
/*
* Create the PartitionDirectory infrastructure if we didn't already.
*/
if (root->glob->partition_directory == NULL)
{
root->glob->partition_directory =
CreatePartitionDirectory(CurrentMemoryContext, true);
}
partdesc = PartitionDirectoryLookup(root->glob->partition_directory,
relation);
rel->part_scheme = find_partition_scheme(root, relation);
Assert(partdesc != NULL && rel->part_scheme != NULL);
rel->boundinfo = partdesc->boundinfo;
rel->nparts = partdesc->nparts;
set_baserel_partition_key_exprs(relation, rel);
set_baserel_partition_constraint(relation, rel);
}
/*
* find_partition_scheme
*
* Find or create a PartitionScheme for this Relation.
*/
static PartitionScheme
find_partition_scheme(PlannerInfo *root, Relation relation)
{
PartitionKey partkey = RelationGetPartitionKey(relation);
ListCell *lc;
int partnatts,
i;
PartitionScheme part_scheme;
/* A partitioned table should have a partition key. */
Assert(partkey != NULL);
partnatts = partkey->partnatts;
/* Search for a matching partition scheme and return if found one. */
foreach(lc, root->part_schemes)
{
part_scheme = lfirst(lc);
/* Match partitioning strategy and number of keys. */
if (partkey->strategy != part_scheme->strategy ||
partnatts != part_scheme->partnatts)
continue;
/* Match partition key type properties. */
if (memcmp(partkey->partopfamily, part_scheme->partopfamily,
sizeof(Oid) * partnatts) != 0 ||
memcmp(partkey->partopcintype, part_scheme->partopcintype,
sizeof(Oid) * partnatts) != 0 ||
memcmp(partkey->partcollation, part_scheme->partcollation,
sizeof(Oid) * partnatts) != 0)
continue;
/*
* Length and byval information should match when partopcintype
* matches.
*/
Assert(memcmp(partkey->parttyplen, part_scheme->parttyplen,
sizeof(int16) * partnatts) == 0);
Assert(memcmp(partkey->parttypbyval, part_scheme->parttypbyval,
sizeof(bool) * partnatts) == 0);
/*
* If partopfamily and partopcintype matched, must have the same
* partition comparison functions. Note that we cannot reliably
* Assert the equality of function structs themselves for they might
* be different across PartitionKey's, so just Assert for the function
* OIDs.
*/
#ifdef USE_ASSERT_CHECKING
for (i = 0; i < partkey->partnatts; i++)
Assert(partkey->partsupfunc[i].fn_oid ==
part_scheme->partsupfunc[i].fn_oid);
#endif
/* Found matching partition scheme. */
return part_scheme;
}
/*
* Did not find matching partition scheme. Create one copying relevant
* information from the relcache. We need to copy the contents of the
* array since the relcache entry may not survive after we have closed the
* relation.
*/
part_scheme = palloc0_object(PartitionSchemeData);
part_scheme->strategy = partkey->strategy;
part_scheme->partnatts = partkey->partnatts;
part_scheme->partopfamily = palloc_array(Oid, partnatts);
memcpy(part_scheme->partopfamily, partkey->partopfamily,
sizeof(Oid) * partnatts);
part_scheme->partopcintype = palloc_array(Oid, partnatts);
memcpy(part_scheme->partopcintype, partkey->partopcintype,
sizeof(Oid) * partnatts);
part_scheme->partcollation = palloc_array(Oid, partnatts);
memcpy(part_scheme->partcollation, partkey->partcollation,
sizeof(Oid) * partnatts);
part_scheme->parttyplen = palloc_array(int16, partnatts);
memcpy(part_scheme->parttyplen, partkey->parttyplen,
sizeof(int16) * partnatts);
part_scheme->parttypbyval = palloc_array(bool, partnatts);
memcpy(part_scheme->parttypbyval, partkey->parttypbyval,
sizeof(bool) * partnatts);
part_scheme->partsupfunc = palloc_array(FmgrInfo, partnatts);
for (i = 0; i < partnatts; i++)
fmgr_info_copy(&part_scheme->partsupfunc[i], &partkey->partsupfunc[i],
CurrentMemoryContext);
/* Add the partitioning scheme to PlannerInfo. */
root->part_schemes = lappend(root->part_schemes, part_scheme);
return part_scheme;
}
/*
* set_baserel_partition_key_exprs
*
* Builds partition key expressions for the given base relation and fills
* rel->partexprs.
*/
static void
set_baserel_partition_key_exprs(Relation relation,
RelOptInfo *rel)
{
PartitionKey partkey = RelationGetPartitionKey(relation);
int partnatts;
int cnt;
List **partexprs;
ListCell *lc;
Index varno = rel->relid;
Assert(IS_SIMPLE_REL(rel) && rel->relid > 0);
/* A partitioned table should have a partition key. */
Assert(partkey != NULL);
partnatts = partkey->partnatts;
partexprs = palloc_array(List *, partnatts);
lc = list_head(partkey->partexprs);
for (cnt = 0; cnt < partnatts; cnt++)
{
Expr *partexpr;
AttrNumber attno = partkey->partattrs[cnt];
if (attno != InvalidAttrNumber)
{
/* Single column partition key is stored as a Var node. */
Assert(attno > 0);
partexpr = (Expr *) makeVar(varno, attno,
partkey->parttypid[cnt],
partkey->parttypmod[cnt],
partkey->parttypcoll[cnt], 0);
}
else
{
if (lc == NULL)
elog(ERROR, "wrong number of partition key expressions");
/* Re-stamp the expression with given varno. */
partexpr = (Expr *) copyObject(lfirst(lc));
ChangeVarNodes((Node *) partexpr, 1, varno, 0);
lc = lnext(partkey->partexprs, lc);
}
/* Base relations have a single expression per key. */
partexprs[cnt] = list_make1(partexpr);
}
rel->partexprs = partexprs;
/*
* A base relation does not have nullable partition key expressions, since
* no outer join is involved. We still allocate an array of empty
* expression lists to keep partition key expression handling code simple.
* See build_joinrel_partition_info() and match_expr_to_partition_keys().
*/
rel->nullable_partexprs = palloc0_array(List *, partnatts);
}
/*
* set_baserel_partition_constraint
*
* Builds the partition constraint for the given base relation and sets it
* in the given RelOptInfo. All Var nodes are restamped with the relid of the
* given relation.
*/
static void
set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
{
List *partconstr;
if (rel->partition_qual) /* already done */
return;
/*
* Run the partition quals through const-simplification similar to check
* constraints. We skip canonicalize_qual, though, because partition
* quals should be in canonical form already; also, since the qual is in
* implicit-AND format, we'd have to explicitly convert it to explicit-AND
* format and back again.
*/
partconstr = RelationGetPartitionQual(relation);
if (partconstr)
{
partconstr = (List *) expression_planner((Expr *) partconstr);
if (rel->relid != 1)
ChangeVarNodes((Node *) partconstr, 1, rel->relid, 0);
rel->partition_qual = partconstr;
}
}
./rel.h 0000664 0001750 0001750 00000062567 15221166247 010666 0 ustar xman xman /*-------------------------------------------------------------------------
*
* rel.h
* POSTGRES relation descriptor (a/k/a relcache entry) definitions.
*
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/utils/rel.h
*
*-------------------------------------------------------------------------
*/
#ifndef REL_H
#define REL_H
#include "access/tupdesc.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
#include "storage/block.h"
#include "storage/relfilelocator.h"
#include "storage/smgr.h"
#include "utils/relcache.h"
#include "utils/reltrigger.h"
/*
* LockRelId and LockInfo really belong to lmgr.h, but it's more convenient
* to declare them here so we can have a LockInfoData field in a Relation.
*/
typedef struct LockRelId
{
Oid relId; /* a relation identifier */
Oid dbId; /* a database identifier */
} LockRelId;
typedef struct LockInfoData
{
LockRelId lockRelId;
} LockInfoData;
typedef LockInfoData *LockInfo;
/*
* Here are the contents of a relation cache entry.
*/
typedef struct RelationData
{
RelFileLocator rd_locator; /* relation physical identifier */
SMgrRelation rd_smgr; /* cached file handle, or NULL */
int rd_refcnt; /* reference count */
ProcNumber rd_backend; /* owning backend's proc number, if temp rel */
bool rd_islocaltemp; /* rel is a temp rel of this session */
bool rd_isnailed; /* rel is nailed in cache */
bool rd_isvalid; /* relcache entry is valid */
bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and
* rd_replidindex) */
bool rd_statvalid; /* is rd_statlist valid? */
/*----------
* rd_createSubid is the ID of the highest subtransaction the rel has
* survived into or zero if the rel or its storage was created before the
* current top transaction. (IndexStmt.oldNumber leads to the case of a new
* rel with an old rd_locator.) rd_firstRelfilelocatorSubid is the ID of the
* highest subtransaction an rd_locator change has survived into or zero if
* rd_locator matches the value it had at the start of the current top
* transaction. (Rolling back the subtransaction that
* rd_firstRelfilelocatorSubid denotes would restore rd_locator to the value it
* had at the start of the current top transaction. Rolling back any
* lower subtransaction would not.) Their accuracy is critical to
* RelationNeedsWAL().
*
* rd_newRelfilelocatorSubid is the ID of the highest subtransaction the
* most-recent relfilenumber change has survived into or zero if not changed
* in the current transaction (or we have forgotten changing it). This
* field is accurate when non-zero, but it can be zero when a relation has
* multiple new relfilenumbers within a single transaction, with one of them
* occurring in a subsequently aborted subtransaction, e.g.
* BEGIN;
* TRUNCATE t;
* SAVEPOINT save;
* TRUNCATE t;
* ROLLBACK TO save;
* -- rd_newRelfilelocatorSubid is now forgotten
*
* If every rd_*Subid field is zero, they are read-only outside
* relcache.c. Files that trigger rd_locator changes by updating
* pg_class.reltablespace and/or pg_class.relfilenode call
* RelationAssumeNewRelfilelocator() to update rd_*Subid.
*
* rd_droppedSubid is the ID of the highest subtransaction that a drop of
* the rel has survived into. In entries visible outside relcache.c, this
* is always zero.
*/
SubTransactionId rd_createSubid; /* rel was created in current xact */
SubTransactionId rd_newRelfilelocatorSubid; /* highest subxact changing
* rd_locator to current value */
SubTransactionId rd_firstRelfilelocatorSubid; /* highest subxact
* changing rd_locator to
* any value */
SubTransactionId rd_droppedSubid; /* dropped with another Subid set */
Form_pg_class rd_rel; /* RELATION tuple */
TupleDesc rd_att; /* tuple descriptor */
Oid rd_id; /* relation's object id */
LockInfoData rd_lockInfo; /* lock mgr's info for locking relation */
RuleLock *rd_rules; /* rewrite rules */
MemoryContext rd_rulescxt; /* private memory cxt for rd_rules, if any */
TriggerDesc *trigdesc; /* Trigger info, or NULL if rel has none */
/* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
/* data managed by RelationGetFKeyList: */
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
/* data managed by RelationGetPartitionKey: */
PartitionKey rd_partkey; /* partition key, or NULL */
MemoryContext rd_partkeycxt; /* private context for rd_partkey, if any */
/* data managed by RelationGetPartitionDesc: */
PartitionDesc rd_partdesc; /* partition descriptor, or NULL */
MemoryContext rd_pdcxt; /* private context for rd_partdesc, if any */
/* Same as above, for partdescs that omit detached partitions */
PartitionDesc rd_partdesc_nodetached; /* partdesc w/o detached parts */
MemoryContext rd_pddcxt; /* for rd_partdesc_nodetached, if any */
/*
* pg_inherits.xmin of the partition that was excluded in
* rd_partdesc_nodetached. This informs a future user of that partdesc:
* if this value is not in progress for the active snapshot, then the
* partdesc can be used, otherwise they have to build a new one. (This
* matches what find_inheritance_children_extended would do).
*/
TransactionId rd_partdesc_nodetached_xmin;
/* data managed by RelationGetPartitionQual: */
List *rd_partcheck; /* partition CHECK quals */
bool rd_partcheckvalid; /* true if list has been computed */
MemoryContext rd_partcheckcxt; /* private cxt for rd_partcheck, if any */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_pkindex; /* OID of (deferrable?) primary key, if any */
bool rd_ispkdeferrable; /* is rd_pkindex a deferrable PK? */
Oid rd_replidindex; /* OID of replica identity index, if any */
/* data managed by RelationGetStatExtList: */
List *rd_statlist; /* list of OIDs of extended stats */
/* data managed by RelationGetIndexAttrBitmap: */
bool rd_attrsvalid; /* are bitmaps of attrs valid? */
Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */
Bitmapset *rd_pkattr; /* cols included in primary key */
Bitmapset *rd_idattr; /* included in replica identity index */
Bitmapset *rd_hotblockingattr; /* cols blocking HOT update */
Bitmapset *rd_summarizedattr; /* cols indexed by summarizing indexes */
PublicationDesc *rd_pubdesc; /* publication descriptor, or NULL */
/*
* rd_options is set whenever rd_rel is loaded into the relcache entry.
* Note that you can NOT look into rd_rel for this data. NULL means "use
* defaults".
*/
bytea *rd_options; /* parsed pg_class.reloptions */
/*
* Oid of the handler for this relation. For an index this is a function
* returning IndexAmRoutine, for table like relations a function returning
* TableAmRoutine. This is stored separately from rd_indam, rd_tableam as
* its lookup requires syscache access, but during relcache bootstrap we
* need to be able to initialize rd_tableam without syscache lookups.
*/
Oid rd_amhandler; /* OID of index AM's handler function */
/*
* Table access method.
*/
const struct TableAmRoutine *rd_tableam;
/* These are non-NULL only for an index relation: */
Form_pg_index rd_index; /* pg_index tuple describing this index */
/* use "struct" here to avoid needing to include htup.h: */
struct HeapTupleData *rd_indextuple; /* all of pg_index tuple */
/*
* index access support info (used only for an index relation)
*
* Note: only default support procs for each opclass are cached, namely
* those with lefttype and righttype equal to the opclass's opcintype. The
* arrays are indexed by support function number, which is a sufficient
* identifier given that restriction.
*/
MemoryContext rd_indexcxt; /* private memory cxt for this stuff */
/* use "struct" here to avoid needing to include amapi.h: */
const struct IndexAmRoutine *rd_indam; /* index AM's API struct */
Oid *rd_opfamily; /* OIDs of op families for each index col */
Oid *rd_opcintype; /* OIDs of opclass declared input data types */
RegProcedure *rd_support; /* OIDs of support procedures */
struct FmgrInfo *rd_supportinfo; /* lookup info for support procedures */
int16 *rd_indoption; /* per-column AM-specific flags */
List *rd_indexprs; /* index expression trees, if any */
List *rd_indexprsExpand; /* expanded index expression trees, if any */
List *rd_indpred; /* index predicate tree, if any */
List *rd_indpredExpand; /* expanded index predicate tree, if any */
Oid *rd_exclops; /* OIDs of exclusion operators, if any */
Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */
uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */
Oid *rd_indcollation; /* OIDs of index collations */
bytea **rd_opcoptions; /* parsed opclass-specific options */
/*
* rd_amcache is available for index and table AMs to cache private data
* about the relation. This must be just a cache since it may get reset
* at any time (in particular, it will get reset by a relcache inval
* message for the relation). If used, it must point to a single memory
* chunk palloc'd in CacheMemoryContext, or in rd_indexcxt for an index
* relation. A relcache reset will include freeing that chunk and setting
* rd_amcache = NULL.
*/
void *rd_amcache; /* available for use by index/table AM */
/*
* foreign-table support
*
* rd_fdwroutine must point to a single memory chunk palloc'd in
* CacheMemoryContext. It will be freed and reset to NULL on a relcache
* reset.
*/
/* use "struct" here to avoid needing to include fdwapi.h: */
struct FdwRoutine *rd_fdwroutine; /* cached function pointers, or NULL */
/*
* Hack for CLUSTER, rewriting ALTER TABLE, etc: when writing a new
* version of a table, we need to make any toast pointers inserted into it
* have the existing toast table's OID, not the OID of the transient toast
* table. If rd_toastoid isn't InvalidOid, it is the OID to place in
* toast pointers inserted into this rel. (Note it's set on the new
* version of the main heap, not the toast table itself.) This also
* causes toast_save_datum() to try to preserve toast value OIDs.
*/
Oid rd_toastoid; /* Real TOAST table's OID, or InvalidOid */
bool pgstat_enabled; /* should relation stats be counted */
/* use "struct" here to avoid needing to include pgstat.h: */
struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
} RelationData;
/*
* ForeignKeyCacheInfo
* Information the relcache can cache about foreign key constraints
*
* This is basically just an image of relevant columns from pg_constraint.
* We make it a subclass of Node so that copyObject() can be used on a list
* of these, but we also ensure it is a "flat" object without substructure,
* so that list_free_deep() is sufficient to free such a list.
* The per-FK-column arrays can be fixed-size because we allow at most
* INDEX_MAX_KEYS columns in a foreign key constraint.
*
* Currently, we mostly cache fields of interest to the planner, but the set
* of fields has already grown the constraint OID for other uses.
*/
typedef struct ForeignKeyCacheInfo
{
pg_node_attr(no_equal, no_read, no_query_jumble)
NodeTag type;
/* oid of the constraint itself */
Oid conoid;
/* relation constrained by the foreign key */
Oid conrelid;
/* relation referenced by the foreign key */
Oid confrelid;
/* number of columns in the foreign key */
int nkeys;
/* Is enforced ? */
bool conenforced;
/*
* these arrays each have nkeys valid entries:
*/
/* cols in referencing table */
AttrNumber conkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
/* cols in referenced table */
AttrNumber confkey[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
/* PK = FK operator OIDs */
Oid conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
} ForeignKeyCacheInfo;
/*
* StdRdOptions
* Standard contents of rd_options for heaps.
*
* RelationGetFillFactor() and RelationGetTargetPageFreeSpace() can only
* be applied to relations that use this format or a superset for
* private options data.
*/
/* autovacuum-related reloptions. */
typedef struct AutoVacOpts
{
bool enabled;
int autovacuum_parallel_workers;
int vacuum_threshold;
int vacuum_max_threshold;
int vacuum_ins_threshold;
int analyze_threshold;
int vacuum_cost_limit;
int freeze_min_age;
int freeze_max_age;
int freeze_table_age;
int multixact_freeze_min_age;
int multixact_freeze_max_age;
int multixact_freeze_table_age;
int log_vacuum_min_duration;
int log_analyze_min_duration;
float8 vacuum_cost_delay;
float8 vacuum_scale_factor;
float8 vacuum_ins_scale_factor;
float8 analyze_scale_factor;
} AutoVacOpts;
/* StdRdOptions->vacuum_index_cleanup values */
typedef enum StdRdOptIndexCleanup
{
STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
} StdRdOptIndexCleanup;
typedef struct StdRdOptions
{
int32 vl_len_; /* varlena header (do not touch directly!) */
int fillfactor; /* page fill factor in percent (0..100) */
int toast_tuple_target; /* target for tuple toasting */
AutoVacOpts autovacuum; /* autovacuum-related options */
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
* to freeze. 0 if disabled, -1 if unspecified.
*/
double vacuum_max_eager_freeze_failure_rate;
} StdRdOptions;
#define HEAP_MIN_FILLFACTOR 10
#define HEAP_DEFAULT_FILLFACTOR 100
/*
* RelationGetToastTupleTarget
* Returns the relation's toast_tuple_target. Note multiple eval of argument!
*/
#define RelationGetToastTupleTarget(relation, defaulttarg) \
((relation)->rd_options ? \
((StdRdOptions *) (relation)->rd_options)->toast_tuple_target : (defaulttarg))
/*
* RelationGetFillFactor
* Returns the relation's fillfactor. Note multiple eval of argument!
*/
#define RelationGetFillFactor(relation, defaultff) \
((relation)->rd_options ? \
((StdRdOptions *) (relation)->rd_options)->fillfactor : (defaultff))
/*
* RelationGetTargetPageUsage
* Returns the relation's desired space usage per page in bytes.
*/
#define RelationGetTargetPageUsage(relation, defaultff) \
(BLCKSZ * RelationGetFillFactor(relation, defaultff) / 100)
/*
* RelationGetTargetPageFreeSpace
* Returns the relation's desired freespace per page in bytes.
*/
#define RelationGetTargetPageFreeSpace(relation, defaultff) \
(BLCKSZ * (100 - RelationGetFillFactor(relation, defaultff)) / 100)
/*
* RelationIsUsedAsCatalogTable
* Returns whether the relation should be treated as a catalog table
* from the pov of logical decoding. Note multiple eval of argument!
*/
#define RelationIsUsedAsCatalogTable(relation) \
((relation)->rd_options && \
((relation)->rd_rel->relkind == RELKIND_RELATION || \
(relation)->rd_rel->relkind == RELKIND_MATVIEW) ? \
((StdRdOptions *) (relation)->rd_options)->user_catalog_table : false)
/*
* RelationGetParallelWorkers
* Returns the relation's parallel_workers reloption setting.
* Note multiple eval of argument!
*/
#define RelationGetParallelWorkers(relation, defaultpw) \
((relation)->rd_options ? \
((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
/* ViewOptions->check_option values */
typedef enum ViewOptCheckOption
{
VIEW_OPTION_CHECK_OPTION_NOT_SET,
VIEW_OPTION_CHECK_OPTION_LOCAL,
VIEW_OPTION_CHECK_OPTION_CASCADED,
} ViewOptCheckOption;
/*
* ViewOptions
* Contents of rd_options for views
*/
typedef struct ViewOptions
{
int32 vl_len_; /* varlena header (do not touch directly!) */
bool security_barrier;
bool security_invoker;
ViewOptCheckOption check_option;
} ViewOptions;
/*
* RelationIsSecurityView
* Returns whether the relation is security view, or not. Note multiple
* eval of argument!
*/
#define RelationIsSecurityView(relation) \
(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW), \
(relation)->rd_options ? \
((ViewOptions *) (relation)->rd_options)->security_barrier : false)
/*
* RelationHasSecurityInvoker
* Returns true if the relation has the security_invoker property set.
* Note multiple eval of argument!
*/
#define RelationHasSecurityInvoker(relation) \
(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW), \
(relation)->rd_options ? \
((ViewOptions *) (relation)->rd_options)->security_invoker : false)
/*
* RelationHasCheckOption
* Returns true if the relation is a view defined with either the local
* or the cascaded check option. Note multiple eval of argument!
*/
#define RelationHasCheckOption(relation) \
(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW), \
(relation)->rd_options && \
((ViewOptions *) (relation)->rd_options)->check_option != \
VIEW_OPTION_CHECK_OPTION_NOT_SET)
/*
* RelationHasLocalCheckOption
* Returns true if the relation is a view defined with the local check
* option. Note multiple eval of argument!
*/
#define RelationHasLocalCheckOption(relation) \
(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW), \
(relation)->rd_options && \
((ViewOptions *) (relation)->rd_options)->check_option == \
VIEW_OPTION_CHECK_OPTION_LOCAL)
/*
* RelationHasCascadedCheckOption
* Returns true if the relation is a view defined with the cascaded check
* option. Note multiple eval of argument!
*/
#define RelationHasCascadedCheckOption(relation) \
(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW), \
(relation)->rd_options && \
((ViewOptions *) (relation)->rd_options)->check_option == \
VIEW_OPTION_CHECK_OPTION_CASCADED)
/*
* RelationIsValid
* True iff relation descriptor is valid.
*/
#define RelationIsValid(relation) ((relation) != NULL)
/*
* RelationHasReferenceCountZero
* True iff relation reference count is zero.
*
* Note:
* Assumes relation descriptor is valid.
*/
#define RelationHasReferenceCountZero(relation) \
((bool)((relation)->rd_refcnt == 0))
/*
* RelationGetForm
* Returns pg_class tuple for a relation.
*
* Note:
* Assumes relation descriptor is valid.
*/
#define RelationGetForm(relation) ((relation)->rd_rel)
/*
* RelationGetRelid
* Returns the OID of the relation
*/
#define RelationGetRelid(relation) ((relation)->rd_id)
/*
* RelationGetNumberOfAttributes
* Returns the total number of attributes in a relation.
*/
#define RelationGetNumberOfAttributes(relation) ((relation)->rd_rel->relnatts)
/*
* IndexRelationGetNumberOfAttributes
* Returns the number of attributes in an index.
*/
#define IndexRelationGetNumberOfAttributes(relation) \
((relation)->rd_index->indnatts)
/*
* IndexRelationGetNumberOfKeyAttributes
* Returns the number of key attributes in an index.
*/
#define IndexRelationGetNumberOfKeyAttributes(relation) \
((relation)->rd_index->indnkeyatts)
/*
* RelationGetDescr
* Returns tuple descriptor for a relation.
*/
#define RelationGetDescr(relation) ((relation)->rd_att)
/*
* RelationGetRelationName
* Returns the rel's name.
*
* Note that the name is only unique within the containing namespace.
*/
#define RelationGetRelationName(relation) \
(NameStr((relation)->rd_rel->relname))
/*
* RelationGetNamespace
* Returns the rel's namespace OID.
*/
#define RelationGetNamespace(relation) \
((relation)->rd_rel->relnamespace)
/*
* RelationIsMapped
* True if the relation uses the relfilenumber map. Note multiple eval
* of argument!
*/
#define RelationIsMapped(relation) \
(RELKIND_HAS_STORAGE((relation)->rd_rel->relkind) && \
((relation)->rd_rel->relfilenode == InvalidRelFileNumber))
#ifndef FRONTEND
/*
* RelationGetSmgr
* Returns smgr file handle for a relation, opening it if needed.
*
* Very little code is authorized to touch rel->rd_smgr directly. Instead
* use this function to fetch its value.
*/
static inline SMgrRelation
RelationGetSmgr(Relation rel)
{
if (unlikely(rel->rd_smgr == NULL))
{
rel->rd_smgr = smgropen(rel->rd_locator, rel->rd_backend);
smgrpin(rel->rd_smgr);
}
return rel->rd_smgr;
}
/*
* RelationCloseSmgr
* Close the relation at the smgr level, if not already done.
*/
static inline void
RelationCloseSmgr(Relation relation)
{
if (relation->rd_smgr != NULL)
{
smgrunpin(relation->rd_smgr);
smgrclose(relation->rd_smgr);
relation->rd_smgr = NULL;
}
}
#endif /* !FRONTEND */
/*
* RelationGetTargetBlock
* Fetch relation's current insertion target block.
*
* Returns InvalidBlockNumber if there is no current target block. Note
* that the target block status is discarded on any smgr-level invalidation,
* so there's no need to re-open the smgr handle if it's not currently open.
*/
#define RelationGetTargetBlock(relation) \
( (relation)->rd_smgr != NULL ? (relation)->rd_smgr->smgr_targblock : InvalidBlockNumber )
/*
* RelationSetTargetBlock
* Set relation's current insertion target block.
*/
#define RelationSetTargetBlock(relation, targblock) \
do { \
RelationGetSmgr(relation)->smgr_targblock = (targblock); \
} while (0)
/*
* RelationIsPermanent
* True if relation is permanent.
*/
#define RelationIsPermanent(relation) \
((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
/*
* RelationNeedsWAL
* True if relation needs WAL.
*
* Returns false if wal_level = minimal and this relation is created or
* truncated in the current transaction. See "Skipping WAL for New
* RelFileLocator" in src/backend/access/transam/README.
*/
#define RelationNeedsWAL(relation) \
(RelationIsPermanent(relation) && (XLogIsNeeded() || \
(relation->rd_createSubid == InvalidSubTransactionId && \
relation->rd_firstRelfilelocatorSubid == InvalidSubTransactionId)))
/*
* RelationUsesLocalBuffers
* True if relation's pages are stored in local buffers.
*/
#define RelationUsesLocalBuffers(relation) \
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
/*
* RELATION_IS_LOCAL
* If a rel is either temp or newly created in the current transaction,
* it can be assumed to be accessible only to the current backend.
* This is typically used to decide that we can skip acquiring locks.
*
* Beware of multiple eval of argument
*/
#define RELATION_IS_LOCAL(relation) \
((relation)->rd_islocaltemp || \
(relation)->rd_createSubid != InvalidSubTransactionId)
/*
* RELATION_IS_OTHER_TEMP
* Test for a temporary relation that belongs to some other session.
*
* Reading another session's temp-table data through never works right:
* the owning session keeps the data in its private local buffer pool,
* which we cannot access. Existing buffer-manager entry points
* (ReadBuffer_common(), StartReadBuffersImpl(), read_stream_begin_impl(),
* and PrefetchBuffer()) already enforce this; any new buffer-access entry
* points must do the same. Command-level code (TRUNCATE, ALTER TABLE,
* VACUUM, CLUSTER, REINDEX, ...) additionally uses this macro for
* command-specific error messages.
*
* Beware of multiple eval of argument
*/
#define RELATION_IS_OTHER_TEMP(relation) \
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
/*
* RelationIsScannable
* Currently can only be false for a materialized view which has not been
* populated by its query. This is likely to get more complicated later,
* so use a macro which looks like a function.
*/
#define RelationIsScannable(relation) ((relation)->rd_rel->relispopulated)
/*
* RelationIsPopulated
* Currently, we don't physically distinguish the "populated" and
* "scannable" properties of matviews, but that may change later.
* Hence, use the appropriate one of these macros in code tests.
*/
#define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
/*
* RelationIsAccessibleInLogicalDecoding
* True if we need to log enough information to have access via
* decoding snapshot.
*/
#define RelationIsAccessibleInLogicalDecoding(relation) \
(XLogLogicalInfoActive() && \
RelationNeedsWAL(relation) && \
(IsCatalogRelation(relation) || RelationIsUsedAsCatalogTable(relation)))
/*
* RelationIsLogicallyLogged
* True if we need to log enough information to extract the data from the
* WAL stream.
*
* We don't log information for unlogged tables (since they don't WAL log
* anyway), for foreign tables (since they don't WAL log, either),
* and for system tables (their content is hard to make sense of, and
* it would complicate decoding slightly for little gain). Note that we *do*
* log information for user defined catalog tables since they presumably are
* interesting to the user...
*/
#define RelationIsLogicallyLogged(relation) \
(XLogLogicalInfoActive() && \
RelationNeedsWAL(relation) && \
(relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE && \
!IsCatalogRelation(relation))
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
#endif /* REL_H */
./relcache.c 0000664 0001750 0001750 00000671474 15222150025 011634 0 ustar xman xman /*-------------------------------------------------------------------------
*
* relcache.c
* POSTGRES relation descriptor cache code
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/utils/cache/relcache.c
*
*-------------------------------------------------------------------------
*/
/*
* INTERFACE ROUTINES
* RelationCacheInitialize - initialize relcache (to empty)
* RelationCacheInitializePhase2 - initialize shared-catalog entries
* RelationCacheInitializePhase3 - finish initializing relcache
* RelationIdGetRelation - get a reldesc by relation id
* RelationClose - close an open relation
*
* NOTES
* The following code contains many undocumented hacks. Please be
* careful....
*/
#include "postgres.h"
#include <sys/file.h>
#include <fcntl.h>
#include <unistd.h>
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/parallel.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "access/tableam.h"
#include "access/tupdesc_details.h"
#include "access/xact.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
#include "commands/publicationcmds.h"
#include "commands/trigger.h"
#include "common/int.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "pgstat.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rowsecurity.h"
#include "storage/fd.h"
#include "storage/lmgr.h"
#include "storage/lock.h"
#include "storage/smgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/relmapper.h"
#include "utils/resowner.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "rewrite/rewriteHandler.h"
#define RELCACHE_INIT_FILEMAGIC 0x573266 /* version ID value */
/*
* Whether to bother checking if relation cache memory needs to be freed
* eagerly. See also RelationBuildDesc() and pg_config_manual.h.
*/
#if defined(RECOVER_RELATION_BUILD_MEMORY) && (RECOVER_RELATION_BUILD_MEMORY != 0)
#define MAYBE_RECOVER_RELATION_BUILD_MEMORY 1
#else
#define RECOVER_RELATION_BUILD_MEMORY 0
#ifdef DISCARD_CACHES_ENABLED
#define MAYBE_RECOVER_RELATION_BUILD_MEMORY 1
#endif
#endif
/*
* hardcoded tuple descriptors, contents generated by genbki.pl
*/
static const FormData_pg_attribute Desc_pg_class[Natts_pg_class] = {Schema_pg_class};
static const FormData_pg_attribute Desc_pg_attribute[Natts_pg_attribute] = {Schema_pg_attribute};
static const FormData_pg_attribute Desc_pg_proc[Natts_pg_proc] = {Schema_pg_proc};
static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type};
static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
static const FormData_pg_attribute Desc_pg_subscription[Natts_pg_subscription] = {Schema_pg_subscription};
/*
* Hash tables that index the relation cache
*
* We used to index the cache by both name and OID, but now there
* is only an index by OID.
*/
typedef struct relidcacheent
{
Oid reloid;
Relation reldesc;
} RelIdCacheEnt;
static HTAB *RelationIdCache;
/*
* This flag is false until we have prepared the critical relcache entries
* that are needed to do indexscans on the tables read by relcache building.
*/
bool criticalRelcachesBuilt = false;
/*
* This flag is false until we have prepared the critical relcache entries
* for shared catalogs (which are the tables needed for login).
*/
bool criticalSharedRelcachesBuilt = false;
/*
* This counter counts relcache inval events received since backend startup
* (but only for rels that are actually in cache). Presently, we use it only
* to detect whether data about to be written by write_relcache_init_file()
* might already be obsolete.
*/
static long relcacheInvalsReceived = 0L;
/*
* in_progress_list is a stack of ongoing RelationBuildDesc() calls. CREATE
* INDEX CONCURRENTLY makes catalog changes under ShareUpdateExclusiveLock.
* It critically relies on each backend absorbing those changes no later than
* next transaction start. Hence, RelationBuildDesc() loops until it finishes
* without accepting a relevant invalidation. (Most invalidation consumers
* don't do this.)
*/
typedef struct inprogressent
{
Oid reloid; /* OID of relation being built */
bool invalidated; /* whether an invalidation arrived for it */
} InProgressEnt;
static InProgressEnt *in_progress_list;
static int in_progress_list_len;
static int in_progress_list_maxlen;
/*
* eoxact_list[] stores the OIDs of relations that (might) need AtEOXact
* cleanup work. This list intentionally has limited size; if it overflows,
* we fall back to scanning the whole hashtable. There is no value in a very
* large list because (1) at some point, a hash_seq_search scan is faster than
* retail lookups, and (2) the value of this is to reduce EOXact work for
* short transactions, which can't have dirtied all that many tables anyway.
* EOXactListAdd() does not bother to prevent duplicate list entries, so the
* cleanup processing must be idempotent.
*/
#define MAX_EOXACT_LIST 32
static Oid eoxact_list[MAX_EOXACT_LIST];
static int eoxact_list_len = 0;
static bool eoxact_list_overflowed = false;
#define EOXactListAdd(rel) \
do { \
if (eoxact_list_len < MAX_EOXACT_LIST) \
eoxact_list[eoxact_list_len++] = (rel)->rd_id; \
else \
eoxact_list_overflowed = true; \
} while (0)
/*
* EOXactTupleDescArray stores TupleDescs that (might) need AtEOXact
* cleanup work. The array expands as needed; there is no hashtable because
* we don't need to access individual items except at EOXact.
*/
static TupleDesc *EOXactTupleDescArray;
static int NextEOXactTupleDescNum = 0;
static int EOXactTupleDescArrayLen = 0;
/*
* macros to manipulate the lookup hashtable
*/
#define RelationCacheInsert(RELATION, replace_allowed) \
do { \
RelIdCacheEnt *hentry; bool found; \
hentry = (RelIdCacheEnt *) hash_search(RelationIdCache, \
&((RELATION)->rd_id), \
HASH_ENTER, &found); \
if (found) \
{ \
/* see comments in RelationBuildDesc and RelationBuildLocalRelation */ \
Relation _old_rel = hentry->reldesc; \
Assert(replace_allowed); \
hentry->reldesc = (RELATION); \
if (RelationHasReferenceCountZero(_old_rel)) \
RelationDestroyRelation(_old_rel, false); \
else if (!IsBootstrapProcessingMode()) \
elog(WARNING, "leaking still-referenced relcache entry for \"%s\"", \
RelationGetRelationName(_old_rel)); \
} \
else \
hentry->reldesc = (RELATION); \
} while(0)
#define RelationIdCacheLookup(ID, RELATION) \
do { \
RelIdCacheEnt *hentry; \
hentry = (RelIdCacheEnt *) hash_search(RelationIdCache, \
&(ID), \
HASH_FIND, NULL); \
if (hentry) \
RELATION = hentry->reldesc; \
else \
RELATION = NULL; \
} while(0)
#define RelationCacheDelete(RELATION) \
do { \
RelIdCacheEnt *hentry; \
hentry = (RelIdCacheEnt *) hash_search(RelationIdCache, \
&((RELATION)->rd_id), \
HASH_REMOVE, NULL); \
if (hentry == NULL) \
elog(WARNING, "failed to delete relcache entry for OID %u", \
(RELATION)->rd_id); \
} while(0)
/*
* Special cache for opclass-related information
*
* Note: only default support procs get cached, ie, those with
* lefttype = righttype = opcintype.
*/
typedef struct opclasscacheent
{
Oid opclassoid; /* lookup key: OID of opclass */
bool valid; /* set true after successful fill-in */
StrategyNumber numSupport; /* max # of support procs (from pg_am) */
Oid opcfamily; /* OID of opclass's family */
Oid opcintype; /* OID of opclass's declared input type */
RegProcedure *supportProcs; /* OIDs of support procedures */
} OpClassCacheEnt;
static HTAB *OpClassCache = NULL;
/* non-export function prototypes */
static void RelationCloseCleanup(Relation relation);
static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
static void RelationInvalidateRelation(Relation relation);
static void RelationClearRelation(Relation relation);
static void RelationRebuildRelation(Relation relation);
static void RelationReloadIndexInfo(Relation relation);
static void RelationReloadNailed(Relation relation);
static void RelationFlushRelation(Relation relation);
static void RememberToFreeTupleDescAtEOX(TupleDesc td);
#ifdef USE_ASSERT_CHECKING
static void AssertPendingSyncConsistency(Relation relation);
#endif
static void AtEOXact_cleanup(Relation relation, bool isCommit);
static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
SubTransactionId mySubid, SubTransactionId parentSubid);
static bool load_relcache_init_file(bool shared);
static void write_relcache_init_file(bool shared);
static void write_item(const void *data, Size len, FILE *fp);
static void formrdesc(const char *relationName, Oid relationReltype,
bool isshared, int natts, const FormData_pg_attribute *attrs);
static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic);
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
static TupleDesc GetPgClassDescriptor(void);
static TupleDesc GetPgIndexDescriptor(void);
static void AttrDefaultFetch(Relation relation, int ndef);
static int AttrDefaultCmp(const void *a, const void *b);
static void CheckNNConstraintFetch(Relation relation);
static int CheckConstraintCmp(const void *a, const void *b);
static void InitIndexAmRoutine(Relation relation);
static void IndexSupportInitialize(oidvector *indclass,
RegProcedure *indexSupport,
Oid *opFamily,
Oid *opcInType,
StrategyNumber maxSupportNumber,
AttrNumber maxAttributeNumber);
static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename, int elevel);
/*
* ScanPgRelation
*
* This is used by RelationBuildDesc to find a pg_class
* tuple matching targetRelId. The caller must hold at least
* AccessShareLock on the target relid to prevent concurrent-update
* scenarios; it isn't guaranteed that all scans used to build the
* relcache entry will use the same snapshot. If, for example,
* an attribute were to be added after scanning pg_class and before
* scanning pg_attribute, relnatts wouldn't match.
*
* NB: the returned tuple has been copied into palloc'd storage
* and must eventually be freed with heap_freetuple.
*/
static HeapTuple
ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic)
{
HeapTuple pg_class_tuple;
Relation pg_class_desc;
SysScanDesc pg_class_scan;
ScanKeyData key[1];
Snapshot snapshot = NULL;
/*
* If something goes wrong during backend startup, we might find ourselves
* trying to read pg_class before we've selected a database. That ain't
* gonna work, so bail out with a useful error message. If this happens,
* it probably means a relcache entry that needs to be nailed isn't.
*/
if (!OidIsValid(MyDatabaseId))
elog(FATAL, "cannot read pg_class without having selected a database");
/*
* form a scan key
*/
ScanKeyInit(&key[0],
Anum_pg_class_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(targetRelId));
/*
* Open pg_class and fetch a tuple. Force heap scan if we haven't yet
* built the critical relcache entries (this includes initdb and startup
* without a pg_internal.init file). The caller can also force a heap
* scan by setting indexOK == false.
*/
pg_class_desc = table_open(RelationRelationId, AccessShareLock);
/*
* The caller might need a tuple that's newer than what's visible to the
* historic snapshot; currently the only case requiring to do so is
* looking up the relfilenumber of non mapped system relations during
* decoding.
*/
if (force_non_historic)
snapshot = RegisterSnapshot(GetNonHistoricCatalogSnapshot(RelationRelationId));
pg_class_scan = systable_beginscan(pg_class_desc, ClassOidIndexId,
indexOK && criticalRelcachesBuilt,
snapshot,
1, key);
pg_class_tuple = systable_getnext(pg_class_scan);
/*
* Must copy tuple before releasing buffer.
*/
if (HeapTupleIsValid(pg_class_tuple))
pg_class_tuple = heap_copytuple(pg_class_tuple);
/* all done */
systable_endscan(pg_class_scan);
if (snapshot)
UnregisterSnapshot(snapshot);
table_close(pg_class_desc, AccessShareLock);
return pg_class_tuple;
}
/*
* AllocateRelationDesc
*
* This is used to allocate memory for a new relation descriptor
* and initialize the rd_rel field from the given pg_class tuple.
*/
static Relation
AllocateRelationDesc(Form_pg_class relp)
{
Relation relation;
MemoryContext oldcxt;
Form_pg_class relationForm;
/* Relcache entries must live in CacheMemoryContext */
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
/*
* allocate and zero space for new relation descriptor
*/
relation = palloc0_object(RelationData);
/* make sure relation is marked as having no open file yet */
relation->rd_smgr = NULL;
/*
* Copy the relation tuple form
*
* We only allocate space for the fixed fields, ie, CLASS_TUPLE_SIZE. The
* variable-length fields (relacl, reloptions) are NOT stored in the
* relcache --- there'd be little point in it, since we don't copy the
* tuple's nulls bitmap and hence wouldn't know if the values are valid.
* Bottom line is that relacl *cannot* be retrieved from the relcache. Get
* it from the syscache if you need it. The same goes for the original
* form of reloptions (however, we do store the parsed form of reloptions
* in rd_options).
*/
relationForm = (Form_pg_class) palloc(CLASS_TUPLE_SIZE);
memcpy(relationForm, relp, CLASS_TUPLE_SIZE);
/* initialize relation tuple form */
relation->rd_rel = relationForm;
/* and allocate attribute tuple form storage */
relation->rd_att = CreateTemplateTupleDesc(relationForm->relnatts);
/* which we mark as a reference-counted tupdesc */
relation->rd_att->tdrefcount = 1;
MemoryContextSwitchTo(oldcxt);
return relation;
}
/*
* RelationParseRelOptions
* Convert pg_class.reloptions into pre-parsed rd_options
*
* tuple is the real pg_class tuple (not rd_rel!) for relation
*
* Note: rd_rel and (if an index) rd_indam must be valid already
*/
static void
RelationParseRelOptions(Relation relation, HeapTuple tuple)
{
bytea *options;
amoptions_function amoptsfn;
relation->rd_options = NULL;
/*
* Look up any AM-specific parse function; fall out if relkind should not
* have options.
*/
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
amoptsfn = NULL;
break;
case RELKIND_INDEX:
case RELKIND_PARTITIONED_INDEX:
amoptsfn = relation->rd_indam->amoptions;
break;
default:
return;
}
/*
* Fetch reloptions from tuple; have to use a hardwired descriptor because
* we might not have any other for pg_class yet (consider executing this
* code for pg_class itself)
*/
options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn);
/*
* Copy parsed data into CacheMemoryContext. To guard against the
* possibility of leaks in the reloptions code, we want to do the actual
* parsing in the caller's memory context and copy the results into
* CacheMemoryContext after the fact.
*/
if (options)
{
relation->rd_options = MemoryContextAlloc(CacheMemoryContext,
VARSIZE(options));
memcpy(relation->rd_options, options, VARSIZE(options));
pfree(options);
}
}
/*
* RelationBuildTupleDesc
*
* Form the relation's tuple descriptor from information in
* the pg_attribute, pg_attrdef & pg_constraint system catalogs.
*/
static void
RelationBuildTupleDesc(Relation relation)
{
HeapTuple pg_attribute_tuple;
Relation pg_attribute_desc;
SysScanDesc pg_attribute_scan;
ScanKeyData skey[2];
int need;
TupleConstr *constr;
AttrMissing *attrmiss = NULL;
int ndef = 0;
/* fill rd_att's type ID fields (compare heap.c's AddNewRelationTuple) */
relation->rd_att->tdtypeid =
relation->rd_rel->reltype ? relation->rd_rel->reltype : RECORDOID;
relation->rd_att->tdtypmod = -1; /* just to be sure */
constr = (TupleConstr *) MemoryContextAllocZero(CacheMemoryContext,
sizeof(TupleConstr));
/*
* Form a scan key that selects only user attributes (attnum > 0).
* (Eliminating system attribute rows at the index level is lots faster
* than fetching them.)
*/
ScanKeyInit(&skey[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
ScanKeyInit(&skey[1],
Anum_pg_attribute_attnum,
BTGreaterStrategyNumber, F_INT2GT,
Int16GetDatum(0));
/*
* Open pg_attribute and begin a scan. Force heap scan if we haven't yet
* built the critical relcache entries (this includes initdb and startup
* without a pg_internal.init file).
*/
pg_attribute_desc = table_open(AttributeRelationId, AccessShareLock);
pg_attribute_scan = systable_beginscan(pg_attribute_desc,
AttributeRelidNumIndexId,
criticalRelcachesBuilt,
NULL,
2, skey);
/*
* add attribute data to relation->rd_att
*/
need = RelationGetNumberOfAttributes(relation);
while (HeapTupleIsValid(pg_attribute_tuple = systable_getnext(pg_attribute_scan)))
{
Form_pg_attribute attp;
int attnum;
attp = (Form_pg_attribute) GETSTRUCT(pg_attribute_tuple);
attnum = attp->attnum;
if (attnum <= 0 || attnum > RelationGetNumberOfAttributes(relation))
elog(ERROR, "invalid attribute number %d for relation \"%s\"",
attp->attnum, RelationGetRelationName(relation));
memcpy(TupleDescAttr(relation->rd_att, attnum - 1),
attp,
ATTRIBUTE_FIXED_PART_SIZE);
populate_compact_attribute(relation->rd_att, attnum - 1);
/* Update constraint/default info */
if (attp->attnotnull)
constr->has_not_null = true;
if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
constr->has_generated_stored = true;
if (attp->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
constr->has_generated_virtual = true;
if (attp->atthasdef)
ndef++;
/* If the column has a "missing" value, put it in the attrmiss array */
if (attp->atthasmissing)
{
Datum missingval;
bool missingNull;
/* Do we have a missing value? */
missingval = heap_getattr(pg_attribute_tuple,
Anum_pg_attribute_attmissingval,
pg_attribute_desc->rd_att,
&missingNull);
if (!missingNull)
{
/* Yes, fetch from the array */
MemoryContext oldcxt;
bool is_null;
int one = 1;
Datum missval;
if (attrmiss == NULL)
attrmiss = (AttrMissing *)
MemoryContextAllocZero(CacheMemoryContext,
relation->rd_rel->relnatts *
sizeof(AttrMissing));
missval = array_get_element(missingval,
1,
&one,
-1,
attp->attlen,
attp->attbyval,
attp->attalign,
&is_null);
Assert(!is_null);
if (attp->attbyval)
{
/* for copy by val just copy the datum direct */
attrmiss[attnum - 1].am_value = missval;
}
else
{
/* otherwise copy in the correct context */
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
attrmiss[attnum - 1].am_value = datumCopy(missval,
attp->attbyval,
attp->attlen);
MemoryContextSwitchTo(oldcxt);
}
attrmiss[attnum - 1].am_present = true;
}
}
need--;
if (need == 0)
break;
}
/*
* end the scan and close the attribute relation
*/
systable_endscan(pg_attribute_scan);
table_close(pg_attribute_desc, AccessShareLock);
if (need != 0)
elog(ERROR, "pg_attribute catalog is missing %d attribute(s) for relation OID %u",
need, RelationGetRelid(relation));
/*
* Set up constraint/default info
*/
if (constr->has_not_null ||
constr->has_generated_stored ||
constr->has_generated_virtual ||
ndef > 0 ||
attrmiss ||
relation->rd_rel->relchecks > 0)
{
bool is_catalog = IsCatalogRelation(relation);
relation->rd_att->constr = constr;
if (ndef > 0) /* DEFAULTs */
AttrDefaultFetch(relation, ndef);
else
constr->num_defval = 0;
constr->missing = attrmiss;
/* CHECK and NOT NULLs */
if (relation->rd_rel->relchecks > 0 ||
(!is_catalog && constr->has_not_null))
CheckNNConstraintFetch(relation);
/*
* Any not-null constraint that wasn't marked invalid by
* CheckNNConstraintFetch must necessarily be valid; make it so in the
* CompactAttribute array.
*/
if (!is_catalog)
{
for (int i = 0; i < relation->rd_rel->relnatts; i++)
{
CompactAttribute *attr;
attr = TupleDescCompactAttr(relation->rd_att, i);
if (attr->attnullability == ATTNULLABLE_UNKNOWN)
attr->attnullability = ATTNULLABLE_VALID;
else
Assert(attr->attnullability == ATTNULLABLE_INVALID ||
attr->attnullability == ATTNULLABLE_UNRESTRICTED);
}
}
if (relation->rd_rel->relchecks == 0)
constr->num_check = 0;
}
else
{
pfree(constr);
relation->rd_att->constr = NULL;
}
TupleDescFinalize(relation->rd_att);
}
/*
* RelationBuildRuleLock
*
* Form the relation's rewrite rules from information in
* the pg_rewrite system catalog.
*
* Note: The rule parsetrees are potentially very complex node structures.
* To allow these trees to be freed when the relcache entry is flushed,
* we make a private memory context to hold the RuleLock information for
* each relcache entry that has associated rules. The context is used
* just for rule info, not for any other subsidiary data of the relcache
* entry, because that keeps the update logic in RelationRebuildRelation()
* manageable. The other subsidiary data structures are simple enough
* to be easy to free explicitly, anyway.
*
* Note: The relation's reloptions must have been extracted first.
*/
static void
RelationBuildRuleLock(Relation relation)
{
MemoryContext rulescxt;
MemoryContext oldcxt;
HeapTuple rewrite_tuple;
Relation rewrite_desc;
TupleDesc rewrite_tupdesc;
SysScanDesc rewrite_scan;
ScanKeyData key;
RuleLock *rulelock;
int numlocks;
RewriteRule **rules;
int maxlocks;
/*
* Make the private context. Assume it'll not contain much data.
*/
rulescxt = AllocSetContextCreate(CacheMemoryContext,
"relation rules",
ALLOCSET_SMALL_SIZES);
relation->rd_rulescxt = rulescxt;
MemoryContextCopyAndSetIdentifier(rulescxt,
RelationGetRelationName(relation));
/*
* allocate an array to hold the rewrite rules (the array is extended if
* necessary)
*/
maxlocks = 4;
rules = (RewriteRule **)
MemoryContextAlloc(rulescxt, sizeof(RewriteRule *) * maxlocks);
numlocks = 0;
/*
* form a scan key
*/
ScanKeyInit(&key,
Anum_pg_rewrite_ev_class,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
/*
* open pg_rewrite and begin a scan
*
* Note: since we scan the rules using RewriteRelRulenameIndexId, we will
* be reading the rules in name order, except possibly during
* emergency-recovery operations (ie, IgnoreSystemIndexes). This in turn
* ensures that rules will be fired in name order.
*/
rewrite_desc = table_open(RewriteRelationId, AccessShareLock);
rewrite_tupdesc = RelationGetDescr(rewrite_desc);
rewrite_scan = systable_beginscan(rewrite_desc,
RewriteRelRulenameIndexId,
true, NULL,
1, &key);
while (HeapTupleIsValid(rewrite_tuple = systable_getnext(rewrite_scan)))
{
Form_pg_rewrite rewrite_form = (Form_pg_rewrite) GETSTRUCT(rewrite_tuple);
bool isnull;
Datum rule_datum;
char *rule_str;
RewriteRule *rule;
Oid check_as_user;
rule = (RewriteRule *) MemoryContextAlloc(rulescxt,
sizeof(RewriteRule));
rule->ruleId = rewrite_form->oid;
rule->event = rewrite_form->ev_type - '0';
rule->enabled = rewrite_form->ev_enabled;
rule->isInstead = rewrite_form->is_instead;
/*
* Must use heap_getattr to fetch ev_action and ev_qual. Also, the
* rule strings are often large enough to be toasted. To avoid
* leaking memory in the caller's context, do the detoasting here so
* we can free the detoasted version.
*/
rule_datum = heap_getattr(rewrite_tuple,
Anum_pg_rewrite_ev_action,
rewrite_tupdesc,
&isnull);
Assert(!isnull);
rule_str = TextDatumGetCString(rule_datum);
oldcxt = MemoryContextSwitchTo(rulescxt);
rule->actions = (List *) stringToNode(rule_str);
MemoryContextSwitchTo(oldcxt);
pfree(rule_str);
rule_datum = heap_getattr(rewrite_tuple,
Anum_pg_rewrite_ev_qual,
rewrite_tupdesc,
&isnull);
Assert(!isnull);
rule_str = TextDatumGetCString(rule_datum);
oldcxt = MemoryContextSwitchTo(rulescxt);
rule->qual = (Node *) stringToNode(rule_str);
MemoryContextSwitchTo(oldcxt);
pfree(rule_str);
/*
* If this is a SELECT rule defining a view, and the view has
* "security_invoker" set, we must perform all permissions checks on
* relations referred to by the rule as the invoking user.
*
* In all other cases (including non-SELECT rules on security invoker
* views), perform the permissions checks as the relation owner.
*/
if (rule->event == CMD_SELECT &&
relation->rd_rel->relkind == RELKIND_VIEW &&
RelationHasSecurityInvoker(relation))
check_as_user = InvalidOid;
else
check_as_user = relation->rd_rel->relowner;
/*
* Scan through the rule's actions and set the checkAsUser field on
* all RTEPermissionInfos. We have to look at the qual as well, in
* case it contains sublinks.
*
* The reason for doing this when the rule is loaded, rather than when
* it is stored, is that otherwise ALTER TABLE OWNER would have to
* grovel through stored rules to update checkAsUser fields. Scanning
* the rule tree during load is relatively cheap (compared to
* constructing it in the first place), so we do it here.
*/
setRuleCheckAsUser((Node *) rule->actions, check_as_user);
setRuleCheckAsUser(rule->qual, check_as_user);
if (numlocks >= maxlocks)
{
maxlocks *= 2;
rules = (RewriteRule **)
repalloc(rules, sizeof(RewriteRule *) * maxlocks);
}
rules[numlocks++] = rule;
}
/*
* end the scan and close the attribute relation
*/
systable_endscan(rewrite_scan);
table_close(rewrite_desc, AccessShareLock);
/*
* there might not be any rules (if relhasrules is out-of-date)
*/
if (numlocks == 0)
{
relation->rd_rules = NULL;
relation->rd_rulescxt = NULL;
MemoryContextDelete(rulescxt);
return;
}
/*
* form a RuleLock and insert into relation
*/
rulelock = (RuleLock *) MemoryContextAlloc(rulescxt, sizeof(RuleLock));
rulelock->numLocks = numlocks;
rulelock->rules = rules;
relation->rd_rules = rulelock;
}
/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
*
* Probably this should be in the rules code someplace...
*/
static bool
equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
{
int i;
/*
* As of 7.3 we assume the rule ordering is repeatable, because
* RelationBuildRuleLock should read 'em in a consistent order. So just
* compare corresponding slots.
*/
if (rlock1 != NULL)
{
if (rlock2 == NULL)
return false;
if (rlock1->numLocks != rlock2->numLocks)
return false;
for (i = 0; i < rlock1->numLocks; i++)
{
RewriteRule *rule1 = rlock1->rules[i];
RewriteRule *rule2 = rlock2->rules[i];
if (rule1->ruleId != rule2->ruleId)
return false;
if (rule1->event != rule2->event)
return false;
if (rule1->enabled != rule2->enabled)
return false;
if (rule1->isInstead != rule2->isInstead)
return false;
if (!equal(rule1->qual, rule2->qual))
return false;
if (!equal(rule1->actions, rule2->actions))
return false;
}
}
else if (rlock2 != NULL)
return false;
return true;
}
/*
* equalPolicy
*
* Determine whether two policies are equivalent
*/
static bool
equalPolicy(RowSecurityPolicy *policy1, RowSecurityPolicy *policy2)
{
int i;
Oid *r1,
*r2;
if (policy1 != NULL)
{
if (policy2 == NULL)
return false;
if (policy1->polcmd != policy2->polcmd)
return false;
if (policy1->hassublinks != policy2->hassublinks)
return false;
if (strcmp(policy1->policy_name, policy2->policy_name) != 0)
return false;
if (ARR_DIMS(policy1->roles)[0] != ARR_DIMS(policy2->roles)[0])
return false;
r1 = (Oid *) ARR_DATA_PTR(policy1->roles);
r2 = (Oid *) ARR_DATA_PTR(policy2->roles);
for (i = 0; i < ARR_DIMS(policy1->roles)[0]; i++)
{
if (r1[i] != r2[i])
return false;
}
if (!equal(policy1->qual, policy2->qual))
return false;
if (!equal(policy1->with_check_qual, policy2->with_check_qual))
return false;
}
else if (policy2 != NULL)
return false;
return true;
}
/*
* equalRSDesc
*
* Determine whether two RowSecurityDesc's are equivalent
*/
static bool
equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
{
ListCell *lc,
*rc;
if (rsdesc1 == NULL && rsdesc2 == NULL)
return true;
if ((rsdesc1 != NULL && rsdesc2 == NULL) ||
(rsdesc1 == NULL && rsdesc2 != NULL))
return false;
if (list_length(rsdesc1->policies) != list_length(rsdesc2->policies))
return false;
/* RelationBuildRowSecurity should build policies in order */
forboth(lc, rsdesc1->policies, rc, rsdesc2->policies)
{
RowSecurityPolicy *l = (RowSecurityPolicy *) lfirst(lc);
RowSecurityPolicy *r = (RowSecurityPolicy *) lfirst(rc);
if (!equalPolicy(l, r))
return false;
}
return true;
}
/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
* AccessShareLock on the target relid.
*
* The new descriptor is inserted into the hash table if insertIt is true.
*
* Returns NULL if no pg_class row could be found for the given relid
* (suggesting we are trying to access a just-deleted relation).
* Any other error is reported via elog.
*/
static Relation
RelationBuildDesc(Oid targetRelId, bool insertIt)
{
int in_progress_offset;
Relation relation;
Oid relid;
HeapTuple pg_class_tuple;
Form_pg_class relp;
/*
* This function and its subroutines can allocate a good deal of transient
* data in CurrentMemoryContext. Traditionally we've just leaked that
* data, reasoning that the caller's context is at worst of transaction
* scope, and relcache loads shouldn't happen so often that it's essential
* to recover transient data before end of statement/transaction. However
* that's definitely not true when debug_discard_caches is active, and
* perhaps it's not true in other cases.
*
* When debug_discard_caches is active or when forced to by
* RECOVER_RELATION_BUILD_MEMORY=1, arrange to allocate the junk in a
* temporary context that we'll free before returning. Make it a child of
* caller's context so that it will get cleaned up appropriately if we
* error out partway through.
*/
#ifdef MAYBE_RECOVER_RELATION_BUILD_MEMORY
MemoryContext tmpcxt = NULL;
MemoryContext oldcxt = NULL;
if (RECOVER_RELATION_BUILD_MEMORY || debug_discard_caches > 0)
{
tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
"RelationBuildDesc workspace",
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(tmpcxt);
}
#endif
/* Register to catch invalidation messages */
if (in_progress_list_len >= in_progress_list_maxlen)
{
int allocsize;
allocsize = in_progress_list_maxlen * 2;
in_progress_list = repalloc(in_progress_list,
allocsize * sizeof(*in_progress_list));
in_progress_list_maxlen = allocsize;
}
in_progress_offset = in_progress_list_len++;
in_progress_list[in_progress_offset].reloid = targetRelId;
retry:
in_progress_list[in_progress_offset].invalidated = false;
/*
* find the tuple in pg_class corresponding to the given relation id
*/
pg_class_tuple = ScanPgRelation(targetRelId, true, false);
/*
* if no such tuple exists, return NULL
*/
if (!HeapTupleIsValid(pg_class_tuple))
{
#ifdef MAYBE_RECOVER_RELATION_BUILD_MEMORY
if (tmpcxt)
{
/* Return to caller's context, and blow away the temporary context */
MemoryContextSwitchTo(oldcxt);
MemoryContextDelete(tmpcxt);
}
#endif
Assert(in_progress_offset + 1 == in_progress_list_len);
in_progress_list_len--;
return NULL;
}
/*
* get information from the pg_class_tuple
*/
relp = (Form_pg_class) GETSTRUCT(pg_class_tuple);
relid = relp->oid;
Assert(relid == targetRelId);
/*
* allocate storage for the relation descriptor, and copy pg_class_tuple
* to relation->rd_rel.
*/
relation = AllocateRelationDesc(relp);
/*
* initialize the relation's relation id (relation->rd_id)
*/
RelationGetRelid(relation) = relid;
/*
* Normal relations are not nailed into the cache. Since we don't flush
* new relations, it won't be new. It could be temp though.
*/
relation->rd_refcnt = 0;
relation->rd_isnailed = false;
relation->rd_createSubid = InvalidSubTransactionId;
relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId;
relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId;
relation->rd_droppedSubid = InvalidSubTransactionId;
switch (relation->rd_rel->relpersistence)
{
case RELPERSISTENCE_UNLOGGED:
case RELPERSISTENCE_PERMANENT:
relation->rd_backend = INVALID_PROC_NUMBER;
relation->rd_islocaltemp = false;
break;
case RELPERSISTENCE_TEMP:
if (isTempOrTempToastNamespace(relation->rd_rel->relnamespace))
{
relation->rd_backend = ProcNumberForTempRelations();
relation->rd_islocaltemp = true;
}
else
{
/*
* If it's a temp table, but not one of ours, we have to use
* the slow, grotty method to figure out the owning backend.
*
* Note: it's possible that rd_backend gets set to
* MyProcNumber here, in case we are looking at a pg_class
* entry left over from a crashed backend that coincidentally
* had the same ProcNumber we're using. We should *not*
* consider such a table to be "ours"; this is why we need the
* separate rd_islocaltemp flag. The pg_class entry will get
* flushed if/when we clean out the corresponding temp table
* namespace in preparation for using it.
*/
relation->rd_backend =
GetTempNamespaceProcNumber(relation->rd_rel->relnamespace);
Assert(relation->rd_backend != INVALID_PROC_NUMBER);
relation->rd_islocaltemp = false;
}
break;
default:
elog(ERROR, "invalid relpersistence: %c",
relation->rd_rel->relpersistence);
break;
}
/*
* initialize the tuple descriptor (relation->rd_att).
*/
RelationBuildTupleDesc(relation);
/* foreign key data is not loaded till asked for */
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
/* partitioning data is not loaded till asked for */
relation->rd_partkey = NULL;
relation->rd_partkeycxt = NULL;
relation->rd_partdesc = NULL;
relation->rd_partdesc_nodetached = NULL;
relation->rd_partdesc_nodetached_xmin = InvalidTransactionId;
relation->rd_pdcxt = NULL;
relation->rd_pddcxt = NULL;
relation->rd_partcheck = NIL;
relation->rd_partcheckvalid = false;
relation->rd_partcheckcxt = NULL;
/*
* initialize access method information
*/
if (relation->rd_rel->relkind == RELKIND_INDEX ||
relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
RelationInitIndexAccessInfo(relation);
else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) ||
relation->rd_rel->relkind == RELKIND_SEQUENCE)
RelationInitTableAccessMethod(relation);
else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Do nothing: access methods are a setting that partitions can
* inherit.
*/
}
else
Assert(relation->rd_rel->relam == InvalidOid);
/* extract reloptions if any */
RelationParseRelOptions(relation, pg_class_tuple);
/*
* Fetch rules and triggers that affect this relation.
*
* Note that RelationBuildRuleLock() relies on this being done after
* extracting the relation's reloptions.
*/
if (relation->rd_rel->relhasrules)
RelationBuildRuleLock(relation);
else
{
relation->rd_rules = NULL;
relation->rd_rulescxt = NULL;
}
if (relation->rd_rel->relhastriggers)
RelationBuildTriggers(relation);
else
relation->trigdesc = NULL;
if (relation->rd_rel->relrowsecurity)
RelationBuildRowSecurity(relation);
else
relation->rd_rsdesc = NULL;
/*
* initialize the relation lock manager information
*/
RelationInitLockInfo(relation); /* see lmgr.c */
/*
* initialize physical addressing information for the relation
*/
RelationInitPhysicalAddr(relation);
/* make sure relation is marked as having no open file yet */
relation->rd_smgr = NULL;
/*
* now we can free the memory allocated for pg_class_tuple
*/
heap_freetuple(pg_class_tuple);
/*
* If an invalidation arrived mid-build, start over. Between here and the
* end of this function, don't add code that does or reasonably could read
* system catalogs. That range must be free from invalidation processing
* for the !insertIt case. For the insertIt case, RelationCacheInsert()
* will enroll this relation in ordinary relcache invalidation processing,
*/
if (in_progress_list[in_progress_offset].invalidated)
{
RelationDestroyRelation(relation, false);
goto retry;
}
Assert(in_progress_offset + 1 == in_progress_list_len);
in_progress_list_len--;
/*
* Insert newly created relation into relcache hash table, if requested.
*
* There is one scenario in which we might find a hashtable entry already
* present, even though our caller failed to find it: if the relation is a
* system catalog or index that's used during relcache load, we might have
* recursively created the same relcache entry during the preceding steps.
* So allow RelationCacheInsert to delete any already-present relcache
* entry for the same OID. The already-present entry should have refcount
* zero (else somebody forgot to close it); in the event that it doesn't,
* we'll elog a WARNING and leak the already-present entry.
*/
if (insertIt)
RelationCacheInsert(relation, true);
/* It's fully valid */
relation->rd_isvalid = true;
#ifdef MAYBE_RECOVER_RELATION_BUILD_MEMORY
if (tmpcxt)
{
/* Return to caller's context, and blow away the temporary context */
MemoryContextSwitchTo(oldcxt);
MemoryContextDelete(tmpcxt);
}
#endif
return relation;
}
/*
* Initialize the physical addressing info (RelFileLocator) for a relcache entry
*
* Note: at the physical level, relations in the pg_global tablespace must
* be treated as shared, even if relisshared isn't set. Hence we do not
* look at relisshared here.
*/
static void
RelationInitPhysicalAddr(Relation relation)
{
RelFileNumber oldnumber = relation->rd_locator.relNumber;
/* these relations kinds never have storage */
if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
return;
if (relation->rd_rel->reltablespace)
relation->rd_locator.spcOid = relation->rd_rel->reltablespace;
else
relation->rd_locator.spcOid = MyDatabaseTableSpace;
if (relation->rd_locator.spcOid == GLOBALTABLESPACE_OID)
relation->rd_locator.dbOid = InvalidOid;
else
relation->rd_locator.dbOid = MyDatabaseId;
if (relation->rd_rel->relfilenode)
{
/*
* Even if we are using a decoding snapshot that doesn't represent the
* current state of the catalog we need to make sure the filenode
* points to the current file since the older file will be gone (or
* truncated). The new file will still contain older rows so lookups
* in them will work correctly. This wouldn't work correctly if
* rewrites were allowed to change the schema in an incompatible way,
* but those are prevented both on catalog tables and on user tables
* declared as additional catalog tables.
*/
if (HistoricSnapshotActive()
&& RelationIsAccessibleInLogicalDecoding(relation)
&& IsTransactionState())
{
HeapTuple phys_tuple;
Form_pg_class physrel;
phys_tuple = ScanPgRelation(RelationGetRelid(relation),
RelationGetRelid(relation) != ClassOidIndexId,
true);
if (!HeapTupleIsValid(phys_tuple))
elog(ERROR, "could not find pg_class entry for %u",
RelationGetRelid(relation));
physrel = (Form_pg_class) GETSTRUCT(phys_tuple);
relation->rd_rel->reltablespace = physrel->reltablespace;
relation->rd_rel->relfilenode = physrel->relfilenode;
heap_freetuple(phys_tuple);
}
relation->rd_locator.relNumber = relation->rd_rel->relfilenode;
}
else
{
/* Consult the relation mapper */
relation->rd_locator.relNumber =
RelationMapOidToFilenumber(relation->rd_id,
relation->rd_rel->relisshared);
if (!RelFileNumberIsValid(relation->rd_locator.relNumber))
elog(ERROR, "could not find relation mapping for relation \"%s\", OID %u",
RelationGetRelationName(relation), relation->rd_id);
}
/*
* For RelationNeedsWAL() to answer correctly on parallel workers, restore
* rd_firstRelfilelocatorSubid. No subtransactions start or end while in
* parallel mode, so the specific SubTransactionId does not matter.
*/
if (IsParallelWorker() && oldnumber != relation->rd_locator.relNumber)
{
if (RelFileLocatorSkippingWAL(relation->rd_locator))
relation->rd_firstRelfilelocatorSubid = TopSubTransactionId;
else
relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId;
}
}
/*
* Fill in the IndexAmRoutine for an index relation.
*
* relation's rd_amhandler and rd_indexcxt must be valid already.
*/
static void
InitIndexAmRoutine(Relation relation)
{
MemoryContext oldctx;
/*
* We formerly specified that the amhandler should return a palloc'd
* struct. That's now deprecated in favor of returning a pointer to a
* static struct, but to avoid completely breaking old external AMs, run
* the amhandler in the relation's rd_indexcxt.
*/
oldctx = MemoryContextSwitchTo(relation->rd_indexcxt);
relation->rd_indam = GetIndexAmRoutine(relation->rd_amhandler);
MemoryContextSwitchTo(oldctx);
}
/*
* Initialize index-access-method support data for an index relation
*/
void
RelationInitIndexAccessInfo(Relation relation)
{
HeapTuple tuple;
Form_pg_am aform;
Datum indcollDatum;
Datum indclassDatum;
Datum indoptionDatum;
bool isnull;
oidvector *indcoll;
oidvector *indclass;
int2vector *indoption;
MemoryContext indexcxt;
MemoryContext oldcontext;
int indnatts;
int indnkeyatts;
uint16 amsupport;
/*
* Make a copy of the pg_index entry for the index. Since pg_index
* contains variable-length and possibly-null fields, we have to do this
* honestly rather than just treating it as a Form_pg_index struct.
*/
tuple = SearchSysCache1(INDEXRELID,
ObjectIdGetDatum(RelationGetRelid(relation)));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for index %u",
RelationGetRelid(relation));
oldcontext = MemoryContextSwitchTo(CacheMemoryContext);
relation->rd_indextuple = heap_copytuple(tuple);
relation->rd_index = (Form_pg_index) GETSTRUCT(relation->rd_indextuple);
MemoryContextSwitchTo(oldcontext);
ReleaseSysCache(tuple);
/*
* Look up the index's access method, save the OID of its handler function
*/
Assert(relation->rd_rel->relam != InvalidOid);
tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(relation->rd_rel->relam));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for access method %u",
relation->rd_rel->relam);
aform = (Form_pg_am) GETSTRUCT(tuple);
relation->rd_amhandler = aform->amhandler;
ReleaseSysCache(tuple);
indnatts = RelationGetNumberOfAttributes(relation);
if (indnatts != IndexRelationGetNumberOfAttributes(relation))
elog(ERROR, "relnatts disagrees with indnatts for index %u",
RelationGetRelid(relation));
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(relation);
/*
* Make the private context to hold index access info. The reason we need
* a context, and not just a couple of pallocs, is so that we won't leak
* any subsidiary info attached to fmgr lookup records.
*/
indexcxt = AllocSetContextCreate(CacheMemoryContext,
"index info",
ALLOCSET_SMALL_SIZES);
relation->rd_indexcxt = indexcxt;
MemoryContextCopyAndSetIdentifier(indexcxt,
RelationGetRelationName(relation));
/*
* Now we can fetch the index AM's API struct
*/
InitIndexAmRoutine(relation);
/*
* Allocate arrays to hold data. Opclasses are not used for included
* columns, so allocate them for indnkeyatts only.
*/
relation->rd_opfamily = (Oid *)
MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid));
relation->rd_opcintype = (Oid *)
MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid));
amsupport = relation->rd_indam->amsupport;
if (amsupport > 0)
{
int nsupport = indnatts * amsupport;
relation->rd_support = (RegProcedure *)
MemoryContextAllocZero(indexcxt, nsupport * sizeof(RegProcedure));
relation->rd_supportinfo = (FmgrInfo *)
MemoryContextAllocZero(indexcxt, nsupport * sizeof(FmgrInfo));
}
else
{
relation->rd_support = NULL;
relation->rd_supportinfo = NULL;
}
relation->rd_indcollation = (Oid *)
MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid));
relation->rd_indoption = (int16 *)
MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(int16));
/*
* indcollation cannot be referenced directly through the C struct,
* because it comes after the variable-width indkey field. Must extract
* the datum the hard way...
*/
indcollDatum = fastgetattr(relation->rd_indextuple,
Anum_pg_index_indcollation,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
indcoll = (oidvector *) DatumGetPointer(indcollDatum);
memcpy(relation->rd_indcollation, indcoll->values, indnkeyatts * sizeof(Oid));
/*
* indclass cannot be referenced directly through the C struct, because it
* comes after the variable-width indkey field. Must extract the datum
* the hard way...
*/
indclassDatum = fastgetattr(relation->rd_indextuple,
Anum_pg_index_indclass,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
indclass = (oidvector *) DatumGetPointer(indclassDatum);
/*
* Fill the support procedure OID array, as well as the info about
* opfamilies and opclass input types. (aminfo and supportinfo are left
* as zeroes, and are filled on-the-fly when used)
*/
IndexSupportInitialize(indclass, relation->rd_support,
relation->rd_opfamily, relation->rd_opcintype,
amsupport, indnkeyatts);
/*
* Similarly extract indoption and copy it to the cache entry
*/
indoptionDatum = fastgetattr(relation->rd_indextuple,
Anum_pg_index_indoption,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
indoption = (int2vector *) DatumGetPointer(indoptionDatum);
memcpy(relation->rd_indoption, indoption->values, indnkeyatts * sizeof(int16));
(void) RelationGetIndexAttOptions(relation, false);
/*
* expressions, predicate, exclusion caches will be filled later
*/
relation->rd_indexprs = NIL;
relation->rd_indexprsExpand = NIL;
relation->rd_indpred = NIL;
relation->rd_indpredExpand = NIL;
relation->rd_exclops = NULL;
relation->rd_exclprocs = NULL;
relation->rd_exclstrats = NULL;
relation->rd_amcache = NULL;
}
/*
* IndexSupportInitialize
* Initializes an index's cached opclass information,
* given the index's pg_index.indclass entry.
*
* Data is returned into *indexSupport, *opFamily, and *opcInType,
* which are arrays allocated by the caller.
*
* The caller also passes maxSupportNumber and maxAttributeNumber, since these
* indicate the size of the arrays it has allocated --- but in practice these
* numbers must always match those obtainable from the system catalog entries
* for the index and access method.
*/
static void
IndexSupportInitialize(oidvector *indclass,
RegProcedure *indexSupport,
Oid *opFamily,
Oid *opcInType,
StrategyNumber maxSupportNumber,
AttrNumber maxAttributeNumber)
{
int attIndex;
for (attIndex = 0; attIndex < maxAttributeNumber; attIndex++)
{
OpClassCacheEnt *opcentry;
if (!OidIsValid(indclass->values[attIndex]))
elog(ERROR, "bogus pg_index tuple");
/* look up the info for this opclass, using a cache */
opcentry = LookupOpclassInfo(indclass->values[attIndex],
maxSupportNumber);
/* copy cached data into relcache entry */
opFamily[attIndex] = opcentry->opcfamily;
opcInType[attIndex] = opcentry->opcintype;
if (maxSupportNumber > 0)
memcpy(&indexSupport[attIndex * maxSupportNumber],
opcentry->supportProcs,
maxSupportNumber * sizeof(RegProcedure));
}
}
/*
* LookupOpclassInfo
*
* This routine maintains a per-opclass cache of the information needed
* by IndexSupportInitialize(). This is more efficient than relying on
* the catalog cache, because we can load all the info about a particular
* opclass in a single indexscan of pg_amproc.
*
* The information from pg_am about expected range of support function
* numbers is passed in, rather than being looked up, mainly because the
* caller will have it already.
*
* Note there is no provision for flushing the cache. This is OK at the
* moment because there is no way to ALTER any interesting properties of an
* existing opclass --- all you can do is drop it, which will result in
* a useless but harmless dead entry in the cache. To support altering
* opclass membership (not the same as opfamily membership!), we'd need to
* be able to flush this cache as well as the contents of relcache entries
* for indexes.
*/
static OpClassCacheEnt *
LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport)
{
OpClassCacheEnt *opcentry;
bool found;
Relation rel;
SysScanDesc scan;
ScanKeyData skey[3];
HeapTuple htup;
bool indexOK;
if (OpClassCache == NULL)
{
/* First time through: initialize the opclass cache */
HASHCTL ctl;
/* Also make sure CacheMemoryContext exists */
if (!CacheMemoryContext)
CreateCacheMemoryContext();
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(OpClassCacheEnt);
OpClassCache = hash_create("Operator class cache", 64,
&ctl, HASH_ELEM | HASH_BLOBS);
}
opcentry = (OpClassCacheEnt *) hash_search(OpClassCache,
&operatorClassOid,
HASH_ENTER, &found);
if (!found)
{
/* Initialize new entry */
opcentry->valid = false; /* until known OK */
opcentry->numSupport = numSupport;
opcentry->supportProcs = NULL; /* filled below */
}
else
{
Assert(numSupport == opcentry->numSupport);
}
/*
* When aggressively testing cache-flush hazards, we disable the operator
* class cache and force reloading of the info on each call. This models
* no real-world behavior, since the cache entries are never invalidated
* otherwise. However it can be helpful for detecting bugs in the cache
* loading logic itself, such as reliance on a non-nailed index. Given
* the limited use-case and the fact that this adds a great deal of
* expense, we enable it only for high values of debug_discard_caches.
*/
#ifdef DISCARD_CACHES_ENABLED
if (debug_discard_caches > 2)
opcentry->valid = false;
#endif
if (opcentry->valid)
return opcentry;
/*
* Need to fill in new entry. First allocate space, unless we already did
* so in some previous attempt.
*/
if (opcentry->supportProcs == NULL && numSupport > 0)
opcentry->supportProcs = (RegProcedure *)
MemoryContextAllocZero(CacheMemoryContext,
numSupport * sizeof(RegProcedure));
/*
* To avoid infinite recursion during startup, force heap scans if we're
* looking up info for the opclasses used by the indexes we would like to
* reference here.
*/
indexOK = criticalRelcachesBuilt ||
(operatorClassOid != OID_BTREE_OPS_OID &&
operatorClassOid != INT2_BTREE_OPS_OID);
/*
* We have to fetch the pg_opclass row to determine its opfamily and
* opcintype, which are needed to look up related operators and functions.
* It'd be convenient to use the syscache here, but that probably doesn't
* work while bootstrapping.
*/
ScanKeyInit(&skey[0],
Anum_pg_opclass_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(operatorClassOid));
rel = table_open(OperatorClassRelationId, AccessShareLock);
scan = systable_beginscan(rel, OpclassOidIndexId, indexOK,
NULL, 1, skey);
if (HeapTupleIsValid(htup = systable_getnext(scan)))
{
Form_pg_opclass opclassform = (Form_pg_opclass) GETSTRUCT(htup);
opcentry->opcfamily = opclassform->opcfamily;
opcentry->opcintype = opclassform->opcintype;
}
else
elog(ERROR, "could not find tuple for opclass %u", operatorClassOid);
systable_endscan(scan);
table_close(rel, AccessShareLock);
/*
* Scan pg_amproc to obtain support procs for the opclass. We only fetch
* the default ones (those with lefttype = righttype = opcintype).
*/
if (numSupport > 0)
{
ScanKeyInit(&skey[0],
Anum_pg_amproc_amprocfamily,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(opcentry->opcfamily));
ScanKeyInit(&skey[1],
Anum_pg_amproc_amproclefttype,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(opcentry->opcintype));
ScanKeyInit(&skey[2],
Anum_pg_amproc_amprocrighttype,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(opcentry->opcintype));
rel = table_open(AccessMethodProcedureRelationId, AccessShareLock);
scan = systable_beginscan(rel, AccessMethodProcedureIndexId, indexOK,
NULL, 3, skey);
while (HeapTupleIsValid(htup = systable_getnext(scan)))
{
Form_pg_amproc amprocform = (Form_pg_amproc) GETSTRUCT(htup);
if (amprocform->amprocnum <= 0 ||
(StrategyNumber) amprocform->amprocnum > numSupport)
elog(ERROR, "invalid amproc number %d for opclass %u",
amprocform->amprocnum, operatorClassOid);
opcentry->supportProcs[amprocform->amprocnum - 1] =
amprocform->amproc;
}
systable_endscan(scan);
table_close(rel, AccessShareLock);
}
opcentry->valid = true;
return opcentry;
}
/*
* Fill in the TableAmRoutine for a relation
*
* relation's rd_amhandler must be valid already.
*/
static void
InitTableAmRoutine(Relation relation)
{
relation->rd_tableam = GetTableAmRoutine(relation->rd_amhandler);
}
/*
* Initialize table access method support for a table like relation
*/
void
RelationInitTableAccessMethod(Relation relation)
{
HeapTuple tuple;
Form_pg_am aform;
if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
{
/*
* Sequences are currently accessed like heap tables, but it doesn't
* seem prudent to show that in the catalog. So just overwrite it
* here.
*/
Assert(relation->rd_rel->relam == InvalidOid);
relation->rd_amhandler = F_HEAP_TABLEAM_HANDLER;
}
else if (IsCatalogRelation(relation))
{
/*
* Avoid doing a syscache lookup for catalog tables.
*/
Assert(relation->rd_rel->relam == HEAP_TABLE_AM_OID);
relation->rd_amhandler = F_HEAP_TABLEAM_HANDLER;
}
else
{
/*
* Look up the table access method, save the OID of its handler
* function.
*/
Assert(relation->rd_rel->relam != InvalidOid);
tuple = SearchSysCache1(AMOID,
ObjectIdGetDatum(relation->rd_rel->relam));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for access method %u",
relation->rd_rel->relam);
aform = (Form_pg_am) GETSTRUCT(tuple);
relation->rd_amhandler = aform->amhandler;
ReleaseSysCache(tuple);
}
/*
* Now we can fetch the table AM's API struct
*/
InitTableAmRoutine(relation);
}
/*
* formrdesc
*
* This is a special cut-down version of RelationBuildDesc(),
* used while initializing the relcache.
* The relation descriptor is built just from the supplied parameters,
* without actually looking at any system table entries. We cheat
* quite a lot since we only need to work for a few basic system
* catalogs.
*
* The catalogs this is used for can't have constraints (except attnotnull),
* default values, rules, or triggers, since we don't cope with any of that.
* (Well, actually, this only matters for properties that need to be valid
* during bootstrap or before RelationCacheInitializePhase3 runs, and none of
* these properties matter then...)
*
* NOTE: we assume we are already switched into CacheMemoryContext.
*/
static void
formrdesc(const char *relationName, Oid relationReltype,
bool isshared,
int natts, const FormData_pg_attribute *attrs)
{
Relation relation;
int i;
bool has_not_null;
/*
* allocate new relation desc, clear all fields of reldesc
*/
relation = palloc0_object(RelationData);
/* make sure relation is marked as having no open file yet */
relation->rd_smgr = NULL;
/*
* initialize reference count: 1 because it is nailed in cache
*/
relation->rd_refcnt = 1;
/*
* all entries built with this routine are nailed-in-cache; none are for
* new or temp relations.
*/
relation->rd_isnailed = true;
relation->rd_createSubid = InvalidSubTransactionId;
relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId;
relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId;
relation->rd_droppedSubid = InvalidSubTransactionId;
relation->rd_backend = INVALID_PROC_NUMBER;
relation->rd_islocaltemp = false;
/*
* initialize relation tuple form
*
* The data we insert here is pretty incomplete/bogus, but it'll serve to
* get us launched. RelationCacheInitializePhase3() will read the real
* data from pg_class and replace what we've done here. Note in
* particular that relowner is left as zero; this cues
* RelationCacheInitializePhase3 that the real data isn't there yet.
*/
relation->rd_rel = (Form_pg_class) palloc0(CLASS_TUPLE_SIZE);
namestrcpy(&relation->rd_rel->relname, relationName);
relation->rd_rel->relnamespace = PG_CATALOG_NAMESPACE;
relation->rd_rel->reltype = relationReltype;
/*
* It's important to distinguish between shared and non-shared relations,
* even at bootstrap time, to make sure we know where they are stored.
*/
relation->rd_rel->relisshared = isshared;
if (isshared)
relation->rd_rel->reltablespace = GLOBALTABLESPACE_OID;
/* formrdesc is used only for permanent relations */
relation->rd_rel->relpersistence = RELPERSISTENCE_PERMANENT;
/* ... and they're always populated, too */
relation->rd_rel->relispopulated = true;
relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
relation->rd_rel->relpages = 0;
relation->rd_rel->reltuples = -1;
relation->rd_rel->relallvisible = 0;
relation->rd_rel->relallfrozen = 0;
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
/*
* initialize attribute tuple form
*
* Unlike the case with the relation tuple, this data had better be right
* because it will never be replaced. The data comes from
* src/include/catalog/ headers via genbki.pl.
*/
relation->rd_att = CreateTemplateTupleDesc(natts);
relation->rd_att->tdrefcount = 1; /* mark as refcounted */
relation->rd_att->tdtypeid = relationReltype;
relation->rd_att->tdtypmod = -1; /* just to be sure */
/*
* initialize tuple desc info
*/
has_not_null = false;
for (i = 0; i < natts; i++)
{
memcpy(TupleDescAttr(relation->rd_att, i),
&attrs[i],
ATTRIBUTE_FIXED_PART_SIZE);
has_not_null |= attrs[i].attnotnull;
populate_compact_attribute(relation->rd_att, i);
}
TupleDescFinalize(relation->rd_att);
/* mark not-null status */
if (has_not_null)
{
TupleConstr *constr = palloc0_object(TupleConstr);
constr->has_not_null = true;
relation->rd_att->constr = constr;
}
/*
* initialize relation id from info in att array (my, this is ugly)
*/
RelationGetRelid(relation) = TupleDescAttr(relation->rd_att, 0)->attrelid;
/*
* All relations made with formrdesc are mapped. This is necessarily so
* because there is no other way to know what filenumber they currently
* have. In bootstrap mode, add them to the initial relation mapper data,
* specifying that the initial filenumber is the same as the OID.
*/
relation->rd_rel->relfilenode = InvalidRelFileNumber;
if (IsBootstrapProcessingMode())
RelationMapUpdateMap(RelationGetRelid(relation),
RelationGetRelid(relation),
isshared, true);
/*
* initialize the relation lock manager information
*/
RelationInitLockInfo(relation); /* see lmgr.c */
/*
* initialize physical addressing information for the relation
*/
RelationInitPhysicalAddr(relation);
/*
* initialize the table am handler
*/
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
relation->rd_tableam = GetHeapamTableAmRoutine();
/*
* initialize the rel-has-index flag, using hardwired knowledge
*/
if (IsBootstrapProcessingMode())
{
/* In bootstrap mode, we have no indexes */
relation->rd_rel->relhasindex = false;
}
else
{
/* Otherwise, all the rels formrdesc is used for have indexes */
relation->rd_rel->relhasindex = true;
}
/*
* add new reldesc to relcache
*/
RelationCacheInsert(relation, false);
/* It's fully valid */
relation->rd_isvalid = true;
}
#ifdef USE_ASSERT_CHECKING
/*
* AssertCouldGetRelation
*
* Check safety of calling RelationIdGetRelation().
*
* In code that reads catalogs in the event of a cache miss, call this
* before checking the cache.
*/
void
AssertCouldGetRelation(void)
{
Assert(IsTransactionState());
AssertBufferLocksPermitCatalogRead();
}
#endif
/* ----------------------------------------------------------------
* Relation Descriptor Lookup Interface
* ----------------------------------------------------------------
*/
/*
* RelationIdGetRelation
*
* Lookup a reldesc by OID; make one if not already in cache.
*
* Returns NULL if no pg_class row could be found for the given relid
* (suggesting we are trying to access a just-deleted relation).
* Any other error is reported via elog.
*
* NB: caller should already have at least AccessShareLock on the
* relation ID, else there are nasty race conditions.
*
* NB: relation ref count is incremented, or set to 1 if new entry.
* Caller should eventually decrement count. (Usually,
* that happens by calling RelationClose().)
*/
Relation
RelationIdGetRelation(Oid relationId)
{
Relation rd;
AssertCouldGetRelation();
/*
* first try to find reldesc in the cache
*/
RelationIdCacheLookup(relationId, rd);
if (RelationIsValid(rd))
{
/* return NULL for dropped relations */
if (rd->rd_droppedSubid != InvalidSubTransactionId)
{
Assert(!rd->rd_isvalid);
return NULL;
}
RelationIncrementReferenceCount(rd);
/* revalidate cache entry if necessary */
if (!rd->rd_isvalid)
{
RelationRebuildRelation(rd);
/*
* Normally entries need to be valid here, but before the relcache
* has been initialized, not enough infrastructure exists to
* perform pg_class lookups. The structure of such entries doesn't
* change, but we still want to update the rd_rel entry. So
* rd_isvalid = false is left in place for a later lookup.
*/
Assert(rd->rd_isvalid ||
(rd->rd_isnailed && !criticalRelcachesBuilt));
}
return rd;
}
/*
* no reldesc in the cache, so have RelationBuildDesc() build one and add
* it.
*/
rd = RelationBuildDesc(relationId, true);
if (RelationIsValid(rd))
RelationIncrementReferenceCount(rd);
return rd;
}
/*
* Returns a schema-qualified name of the relation.
*/
char *
RelationGetQualifiedRelationName(Relation rel)
{
return get_qualified_objname(RelationGetNamespace(rel),
RelationGetRelationName(rel));
}
/* ----------------------------------------------------------------
* cache invalidation support routines
* ----------------------------------------------------------------
*/
/* ResourceOwner callbacks to track relcache references */
static void ResOwnerReleaseRelation(Datum res);
static char *ResOwnerPrintRelCache(Datum res);
static const ResourceOwnerDesc relref_resowner_desc =
{
.name = "relcache reference",
.release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
.release_priority = RELEASE_PRIO_RELCACHE_REFS,
.ReleaseResource = ResOwnerReleaseRelation,
.DebugPrint = ResOwnerPrintRelCache
};
/* Convenience wrappers over ResourceOwnerRemember/Forget */
static inline void
ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
{
ResourceOwnerRemember(owner, PointerGetDatum(rel), &relref_resowner_desc);
}
static inline void
ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
{
ResourceOwnerForget(owner, PointerGetDatum(rel), &relref_resowner_desc);
}
/*
* RelationIncrementReferenceCount
* Increments relation reference count.
*
* Note: bootstrap mode has its own weird ideas about relation refcount
* behavior; we ought to fix it someday, but for now, just disable
* reference count ownership tracking in bootstrap mode.
*/
void
RelationIncrementReferenceCount(Relation rel)
{
ResourceOwnerEnlarge(CurrentResourceOwner);
rel->rd_refcnt += 1;
if (!IsBootstrapProcessingMode())
ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel);
}
/*
* RelationDecrementReferenceCount
* Decrements relation reference count.
*/
void
RelationDecrementReferenceCount(Relation rel)
{
Assert(rel->rd_refcnt > 0);
rel->rd_refcnt -= 1;
if (!IsBootstrapProcessingMode())
ResourceOwnerForgetRelationRef(CurrentResourceOwner, rel);
}
/*
* RelationClose - close an open relation
*
* Actually, we just decrement the refcount.
*
* NOTE: if compiled with -DRELCACHE_FORCE_RELEASE then relcache entries
* will be freed as soon as their refcount goes to zero. In combination
* with aset.c's CLOBBER_FREED_MEMORY option, this provides a good test
* to catch references to already-released relcache entries. It slows
* things down quite a bit, however.
*/
void
RelationClose(Relation relation)
{
/* Note: no locking manipulations needed */
RelationDecrementReferenceCount(relation);
RelationCloseCleanup(relation);
}
static void
RelationCloseCleanup(Relation relation)
{
/*
* If the relation is no longer open in this session, we can clean up any
* stale partition descriptors it has. This is unlikely, so check to see
* if there are child contexts before expending a call to mcxt.c.
*/
if (RelationHasReferenceCountZero(relation))
{
if (relation->rd_pdcxt != NULL &&
relation->rd_pdcxt->firstchild != NULL)
MemoryContextDeleteChildren(relation->rd_pdcxt);
if (relation->rd_pddcxt != NULL &&
relation->rd_pddcxt->firstchild != NULL)
MemoryContextDeleteChildren(relation->rd_pddcxt);
}
#ifdef RELCACHE_FORCE_RELEASE
if (RelationHasReferenceCountZero(relation) &&
relation->rd_createSubid == InvalidSubTransactionId &&
relation->rd_firstRelfilelocatorSubid == InvalidSubTransactionId)
RelationClearRelation(relation);
#endif
}
/*
* RelationReloadIndexInfo - reload minimal information for an open index
*
* This function is used only for indexes. A relcache inval on an index
* can mean that its pg_class or pg_index row changed. There are only
* very limited changes that are allowed to an existing index's schema,
* so we can update the relcache entry without a complete rebuild; which
* is fortunate because we can't rebuild an index entry that is "nailed"
* and/or in active use. We support full replacement of the pg_class row,
* as well as updates of a few simple fields of the pg_index row.
*
* We assume that at the time we are called, we have at least AccessShareLock
* on the target index.
*
* If the target index is an index on pg_class or pg_index, we'd better have
* previously gotten at least AccessShareLock on its underlying catalog,
* else we are at risk of deadlock against someone trying to exclusive-lock
* the heap and index in that order. This is ensured in current usage by
* only applying this to indexes being opened or having positive refcount.
*/
static void
RelationReloadIndexInfo(Relation relation)
{
bool indexOK;
HeapTuple pg_class_tuple;
Form_pg_class relp;
/* Should be called only for invalidated, live indexes */
Assert((relation->rd_rel->relkind == RELKIND_INDEX ||
relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
!relation->rd_isvalid &&
relation->rd_droppedSubid == InvalidSubTransactionId);
/*
* If it's a shared index, we might be called before backend startup has
* finished selecting a database, in which case we have no way to read
* pg_class yet. However, a shared index can never have any significant
* schema updates, so it's okay to mostly ignore the invalidation signal.
* Its physical relfilenumber might've changed, but that's all. Update
* the physical relfilenumber, mark it valid and return without doing
* anything more.
*/
if (relation->rd_rel->relisshared && !criticalRelcachesBuilt)
{
RelationInitPhysicalAddr(relation);
relation->rd_isvalid = true;
return;
}
/*
* Read the pg_class row
*
* Don't try to use an indexscan of pg_class_oid_index to reload the info
* for pg_class_oid_index ...
*/
indexOK = (RelationGetRelid(relation) != ClassOidIndexId);
pg_class_tuple = ScanPgRelation(RelationGetRelid(relation), indexOK, false);
if (!HeapTupleIsValid(pg_class_tuple))
elog(ERROR, "could not find pg_class tuple for index %u",
RelationGetRelid(relation));
relp = (Form_pg_class) GETSTRUCT(pg_class_tuple);
memcpy(relation->rd_rel, relp, CLASS_TUPLE_SIZE);
/* Reload reloptions in case they changed */
if (relation->rd_options)
pfree(relation->rd_options);
RelationParseRelOptions(relation, pg_class_tuple);
/* done with pg_class tuple */
heap_freetuple(pg_class_tuple);
/* We must recalculate physical address in case it changed */
RelationInitPhysicalAddr(relation);
/*
* For a non-system index, there are fields of the pg_index row that are
* allowed to change, so re-read that row and update the relcache entry.
* Most of the info derived from pg_index (such as support function lookup
* info) cannot change, and indeed the whole point of this routine is to
* update the relcache entry without clobbering that data; so wholesale
* replacement is not appropriate.
*/
if (!IsSystemRelation(relation))
{
HeapTuple tuple;
Form_pg_index index;
tuple = SearchSysCache1(INDEXRELID,
ObjectIdGetDatum(RelationGetRelid(relation)));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for index %u",
RelationGetRelid(relation));
index = (Form_pg_index) GETSTRUCT(tuple);
/*
* Basically, let's just copy all the bool fields. There are one or
* two of these that can't actually change in the current code, but
* it's not worth it to track exactly which ones they are. None of
* the array fields are allowed to change, though.
*/
relation->rd_index->indisunique = index->indisunique;
relation->rd_index->indnullsnotdistinct = index->indnullsnotdistinct;
relation->rd_index->indisprimary = index->indisprimary;
relation->rd_index->indisexclusion = index->indisexclusion;
relation->rd_index->indimmediate = index->indimmediate;
relation->rd_index->indisclustered = index->indisclustered;
relation->rd_index->indisvalid = index->indisvalid;
relation->rd_index->indcheckxmin = index->indcheckxmin;
relation->rd_index->indisready = index->indisready;
relation->rd_index->indislive = index->indislive;
relation->rd_index->indisreplident = index->indisreplident;
/* Copy xmin too, as that is needed to make sense of indcheckxmin */
HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data,
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
}
/* Okay, now it's valid again */
relation->rd_isvalid = true;
}
/*
* RelationReloadNailed - reload minimal information for nailed relations.
*
* The structure of a nailed relation can never change (which is good, because
* we rely on knowing their structure to be able to read catalog content). But
* some parts, e.g. pg_class.relfrozenxid, are still important to have
* accurate content for. Therefore those need to be reloaded after the arrival
* of invalidations.
*/
static void
RelationReloadNailed(Relation relation)
{
/* Should be called only for invalidated, nailed relations */
Assert(!relation->rd_isvalid);
Assert(relation->rd_isnailed);
/* nailed indexes are handled by RelationReloadIndexInfo() */
Assert(relation->rd_rel->relkind == RELKIND_RELATION);
AssertCouldGetRelation();
/*
* Redo RelationInitPhysicalAddr in case it is a mapped relation whose
* mapping changed.
*/
RelationInitPhysicalAddr(relation);
/*
* Reload a non-index entry. We can't easily do so if relcaches aren't
* yet built, but that's fine because at that stage the attributes that
* need to be current (like relfrozenxid) aren't yet accessed. To ensure
* the entry will later be revalidated, we leave it in invalid state, but
* allow use (cf. RelationIdGetRelation()).
*/
if (criticalRelcachesBuilt)
{
HeapTuple pg_class_tuple;
Form_pg_class relp;
/*
* NB: Mark the entry as valid before starting to scan, to avoid
* self-recursion when re-building pg_class.
*/
relation->rd_isvalid = true;
pg_class_tuple = ScanPgRelation(RelationGetRelid(relation),
true, false);
relp = (Form_pg_class) GETSTRUCT(pg_class_tuple);
memcpy(relation->rd_rel, relp, CLASS_TUPLE_SIZE);
heap_freetuple(pg_class_tuple);
/*
* Again mark as valid, to protect against concurrently arriving
* invalidations.
*/
relation->rd_isvalid = true;
}
}
/*
* RelationDestroyRelation
*
* Physically delete a relation cache entry and all subsidiary data.
* Caller must already have unhooked the entry from the hash table.
*/
static void
RelationDestroyRelation(Relation relation, bool remember_tupdesc)
{
Assert(RelationHasReferenceCountZero(relation));
/*
* Make sure smgr and lower levels close the relation's files, if they
* weren't closed already. (This was probably done by caller, but let's
* just be real sure.)
*/
RelationCloseSmgr(relation);
/* break mutual link with stats entry */
pgstat_unlink_relation(relation);
/*
* Free all the subsidiary data structures of the relcache entry, then the
* entry itself.
*/
if (relation->rd_rel)
pfree(relation->rd_rel);
/* can't use DecrTupleDescRefCount here */
Assert(relation->rd_att->tdrefcount > 0);
if (--relation->rd_att->tdrefcount == 0)
{
/*
* If we Rebuilt a relcache entry during a transaction then its
* possible we did that because the TupDesc changed as the result of
* an ALTER TABLE that ran at less than AccessExclusiveLock. It's
* possible someone copied that TupDesc, in which case the copy would
* point to free'd memory. So if we rebuild an entry we keep the
* TupDesc around until end of transaction, to be safe.
*/
if (remember_tupdesc)
RememberToFreeTupleDescAtEOX(relation->rd_att);
else
FreeTupleDesc(relation->rd_att);
}
FreeTriggerDesc(relation->trigdesc);
list_free_deep(relation->rd_fkeylist);
list_free(relation->rd_indexlist);
list_free(relation->rd_statlist);
bms_free(relation->rd_keyattr);
bms_free(relation->rd_pkattr);
bms_free(relation->rd_idattr);
bms_free(relation->rd_hotblockingattr);
bms_free(relation->rd_summarizedattr);
if (relation->rd_pubdesc)
pfree(relation->rd_pubdesc);
if (relation->rd_options)
pfree(relation->rd_options);
if (relation->rd_indextuple)
pfree(relation->rd_indextuple);
if (relation->rd_amcache)
pfree(relation->rd_amcache);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
if (relation->rd_indexcxt)
MemoryContextDelete(relation->rd_indexcxt);
if (relation->rd_rulescxt)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_pdcxt)
MemoryContextDelete(relation->rd_pdcxt);
if (relation->rd_pddcxt)
MemoryContextDelete(relation->rd_pddcxt);
if (relation->rd_partcheckcxt)
MemoryContextDelete(relation->rd_partcheckcxt);
pfree(relation);
}
/*
* RelationInvalidateRelation - mark a relation cache entry as invalid
*
* An entry that's marked as invalid will be reloaded on next access.
*/
static void
RelationInvalidateRelation(Relation relation)
{
/*
* Make sure smgr and lower levels close the relation's files, if they
* weren't closed already. If the relation is not getting deleted, the
* next smgr access should reopen the files automatically. This ensures
* that the low-level file access state is updated after, say, a vacuum
* truncation.
*/
RelationCloseSmgr(relation);
/* Free AM cached data, if any */
if (relation->rd_amcache)
pfree(relation->rd_amcache);
relation->rd_amcache = NULL;
relation->rd_isvalid = false;
}
/*
* RelationClearRelation - physically blow away a relation cache entry
*
* The caller must ensure that the entry is no longer needed, i.e. its
* reference count is zero. Also, the rel or its storage must not be created
* in the current transaction (rd_createSubid and rd_firstRelfilelocatorSubid
* must not be set).
*/
static void
RelationClearRelation(Relation relation)
{
Assert(RelationHasReferenceCountZero(relation));
Assert(!relation->rd_isnailed);
/*
* Relations created in the same transaction must never be removed, see
* RelationFlushRelation.
*/
Assert(relation->rd_createSubid == InvalidSubTransactionId);
Assert(relation->rd_firstRelfilelocatorSubid == InvalidSubTransactionId);
Assert(relation->rd_droppedSubid == InvalidSubTransactionId);
/* first mark it as invalid */
RelationInvalidateRelation(relation);
/* Remove it from the hash table */
RelationCacheDelete(relation);
/* And release storage */
RelationDestroyRelation(relation, false);
}
/*
* RelationRebuildRelation - rebuild a relation cache entry in place
*
* Reset and rebuild a relation cache entry from scratch (that is, from
* catalog entries). This is used when we are notified of a change to an open
* relation (one with refcount > 0). The entry is reconstructed without
* moving the physical RelationData record, so that the refcount holder's
* pointer is still valid.
*
* NB: when rebuilding, we'd better hold some lock on the relation, else the
* catalog data we need to read could be changing under us. Also, a rel to be
* rebuilt had better have refcnt > 0. This is because a sinval reset could
* happen while we're accessing the catalogs, and the rel would get blown away
* underneath us by RelationCacheInvalidate if it has zero refcnt.
*/
static void
RelationRebuildRelation(Relation relation)
{
Assert(!RelationHasReferenceCountZero(relation));
AssertCouldGetRelation();
/* there is no reason to ever rebuild a dropped relation */
Assert(relation->rd_droppedSubid == InvalidSubTransactionId);
/* Close and mark it as invalid until we've finished the rebuild */
RelationInvalidateRelation(relation);
/*
* Indexes only have a limited number of possible schema changes, and we
* don't want to use the full-blown procedure because it's a headache for
* indexes that reload itself depends on.
*
* As an exception, use the full procedure if the index access info hasn't
* been initialized yet. Index creation relies on that: it first builds
* the relcache entry with RelationBuildLocalRelation(), creates the
* pg_index tuple only after that, and then relies on
* CommandCounterIncrement to load the pg_index contents.
*/
if ((relation->rd_rel->relkind == RELKIND_INDEX ||
relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
relation->rd_indexcxt != NULL)
{
RelationReloadIndexInfo(relation);
return;
}
/* Nailed relations are handled separately. */
else if (relation->rd_isnailed)
{
RelationReloadNailed(relation);
return;
}
else
{
/*
* Our strategy for rebuilding an open relcache entry is to build a
* new entry from scratch, swap its contents with the old entry, and
* finally delete the new entry (along with any infrastructure swapped
* over from the old entry). This is to avoid trouble in case an
* error causes us to lose control partway through. The old entry
* will still be marked !rd_isvalid, so we'll try to rebuild it again
* on next access. Meanwhile it's not any less valid than it was
* before, so any code that might expect to continue accessing it
* isn't hurt by the rebuild failure. (Consider for example a
* subtransaction that ALTERs a table and then gets canceled partway
* through the cache entry rebuild. The outer transaction should
* still see the not-modified cache entry as valid.) The worst
* consequence of an error is leaking the necessarily-unreferenced new
* entry, and this shouldn't happen often enough for that to be a big
* problem.
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_*Subid, and rd_toastoid state. Also attempt to preserve the
* pg_class entry (rd_rel), tupledesc, rewrite-rule, partition key,
* and partition descriptor substructures in place, because various
* places assume that these structures won't move while they are
* working with an open relcache entry. (Note: the refcount
* mechanism for tupledescs might someday allow us to remove this hack
* for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
* necessarily belong to that resource owner.
*/
Relation newrel;
Oid save_relid = RelationGetRelid(relation);
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
bool keep_partkey;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
/*
* Between here and the end of the swap, don't add code that does or
* reasonably could read system catalogs. That range must be free
* from invalidation processing. See RelationBuildDesc() manipulation
* of in_progress_list.
*/
if (newrel == NULL)
{
/*
* We can validly get here, if we're using a historic snapshot in
* which a relation, accessed from outside logical decoding, is
* still invisible. In that case it's fine to just mark the
* relation as invalid and return - it'll fully get reloaded by
* the cache reset at the end of logical decoding (or at the next
* access). During normal processing we don't want to ignore this
* case as it shouldn't happen there, as explained below.
*/
if (HistoricSnapshotActive())
return;
/*
* This shouldn't happen as dropping a relation is intended to be
* impossible if still referenced (cf. CheckTableNotInUse()). But
* if we get here anyway, we can't just delete the relcache entry,
* as it possibly could get accessed later (as e.g. the error
* might get trapped and handled via a subtransaction rollback).
*/
elog(ERROR, "relation %u deleted while still in use", save_relid);
}
/*
* If we were to, again, have cases of the relkind of a relcache entry
* changing, we would need to ensure that pgstats does not get
* confused.
*/
Assert(relation->rd_rel->relkind == newrel->rd_rel->relkind);
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
/* partkey is immutable once set up, so we can always keep it */
keep_partkey = (relation->rd_partkey != NULL);
/*
* Perform swapping of the relcache entry contents. Within this
* process the old entry is momentarily invalid, so there *must* be no
* possibility of CHECK_FOR_INTERRUPTS within this sequence. Do it in
* all-in-line code for safety.
*
* Since the vast majority of fields should be swapped, our method is
* to swap the whole structures and then re-swap those few fields we
* didn't want swapped.
*/
#define SWAPFIELD(fldtype, fldname) \
do { \
fldtype _tmp = newrel->fldname; \
newrel->fldname = relation->fldname; \
relation->fldname = _tmp; \
} while (0)
/* swap all Relation struct fields */
{
RelationData tmpstruct;
memcpy(&tmpstruct, newrel, sizeof(RelationData));
memcpy(newrel, relation, sizeof(RelationData));
memcpy(relation, &tmpstruct, sizeof(RelationData));
}
/* rd_smgr must not be swapped, due to back-links from smgr level */
SWAPFIELD(SMgrRelation, rd_smgr);
/* rd_refcnt must be preserved */
SWAPFIELD(int, rd_refcnt);
/* isnailed shouldn't change */
Assert(newrel->rd_isnailed == relation->rd_isnailed);
/* creation sub-XIDs must be preserved */
SWAPFIELD(SubTransactionId, rd_createSubid);
SWAPFIELD(SubTransactionId, rd_newRelfilelocatorSubid);
SWAPFIELD(SubTransactionId, rd_firstRelfilelocatorSubid);
SWAPFIELD(SubTransactionId, rd_droppedSubid);
/* un-swap rd_rel pointers, swap contents instead */
SWAPFIELD(Form_pg_class, rd_rel);
/* ... but actually, we don't have to update newrel->rd_rel */
memcpy(relation->rd_rel, newrel->rd_rel, CLASS_TUPLE_SIZE);
/* preserve old tupledesc, rules, policies if no logical change */
if (keep_tupdesc)
SWAPFIELD(TupleDesc, rd_att);
if (keep_rules)
{
SWAPFIELD(RuleLock *, rd_rules);
SWAPFIELD(MemoryContext, rd_rulescxt);
}
if (keep_policies)
SWAPFIELD(RowSecurityDesc *, rd_rsdesc);
/* toast OID override must be preserved */
SWAPFIELD(Oid, rd_toastoid);
/* pgstat_info / enabled must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
SWAPFIELD(bool, pgstat_enabled);
/* preserve old partition key if we have one */
if (keep_partkey)
{
SWAPFIELD(PartitionKey, rd_partkey);
SWAPFIELD(MemoryContext, rd_partkeycxt);
}
if (newrel->rd_pdcxt != NULL || newrel->rd_pddcxt != NULL)
{
/*
* We are rebuilding a partitioned relation with a non-zero
* reference count, so we must keep the old partition descriptor
* around, in case there's a PartitionDirectory with a pointer to
* it. This means we can't free the old rd_pdcxt yet. (This is
* necessary because RelationGetPartitionDesc hands out direct
* pointers to the relcache's data structure, unlike our usual
* practice which is to hand out copies. We'd have the same
* problem with rd_partkey, except that we always preserve that
* once created.)
*
* To ensure that it's not leaked completely, re-attach it to the
* new reldesc, or make it a child of the new reldesc's rd_pdcxt
* in the unlikely event that there is one already. (Compare hack
* in RelationBuildPartitionDesc.) RelationClose will clean up
* any such contexts once the reference count reaches zero.
*
* In the case where the reference count is zero, this code is not
* reached, which should be OK because in that case there should
* be no PartitionDirectory with a pointer to the old entry.
*
* Note that newrel and relation have already been swapped, so the
* "old" partition descriptor is actually the one hanging off of
* newrel.
*/
relation->rd_partdesc = NULL; /* ensure rd_partdesc is invalid */
relation->rd_partdesc_nodetached = NULL;
relation->rd_partdesc_nodetached_xmin = InvalidTransactionId;
if (relation->rd_pdcxt != NULL) /* probably never happens */
MemoryContextSetParent(newrel->rd_pdcxt, relation->rd_pdcxt);
else
relation->rd_pdcxt = newrel->rd_pdcxt;
if (relation->rd_pddcxt != NULL)
MemoryContextSetParent(newrel->rd_pddcxt, relation->rd_pddcxt);
else
relation->rd_pddcxt = newrel->rd_pddcxt;
/* drop newrel's pointers so we don't destroy it below */
newrel->rd_partdesc = NULL;
newrel->rd_partdesc_nodetached = NULL;
newrel->rd_partdesc_nodetached_xmin = InvalidTransactionId;
newrel->rd_pdcxt = NULL;
newrel->rd_pddcxt = NULL;
}
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
RelationDestroyRelation(newrel, !keep_tupdesc);
}
}
/*
* RelationFlushRelation
*
* Rebuild the relation if it is open (refcount > 0), else blow it away.
* This is used when we receive a cache invalidation event for the rel.
*/
static void
RelationFlushRelation(Relation relation)
{
if (relation->rd_createSubid != InvalidSubTransactionId ||
relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId)
{
/*
* New relcache entries are always rebuilt, not flushed; else we'd
* forget the "new" status of the relation. Ditto for the
* new-relfilenumber status.
*/
if (IsTransactionState() && relation->rd_droppedSubid == InvalidSubTransactionId)
{
/*
* The rel could have zero refcnt here, so temporarily increment
* the refcnt to ensure it's safe to rebuild it. We can assume
* that the current transaction has some lock on the rel already.
*/
RelationIncrementReferenceCount(relation);
RelationRebuildRelation(relation);
RelationDecrementReferenceCount(relation);
}
else
RelationInvalidateRelation(relation);
}
else
{
/*
* Pre-existing rels can be dropped from the relcache if not open.
*
* If the entry is in use, rebuild it if possible. If we're not
* inside a valid transaction, we can't do any catalog access so it's
* not possible to rebuild yet. Just mark it as invalid in that case,
* so that the rebuild will occur when the entry is next opened.
*
* Note: it's possible that we come here during subtransaction abort,
* and the reason for wanting to rebuild is that the rel is open in
* the outer transaction. In that case it might seem unsafe to not
* rebuild immediately, since whatever code has the rel already open
* will keep on using the relcache entry as-is. However, in such a
* case the outer transaction should be holding a lock that's
* sufficient to prevent any significant change in the rel's schema,
* so the existing entry contents should be good enough for its
* purposes; at worst we might be behind on statistics updates or the
* like. (See also CheckTableNotInUse() and its callers.)
*/
if (RelationHasReferenceCountZero(relation))
RelationClearRelation(relation);
else if (!IsTransactionState())
RelationInvalidateRelation(relation);
else if (relation->rd_isnailed && relation->rd_refcnt == 1)
{
/*
* A nailed relation with refcnt == 1 is unused. We cannot clear
* it, but there's also no need no need to rebuild it immediately.
*/
RelationInvalidateRelation(relation);
}
else
RelationRebuildRelation(relation);
}
}
/*
* RelationForgetRelation - caller reports that it dropped the relation
*/
void
RelationForgetRelation(Oid rid)
{
Relation relation;
RelationIdCacheLookup(rid, relation);
if (!relation)
return; /* not in cache, nothing to do */
if (!RelationHasReferenceCountZero(relation))
elog(ERROR, "relation %u is still open", rid);
Assert(relation->rd_droppedSubid == InvalidSubTransactionId);
if (relation->rd_createSubid != InvalidSubTransactionId ||
relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId)
{
/*
* In the event of subtransaction rollback, we must not forget
* rd_*Subid. Mark the entry "dropped" and invalidate it, instead of
* destroying it right away. (If we're in a top transaction, we could
* opt to destroy the entry.)
*/
relation->rd_droppedSubid = GetCurrentSubTransactionId();
RelationInvalidateRelation(relation);
}
else
RelationClearRelation(relation);
}
/*
* RelationCacheInvalidateEntry
*
* This routine is invoked for SI cache flush messages.
*
* Any relcache entry matching the relid must be flushed. (Note: caller has
* already determined that the relid belongs to our database or is a shared
* relation.)
*
* We used to skip local relations, on the grounds that they could
* not be targets of cross-backend SI update messages; but it seems
* safer to process them, so that our *own* SI update messages will
* have the same effects during CommandCounterIncrement for both
* local and nonlocal relations.
*/
void
RelationCacheInvalidateEntry(Oid relationId)
{
Relation relation;
RelationIdCacheLookup(relationId, relation);
if (relation)
{
relcacheInvalsReceived++;
RelationFlushRelation(relation);
}
else
{
int i;
for (i = 0; i < in_progress_list_len; i++)
if (in_progress_list[i].reloid == relationId)
in_progress_list[i].invalidated = true;
}
}
/*
* RelationCacheInvalidate
* Blow away cached relation descriptors that have zero reference counts,
* and rebuild those with positive reference counts. Also reset the smgr
* relation cache and re-read relation mapping data.
*
* Apart from debug_discard_caches, this is currently used only to recover
* from SI message buffer overflow, so we do not touch relations having
* new-in-transaction relfilenumbers; they cannot be targets of cross-backend
* SI updates (and our own updates now go through a separate linked list
* that isn't limited by the SI message buffer size).
*
* We do this in two phases: the first pass deletes deletable items, and
* the second one rebuilds the rebuildable items. This is essential for
* safety, because hash_seq_search only copes with concurrent deletion of
* the element it is currently visiting. If a second SI overflow were to
* occur while we are walking the table, resulting in recursive entry to
* this routine, we could crash because the inner invocation blows away
* the entry next to be visited by the outer scan. But this way is OK,
* because (a) during the first pass we won't process any more SI messages,
* so hash_seq_search will complete safely; (b) during the second pass we
* only hold onto pointers to nondeletable entries.
*
* The two-phase approach also makes it easy to update relfilenumbers for
* mapped relations before we do anything else, and to ensure that the
* second pass processes nailed-in-cache items before other nondeletable
* items. This should ensure that system catalogs are up to date before
* we attempt to use them to reload information about other open relations.
*
* After those two phases of work having immediate effects, we normally
* signal any RelationBuildDesc() on the stack to start over. However, we
* don't do this if called as part of debug_discard_caches. Otherwise,
* RelationBuildDesc() would become an infinite loop.
*/
void
RelationCacheInvalidate(bool debug_discard)
{
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
Relation relation;
List *rebuildFirstList = NIL;
List *rebuildList = NIL;
ListCell *l;
int i;
/*
* Reload relation mapping data before starting to reconstruct cache.
*/
RelationMapInvalidateAll();
/* Phase 1 */
hash_seq_init(&status, RelationIdCache);
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
{
relation = idhentry->reldesc;
/*
* Ignore new relations; no other backend will manipulate them before
* we commit. Likewise, before replacing a relation's relfilelocator,
* we shall have acquired AccessExclusiveLock and drained any
* applicable pending invalidations.
*/
if (relation->rd_createSubid != InvalidSubTransactionId ||
relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId)
continue;
relcacheInvalsReceived++;
if (RelationHasReferenceCountZero(relation))
{
/* Delete this entry immediately */
RelationClearRelation(relation);
}
else
{
/*
* If it's a mapped relation, immediately update its rd_locator in
* case its relfilenumber changed. We must do this during phase 1
* in case the relation is consulted during rebuild of other
* relcache entries in phase 2. It's safe since consulting the
* map doesn't involve any access to relcache entries.
*/
if (RelationIsMapped(relation))
{
RelationCloseSmgr(relation);
RelationInitPhysicalAddr(relation);
}
/*
* Add this entry to list of stuff to rebuild in second pass.
* pg_class goes to the front of rebuildFirstList while
* pg_class_oid_index goes to the back of rebuildFirstList, so
* they are done first and second respectively. Other nailed
* relations go to the front of rebuildList, so they'll be done
* next in no particular order; and everything else goes to the
* back of rebuildList.
*/
if (RelationGetRelid(relation) == RelationRelationId)
rebuildFirstList = lcons(relation, rebuildFirstList);
else if (RelationGetRelid(relation) == ClassOidIndexId)
rebuildFirstList = lappend(rebuildFirstList, relation);
else if (relation->rd_isnailed)
rebuildList = lcons(relation, rebuildList);
else
rebuildList = lappend(rebuildList, relation);
}
}
/*
* We cannot destroy the SMgrRelations as there might still be references
* to them, but close the underlying file descriptors.
*/
smgrreleaseall();
/*
* Phase 2: rebuild (or invalidate) the items found to need rebuild in
* phase 1
*/
foreach(l, rebuildFirstList)
{
relation = (Relation) lfirst(l);
if (!IsTransactionState() || (relation->rd_isnailed && relation->rd_refcnt == 1))
RelationInvalidateRelation(relation);
else
RelationRebuildRelation(relation);
}
list_free(rebuildFirstList);
foreach(l, rebuildList)
{
relation = (Relation) lfirst(l);
if (!IsTransactionState() || (relation->rd_isnailed && relation->rd_refcnt == 1))
RelationInvalidateRelation(relation);
else
RelationRebuildRelation(relation);
}
list_free(rebuildList);
if (!debug_discard)
/* Any RelationBuildDesc() on the stack must start over. */
for (i = 0; i < in_progress_list_len; i++)
in_progress_list[i].invalidated = true;
}
static void
RememberToFreeTupleDescAtEOX(TupleDesc td)
{
if (EOXactTupleDescArray == NULL)
{
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
EOXactTupleDescArrayLen = 16;
NextEOXactTupleDescNum = 0;
MemoryContextSwitchTo(oldcxt);
}
else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
{
int32 newlen = EOXactTupleDescArrayLen * 2;
Assert(EOXactTupleDescArrayLen > 0);
EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
newlen * sizeof(TupleDesc));
EOXactTupleDescArrayLen = newlen;
}
EOXactTupleDescArray[NextEOXactTupleDescNum++] = td;
}
#ifdef USE_ASSERT_CHECKING
static void
AssertPendingSyncConsistency(Relation relation)
{
bool relcache_verdict =
RelationIsPermanent(relation) &&
((relation->rd_createSubid != InvalidSubTransactionId &&
RELKIND_HAS_STORAGE(relation->rd_rel->relkind)) ||
relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId);
Assert(relcache_verdict == RelFileLocatorSkippingWAL(relation->rd_locator));
if (relation->rd_droppedSubid != InvalidSubTransactionId)
Assert(!relation->rd_isvalid &&
(relation->rd_createSubid != InvalidSubTransactionId ||
relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId));
}
/*
* AssertPendingSyncs_RelationCache
*
* Assert that relcache.c and storage.c agree on whether to skip WAL.
*/
void
AssertPendingSyncs_RelationCache(void)
{
HASH_SEQ_STATUS status;
LOCALLOCK *locallock;
Relation *rels;
int maxrels;
int nrels;
RelIdCacheEnt *idhentry;
int i;
/*
* Open every relation that this transaction has locked. If, for some
* relation, storage.c is skipping WAL and relcache.c is not skipping WAL,
* a CommandCounterIncrement() typically yields a local invalidation
* message that destroys the relcache entry. By recreating such entries
* here, we detect the problem.
*/
PushActiveSnapshot(GetTransactionSnapshot());
maxrels = 1;
rels = palloc(maxrels * sizeof(*rels));
nrels = 0;
hash_seq_init(&status, GetLockMethodLocalHash());
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
{
Oid relid;
Relation r;
if (locallock->nLocks <= 0)
continue;
if ((LockTagType) locallock->tag.lock.locktag_type !=
LOCKTAG_RELATION)
continue;
relid = locallock->tag.lock.locktag_field2;
r = RelationIdGetRelation(relid);
if (!RelationIsValid(r))
continue;
if (nrels >= maxrels)
{
maxrels *= 2;
rels = repalloc(rels, maxrels * sizeof(*rels));
}
rels[nrels++] = r;
}
hash_seq_init(&status, RelationIdCache);
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
AssertPendingSyncConsistency(idhentry->reldesc);
for (i = 0; i < nrels; i++)
RelationClose(rels[i]);
PopActiveSnapshot();
}
#endif
/*
* AtEOXact_RelationCache
*
* Clean up the relcache at main-transaction commit or abort.
*
* Note: this must be called *before* processing invalidation messages.
* In the case of abort, we don't want to try to rebuild any invalidated
* cache entries (since we can't safely do database accesses). Therefore
* we must reset refcnts before handling pending invalidations.
*
* As of PostgreSQL 8.1, relcache refcnts should get released by the
* ResourceOwner mechanism. This routine just does a debugging
* cross-check that no pins remain. However, we also need to do special
* cleanup when the current transaction created any relations or made use
* of forced index lists.
*/
void
AtEOXact_RelationCache(bool isCommit)
{
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
int i;
/*
* Forget in_progress_list. This is relevant when we're aborting due to
* an error during RelationBuildDesc().
*/
Assert(in_progress_list_len == 0 || !isCommit);
in_progress_list_len = 0;
/*
* Unless the eoxact_list[] overflowed, we only need to examine the rels
* listed in it. Otherwise fall back on a hash_seq_search scan.
*
* For simplicity, eoxact_list[] entries are not deleted till end of
* top-level transaction, even though we could remove them at
* subtransaction end in some cases, or remove relations from the list if
* they are cleared for other reasons. Therefore we should expect the
* case that list entries are not found in the hashtable; if not, there's
* nothing to do for them.
*/
if (eoxact_list_overflowed)
{
hash_seq_init(&status, RelationIdCache);
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
{
AtEOXact_cleanup(idhentry->reldesc, isCommit);
}
}
else
{
for (i = 0; i < eoxact_list_len; i++)
{
idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
&eoxact_list[i],
HASH_FIND,
NULL);
if (idhentry != NULL)
AtEOXact_cleanup(idhentry->reldesc, isCommit);
}
}
if (EOXactTupleDescArrayLen > 0)
{
Assert(EOXactTupleDescArray != NULL);
for (i = 0; i < NextEOXactTupleDescNum; i++)
FreeTupleDesc(EOXactTupleDescArray[i]);
pfree(EOXactTupleDescArray);
EOXactTupleDescArray = NULL;
}
/* Now we're out of the transaction and can clear the lists */
eoxact_list_len = 0;
eoxact_list_overflowed = false;
NextEOXactTupleDescNum = 0;
EOXactTupleDescArrayLen = 0;
}
/*
* AtEOXact_cleanup
*
* Clean up a single rel at main-transaction commit or abort
*
* NB: this processing must be idempotent, because EOXactListAdd() doesn't
* bother to prevent duplicate entries in eoxact_list[].
*/
static void
AtEOXact_cleanup(Relation relation, bool isCommit)
{
bool clear_relcache = false;
/*
* The relcache entry's ref count should be back to its normal
* not-in-a-transaction state: 0 unless it's nailed in cache.
*
* In bootstrap mode, this is NOT true, so don't check it --- the
* bootstrap code expects relations to stay open across start/commit
* transaction calls. (That seems bogus, but it's not worth fixing.)
*
* Note: ideally this check would be applied to every relcache entry, not
* just those that have eoxact work to do. But it's not worth forcing a
* scan of the whole relcache just for this. (Moreover, doing so would
* mean that assert-enabled testing never tests the hash_search code path
* above, which seems a bad idea.)
*/
#ifdef USE_ASSERT_CHECKING
if (!IsBootstrapProcessingMode())
{
int expected_refcnt;
expected_refcnt = relation->rd_isnailed ? 1 : 0;
Assert(relation->rd_refcnt == expected_refcnt);
}
#endif
/*
* Is the relation live after this transaction ends?
*
* During commit, clear the relcache entry if it is preserved after
* relation drop, in order not to orphan the entry. During rollback,
* clear the relcache entry if the relation is created in the current
* transaction since it isn't interesting any longer once we are out of
* the transaction.
*/
clear_relcache =
(isCommit ?
relation->rd_droppedSubid != InvalidSubTransactionId :
relation->rd_createSubid != InvalidSubTransactionId);
/*
* Since we are now out of the transaction, reset the subids to zero. That
* also lets RelationClearRelation() drop the relcache entry.
*/
relation->rd_createSubid = InvalidSubTransactionId;
relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId;
relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId;
relation->rd_droppedSubid = InvalidSubTransactionId;
if (clear_relcache)
{
if (RelationHasReferenceCountZero(relation))
{
RelationClearRelation(relation);
return;
}
else
{
/*
* Hmm, somewhere there's a (leaked?) reference to the relation.
* We daren't remove the entry for fear of dereferencing a
* dangling pointer later. Bleat, and mark it as not belonging to
* the current transaction. Hopefully it'll get cleaned up
* eventually. This must be just a WARNING to avoid
* error-during-error-recovery loops.
*/
elog(WARNING, "cannot remove relcache entry for \"%s\" because it has nonzero refcount",
RelationGetRelationName(relation));
}
}
}
/*
* AtEOSubXact_RelationCache
*
* Clean up the relcache at sub-transaction commit or abort.
*
* Note: this must be called *before* processing invalidation messages.
*/
void
AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
SubTransactionId parentSubid)
{
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
int i;
/*
* Forget in_progress_list. This is relevant when we're aborting due to
* an error during RelationBuildDesc(). We don't commit subtransactions
* during RelationBuildDesc().
*/
Assert(in_progress_list_len == 0 || !isCommit);
in_progress_list_len = 0;
/*
* Unless the eoxact_list[] overflowed, we only need to examine the rels
* listed in it. Otherwise fall back on a hash_seq_search scan. Same
* logic as in AtEOXact_RelationCache.
*/
if (eoxact_list_overflowed)
{
hash_seq_init(&status, RelationIdCache);
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
{
AtEOSubXact_cleanup(idhentry->reldesc, isCommit,
mySubid, parentSubid);
}
}
else
{
for (i = 0; i < eoxact_list_len; i++)
{
idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
&eoxact_list[i],
HASH_FIND,
NULL);
if (idhentry != NULL)
AtEOSubXact_cleanup(idhentry->reldesc, isCommit,
mySubid, parentSubid);
}
}
/* Don't reset the list; we still need more cleanup later */
}
/*
* AtEOSubXact_cleanup
*
* Clean up a single rel at subtransaction commit or abort
*
* NB: this processing must be idempotent, because EOXactListAdd() doesn't
* bother to prevent duplicate entries in eoxact_list[].
*/
static void
AtEOSubXact_cleanup(Relation relation, bool isCommit,
SubTransactionId mySubid, SubTransactionId parentSubid)
{
/*
* Is it a relation created in the current subtransaction?
*
* During subcommit, mark it as belonging to the parent, instead, as long
* as it has not been dropped. Otherwise simply delete the relcache entry.
* --- it isn't interesting any longer.
*/
if (relation->rd_createSubid == mySubid)
{
/*
* Valid rd_droppedSubid means the corresponding relation is dropped
* but the relcache entry is preserved for at-commit pending sync. We
* need to drop it explicitly here not to make the entry orphan.
*/
Assert(relation->rd_droppedSubid == mySubid ||
relation->rd_droppedSubid == InvalidSubTransactionId);
if (isCommit && relation->rd_droppedSubid == InvalidSubTransactionId)
relation->rd_createSubid = parentSubid;
else if (RelationHasReferenceCountZero(relation))
{
/* allow the entry to be removed */
relation->rd_createSubid = InvalidSubTransactionId;
relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId;
relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId;
relation->rd_droppedSubid = InvalidSubTransactionId;
RelationClearRelation(relation);
return;
}
else
{
/*
* Hmm, somewhere there's a (leaked?) reference to the relation.
* We daren't remove the entry for fear of dereferencing a
* dangling pointer later. Bleat, and transfer it to the parent
* subtransaction so we can try again later. This must be just a
* WARNING to avoid error-during-error-recovery loops.
*/
relation->rd_createSubid = parentSubid;
elog(WARNING, "cannot remove relcache entry for \"%s\" because it has nonzero refcount",
RelationGetRelationName(relation));
}
}
/*
* Likewise, update or drop any new-relfilenumber-in-subtransaction record
* or drop record.
*/
if (relation->rd_newRelfilelocatorSubid == mySubid)
{
if (isCommit)
relation->rd_newRelfilelocatorSubid = parentSubid;
else
relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId;
}
if (relation->rd_firstRelfilelocatorSubid == mySubid)
{
if (isCommit)
relation->rd_firstRelfilelocatorSubid = parentSubid;
else
relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId;
}
if (relation->rd_droppedSubid == mySubid)
{
if (isCommit)
relation->rd_droppedSubid = parentSubid;
else
relation->rd_droppedSubid = InvalidSubTransactionId;
}
}
/*
* RelationBuildLocalRelation
* Build a relcache entry for an about-to-be-created relation,
* and enter it into the relcache.
*/
Relation
RelationBuildLocalRelation(const char *relname,
Oid relnamespace,
TupleDesc tupDesc,
Oid relid,
Oid accessmtd,
RelFileNumber relfilenumber,
Oid reltablespace,
bool shared_relation,
bool mapped_relation,
char relpersistence,
char relkind)
{
Relation rel;
MemoryContext oldcxt;
int natts = tupDesc->natts;
int i;
bool has_not_null;
bool nailit;
Assert(natts >= 0);
/*
* check for creation of a rel that must be nailed in cache.
*
* XXX this list had better match the relations specially handled in
* RelationCacheInitializePhase2/3.
*/
switch (relid)
{
case DatabaseRelationId:
case AuthIdRelationId:
case AuthMemRelationId:
case RelationRelationId:
case AttributeRelationId:
case ProcedureRelationId:
case TypeRelationId:
nailit = true;
break;
default:
nailit = false;
break;
}
/*
* check that hardwired list of shared rels matches what's in the
* bootstrap .bki file. If you get a failure here during initdb, you
* probably need to fix IsSharedRelation() to match whatever you've done
* to the set of shared relations.
*/
if (shared_relation != IsSharedRelation(relid))
elog(ERROR, "shared_relation flag for \"%s\" does not match IsSharedRelation(%u)",
relname, relid);
/* Shared relations had better be mapped, too */
Assert(mapped_relation || !shared_relation);
/*
* switch to the cache context to create the relcache entry.
*/
if (!CacheMemoryContext)
CreateCacheMemoryContext();
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
/*
* allocate a new relation descriptor and fill in basic state fields.
*/
rel = palloc0_object(RelationData);
/* make sure relation is marked as having no open file yet */
rel->rd_smgr = NULL;
/* mark it nailed if appropriate */
rel->rd_isnailed = nailit;
rel->rd_refcnt = nailit ? 1 : 0;
/* it's being created in this transaction */
rel->rd_createSubid = GetCurrentSubTransactionId();
rel->rd_newRelfilelocatorSubid = InvalidSubTransactionId;
rel->rd_firstRelfilelocatorSubid = InvalidSubTransactionId;
rel->rd_droppedSubid = InvalidSubTransactionId;
/*
* create a new tuple descriptor from the one passed in. We do this
* partly to copy it into the cache context, and partly because the new
* relation can't have any defaults or constraints yet; they have to be
* added in later steps, because they require additions to multiple system
* catalogs. We can copy attnotnull constraints here, however.
*/
rel->rd_att = CreateTupleDescCopy(tupDesc);
rel->rd_att->tdrefcount = 1; /* mark as refcounted */
has_not_null = false;
for (i = 0; i < natts; i++)
{
Form_pg_attribute satt = TupleDescAttr(tupDesc, i);
Form_pg_attribute datt = TupleDescAttr(rel->rd_att, i);
datt->attidentity = satt->attidentity;
datt->attgenerated = satt->attgenerated;
datt->attnotnull = satt->attnotnull;
has_not_null |= satt->attnotnull;
populate_compact_attribute(rel->rd_att, i);
if (satt->attnotnull)
{
CompactAttribute *scatt = TupleDescCompactAttr(tupDesc, i);
CompactAttribute *dcatt = TupleDescCompactAttr(rel->rd_att, i);
dcatt->attnullability = scatt->attnullability;
}
}
if (has_not_null)
{
TupleConstr *constr = palloc0_object(TupleConstr);
constr->has_not_null = true;
rel->rd_att->constr = constr;
}
/*
* initialize relation tuple form (caller may add/override data later)
*/
rel->rd_rel = (Form_pg_class) palloc0(CLASS_TUPLE_SIZE);
namestrcpy(&rel->rd_rel->relname, relname);
rel->rd_rel->relnamespace = relnamespace;
rel->rd_rel->relkind = relkind;
rel->rd_rel->relnatts = natts;
rel->rd_rel->reltype = InvalidOid;
/* needed when bootstrapping: */
rel->rd_rel->relowner = BOOTSTRAP_SUPERUSERID;
/* set up persistence and relcache fields dependent on it */
rel->rd_rel->relpersistence = relpersistence;
switch (relpersistence)
{
case RELPERSISTENCE_UNLOGGED:
case RELPERSISTENCE_PERMANENT:
rel->rd_backend = INVALID_PROC_NUMBER;
rel->rd_islocaltemp = false;
break;
case RELPERSISTENCE_TEMP:
Assert(isTempOrTempToastNamespace(relnamespace));
rel->rd_backend = ProcNumberForTempRelations();
rel->rd_islocaltemp = true;
break;
default:
elog(ERROR, "invalid relpersistence: %c", relpersistence);
break;
}
/* if it's a materialized view, it's not populated initially */
if (relkind == RELKIND_MATVIEW)
rel->rd_rel->relispopulated = false;
else
rel->rd_rel->relispopulated = true;
/* set replica identity -- system catalogs and non-tables don't have one */
if (!IsCatalogNamespace(relnamespace) &&
(relkind == RELKIND_RELATION ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_PARTITIONED_TABLE))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenumber to zero and rely
* on RelationInitPhysicalAddr to consult the map.
*/
rel->rd_rel->relisshared = shared_relation;
RelationGetRelid(rel) = relid;
for (i = 0; i < natts; i++)
TupleDescAttr(rel->rd_att, i)->attrelid = relid;
TupleDescFinalize(rel->rd_att);
rel->rd_rel->reltablespace = reltablespace;
if (mapped_relation)
{
rel->rd_rel->relfilenode = InvalidRelFileNumber;
/* Add it to the active mapping information */
RelationMapUpdateMap(relid, relfilenumber, shared_relation, true);
}
else
rel->rd_rel->relfilenode = relfilenumber;
RelationInitLockInfo(rel); /* see lmgr.c */
RelationInitPhysicalAddr(rel);
rel->rd_rel->relam = accessmtd;
/*
* RelationInitTableAccessMethod will do syscache lookups, so we mustn't
* run it in CacheMemoryContext. Fortunately, the remaining steps don't
* require a long-lived current context.
*/
MemoryContextSwitchTo(oldcxt);
if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE)
RelationInitTableAccessMethod(rel);
/*
* Leave index access method uninitialized, because the pg_index row has
* not been inserted at this stage of index creation yet. The cache
* invalidation after pg_index row has been inserted will initialize it.
*/
/*
* Okay to insert into the relcache hash table.
*
* Ordinarily, there should certainly not be an existing hash entry for
* the same OID; but during bootstrap, when we create a "real" relcache
* entry for one of the bootstrap relations, we'll be overwriting the
* phony one created with formrdesc. So allow that to happen for nailed
* rels.
*/
RelationCacheInsert(rel, nailit);
/*
* Flag relation as needing eoxact cleanup (to clear rd_createSubid). We
* can't do this before storing relid in it.
*/
EOXactListAdd(rel);
/* It's fully valid */
rel->rd_isvalid = true;
/*
* Caller expects us to pin the returned entry.
*/
RelationIncrementReferenceCount(rel);
return rel;
}
/*
* RelationSetNewRelfilenumber
*
* Assign a new relfilenumber (physical file name), and possibly a new
* persistence setting, to the relation.
*
* This allows a full rewrite of the relation to be done with transactional
* safety (since the filenumber assignment can be rolled back). Note however
* that there is no simple way to access the relation's old data for the
* remainder of the current transaction. This limits the usefulness to cases
* such as TRUNCATE or rebuilding an index from scratch.
*
* Caller must already hold exclusive lock on the relation.
*/
void
RelationSetNewRelfilenumber(Relation relation, char persistence)
{
RelFileNumber newrelfilenumber;
Relation pg_class;
ItemPointerData otid;
HeapTuple tuple;
Form_pg_class classform;
MultiXactId minmulti = InvalidMultiXactId;
TransactionId freezeXid = InvalidTransactionId;
RelFileLocator newrlocator;
if (!IsBinaryUpgrade)
{
/* Allocate a new relfilenumber */
newrelfilenumber = GetNewRelFileNumber(relation->rd_rel->reltablespace,
NULL, persistence);
}
else if (relation->rd_rel->relkind == RELKIND_INDEX)
{
if (!OidIsValid(binary_upgrade_next_index_pg_class_relfilenumber))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("index relfilenumber value not set when in binary upgrade mode")));
newrelfilenumber = binary_upgrade_next_index_pg_class_relfilenumber;
binary_upgrade_next_index_pg_class_relfilenumber = InvalidOid;
}
else if (relation->rd_rel->relkind == RELKIND_RELATION)
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_relfilenumber))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("heap relfilenumber value not set when in binary upgrade mode")));
newrelfilenumber = binary_upgrade_next_heap_pg_class_relfilenumber;
binary_upgrade_next_heap_pg_class_relfilenumber = InvalidOid;
}
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unexpected request for new relfilenumber in binary upgrade mode")));
/*
* Get a writable copy of the pg_class tuple for the given relation.
*/
pg_class = table_open(RelationRelationId, RowExclusiveLock);
tuple = SearchSysCacheLockedCopy1(RELOID,
ObjectIdGetDatum(RelationGetRelid(relation)));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for relation %u",
RelationGetRelid(relation));
otid = tuple->t_self;
classform = (Form_pg_class) GETSTRUCT(tuple);
/*
* Schedule unlinking of the old storage at transaction commit, except
* when performing a binary upgrade, when we must do it immediately.
*/
if (IsBinaryUpgrade)
{
SMgrRelation srel;
/*
* During a binary upgrade, we use this code path to ensure that
* pg_largeobject and its index have the same relfilenumbers as in the
* old cluster. This is necessary because pg_upgrade treats
* pg_largeobject like a user table, not a system table. It is however
* possible that a table or index may need to end up with the same
* relfilenumber in the new cluster as what it had in the old cluster.
* Hence, we can't wait until commit time to remove the old storage.
*
* In general, this function needs to have transactional semantics,
* and removing the old storage before commit time surely isn't.
* However, it doesn't really matter, because if a binary upgrade
* fails at this stage, the new cluster will need to be recreated
* anyway.
*/
srel = smgropen(relation->rd_locator, relation->rd_backend);
smgrdounlinkall(&srel, 1, false);
smgrclose(srel);
}
else
{
/* Not a binary upgrade, so just schedule it to happen later. */
RelationDropStorage(relation);
}
/*
* Create storage for the main fork of the new relfilenumber. If it's a
* table-like object, call into the table AM to do so, which'll also
* create the table's init fork if needed.
*
* NOTE: If relevant for the AM, any conflict in relfilenumber value will
* be caught here, if GetNewRelFileNumber messes up for any reason.
*/
newrlocator = relation->rd_locator;
newrlocator.relNumber = newrelfilenumber;
if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
{
table_relation_set_new_filelocator(relation, &newrlocator,
persistence,
&freezeXid, &minmulti);
}
else if (RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
{
/* handle these directly, at least for now */
SMgrRelation srel;
srel = RelationCreateStorage(newrlocator, persistence, true);
smgrclose(srel);
}
else
{
/* we shouldn't be called for anything else */
elog(ERROR, "relation \"%s\" does not have storage",
RelationGetRelationName(relation));
}
/*
* If we're dealing with a mapped index, pg_class.relfilenode doesn't
* change; instead we have to send the update to the relation mapper.
*
* For mapped indexes, we don't actually change the pg_class entry at all;
* this is essential when reindexing pg_class itself. That leaves us with
* possibly-inaccurate values of relpages etc, but those will be fixed up
* later.
*/
if (RelationIsMapped(relation))
{
/* This case is only supported for indexes */
Assert(relation->rd_rel->relkind == RELKIND_INDEX);
/* Since we're not updating pg_class, these had better not change */
Assert(classform->relfrozenxid == freezeXid);
Assert(classform->relminmxid == minmulti);
Assert(classform->relpersistence == persistence);
/*
* In some code paths it's possible that the tuple update we'd
* otherwise do here is the only thing that would assign an XID for
* the current transaction. However, we must have an XID to delete
* files, so make sure one is assigned.
*/
(void) GetCurrentTransactionId();
/* Do the deed */
RelationMapUpdateMap(RelationGetRelid(relation),
newrelfilenumber,
relation->rd_rel->relisshared,
false);
/* Since we're not updating pg_class, must trigger inval manually */
CacheInvalidateRelcache(relation);
}
else
{
/* Normal case, update the pg_class entry */
classform->relfilenode = newrelfilenumber;
/* relpages etc. never change for sequences */
if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
{
classform->relpages = 0; /* it's empty until further notice */
classform->reltuples = -1;
classform->relallvisible = 0;
classform->relallfrozen = 0;
}
classform->relfrozenxid = freezeXid;
classform->relminmxid = minmulti;
classform->relpersistence = persistence;
CatalogTupleUpdate(pg_class, &otid, tuple);
}
UnlockTuple(pg_class, &otid, InplaceUpdateTupleLock);
heap_freetuple(tuple);
table_close(pg_class, RowExclusiveLock);
/*
* Make the pg_class row change or relation map change visible. This will
* cause the relcache entry to get updated, too.
*/
CommandCounterIncrement();
RelationAssumeNewRelfilelocator(relation);
}
/*
* RelationAssumeNewRelfilelocator
*
* Code that modifies pg_class.reltablespace or pg_class.relfilenode must call
* this. The call shall precede any code that might insert WAL records whose
* replay would modify bytes in the new RelFileLocator, and the call shall follow
* any WAL modifying bytes in the prior RelFileLocator. See struct RelationData.
* Ideally, call this as near as possible to the CommandCounterIncrement()
* that makes the pg_class change visible (before it or after it); that
* minimizes the chance of future development adding a forbidden WAL insertion
* between RelationAssumeNewRelfilelocator() and CommandCounterIncrement().
*/
void
RelationAssumeNewRelfilelocator(Relation relation)
{
relation->rd_newRelfilelocatorSubid = GetCurrentSubTransactionId();
if (relation->rd_firstRelfilelocatorSubid == InvalidSubTransactionId)
relation->rd_firstRelfilelocatorSubid = relation->rd_newRelfilelocatorSubid;
/* Flag relation as needing eoxact cleanup (to clear these fields) */
EOXactListAdd(relation);
}
/*
* RelationCacheInitialize
*
* This initializes the relation descriptor cache. At the time
* that this is invoked, we can't do database access yet (mainly
* because the transaction subsystem is not up); all we are doing
* is making an empty cache hashtable. This must be done before
* starting the initialization transaction, because otherwise
* AtEOXact_RelationCache would crash if that transaction aborts
* before we can get the relcache set up.
*/
#define INITRELCACHESIZE 400
void
RelationCacheInitialize(void)
{
HASHCTL ctl;
int allocsize;
/*
* make sure cache memory context exists
*/
if (!CacheMemoryContext)
CreateCacheMemoryContext();
/*
* create hashtable that indexes the relcache
*/
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(RelIdCacheEnt);
RelationIdCache = hash_create("Relcache by OID", INITRELCACHESIZE,
&ctl, HASH_ELEM | HASH_BLOBS);
/*
* reserve enough in_progress_list slots for many cases
*/
allocsize = 4;
in_progress_list =
MemoryContextAlloc(CacheMemoryContext,
allocsize * sizeof(*in_progress_list));
in_progress_list_maxlen = allocsize;
/*
* relation mapper needs to be initialized too
*/
RelationMapInitialize();
}
/*
* RelationCacheInitializePhase2
*
* This is called to prepare for access to shared catalogs during startup.
* We must at least set up nailed reldescs for pg_database, pg_authid,
* pg_auth_members, and pg_shseclabel. Ideally we'd like to have reldescs
* for their indexes, too. We attempt to load this information from the
* shared relcache init file. If that's missing or broken, just make
* phony entries for the catalogs themselves.
* RelationCacheInitializePhase3 will clean up as needed.
*/
void
RelationCacheInitializePhase2(void)
{
MemoryContext oldcxt;
/*
* relation mapper needs initialized too
*/
RelationMapInitializePhase2();
/*
* In bootstrap mode, the shared catalogs aren't there yet anyway, so do
* nothing.
*/
if (IsBootstrapProcessingMode())
return;
/*
* switch to cache memory context
*/
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
/*
* Try to load the shared relcache cache file. If unsuccessful, bootstrap
* the cache with pre-made descriptors for the critical shared catalogs.
*/
if (!load_relcache_init_file(true))
{
formrdesc("pg_database", DatabaseRelation_Rowtype_Id, true,
Natts_pg_database, Desc_pg_database);
formrdesc("pg_authid", AuthIdRelation_Rowtype_Id, true,
Natts_pg_authid, Desc_pg_authid);
formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
Natts_pg_auth_members, Desc_pg_auth_members);
formrdesc("pg_shseclabel", SharedSecLabelRelation_Rowtype_Id, true,
Natts_pg_shseclabel, Desc_pg_shseclabel);
formrdesc("pg_subscription", SubscriptionRelation_Rowtype_Id, true,
Natts_pg_subscription, Desc_pg_subscription);
#define NUM_CRITICAL_SHARED_RELS 5 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
}
/*
* RelationCacheInitializePhase3
*
* This is called as soon as the catcache and transaction system
* are functional and we have determined MyDatabaseId. At this point
* we can actually read data from the database's system catalogs.
* We first try to read pre-computed relcache entries from the local
* relcache init file. If that's missing or broken, make phony entries
* for the minimum set of nailed-in-cache relations. Then (unless
* bootstrapping) make sure we have entries for the critical system
* indexes. Once we've done all this, we have enough infrastructure to
* open any system catalog or use any catcache. The last step is to
* rewrite the cache files if needed.
*/
void
RelationCacheInitializePhase3(void)
{
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
MemoryContext oldcxt;
bool needNewCacheFile = !criticalSharedRelcachesBuilt;
/*
* relation mapper needs initialized too
*/
RelationMapInitializePhase3();
/*
* switch to cache memory context
*/
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
/*
* Try to load the local relcache cache file. If unsuccessful, bootstrap
* the cache with pre-made descriptors for the critical "nailed-in" system
* catalogs.
*/
if (IsBootstrapProcessingMode() ||
!load_relcache_init_file(false))
{
needNewCacheFile = true;
formrdesc("pg_class", RelationRelation_Rowtype_Id, false,
Natts_pg_class, Desc_pg_class);
formrdesc("pg_attribute", AttributeRelation_Rowtype_Id, false,
Natts_pg_attribute, Desc_pg_attribute);
formrdesc("pg_proc", ProcedureRelation_Rowtype_Id, false,
Natts_pg_proc, Desc_pg_proc);
formrdesc("pg_type", TypeRelation_Rowtype_Id, false,
Natts_pg_type, Desc_pg_type);
#define NUM_CRITICAL_LOCAL_RELS 4 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
/* In bootstrap mode, the faked-up formrdesc info is all we'll have */
if (IsBootstrapProcessingMode())
return;
/*
* If we didn't get the critical system indexes loaded into relcache, do
* so now. These are critical because the catcache and/or opclass cache
* depend on them for fetches done during relcache load. Thus, we have an
* infinite-recursion problem. We can break the recursion by doing
* heapscans instead of indexscans at certain key spots. To avoid hobbling
* performance, we only want to do that until we have the critical indexes
* loaded into relcache. Thus, the flag criticalRelcachesBuilt is used to
* decide whether to do heapscan or indexscan at the key spots, and we set
* it true after we've loaded the critical indexes.
*
* The critical indexes are marked as "nailed in cache", partly to make it
* easy for load_relcache_init_file to count them, but mainly because we
* cannot flush and rebuild them once we've set criticalRelcachesBuilt to
* true. (NOTE: perhaps it would be possible to reload them by
* temporarily setting criticalRelcachesBuilt to false again. For now,
* though, we just nail 'em in.)
*
* RewriteRelRulenameIndexId and TriggerRelidNameIndexId are not critical
* in the same way as the others, because the critical catalogs don't
* (currently) have any rules or triggers, and so these indexes can be
* rebuilt without inducing recursion. However they are used during
* relcache load when a rel does have rules or triggers, so we choose to
* nail them for performance reasons.
*/
if (!criticalRelcachesBuilt)
{
load_critical_index(ClassOidIndexId,
RelationRelationId);
load_critical_index(AttributeRelidNumIndexId,
AttributeRelationId);
load_critical_index(IndexRelidIndexId,
IndexRelationId);
load_critical_index(OpclassOidIndexId,
OperatorClassRelationId);
load_critical_index(AccessMethodProcedureIndexId,
AccessMethodProcedureRelationId);
load_critical_index(RewriteRelRulenameIndexId,
RewriteRelationId);
load_critical_index(TriggerRelidNameIndexId,
TriggerRelationId);
#define NUM_CRITICAL_LOCAL_INDEXES 7 /* fix if you change list above */
criticalRelcachesBuilt = true;
}
/*
* Process critical shared indexes too.
*
* DatabaseNameIndexId isn't critical for relcache loading, but rather for
* initial lookup of MyDatabaseId, without which we'll never find any
* non-shared catalogs at all. Autovacuum calls InitPostgres with a
* database OID, so it instead depends on DatabaseOidIndexId. We also
* need to nail up some indexes on pg_authid and pg_auth_members for use
* during client authentication. SharedSecLabelObjectIndexId isn't
* critical for the core system, but authentication hooks might be
* interested in it.
*/
if (!criticalSharedRelcachesBuilt)
{
load_critical_index(DatabaseNameIndexId,
DatabaseRelationId);
load_critical_index(DatabaseOidIndexId,
DatabaseRelationId);
load_critical_index(AuthIdRolnameIndexId,
AuthIdRelationId);
load_critical_index(AuthIdOidIndexId,
AuthIdRelationId);
load_critical_index(AuthMemMemRoleIndexId,
AuthMemRelationId);
load_critical_index(SharedSecLabelObjectIndexId,
SharedSecLabelRelationId);
#define NUM_CRITICAL_SHARED_INDEXES 6 /* fix if you change list above */
criticalSharedRelcachesBuilt = true;
}
/*
* Now, scan all the relcache entries and update anything that might be
* wrong in the results from formrdesc or the relcache cache file. If we
* faked up relcache entries using formrdesc, then read the real pg_class
* rows and replace the fake entries with them. Also, if any of the
* relcache entries have rules, triggers, or security policies, load that
* info the hard way since it isn't recorded in the cache file.
*
* Whenever we access the catalogs to read data, there is a possibility of
* a shared-inval cache flush causing relcache entries to be removed.
* Since hash_seq_search only guarantees to still work after the *current*
* entry is removed, it's unsafe to continue the hashtable scan afterward.
* We handle this by restarting the scan from scratch after each access.
* This is theoretically O(N^2), but the number of entries that actually
* need to be fixed is small enough that it doesn't matter.
*/
hash_seq_init(&status, RelationIdCache);
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
{
Relation relation = idhentry->reldesc;
bool restart = false;
/*
* Make sure *this* entry doesn't get flushed while we work with it.
*/
RelationIncrementReferenceCount(relation);
/*
* If it's a faked-up entry, read the real pg_class tuple.
*/
if (relation->rd_rel->relowner == InvalidOid)
{
HeapTuple htup;
Form_pg_class relp;
htup = SearchSysCache1(RELOID,
ObjectIdGetDatum(RelationGetRelid(relation)));
if (!HeapTupleIsValid(htup))
ereport(FATAL,
errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg_internal("cache lookup failed for relation %u",
RelationGetRelid(relation)));
relp = (Form_pg_class) GETSTRUCT(htup);
/*
* Copy tuple to relation->rd_rel. (See notes in
* AllocateRelationDesc())
*/
memcpy((char *) relation->rd_rel, (char *) relp, CLASS_TUPLE_SIZE);
/* Update rd_options while we have the tuple */
if (relation->rd_options)
pfree(relation->rd_options);
RelationParseRelOptions(relation, htup);
/*
* Check the values in rd_att were set up correctly. (We cannot
* just copy them over now: formrdesc must have set up the rd_att
* data correctly to start with, because it may already have been
* copied into one or more catcache entries.)
*/
Assert(relation->rd_att->tdtypeid == relp->reltype);
Assert(relation->rd_att->tdtypmod == -1);
ReleaseSysCache(htup);
/* relowner had better be OK now, else we'll loop forever */
if (relation->rd_rel->relowner == InvalidOid)
elog(ERROR, "invalid relowner in pg_class entry for \"%s\"",
RelationGetRelationName(relation));
restart = true;
}
/*
* Fix data that isn't saved in relcache cache file.
*
* relhasrules or relhastriggers could possibly be wrong or out of
* date. If we don't actually find any rules or triggers, clear the
* local copy of the flag so that we don't get into an infinite loop
* here. We don't make any attempt to fix the pg_class entry, though.
*/
if (relation->rd_rel->relhasrules && relation->rd_rules == NULL)
{
RelationBuildRuleLock(relation);
if (relation->rd_rules == NULL)
relation->rd_rel->relhasrules = false;
restart = true;
}
if (relation->rd_rel->relhastriggers && relation->trigdesc == NULL)
{
RelationBuildTriggers(relation);
if (relation->trigdesc == NULL)
relation->rd_rel->relhastriggers = false;
restart = true;
}
/*
* Re-load the row security policies if the relation has them, since
* they are not preserved in the cache. Note that we can never NOT
* have a policy while relrowsecurity is true,
* RelationBuildRowSecurity will create a single default-deny policy
* if there is no policy defined in pg_policy.
*/
if (relation->rd_rel->relrowsecurity && relation->rd_rsdesc == NULL)
{
RelationBuildRowSecurity(relation);
Assert(relation->rd_rsdesc != NULL);
restart = true;
}
/* Reload tableam data if needed */
if (relation->rd_tableam == NULL &&
(RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE))
{
RelationInitTableAccessMethod(relation);
Assert(relation->rd_tableam != NULL);
restart = true;
}
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
/* Now, restart the hashtable scan if needed */
if (restart)
{
hash_seq_term(&status);
hash_seq_init(&status, RelationIdCache);
}
}
/*
* Lastly, write out new relcache cache files if needed. We don't bother
* to distinguish cases where only one of the two needs an update.
*/
if (needNewCacheFile)
{
/*
* Force all the catcaches to finish initializing and thereby open the
* catalogs and indexes they use. This will preload the relcache with
* entries for all the most important system catalogs and indexes, so
* that the init files will be most useful for future backends.
*/
InitCatalogCachePhase2();
/* now write the files */
write_relcache_init_file(true);
write_relcache_init_file(false);
}
}
/*
* Load one critical system index into the relcache
*
* indexoid is the OID of the target index, heapoid is the OID of the catalog
* it belongs to.
*/
static void
load_critical_index(Oid indexoid, Oid heapoid)
{
Relation ird;
/*
* We must lock the underlying catalog before locking the index to avoid
* deadlock, since RelationBuildDesc might well need to read the catalog,
* and if anyone else is exclusive-locking this catalog and index they'll
* be doing it in that order.
*/
LockRelationOid(heapoid, AccessShareLock);
LockRelationOid(indexoid, AccessShareLock);
ird = RelationBuildDesc(indexoid, true);
if (ird == NULL)
ereport(PANIC,
errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal("could not open critical system index %u", indexoid));
ird->rd_isnailed = true;
ird->rd_refcnt = 1;
UnlockRelationOid(indexoid, AccessShareLock);
UnlockRelationOid(heapoid, AccessShareLock);
(void) RelationGetIndexAttOptions(ird, false);
}
/*
* GetPgClassDescriptor -- get a predefined tuple descriptor for pg_class
* GetPgIndexDescriptor -- get a predefined tuple descriptor for pg_index
*
* We need this kluge because we have to be able to access non-fixed-width
* fields of pg_class and pg_index before we have the standard catalog caches
* available. We use predefined data that's set up in just the same way as
* the bootstrapped reldescs used by formrdesc(). The resulting tupdesc is
* not 100% kosher: it does not have the correct rowtype OID in tdtypeid, nor
* does it have a TupleConstr field. But it's good enough for the purpose of
* extracting fields.
*/
static TupleDesc
BuildHardcodedDescriptor(int natts, const FormData_pg_attribute *attrs)
{
TupleDesc result;
MemoryContext oldcxt;
int i;
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
result = CreateTemplateTupleDesc(natts);
result->tdtypeid = RECORDOID; /* not right, but we don't care */
result->tdtypmod = -1;
for (i = 0; i < natts; i++)
{
memcpy(TupleDescAttr(result, i), &attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
populate_compact_attribute(result, i);
}
TupleDescFinalize(result);
/* Note: we don't bother to set up a TupleConstr entry */
MemoryContextSwitchTo(oldcxt);
return result;
}
static TupleDesc
GetPgClassDescriptor(void)
{
static TupleDesc pgclassdesc = NULL;
/* Already done? */
if (pgclassdesc == NULL)
pgclassdesc = BuildHardcodedDescriptor(Natts_pg_class,
Desc_pg_class);
return pgclassdesc;
}
static TupleDesc
GetPgIndexDescriptor(void)
{
static TupleDesc pgindexdesc = NULL;
/* Already done? */
if (pgindexdesc == NULL)
pgindexdesc = BuildHardcodedDescriptor(Natts_pg_index,
Desc_pg_index);
return pgindexdesc;
}
/*
* Load any default attribute value definitions for the relation.
*
* ndef is the number of attributes that were marked atthasdef.
*
* Note: we don't make it a hard error to be missing some pg_attrdef records.
* We can limp along as long as nothing needs to use the default value. Code
* that fails to find an expected AttrDefault record should throw an error.
*/
static void
AttrDefaultFetch(Relation relation, int ndef)
{
AttrDefault *attrdef;
Relation adrel;
SysScanDesc adscan;
ScanKeyData skey;
HeapTuple htup;
int found = 0;
/* Allocate array with room for as many entries as expected */
attrdef = (AttrDefault *)
MemoryContextAllocZero(CacheMemoryContext,
ndef * sizeof(AttrDefault));
/* Search pg_attrdef for relevant entries */
ScanKeyInit(&skey,
Anum_pg_attrdef_adrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
adrel = table_open(AttrDefaultRelationId, AccessShareLock);
adscan = systable_beginscan(adrel, AttrDefaultIndexId, true,
NULL, 1, &skey);
while (HeapTupleIsValid(htup = systable_getnext(adscan)))
{
Form_pg_attrdef adform = (Form_pg_attrdef) GETSTRUCT(htup);
Datum val;
bool isnull;
/* protect limited size of array */
if (found >= ndef)
{
elog(WARNING, "unexpected pg_attrdef record found for attribute %d of relation \"%s\"",
adform->adnum, RelationGetRelationName(relation));
break;
}
val = fastgetattr(htup,
Anum_pg_attrdef_adbin,
adrel->rd_att, &isnull);
if (isnull)
elog(WARNING, "null adbin for attribute %d of relation \"%s\"",
adform->adnum, RelationGetRelationName(relation));
else
{
/* detoast and convert to cstring in caller's context */
char *s = TextDatumGetCString(val);
attrdef[found].adnum = adform->adnum;
attrdef[found].adbin = MemoryContextStrdup(CacheMemoryContext, s);
pfree(s);
found++;
}
}
systable_endscan(adscan);
table_close(adrel, AccessShareLock);
if (found != ndef)
elog(WARNING, "%d pg_attrdef record(s) missing for relation \"%s\"",
ndef - found, RelationGetRelationName(relation));
/*
* Sort the AttrDefault entries by adnum, for the convenience of
* equalTupleDescs(). (Usually, they already will be in order, but this
* might not be so if systable_getnext isn't using an index.)
*/
if (found > 1)
qsort(attrdef, found, sizeof(AttrDefault), AttrDefaultCmp);
/* Install array only after it's fully valid */
relation->rd_att->constr->defval = attrdef;
relation->rd_att->constr->num_defval = found;
}
/*
* qsort comparator to sort AttrDefault entries by adnum
*/
static int
AttrDefaultCmp(const void *a, const void *b)
{
const AttrDefault *ada = (const AttrDefault *) a;
const AttrDefault *adb = (const AttrDefault *) b;
return pg_cmp_s16(ada->adnum, adb->adnum);
}
/*
* Load any check constraints for the relation, and update not-null validity
* of invalid constraints.
*
* As with defaults, if we don't find the expected number of them, just warn
* here. The executor should throw an error if an INSERT/UPDATE is attempted.
*/
static void
CheckNNConstraintFetch(Relation relation)
{
ConstrCheck *check;
int ncheck = relation->rd_rel->relchecks;
Relation conrel;
SysScanDesc conscan;
ScanKeyData skey[1];
HeapTuple htup;
int found = 0;
/* Allocate array with room for as many entries as expected, if needed */
if (ncheck > 0)
check = (ConstrCheck *)
MemoryContextAllocZero(CacheMemoryContext,
ncheck * sizeof(ConstrCheck));
else
check = NULL;
/* Search pg_constraint for relevant entries */
ScanKeyInit(&skey[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
conrel = table_open(ConstraintRelationId, AccessShareLock);
conscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true,
NULL, 1, skey);
while (HeapTupleIsValid(htup = systable_getnext(conscan)))
{
Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(htup);
Datum val;
bool isnull;
/*
* If this is a not-null constraint, then only look at it if it's
* invalid, and if so, mark the TupleDesc entry as known invalid.
* Otherwise move on. We'll mark any remaining columns that are still
* in UNKNOWN state as known valid later. This allows us not to have
* to extract the attnum from this constraint tuple in the vast
* majority of cases.
*/
if (conform->contype == CONSTRAINT_NOTNULL)
{
if (!conform->convalidated)
{
AttrNumber attnum;
attnum = extractNotNullColumn(htup);
Assert(relation->rd_att->compact_attrs[attnum - 1].attnullability ==
ATTNULLABLE_UNKNOWN);
relation->rd_att->compact_attrs[attnum - 1].attnullability =
ATTNULLABLE_INVALID;
}
continue;
}
/* For what follows, consider check constraints only */
if (conform->contype != CONSTRAINT_CHECK)
continue;
/* protect limited size of array */
if (found >= ncheck)
{
elog(WARNING, "unexpected pg_constraint record found for relation \"%s\"",
RelationGetRelationName(relation));
break;
}
/* Grab and test conbin is actually set */
val = fastgetattr(htup,
Anum_pg_constraint_conbin,
conrel->rd_att, &isnull);
if (isnull)
elog(WARNING, "null conbin for relation \"%s\"",
RelationGetRelationName(relation));
else
{
/* detoast and convert to cstring in caller's context */
char *s = TextDatumGetCString(val);
check[found].ccenforced = conform->conenforced;
check[found].ccvalid = conform->convalidated;
check[found].ccnoinherit = conform->connoinherit;
check[found].ccname = MemoryContextStrdup(CacheMemoryContext,
NameStr(conform->conname));
check[found].ccbin = MemoryContextStrdup(CacheMemoryContext, s);
pfree(s);
found++;
}
}
systable_endscan(conscan);
table_close(conrel, AccessShareLock);
if (found != ncheck)
elog(WARNING, "%d pg_constraint record(s) missing for relation \"%s\"",
ncheck - found, RelationGetRelationName(relation));
/*
* Sort the records by name. This ensures that CHECKs are applied in a
* deterministic order, and it also makes equalTupleDescs() faster.
*/
if (found > 1)
qsort(check, found, sizeof(ConstrCheck), CheckConstraintCmp);
/* Install array only after it's fully valid */
relation->rd_att->constr->check = check;
relation->rd_att->constr->num_check = found;
}
/*
* qsort comparator to sort ConstrCheck entries by name
*/
static int
CheckConstraintCmp(const void *a, const void *b)
{
const ConstrCheck *ca = (const ConstrCheck *) a;
const ConstrCheck *cb = (const ConstrCheck *) b;
return strcmp(ca->ccname, cb->ccname);
}
/*
* RelationGetFKeyList -- get a list of foreign key info for the relation
*
* Returns a list of ForeignKeyCacheInfo structs, one per FK constraining
* the given relation. This data is a direct copy of relevant fields from
* pg_constraint. The list items are in no particular order.
*
* CAUTION: the returned list is part of the relcache's data, and could
* vanish in a relcache entry reset. Callers must inspect or copy it
* before doing anything that might trigger a cache flush, such as
* system catalog accesses. copyObject() can be used if desired.
* (We define it this way because current callers want to filter and
* modify the list entries anyway, so copying would be a waste of time.)
*/
List *
RelationGetFKeyList(Relation relation)
{
List *result;
Relation conrel;
SysScanDesc conscan;
ScanKeyData skey;
HeapTuple htup;
List *oldlist;
MemoryContext oldcxt;
/* Quick exit if we already computed the list. */
if (relation->rd_fkeyvalid)
return relation->rd_fkeylist;
/*
* We build the list we intend to return (in the caller's context) while
* doing the scan. After successfully completing the scan, we copy that
* list into the relcache entry. This avoids cache-context memory leakage
* if we get some sort of error partway through.
*/
result = NIL;
/* Prepare to scan pg_constraint for entries having conrelid = this rel. */
ScanKeyInit(&skey,
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
conrel = table_open(ConstraintRelationId, AccessShareLock);
conscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true,
NULL, 1, &skey);
while (HeapTupleIsValid(htup = systable_getnext(conscan)))
{
Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(htup);
ForeignKeyCacheInfo *info;
/* consider only foreign keys */
if (constraint->contype != CONSTRAINT_FOREIGN)
continue;
info = makeNode(ForeignKeyCacheInfo);
info->conoid = constraint->oid;
info->conrelid = constraint->conrelid;
info->confrelid = constraint->confrelid;
info->conenforced = constraint->conenforced;
DeconstructFkConstraintRow(htup, &info->nkeys,
info->conkey,
info->confkey,
info->conpfeqop,
NULL, NULL, NULL, NULL);
/* Add FK's node to the result list */
result = lappend(result, info);
}
systable_endscan(conscan);
table_close(conrel, AccessShareLock);
/* Now save a copy of the completed list in the relcache entry. */
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
oldlist = relation->rd_fkeylist;
relation->rd_fkeylist = copyObject(result);
relation->rd_fkeyvalid = true;
MemoryContextSwitchTo(oldcxt);
/* Don't leak the old list, if there is one */
list_free_deep(oldlist);
return result;
}
/*
* RelationGetIndexList -- get a list of OIDs of indexes on this relation
*
* The index list is created only if someone requests it. We scan pg_index
* to find relevant indexes, and add the list to the relcache entry so that
* we won't have to compute it again. Note that shared cache inval of a
* relcache entry will delete the old list and set rd_indexvalid to false,
* so that we must recompute the index list on next request. This handles
* creation or deletion of an index.
*
* Indexes that are marked not indislive are omitted from the returned list.
* Such indexes are expected to be dropped momentarily, and should not be
* touched at all by any caller of this function.
*
* The returned list is guaranteed to be sorted in order by OID. This is
* needed by the executor, since for index types that we obtain exclusive
* locks on when updating the index, all backends must lock the indexes in
* the same order or we will get deadlocks (see ExecOpenIndices()). Any
* consistent ordering would do, but ordering by OID is easy.
*
* Since shared cache inval causes the relcache's copy of the list to go away,
* we return a copy of the list palloc'd in the caller's context. The caller
* may list_free() the returned list after scanning it. This is necessary
* since the caller will typically be doing syscache lookups on the relevant
* indexes, and syscache lookup could cause SI messages to be processed!
*
* In exactly the same way, we update rd_pkindex, which is the OID of the
* relation's primary key index if any, else InvalidOid; and rd_replidindex,
* which is the pg_class OID of an index to be used as the relation's
* replication identity index, or InvalidOid if there is no such index.
*/
List *
RelationGetIndexList(Relation relation)
{
Relation indrel;
SysScanDesc indscan;
ScanKeyData skey;
HeapTuple htup;
List *result;
List *oldlist;
char replident = relation->rd_rel->relreplident;
Oid pkeyIndex = InvalidOid;
Oid candidateIndex = InvalidOid;
bool pkdeferrable = false;
MemoryContext oldcxt;
/* Quick exit if we already computed the list. */
if (relation->rd_indexvalid)
return list_copy(relation->rd_indexlist);
/*
* We build the list we intend to return (in the caller's context) while
* doing the scan. After successfully completing the scan, we copy that
* list into the relcache entry. This avoids cache-context memory leakage
* if we get some sort of error partway through.
*/
result = NIL;
/* Prepare to scan pg_index for entries having indrelid = this rel. */
ScanKeyInit(&skey,
Anum_pg_index_indrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
indrel = table_open(IndexRelationId, AccessShareLock);
indscan = systable_beginscan(indrel, IndexIndrelidIndexId, true,
NULL, 1, &skey);
while (HeapTupleIsValid(htup = systable_getnext(indscan)))
{
Form_pg_index index = (Form_pg_index) GETSTRUCT(htup);
/*
* Ignore any indexes that are currently being dropped. This will
* prevent them from being searched, inserted into, or considered in
* HOT-safety decisions. It's unsafe to touch such an index at all
* since its catalog entries could disappear at any instant.
*/
if (!index->indislive)
continue;
/* add index's OID to result list */
result = lappend_oid(result, index->indexrelid);
/*
* Non-unique or predicate indexes aren't interesting for either oid
* indexes or replication identity indexes, so don't check them.
* Deferred ones are not useful for replication identity either; but
* we do include them if they are PKs.
*/
if (!index->indisunique ||
!heap_attisnull(htup, Anum_pg_index_indpred, NULL))
continue;
/*
* Remember primary key index, if any. For regular tables we do this
* only if the index is valid; but for partitioned tables, then we do
* it even if it's invalid.
*
* The reason for returning invalid primary keys for partitioned
* tables is that we need it to prevent drop of not-null constraints
* that may underlie such a primary key, which is only a problem for
* partitioned tables.
*/
if (index->indisprimary &&
(index->indisvalid ||
relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
pkeyIndex = index->indexrelid;
pkdeferrable = !index->indimmediate;
}
if (!index->indimmediate)
continue;
if (!index->indisvalid)
continue;
/* remember explicitly chosen replica index */
if (index->indisreplident)
candidateIndex = index->indexrelid;
}
systable_endscan(indscan);
table_close(indrel, AccessShareLock);
/* Sort the result list into OID order, per API spec. */
list_sort(result, list_oid_cmp);
/* Now save a copy of the completed list in the relcache entry. */
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
oldlist = relation->rd_indexlist;
relation->rd_indexlist = list_copy(result);
relation->rd_pkindex = pkeyIndex;
relation->rd_ispkdeferrable = pkdeferrable;
if (replident == REPLICA_IDENTITY_DEFAULT && OidIsValid(pkeyIndex) && !pkdeferrable)
relation->rd_replidindex = pkeyIndex;
else if (replident == REPLICA_IDENTITY_INDEX && OidIsValid(candidateIndex))
relation->rd_replidindex = candidateIndex;
else
relation->rd_replidindex = InvalidOid;
relation->rd_indexvalid = true;
MemoryContextSwitchTo(oldcxt);
/* Don't leak the old list, if there is one */
list_free(oldlist);
return result;
}
/*
* RelationGetStatExtList
* get a list of OIDs of statistics objects on this relation
*
* The statistics list is created only if someone requests it, in a way
* similar to RelationGetIndexList(). We scan pg_statistic_ext to find
* relevant statistics, and add the list to the relcache entry so that we
* won't have to compute it again. Note that shared cache inval of a
* relcache entry will delete the old list and set rd_statvalid to 0,
* so that we must recompute the statistics list on next request. This
* handles creation or deletion of a statistics object.
*
* The returned list is guaranteed to be sorted in order by OID, although
* this is not currently needed.
*
* Since shared cache inval causes the relcache's copy of the list to go away,
* we return a copy of the list palloc'd in the caller's context. The caller
* may list_free() the returned list after scanning it. This is necessary
* since the caller will typically be doing syscache lookups on the relevant
* statistics, and syscache lookup could cause SI messages to be processed!
*/
List *
RelationGetStatExtList(Relation relation)
{
Relation indrel;
SysScanDesc indscan;
ScanKeyData skey;
HeapTuple htup;
List *result;
List *oldlist;
MemoryContext oldcxt;
/* Quick exit if we already computed the list. */
if (relation->rd_statvalid != 0)
return list_copy(relation->rd_statlist);
/*
* We build the list we intend to return (in the caller's context) while
* doing the scan. After successfully completing the scan, we copy that
* list into the relcache entry. This avoids cache-context memory leakage
* if we get some sort of error partway through.
*/
result = NIL;
/*
* Prepare to scan pg_statistic_ext for entries having stxrelid = this
* rel.
*/
ScanKeyInit(&skey,
Anum_pg_statistic_ext_stxrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
indrel = table_open(StatisticExtRelationId, AccessShareLock);
indscan = systable_beginscan(indrel, StatisticExtRelidIndexId, true,
NULL, 1, &skey);
while (HeapTupleIsValid(htup = systable_getnext(indscan)))
{
Oid oid = ((Form_pg_statistic_ext) GETSTRUCT(htup))->oid;
result = lappend_oid(result, oid);
}
systable_endscan(indscan);
table_close(indrel, AccessShareLock);
/* Sort the result list into OID order, per API spec. */
list_sort(result, list_oid_cmp);
/* Now save a copy of the completed list in the relcache entry. */
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
oldlist = relation->rd_statlist;
relation->rd_statlist = list_copy(result);
relation->rd_statvalid = true;
MemoryContextSwitchTo(oldcxt);
/* Don't leak the old list, if there is one */
list_free(oldlist);
return result;
}
/*
* RelationGetPrimaryKeyIndex -- get OID of the relation's primary key index
*
* Returns InvalidOid if there is no such index, or if the primary key is
* DEFERRABLE and the caller isn't OK with that.
*/
Oid
RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok)
{
List *ilist;
if (!relation->rd_indexvalid)
{
/* RelationGetIndexList does the heavy lifting. */
ilist = RelationGetIndexList(relation);
list_free(ilist);
Assert(relation->rd_indexvalid);
}
if (deferrable_ok)
return relation->rd_pkindex;
else if (relation->rd_ispkdeferrable)
return InvalidOid;
return relation->rd_pkindex;
}
/*
* RelationGetReplicaIndex -- get OID of the relation's replica identity index
*
* Returns InvalidOid if there is no such index.
*/
Oid
RelationGetReplicaIndex(Relation relation)
{
List *ilist;
if (!relation->rd_indexvalid)
{
/* RelationGetIndexList does the heavy lifting. */
ilist = RelationGetIndexList(relation);
list_free(ilist);
Assert(relation->rd_indexvalid);
}
return relation->rd_replidindex;
}
/*
* RelationGetIndexExpressions -- get the index expressions for an index
*
* We cache the result of transforming pg_index.indexprs into a node tree.
* If the rel is not an index or has no expressional columns, we return NIL.
* Otherwise, the returned tree is copied into the caller's memory context.
* (We don't want to return a pointer to the relcache copy, since it could
* disappear due to relcache invalidation.)
*/
List *
RelationGetIndexExpressions(Relation relation)
{
List *result;
Datum exprsDatum;
bool isnull;
char *exprsString;
MemoryContext oldcxt;
/* Quick exit if we already computed the result. */
if (relation->rd_indexprs)
return copyObject(relation->rd_indexprs);
/* Quick exit if there is nothing to do. */
if (relation->rd_indextuple == NULL ||
heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL))
return NIL;
/*
* We build the tree we intend to return in the caller's context. After
* successfully completing the work, we copy it into the relcache entry.
* This avoids problems if we get some sort of error partway through.
*/
exprsDatum = heap_getattr(relation->rd_indextuple,
Anum_pg_index_indexprs,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
exprsString = TextDatumGetCString(exprsDatum);
result = (List *) stringToNode(exprsString);
pfree(exprsString);
/*
* Run the expressions through eval_const_expressions. This is not just an
* optimization, but is necessary, because the planner will be comparing
* them to similarly-processed qual clauses, and may fail to detect valid
* matches without this. We must not use canonicalize_qual, however,
* since these aren't qual expressions.
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
/* Now save a copy of the completed tree in the relcache entry. */
oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
relation->rd_indexprs = copyObject(result);
MemoryContextSwitchTo(oldcxt);
return result;
}
List *
RelationGetIndexExpressionsExpand(Relation relation)
{
List *result;
MemoryContext oldcxt;
bool isnull;
Datum heapRelidDatum;
Oid heapRelid;
/* Quick exit if we already computed the result. */
if (relation->rd_indexprsExpand)
return copyObject(relation->rd_indexprsExpand);
/* Quick exit if there is nothing to do. */
if (relation->rd_indextuple == NULL ||
heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL))
return NIL;
result = RelationGetIndexExpressions(relation);
if (result == NIL)
return NIL;
heapRelidDatum = heap_getattr(relation->rd_indextuple,
Anum_pg_index_indrelid,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
heapRelid = DatumGetObjectId(heapRelidDatum);
result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
/* Now save a copy of the completed tree in the relcache entry. */
oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
relation->rd_indexprsExpand = copyObject(result);
MemoryContextSwitchTo(oldcxt);
return result;
}
/*
* RelationGetDummyIndexExpressions -- get dummy expressions for an index
*
* Return a list of dummy expressions (just Const nodes) with the same
* types/typmods/collations as the index's real expressions. This is
* useful in situations where we don't want to run any user-defined code.
*/
List *
RelationGetDummyIndexExpressions(Relation relation)
{
List *result;
Datum exprsDatum;
bool isnull;
char *exprsString;
List *rawExprs;
ListCell *lc;
/* Quick exit if there is nothing to do. */
if (relation->rd_indextuple == NULL ||
heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL))
return NIL;
/* Extract raw node tree(s) from index tuple. */
exprsDatum = heap_getattr(relation->rd_indextuple,
Anum_pg_index_indexprs,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
exprsString = TextDatumGetCString(exprsDatum);
rawExprs = (List *) stringToNode(exprsString);
pfree(exprsString);
/* Construct null Consts; the typlen and typbyval are arbitrary. */
result = NIL;
foreach(lc, rawExprs)
{
Node *rawExpr = (Node *) lfirst(lc);
result = lappend(result,
makeConst(exprType(rawExpr),
exprTypmod(rawExpr),
exprCollation(rawExpr),
1,
(Datum) 0,
true,
true));
}
return result;
}
/*
* RelationGetIndexPredicate -- get the index predicate for an index
*
* We cache the result of transforming pg_index.indpred into an implicit-AND
* node tree (suitable for use in planning).
* If the rel is not an index or has no predicate, we return NIL.
* Otherwise, the returned tree is copied into the caller's memory context.
* (We don't want to return a pointer to the relcache copy, since it could
* disappear due to relcache invalidation.)
*/
List *
RelationGetIndexPredicate(Relation relation)
{
List *result;
Datum predDatum;
bool isnull;
char *predString;
MemoryContext oldcxt;
/* Quick exit if we already computed the result. */
if (relation->rd_indpred)
return copyObject(relation->rd_indpred);
/* Quick exit if there is nothing to do. */
if (relation->rd_indextuple == NULL ||
heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL))
return NIL;
/*
* We build the tree we intend to return in the caller's context. After
* successfully completing the work, we copy it into the relcache entry.
* This avoids problems if we get some sort of error partway through.
*/
predDatum = heap_getattr(relation->rd_indextuple,
Anum_pg_index_indpred,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
predString = TextDatumGetCString(predDatum);
result = (List *) stringToNode(predString);
pfree(predString);
/*
* Run the expression through const-simplification and canonicalization.
* This is not just an optimization, but is necessary, because the planner
* will be comparing it to similarly-processed qual clauses, and may fail
* to detect valid matches without this. This must match the processing
* done to qual clauses in preprocess_expression()! (We can skip the
* stuff involving subqueries, however, since we don't allow any in index
* predicates.)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
result = (List *) canonicalize_qual((Expr *) result, false);
/* Also convert to implicit-AND format */
result = make_ands_implicit((Expr *) result);
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
/* Now save a copy of the completed tree in the relcache entry. */
oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
relation->rd_indpred = copyObject(result);
MemoryContextSwitchTo(oldcxt);
return result;
}
List *
RelationGetIndexPredicateExpand(Relation relation)
{
List *result;
MemoryContext oldcxt;
bool isnull;
Datum heapRelidDatum;
Oid heapRelid;
/* Quick exit if we already computed the result. */
if (relation->rd_indpredExpand)
return copyObject(relation->rd_indpredExpand);
/* Quick exit if there is nothing to do. */
if (relation->rd_indextuple == NULL ||
heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL))
return NIL;
result = RelationGetIndexPredicate(relation);
if (result == NIL)
return NIL;
heapRelidDatum = heap_getattr(relation->rd_indextuple,
Anum_pg_index_indrelid,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
heapRelid = DatumGetObjectId(heapRelidDatum);
result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
/* Now save a copy of the completed tree in the relcache entry. */
oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
relation->rd_indpredExpand = copyObject(result);
MemoryContextSwitchTo(oldcxt);
return result;
}
/*
* RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
*
* The result has a bit set for each attribute used anywhere in the index
* definitions of all the indexes on this relation. (This includes not only
* simple index keys, but attributes used in expressions and partial-index
* predicates.)
*
* Depending on attrKind, a bitmap covering attnums for certain columns is
* returned:
* INDEX_ATTR_BITMAP_KEY Columns in non-partial unique indexes not
* in expressions (i.e., usable for FKs)
* INDEX_ATTR_BITMAP_PRIMARY_KEY Columns in the table's primary key
* (beware: even if PK is deferrable!)
* INDEX_ATTR_BITMAP_IDENTITY_KEY Columns in the table's replica identity
* index (empty if FULL)
* INDEX_ATTR_BITMAP_HOT_BLOCKING Columns that block updates from being HOT
* INDEX_ATTR_BITMAP_SUMMARIZED Columns included in summarizing indexes
*
* Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
* we can include system attributes (e.g., OID) in the bitmap representation.
*
* Deferred indexes are considered for the primary key, but not for replica
* identity.
*
* Caller had better hold at least RowExclusiveLock on the target relation
* to ensure it is safe (deadlock-free) for us to take locks on the relation's
* indexes. Note that since the introduction of CREATE INDEX CONCURRENTLY,
* that lock level doesn't guarantee a stable set of indexes, so we have to
* be prepared to retry here in case of a change in the set of indexes.
*
* The returned result is palloc'd in the caller's memory context and should
* be bms_free'd when not needed anymore.
*/
Bitmapset *
RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
{
Bitmapset *uindexattrs; /* columns in unique indexes */
Bitmapset *pkindexattrs; /* columns in the primary index */
Bitmapset *idindexattrs; /* columns in the replica identity */
Bitmapset *hotblockingattrs; /* columns with HOT blocking indexes */
Bitmapset *summarizedattrs; /* columns with summarizing indexes */
List *indexoidlist;
List *newindexoidlist;
Oid relpkindex;
Oid relreplindex;
ListCell *l;
MemoryContext oldcxt;
/* Quick exit if we already computed the result. */
if (relation->rd_attrsvalid)
{
switch (attrKind)
{
case INDEX_ATTR_BITMAP_KEY:
return bms_copy(relation->rd_keyattr);
case INDEX_ATTR_BITMAP_PRIMARY_KEY:
return bms_copy(relation->rd_pkattr);
case INDEX_ATTR_BITMAP_IDENTITY_KEY:
return bms_copy(relation->rd_idattr);
case INDEX_ATTR_BITMAP_HOT_BLOCKING:
return bms_copy(relation->rd_hotblockingattr);
case INDEX_ATTR_BITMAP_SUMMARIZED:
return bms_copy(relation->rd_summarizedattr);
default:
elog(ERROR, "unknown attrKind %u", attrKind);
}
}
/* Fast path if definitely no indexes */
if (!RelationGetForm(relation)->relhasindex)
return NULL;
/*
* Get cached list of index OIDs. If we have to start over, we do so here.
*/
restart:
indexoidlist = RelationGetIndexList(relation);
/* Fall out if no indexes (but relhasindex was set) */
if (indexoidlist == NIL)
return NULL;
/*
* Copy the rd_pkindex and rd_replidindex values computed by
* RelationGetIndexList before proceeding. This is needed because a
* relcache flush could occur inside index_open below, resetting the
* fields managed by RelationGetIndexList. We need to do the work with
* stable values of these fields.
*/
relpkindex = relation->rd_pkindex;
relreplindex = relation->rd_replidindex;
/*
* For each index, add referenced attributes to indexattrs.
*
* Note: we consider all indexes returned by RelationGetIndexList, even if
* they are not indisready or indisvalid. This is important because an
* index for which CREATE INDEX CONCURRENTLY has just started must be
* included in HOT-safety decisions (see README.HOT). If a DROP INDEX
* CONCURRENTLY is far enough along that we should ignore the index, it
* won't be returned at all by RelationGetIndexList.
*/
uindexattrs = NULL;
pkindexattrs = NULL;
idindexattrs = NULL;
hotblockingattrs = NULL;
summarizedattrs = NULL;
foreach(l, indexoidlist)
{
Oid indexOid = lfirst_oid(l);
Relation indexDesc;
Datum datum;
bool isnull;
Node *indexExpressions;
Node *indexPredicate;
int i;
bool isKey; /* candidate key */
bool isPK; /* primary key */
bool isIDKey; /* replica identity index */
Bitmapset **attrs;
indexDesc = index_open(indexOid, AccessShareLock);
/*
* Extract index expressions and index predicate. Note: Don't use
* RelationGetIndexExpressions()/RelationGetIndexPredicate(), because
* those might run constant expressions evaluation, which needs a
* snapshot, which we might not have here. (Also, it's probably more
* sound to collect the bitmaps before any transformations that might
* eliminate columns, but the practical impact of this is limited.)
*/
datum = heap_getattr(indexDesc->rd_indextuple, Anum_pg_index_indexprs,
GetPgIndexDescriptor(), &isnull);
if (!isnull)
indexExpressions = stringToNode(TextDatumGetCString(datum));
else
indexExpressions = NULL;
datum = heap_getattr(indexDesc->rd_indextuple, Anum_pg_index_indpred,
GetPgIndexDescriptor(), &isnull);
if (!isnull)
indexPredicate = stringToNode(TextDatumGetCString(datum));
else
indexPredicate = NULL;
/* Can this index be referenced by a foreign key? */
isKey = indexDesc->rd_index->indisunique &&
indexExpressions == NULL &&
indexPredicate == NULL;
/* Is this a primary key? */
isPK = (indexOid == relpkindex);
/* Is this index the configured (or default) replica identity? */
isIDKey = (indexOid == relreplindex);
/*
* If the index is summarizing, it doesn't block HOT updates, but we
* may still need to update it (if the attributes were modified). So
* decide which bitmap we'll update in the following loop.
*/
if (indexDesc->rd_indam->amsummarizing)
attrs = &summarizedattrs;
else
attrs = &hotblockingattrs;
/* Collect simple attribute references */
for (i = 0; i < indexDesc->rd_index->indnatts; i++)
{
int attrnum = indexDesc->rd_index->indkey.values[i];
/*
* Since we have covering indexes with non-key columns, we must
* handle them accurately here. non-key columns must be added into
* hotblockingattrs or summarizedattrs, since they are in index,
* and update shouldn't miss them.
*
* Summarizing indexes do not block HOT, but do need to be updated
* when the column value changes, thus require a separate
* attribute bitmapset.
*
* Obviously, non-key columns couldn't be referenced by foreign
* key or identity key. Hence we do not include them into
* uindexattrs, pkindexattrs and idindexattrs bitmaps.
*/
if (attrnum != 0)
{
*attrs = bms_add_member(*attrs,
attrnum - FirstLowInvalidHeapAttributeNumber);
if (isKey && i < indexDesc->rd_index->indnkeyatts)
uindexattrs = bms_add_member(uindexattrs,
attrnum - FirstLowInvalidHeapAttributeNumber);
if (isPK && i < indexDesc->rd_index->indnkeyatts)
pkindexattrs = bms_add_member(pkindexattrs,
attrnum - FirstLowInvalidHeapAttributeNumber);
if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
idindexattrs = bms_add_member(idindexattrs,
attrnum - FirstLowInvalidHeapAttributeNumber);
}
}
/* Collect all attributes used in expressions, too */
pull_varattnos(indexExpressions, 1, attrs);
/* Collect all attributes in the index predicate, too */
pull_varattnos(indexPredicate, 1, attrs);
index_close(indexDesc, AccessShareLock);
}
/*
* During one of the index_opens in the above loop, we might have received
* a relcache flush event on this relcache entry, which might have been
* signaling a change in the rel's index list. If so, we'd better start
* over to ensure we deliver up-to-date attribute bitmaps.
*/
newindexoidlist = RelationGetIndexList(relation);
if (equal(indexoidlist, newindexoidlist) &&
relpkindex == relation->rd_pkindex &&
relreplindex == relation->rd_replidindex)
{
/* Still the same index set, so proceed */
list_free(newindexoidlist);
list_free(indexoidlist);
}
else
{
/* Gotta do it over ... might as well not leak memory */
list_free(newindexoidlist);
list_free(indexoidlist);
bms_free(uindexattrs);
bms_free(pkindexattrs);
bms_free(idindexattrs);
bms_free(hotblockingattrs);
bms_free(summarizedattrs);
goto restart;
}
/* Don't leak the old values of these bitmaps, if any */
relation->rd_attrsvalid = false;
bms_free(relation->rd_keyattr);
relation->rd_keyattr = NULL;
bms_free(relation->rd_pkattr);
relation->rd_pkattr = NULL;
bms_free(relation->rd_idattr);
relation->rd_idattr = NULL;
bms_free(relation->rd_hotblockingattr);
relation->rd_hotblockingattr = NULL;
bms_free(relation->rd_summarizedattr);
relation->rd_summarizedattr = NULL;
/*
* Now save copies of the bitmaps in the relcache entry. We intentionally
* set rd_attrsvalid last, because that's the one that signals validity of
* the values; if we run out of memory before making that copy, we won't
* leave the relcache entry looking like the other ones are valid but
* empty.
*/
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
relation->rd_keyattr = bms_copy(uindexattrs);
relation->rd_pkattr = bms_copy(pkindexattrs);
relation->rd_idattr = bms_copy(idindexattrs);
relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
relation->rd_summarizedattr = bms_copy(summarizedattrs);
relation->rd_attrsvalid = true;
MemoryContextSwitchTo(oldcxt);
/* We return our original working copy for caller to play with */
switch (attrKind)
{
case INDEX_ATTR_BITMAP_KEY:
return uindexattrs;
case INDEX_ATTR_BITMAP_PRIMARY_KEY:
return pkindexattrs;
case INDEX_ATTR_BITMAP_IDENTITY_KEY:
return idindexattrs;
case INDEX_ATTR_BITMAP_HOT_BLOCKING:
return hotblockingattrs;
case INDEX_ATTR_BITMAP_SUMMARIZED:
return summarizedattrs;
default:
elog(ERROR, "unknown attrKind %u", attrKind);
return NULL;
}
}
/*
* RelationGetIdentityKeyBitmap -- get a bitmap of replica identity attribute
* numbers
*
* A bitmap of index attribute numbers for the configured replica identity
* index is returned.
*
* See also comments of RelationGetIndexAttrBitmap().
*
* This is a special purpose function used during logical replication. Here,
* unlike RelationGetIndexAttrBitmap(), we don't acquire a lock on the required
* index as we build the cache entry using a historic snapshot and all the
* later changes are absorbed while decoding WAL. Due to this reason, we don't
* need to retry here in case of a change in the set of indexes.
*/
Bitmapset *
RelationGetIdentityKeyBitmap(Relation relation)
{
Bitmapset *idindexattrs = NULL; /* columns in the replica identity */
Relation indexDesc;
int i;
Oid replidindex;
MemoryContext oldcxt;
/* Quick exit if we already computed the result */
if (relation->rd_idattr != NULL)
return bms_copy(relation->rd_idattr);
/* Fast path if definitely no indexes */
if (!RelationGetForm(relation)->relhasindex)
return NULL;
/* Historic snapshot must be set. */
Assert(HistoricSnapshotActive());
replidindex = RelationGetReplicaIndex(relation);
/* Fall out if there is no replica identity index */
if (!OidIsValid(replidindex))
return NULL;
/* Look up the description for the replica identity index */
indexDesc = RelationIdGetRelation(replidindex);
if (!RelationIsValid(indexDesc))
elog(ERROR, "could not open relation with OID %u",
relation->rd_replidindex);
/* Add referenced attributes to idindexattrs */
for (i = 0; i < indexDesc->rd_index->indnatts; i++)
{
int attrnum = indexDesc->rd_index->indkey.values[i];
/*
* We don't include non-key columns into idindexattrs bitmaps. See
* RelationGetIndexAttrBitmap.
*/
if (attrnum != 0)
{
if (i < indexDesc->rd_index->indnkeyatts)
idindexattrs = bms_add_member(idindexattrs,
attrnum - FirstLowInvalidHeapAttributeNumber);
}
}
RelationClose(indexDesc);
/* Don't leak the old values of these bitmaps, if any */
bms_free(relation->rd_idattr);
relation->rd_idattr = NULL;
/* Now save copy of the bitmap in the relcache entry */
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
relation->rd_idattr = bms_copy(idindexattrs);
MemoryContextSwitchTo(oldcxt);
/* We return our original working copy for caller to play with */
return idindexattrs;
}
/*
* RelationGetExclusionInfo -- get info about index's exclusion constraint
*
* This should be called only for an index that is known to have an associated
* exclusion constraint or primary key/unique constraint using WITHOUT
* OVERLAPS.
*
* It returns arrays (palloc'd in caller's context) of the exclusion operator
* OIDs, their underlying functions' OIDs, and their strategy numbers in the
* index's opclasses. We cache all this information since it requires a fair
* amount of work to get.
*/
void
RelationGetExclusionInfo(Relation indexRelation,
Oid **operators,
Oid **procs,
uint16 **strategies)
{
int indnkeyatts;
Oid *ops;
Oid *funcs;
uint16 *strats;
Relation conrel;
SysScanDesc conscan;
ScanKeyData skey[1];
HeapTuple htup;
bool found;
MemoryContext oldcxt;
int i;
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(indexRelation);
/* Allocate result space in caller context */
*operators = ops = palloc_array(Oid, indnkeyatts);
*procs = funcs = palloc_array(Oid, indnkeyatts);
*strategies = strats = palloc_array(uint16, indnkeyatts);
/* Quick exit if we have the data cached already */
if (indexRelation->rd_exclstrats != NULL)
{
memcpy(ops, indexRelation->rd_exclops, sizeof(Oid) * indnkeyatts);
memcpy(funcs, indexRelation->rd_exclprocs, sizeof(Oid) * indnkeyatts);
memcpy(strats, indexRelation->rd_exclstrats, sizeof(uint16) * indnkeyatts);
return;
}
/*
* Search pg_constraint for the constraint associated with the index. To
* make this not too painfully slow, we use the index on conrelid; that
* will hold the parent relation's OID not the index's own OID.
*
* Note: if we wanted to rely on the constraint name matching the index's
* name, we could just do a direct lookup using pg_constraint's unique
* index. For the moment it doesn't seem worth requiring that.
*/
ScanKeyInit(&skey[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(indexRelation->rd_index->indrelid));
conrel = table_open(ConstraintRelationId, AccessShareLock);
conscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true,
NULL, 1, skey);
found = false;
while (HeapTupleIsValid(htup = systable_getnext(conscan)))
{
Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(htup);
Datum val;
bool isnull;
ArrayType *arr;
int nelem;
/* We want the exclusion constraint owning the index */
if ((conform->contype != CONSTRAINT_EXCLUSION &&
!(conform->conperiod && (conform->contype == CONSTRAINT_PRIMARY
|| conform->contype == CONSTRAINT_UNIQUE))) ||
conform->conindid != RelationGetRelid(indexRelation))
continue;
/* There should be only one */
if (found)
elog(ERROR, "unexpected exclusion constraint record found for rel %s",
RelationGetRelationName(indexRelation));
found = true;
/* Extract the operator OIDS from conexclop */
val = fastgetattr(htup,
Anum_pg_constraint_conexclop,
conrel->rd_att, &isnull);
if (isnull)
elog(ERROR, "null conexclop for rel %s",
RelationGetRelationName(indexRelation));
arr = DatumGetArrayTypeP(val); /* ensure not toasted */
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem != indnkeyatts ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "conexclop is not a 1-D Oid array");
memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * indnkeyatts);
}
systable_endscan(conscan);
table_close(conrel, AccessShareLock);
if (!found)
elog(ERROR, "exclusion constraint record missing for rel %s",
RelationGetRelationName(indexRelation));
/* We need the func OIDs and strategy numbers too */
for (i = 0; i < indnkeyatts; i++)
{
funcs[i] = get_opcode(ops[i]);
strats[i] = get_op_opfamily_strategy(ops[i],
indexRelation->rd_opfamily[i]);
/* shouldn't fail, since it was checked at index creation */
if (strats[i] == InvalidStrategy)
elog(ERROR, "could not find strategy for operator %u in family %u",
ops[i], indexRelation->rd_opfamily[i]);
}
/* Save a copy of the results in the relcache entry. */
oldcxt = MemoryContextSwitchTo(indexRelation->rd_indexcxt);
indexRelation->rd_exclops = palloc_array(Oid, indnkeyatts);
indexRelation->rd_exclprocs = palloc_array(Oid, indnkeyatts);
indexRelation->rd_exclstrats = palloc_array(uint16, indnkeyatts);
memcpy(indexRelation->rd_exclops, ops, sizeof(Oid) * indnkeyatts);
memcpy(indexRelation->rd_exclprocs, funcs, sizeof(Oid) * indnkeyatts);
memcpy(indexRelation->rd_exclstrats, strats, sizeof(uint16) * indnkeyatts);
MemoryContextSwitchTo(oldcxt);
}
/*
* Get the publication information for the given relation.
*
* Traverse all the publications which the relation is in to get the
* publication actions and validate:
* 1. The row filter expressions for such publications if any. We consider the
* row filter expression as invalid if it references any column which is not
* part of REPLICA IDENTITY.
* 2. The column list for such publication if any. We consider the column list
* invalid if REPLICA IDENTITY contains any column that is not part of it.
* 3. The generated columns of the relation for such publications. We consider
* any reference of an unpublished generated column in REPLICA IDENTITY as
* invalid.
*
* To avoid fetching the publication information repeatedly, we cache the
* publication actions, row filter validation information, column list
* validation information, and generated column validation information.
*/
void
RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
{
List *puboids = NIL;
List *exceptpuboids = NIL;
List *alltablespuboids;
ListCell *lc;
MemoryContext oldcxt;
Oid schemaid;
List *ancestors = NIL;
Oid relid = RelationGetRelid(relation);
/*
* If not publishable, it publishes no actions. (pgoutput_change() will
* ignore it.)
*/
if (!is_publishable_relation(relation))
{
memset(pubdesc, 0, sizeof(PublicationDesc));
pubdesc->rf_valid_for_update = true;
pubdesc->rf_valid_for_delete = true;
pubdesc->cols_valid_for_update = true;
pubdesc->cols_valid_for_delete = true;
pubdesc->gencols_valid_for_update = true;
pubdesc->gencols_valid_for_delete = true;
return;
}
if (relation->rd_pubdesc)
{
memcpy(pubdesc, relation->rd_pubdesc, sizeof(PublicationDesc));
return;
}
memset(pubdesc, 0, sizeof(PublicationDesc));
pubdesc->rf_valid_for_update = true;
pubdesc->rf_valid_for_delete = true;
pubdesc->cols_valid_for_update = true;
pubdesc->cols_valid_for_delete = true;
pubdesc->gencols_valid_for_update = true;
pubdesc->gencols_valid_for_delete = true;
/* Fetch the publication membership info. */
puboids = GetRelationIncludedPublications(relid);
schemaid = RelationGetNamespace(relation);
puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid));
if (relation->rd_rel->relispartition)
{
Oid last_ancestor_relid;
/* Add publications that the ancestors are in too. */
ancestors = get_partition_ancestors(relid);
last_ancestor_relid = llast_oid(ancestors);
foreach(lc, ancestors)
{
Oid ancestor = lfirst_oid(lc);
puboids = list_concat_unique_oid(puboids,
GetRelationIncludedPublications(ancestor));
schemaid = get_rel_namespace(ancestor);
puboids = list_concat_unique_oid(puboids,
GetSchemaPublications(schemaid));
}
/*
* Only the top-most ancestor can appear in the EXCEPT clause.
* Therefore, for a partition, exclusion must be evaluated at the
* top-most ancestor.
*/
exceptpuboids = GetRelationExcludedPublications(last_ancestor_relid);
}
else
{
/*
* For a regular table or a root partitioned table, check exclusion on
* table itself.
*/
exceptpuboids = GetRelationExcludedPublications(relid);
}
alltablespuboids = GetAllTablesPublications();
puboids = list_concat_unique_oid(puboids,
list_difference_oid(alltablespuboids,
exceptpuboids));
foreach(lc, puboids)
{
Oid pubid = lfirst_oid(lc);
HeapTuple tup;
Form_pg_publication pubform;
bool invalid_column_list;
bool invalid_gen_col;
tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for publication %u", pubid);
pubform = (Form_pg_publication) GETSTRUCT(tup);
pubdesc->pubactions.pubinsert |= pubform->pubinsert;
pubdesc->pubactions.pubupdate |= pubform->pubupdate;
pubdesc->pubactions.pubdelete |= pubform->pubdelete;
pubdesc->pubactions.pubtruncate |= pubform->pubtruncate;
/*
* Check if all columns referenced in the filter expression are part
* of the REPLICA IDENTITY index or not.
*
* If the publication is FOR ALL TABLES then it means the table has no
* row filters and we can skip the validation.
*/
if (!pubform->puballtables &&
(pubform->pubupdate || pubform->pubdelete) &&
pub_rf_contains_invalid_column(pubid, relation, ancestors,
pubform->pubviaroot))
{
if (pubform->pubupdate)
pubdesc->rf_valid_for_update = false;
if (pubform->pubdelete)
pubdesc->rf_valid_for_delete = false;
}
/*
* Check if all columns are part of the REPLICA IDENTITY index or not.
*
* Check if all generated columns included in the REPLICA IDENTITY are
* published.
*/
if ((pubform->pubupdate || pubform->pubdelete) &&
pub_contains_invalid_column(pubid, relation, ancestors,
pubform->pubviaroot,
pubform->pubgencols,
&invalid_column_list,
&invalid_gen_col))
{
if (pubform->pubupdate)
{
pubdesc->cols_valid_for_update = !invalid_column_list;
pubdesc->gencols_valid_for_update = !invalid_gen_col;
}
if (pubform->pubdelete)
{
pubdesc->cols_valid_for_delete = !invalid_column_list;
pubdesc->gencols_valid_for_delete = !invalid_gen_col;
}
}
ReleaseSysCache(tup);
/*
* If we know everything is replicated and the row filter is invalid
* for update and delete, there is no point to check for other
* publications.
*/
if (pubdesc->pubactions.pubinsert && pubdesc->pubactions.pubupdate &&
pubdesc->pubactions.pubdelete && pubdesc->pubactions.pubtruncate &&
!pubdesc->rf_valid_for_update && !pubdesc->rf_valid_for_delete)
break;
/*
* If we know everything is replicated and the column list is invalid
* for update and delete, there is no point to check for other
* publications.
*/
if (pubdesc->pubactions.pubinsert && pubdesc->pubactions.pubupdate &&
pubdesc->pubactions.pubdelete && pubdesc->pubactions.pubtruncate &&
!pubdesc->cols_valid_for_update && !pubdesc->cols_valid_for_delete)
break;
/*
* If we know everything is replicated and replica identity has an
* unpublished generated column, there is no point to check for other
* publications.
*/
if (pubdesc->pubactions.pubinsert && pubdesc->pubactions.pubupdate &&
pubdesc->pubactions.pubdelete && pubdesc->pubactions.pubtruncate &&
!pubdesc->gencols_valid_for_update &&
!pubdesc->gencols_valid_for_delete)
break;
}
if (relation->rd_pubdesc)
{
pfree(relation->rd_pubdesc);
relation->rd_pubdesc = NULL;
}
/* Now save copy of the descriptor in the relcache entry. */
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
relation->rd_pubdesc = palloc_object(PublicationDesc);
memcpy(relation->rd_pubdesc, pubdesc, sizeof(PublicationDesc));
MemoryContextSwitchTo(oldcxt);
}
static bytea **
CopyIndexAttOptions(bytea **srcopts, int natts)
{
bytea **opts = palloc_array(bytea *, natts);
for (int i = 0; i < natts; i++)
{
bytea *opt = srcopts[i];
opts[i] = !opt ? NULL : (bytea *)
DatumGetPointer(datumCopy(PointerGetDatum(opt), false, -1));
}
return opts;
}
/*
* RelationGetIndexAttOptions
* get AM/opclass-specific options for an index parsed into a binary form
*/
bytea **
RelationGetIndexAttOptions(Relation relation, bool copy)
{
MemoryContext oldcxt;
bytea **opts = relation->rd_opcoptions;
Oid relid = RelationGetRelid(relation);
int natts = RelationGetNumberOfAttributes(relation); /* XXX
* IndexRelationGetNumberOfKeyAttributes */
int i;
/* Try to copy cached options. */
if (opts)
return copy ? CopyIndexAttOptions(opts, natts) : opts;
/* Get and parse opclass options. */
opts = palloc0_array(bytea *, natts);
for (i = 0; i < natts; i++)
{
if (criticalRelcachesBuilt && relid != AttributeRelidNumIndexId)
{
Datum attoptions = get_attoptions(relid, i + 1);
opts[i] = index_opclass_options(relation, i + 1, attoptions, false);
if (attoptions != (Datum) 0)
pfree(DatumGetPointer(attoptions));
}
}
/* Copy parsed options to the cache. */
oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
relation->rd_opcoptions = CopyIndexAttOptions(opts, natts);
MemoryContextSwitchTo(oldcxt);
if (copy)
return opts;
for (i = 0; i < natts; i++)
{
if (opts[i])
pfree(opts[i]);
}
pfree(opts);
return relation->rd_opcoptions;
}
/*
* Routines to support ereport() reports of relation-related errors
*
* These could have been put into elog.c, but it seems like a module layering
* violation to have elog.c calling relcache or syscache stuff --- and we
* definitely don't want elog.h including rel.h. So we put them here.
*/
/*
* errtable --- stores schema_name and table_name of a table
* within the current errordata.
*/
int
errtable(Relation rel)
{
err_generic_string(PG_DIAG_SCHEMA_NAME,
get_namespace_name(RelationGetNamespace(rel)));
err_generic_string(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
return 0; /* return value does not matter */
}
/*
* errtablecol --- stores schema_name, table_name and column_name
* of a table column within the current errordata.
*
* The column is specified by attribute number --- for most callers, this is
* easier and less error-prone than getting the column name for themselves.
*/
int
errtablecol(Relation rel, int attnum)
{
TupleDesc reldesc = RelationGetDescr(rel);
const char *colname;
/* Use reldesc if it's a user attribute, else consult the catalogs */
if (attnum > 0 && attnum <= reldesc->natts)
colname = NameStr(TupleDescAttr(reldesc, attnum - 1)->attname);
else
colname = get_attname(RelationGetRelid(rel), attnum, false);
return errtablecolname(rel, colname);
}
/*
* errtablecolname --- stores schema_name, table_name and column_name
* of a table column within the current errordata, where the column name is
* given directly rather than extracted from the relation's catalog data.
*
* Don't use this directly unless errtablecol() is inconvenient for some
* reason. This might possibly be needed during intermediate states in ALTER
* TABLE, for instance.
*/
int
errtablecolname(Relation rel, const char *colname)
{
errtable(rel);
err_generic_string(PG_DIAG_COLUMN_NAME, colname);
return 0; /* return value does not matter */
}
/*
* errtableconstraint --- stores schema_name, table_name and constraint_name
* of a table-related constraint within the current errordata.
*/
int
errtableconstraint(Relation rel, const char *conname)
{
errtable(rel);
err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname);
return 0; /* return value does not matter */
}
/*
* load_relcache_init_file, write_relcache_init_file
*
* In late 1992, we started regularly having databases with more than
* a thousand classes in them. With this number of classes, it became
* critical to do indexed lookups on the system catalogs.
*
* Bootstrapping these lookups is very hard. We want to be able to
* use an index on pg_attribute, for example, but in order to do so,
* we must have read pg_attribute for the attributes in the index,
* which implies that we need to use the index.
*
* In order to get around the problem, we do the following:
*
* + When the database system is initialized (at initdb time), we
* don't use indexes. We do sequential scans.
*
* + When the backend is started up in normal mode, we load an image
* of the appropriate relation descriptors, in internal format,
* from an initialization file in the data/base/... directory.
*
* + If the initialization file isn't there, then we create the
* relation descriptors using sequential scans and write 'em to
* the initialization file for use by subsequent backends.
*
* As of Postgres 9.0, there is one local initialization file in each
* database, plus one shared initialization file for shared catalogs.
*
* We could dispense with the initialization files and just build the
* critical reldescs the hard way on every backend startup, but that
* slows down backend startup noticeably.
*
* We can in fact go further, and save more relcache entries than
* just the ones that are absolutely critical; this allows us to speed
* up backend startup by not having to build such entries the hard way.
* Presently, all the catalog and index entries that are referred to
* by catcaches are stored in the initialization files.
*
* The same mechanism that detects when catcache and relcache entries
* need to be invalidated (due to catalog updates) also arranges to
* unlink the initialization files when the contents may be out of date.
* The files will then be rebuilt during the next backend startup.
*/
/*
* load_relcache_init_file -- attempt to load cache from the shared
* or local cache init file
*
* If successful, return true and set criticalRelcachesBuilt or
* criticalSharedRelcachesBuilt to true.
* If not successful, return false.
*
* NOTE: we assume we are already switched into CacheMemoryContext.
*/
static bool
load_relcache_init_file(bool shared)
{
FILE *fp;
char initfilename[MAXPGPATH];
Relation *rels;
int relno,
num_rels,
max_rels,
nailed_rels,
nailed_indexes,
magic;
int i;
if (shared)
snprintf(initfilename, sizeof(initfilename), "global/%s",
RELCACHE_INIT_FILENAME);
else
snprintf(initfilename, sizeof(initfilename), "%s/%s",
DatabasePath, RELCACHE_INIT_FILENAME);
fp = AllocateFile(initfilename, PG_BINARY_R);
if (fp == NULL)
return false;
/*
* Read the index relcache entries from the file. Note we will not enter
* any of them into the cache if the read fails partway through; this
* helps to guard against broken init files.
*/
max_rels = 100;
rels = (Relation *) palloc(max_rels * sizeof(Relation));
num_rels = 0;
nailed_rels = nailed_indexes = 0;
/* check for correct magic number (compatible version) */
if (fread(&magic, 1, sizeof(magic), fp) != sizeof(magic))
goto read_failed;
if (magic != RELCACHE_INIT_FILEMAGIC)
goto read_failed;
for (relno = 0;; relno++)
{
Size len;
size_t nread;
Relation rel;
Form_pg_class relform;
bool has_not_null;
/* first read the relation descriptor length */
nread = fread(&len, 1, sizeof(len), fp);
if (nread != sizeof(len))
{
if (nread == 0)
break; /* end of file */
goto read_failed;
}
/* safety check for incompatible relcache layout */
if (len != sizeof(RelationData))
goto read_failed;
/* allocate another relcache header */
if (num_rels >= max_rels)
{
max_rels *= 2;
rels = (Relation *) repalloc(rels, max_rels * sizeof(Relation));
}
rel = rels[num_rels++] = (Relation) palloc(len);
/* then, read the Relation structure */
if (fread(rel, 1, len, fp) != len)
goto read_failed;
/* next read the relation tuple form */
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
relform = (Form_pg_class) palloc(len);
if (fread(relform, 1, len, fp) != len)
goto read_failed;
rel->rd_rel = relform;
/* initialize attribute tuple forms */
rel->rd_att = CreateTemplateTupleDesc(relform->relnatts);
rel->rd_att->tdrefcount = 1; /* mark as refcounted */
rel->rd_att->tdtypeid = relform->reltype ? relform->reltype : RECORDOID;
rel->rd_att->tdtypmod = -1; /* just to be sure */
/* next read all the attribute tuple form data entries */
has_not_null = false;
for (i = 0; i < relform->relnatts; i++)
{
Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
if (len != ATTRIBUTE_FIXED_PART_SIZE)
goto read_failed;
if (fread(attr, 1, len, fp) != len)
goto read_failed;
has_not_null |= attr->attnotnull;
populate_compact_attribute(rel->rd_att, i);
}
TupleDescFinalize(rel->rd_att);
/* next read the access method specific field */
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
if (len > 0)
{
rel->rd_options = palloc(len);
if (fread(rel->rd_options, 1, len, fp) != len)
goto read_failed;
if (len != VARSIZE(rel->rd_options))
goto read_failed; /* sanity check */
}
else
{
rel->rd_options = NULL;
}
/* mark not-null status */
if (has_not_null)
{
TupleConstr *constr = palloc0_object(TupleConstr);
constr->has_not_null = true;
rel->rd_att->constr = constr;
}
/*
* If it's an index, there's more to do. Note we explicitly ignore
* partitioned indexes here.
*/
if (rel->rd_rel->relkind == RELKIND_INDEX)
{
MemoryContext indexcxt;
Oid *opfamily;
Oid *opcintype;
RegProcedure *support;
int nsupport;
int16 *indoption;
Oid *indcollation;
/* Count nailed indexes to ensure we have 'em all */
if (rel->rd_isnailed)
nailed_indexes++;
/* read the pg_index tuple */
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
rel->rd_indextuple = (HeapTuple) palloc(len);
if (fread(rel->rd_indextuple, 1, len, fp) != len)
goto read_failed;
/* Fix up internal pointers in the tuple -- see heap_copytuple */
rel->rd_indextuple->t_data = (HeapTupleHeader) ((char *) rel->rd_indextuple + HEAPTUPLESIZE);
rel->rd_index = (Form_pg_index) GETSTRUCT(rel->rd_indextuple);
/*
* prepare index info context --- parameters should match
* RelationInitIndexAccessInfo
*/
indexcxt = AllocSetContextCreate(CacheMemoryContext,
"index info",
ALLOCSET_SMALL_SIZES);
rel->rd_indexcxt = indexcxt;
MemoryContextCopyAndSetIdentifier(indexcxt,
RelationGetRelationName(rel));
/*
* Now we can fetch the index AM's API struct. (We can't store
* that in the init file, since it contains function pointers that
* might vary across server executions. Fortunately, it should be
* safe to call the amhandler even while bootstrapping indexes.)
*/
InitIndexAmRoutine(rel);
/* read the vector of opfamily OIDs */
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
opfamily = (Oid *) MemoryContextAlloc(indexcxt, len);
if (fread(opfamily, 1, len, fp) != len)
goto read_failed;
rel->rd_opfamily = opfamily;
/* read the vector of opcintype OIDs */
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
opcintype = (Oid *) MemoryContextAlloc(indexcxt, len);
if (fread(opcintype, 1, len, fp) != len)
goto read_failed;
rel->rd_opcintype = opcintype;
/* read the vector of support procedure OIDs */
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
support = (RegProcedure *) MemoryContextAlloc(indexcxt, len);
if (fread(support, 1, len, fp) != len)
goto read_failed;
rel->rd_support = support;
/* read the vector of collation OIDs */
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
indcollation = (Oid *) MemoryContextAlloc(indexcxt, len);
if (fread(indcollation, 1, len, fp) != len)
goto read_failed;
rel->rd_indcollation = indcollation;
/* read the vector of indoption values */
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
indoption = (int16 *) MemoryContextAlloc(indexcxt, len);
if (fread(indoption, 1, len, fp) != len)
goto read_failed;
rel->rd_indoption = indoption;
/* read the vector of opcoptions values */
rel->rd_opcoptions = (bytea **)
MemoryContextAllocZero(indexcxt, sizeof(*rel->rd_opcoptions) * relform->relnatts);
for (i = 0; i < relform->relnatts; i++)
{
if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
if (len > 0)
{
rel->rd_opcoptions[i] = (bytea *) MemoryContextAlloc(indexcxt, len);
if (fread(rel->rd_opcoptions[i], 1, len, fp) != len)
goto read_failed;
}
}
/* set up zeroed fmgr-info vector */
nsupport = relform->relnatts * rel->rd_indam->amsupport;
rel->rd_supportinfo = (FmgrInfo *)
MemoryContextAllocZero(indexcxt, nsupport * sizeof(FmgrInfo));
}
else
{
/* Count nailed rels to ensure we have 'em all */
if (rel->rd_isnailed)
nailed_rels++;
/* Load table AM data */
if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE)
RelationInitTableAccessMethod(rel);
Assert(rel->rd_index == NULL);
Assert(rel->rd_indextuple == NULL);
Assert(rel->rd_indexcxt == NULL);
Assert(rel->rd_indam == NULL);
Assert(rel->rd_opfamily == NULL);
Assert(rel->rd_opcintype == NULL);
Assert(rel->rd_support == NULL);
Assert(rel->rd_supportinfo == NULL);
Assert(rel->rd_indoption == NULL);
Assert(rel->rd_indcollation == NULL);
Assert(rel->rd_opcoptions == NULL);
}
/*
* Rules and triggers are not saved (mainly because the internal
* format is complex and subject to change). They must be rebuilt if
* needed by RelationCacheInitializePhase3. This is not expected to
* be a big performance hit since few system catalogs have such. Ditto
* for RLS policy data, partition info, index expressions, predicates,
* exclusion info, and FDW info.
*/
rel->rd_rules = NULL;
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkey = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partdesc = NULL;
rel->rd_partdesc_nodetached = NULL;
rel->rd_partdesc_nodetached_xmin = InvalidTransactionId;
rel->rd_pdcxt = NULL;
rel->rd_pddcxt = NULL;
rel->rd_partcheck = NIL;
rel->rd_partcheckvalid = false;
rel->rd_partcheckcxt = NULL;
rel->rd_indexprs = NIL;
rel->rd_indexprsExpand = NIL;
rel->rd_indpred = NIL;
rel->rd_indpredExpand = NIL;
rel->rd_exclops = NULL;
rel->rd_exclprocs = NULL;
rel->rd_exclstrats = NULL;
rel->rd_fdwroutine = NULL;
/*
* Reset transient-state fields in the relcache entry
*/
rel->rd_smgr = NULL;
if (rel->rd_isnailed)
rel->rd_refcnt = 1;
else
rel->rd_refcnt = 0;
rel->rd_indexvalid = false;
rel->rd_indexlist = NIL;
rel->rd_pkindex = InvalidOid;
rel->rd_replidindex = InvalidOid;
rel->rd_attrsvalid = false;
rel->rd_keyattr = NULL;
rel->rd_pkattr = NULL;
rel->rd_idattr = NULL;
rel->rd_pubdesc = NULL;
rel->rd_statvalid = false;
rel->rd_statlist = NIL;
rel->rd_fkeyvalid = false;
rel->rd_fkeylist = NIL;
rel->rd_createSubid = InvalidSubTransactionId;
rel->rd_newRelfilelocatorSubid = InvalidSubTransactionId;
rel->rd_firstRelfilelocatorSubid = InvalidSubTransactionId;
rel->rd_droppedSubid = InvalidSubTransactionId;
rel->rd_amcache = NULL;
rel->pgstat_info = NULL;
/*
* Recompute lock and physical addressing info. This is needed in
* case the pg_internal.init file was copied from some other database
* by CREATE DATABASE.
*/
RelationInitLockInfo(rel);
RelationInitPhysicalAddr(rel);
}
/*
* We reached the end of the init file without apparent problem. Did we
* get the right number of nailed items? This is a useful crosscheck in
* case the set of critical rels or indexes changes. However, that should
* not happen in a normally-running system, so let's bleat if it does.
*
* For the shared init file, we're called before client authentication is
* done, which means that elog(WARNING) will go only to the postmaster
* log, where it's easily missed. To ensure that developers notice bad
* values of NUM_CRITICAL_SHARED_RELS/NUM_CRITICAL_SHARED_INDEXES, we put
* an Assert(false) there.
*/
if (shared)
{
if (nailed_rels != NUM_CRITICAL_SHARED_RELS ||
nailed_indexes != NUM_CRITICAL_SHARED_INDEXES)
{
elog(WARNING, "found %d nailed shared rels and %d nailed shared indexes in init file, but expected %d and %d respectively",
nailed_rels, nailed_indexes,
NUM_CRITICAL_SHARED_RELS, NUM_CRITICAL_SHARED_INDEXES);
/* Make sure we get developers' attention about this */
Assert(false);
/* In production builds, recover by bootstrapping the relcache */
goto read_failed;
}
}
else
{
if (nailed_rels != NUM_CRITICAL_LOCAL_RELS ||
nailed_indexes != NUM_CRITICAL_LOCAL_INDEXES)
{
elog(WARNING, "found %d nailed rels and %d nailed indexes in init file, but expected %d and %d respectively",
nailed_rels, nailed_indexes,
NUM_CRITICAL_LOCAL_RELS, NUM_CRITICAL_LOCAL_INDEXES);
/* We don't need an Assert() in this case */
goto read_failed;
}
}
/*
* OK, all appears well.
*
* Now insert all the new relcache entries into the cache.
*/
for (relno = 0; relno < num_rels; relno++)
{
RelationCacheInsert(rels[relno], false);
}
pfree(rels);
FreeFile(fp);
if (shared)
criticalSharedRelcachesBuilt = true;
else
criticalRelcachesBuilt = true;
return true;
/*
* init file is broken, so do it the hard way. We don't bother trying to
* free the clutter we just allocated; it's not in the relcache so it
* won't hurt.
*/
read_failed:
pfree(rels);
FreeFile(fp);
return false;
}
/*
* Write out a new initialization file with the current contents
* of the relcache (either shared rels or local rels, as indicated).
*/
static void
write_relcache_init_file(bool shared)
{
FILE *fp;
char tempfilename[MAXPGPATH];
char finalfilename[MAXPGPATH];
int magic;
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
int i;
/*
* If we have already received any relcache inval events, there's no
* chance of succeeding so we may as well skip the whole thing.
*/
if (relcacheInvalsReceived != 0L)
return;
/*
* We must write a temporary file and rename it into place. Otherwise,
* another backend starting at about the same time might crash trying to
* read the partially-complete file.
*/
if (shared)
{
snprintf(tempfilename, sizeof(tempfilename), "global/%s.%d",
RELCACHE_INIT_FILENAME, MyProcPid);
snprintf(finalfilename, sizeof(finalfilename), "global/%s",
RELCACHE_INIT_FILENAME);
}
else
{
snprintf(tempfilename, sizeof(tempfilename), "%s/%s.%d",
DatabasePath, RELCACHE_INIT_FILENAME, MyProcPid);
snprintf(finalfilename, sizeof(finalfilename), "%s/%s",
DatabasePath, RELCACHE_INIT_FILENAME);
}
unlink(tempfilename); /* in case it exists w/wrong permissions */
fp = AllocateFile(tempfilename, PG_BINARY_W);
if (fp == NULL)
{
/*
* We used to consider this a fatal error, but we might as well
* continue with backend startup ...
*/
ereport(WARNING,
(errcode_for_file_access(),
errmsg("could not create relation-cache initialization file \"%s\": %m",
tempfilename),
errdetail("Continuing anyway, but there's something wrong.")));
return;
}
/*
* Write a magic number to serve as a file version identifier. We can
* change the magic number whenever the relcache layout changes.
*/
magic = RELCACHE_INIT_FILEMAGIC;
if (fwrite(&magic, 1, sizeof(magic), fp) != sizeof(magic))
ereport(FATAL,
errcode_for_file_access(),
errmsg_internal("could not write init file: %m"));
/*
* Write all the appropriate reldescs (in no particular order).
*/
hash_seq_init(&status, RelationIdCache);
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
{
Relation rel = idhentry->reldesc;
Form_pg_class relform = rel->rd_rel;
/* ignore if not correct group */
if (relform->relisshared != shared)
continue;
/*
* Ignore if not supposed to be in init file. We can allow any shared
* relation that's been loaded so far to be in the shared init file,
* but unshared relations must be ones that should be in the local
* file per RelationIdIsInInitFile. (Note: if you want to change the
* criterion for rels to be kept in the init file, see also inval.c.
* The reason for filtering here is to be sure that we don't put
* anything into the local init file for which a relcache inval would
* not cause invalidation of that init file.)
*/
if (!shared && !RelationIdIsInInitFile(RelationGetRelid(rel)))
{
/* Nailed rels had better get stored. */
Assert(!rel->rd_isnailed);
continue;
}
/* first write the relcache entry proper */
write_item(rel, sizeof(RelationData), fp);
/* next write the relation tuple form */
write_item(relform, CLASS_TUPLE_SIZE, fp);
/* next, do all the attribute tuple form data entries */
for (i = 0; i < relform->relnatts; i++)
{
write_item(TupleDescAttr(rel->rd_att, i),
ATTRIBUTE_FIXED_PART_SIZE, fp);
}
/* next, do the access method specific field */
write_item(rel->rd_options,
(rel->rd_options ? VARSIZE(rel->rd_options) : 0),
fp);
/*
* If it's an index, there's more to do. Note we explicitly ignore
* partitioned indexes here.
*/
if (rel->rd_rel->relkind == RELKIND_INDEX)
{
/* write the pg_index tuple */
/* we assume this was created by heap_copytuple! */
write_item(rel->rd_indextuple,
HEAPTUPLESIZE + rel->rd_indextuple->t_len,
fp);
/* write the vector of opfamily OIDs */
write_item(rel->rd_opfamily,
relform->relnatts * sizeof(Oid),
fp);
/* write the vector of opcintype OIDs */
write_item(rel->rd_opcintype,
relform->relnatts * sizeof(Oid),
fp);
/* write the vector of support procedure OIDs */
write_item(rel->rd_support,
relform->relnatts * (rel->rd_indam->amsupport * sizeof(RegProcedure)),
fp);
/* write the vector of collation OIDs */
write_item(rel->rd_indcollation,
relform->relnatts * sizeof(Oid),
fp);
/* write the vector of indoption values */
write_item(rel->rd_indoption,
relform->relnatts * sizeof(int16),
fp);
Assert(rel->rd_opcoptions);
/* write the vector of opcoptions values */
for (i = 0; i < relform->relnatts; i++)
{
bytea *opt = rel->rd_opcoptions[i];
write_item(opt, opt ? VARSIZE(opt) : 0, fp);
}
}
}
if (FreeFile(fp))
ereport(FATAL,
errcode_for_file_access(),
errmsg_internal("could not write init file: %m"));
/*
* Now we have to check whether the data we've so painstakingly
* accumulated is already obsolete due to someone else's just-committed
* catalog changes. If so, we just delete the temp file and leave it to
* the next backend to try again. (Our own relcache entries will be
* updated by SI message processing, but we can't be sure whether what we
* wrote out was up-to-date.)
*
* This mustn't run concurrently with the code that unlinks an init file
* and sends SI messages, so grab a serialization lock for the duration.
*/
LWLockAcquire(RelCacheInitLock, LW_EXCLUSIVE);
/* Make sure we have seen all incoming SI messages */
AcceptInvalidationMessages();
/*
* If we have received any SI relcache invals since backend start, assume
* we may have written out-of-date data.
*/
if (relcacheInvalsReceived == 0L)
{
/*
* OK, rename the temp file to its final name, deleting any
* previously-existing init file.
*
* Note: a failure here is possible under Cygwin, if some other
* backend is holding open an unlinked-but-not-yet-gone init file. So
* treat this as a noncritical failure; just remove the useless temp
* file on failure.
*/
if (rename(tempfilename, finalfilename) < 0)
unlink(tempfilename);
}
else
{
/* Delete the already-obsolete temp file */
unlink(tempfilename);
}
LWLockRelease(RelCacheInitLock);
}
/* write a chunk of data preceded by its length */
static void
write_item(const void *data, Size len, FILE *fp)
{
if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len))
ereport(FATAL,
errcode_for_file_access(),
errmsg_internal("could not write init file: %m"));
if (len > 0 && fwrite(data, 1, len, fp) != len)
ereport(FATAL,
errcode_for_file_access(),
errmsg_internal("could not write init file: %m"));
}
/*
* Determine whether a given relation (identified by OID) is one of the ones
* we should store in a relcache init file.
*
* We must cache all nailed rels, and for efficiency we should cache every rel
* that supports a syscache. The former set is almost but not quite a subset
* of the latter. The special cases are relations where
* RelationCacheInitializePhase2/3 chooses to nail for efficiency reasons, but
* which do not support any syscache.
*/
bool
RelationIdIsInInitFile(Oid relationId)
{
if (relationId == SharedSecLabelRelationId ||
relationId == TriggerRelidNameIndexId ||
relationId == DatabaseNameIndexId ||
relationId == SharedSecLabelObjectIndexId)
{
/*
* If this Assert fails, we don't need the applicable special case
* anymore.
*/
Assert(!RelationSupportsSysCache(relationId));
return true;
}
return RelationSupportsSysCache(relationId);
}
/*
* Invalidate (remove) the init file during commit of a transaction that
* changed one or more of the relation cache entries that are kept in the
* local init file.
*
* To be safe against concurrent inspection or rewriting of the init file,
* we must take RelCacheInitLock, then remove the old init file, then send
* the SI messages that include relcache inval for such relations, and then
* release RelCacheInitLock. This serializes the whole affair against
* write_relcache_init_file, so that we can be sure that any other process
* that's concurrently trying to create a new init file won't move an
* already-stale version into place after we unlink. Also, because we unlink
* before sending the SI messages, a backend that's currently starting cannot
* read the now-obsolete init file and then miss the SI messages that will
* force it to update its relcache entries. (This works because the backend
* startup sequence gets into the sinval array before trying to load the init
* file.)
*
* We take the lock and do the unlink in RelationCacheInitFilePreInvalidate,
* then release the lock in RelationCacheInitFilePostInvalidate. Caller must
* send any pending SI messages between those calls.
*/
void
RelationCacheInitFilePreInvalidate(void)
{
char localinitfname[MAXPGPATH];
char sharedinitfname[MAXPGPATH];
if (DatabasePath)
snprintf(localinitfname, sizeof(localinitfname), "%s/%s",
DatabasePath, RELCACHE_INIT_FILENAME);
snprintf(sharedinitfname, sizeof(sharedinitfname), "global/%s",
RELCACHE_INIT_FILENAME);
LWLockAcquire(RelCacheInitLock, LW_EXCLUSIVE);
/*
* The files might not be there if no backend has been started since the
* last removal. But complain about failures other than ENOENT with
* ERROR. Fortunately, it's not too late to abort the transaction if we
* can't get rid of the would-be-obsolete init file.
*/
if (DatabasePath)
unlink_initfile(localinitfname, ERROR);
unlink_initfile(sharedinitfname, ERROR);
}
void
RelationCacheInitFilePostInvalidate(void)
{
LWLockRelease(RelCacheInitLock);
}
/*
* Remove the init files during postmaster startup.
*
* We used to keep the init files across restarts, but that is unsafe in PITR
* scenarios, and even in simple crash-recovery cases there are windows for
* the init files to become out-of-sync with the database. So now we just
* remove them during startup and expect the first backend launch to rebuild
* them. Of course, this has to happen in each database of the cluster.
*/
void
RelationCacheInitFileRemove(void)
{
const char *tblspcdir = PG_TBLSPC_DIR;
DIR *dir;
struct dirent *de;
char path[MAXPGPATH + sizeof(PG_TBLSPC_DIR) + sizeof(TABLESPACE_VERSION_DIRECTORY)];
snprintf(path, sizeof(path), "global/%s",
RELCACHE_INIT_FILENAME);
unlink_initfile(path, LOG);
/* Scan everything in the default tablespace */
RelationCacheInitFileRemoveInDir("base");
/* Scan the tablespace link directory to find non-default tablespaces */
dir = AllocateDir(tblspcdir);
while ((de = ReadDirExtended(dir, tblspcdir, LOG)) != NULL)
{
if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
{
/* Scan the tablespace dir for per-database dirs */
snprintf(path, sizeof(path), "%s/%s/%s",
tblspcdir, de->d_name, TABLESPACE_VERSION_DIRECTORY);
RelationCacheInitFileRemoveInDir(path);
}
}
FreeDir(dir);
}
/* Process one per-tablespace directory for RelationCacheInitFileRemove */
static void
RelationCacheInitFileRemoveInDir(const char *tblspcpath)
{
DIR *dir;
struct dirent *de;
char initfilename[MAXPGPATH * 2];
/* Scan the tablespace directory to find per-database directories */
dir = AllocateDir(tblspcpath);
while ((de = ReadDirExtended(dir, tblspcpath, LOG)) != NULL)
{
if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
{
/* Try to remove the init file in each database */
snprintf(initfilename, sizeof(initfilename), "%s/%s/%s",
tblspcpath, de->d_name, RELCACHE_INIT_FILENAME);
unlink_initfile(initfilename, LOG);
}
}
FreeDir(dir);
}
static void
unlink_initfile(const char *initfilename, int elevel)
{
if (unlink(initfilename) < 0)
{
/* It might not be there, but log any error other than ENOENT */
if (errno != ENOENT)
ereport(elevel,
(errcode_for_file_access(),
errmsg("could not remove cache file \"%s\": %m",
initfilename)));
}
}
/*
* ResourceOwner callbacks
*/
static char *
ResOwnerPrintRelCache(Datum res)
{
Relation rel = (Relation) DatumGetPointer(res);
return psprintf("relation \"%s\"", RelationGetRelationName(rel));
}
static void
ResOwnerReleaseRelation(Datum res)
{
Relation rel = (Relation) DatumGetPointer(res);
/*
* This reference has already been removed from the resource owner, so
* just decrement reference count without calling
* ResourceOwnerForgetRelationRef.
*/
Assert(rel->rd_refcnt > 0);
rel->rd_refcnt -= 1;
RelationCloseCleanup((Relation) DatumGetPointer(res));
}
List *
ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId)
{
bool opened_relation = false;
TupleDesc tupdesc;
if (list == NIL || (heapRelation == NULL && heapRelId == InvalidOid))
return list;
if (heapRelation == NULL)
{
heapRelation = table_open(heapRelId, NoLock);
opened_relation = true;
}
tupdesc = RelationGetDescr(heapRelation);
if ((tupdesc->constr && tupdesc->constr->has_generated_virtual))
{
int j;
Bitmapset *indexattrs = NULL;
pull_varattnos((Node *)list, 1, &indexattrs);
j = -1;
while ((j = bms_next_member(indexattrs, j)) >= 0)
{
AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
if (attno > 0 &&
TupleDescAttr(tupdesc, attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
{
list = (List *)expand_generated_columns_in_expr((Node *)list, heapRelation, 1);
break;
}
}
}
if (opened_relation)
table_close(heapRelation, NoLock);
return list;
}
./relcache.h 0000664 0001750 0001750 00000012244 15222103244 011622 0 ustar xman xman /*-------------------------------------------------------------------------
*
* relcache.h
* Relation descriptor cache definitions.
*
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/utils/relcache.h
*
*-------------------------------------------------------------------------
*/
#ifndef RELCACHE_H
#define RELCACHE_H
#include "access/tupdesc.h"
#include "common/relpath.h"
#include "nodes/bitmapset.h"
/*
* Name of relcache init file(s), used to speed up backend startup
*/
#define RELCACHE_INIT_FILENAME "pg_internal.init"
typedef struct RelationData *Relation;
/* ----------------
* RelationPtr is used in the executor to support index scans
* where we have to keep track of several index relations in an
* array. -cim 9/10/89
* ----------------
*/
typedef Relation *RelationPtr;
/*
* Routines to open (lookup) and close a relcache entry
*/
#ifdef USE_ASSERT_CHECKING
extern void AssertCouldGetRelation(void);
#else
static inline void
AssertCouldGetRelation(void)
{
}
#endif
extern Relation RelationIdGetRelation(Oid relationId);
extern char *RelationGetQualifiedRelationName(Relation rel);
extern void RelationClose(Relation relation);
/*
* Routines to compute/retrieve additional cached information
*/
extern List *RelationGetFKeyList(Relation relation);
extern List *RelationGetIndexList(Relation relation);
extern List *RelationGetStatExtList(Relation relation);
extern Oid RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok);
extern Oid RelationGetReplicaIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
extern List *RelationGetIndexExpressionsExpand(Relation relation);
extern List *RelationGetDummyIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
extern List *RelationGetIndexPredicateExpand(Relation relation);
extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
/*
* Which set of columns to return by RelationGetIndexAttrBitmap.
*/
typedef enum IndexAttrBitmapKind
{
INDEX_ATTR_BITMAP_KEY,
INDEX_ATTR_BITMAP_PRIMARY_KEY,
INDEX_ATTR_BITMAP_IDENTITY_KEY,
INDEX_ATTR_BITMAP_HOT_BLOCKING,
INDEX_ATTR_BITMAP_SUMMARIZED,
} IndexAttrBitmapKind;
extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
IndexAttrBitmapKind attrKind);
extern Bitmapset *RelationGetIdentityKeyBitmap(Relation relation);
extern void RelationGetExclusionInfo(Relation indexRelation,
Oid **operators,
Oid **procs,
uint16 **strategies);
extern void RelationInitIndexAccessInfo(Relation relation);
/* caller must include pg_publication.h */
struct PublicationDesc;
extern void RelationBuildPublicationDesc(Relation relation,
struct PublicationDesc *pubdesc);
extern void RelationInitTableAccessMethod(Relation relation);
/*
* Routines to support ereport() reports of relation-related errors
*/
extern int errtable(Relation rel);
extern int errtablecol(Relation rel, int attnum);
extern int errtablecolname(Relation rel, const char *colname);
extern int errtableconstraint(Relation rel, const char *conname);
/*
* Routines for backend startup
*/
extern void RelationCacheInitialize(void);
extern void RelationCacheInitializePhase2(void);
extern void RelationCacheInitializePhase3(void);
/*
* Routine to create a relcache entry for an about-to-be-created relation
*/
extern Relation RelationBuildLocalRelation(const char *relname,
Oid relnamespace,
TupleDesc tupDesc,
Oid relid,
Oid accessmtd,
RelFileNumber relfilenumber,
Oid reltablespace,
bool shared_relation,
bool mapped_relation,
char relpersistence,
char relkind);
/*
* Routines to manage assignment of new relfilenumber to a relation
*/
extern void RelationSetNewRelfilenumber(Relation relation, char persistence);
extern void RelationAssumeNewRelfilelocator(Relation relation);
/*
* Routines for flushing/rebuilding relcache entries in various scenarios
*/
extern void RelationForgetRelation(Oid rid);
extern void RelationCacheInvalidateEntry(Oid relationId);
extern void RelationCacheInvalidate(bool debug_discard);
#ifdef USE_ASSERT_CHECKING
extern void AssertPendingSyncs_RelationCache(void);
#else
#define AssertPendingSyncs_RelationCache() do {} while (0)
#endif
extern void AtEOXact_RelationCache(bool isCommit);
extern void AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
SubTransactionId parentSubid);
/*
* Routines to help manage rebuilding of relcache init files
*/
extern bool RelationIdIsInInitFile(Oid relationId);
extern void RelationCacheInitFilePreInvalidate(void);
extern void RelationCacheInitFilePostInvalidate(void);
extern void RelationCacheInitFileRemove(void);
/* should be used only by relcache.c and catcache.c */
extern PGDLLIMPORT bool criticalRelcachesBuilt;
/* should be used only by relcache.c and postinit.c */
extern PGDLLIMPORT bool criticalSharedRelcachesBuilt;
extern List *ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId);
#endif /* RELCACHE_H */
./selfuncs.c 0000664 0001750 0001750 00001065412 15221500744 011704 0 ustar xman xman /*-------------------------------------------------------------------------
*
* selfuncs.c
* Selectivity functions and index cost estimation functions for
* standard operators and index access methods.
*
* Selectivity routines are registered in the pg_operator catalog
* in the "oprrest" and "oprjoin" attributes.
*
* Index cost functions are located via the index AM's API struct,
* which is obtained from the handler function registered in pg_am.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/utils/adt/selfuncs.c
*
*-------------------------------------------------------------------------
*/
/*----------
* Operator selectivity estimation functions are called to estimate the
* selectivity of WHERE clauses whose top-level operator is their operator.
* We divide the problem into two cases:
* Restriction clause estimation: the clause involves vars of just
* one relation.
* Join clause estimation: the clause involves vars of multiple rels.
* Join selectivity estimation is far more difficult and usually less accurate
* than restriction estimation.
*
* When dealing with the inner scan of a nestloop join, we consider the
* join's joinclauses as restriction clauses for the inner relation, and
* treat vars of the outer relation as parameters (a/k/a constants of unknown
* values). So, restriction estimators need to be able to accept an argument
* telling which relation is to be treated as the variable.
*
* The call convention for a restriction estimator (oprrest function) is
*
* Selectivity oprrest (PlannerInfo *root,
* Oid operator,
* List *args,
* int varRelid);
*
* root: general information about the query (rtable and RelOptInfo lists
* are particularly important for the estimator).
* operator: OID of the specific operator in question.
* args: argument list from the operator clause.
* varRelid: if not zero, the relid (rtable index) of the relation to
* be treated as the variable relation. May be zero if the args list
* is known to contain vars of only one relation.
*
* This is represented at the SQL level (in pg_proc) as
*
* float8 oprrest (internal, oid, internal, int4);
*
* The result is a selectivity, that is, a fraction (0 to 1) of the rows
* of the relation that are expected to produce a TRUE result for the
* given operator.
*
* The call convention for a join estimator (oprjoin function) is similar
* except that varRelid is not needed, and instead join information is
* supplied:
*
* Selectivity oprjoin (PlannerInfo *root,
* Oid operator,
* List *args,
* JoinType jointype,
* SpecialJoinInfo *sjinfo);
*
* float8 oprjoin (internal, oid, internal, int2, internal);
*
* (Before Postgres 8.4, join estimators had only the first four of these
* parameters. That signature is still allowed, but deprecated.) The
* relationship between jointype and sjinfo is explained in the comments for
* clause_selectivity() --- the short version is that jointype is usually
* best ignored in favor of examining sjinfo.
*
* Join selectivity for regular inner and outer joins is defined as the
* fraction (0 to 1) of the cross product of the relations that is expected
* to produce a TRUE result for the given operator. For both semi and anti
* joins, however, the selectivity is defined as the fraction of the left-hand
* side relation's rows that are expected to have a match (ie, at least one
* row with a TRUE result) in the right-hand side.
*
* For both oprrest and oprjoin functions, the operator's input collation OID
* (if any) is passed using the standard fmgr mechanism, so that the estimator
* function can fetch it with PG_GET_COLLATION(). Note, however, that all
* statistics in pg_statistic are currently built using the relevant column's
* collation.
*----------
*/
#include "postgres.h"
#include <ctype.h>
#include <math.h>
#include "access/brin.h"
#include "access/brin_page.h"
#include "access/gin.h"
#include "access/table.h"
#include "access/tableam.h"
#include "access/visibilitymap.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/plancat.h"
#include "parser/parse_clause.h"
#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "statistics/statistics.h"
#include "storage/bufmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/index_selfuncs.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "utils/rel.h"
#include "utils/selfuncs.h"
#include "utils/snapmgr.h"
#include "utils/spccache.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "utils/typcache.h"
#define DEFAULT_PAGE_CPU_MULTIPLIER 50.0
/*
* In production builds, switch to hash-based MCV matching when the lists are
* large enough to amortize hash setup cost. (This threshold is compared to
* the sum of the lengths of the two MCV lists. This is simplistic but seems
* to work well enough.) In debug builds, we use a smaller threshold so that
* the regression tests cover both paths well.
*/
#ifndef USE_ASSERT_CHECKING
#define EQJOINSEL_MCV_HASH_THRESHOLD 200
#else
#define EQJOINSEL_MCV_HASH_THRESHOLD 20
#endif
/* Entries in the simplehash hash table used by eqjoinsel_find_matches */
typedef struct MCVHashEntry
{
Datum value; /* the value represented by this entry */
int index; /* its index in the relevant AttStatsSlot */
uint32 hash; /* hash code for the Datum */
char status; /* status code used by simplehash.h */
} MCVHashEntry;
/* private_data for the simplehash hash table */
typedef struct MCVHashContext
{
FunctionCallInfo equal_fcinfo; /* the equality join operator */
FunctionCallInfo hash_fcinfo; /* the hash function to use */
bool op_is_reversed; /* equality compares hash type to probe type */
bool insert_mode; /* doing inserts or lookups? */
bool hash_typbyval; /* typbyval of hashed data type */
int16 hash_typlen; /* typlen of hashed data type */
} MCVHashContext;
/* forward reference */
typedef struct MCVHashTable_hash MCVHashTable_hash;
/* Hooks for plugins to get control when we ask for stats */
get_relation_stats_hook_type get_relation_stats_hook = NULL;
get_index_stats_hook_type get_index_stats_hook = NULL;
static double eqsel_internal(PG_FUNCTION_ARGS, bool negate);
static double eqjoinsel_inner(FmgrInfo *eqproc, Oid collation,
Oid hashLeft, Oid hashRight,
VariableStatData *vardata1, VariableStatData *vardata2,
double nd1, double nd2,
bool isdefault1, bool isdefault2,
AttStatsSlot *sslot1, AttStatsSlot *sslot2,
Form_pg_statistic stats1, Form_pg_statistic stats2,
bool have_mcvs1, bool have_mcvs2,
bool *hasmatch1, bool *hasmatch2,
int *p_nmatches);
static double eqjoinsel_semi(FmgrInfo *eqproc, Oid collation,
Oid hashLeft, Oid hashRight,
bool op_is_reversed,
VariableStatData *vardata1, VariableStatData *vardata2,
double nd1, double nd2,
bool isdefault1, bool isdefault2,
AttStatsSlot *sslot1, AttStatsSlot *sslot2,
Form_pg_statistic stats1, Form_pg_statistic stats2,
bool have_mcvs1, bool have_mcvs2,
bool *hasmatch1, bool *hasmatch2,
int *p_nmatches,
RelOptInfo *inner_rel);
static void eqjoinsel_find_matches(FmgrInfo *eqproc, Oid collation,
Oid hashLeft, Oid hashRight,
bool op_is_reversed,
AttStatsSlot *sslot1, AttStatsSlot *sslot2,
int nvalues1, int nvalues2,
bool *hasmatch1, bool *hasmatch2,
int *p_nmatches, double *p_matchprodfreq);
static uint32 hash_mcv(MCVHashTable_hash *tab, Datum key);
static bool mcvs_equal(MCVHashTable_hash *tab, Datum key0, Datum key1);
static bool estimate_multivariate_ndistinct(PlannerInfo *root,
RelOptInfo *rel, List **varinfos, double *ndistinct);
static bool convert_to_scalar(Datum value, Oid valuetypid, Oid collid,
double *scaledvalue,
Datum lobound, Datum hibound, Oid boundstypid,
double *scaledlobound, double *scaledhibound);
static double convert_numeric_to_scalar(Datum value, Oid typid, bool *failure);
static void convert_string_to_scalar(char *value,
double *scaledvalue,
char *lobound,
double *scaledlobound,
char *hibound,
double *scaledhibound);
static void convert_bytea_to_scalar(Datum value,
double *scaledvalue,
Datum lobound,
double *scaledlobound,
Datum hibound,
double *scaledhibound);
static double convert_one_string_to_scalar(char *value,
int rangelo, int rangehi);
static double convert_one_bytea_to_scalar(unsigned char *value, int valuelen,
int rangelo, int rangehi);
static char *convert_string_datum(Datum value, Oid typid, Oid collid,
bool *failure);
static double convert_timevalue_to_scalar(Datum value, Oid typid,
bool *failure);
static Node *strip_all_phvs_deep(PlannerInfo *root, Node *node);
static bool contain_placeholder_walker(Node *node, void *context);
static Node *strip_all_phvs_mutator(Node *node, void *context);
static void examine_simple_variable(PlannerInfo *root, Var *var,
VariableStatData *vardata);
static void examine_indexcol_variable(PlannerInfo *root, IndexOptInfo *index,
int indexcol, VariableStatData *vardata);
static bool get_variable_range(PlannerInfo *root, VariableStatData *vardata,
Oid sortop, Oid collation,
Datum *min, Datum *max);
static void get_stats_slot_range(AttStatsSlot *sslot,
Oid opfuncoid, FmgrInfo *opproc,
Oid collation, int16 typLen, bool typByVal,
Datum *min, Datum *max, bool *p_have_data);
static bool get_actual_variable_range(PlannerInfo *root,
VariableStatData *vardata,
Oid sortop, Oid collation,
Datum *min, Datum *max);
static bool get_actual_variable_endpoint(Relation heapRel,
Relation indexRel,
ScanDirection indexscandir,
ScanKey scankeys,
int16 typLen,
bool typByVal,
TupleTableSlot *tableslot,
MemoryContext outercontext,
Datum *endpointDatum);
static RelOptInfo *find_join_input_rel(PlannerInfo *root, Relids relids);
static double btcost_correlation(IndexOptInfo *index,
VariableStatData *vardata);
/* Define support routines for MCV hash tables */
#define SH_PREFIX MCVHashTable
#define SH_ELEMENT_TYPE MCVHashEntry
#define SH_KEY_TYPE Datum
#define SH_KEY value
#define SH_HASH_KEY(tab,key) hash_mcv(tab, key)
#define SH_EQUAL(tab,key0,key1) mcvs_equal(tab, key0, key1)
#define SH_SCOPE static inline
#define SH_STORE_HASH
#define SH_GET_HASH(tab,ent) (ent)->hash
#define SH_DEFINE
#define SH_DECLARE
#include "lib/simplehash.h"
/*
* eqsel - Selectivity of "=" for any data types.
*
* Note: this routine is also used to estimate selectivity for some
* operators that are not "=" but have comparable selectivity behavior,
* such as "~=" (geometric approximate-match). Even for "=", we must
* keep in mind that the left and right datatypes may differ.
*/
Datum
eqsel(PG_FUNCTION_ARGS)
{
PG_RETURN_FLOAT8((float8) eqsel_internal(fcinfo, false));
}
/*
* Common code for eqsel() and neqsel()
*/
static double
eqsel_internal(PG_FUNCTION_ARGS, bool negate)
{
PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
Oid operator = PG_GETARG_OID(1);
List *args = (List *) PG_GETARG_POINTER(2);
int varRelid = PG_GETARG_INT32(3);
Oid collation = PG_GET_COLLATION();
VariableStatData vardata;
Node *other;
bool varonleft;
double selec;
/*
* When asked about <>, we do the estimation using the corresponding =
* operator, then convert to <> via "1.0 - eq_selectivity - nullfrac".
*/
if (negate)
{
operator = get_negator(operator);
if (!OidIsValid(operator))
{
/* Use default selectivity (should we raise an error instead?) */
return 1.0 - DEFAULT_EQ_SEL;
}
}
/*
* If expression is not variable = something or something = variable, then
* punt and return a default estimate.
*/
if (!get_restriction_variable(root, args, varRelid,
&vardata, &other, &varonleft))
return negate ? (1.0 - DEFAULT_EQ_SEL) : DEFAULT_EQ_SEL;
/*
* We can do a lot better if the something is a constant. (Note: the
* Const might result from estimation rather than being a simple constant
* in the query.)
*/
if (IsA(other, Const))
selec = var_eq_const(&vardata, operator, collation,
((Const *) other)->constvalue,
((Const *) other)->constisnull,
varonleft, negate);
else
selec = var_eq_non_const(&vardata, operator, collation, other,
varonleft, negate);
ReleaseVariableStats(vardata);
return selec;
}
/*
* var_eq_const --- eqsel for var = const case
*
* This is exported so that some other estimation functions can use it.
*/
double
var_eq_const(VariableStatData *vardata, Oid oproid, Oid collation,
Datum constval, bool constisnull,
bool varonleft, bool negate)
{
double selec;
double nullfrac = 0.0;
bool isdefault;
Oid opfuncoid;
/*
* If the constant is NULL, assume operator is strict and return zero, ie,
* operator will never return TRUE. (It's zero even for a negator op.)
*/
if (constisnull)
return 0.0;
/*
* Grab the nullfrac for use below. Note we allow use of nullfrac
* regardless of security check.
*/
if (HeapTupleIsValid(vardata->statsTuple))
{
Form_pg_statistic stats;
stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
nullfrac = stats->stanullfrac;
}
/*
* If we matched the var to a unique index, DISTINCT or GROUP-BY clause,
* assume there is exactly one match regardless of anything else. (This
* is slightly bogus, since the index or clause's equality operator might
* be different from ours, but it's much more likely to be right than
* ignoring the information.)
*/
if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0)
{
selec = 1.0 / vardata->rel->tuples;
}
else if (HeapTupleIsValid(vardata->statsTuple) &&
statistic_proc_security_check(vardata,
(opfuncoid = get_opcode(oproid))))
{
AttStatsSlot sslot;
bool match = false;
int i;
/*
* Is the constant "=" to any of the column's most common values?
* (Although the given operator may not really be "=", we will assume
* that seeing whether it returns TRUE is an appropriate test. If you
* don't like this, maybe you shouldn't be using eqsel for your
* operator...)
*/
if (get_attstatsslot(&sslot, vardata->statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS))
{
LOCAL_FCINFO(fcinfo, 2);
FmgrInfo eqproc;
fmgr_info(opfuncoid, &eqproc);
/*
* Save a few cycles by setting up the fcinfo struct just once.
* Using FunctionCallInvoke directly also avoids failure if the
* eqproc returns NULL, though really equality functions should
* never do that.
*/
InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation,
NULL, NULL);
fcinfo->args[0].isnull = false;
fcinfo->args[1].isnull = false;
/* be careful to apply operator right way 'round */
if (varonleft)
fcinfo->args[1].value = constval;
else
fcinfo->args[0].value = constval;
for (i = 0; i < sslot.nvalues; i++)
{
Datum fresult;
if (varonleft)
fcinfo->args[0].value = sslot.values[i];
else
fcinfo->args[1].value = sslot.values[i];
fcinfo->isnull = false;
fresult = FunctionCallInvoke(fcinfo);
if (!fcinfo->isnull && DatumGetBool(fresult))
{
match = true;
break;
}
}
}
else
{
/* no most-common-value info available */
i = 0; /* keep compiler quiet */
}
if (match)
{
/*
* Constant is "=" to this common value. We know selectivity
* exactly (or as exactly as ANALYZE could calculate it, anyway).
*/
selec = sslot.numbers[i];
}
else
{
/*
* Comparison is against a constant that is neither NULL nor any
* of the common values. Its selectivity cannot be more than
* this:
*/
double sumcommon = 0.0;
double otherdistinct;
for (i = 0; i < sslot.nnumbers; i++)
sumcommon += sslot.numbers[i];
selec = 1.0 - sumcommon - nullfrac;
CLAMP_PROBABILITY(selec);
/*
* and in fact it's probably a good deal less. We approximate that
* all the not-common values share this remaining fraction
* equally, so we divide by the number of other distinct values.
*/
otherdistinct = get_variable_numdistinct(vardata, &isdefault) -
sslot.nnumbers;
if (otherdistinct > 1)
selec /= otherdistinct;
/*
* Another cross-check: selectivity shouldn't be estimated as more
* than the least common "most common value".
*/
if (sslot.nnumbers > 0 && selec > sslot.numbers[sslot.nnumbers - 1])
selec = sslot.numbers[sslot.nnumbers - 1];
}
free_attstatsslot(&sslot);
}
else
{
/*
* No ANALYZE stats available, so make a guess using estimated number
* of distinct values and assuming they are equally common. (The guess
* is unlikely to be very good, but we do know a few special cases.)
*/
selec = 1.0 / get_variable_numdistinct(vardata, &isdefault);
}
/* now adjust if we wanted <> rather than = */
if (negate)
selec = 1.0 - selec - nullfrac;
/* result should be in range, but make sure... */
CLAMP_PROBABILITY(selec);
return selec;
}
/*
* var_eq_non_const --- eqsel for var = something-other-than-const case
*
* This is exported so that some other estimation functions can use it.
*/
double
var_eq_non_const(VariableStatData *vardata, Oid oproid, Oid collation,
Node *other,
bool varonleft, bool negate)
{
double selec;
double nullfrac = 0.0;
bool isdefault;
/*
* Grab the nullfrac for use below.
*/
if (HeapTupleIsValid(vardata->statsTuple))
{
Form_pg_statistic stats;
stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
nullfrac = stats->stanullfrac;
}
/*
* If we matched the var to a unique index, DISTINCT or GROUP-BY clause,
* assume there is exactly one match regardless of anything else. (This
* is slightly bogus, since the index or clause's equality operator might
* be different from ours, but it's much more likely to be right than
* ignoring the information.)
*/
if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0)
{
selec = 1.0 / vardata->rel->tuples;
}
else if (HeapTupleIsValid(vardata->statsTuple))
{
double ndistinct;
AttStatsSlot sslot;
/*
* Search is for a value that we do not know a priori, but we will
* assume it is not NULL. Estimate the selectivity as non-null
* fraction divided by number of distinct values, so that we get a
* result averaged over all possible values whether common or
* uncommon. (Essentially, we are assuming that the not-yet-known
* comparison value is equally likely to be any of the possible
* values, regardless of their frequency in the table. Is that a good
* idea?)
*/
selec = 1.0 - nullfrac;
ndistinct = get_variable_numdistinct(vardata, &isdefault);
if (ndistinct > 1)
selec /= ndistinct;
/*
* Cross-check: selectivity should never be estimated as more than the
* most common value's.
*/
if (get_attstatsslot(&sslot, vardata->statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
ATTSTATSSLOT_NUMBERS))
{
if (sslot.nnumbers > 0 && selec > sslot.numbers[0])
selec = sslot.numbers[0];
free_attstatsslot(&sslot);
}
}
else
{
/*
* No ANALYZE stats available, so make a guess using estimated number
* of distinct values and assuming they are equally common. (The guess
* is unlikely to be very good, but we do know a few special cases.)
*/
selec = 1.0 / get_variable_numdistinct(vardata, &isdefault);
}
/* now adjust if we wanted <> rather than = */
if (negate)
selec = 1.0 - selec - nullfrac;
/* result should be in range, but make sure... */
CLAMP_PROBABILITY(selec);
return selec;
}
/*
* neqsel - Selectivity of "!=" for any data types.
*
* This routine is also used for some operators that are not "!="
* but have comparable selectivity behavior. See above comments
* for eqsel().
*/
Datum
neqsel(PG_FUNCTION_ARGS)
{
PG_RETURN_FLOAT8((float8) eqsel_internal(fcinfo, true));
}
/*
* scalarineqsel - Selectivity of "<", "<=", ">", ">=" for scalars.
*
* This is the guts of scalarltsel/scalarlesel/scalargtsel/scalargesel.
* The isgt and iseq flags distinguish which of the four cases apply.
*
* The caller has commuted the clause, if necessary, so that we can treat
* the variable as being on the left. The caller must also make sure that
* the other side of the clause is a non-null Const, and dissect that into
* a value and datatype. (This definition simplifies some callers that
* want to estimate against a computed value instead of a Const node.)
*
* This routine works for any datatype (or pair of datatypes) known to
* convert_to_scalar(). If it is applied to some other datatype,
* it will return an approximate estimate based on assuming that the constant
* value falls in the middle of the bin identified by binary search.
*/
static double
scalarineqsel(PlannerInfo *root, Oid operator, bool isgt, bool iseq,
Oid collation,
VariableStatData *vardata, Datum constval, Oid consttype)
{
Form_pg_statistic stats;
FmgrInfo opproc;
double mcv_selec,
hist_selec,
sumcommon;
double selec;
if (!HeapTupleIsValid(vardata->statsTuple))
{
/*
* No stats are available. Typically this means we have to fall back
* on the default estimate; but if the variable is CTID then we can
* make an estimate based on comparing the constant to the table size.
*/
if (vardata->var && IsA(vardata->var, Var) &&
((Var *) vardata->var)->varattno == SelfItemPointerAttributeNumber)
{
ItemPointer itemptr;
double block;
double density;
/*
* If the relation's empty, we're going to include all of it.
* (This is mostly to avoid divide-by-zero below.)
*/
if (vardata->rel->pages == 0)
return 1.0;
itemptr = (ItemPointer) DatumGetPointer(constval);
block = ItemPointerGetBlockNumberNoCheck(itemptr);
/*
* Determine the average number of tuples per page (density).
*
* Since the last page will, on average, be only half full, we can
* estimate it to have half as many tuples as earlier pages. So
* give it half the weight of a regular page.
*/
density = vardata->rel->tuples / (vardata->rel->pages - 0.5);
/* If target is the last page, use half the density. */
if (block >= vardata->rel->pages - 1)
density *= 0.5;
/*
* Using the average tuples per page, calculate how far into the
* page the itemptr is likely to be and adjust block accordingly,
* by adding that fraction of a whole block (but never more than a
* whole block, no matter how high the itemptr's offset is). Here
* we are ignoring the possibility of dead-tuple line pointers,
* which is fairly bogus, but we lack the info to do better.
*/
if (density > 0.0)
{
OffsetNumber offset = ItemPointerGetOffsetNumberNoCheck(itemptr);
block += Min(offset / density, 1.0);
}
/*
* Convert relative block number to selectivity. Again, the last
* page has only half weight.
*/
selec = block / (vardata->rel->pages - 0.5);
/*
* The calculation so far gave us a selectivity for the "<=" case.
* We'll have one fewer tuple for "<" and one additional tuple for
* ">=", the latter of which we'll reverse the selectivity for
* below, so we can simply subtract one tuple for both cases. The
* cases that need this adjustment can be identified by iseq being
* equal to isgt.
*/
if (iseq == isgt && vardata->rel->tuples >= 1.0)
selec -= (1.0 / vardata->rel->tuples);
/* Finally, reverse the selectivity for the ">", ">=" cases. */
if (isgt)
selec = 1.0 - selec;
CLAMP_PROBABILITY(selec);
return selec;
}
/* no stats available, so default result */
return DEFAULT_INEQ_SEL;
}
stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
fmgr_info(get_opcode(operator), &opproc);
/*
* If we have most-common-values info, add up the fractions of the MCV
* entries that satisfy MCV OP CONST. These fractions contribute directly
* to the result selectivity. Also add up the total fraction represented
* by MCV entries.
*/
mcv_selec = mcv_selectivity(vardata, &opproc, collation, constval, true,
&sumcommon);
/*
* If there is a histogram, determine which bin the constant falls in, and
* compute the resulting contribution to selectivity.
*/
hist_selec = ineq_histogram_selectivity(root, vardata,
operator, &opproc, isgt, iseq,
collation,
constval, consttype);
/*
* Now merge the results from the MCV and histogram calculations,
* realizing that the histogram covers only the non-null values that are
* not listed in MCV.
*/
selec = 1.0 - stats->stanullfrac - sumcommon;
if (hist_selec >= 0.0)
selec *= hist_selec;
else
{
/*
* If no histogram but there are values not accounted for by MCV,
* arbitrarily assume half of them will match.
*/
selec *= 0.5;
}
selec += mcv_selec;
/* result should be in range, but make sure... */
CLAMP_PROBABILITY(selec);
return selec;
}
/*
* mcv_selectivity - Examine the MCV list for selectivity estimates
*
* Determine the fraction of the variable's MCV population that satisfies
* the predicate (VAR OP CONST), or (CONST OP VAR) if !varonleft. Also
* compute the fraction of the total column population represented by the MCV
* list. This code will work for any boolean-returning predicate operator.
*
* The function result is the MCV selectivity, and the fraction of the
* total population is returned into *sumcommonp. Zeroes are returned
* if there is no MCV list.
*/
double
mcv_selectivity(VariableStatData *vardata, FmgrInfo *opproc, Oid collation,
Datum constval, bool varonleft,
double *sumcommonp)
{
double mcv_selec,
sumcommon;
AttStatsSlot sslot;
int i;
mcv_selec = 0.0;
sumcommon = 0.0;
if (HeapTupleIsValid(vardata->statsTuple) &&
statistic_proc_security_check(vardata, opproc->fn_oid) &&
get_attstatsslot(&sslot, vardata->statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS))
{
LOCAL_FCINFO(fcinfo, 2);
/*
* We invoke the opproc "by hand" so that we won't fail on NULL
* results. Such cases won't arise for normal comparison functions,
* but generic_restriction_selectivity could perhaps be used with
* operators that can return NULL. A small side benefit is to not
* need to re-initialize the fcinfo struct from scratch each time.
*/
InitFunctionCallInfoData(*fcinfo, opproc, 2, collation,
NULL, NULL);
fcinfo->args[0].isnull = false;
fcinfo->args[1].isnull = false;
/* be careful to apply operator right way 'round */
if (varonleft)
fcinfo->args[1].value = constval;
else
fcinfo->args[0].value = constval;
for (i = 0; i < sslot.nvalues; i++)
{
Datum fresult;
if (varonleft)
fcinfo->args[0].value = sslot.values[i];
else
fcinfo->args[1].value = sslot.values[i];
fcinfo->isnull = false;
fresult = FunctionCallInvoke(fcinfo);
if (!fcinfo->isnull && DatumGetBool(fresult))
mcv_selec += sslot.numbers[i];
sumcommon += sslot.numbers[i];
}
free_attstatsslot(&sslot);
}
*sumcommonp = sumcommon;
return mcv_selec;
}
/*
* histogram_selectivity - Examine the histogram for selectivity estimates
*
* Determine the fraction of the variable's histogram entries that satisfy
* the predicate (VAR OP CONST), or (CONST OP VAR) if !varonleft.
*
* This code will work for any boolean-returning predicate operator, whether
* or not it has anything to do with the histogram sort operator. We are
* essentially using the histogram just as a representative sample. However,
* small histograms are unlikely to be all that representative, so the caller
* should be prepared to fall back on some other estimation approach when the
* histogram is missing or very small. It may also be prudent to combine this
* approach with another one when the histogram is small.
*
* If the actual histogram size is not at least min_hist_size, we won't bother
* to do the calculation at all. Also, if the n_skip parameter is > 0, we
* ignore the first and last n_skip histogram elements, on the grounds that
* they are outliers and hence not very representative. Typical values for
* these parameters are 10 and 1.
*
* The function result is the selectivity, or -1 if there is no histogram
* or it's smaller than min_hist_size.
*
* The output parameter *hist_size receives the actual histogram size,
* or zero if no histogram. Callers may use this number to decide how
* much faith to put in the function result.
*
* Note that the result disregards both the most-common-values (if any) and
* null entries. The caller is expected to combine this result with
* statistics for those portions of the column population. It may also be
* prudent to clamp the result range, ie, disbelieve exact 0 or 1 outputs.
*/
double
histogram_selectivity(VariableStatData *vardata,
FmgrInfo *opproc, Oid collation,
Datum constval, bool varonleft,
int min_hist_size, int n_skip,
int *hist_size)
{
double result;
AttStatsSlot sslot;
/* check sanity of parameters */
Assert(n_skip >= 0);
Assert(min_hist_size > 2 * n_skip);
if (HeapTupleIsValid(vardata->statsTuple) &&
statistic_proc_security_check(vardata, opproc->fn_oid) &&
get_attstatsslot(&sslot, vardata->statsTuple,
STATISTIC_KIND_HISTOGRAM, InvalidOid,
ATTSTATSSLOT_VALUES))
{
*hist_size = sslot.nvalues;
if (sslot.nvalues >= min_hist_size)
{
LOCAL_FCINFO(fcinfo, 2);
int nmatch = 0;
int i;
/*
* We invoke the opproc "by hand" so that we won't fail on NULL
* results. Such cases won't arise for normal comparison
* functions, but generic_restriction_selectivity could perhaps be
* used with operators that can return NULL. A small side benefit
* is to not need to re-initialize the fcinfo struct from scratch
* each time.
*/
InitFunctionCallInfoData(*fcinfo, opproc, 2, collation,
NULL, NULL);
fcinfo->args[0].isnull = false;
fcinfo->args[1].isnull = false;
/* be careful to apply operator right way 'round */
if (varonleft)
fcinfo->args[1].value = constval;
else
fcinfo->args[0].value = constval;
for (i = n_skip; i < sslot.nvalues - n_skip; i++)
{
Datum fresult;
if (varonleft)
fcinfo->args[0].value = sslot.values[i];
else
fcinfo->args[1].value = sslot.values[i];
fcinfo->isnull = false;
fresult = FunctionCallInvoke(fcinfo);
if (!fcinfo->isnull && DatumGetBool(fresult))
nmatch++;
}
result = ((double) nmatch) / ((double) (sslot.nvalues - 2 * n_skip));
}
else
result = -1;
free_attstatsslot(&sslot);
}
else
{
*hist_size = 0;
result = -1;
}
return result;
}
/*
* generic_restriction_selectivity - Selectivity for almost anything
*
* This function estimates selectivity for operators that we don't have any
* special knowledge about, but are on data types that we collect standard
* MCV and/or histogram statistics for. (Additional assumptions are that
* the operator is strict and immutable, or at least stable.)
*
* If we have "VAR OP CONST" or "CONST OP VAR", selectivity is estimated by
* applying the operator to each element of the column's MCV and/or histogram
* stats, and merging the results using the assumption that the histogram is
* a reasonable random sample of the column's non-MCV population. Note that
* if the operator's semantics are related to the histogram ordering, this
* might not be such a great assumption; other functions such as
* scalarineqsel() are probably a better match in such cases.
*
* Otherwise, fall back to the default selectivity provided by the caller.
*/
double
generic_restriction_selectivity(PlannerInfo *root, Oid oproid, Oid collation,
List *args, int varRelid,
double default_selectivity)
{
double selec;
VariableStatData vardata;
Node *other;
bool varonleft;
/*
* If expression is not variable OP something or something OP variable,
* then punt and return the default estimate.
*/
if (!get_restriction_variable(root, args, varRelid,
&vardata, &other, &varonleft))
return default_selectivity;
/*
* If the something is a NULL constant, assume operator is strict and
* return zero, ie, operator will never return TRUE.
*/
if (IsA(other, Const) &&
((Const *) other)->constisnull)
{
ReleaseVariableStats(vardata);
return 0.0;
}
if (IsA(other, Const))
{
/* Variable is being compared to a known non-null constant */
Datum constval = ((Const *) other)->constvalue;
FmgrInfo opproc;
double mcvsum;
double mcvsel;
double nullfrac;
int hist_size;
fmgr_info(get_opcode(oproid), &opproc);
/*
* Calculate the selectivity for the column's most common values.
*/
mcvsel = mcv_selectivity(&vardata, &opproc, collation,
constval, varonleft,
&mcvsum);
/*
* If the histogram is large enough, see what fraction of it matches
* the query, and assume that's representative of the non-MCV
* population. Otherwise use the default selectivity for the non-MCV
* population.
*/
selec = histogram_selectivity(&vardata, &opproc, collation,
constval, varonleft,
10, 1, &hist_size);
if (selec < 0)
{
/* Nope, fall back on default */
selec = default_selectivity;
}
else if (hist_size < 100)
{
/*
* For histogram sizes from 10 to 100, we combine the histogram
* and default selectivities, putting increasingly more trust in
* the histogram for larger sizes.
*/
double hist_weight = hist_size / 100.0;
selec = selec * hist_weight +
default_selectivity * (1.0 - hist_weight);
}
/* In any case, don't believe extremely small or large estimates. */
if (selec < 0.0001)
selec = 0.0001;
else if (selec > 0.9999)
selec = 0.9999;
/* Don't forget to account for nulls. */
if (HeapTupleIsValid(vardata.statsTuple))
nullfrac = ((Form_pg_statistic) GETSTRUCT(vardata.statsTuple))->stanullfrac;
else
nullfrac = 0.0;
/*
* Now merge the results from the MCV and histogram calculations,
* realizing that the histogram covers only the non-null values that
* are not listed in MCV.
*/
selec *= 1.0 - nullfrac - mcvsum;
selec += mcvsel;
}
else
{
/* Comparison value is not constant, so we can't do anything */
selec = default_selectivity;
}
ReleaseVariableStats(vardata);
/* result should be in range, but make sure... */
CLAMP_PROBABILITY(selec);
return selec;
}
/*
* ineq_histogram_selectivity - Examine the histogram for scalarineqsel
*
* Determine the fraction of the variable's histogram population that
* satisfies the inequality condition, ie, VAR < (or <=, >, >=) CONST.
* The isgt and iseq flags distinguish which of the four cases apply.
*
* While opproc could be looked up from the operator OID, common callers
* also need to call it separately, so we make the caller pass both.
*
* Returns -1 if there is no histogram (valid results will always be >= 0).
*
* Note that the result disregards both the most-common-values (if any) and
* null entries. The caller is expected to combine this result with
* statistics for those portions of the column population.
*
* This is exported so that some other estimation functions can use it.
*/
double
ineq_histogram_selectivity(PlannerInfo *root,
VariableStatData *vardata,
Oid opoid, FmgrInfo *opproc, bool isgt, bool iseq,
Oid collation,
Datum constval, Oid consttype)
{
double hist_selec;
AttStatsSlot sslot;
hist_selec = -1.0;
/*
* Someday, ANALYZE might store more than one histogram per rel/att,
* corresponding to more than one possible sort ordering defined for the
* column type. Right now, we know there is only one, so just grab it and
* see if it matches the query.
*
* Note that we can't use opoid as search argument; the staop appearing in
* pg_statistic will be for the relevant '<' operator, but what we have
* might be some other inequality operator such as '>='. (Even if opoid
* is a '<' operator, it could be cross-type.) Hence we must use
* comparison_ops_are_compatible() to see if the operators match.
*/
if (HeapTupleIsValid(vardata->statsTuple) &&
statistic_proc_security_check(vardata, opproc->fn_oid) &&
get_attstatsslot(&sslot, vardata->statsTuple,
STATISTIC_KIND_HISTOGRAM, InvalidOid,
ATTSTATSSLOT_VALUES))
{
if (sslot.nvalues > 1 &&
sslot.stacoll == collation &&
comparison_ops_are_compatible(sslot.staop, opoid))
{
/*
* Use binary search to find the desired location, namely the
* right end of the histogram bin containing the comparison value,
* which is the leftmost entry for which the comparison operator
* succeeds (if isgt) or fails (if !isgt).
*
* In this loop, we pay no attention to whether the operator iseq
* or not; that detail will be mopped up below. (We cannot tell,
* anyway, whether the operator thinks the values are equal.)
*
* If the binary search accesses the first or last histogram
* entry, we try to replace that endpoint with the true column min
* or max as found by get_actual_variable_range(). This
* ameliorates misestimates when the min or max is moving as a
* result of changes since the last ANALYZE. Note that this could
* result in effectively including MCVs into the histogram that
* weren't there before, but we don't try to correct for that.
*/
double histfrac;
int lobound = 0; /* first possible slot to search */
int hibound = sslot.nvalues; /* last+1 slot to search */
bool have_end = false;
/*
* If there are only two histogram entries, we'll want up-to-date
* values for both. (If there are more than two, we need at most
* one of them to be updated, so we deal with that within the
* loop.)
*/
if (sslot.nvalues == 2)
have_end = get_actual_variable_range(root,
vardata,
sslot.staop,
collation,
&sslot.values[0],
&sslot.values[1]);
while (lobound < hibound)
{
int probe = (lobound + hibound) / 2;
bool ltcmp;
/*
* If we find ourselves about to compare to the first or last
* histogram entry, first try to replace it with the actual
* current min or max (unless we already did so above).
*/
if (probe == 0 && sslot.nvalues > 2)
have_end = get_actual_variable_range(root,
vardata,
sslot.staop,
collation,
&sslot.values[0],
NULL);
else if (probe == sslot.nvalues - 1 && sslot.nvalues > 2)
have_end = get_actual_variable_range(root,
vardata,
sslot.staop,
collation,
NULL,
&sslot.values[probe]);
ltcmp = DatumGetBool(FunctionCall2Coll(opproc,
collation,
sslot.values[probe],
constval));
if (isgt)
ltcmp = !ltcmp;
if (ltcmp)
lobound = probe + 1;
else
hibound = probe;
}
if (lobound <= 0)
{
/*
* Constant is below lower histogram boundary. More
* precisely, we have found that no entry in the histogram
* satisfies the inequality clause (if !isgt) or they all do
* (if isgt). We estimate that that's true of the entire
* table, so set histfrac to 0.0 (which we'll flip to 1.0
* below, if isgt).
*/
histfrac = 0.0;
}
else if (lobound >= sslot.nvalues)
{
/*
* Inverse case: constant is above upper histogram boundary.
*/
histfrac = 1.0;
}
else
{
/* We have values[i-1] <= constant <= values[i]. */
int i = lobound;
double eq_selec = 0;
double val,
high,
low;
double binfrac;
/*
* In the cases where we'll need it below, obtain an estimate
* of the selectivity of "x = constval". We use a calculation
* similar to what var_eq_const() does for a non-MCV constant,
* ie, estimate that all distinct non-MCV values occur equally
* often. But multiplication by "1.0 - sumcommon - nullfrac"
* will be done by our caller, so we shouldn't do that here.
* Therefore we can't try to clamp the estimate by reference
* to the least common MCV; the result would be too small.
*
* Note: since this is effectively assuming that constval
* isn't an MCV, it's logically dubious if constval in fact is
* one. But we have to apply *some* correction for equality,
* and anyway we cannot tell if constval is an MCV, since we
* don't have a suitable equality operator at hand.
*/
if (i == 1 || isgt == iseq)
{
double otherdistinct;
bool isdefault;
AttStatsSlot mcvslot;
/* Get estimated number of distinct values */
otherdistinct = get_variable_numdistinct(vardata,
&isdefault);
/* Subtract off the number of known MCVs */
if (get_attstatsslot(&mcvslot, vardata->statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
ATTSTATSSLOT_NUMBERS))
{
otherdistinct -= mcvslot.nnumbers;
free_attstatsslot(&mcvslot);
}
/* If result doesn't seem sane, leave eq_selec at 0 */
if (otherdistinct > 1)
eq_selec = 1.0 / otherdistinct;
}
/*
* Convert the constant and the two nearest bin boundary
* values to a uniform comparison scale, and do a linear
* interpolation within this bin.
*/
if (convert_to_scalar(constval, consttype, collation,
&val,
sslot.values[i - 1], sslot.values[i],
vardata->vartype,
&low, &high))
{
if (high <= low)
{
/* cope if bin boundaries appear identical */
binfrac = 0.5;
}
else if (val <= low)
binfrac = 0.0;
else if (val >= high)
binfrac = 1.0;
else
{
binfrac = (val - low) / (high - low);
/*
* Watch out for the possibility that we got a NaN or
* Infinity from the division. This can happen
* despite the previous checks, if for example "low"
* is -Infinity.
*/
if (isnan(binfrac) ||
binfrac < 0.0 || binfrac > 1.0)
binfrac = 0.5;
}
}
else
{
/*
* Ideally we'd produce an error here, on the grounds that
* the given operator shouldn't have scalarXXsel
* registered as its selectivity func unless we can deal
* with its operand types. But currently, all manner of
* stuff is invoking scalarXXsel, so give a default
* estimate until that can be fixed.
*/
binfrac = 0.5;
}
/*
* Now, compute the overall selectivity across the values
* represented by the histogram. We have i-1 full bins and
* binfrac partial bin below the constant.
*/
histfrac = (double) (i - 1) + binfrac;
histfrac /= (double) (sslot.nvalues - 1);
/*
* At this point, histfrac is an estimate of the fraction of
* the population represented by the histogram that satisfies
* "x <= constval". Somewhat remarkably, this statement is
* true regardless of which operator we were doing the probes
* with, so long as convert_to_scalar() delivers reasonable
* results. If the probe constant is equal to some histogram
* entry, we would have considered the bin to the left of that
* entry if probing with "<" or ">=", or the bin to the right
* if probing with "<=" or ">"; but binfrac would have come
* out as 1.0 in the first case and 0.0 in the second, leading
* to the same histfrac in either case. For probe constants
* between histogram entries, we find the same bin and get the
* same estimate with any operator.
*
* The fact that the estimate corresponds to "x <= constval"
* and not "x < constval" is because of the way that ANALYZE
* constructs the histogram: each entry is, effectively, the
* rightmost value in its sample bucket. So selectivity
* values that are exact multiples of 1/(histogram_size-1)
* should be understood as estimates including a histogram
* entry plus everything to its left.
*
* However, that breaks down for the first histogram entry,
* which necessarily is the leftmost value in its sample
* bucket. That means the first histogram bin is slightly
* narrower than the rest, by an amount equal to eq_selec.
* Another way to say that is that we want "x <= leftmost" to
* be estimated as eq_selec not zero. So, if we're dealing
* with the first bin (i==1), rescale to make that true while
* adjusting the rest of that bin linearly.
*/
if (i == 1)
histfrac += eq_selec * (1.0 - binfrac);
/*
* "x <= constval" is good if we want an estimate for "<=" or
* ">", but if we are estimating for "<" or ">=", we now need
* to decrease the estimate by eq_selec.
*/
if (isgt == iseq)
histfrac -= eq_selec;
}
/*
* Now the estimate is finished for "<" and "<=" cases. If we are
* estimating for ">" or ">=", flip it.
*/
hist_selec = isgt ? (1.0 - histfrac) : histfrac;
/*
* The histogram boundaries are only approximate to begin with,
* and may well be out of date anyway. Therefore, don't believe
* extremely small or large selectivity estimates --- unless we
* got actual current endpoint values from the table, in which
* case just do the usual sanity clamp. Somewhat arbitrarily, we
* set the cutoff for other cases at a hundredth of the histogram
* resolution.
*/
if (have_end)
CLAMP_PROBABILITY(hist_selec);
else
{
double cutoff = 0.01 / (double) (sslot.nvalues - 1);
if (hist_selec < cutoff)
hist_selec = cutoff;
else if (hist_selec > 1.0 - cutoff)
hist_selec = 1.0 - cutoff;
}
}
else if (sslot.nvalues > 1)
{
/*
* If we get here, we have a histogram but it's not sorted the way
* we want. Do a brute-force search to see how many of the
* entries satisfy the comparison condition, and take that
* fraction as our estimate. (This is identical to the inner loop
* of histogram_selectivity; maybe share code?)
*/
LOCAL_FCINFO(fcinfo, 2);
int nmatch = 0;
InitFunctionCallInfoData(*fcinfo, opproc, 2, collation,
NULL, NULL);
fcinfo->args[0].isnull = false;
fcinfo->args[1].isnull = false;
fcinfo->args[1].value = constval;
for (int i = 0; i < sslot.nvalues; i++)
{
Datum fresult;
fcinfo->args[0].value = sslot.values[i];
fcinfo->isnull = false;
fresult = FunctionCallInvoke(fcinfo);
if (!fcinfo->isnull && DatumGetBool(fresult))
nmatch++;
}
hist_selec = ((double) nmatch) / ((double) sslot.nvalues);
/*
* As above, clamp to a hundredth of the histogram resolution.
* This case is surely even less trustworthy than the normal one,
* so we shouldn't believe exact 0 or 1 selectivity. (Maybe the
* clamp should be more restrictive in this case?)
*/
{
double cutoff = 0.01 / (double) (sslot.nvalues - 1);
if (hist_selec < cutoff)
hist_selec = cutoff;
else if (hist_selec > 1.0 - cutoff)
hist_selec = 1.0 - cutoff;
}
}
free_attstatsslot(&sslot);
}
return hist_selec;
}
/*
* Common wrapper function for the selectivity estimators that simply
* invoke scalarineqsel().
*/
static Datum
scalarineqsel_wrapper(PG_FUNCTION_ARGS, bool isgt, bool iseq)
{
PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
Oid operator = PG_GETARG_OID(1);
List *args = (List *) PG_GETARG_POINTER(2);
int varRelid = PG_GETARG_INT32(3);
Oid collation = PG_GET_COLLATION();
VariableStatData vardata;
Node *other;
bool varonleft;
Datum constval;
Oid consttype;
double selec;
/*
* If expression is not variable op something or something op variable,
* then punt and return a default estimate.
*/
if (!get_restriction_variable(root, args, varRelid,
&vardata, &other, &varonleft))
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
/*
* Can't do anything useful if the something is not a constant, either.
*/
if (!IsA(other, Const))
{
ReleaseVariableStats(vardata);
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
}
/*
* If the constant is NULL, assume operator is strict and return zero, ie,
* operator will never return TRUE.
*/
if (((Const *) other)->constisnull)
{
ReleaseVariableStats(vardata);
PG_RETURN_FLOAT8(0.0);
}
constval = ((Const *) other)->constvalue;
consttype = ((Const *) other)->consttype;
/*
* Force the var to be on the left to simplify logic in scalarineqsel.
*/
if (!varonleft)
{
operator = get_commutator(operator);
if (!operator)
{
/* Use default selectivity (should we raise an error instead?) */
ReleaseVariableStats(vardata);
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
}
isgt = !isgt;
}
/* The rest of the work is done by scalarineqsel(). */
selec = scalarineqsel(root, operator, isgt, iseq, collation,
&vardata, constval, consttype);
ReleaseVariableStats(vardata);
PG_RETURN_FLOAT8((float8) selec);
}
/*
* scalarltsel - Selectivity of "<" for scalars.
*/
Datum
scalarltsel(PG_FUNCTION_ARGS)
{
return scalarineqsel_wrapper(fcinfo, false, false);
}
/*
* scalarlesel - Selectivity of "<=" for scalars.
*/
Datum
scalarlesel(PG_FUNCTION_ARGS)
{
return scalarineqsel_wrapper(fcinfo, false, true);
}
/*
* scalargtsel - Selectivity of ">" for scalars.
*/
Datum
scalargtsel(PG_FUNCTION_ARGS)
{
return scalarineqsel_wrapper(fcinfo, true, false);
}
/*
* scalargesel - Selectivity of ">=" for scalars.
*/
Datum
scalargesel(PG_FUNCTION_ARGS)
{
return scalarineqsel_wrapper(fcinfo, true, true);
}
/*
* boolvarsel - Selectivity of Boolean variable.
*
* This can actually be called on any boolean-valued expression. If it
* involves only Vars of the specified relation, and if there are statistics
* about the Var or expression (the latter is possible if it's indexed) then
* we'll produce a real estimate; otherwise it's just a default.
*/
Selectivity
boolvarsel(PlannerInfo *root, Node *arg, int varRelid)
{
VariableStatData vardata;
double selec;
examine_variable(root, arg, varRelid, &vardata);
if (HeapTupleIsValid(vardata.statsTuple))
{
/*
* A boolean variable V is equivalent to the clause V = 't', so we
* compute the selectivity as if that is what we have.
*/
selec = var_eq_const(&vardata, BooleanEqualOperator, InvalidOid,
BoolGetDatum(true), false, true, false);
}
else if (is_funcclause(arg))
{
/*
* If we have no stats and it's a function call, estimate 0.3333333.
* This seems a pretty unprincipled choice, but Postgres has been
* using that estimate for function calls since 1992. The hoariness
* of this behavior suggests that we should not be in too much hurry
* to use another value.
*/
selec = 0.3333333;
}
else
{
/* Otherwise, the default estimate is 0.5 */
selec = 0.5;
}
ReleaseVariableStats(vardata);
return selec;
}
/*
* booltestsel - Selectivity of BooleanTest Node.
*/
Selectivity
booltestsel(PlannerInfo *root, BoolTestType booltesttype, Node *arg,
int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo)
{
VariableStatData vardata;
double selec;
examine_variable(root, arg, varRelid, &vardata);
if (HeapTupleIsValid(vardata.statsTuple))
{
Form_pg_statistic stats;
double freq_null;
AttStatsSlot sslot;
stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple);
freq_null = stats->stanullfrac;
if (get_attstatsslot(&sslot, vardata.statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)
&& sslot.nnumbers > 0)
{
double freq_true;
double freq_false;
/*
* Get first MCV frequency and derive frequency for true.
*/
if (DatumGetBool(sslot.values[0]))
freq_true = sslot.numbers[0];
else
freq_true = 1.0 - sslot.numbers[0] - freq_null;
/*
* Next derive frequency for false. Then use these as appropriate
* to derive frequency for each case.
*/
freq_false = 1.0 - freq_true - freq_null;
switch (booltesttype)
{
case IS_UNKNOWN:
/* select only NULL values */
selec = freq_null;
break;
case IS_NOT_UNKNOWN:
/* select non-NULL values */
selec = 1.0 - freq_null;
break;
case IS_TRUE:
/* select only TRUE values */
selec = freq_true;
break;
case IS_NOT_TRUE:
/* select non-TRUE values */
selec = 1.0 - freq_true;
break;
case IS_FALSE:
/* select only FALSE values */
selec = freq_false;
break;
case IS_NOT_FALSE:
/* select non-FALSE values */
selec = 1.0 - freq_false;
break;
default:
elog(ERROR, "unrecognized booltesttype: %d",
(int) booltesttype);
selec = 0.0; /* Keep compiler quiet */
break;
}
free_attstatsslot(&sslot);
}
else
{
/*
* No most-common-value info available. Still have null fraction
* information, so use it for IS [NOT] UNKNOWN. Otherwise adjust
* for null fraction and assume a 50-50 split of TRUE and FALSE.
*/
switch (booltesttype)
{
case IS_UNKNOWN:
/* select only NULL values */
selec = freq_null;
break;
case IS_NOT_UNKNOWN:
/* select non-NULL values */
selec = 1.0 - freq_null;
break;
case IS_TRUE:
case IS_FALSE:
/* Assume we select half of the non-NULL values */
selec = (1.0 - freq_null) / 2.0;
break;
case IS_NOT_TRUE:
case IS_NOT_FALSE:
/* Assume we select NULLs plus half of the non-NULLs */
/* equiv. to freq_null + (1.0 - freq_null) / 2.0 */
selec = (freq_null + 1.0) / 2.0;
break;
default:
elog(ERROR, "unrecognized booltesttype: %d",
(int) booltesttype);
selec = 0.0; /* Keep compiler quiet */
break;
}
}
}
else
{
/*
* If we can't get variable statistics for the argument, perhaps
* clause_selectivity can do something with it. We ignore the
* possibility of a NULL value when using clause_selectivity, and just
* assume the value is either TRUE or FALSE.
*/
switch (booltesttype)
{
case IS_UNKNOWN:
selec = DEFAULT_UNK_SEL;
break;
case IS_NOT_UNKNOWN:
selec = DEFAULT_NOT_UNK_SEL;
break;
case IS_TRUE:
case IS_NOT_FALSE:
selec = (double) clause_selectivity(root, arg,
varRelid,
jointype, sjinfo);
break;
case IS_FALSE:
case IS_NOT_TRUE:
selec = 1.0 - (double) clause_selectivity(root, arg,
varRelid,
jointype, sjinfo);
break;
default:
elog(ERROR, "unrecognized booltesttype: %d",
(int) booltesttype);
selec = 0.0; /* Keep compiler quiet */
break;
}
}
ReleaseVariableStats(vardata);
/* result should be in range, but make sure... */
CLAMP_PROBABILITY(selec);
return (Selectivity) selec;
}
/*
* nulltestsel - Selectivity of NullTest Node.
*/
Selectivity
nulltestsel(PlannerInfo *root, NullTestType nulltesttype, Node *arg,
int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo)
{
VariableStatData vardata;
double selec;
examine_variable(root, arg, varRelid, &vardata);
if (HeapTupleIsValid(vardata.statsTuple))
{
Form_pg_statistic stats;
double freq_null;
stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple);
freq_null = stats->stanullfrac;
switch (nulltesttype)
{
case IS_NULL:
/*
* Use freq_null directly.
*/
selec = freq_null;
break;
case IS_NOT_NULL:
/*
* Select not unknown (not null) values. Calculate from
* freq_null.
*/
selec = 1.0 - freq_null;
break;
default:
elog(ERROR, "unrecognized nulltesttype: %d",
(int) nulltesttype);
return (Selectivity) 0; /* keep compiler quiet */
}
}
else if (vardata.var && IsA(vardata.var, Var) &&
((Var *) vardata.var)->varattno < 0)
{
/*
* There are no stats for system columns, but we know they are never
* NULL.
*/
selec = (nulltesttype == IS_NULL) ? 0.0 : 1.0;
}
else
{
/*
* No ANALYZE stats available, so make a guess
*/
switch (nulltesttype)
{
case IS_NULL:
selec = DEFAULT_UNK_SEL;
break;
case IS_NOT_NULL:
selec = DEFAULT_NOT_UNK_SEL;
break;
default:
elog(ERROR, "unrecognized nulltesttype: %d",
(int) nulltesttype);
return (Selectivity) 0; /* keep compiler quiet */
}
}
ReleaseVariableStats(vardata);
/* result should be in range, but make sure... */
CLAMP_PROBABILITY(selec);
return (Selectivity) selec;
}
/*
* strip_array_coercion - strip binary-compatible relabeling from an array expr
*
* For array values, the parser normally generates ArrayCoerceExpr conversions,
* but it seems possible that RelabelType might show up. Also, the planner
* is not currently tense about collapsing stacked ArrayCoerceExpr nodes,
* so we need to be ready to deal with more than one level.
*/
static Node *
strip_array_coercion(Node *node)
{
for (;;)
{
if (node && IsA(node, ArrayCoerceExpr))
{
ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
/*
* If the per-element expression is just a RelabelType on top of
* CaseTestExpr, then we know it's a binary-compatible relabeling.
*/
if (IsA(acoerce->elemexpr, RelabelType) &&
IsA(((RelabelType *) acoerce->elemexpr)->arg, CaseTestExpr))
node = (Node *) acoerce->arg;
else
break;
}
else if (node && IsA(node, RelabelType))
{
/* We don't really expect this case, but may as well cope */
node = (Node *) ((RelabelType *) node)->arg;
}
else
break;
}
return node;
}
/*
* scalararraysel - Selectivity of ScalarArrayOpExpr Node.
*/
Selectivity
scalararraysel(PlannerInfo *root,
ScalarArrayOpExpr *clause,
bool is_join_clause,
int varRelid,
JoinType jointype,
SpecialJoinInfo *sjinfo)
{
Oid operator = clause->opno;
bool useOr = clause->useOr;
bool isEquality = false;
bool isInequality = false;
Node *leftop;
Node *rightop;
Oid nominal_element_type;
Oid nominal_element_collation;
TypeCacheEntry *typentry;
RegProcedure oprsel;
FmgrInfo oprselproc;
Selectivity s1;
Selectivity s1disjoint;
/* First, deconstruct the expression */
Assert(list_length(clause->args) == 2);
leftop = (Node *) linitial(clause->args);
rightop = (Node *) lsecond(clause->args);
/* aggressively reduce both sides to constants */
leftop = estimate_expression_value(root, leftop);
rightop = estimate_expression_value(root, rightop);
/* get nominal (after relabeling) element type of rightop */
nominal_element_type = get_base_element_type(exprType(rightop));
if (!OidIsValid(nominal_element_type))
return (Selectivity) 0.5; /* probably shouldn't happen */
/* get nominal collation, too, for generating constants */
nominal_element_collation = exprCollation(rightop);
/* look through any binary-compatible relabeling of rightop */
rightop = strip_array_coercion(rightop);
/*
* Detect whether the operator is the default equality or inequality
* operator of the array element type.
*/
typentry = lookup_type_cache(nominal_element_type, TYPECACHE_EQ_OPR);
if (OidIsValid(typentry->eq_opr))
{
if (operator == typentry->eq_opr)
isEquality = true;
else if (get_negator(operator) == typentry->eq_opr)
isInequality = true;
}
/*
* If it is equality or inequality, we might be able to estimate this as a
* form of array containment; for instance "const = ANY(column)" can be
* treated as "ARRAY[const] <@ column". scalararraysel_containment tries
* that, and returns the selectivity estimate if successful, or -1 if not.
*/
if ((isEquality || isInequality) && !is_join_clause)
{
s1 = scalararraysel_containment(root, leftop, rightop,
nominal_element_type,
isEquality, useOr, varRelid);
if (s1 >= 0.0)
return s1;
}
/*
* Look up the underlying operator's selectivity estimator. Punt if it
* hasn't got one.
*/
if (is_join_clause)
oprsel = get_oprjoin(operator);
else
oprsel = get_oprrest(operator);
if (!oprsel)
return (Selectivity) 0.5;
fmgr_info(oprsel, &oprselproc);
/*
* In the array-containment check above, we must only believe that an
* operator is equality or inequality if it is the default btree equality
* operator (or its negator) for the element type, since those are the
* operators that array containment will use. But in what follows, we can
* be a little laxer, and also believe that any operators using eqsel() or
* neqsel() as selectivity estimator act like equality or inequality.
*/
if (oprsel == F_EQSEL || oprsel == F_EQJOINSEL)
isEquality = true;
else if (oprsel == F_NEQSEL || oprsel == F_NEQJOINSEL)
isInequality = true;
/*
* We consider three cases:
*
* 1. rightop is an Array constant: deconstruct the array, apply the
* operator's selectivity function for each array element, and merge the
* results in the same way that clausesel.c does for AND/OR combinations.
*
* 2. rightop is an ARRAY[] construct: apply the operator's selectivity
* function for each element of the ARRAY[] construct, and merge.
*
* 3. otherwise, make a guess ...
*/
if (rightop && IsA(rightop, Const))
{
Datum arraydatum = ((Const *) rightop)->constvalue;
bool arrayisnull = ((Const *) rightop)->constisnull;
ArrayType *arrayval;
int16 elmlen;
bool elmbyval;
char elmalign;
int num_elems;
Datum *elem_values;
bool *elem_nulls;
int i;
if (arrayisnull) /* qual can't succeed if null array */
return (Selectivity) 0.0;
arrayval = DatumGetArrayTypeP(arraydatum);
/*
* When the array contains a NULL constant, same as var_eq_const, we
* assume the operator is strict and nothing will match, thus return
* 0.0.
*/
if (!useOr && array_contains_nulls(arrayval))
return (Selectivity) 0.0;
get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
&elmlen, &elmbyval, &elmalign);
deconstruct_array(arrayval,
ARR_ELEMTYPE(arrayval),
elmlen, elmbyval, elmalign,
&elem_values, &elem_nulls, &num_elems);
/*
* For generic operators, we assume the probability of success is
* independent for each array element. But for "= ANY" or "<> ALL",
* if the array elements are distinct (which'd typically be the case)
* then the probabilities are disjoint, and we should just sum them.
*
* If we were being really tense we would try to confirm that the
* elements are all distinct, but that would be expensive and it
* doesn't seem to be worth the cycles; it would amount to penalizing
* well-written queries in favor of poorly-written ones. However, we
* do protect ourselves a little bit by checking whether the
* disjointness assumption leads to an impossible (out of range)
* probability; if so, we fall back to the normal calculation.
*/
s1 = s1disjoint = (useOr ? 0.0 : 1.0);
for (i = 0; i < num_elems; i++)
{
List *args;
Selectivity s2;
args = list_make2(leftop,
makeConst(nominal_element_type,
-1,
nominal_element_collation,
elmlen,
elem_values[i],
elem_nulls[i],
elmbyval));
if (is_join_clause)
s2 = DatumGetFloat8(FunctionCall5Coll(&oprselproc,
clause->inputcollid,
PointerGetDatum(root),
ObjectIdGetDatum(operator),
PointerGetDatum(args),
Int16GetDatum(jointype),
PointerGetDatum(sjinfo)));
else
s2 = DatumGetFloat8(FunctionCall4Coll(&oprselproc,
clause->inputcollid,
PointerGetDatum(root),
ObjectIdGetDatum(operator),
PointerGetDatum(args),
Int32GetDatum(varRelid)));
if (useOr)
{
s1 = s1 + s2 - s1 * s2;
if (isEquality)
s1disjoint += s2;
}
else
{
s1 = s1 * s2;
if (isInequality)
s1disjoint += s2 - 1.0;
}
}
/* accept disjoint-probability estimate if in range */
if ((useOr ? isEquality : isInequality) &&
s1disjoint >= 0.0 && s1disjoint <= 1.0)
s1 = s1disjoint;
}
else if (rightop && IsA(rightop, ArrayExpr) &&
!((ArrayExpr *) rightop)->multidims)
{
ArrayExpr *arrayexpr = (ArrayExpr *) rightop;
int16 elmlen;
bool elmbyval;
ListCell *l;
get_typlenbyval(arrayexpr->element_typeid,
&elmlen, &elmbyval);
/*
* We use the assumption of disjoint probabilities here too, although
* the odds of equal array elements are rather higher if the elements
* are not all constants (which they won't be, else constant folding
* would have reduced the ArrayExpr to a Const). In this path it's
* critical to have the sanity check on the s1disjoint estimate.
*/
s1 = s1disjoint = (useOr ? 0.0 : 1.0);
foreach(l, arrayexpr->elements)
{
Node *elem = (Node *) lfirst(l);
List *args;
Selectivity s2;
/*
* When the array contains a NULL constant, same as var_eq_const,
* we assume the operator is strict and nothing will match, thus
* return 0.0.
*/
if (!useOr && IsA(elem, Const) && ((Const *) elem)->constisnull)
return (Selectivity) 0.0;
/*
* Theoretically, if elem isn't of nominal_element_type we should
* insert a RelabelType, but it seems unlikely that any operator
* estimation function would really care ...
*/
args = list_make2(leftop, elem);
if (is_join_clause)
s2 = DatumGetFloat8(FunctionCall5Coll(&oprselproc,
clause->inputcollid,
PointerGetDatum(root),
ObjectIdGetDatum(operator),
PointerGetDatum(args),
Int16GetDatum(jointype),
PointerGetDatum(sjinfo)));
else
s2 = DatumGetFloat8(FunctionCall4Coll(&oprselproc,
clause->inputcollid,
PointerGetDatum(root),
ObjectIdGetDatum(operator),
PointerGetDatum(args),
Int32GetDatum(varRelid)));
if (useOr)
{
s1 = s1 + s2 - s1 * s2;
if (isEquality)
s1disjoint += s2;
}
else
{
s1 = s1 * s2;
if (isInequality)
s1disjoint += s2 - 1.0;
}
}
/* accept disjoint-probability estimate if in range */
if ((useOr ? isEquality : isInequality) &&
s1disjoint >= 0.0 && s1disjoint <= 1.0)
s1 = s1disjoint;
}
else
{
CaseTestExpr *dummyexpr;
List *args;
Selectivity s2;
int i;
/*
* We need a dummy rightop to pass to the operator selectivity
* routine. It can be pretty much anything that doesn't look like a
* constant; CaseTestExpr is a convenient choice.
*/
dummyexpr = makeNode(CaseTestExpr);
dummyexpr->typeId = nominal_element_type;
dummyexpr->typeMod = -1;
dummyexpr->collation = clause->inputcollid;
args = list_make2(leftop, dummyexpr);
if (is_join_clause)
s2 = DatumGetFloat8(FunctionCall5Coll(&oprselproc,
clause->inputcollid,
PointerGetDatum(root),
ObjectIdGetDatum(operator),
PointerGetDatum(args),
Int16GetDatum(jointype),
PointerGetDatum(sjinfo)));
else
s2 = DatumGetFloat8(FunctionCall4Coll(&oprselproc,
clause->inputcollid,
PointerGetDatum(root),
ObjectIdGetDatum(operator),
PointerGetDatum(args),
Int32GetDatum(varRelid)));
s1 = useOr ? 0.0 : 1.0;
/*
* Arbitrarily assume 10 elements in the eventual array value (see
* also estimate_array_length). We don't risk an assumption of
* disjoint probabilities here.
*/
for (i = 0; i < 10; i++)
{
if (useOr)
s1 = s1 + s2 - s1 * s2;
else
s1 = s1 * s2;
}
}
/* result should be in range, but make sure... */
CLAMP_PROBABILITY(s1);
return s1;
}
/*
* Estimate number of elements in the array yielded by an expression.
*
* Note: the result is integral, but we use "double" to avoid overflow
* concerns. Most callers will use it in double-type expressions anyway.
*
* Note: in some code paths root can be passed as NULL, resulting in
* slightly worse estimates.
*/
double
estimate_array_length(PlannerInfo *root, Node *arrayexpr)
{
/* look through any binary-compatible relabeling of arrayexpr */
arrayexpr = strip_array_coercion(arrayexpr);
if (arrayexpr && IsA(arrayexpr, Const))
{
Datum arraydatum = ((Const *) arrayexpr)->constvalue;
bool arrayisnull = ((Const *) arrayexpr)->constisnull;
ArrayType *arrayval;
if (arrayisnull)
return 0;
arrayval = DatumGetArrayTypeP(arraydatum);
return ArrayGetNItems(ARR_NDIM(arrayval), ARR_DIMS(arrayval));
}
else if (arrayexpr && IsA(arrayexpr, ArrayExpr) &&
!((ArrayExpr *) arrayexpr)->multidims)
{
return list_length(((ArrayExpr *) arrayexpr)->elements);
}
else if (arrayexpr && root)
{
/* See if we can find any statistics about it */
VariableStatData vardata;
AttStatsSlot sslot;
double nelem = 0;
/*
* Skip calling examine_variable for Var with varno 0, which has no
* valid relation entry and would error in find_base_rel. Such a Var
* can appear when a nested set operation's output type doesn't match
* the parent's expected type, because recurse_set_operations builds a
* projection target list using generate_setop_tlist with varno 0, and
* if the required type coercion involves an ArrayCoerceExpr, we can
* be called on that Var.
*/
if (IsA(arrayexpr, Var) && ((Var *) arrayexpr)->varno == 0)
return 10; /* default guess, should match scalararraysel */
examine_variable(root, arrayexpr, 0, &vardata);
if (HeapTupleIsValid(vardata.statsTuple))
{
/*
* Found stats, so use the average element count, which is stored
* in the last stanumbers element of the DECHIST statistics.
* Actually that is the average count of *distinct* elements;
* perhaps we should scale it up somewhat?
*/
if (get_attstatsslot(&sslot, vardata.statsTuple,
STATISTIC_KIND_DECHIST, InvalidOid,
ATTSTATSSLOT_NUMBERS))
{
if (sslot.nnumbers > 0)
nelem = clamp_row_est(sslot.numbers[sslot.nnumbers - 1]);
free_attstatsslot(&sslot);
}
}
ReleaseVariableStats(vardata);
if (nelem > 0)
return nelem;
}
/* Else use a default guess --- this should match scalararraysel */
return 10;
}
/*
* rowcomparesel - Selectivity of RowCompareExpr Node.
*
* We estimate RowCompare selectivity by considering just the first (high
* order) columns, which makes it equivalent to an ordinary OpExpr. While
* this estimate could be refined by considering additional columns, it
* seems unlikely that we could do a lot better without multi-column
* statistics.
*/
Selectivity
rowcomparesel(PlannerInfo *root,
RowCompareExpr *clause,
int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo)
{
Selectivity s1;
Oid opno = linitial_oid(clause->opnos);
Oid inputcollid = linitial_oid(clause->inputcollids);
List *opargs;
bool is_join_clause;
/* Build equivalent arg list for single operator */
opargs = list_make2(linitial(clause->largs), linitial(clause->rargs));
/*
* Decide if it's a join clause. This should match clausesel.c's
* treat_as_join_clause(), except that we intentionally consider only the
* leading columns and not the rest of the clause.
*/
if (varRelid != 0)
{
/*
* Caller is forcing restriction mode (eg, because we are examining an
* inner indexscan qual).
*/
is_join_clause = false;
}
else if (sjinfo == NULL)
{
/*
* It must be a restriction clause, since it's being evaluated at a
* scan node.
*/
is_join_clause = false;
}
else
{
/*
* Otherwise, it's a join if there's more than one base relation used.
*/
is_join_clause = (NumRelids(root, (Node *) opargs) > 1);
}
if (is_join_clause)
{
/* Estimate selectivity for a join clause. */
s1 = join_selectivity(root, opno,
opargs,
inputcollid,
jointype,
sjinfo);
}
else
{
/* Estimate selectivity for a restriction clause. */
s1 = restriction_selectivity(root, opno,
opargs,
inputcollid,
varRelid);
}
return s1;
}
/*
* eqjoinsel - Join selectivity of "="
*/
Datum
eqjoinsel(PG_FUNCTION_ARGS)
{
PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
Oid operator = PG_GETARG_OID(1);
List *args = (List *) PG_GETARG_POINTER(2);
#ifdef NOT_USED
JoinType jointype = (JoinType) PG_GETARG_INT16(3);
#endif
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4);
Oid collation = PG_GET_COLLATION();
double selec;
double selec_inner;
VariableStatData vardata1;
VariableStatData vardata2;
double nd1;
double nd2;
bool isdefault1;
bool isdefault2;
Oid opfuncoid;
FmgrInfo eqproc;
Oid hashLeft = InvalidOid;
Oid hashRight = InvalidOid;
AttStatsSlot sslot1;
AttStatsSlot sslot2;
Form_pg_statistic stats1 = NULL;
Form_pg_statistic stats2 = NULL;
bool have_mcvs1 = false;
bool have_mcvs2 = false;
bool *hasmatch1 = NULL;
bool *hasmatch2 = NULL;
int nmatches = 0;
bool get_mcv_stats;
bool join_is_reversed;
RelOptInfo *inner_rel;
get_join_variables(root, args, sjinfo,
&vardata1, &vardata2, &join_is_reversed);
nd1 = get_variable_numdistinct(&vardata1, &isdefault1);
nd2 = get_variable_numdistinct(&vardata2, &isdefault2);
opfuncoid = get_opcode(operator);
memset(&sslot1, 0, sizeof(sslot1));
memset(&sslot2, 0, sizeof(sslot2));
/*
* There is no use in fetching one side's MCVs if we lack MCVs for the
* other side, so do a quick check to verify that both stats exist.
*/
get_mcv_stats = (HeapTupleIsValid(vardata1.statsTuple) &&
HeapTupleIsValid(vardata2.statsTuple) &&
get_attstatsslot(&sslot1, vardata1.statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
0) &&
get_attstatsslot(&sslot2, vardata2.statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
0));
if (HeapTupleIsValid(vardata1.statsTuple))
{
/* note we allow use of nullfrac regardless of security check */
stats1 = (Form_pg_statistic) GETSTRUCT(vardata1.statsTuple);
if (get_mcv_stats &&
statistic_proc_security_check(&vardata1, opfuncoid))
have_mcvs1 = get_attstatsslot(&sslot1, vardata1.statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
}
if (HeapTupleIsValid(vardata2.statsTuple))
{
/* note we allow use of nullfrac regardless of security check */
stats2 = (Form_pg_statistic) GETSTRUCT(vardata2.statsTuple);
if (get_mcv_stats &&
statistic_proc_security_check(&vardata2, opfuncoid))
have_mcvs2 = get_attstatsslot(&sslot2, vardata2.statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
}
/* Prepare info usable by both eqjoinsel_inner and eqjoinsel_semi */
if (have_mcvs1 && have_mcvs2)
{
fmgr_info(opfuncoid, &eqproc);
hasmatch1 = (bool *) palloc0(sslot1.nvalues * sizeof(bool));
hasmatch2 = (bool *) palloc0(sslot2.nvalues * sizeof(bool));
/*
* If the MCV lists are long enough to justify hashing, try to look up
* hash functions for the join operator.
*/
if ((sslot1.nvalues + sslot2.nvalues) >= EQJOINSEL_MCV_HASH_THRESHOLD)
(void) get_op_hash_functions_ext(operator,
exprType((Node *) linitial(args)),
&hashLeft, &hashRight);
}
else
memset(&eqproc, 0, sizeof(eqproc)); /* silence uninit-var warnings */
/* We need to compute the inner-join selectivity in all cases */
selec_inner = eqjoinsel_inner(&eqproc, collation,
hashLeft, hashRight,
&vardata1, &vardata2,
nd1, nd2,
isdefault1, isdefault2,
&sslot1, &sslot2,
stats1, stats2,
have_mcvs1, have_mcvs2,
hasmatch1, hasmatch2,
&nmatches);
switch (sjinfo->jointype)
{
case JOIN_INNER:
case JOIN_LEFT:
case JOIN_FULL:
selec = selec_inner;
break;
case JOIN_SEMI:
case JOIN_ANTI:
/*
* Look up the join's inner relation. min_righthand is sufficient
* information because neither SEMI nor ANTI joins permit any
* reassociation into or out of their RHS, so the righthand will
* always be exactly that set of rels.
*/
inner_rel = find_join_input_rel(root, sjinfo->min_righthand);
if (!join_is_reversed)
selec = eqjoinsel_semi(&eqproc, collation,
hashLeft, hashRight,
false,
&vardata1, &vardata2,
nd1, nd2,
isdefault1, isdefault2,
&sslot1, &sslot2,
stats1, stats2,
have_mcvs1, have_mcvs2,
hasmatch1, hasmatch2,
&nmatches,
inner_rel);
else
selec = eqjoinsel_semi(&eqproc, collation,
hashLeft, hashRight,
true,
&vardata2, &vardata1,
nd2, nd1,
isdefault2, isdefault1,
&sslot2, &sslot1,
stats2, stats1,
have_mcvs2, have_mcvs1,
hasmatch2, hasmatch1,
&nmatches,
inner_rel);
/*
* We should never estimate the output of a semijoin to be more
* rows than we estimate for an inner join with the same input
* rels and join condition; it's obviously impossible for that to
* happen. The former estimate is N1 * Ssemi while the latter is
* N1 * N2 * Sinner, so we may clamp Ssemi <= N2 * Sinner. Doing
* this is worthwhile because of the shakier estimation rules we
* use in eqjoinsel_semi, particularly in cases where it has to
* punt entirely.
*/
selec = Min(selec, inner_rel->rows * selec_inner);
break;
default:
/* other values not expected here */
elog(ERROR, "unrecognized join type: %d",
(int) sjinfo->jointype);
selec = 0; /* keep compiler quiet */
break;
}
free_attstatsslot(&sslot1);
free_attstatsslot(&sslot2);
ReleaseVariableStats(vardata1);
ReleaseVariableStats(vardata2);
if (hasmatch1)
pfree(hasmatch1);
if (hasmatch2)
pfree(hasmatch2);
CLAMP_PROBABILITY(selec);
PG_RETURN_FLOAT8((float8) selec);
}
/*
* eqjoinsel_inner --- eqjoinsel for normal inner join
*
* In addition to computing the selectivity estimate, this will fill
* hasmatch1[], hasmatch2[], and *p_nmatches (if have_mcvs1 && have_mcvs2).
* We may be able to re-use that data in eqjoinsel_semi.
*
* We also use this for LEFT/FULL outer joins; it's not presently clear
* that it's worth trying to distinguish them here.
*/
static double
eqjoinsel_inner(FmgrInfo *eqproc, Oid collation,
Oid hashLeft, Oid hashRight,
VariableStatData *vardata1, VariableStatData *vardata2,
double nd1, double nd2,
bool isdefault1, bool isdefault2,
AttStatsSlot *sslot1, AttStatsSlot *sslot2,
Form_pg_statistic stats1, Form_pg_statistic stats2,
bool have_mcvs1, bool have_mcvs2,
bool *hasmatch1, bool *hasmatch2,
int *p_nmatches)
{
double selec;
if (have_mcvs1 && have_mcvs2)
{
/*
* We have most-common-value lists for both relations. Run through
* the lists to see which MCVs actually join to each other with the
* given operator. This allows us to determine the exact join
* selectivity for the portion of the relations represented by the MCV
* lists. We still have to estimate for the remaining population, but
* in a skewed distribution this gives us a big leg up in accuracy.
* For motivation see the analysis in Y. Ioannidis and S.
* Christodoulakis, "On the propagation of errors in the size of join
* results", Technical Report 1018, Computer Science Dept., University
* of Wisconsin, Madison, March 1991 (available from ftp.cs.wisc.edu).
*/
double nullfrac1 = stats1->stanullfrac;
double nullfrac2 = stats2->stanullfrac;
double matchprodfreq,
matchfreq1,
matchfreq2,
unmatchfreq1,
unmatchfreq2,
otherfreq1,
otherfreq2,
totalsel1,
totalsel2;
int i,
nmatches;
/* Fill the match arrays */
eqjoinsel_find_matches(eqproc, collation,
hashLeft, hashRight,
false,
sslot1, sslot2,
sslot1->nvalues, sslot2->nvalues,
hasmatch1, hasmatch2,
p_nmatches, &matchprodfreq);
nmatches = *p_nmatches;
CLAMP_PROBABILITY(matchprodfreq);
/* Sum up frequencies of matched and unmatched MCVs */
matchfreq1 = unmatchfreq1 = 0.0;
for (i = 0; i < sslot1->nvalues; i++)
{
if (hasmatch1[i])
matchfreq1 += sslot1->numbers[i];
else
unmatchfreq1 += sslot1->numbers[i];
}
CLAMP_PROBABILITY(matchfreq1);
CLAMP_PROBABILITY(unmatchfreq1);
matchfreq2 = unmatchfreq2 = 0.0;
for (i = 0; i < sslot2->nvalues; i++)
{
if (hasmatch2[i])
matchfreq2 += sslot2->numbers[i];
else
unmatchfreq2 += sslot2->numbers[i];
}
CLAMP_PROBABILITY(matchfreq2);
CLAMP_PROBABILITY(unmatchfreq2);
/*
* Compute total frequency of non-null values that are not in the MCV
* lists.
*/
otherfreq1 = 1.0 - nullfrac1 - matchfreq1 - unmatchfreq1;
otherfreq2 = 1.0 - nullfrac2 - matchfreq2 - unmatchfreq2;
CLAMP_PROBABILITY(otherfreq1);
CLAMP_PROBABILITY(otherfreq2);
/*
* We can estimate the total selectivity from the point of view of
* relation 1 as: the known selectivity for matched MCVs, plus
* unmatched MCVs that are assumed to match against random members of
* relation 2's non-MCV population, plus non-MCV values that are
* assumed to match against random members of relation 2's unmatched
* MCVs plus non-MCV values.
*/
totalsel1 = matchprodfreq;
if (nd2 > sslot2->nvalues)
totalsel1 += unmatchfreq1 * otherfreq2 / (nd2 - sslot2->nvalues);
if (nd2 > nmatches)
totalsel1 += otherfreq1 * (otherfreq2 + unmatchfreq2) /
(nd2 - nmatches);
/* Same estimate from the point of view of relation 2. */
totalsel2 = matchprodfreq;
if (nd1 > sslot1->nvalues)
totalsel2 += unmatchfreq2 * otherfreq1 / (nd1 - sslot1->nvalues);
if (nd1 > nmatches)
totalsel2 += otherfreq2 * (otherfreq1 + unmatchfreq1) /
(nd1 - nmatches);
/*
* Use the smaller of the two estimates. This can be justified in
* essentially the same terms as given below for the no-stats case: to
* a first approximation, we are estimating from the point of view of
* the relation with smaller nd.
*/
selec = (totalsel1 < totalsel2) ? totalsel1 : totalsel2;
}
else
{
/*
* We do not have MCV lists for both sides. Estimate the join
* selectivity as MIN(1/nd1,1/nd2)*(1-nullfrac1)*(1-nullfrac2). This
* is plausible if we assume that the join operator is strict and the
* non-null values are about equally distributed: a given non-null
* tuple of rel1 will join to either zero or N2*(1-nullfrac2)/nd2 rows
* of rel2, so total join rows are at most
* N1*(1-nullfrac1)*N2*(1-nullfrac2)/nd2 giving a join selectivity of
* not more than (1-nullfrac1)*(1-nullfrac2)/nd2. By the same logic it
* is not more than (1-nullfrac1)*(1-nullfrac2)/nd1, so the expression
* with MIN() is an upper bound. Using the MIN() means we estimate
* from the point of view of the relation with smaller nd (since the
* larger nd is determining the MIN). It is reasonable to assume that
* most tuples in this rel will have join partners, so the bound is
* probably reasonably tight and should be taken as-is.
*
* XXX Can we be smarter if we have an MCV list for just one side? It
* seems that if we assume equal distribution for the other side, we
* end up with the same answer anyway.
*/
double nullfrac1 = stats1 ? stats1->stanullfrac : 0.0;
double nullfrac2 = stats2 ? stats2->stanullfrac : 0.0;
selec = (1.0 - nullfrac1) * (1.0 - nullfrac2);
if (nd1 > nd2)
selec /= nd1;
else
selec /= nd2;
}
return selec;
}
/*
* eqjoinsel_semi --- eqjoinsel for semi join
*
* (Also used for anti join, which we are supposed to estimate the same way.)
* Caller has ensured that vardata1 is the LHS variable; however, eqproc
* is for the original join operator, which might now need to have the inputs
* swapped in order to apply correctly. Also, if have_mcvs1 && have_mcvs2
* then hasmatch1[], hasmatch2[], and *p_nmatches were filled by
* eqjoinsel_inner.
*/
static double
eqjoinsel_semi(FmgrInfo *eqproc, Oid collation,
Oid hashLeft, Oid hashRight,
bool op_is_reversed,
VariableStatData *vardata1, VariableStatData *vardata2,
double nd1, double nd2,
bool isdefault1, bool isdefault2,
AttStatsSlot *sslot1, AttStatsSlot *sslot2,
Form_pg_statistic stats1, Form_pg_statistic stats2,
bool have_mcvs1, bool have_mcvs2,
bool *hasmatch1, bool *hasmatch2,
int *p_nmatches,
RelOptInfo *inner_rel)
{
double selec;
/*
* We clamp nd2 to be not more than what we estimate the inner relation's
* size to be. This is intuitively somewhat reasonable since obviously
* there can't be more than that many distinct values coming from the
* inner rel. The reason for the asymmetry (ie, that we don't clamp nd1
* likewise) is that this is the only pathway by which restriction clauses
* applied to the inner rel will affect the join result size estimate,
* since set_joinrel_size_estimates will multiply SEMI/ANTI selectivity by
* only the outer rel's size. If we clamped nd1 we'd be double-counting
* the selectivity of outer-rel restrictions.
*
* We can apply this clamping both with respect to the base relation from
* which the join variable comes (if there is just one), and to the
* immediate inner input relation of the current join.
*
* If we clamp, we can treat nd2 as being a non-default estimate; it's not
* great, maybe, but it didn't come out of nowhere either. This is most
* helpful when the inner relation is empty and consequently has no stats.
*/
if (vardata2->rel)
{
if (nd2 >= vardata2->rel->rows)
{
nd2 = vardata2->rel->rows;
isdefault2 = false;
}
}
if (nd2 >= inner_rel->rows)
{
nd2 = inner_rel->rows;
isdefault2 = false;
}
if (have_mcvs1 && have_mcvs2)
{
/*
* We have most-common-value lists for both relations. Run through
* the lists to see which MCVs actually join to each other with the
* given operator. This allows us to determine the exact join
* selectivity for the portion of the relations represented by the MCV
* lists. We still have to estimate for the remaining population, but
* in a skewed distribution this gives us a big leg up in accuracy.
*/
double nullfrac1 = stats1->stanullfrac;
double matchprodfreq,
matchfreq1,
uncertainfrac,
uncertain;
int i,
nmatches,
clamped_nvalues2;
/*
* The clamping above could have resulted in nd2 being less than
* sslot2->nvalues; in which case, we assume that precisely the nd2
* most common values in the relation will appear in the join input,
* and so compare to only the first nd2 members of the MCV list. Of
* course this is frequently wrong, but it's the best bet we can make.
*/
clamped_nvalues2 = Min(sslot2->nvalues, nd2);
/*
* If we did not set clamped_nvalues2 to less than sslot2->nvalues,
* then the hasmatch1[] and hasmatch2[] match flags computed by
* eqjoinsel_inner are still perfectly applicable, so we need not
* re-do the matching work. Note that it does not matter if
* op_is_reversed: we'd get the same answers.
*
* If we did clamp, then a different set of sslot2 values is to be
* compared, so we have to re-do the matching.
*/
if (clamped_nvalues2 != sslot2->nvalues)
{
/* Must re-zero the arrays */
memset(hasmatch1, 0, sslot1->nvalues * sizeof(bool));
memset(hasmatch2, 0, clamped_nvalues2 * sizeof(bool));
/* Re-fill the match arrays */
eqjoinsel_find_matches(eqproc, collation,
hashLeft, hashRight,
op_is_reversed,
sslot1, sslot2,
sslot1->nvalues, clamped_nvalues2,
hasmatch1, hasmatch2,
p_nmatches, &matchprodfreq);
}
nmatches = *p_nmatches;
/* Sum up frequencies of matched MCVs */
matchfreq1 = 0.0;
for (i = 0; i < sslot1->nvalues; i++)
{
if (hasmatch1[i])
matchfreq1 += sslot1->numbers[i];
}
CLAMP_PROBABILITY(matchfreq1);
/*
* Now we need to estimate the fraction of relation 1 that has at
* least one join partner. We know for certain that the matched MCVs
* do, so that gives us a lower bound, but we're really in the dark
* about everything else. Our crude approach is: if nd1 <= nd2 then
* assume all non-null rel1 rows have join partners, else assume for
* the uncertain rows that a fraction nd2/nd1 have join partners. We
* can discount the known-matched MCVs from the distinct-values counts
* before doing the division.
*
* Crude as the above is, it's completely useless if we don't have
* reliable ndistinct values for both sides. Hence, if either nd1 or
* nd2 is default, punt and assume half of the uncertain rows have
* join partners.
*/
if (!isdefault1 && !isdefault2)
{
nd1 -= nmatches;
nd2 -= nmatches;
if (nd1 <= nd2 || nd2 < 0)
uncertainfrac = 1.0;
else
uncertainfrac = nd2 / nd1;
}
else
uncertainfrac = 0.5;
uncertain = 1.0 - matchfreq1 - nullfrac1;
CLAMP_PROBABILITY(uncertain);
selec = matchfreq1 + uncertainfrac * uncertain;
}
else
{
/*
* Without MCV lists for both sides, we can only use the heuristic
* about nd1 vs nd2.
*/
double nullfrac1 = stats1 ? stats1->stanullfrac : 0.0;
if (!isdefault1 && !isdefault2)
{
if (nd1 <= nd2 || nd2 < 0)
selec = 1.0 - nullfrac1;
else
selec = (nd2 / nd1) * (1.0 - nullfrac1);
}
else
selec = 0.5 * (1.0 - nullfrac1);
}
return selec;
}
/*
* Identify matching MCVs for eqjoinsel_inner or eqjoinsel_semi.
*
* Inputs:
* eqproc: FmgrInfo for equality function to use (might be reversed)
* collation: OID of collation to use
* hashLeft, hashRight: OIDs of hash functions associated with equality op,
* or InvalidOid if we're not to use hashing
* op_is_reversed: indicates that eqproc compares right type to left type
* sslot1, sslot2: MCV values for the lefthand and righthand inputs
* nvalues1, nvalues2: number of values to be considered (can be less than
* sslotN->nvalues, but not more)
* Outputs:
* hasmatch1[], hasmatch2[]: pre-zeroed arrays of lengths nvalues1, nvalues2;
* entries are set to true if that MCV has a match on the other side
* *p_nmatches: receives number of MCV pairs that match
* *p_matchprodfreq: receives sum(sslot1->numbers[i] * sslot2->numbers[j])
* for matching MCVs
*
* Note that hashLeft is for the eqproc's left-hand input type, hashRight
* for its right, regardless of op_is_reversed.
*
* Note we assume that each MCV will match at most one member of the other
* MCV list. If the operator isn't really equality, there could be multiple
* matches --- but we don't look for them, both for speed and because the
* math wouldn't add up...
*/
static void
eqjoinsel_find_matches(FmgrInfo *eqproc, Oid collation,
Oid hashLeft, Oid hashRight,
bool op_is_reversed,
AttStatsSlot *sslot1, AttStatsSlot *sslot2,
int nvalues1, int nvalues2,
bool *hasmatch1, bool *hasmatch2,
int *p_nmatches, double *p_matchprodfreq)
{
LOCAL_FCINFO(fcinfo, 2);
double matchprodfreq = 0.0;
int nmatches = 0;
/*
* Save a few cycles by setting up the fcinfo struct just once. Using
* FunctionCallInvoke directly also avoids failure if the eqproc returns
* NULL, though really equality functions should never do that.
*/
InitFunctionCallInfoData(*fcinfo, eqproc, 2, collation,
NULL, NULL);
fcinfo->args[0].isnull = false;
fcinfo->args[1].isnull = false;
if (OidIsValid(hashLeft) && OidIsValid(hashRight))
{
/* Use a hash table to speed up the matching */
LOCAL_FCINFO(hash_fcinfo, 1);
FmgrInfo hash_proc;
MCVHashContext hashContext;
MCVHashTable_hash *hashTable;
AttStatsSlot *statsProbe;
AttStatsSlot *statsHash;
bool *hasMatchProbe;
bool *hasMatchHash;
int nvaluesProbe;
int nvaluesHash;
/* Make sure we build the hash table on the smaller array. */
if (sslot1->nvalues >= sslot2->nvalues)
{
statsProbe = sslot1;
statsHash = sslot2;
hasMatchProbe = hasmatch1;
hasMatchHash = hasmatch2;
nvaluesProbe = nvalues1;
nvaluesHash = nvalues2;
}
else
{
/* We'll have to reverse the direction of use of the operator. */
op_is_reversed = !op_is_reversed;
statsProbe = sslot2;
statsHash = sslot1;
hasMatchProbe = hasmatch2;
hasMatchHash = hasmatch1;
nvaluesProbe = nvalues2;
nvaluesHash = nvalues1;
}
/*
* Build the hash table on the smaller array, using the appropriate
* hash function for its data type.
*/
fmgr_info(op_is_reversed ? hashLeft : hashRight, &hash_proc);
InitFunctionCallInfoData(*hash_fcinfo, &hash_proc, 1, collation,
NULL, NULL);
hash_fcinfo->args[0].isnull = false;
hashContext.equal_fcinfo = fcinfo;
hashContext.hash_fcinfo = hash_fcinfo;
hashContext.op_is_reversed = op_is_reversed;
hashContext.insert_mode = true;
get_typlenbyval(statsHash->valuetype,
&hashContext.hash_typlen,
&hashContext.hash_typbyval);
hashTable = MCVHashTable_create(CurrentMemoryContext,
nvaluesHash,
&hashContext);
for (int i = 0; i < nvaluesHash; i++)
{
bool found = false;
MCVHashEntry *entry = MCVHashTable_insert(hashTable,
statsHash->values[i],
&found);
/*
* MCVHashTable_insert will only report "found" if the new value
* is equal to some previous one per datum_image_eq(). That
* probably shouldn't happen, since we're not expecting duplicates
* in the MCV list. If we do find a dup, just ignore it, leaving
* the hash entry's index pointing at the first occurrence. That
* matches the behavior that the non-hashed code path would have.
*/
if (likely(!found))
entry->index = i;
}
/*
* Prepare to probe the hash table. If the probe values are of a
* different data type, then we need to change hash functions. (This
* code relies on the assumption that since we defined SH_STORE_HASH,
* simplehash.h will never need to compute hash values for existing
* hash table entries.)
*/
hashContext.insert_mode = false;
if (hashLeft != hashRight)
{
fmgr_info(op_is_reversed ? hashRight : hashLeft, &hash_proc);
/* Resetting hash_fcinfo is probably unnecessary, but be safe */
InitFunctionCallInfoData(*hash_fcinfo, &hash_proc, 1, collation,
NULL, NULL);
hash_fcinfo->args[0].isnull = false;
}
/* Look up each probe value in turn. */
for (int i = 0; i < nvaluesProbe; i++)
{
MCVHashEntry *entry = MCVHashTable_lookup(hashTable,
statsProbe->values[i]);
/* As in the other code path, skip already-matched hash entries */
if (entry != NULL && !hasMatchHash[entry->index])
{
hasMatchHash[entry->index] = hasMatchProbe[i] = true;
nmatches++;
matchprodfreq += statsHash->numbers[entry->index] * statsProbe->numbers[i];
}
}
MCVHashTable_destroy(hashTable);
}
else
{
/* We're not to use hashing, so do it the O(N^2) way */
int index1,
index2;
/* Set up to supply the values in the order the operator expects */
if (op_is_reversed)
{
index1 = 1;
index2 = 0;
}
else
{
index1 = 0;
index2 = 1;
}
for (int i = 0; i < nvalues1; i++)
{
fcinfo->args[index1].value = sslot1->values[i];
for (int j = 0; j < nvalues2; j++)
{
Datum fresult;
if (hasmatch2[j])
continue;
fcinfo->args[index2].value = sslot2->values[j];
fcinfo->isnull = false;
fresult = FunctionCallInvoke(fcinfo);
if (!fcinfo->isnull && DatumGetBool(fresult))
{
hasmatch1[i] = hasmatch2[j] = true;
matchprodfreq += sslot1->numbers[i] * sslot2->numbers[j];
nmatches++;
break;
}
}
}
}
*p_nmatches = nmatches;
*p_matchprodfreq = matchprodfreq;
}
/*
* Support functions for the hash tables used by eqjoinsel_find_matches
*/
static uint32
hash_mcv(MCVHashTable_hash *tab, Datum key)
{
MCVHashContext *context = (MCVHashContext *) tab->private_data;
FunctionCallInfo fcinfo = context->hash_fcinfo;
Datum fresult;
fcinfo->args[0].value = key;
fcinfo->isnull = false;
fresult = FunctionCallInvoke(fcinfo);
Assert(!fcinfo->isnull);
return DatumGetUInt32(fresult);
}
static bool
mcvs_equal(MCVHashTable_hash *tab, Datum key0, Datum key1)
{
MCVHashContext *context = (MCVHashContext *) tab->private_data;
if (context->insert_mode)
{
/*
* During the insertion step, any comparisons will be between two
* Datums of the hash table's data type, so if the given operator is
* cross-type it will be the wrong thing to use. Fortunately, we can
* use datum_image_eq instead. The MCV values should all be distinct
* anyway, so it's mostly pro-forma to compare them at all.
*/
return datum_image_eq(key0, key1,
context->hash_typbyval, context->hash_typlen);
}
else
{
FunctionCallInfo fcinfo = context->equal_fcinfo;
Datum fresult;
/*
* Apply the operator the correct way around. Although simplehash.h
* doesn't document this explicitly, during lookups key0 is from the
* hash table while key1 is the probe value, so we should compare them
* in that order only if op_is_reversed.
*/
if (context->op_is_reversed)
{
fcinfo->args[0].value = key0;
fcinfo->args[1].value = key1;
}
else
{
fcinfo->args[0].value = key1;
fcinfo->args[1].value = key0;
}
fcinfo->isnull = false;
fresult = FunctionCallInvoke(fcinfo);
return (!fcinfo->isnull && DatumGetBool(fresult));
}
}
/*
* neqjoinsel - Join selectivity of "!="
*/
Datum
neqjoinsel(PG_FUNCTION_ARGS)
{
PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
Oid operator = PG_GETARG_OID(1);
List *args = (List *) PG_GETARG_POINTER(2);
JoinType jointype = (JoinType) PG_GETARG_INT16(3);
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4);
Oid collation = PG_GET_COLLATION();
float8 result;
if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
{
/*
* For semi-joins, if there is more than one distinct value in the RHS
* relation then every non-null LHS row must find a row to join since
* it can only be equal to one of them. We'll assume that there is
* always more than one distinct RHS value for the sake of stability,
* though in theory we could have special cases for empty RHS
* (selectivity = 0) and single-distinct-value RHS (selectivity =
* fraction of LHS that has the same value as the single RHS value).
*
* For anti-joins, if we use the same assumption that there is more
* than one distinct key in the RHS relation, then every non-null LHS
* row must be suppressed by the anti-join.
*
* So either way, the selectivity estimate should be 1 - nullfrac.
*/
VariableStatData leftvar;
VariableStatData rightvar;
bool reversed;
HeapTuple statsTuple;
double nullfrac;
get_join_variables(root, args, sjinfo, &leftvar, &rightvar, &reversed);
statsTuple = reversed ? rightvar.statsTuple : leftvar.statsTuple;
if (HeapTupleIsValid(statsTuple))
nullfrac = ((Form_pg_statistic) GETSTRUCT(statsTuple))->stanullfrac;
else
nullfrac = 0.0;
ReleaseVariableStats(leftvar);
ReleaseVariableStats(rightvar);
result = 1.0 - nullfrac;
}
else
{
/*
* We want 1 - eqjoinsel() where the equality operator is the one
* associated with this != operator, that is, its negator.
*/
Oid eqop = get_negator(operator);
if (eqop)
{
result =
DatumGetFloat8(DirectFunctionCall5Coll(eqjoinsel,
collation,
PointerGetDatum(root),
ObjectIdGetDatum(eqop),
PointerGetDatum(args),
Int16GetDatum(jointype),
PointerGetDatum(sjinfo)));
}
else
{
/* Use default selectivity (should we raise an error instead?) */
result = DEFAULT_EQ_SEL;
}
result = 1.0 - result;
}
PG_RETURN_FLOAT8(result);
}
/*
* scalarltjoinsel - Join selectivity of "<" for scalars
*/
Datum
scalarltjoinsel(PG_FUNCTION_ARGS)
{
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
}
/*
* scalarlejoinsel - Join selectivity of "<=" for scalars
*/
Datum
scalarlejoinsel(PG_FUNCTION_ARGS)
{
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
}
/*
* scalargtjoinsel - Join selectivity of ">" for scalars
*/
Datum
scalargtjoinsel(PG_FUNCTION_ARGS)
{
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
}
/*
* scalargejoinsel - Join selectivity of ">=" for scalars
*/
Datum
scalargejoinsel(PG_FUNCTION_ARGS)
{
PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
}
/*
* mergejoinscansel - Scan selectivity of merge join.
*
* A merge join will stop as soon as it exhausts either input stream.
* Therefore, if we can estimate the ranges of both input variables,
* we can estimate how much of the input will actually be read. This
* can have a considerable impact on the cost when using indexscans.
*
* Also, we can estimate how much of each input has to be read before the
* first join pair is found, which will affect the join's startup time.
*
* clause should be a clause already known to be mergejoinable. opfamily,
* cmptype, and nulls_first specify the sort ordering being used.
*
* The outputs are:
* *leftstart is set to the fraction of the left-hand variable expected
* to be scanned before the first join pair is found (0 to 1).
* *leftend is set to the fraction of the left-hand variable expected
* to be scanned before the join terminates (0 to 1).
* *rightstart, *rightend similarly for the right-hand variable.
*/
void
mergejoinscansel(PlannerInfo *root, Node *clause,
Oid opfamily, CompareType cmptype, bool nulls_first,
Selectivity *leftstart, Selectivity *leftend,
Selectivity *rightstart, Selectivity *rightend)
{
Node *left,
*right;
VariableStatData leftvar,
rightvar;
Oid opmethod;
int op_strategy;
Oid op_lefttype;
Oid op_righttype;
Oid opno,
collation,
lsortop,
rsortop,
lstatop,
rstatop,
ltop,
leop,
revltop,
revleop;
StrategyNumber ltstrat,
lestrat,
gtstrat,
gestrat;
bool isgt;
Datum leftmin,
leftmax,
rightmin,
rightmax;
double selec;
/* Set default results if we can't figure anything out. */
/* XXX should default "start" fraction be a bit more than 0? */
*leftstart = *rightstart = 0.0;
*leftend = *rightend = 1.0;
/* Deconstruct the merge clause */
if (!is_opclause(clause))
return; /* shouldn't happen */
opno = ((OpExpr *) clause)->opno;
collation = ((OpExpr *) clause)->inputcollid;
left = get_leftop((Expr *) clause);
right = get_rightop((Expr *) clause);
if (!right)
return; /* shouldn't happen */
/* Look for stats for the inputs */
examine_variable(root, left, 0, &leftvar);
examine_variable(root, right, 0, &rightvar);
opmethod = get_opfamily_method(opfamily);
/* Extract the operator's declared left/right datatypes */
get_op_opfamily_properties(opno, opfamily, false,
&op_strategy,
&op_lefttype,
&op_righttype);
Assert(IndexAmTranslateStrategy(op_strategy, opmethod, opfamily, true) == COMPARE_EQ);
/*
* Look up the various operators we need. If we don't find them all, it
* probably means the opfamily is broken, but we just fail silently.
*
* Note: we expect that pg_statistic histograms will be sorted by the '<'
* operator, regardless of which sort direction we are considering.
*/
switch (cmptype)
{
case COMPARE_LT:
isgt = false;
ltstrat = IndexAmTranslateCompareType(COMPARE_LT, opmethod, opfamily, true);
lestrat = IndexAmTranslateCompareType(COMPARE_LE, opmethod, opfamily, true);
if (op_lefttype == op_righttype)
{
/* easy case */
ltop = get_opfamily_member(opfamily,
op_lefttype, op_righttype,
ltstrat);
leop = get_opfamily_member(opfamily,
op_lefttype, op_righttype,
lestrat);
lsortop = ltop;
rsortop = ltop;
lstatop = lsortop;
rstatop = rsortop;
revltop = ltop;
revleop = leop;
}
else
{
ltop = get_opfamily_member(opfamily,
op_lefttype, op_righttype,
ltstrat);
leop = get_opfamily_member(opfamily,
op_lefttype, op_righttype,
lestrat);
lsortop = get_opfamily_member(opfamily,
op_lefttype, op_lefttype,
ltstrat);
rsortop = get_opfamily_member(opfamily,
op_righttype, op_righttype,
ltstrat);
lstatop = lsortop;
rstatop = rsortop;
revltop = get_opfamily_member(opfamily,
op_righttype, op_lefttype,
ltstrat);
revleop = get_opfamily_member(opfamily,
op_righttype, op_lefttype,
lestrat);
}
break;
case COMPARE_GT:
/* descending-order case */
isgt = true;
ltstrat = IndexAmTranslateCompareType(COMPARE_LT, opmethod, opfamily, true);
gtstrat = IndexAmTranslateCompareType(COMPARE_GT, opmethod, opfamily, true);
gestrat = IndexAmTranslateCompareType(COMPARE_GE, opmethod, opfamily, true);
if (op_lefttype == op_righttype)
{
/* easy case */
ltop = get_opfamily_member(opfamily,
op_lefttype, op_righttype,
gtstrat);
leop = get_opfamily_member(opfamily,
op_lefttype, op_righttype,
gestrat);
lsortop = ltop;
rsortop = ltop;
lstatop = get_opfamily_member(opfamily,
op_lefttype, op_lefttype,
ltstrat);
rstatop = lstatop;
revltop = ltop;
revleop = leop;
}
else
{
ltop = get_opfamily_member(opfamily,
op_lefttype, op_righttype,
gtstrat);
leop = get_opfamily_member(opfamily,
op_lefttype, op_righttype,
gestrat);
lsortop = get_opfamily_member(opfamily,
op_lefttype, op_lefttype,
gtstrat);
rsortop = get_opfamily_member(opfamily,
op_righttype, op_righttype,
gtstrat);
lstatop = get_opfamily_member(opfamily,
op_lefttype, op_lefttype,
ltstrat);
rstatop = get_opfamily_member(opfamily,
op_righttype, op_righttype,
ltstrat);
revltop = get_opfamily_member(opfamily,
op_righttype, op_lefttype,
gtstrat);
revleop = get_opfamily_member(opfamily,
op_righttype, op_lefttype,
gestrat);
}
break;
default:
goto fail; /* shouldn't get here */
}
if (!OidIsValid(lsortop) ||
!OidIsValid(rsortop) ||
!OidIsValid(lstatop) ||
!OidIsValid(rstatop) ||
!OidIsValid(ltop) ||
!OidIsValid(leop) ||
!OidIsValid(revltop) ||
!OidIsValid(revleop))
goto fail; /* insufficient info in catalogs */
/* Try to get ranges of both inputs */
if (!isgt)
{
if (!get_variable_range(root, &leftvar, lstatop, collation,
&leftmin, &leftmax))
goto fail; /* no range available from stats */
if (!get_variable_range(root, &rightvar, rstatop, collation,
&rightmin, &rightmax))
goto fail; /* no range available from stats */
}
else
{
/* need to swap the max and min */
if (!get_variable_range(root, &leftvar, lstatop, collation,
&leftmax, &leftmin))
goto fail; /* no range available from stats */
if (!get_variable_range(root, &rightvar, rstatop, collation,
&rightmax, &rightmin))
goto fail; /* no range available from stats */
}
/*
* Now, the fraction of the left variable that will be scanned is the
* fraction that's <= the right-side maximum value. But only believe
* non-default estimates, else stick with our 1.0.
*/
selec = scalarineqsel(root, leop, isgt, true, collation, &leftvar,
rightmax, op_righttype);
if (selec != DEFAULT_INEQ_SEL)
*leftend = selec;
/* And similarly for the right variable. */
selec = scalarineqsel(root, revleop, isgt, true, collation, &rightvar,
leftmax, op_lefttype);
if (selec != DEFAULT_INEQ_SEL)
*rightend = selec;
/*
* Only one of the two "end" fractions can really be less than 1.0;
* believe the smaller estimate and reset the other one to exactly 1.0. If
* we get exactly equal estimates (as can easily happen with self-joins),
* believe neither.
*/
if (*leftend > *rightend)
*leftend = 1.0;
else if (*leftend < *rightend)
*rightend = 1.0;
else
*leftend = *rightend = 1.0;
/*
* Also, the fraction of the left variable that will be scanned before the
* first join pair is found is the fraction that's < the right-side
* minimum value. But only believe non-default estimates, else stick with
* our own default.
*/
selec = scalarineqsel(root, ltop, isgt, false, collation, &leftvar,
rightmin, op_righttype);
if (selec != DEFAULT_INEQ_SEL)
*leftstart = selec;
/* And similarly for the right variable. */
selec = scalarineqsel(root, revltop, isgt, false, collation, &rightvar,
leftmin, op_lefttype);
if (selec != DEFAULT_INEQ_SEL)
*rightstart = selec;
/*
* Only one of the two "start" fractions can really be more than zero;
* believe the larger estimate and reset the other one to exactly 0.0. If
* we get exactly equal estimates (as can easily happen with self-joins),
* believe neither.
*/
if (*leftstart < *rightstart)
*leftstart = 0.0;
else if (*leftstart > *rightstart)
*rightstart = 0.0;
else
*leftstart = *rightstart = 0.0;
/*
* If the sort order is nulls-first, we're going to have to skip over any
* nulls too. These would not have been counted by scalarineqsel, and we
* can safely add in this fraction regardless of whether we believe
* scalarineqsel's results or not. But be sure to clamp the sum to 1.0!
*/
if (nulls_first)
{
Form_pg_statistic stats;
if (HeapTupleIsValid(leftvar.statsTuple))
{
stats = (Form_pg_statistic) GETSTRUCT(leftvar.statsTuple);
*leftstart += stats->stanullfrac;
CLAMP_PROBABILITY(*leftstart);
*leftend += stats->stanullfrac;
CLAMP_PROBABILITY(*leftend);
}
if (HeapTupleIsValid(rightvar.statsTuple))
{
stats = (Form_pg_statistic) GETSTRUCT(rightvar.statsTuple);
*rightstart += stats->stanullfrac;
CLAMP_PROBABILITY(*rightstart);
*rightend += stats->stanullfrac;
CLAMP_PROBABILITY(*rightend);
}
}
/* Disbelieve start >= end, just in case that can happen */
if (*leftstart >= *leftend)
{
*leftstart = 0.0;
*leftend = 1.0;
}
if (*rightstart >= *rightend)
{
*rightstart = 0.0;
*rightend = 1.0;
}
fail:
ReleaseVariableStats(leftvar);
ReleaseVariableStats(rightvar);
}
/*
* matchingsel -- generic matching-operator selectivity support
*
* Use these for any operators that (a) are on data types for which we collect
* standard statistics, and (b) have behavior for which the default estimate
* (twice DEFAULT_EQ_SEL) is sane. Typically that is good for match-like
* operators.
*/
Datum
matchingsel(PG_FUNCTION_ARGS)
{
PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
Oid operator = PG_GETARG_OID(1);
List *args = (List *) PG_GETARG_POINTER(2);
int varRelid = PG_GETARG_INT32(3);
Oid collation = PG_GET_COLLATION();
double selec;
/* Use generic restriction selectivity logic. */
selec = generic_restriction_selectivity(root, operator, collation,
args, varRelid,
DEFAULT_MATCHING_SEL);
PG_RETURN_FLOAT8((float8) selec);
}
Datum
matchingjoinsel(PG_FUNCTION_ARGS)
{
/* Just punt, for the moment. */
PG_RETURN_FLOAT8(DEFAULT_MATCHING_SEL);
}
/*
* Helper routine for estimate_num_groups: add an item to a list of
* GroupVarInfos, but only if it's not known equal to any of the existing
* entries.
*/
typedef struct
{
Node *var; /* might be an expression, not just a Var */
RelOptInfo *rel; /* relation it belongs to */
double ndistinct; /* # distinct values */
bool isdefault; /* true if DEFAULT_NUM_DISTINCT was used */
} GroupVarInfo;
static List *
add_unique_group_var(PlannerInfo *root, List *varinfos,
Node *var, VariableStatData *vardata)
{
GroupVarInfo *varinfo;
double ndistinct;
bool isdefault;
ListCell *lc;
ndistinct = get_variable_numdistinct(vardata, &isdefault);
/*
* The nullingrels bits within the var could cause the same var to be
* counted multiple times if it's marked with different nullingrels. They
* could also prevent us from matching the var to the expressions in
* extended statistics (see estimate_multivariate_ndistinct). So strip
* them out first.
*/
var = remove_nulling_relids(var, root->outer_join_rels, NULL);
foreach(lc, varinfos)
{
varinfo = (GroupVarInfo *) lfirst(lc);
/* Drop exact duplicates */
if (equal(var, varinfo->var))
return varinfos;
/*
* Drop known-equal vars, but only if they belong to different
* relations (see comments for estimate_num_groups). We aren't too
* fussy about the semantics of "equal" here.
*/
if (vardata->rel != varinfo->rel &&
exprs_known_equal(root, var, varinfo->var, InvalidOid))
{
if (varinfo->ndistinct <= ndistinct)
{
/* Keep older item, forget new one */
return varinfos;
}
else
{
/* Delete the older item */
varinfos = foreach_delete_current(varinfos, lc);
}
}
}
varinfo = palloc_object(GroupVarInfo);
varinfo->var = var;
varinfo->rel = vardata->rel;
varinfo->ndistinct = ndistinct;
varinfo->isdefault = isdefault;
varinfos = lappend(varinfos, varinfo);
return varinfos;
}
/*
* estimate_num_groups - Estimate number of groups in a grouped query
*
* Given a query having a GROUP BY clause, estimate how many groups there
* will be --- ie, the number of distinct combinations of the GROUP BY
* expressions.
*
* This routine is also used to estimate the number of rows emitted by
* a DISTINCT filtering step; that is an isomorphic problem. (Note:
* actually, we only use it for DISTINCT when there's no grouping or
* aggregation ahead of the DISTINCT.)
*
* Inputs:
* root - the query
* groupExprs - list of expressions being grouped by
* input_rows - number of rows estimated to arrive at the group/unique
* filter step
* pgset - NULL, or a List** pointing to a grouping set to filter the
* groupExprs against
*
* Outputs:
* estinfo - When passed as non-NULL, the function will set bits in the
* "flags" field in order to provide callers with additional information
* about the estimation. Currently, we only set the SELFLAG_USED_DEFAULT
* bit if we used any default values in the estimation.
*
* Given the lack of any cross-correlation statistics in the system, it's
* impossible to do anything really trustworthy with GROUP BY conditions
* involving multiple Vars. We should however avoid assuming the worst
* case (all possible cross-product terms actually appear as groups) since
* very often the grouped-by Vars are highly correlated. Our current approach
* is as follows:
* 1. Expressions yielding boolean are assumed to contribute two groups,
* independently of their content, and are ignored in the subsequent
* steps. This is mainly because tests like "col IS NULL" break the
* heuristic used in step 2 especially badly.
* 2. Reduce the given expressions to a list of unique Vars used. For
* example, GROUP BY a, a + b is treated the same as GROUP BY a, b.
* It is clearly correct not to count the same Var more than once.
* It is also reasonable to treat f(x) the same as x: f() cannot
* increase the number of distinct values (unless it is volatile,
* which we consider unlikely for grouping), but it probably won't
* reduce the number of distinct values much either.
* As a special case, if a GROUP BY expression can be matched to an
* expressional index for which we have statistics, then we treat the
* whole expression as though it were just a Var.
* 3. If the list contains Vars of different relations that are known equal
* due to equivalence classes, then drop all but one of the Vars from each
* known-equal set, keeping the one with smallest estimated # of values
* (since the extra values of the others can't appear in joined rows).
* Note the reason we only consider Vars of different relations is that
* if we considered ones of the same rel, we'd be double-counting the
* restriction selectivity of the equality in the next step.
* 4. For Vars within a single source rel, we multiply together the numbers
* of values, clamp to the number of rows in the rel (divided by 10 if
* more than one Var), and then multiply by a factor based on the
* selectivity of the restriction clauses for that rel. When there's
* more than one Var, the initial product is probably too high (it's the
* worst case) but clamping to a fraction of the rel's rows seems to be a
* helpful heuristic for not letting the estimate get out of hand. (The
* factor of 10 is derived from pre-Postgres-7.4 practice.) The factor
* we multiply by to adjust for the restriction selectivity assumes that
* the restriction clauses are independent of the grouping, which may not
* be a valid assumption, but it's hard to do better.
* 5. If there are Vars from multiple rels, we repeat step 4 for each such
* rel, and multiply the results together.
* Note that rels not containing grouped Vars are ignored completely, as are
* join clauses. Such rels cannot increase the number of groups, and we
* assume such clauses do not reduce the number either (somewhat bogus,
* but we don't have the info to do better).
*/
double
estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
List **pgset, EstimationInfo *estinfo)
{
List *varinfos = NIL;
double srf_multiplier = 1.0;
double numdistinct;
ListCell *l;
int i;
/* Zero the estinfo output parameter, if non-NULL */
if (estinfo != NULL)
memset(estinfo, 0, sizeof(EstimationInfo));
/*
* We don't ever want to return an estimate of zero groups, as that tends
* to lead to division-by-zero and other unpleasantness. The input_rows
* estimate is usually already at least 1, but clamp it just in case it
* isn't.
*/
input_rows = clamp_row_est(input_rows);
/*
* If no grouping columns, there's exactly one group. (This can't happen
* for normal cases with GROUP BY or DISTINCT, but it is possible for
* corner cases with set operations.)
*/
if (groupExprs == NIL || (pgset && *pgset == NIL))
return 1.0;
/*
* Count groups derived from boolean grouping expressions. For other
* expressions, find the unique Vars used, treating an expression as a Var
* if we can find stats for it. For each one, record the statistical
* estimate of number of distinct values (total in its table, without
* regard for filtering).
*/
numdistinct = 1.0;
i = 0;
foreach(l, groupExprs)
{
Node *groupexpr = (Node *) lfirst(l);
double this_srf_multiplier;
VariableStatData vardata;
List *varshere;
ListCell *l2;
/* is expression in this grouping set? */
if (pgset && !list_member_int(*pgset, i++))
continue;
/*
* Set-returning functions in grouping columns are a bit problematic.
* The code below will effectively ignore their SRF nature and come up
* with a numdistinct estimate as though they were scalar functions.
* We compensate by scaling up the end result by the largest SRF
* rowcount estimate. (This will be an overestimate if the SRF
* produces multiple copies of any output value, but it seems best to
* assume the SRF's outputs are distinct. In any case, it's probably
* pointless to worry too much about this without much better
* estimates for SRF output rowcounts than we have today.)
*/
this_srf_multiplier = expression_returns_set_rows(root, groupexpr);
if (srf_multiplier < this_srf_multiplier)
srf_multiplier = this_srf_multiplier;
/* Short-circuit for expressions returning boolean */
if (exprType(groupexpr) == BOOLOID)
{
numdistinct *= 2.0;
continue;
}
/*
* If examine_variable is able to deduce anything about the GROUP BY
* expression, treat it as a single variable even if it's really more
* complicated.
*
* XXX This has the consequence that if there's a statistics object on
* the expression, we don't split it into individual Vars. This
* affects our selection of statistics in
* estimate_multivariate_ndistinct, because it's probably better to
* use more accurate estimate for each expression and treat them as
* independent, than to combine estimates for the extracted variables
* when we don't know how that relates to the expressions.
*/
examine_variable(root, groupexpr, 0, &vardata);
if (HeapTupleIsValid(vardata.statsTuple) || vardata.isunique)
{
varinfos = add_unique_group_var(root, varinfos,
groupexpr, &vardata);
ReleaseVariableStats(vardata);
continue;
}
ReleaseVariableStats(vardata);
/*
* Else pull out the component Vars. Handle PlaceHolderVars by
* recursing into their arguments (effectively assuming that the
* PlaceHolderVar doesn't change the number of groups, which boils
* down to ignoring the possible addition of nulls to the result set).
*/
varshere = pull_var_clause(groupexpr,
PVC_RECURSE_AGGREGATES |
PVC_RECURSE_WINDOWFUNCS |
PVC_RECURSE_PLACEHOLDERS);
/*
* If we find any variable-free GROUP BY item, then either it is a
* constant (and we can ignore it) or it contains a volatile function;
* in the latter case we punt and assume that each input row will
* yield a distinct group.
*/
if (varshere == NIL)
{
if (contain_volatile_functions(groupexpr))
return input_rows;
continue;
}
/*
* Else add variables to varinfos list
*/
foreach(l2, varshere)
{
Node *var = (Node *) lfirst(l2);
examine_variable(root, var, 0, &vardata);
varinfos = add_unique_group_var(root, varinfos, var, &vardata);
ReleaseVariableStats(vardata);
}
}
/*
* If now no Vars, we must have an all-constant or all-boolean GROUP BY
* list.
*/
if (varinfos == NIL)
{
/* Apply SRF multiplier as we would do in the long path */
numdistinct *= srf_multiplier;
/* Round off */
numdistinct = ceil(numdistinct);
/* Guard against out-of-range answers */
if (numdistinct > input_rows)
numdistinct = input_rows;
if (numdistinct < 1.0)
numdistinct = 1.0;
return numdistinct;
}
/*
* Group Vars by relation and estimate total numdistinct.
*
* For each iteration of the outer loop, we process the frontmost Var in
* varinfos, plus all other Vars in the same relation. We remove these
* Vars from the newvarinfos list for the next iteration. This is the
* easiest way to group Vars of same rel together.
*/
do
{
GroupVarInfo *varinfo1 = (GroupVarInfo *) linitial(varinfos);
RelOptInfo *rel = varinfo1->rel;
double reldistinct = 1;
double relmaxndistinct = reldistinct;
int relvarcount = 0;
List *newvarinfos = NIL;
List *relvarinfos = NIL;
/*
* Split the list of varinfos in two - one for the current rel, one
* for remaining Vars on other rels.
*/
relvarinfos = lappend(relvarinfos, varinfo1);
for_each_from(l, varinfos, 1)
{
GroupVarInfo *varinfo2 = (GroupVarInfo *) lfirst(l);
if (varinfo2->rel == varinfo1->rel)
{
/* varinfos on current rel */
relvarinfos = lappend(relvarinfos, varinfo2);
}
else
{
/* not time to process varinfo2 yet */
newvarinfos = lappend(newvarinfos, varinfo2);
}
}
/*
* Get the numdistinct estimate for the Vars of this rel. We
* iteratively search for multivariate n-distinct with maximum number
* of vars; assuming that each var group is independent of the others,
* we multiply them together. Any remaining relvarinfos after no more
* multivariate matches are found are assumed independent too, so
* their individual ndistinct estimates are multiplied also.
*
* While iterating, count how many separate numdistinct values we
* apply. We apply a fudge factor below, but only if we multiplied
* more than one such values.
*/
while (relvarinfos)
{
double mvndistinct;
if (estimate_multivariate_ndistinct(root, rel, &relvarinfos,
&mvndistinct))
{
reldistinct *= mvndistinct;
if (relmaxndistinct < mvndistinct)
relmaxndistinct = mvndistinct;
relvarcount++;
}
else
{
foreach(l, relvarinfos)
{
GroupVarInfo *varinfo2 = (GroupVarInfo *) lfirst(l);
reldistinct *= varinfo2->ndistinct;
if (relmaxndistinct < varinfo2->ndistinct)
relmaxndistinct = varinfo2->ndistinct;
relvarcount++;
/*
* When varinfo2's isdefault is set then we'd better set
* the SELFLAG_USED_DEFAULT bit in the EstimationInfo.
*/
if (estinfo != NULL && varinfo2->isdefault)
estinfo->flags |= SELFLAG_USED_DEFAULT;
}
/* we're done with this relation */
relvarinfos = NIL;
}
}
/*
* Sanity check --- don't divide by zero if empty relation.
*/
Assert(IS_SIMPLE_REL(rel));
if (rel->tuples > 0)
{
/*
* Clamp to size of rel, or size of rel / 10 if multiple Vars. The
* fudge factor is because the Vars are probably correlated but we
* don't know by how much. We should never clamp to less than the
* largest ndistinct value for any of the Vars, though, since
* there will surely be at least that many groups.
*/
double clamp = rel->tuples;
if (relvarcount > 1)
{
clamp *= 0.1;
if (clamp < relmaxndistinct)
{
clamp = relmaxndistinct;
/* for sanity in case some ndistinct is too large: */
if (clamp > rel->tuples)
clamp = rel->tuples;
}
}
if (reldistinct > clamp)
reldistinct = clamp;
/*
* Update the estimate based on the restriction selectivity,
* guarding against division by zero when reldistinct is zero.
* Also skip this if we know that we are returning all rows.
*/
if (reldistinct > 0 && rel->rows < rel->tuples)
{
/*
* Given a table containing N rows with n distinct values in a
* uniform distribution, if we select p rows at random then
* the expected number of distinct values selected is
*
* n * (1 - product((N-N/n-i)/(N-i), i=0..p-1))
*
* = n * (1 - (N-N/n)! / (N-N/n-p)! * (N-p)! / N!)
*
* See "Approximating block accesses in database
* organizations", S. B. Yao, Communications of the ACM,
* Volume 20 Issue 4, April 1977 Pages 260-261.
*
* Alternatively, re-arranging the terms from the factorials,
* this may be written as
*
* n * (1 - product((N-p-i)/(N-i), i=0..N/n-1))
*
* This form of the formula is more efficient to compute in
* the common case where p is larger than N/n. Additionally,
* as pointed out by Dell'Era, if i << N for all terms in the
* product, it can be approximated by
*
* n * (1 - ((N-p)/N)^(N/n))
*
* See "Expected distinct values when selecting from a bag
* without replacement", Alberto Dell'Era,
* http://www.adellera.it/investigations/distinct_balls/.
*
* The condition i << N is equivalent to n >> 1, so this is a
* good approximation when the number of distinct values in
* the table is large. It turns out that this formula also
* works well even when n is small.
*/
reldistinct *=
(1 - pow((rel->tuples - rel->rows) / rel->tuples,
rel->tuples / reldistinct));
}
reldistinct = clamp_row_est(reldistinct);
/*
* Update estimate of total distinct groups.
*/
numdistinct *= reldistinct;
}
varinfos = newvarinfos;
} while (varinfos != NIL);
/* Now we can account for the effects of any SRFs */
numdistinct *= srf_multiplier;
/* Round off */
numdistinct = ceil(numdistinct);
/* Guard against out-of-range answers */
if (numdistinct > input_rows)
numdistinct = input_rows;
if (numdistinct < 1.0)
numdistinct = 1.0;
return numdistinct;
}
/*
* Try to estimate the bucket size of the hash join inner side when the join
* condition contains two or more clauses by employing extended statistics.
*
* The main idea of this approach is that the distinct value generated by
* multivariate estimation on two or more columns would provide less bucket size
* than estimation on one separate column.
*
* IMPORTANT: It is crucial to synchronize the approach of combining different
* estimations with the caller's method.
*
* Return a list of clauses that didn't fetch any extended statistics.
*/
List *
estimate_multivariate_bucketsize(PlannerInfo *root, RelOptInfo *inner,
List *hashclauses,
Selectivity *innerbucketsize)
{
List *clauses;
List *otherclauses;
double ndistinct;
if (list_length(hashclauses) <= 1)
{
/*
* Nothing to do for a single clause. Could we employ univariate
* extended stat here?
*/
return hashclauses;
}
/* "clauses" is the list of hashclauses we've not dealt with yet */
clauses = list_copy(hashclauses);
/* "otherclauses" holds clauses we are going to return to caller */
otherclauses = NIL;
/* current estimate of ndistinct */
ndistinct = 1.0;
while (clauses != NIL)
{
ListCell *lc;
int relid = -1;
List *varinfos = NIL;
List *origin_rinfos = NIL;
double mvndistinct;
List *origin_varinfos;
int group_relid = -1;
RelOptInfo *group_rel = NULL;
ListCell *lc1,
*lc2;
/*
* Find clauses, referencing the same single base relation and try to
* estimate such a group with extended statistics. Create varinfo for
* an approved clause, push it to otherclauses, if it can't be
* estimated here or ignore to process at the next iteration.
*/
foreach(lc, clauses)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
Node *expr;
Relids relids;
GroupVarInfo *varinfo;
/*
* Find the inner side of the join, which we need to estimate the
* number of buckets. Use outer_is_left because the
* clause_sides_match_join routine has called on hash clauses.
*/
relids = rinfo->outer_is_left ?
rinfo->right_relids : rinfo->left_relids;
expr = rinfo->outer_is_left ?
get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
if (bms_get_singleton_member(relids, &relid) &&
root->simple_rel_array[relid]->statlist != NIL)
{
bool is_duplicate = false;
/*
* This inner-side expression references only one relation.
* Extended statistics on this clause can exist.
*/
if (group_relid < 0)
{
RangeTblEntry *rte = root->simple_rte_array[relid];
if (!rte || (rte->relkind != RELKIND_RELATION &&
rte->relkind != RELKIND_MATVIEW &&
rte->relkind != RELKIND_FOREIGN_TABLE &&
rte->relkind != RELKIND_PARTITIONED_TABLE))
{
/* Extended statistics can't exist in principle */
otherclauses = lappend(otherclauses, rinfo);
clauses = foreach_delete_current(clauses, lc);
continue;
}
group_relid = relid;
group_rel = root->simple_rel_array[relid];
}
else if (group_relid != relid)
{
/*
* Being in the group forming state we don't need other
* clauses.
*/
continue;
}
/*
* We're going to add the new clause to the varinfos list. We
* might re-use add_unique_group_var(), but we don't do so for
* two reasons.
*
* 1) We must keep the origin_rinfos list ordered exactly the
* same way as varinfos.
*
* 2) add_unique_group_var() is designed for
* estimate_num_groups(), where a larger number of groups is
* worse. While estimating the number of hash buckets, we
* have the opposite: a lesser number of groups is worse.
* Therefore, we don't have to remove "known equal" vars: the
* removed var may valuably contribute to the multivariate
* statistics to grow the number of groups.
*/
/*
* Clear nullingrels to correctly match hash keys. See
* add_unique_group_var()'s comment for details.
*/
expr = remove_nulling_relids(expr, root->outer_join_rels, NULL);
/*
* Detect and exclude exact duplicates from the list of hash
* keys (like add_unique_group_var does).
*/
foreach(lc1, varinfos)
{
varinfo = (GroupVarInfo *) lfirst(lc1);
if (!equal(expr, varinfo->var))
continue;
is_duplicate = true;
break;
}
if (is_duplicate)
{
/*
* Skip exact duplicates. Adding them to the otherclauses
* list also doesn't make sense.
*/
continue;
}
/*
* Initialize GroupVarInfo. We only use it to call
* estimate_multivariate_ndistinct(), which doesn't care about
* ndistinct and isdefault fields. Thus, skip these fields.
*/
varinfo = palloc0_object(GroupVarInfo);
varinfo->var = expr;
varinfo->rel = root->simple_rel_array[relid];
varinfos = lappend(varinfos, varinfo);
/*
* Remember the link to RestrictInfo for the case the clause
* is failed to be estimated.
*/
origin_rinfos = lappend(origin_rinfos, rinfo);
}
else
{
/* This clause can't be estimated with extended statistics */
otherclauses = lappend(otherclauses, rinfo);
}
clauses = foreach_delete_current(clauses, lc);
}
if (list_length(varinfos) < 2)
{
/*
* Multivariate statistics doesn't apply to single columns except
* for expressions, but it has not been implemented yet.
*/
otherclauses = list_concat(otherclauses, origin_rinfos);
list_free_deep(varinfos);
list_free(origin_rinfos);
continue;
}
Assert(group_rel != NULL);
/* Employ the extended statistics. */
origin_varinfos = varinfos;
for (;;)
{
bool estimated = estimate_multivariate_ndistinct(root,
group_rel,
&varinfos,
&mvndistinct);
if (!estimated)
break;
/*
* We've got an estimation. Use ndistinct value in a consistent
* way - according to the caller's logic (see
* final_cost_hashjoin).
*/
if (ndistinct < mvndistinct)
ndistinct = mvndistinct;
Assert(ndistinct >= 1.0);
}
Assert(list_length(origin_varinfos) == list_length(origin_rinfos));
/* Collect unmatched clauses as otherclauses. */
forboth(lc1, origin_varinfos, lc2, origin_rinfos)
{
GroupVarInfo *vinfo = lfirst(lc1);
if (!list_member_ptr(varinfos, vinfo))
/* Already estimated */
continue;
/* Can't be estimated here - push to the returning list */
otherclauses = lappend(otherclauses, lfirst(lc2));
}
}
*innerbucketsize = 1.0 / ndistinct;
return otherclauses;
}
/*
* Estimate hash bucket statistics when the specified expression is used
* as a hash key for the given number of buckets.
*
* This attempts to determine two values:
*
* 1. The frequency of the most common value of the expression (returns
* zero into *mcv_freq if we can't get that). This will be frequency
* relative to the entire underlying table.
*
* 2. The "bucketsize fraction", ie, average number of entries in a bucket
* divided by total number of tuples to be hashed.
*
* XXX This is really pretty bogus since we're effectively assuming that the
* distribution of hash keys will be the same after applying restriction
* clauses as it was in the underlying relation. However, we are not nearly
* smart enough to figure out how the restrict clauses might change the
* distribution, so this will have to do for now.
*
* We are passed the number of buckets the executor will use for the given
* input relation. If the data were perfectly distributed, with the same
* number of tuples going into each available bucket, then the bucketsize
* fraction would be 1/nbuckets. But this happy state of affairs will occur
* only if (a) there are at least nbuckets distinct data values, and (b)
* we have a not-too-skewed data distribution. Otherwise the buckets will
* be nonuniformly occupied. If the other relation in the join has a key
* distribution similar to this one's, then the most-loaded buckets are
* exactly those that will be probed most often. Therefore, the "average"
* bucket size for costing purposes should really be taken as something close
* to the "worst case" bucket size. We try to estimate this by adjusting the
* fraction if there are too few distinct data values, and then clamping to
* at least the bucket size implied by the most common value's frequency.
*
* If no statistics are available, use a default estimate of 0.1. This will
* discourage use of a hash rather strongly if the inner relation is large,
* which is what we want. We do not want to hash unless we know that the
* inner rel is well-dispersed (or the alternatives seem much worse).
*
* The caller should also check that the mcv_freq is not so large that the
* most common value would by itself require an impractically large bucket.
* In a hash join, the executor can split buckets if they get too big, but
* obviously that doesn't help for a bucket that contains many duplicates of
* the same value.
*/
void
estimate_hash_bucket_stats(PlannerInfo *root, Node *hashkey, double nbuckets,
Selectivity *mcv_freq,
Selectivity *bucketsize_frac)
{
VariableStatData vardata;
double estfract,
ndistinct;
bool isdefault;
AttStatsSlot sslot;
examine_variable(root, hashkey, 0, &vardata);
/* Initialize *mcv_freq to "unknown" */
*mcv_freq = 0.0;
/* Look up the frequency of the most common value, if available */
if (HeapTupleIsValid(vardata.statsTuple))
{
if (get_attstatsslot(&sslot, vardata.statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
ATTSTATSSLOT_NUMBERS))
{
/*
* The first MCV stat is for the most common value.
*/
if (sslot.nnumbers > 0)
*mcv_freq = sslot.numbers[0];
free_attstatsslot(&sslot);
}
else if (get_attstatsslot(&sslot, vardata.statsTuple,
STATISTIC_KIND_HISTOGRAM, InvalidOid,
0))
{
/*
* If there are no recorded MCVs, but we do have a histogram, then
* assume that ANALYZE determined that the column is unique.
*/
if (vardata.rel && vardata.rel->tuples > 0)
*mcv_freq = 1.0 / vardata.rel->tuples;
}
}
/* Get number of distinct values */
ndistinct = get_variable_numdistinct(&vardata, &isdefault);
/*
* If ndistinct isn't real, punt. We normally return 0.1, but if the
* mcv_freq is known to be even higher than that, use it instead.
*/
if (isdefault)
{
*bucketsize_frac = (Selectivity) Max(0.1, *mcv_freq);
ReleaseVariableStats(vardata);
return;
}
/*
* Adjust ndistinct to account for restriction clauses. Observe we are
* assuming that the data distribution is affected uniformly by the
* restriction clauses!
*
* XXX Possibly better way, but much more expensive: multiply by
* selectivity of rel's restriction clauses that mention the target Var.
*/
if (vardata.rel && vardata.rel->tuples > 0)
{
ndistinct *= vardata.rel->rows / vardata.rel->tuples;
ndistinct = clamp_row_est(ndistinct);
}
/*
* Initial estimate of bucketsize fraction is 1/nbuckets as long as the
* number of buckets is less than the expected number of distinct values;
* otherwise it is 1/ndistinct.
*/
if (ndistinct > nbuckets)
estfract = 1.0 / nbuckets;
else
estfract = 1.0 / ndistinct;
/*
* Clamp the bucketsize fraction to be not less than the MCV frequency,
* since whichever bucket the MCV values end up in will have at least that
* size. This has no effect if *mcv_freq is still zero.
*/
estfract = Max(estfract, *mcv_freq);
*bucketsize_frac = (Selectivity) estfract;
ReleaseVariableStats(vardata);
}
/*
* estimate_hashagg_tablesize
* estimate the number of bytes that a hash aggregate hashtable will
* require based on the agg_costs, path width and number of groups.
*
* We return the result as "double" to forestall any possible overflow
* problem in the multiplication by dNumGroups.
*
* XXX this may be over-estimating the size now that hashagg knows to omit
* unneeded columns from the hashtable. Also for mixed-mode grouping sets,
* grouping columns not in the hashed set are counted here even though hashagg
* won't store them. Is this a problem?
*/
double
estimate_hashagg_tablesize(PlannerInfo *root, Path *path,
const AggClauseCosts *agg_costs, double dNumGroups)
{
Size hashentrysize;
hashentrysize = hash_agg_entry_size(list_length(root->aggtransinfos),
path->pathtarget->width,
agg_costs->transitionSpace);
/*
* Note that this disregards the effect of fill-factor and growth policy
* of the hash table. That's probably ok, given that the default
* fill-factor is relatively high. It'd be hard to meaningfully factor in
* "double-in-size" growth policies here.
*/
return hashentrysize * dNumGroups;
}
/*-------------------------------------------------------------------------
*
* Support routines
*
*-------------------------------------------------------------------------
*/
/*
* Find the best matching ndistinct extended statistics for the given list of
* GroupVarInfos.
*
* Callers must ensure that the given GroupVarInfos all belong to 'rel' and
* the GroupVarInfos list does not contain any duplicate Vars or expressions.
*
* When statistics are found that match > 1 of the given GroupVarInfo, the
* *ndistinct parameter is set according to the ndistinct estimate and a new
* list is built with the matching GroupVarInfos removed, which is output via
* the *varinfos parameter before returning true. When no matching stats are
* found, false is returned and the *varinfos and *ndistinct parameters are
* left untouched.
*/
static bool
estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
List **varinfos, double *ndistinct)
{
ListCell *lc;
int nmatches_vars;
int nmatches_exprs;
Oid statOid = InvalidOid;
MVNDistinct *stats;
StatisticExtInfo *matched_info = NULL;
RangeTblEntry *rte = planner_rt_fetch(rel->relid, root);
/* bail out immediately if the table has no extended statistics */
if (!rel->statlist)
return false;
/* look for the ndistinct statistics object matching the most vars */
nmatches_vars = 0; /* we require at least two matches */
nmatches_exprs = 0;
foreach(lc, rel->statlist)
{
ListCell *lc2;
StatisticExtInfo *info = (StatisticExtInfo *) lfirst(lc);
int nshared_vars = 0;
int nshared_exprs = 0;
/* skip statistics of other kinds */
if (info->kind != STATS_EXT_NDISTINCT)
continue;
/* skip statistics with mismatching stxdinherit value */
if (info->inherit != rte->inh)
continue;
/*
* Determine how many expressions (and variables in non-matched
* expressions) match. We'll then use these numbers to pick the
* statistics object that best matches the clauses.
*/
foreach(lc2, *varinfos)
{
ListCell *lc3;
GroupVarInfo *varinfo = (GroupVarInfo *) lfirst(lc2);
AttrNumber attnum;
Assert(varinfo->rel == rel);
/* simple Var, search in statistics keys directly */
if (IsA(varinfo->var, Var))
{
attnum = ((Var *) varinfo->var)->varattno;
/*
* Ignore system attributes - we don't support statistics on
* them, so can't match them (and it'd fail as the values are
* negative).
*/
if (!AttrNumberIsForUserDefinedAttr(attnum))
continue;
if (bms_is_member(attnum, info->keys))
nshared_vars++;
continue;
}
/* expression - see if it's in the statistics object */
foreach(lc3, info->exprs)
{
Node *expr = (Node *) lfirst(lc3);
if (equal(varinfo->var, expr))
{
nshared_exprs++;
break;
}
}
}
/*
* The ndistinct extended statistics contain estimates for a minimum
* of pairs of columns which the statistics are defined on and
* certainly not single columns. Here we skip unless we managed to
* match to at least two columns.
*/
if (nshared_vars + nshared_exprs < 2)
continue;
/*
* Check if these statistics are a better match than the previous best
* match and if so, take note of the StatisticExtInfo.
*
* The statslist is sorted by statOid, so the StatisticExtInfo we
* select as the best match is deterministic even when multiple sets
* of statistics match equally as well.
*/
if ((nshared_exprs > nmatches_exprs) ||
(((nshared_exprs == nmatches_exprs)) && (nshared_vars > nmatches_vars)))
{
statOid = info->statOid;
nmatches_vars = nshared_vars;
nmatches_exprs = nshared_exprs;
matched_info = info;
}
}
/* No match? */
if (statOid == InvalidOid)
return false;
Assert(nmatches_vars + nmatches_exprs > 1);
stats = statext_ndistinct_load(statOid, rte->inh);
/*
* If we have a match, search it for the specific item that matches (there
* must be one), and construct the output values.
*/
if (stats)
{
int i;
List *newlist = NIL;
MVNDistinctItem *item = NULL;
ListCell *lc2;
Bitmapset *matched = NULL;
AttrNumber attnum_offset;
/*
* How much we need to offset the attnums? If there are no
* expressions, no offset is needed. Otherwise offset enough to move
* the lowest one (which is equal to number of expressions) to 1.
*/
if (matched_info->exprs)
attnum_offset = (list_length(matched_info->exprs) + 1);
else
attnum_offset = 0;
/* see what actually matched */
foreach(lc2, *varinfos)
{
ListCell *lc3;
int idx;
bool found = false;
GroupVarInfo *varinfo = (GroupVarInfo *) lfirst(lc2);
/*
* Process a simple Var expression, by matching it to keys
* directly. If there's a matching expression, we'll try matching
* it later.
*/
if (IsA(varinfo->var, Var))
{
AttrNumber attnum = ((Var *) varinfo->var)->varattno;
/*
* Ignore expressions on system attributes. Can't rely on the
* bms check for negative values.
*/
if (!AttrNumberIsForUserDefinedAttr(attnum))
continue;
/* Is the variable covered by the statistics object? */
if (!bms_is_member(attnum, matched_info->keys))
continue;
attnum = attnum + attnum_offset;
/* ensure sufficient offset */
Assert(AttrNumberIsForUserDefinedAttr(attnum));
matched = bms_add_member(matched, attnum);
found = true;
}
/*
* XXX Maybe we should allow searching the expressions even if we
* found an attribute matching the expression? That would handle
* trivial expressions like "(a)" but it seems fairly useless.
*/
if (found)
continue;
/* expression - see if it's in the statistics object */
idx = 0;
foreach(lc3, matched_info->exprs)
{
Node *expr = (Node *) lfirst(lc3);
if (equal(varinfo->var, expr))
{
AttrNumber attnum = -(idx + 1);
attnum = attnum + attnum_offset;
/* ensure sufficient offset */
Assert(AttrNumberIsForUserDefinedAttr(attnum));
matched = bms_add_member(matched, attnum);
/* there should be just one matching expression */
break;
}
idx++;
}
}
/* Find the specific item that exactly matches the combination */
for (i = 0; i < stats->nitems; i++)
{
int j;
MVNDistinctItem *tmpitem = &stats->items[i];
if (tmpitem->nattributes != bms_num_members(matched))
continue;
/* assume it's the right item */
item = tmpitem;
/* check that all item attributes/expressions fit the match */
for (j = 0; j < tmpitem->nattributes; j++)
{
AttrNumber attnum = tmpitem->attributes[j];
/*
* Thanks to how we constructed the matched bitmap above, we
* can just offset all attnums the same way.
*/
attnum = attnum + attnum_offset;
if (!bms_is_member(attnum, matched))
{
/* nah, it's not this item */
item = NULL;
break;
}
}
/*
* If the item has all the matched attributes, we know it's the
* right one - there can't be a better one. matching more.
*/
if (item)
break;
}
/*
* Make sure we found an item. There has to be one, because ndistinct
* statistics includes all combinations of attributes.
*/
if (!item)
elog(ERROR, "corrupt MVNDistinct entry");
/* Form the output varinfo list, keeping only unmatched ones */
foreach(lc, *varinfos)
{
GroupVarInfo *varinfo = (GroupVarInfo *) lfirst(lc);
ListCell *lc3;
bool found = false;
/*
* Let's look at plain variables first, because it's the most
* common case and the check is quite cheap. We can simply get the
* attnum and check (with an offset) matched bitmap.
*/
if (IsA(varinfo->var, Var))
{
AttrNumber attnum = ((Var *) varinfo->var)->varattno;
/*
* If it's a system attribute, we're done. We don't support
* extended statistics on system attributes, so it's clearly
* not matched. Just keep the expression and continue.
*/
if (!AttrNumberIsForUserDefinedAttr(attnum))
{
newlist = lappend(newlist, varinfo);
continue;
}
/* apply the same offset as above */
attnum += attnum_offset;
/* if it's not matched, keep the varinfo */
if (!bms_is_member(attnum, matched))
newlist = lappend(newlist, varinfo);
/* The rest of the loop deals with complex expressions. */
continue;
}
/*
* Process complex expressions, not just simple Vars.
*
* First, we search for an exact match of an expression. If we
* find one, we can just discard the whole GroupVarInfo, with all
* the variables we extracted from it.
*
* Otherwise we inspect the individual vars, and try matching it
* to variables in the item.
*/
foreach(lc3, matched_info->exprs)
{
Node *expr = (Node *) lfirst(lc3);
if (equal(varinfo->var, expr))
{
found = true;
break;
}
}
/* found exact match, skip */
if (found)
continue;
newlist = lappend(newlist, varinfo);
}
*varinfos = newlist;
*ndistinct = item->ndistinct;
return true;
}
return false;
}
/*
* convert_to_scalar
* Convert non-NULL values of the indicated types to the comparison
* scale needed by scalarineqsel().
* Returns "true" if successful.
*
* XXX this routine is a hack: ideally we should look up the conversion
* subroutines in pg_type.
*
* All numeric datatypes are simply converted to their equivalent
* "double" values. (NUMERIC values that are outside the range of "double"
* are clamped to +/- HUGE_VAL.)
*
* String datatypes are converted by convert_string_to_scalar(),
* which is explained below. The reason why this routine deals with
* three values at a time, not just one, is that we need it for strings.
*
* The bytea datatype is just enough different from strings that it has
* to be treated separately.
*
* The several datatypes representing absolute times are all converted
* to Timestamp, which is actually an int64, and then we promote that to
* a double. Note this will give correct results even for the "special"
* values of Timestamp, since those are chosen to compare correctly;
* see timestamp_cmp.
*
* The several datatypes representing relative times (intervals) are all
* converted to measurements expressed in seconds.
*/
static bool
convert_to_scalar(Datum value, Oid valuetypid, Oid collid, double *scaledvalue,
Datum lobound, Datum hibound, Oid boundstypid,
double *scaledlobound, double *scaledhibound)
{
bool failure = false;
/*
* Both the valuetypid and the boundstypid should exactly match the
* declared input type(s) of the operator we are invoked for. However,
* extensions might try to use scalarineqsel as estimator for operators
* with input type(s) we don't handle here; in such cases, we want to
* return false, not fail. In any case, we mustn't assume that valuetypid
* and boundstypid are identical.
*
* XXX The histogram we are interpolating between points of could belong
* to a column that's only binary-compatible with the declared type. In
* essence we are assuming that the semantics of binary-compatible types
* are enough alike that we can use a histogram generated with one type's
* operators to estimate selectivity for the other's. This is outright
* wrong in some cases --- in particular signed versus unsigned
* interpretation could trip us up. But it's useful enough in the
* majority of cases that we do it anyway. Should think about more
* rigorous ways to do it.
*/
switch (valuetypid)
{
/*
* Built-in numeric types
*/
case BOOLOID:
case INT2OID:
case INT4OID:
case INT8OID:
case FLOAT4OID:
case FLOAT8OID:
case NUMERICOID:
case OIDOID:
case REGPROCOID:
case REGPROCEDUREOID:
case REGOPEROID:
case REGOPERATOROID:
case REGCLASSOID:
case REGTYPEOID:
case REGCOLLATIONOID:
case REGCONFIGOID:
case REGDICTIONARYOID:
case REGROLEOID:
case REGNAMESPACEOID:
case REGDATABASEOID:
*scaledvalue = convert_numeric_to_scalar(value, valuetypid,
&failure);
*scaledlobound = convert_numeric_to_scalar(lobound, boundstypid,
&failure);
*scaledhibound = convert_numeric_to_scalar(hibound, boundstypid,
&failure);
return !failure;
/*
* Built-in string types
*/
case CHAROID:
case BPCHAROID:
case VARCHAROID:
case TEXTOID:
case NAMEOID:
{
char *valstr = convert_string_datum(value, valuetypid,
collid, &failure);
char *lostr = convert_string_datum(lobound, boundstypid,
collid, &failure);
char *histr = convert_string_datum(hibound, boundstypid,
collid, &failure);
/*
* Bail out if any of the values is not of string type. We
* might leak converted strings for the other value(s), but
* that's not worth troubling over.
*/
if (failure)
return false;
convert_string_to_scalar(valstr, scaledvalue,
lostr, scaledlobound,
histr, scaledhibound);
pfree(valstr);
pfree(lostr);
pfree(histr);
return true;
}
/*
* Built-in bytea type
*/
case BYTEAOID:
{
/* We only support bytea vs bytea comparison */
if (boundstypid != BYTEAOID)
return false;
convert_bytea_to_scalar(value, scaledvalue,
lobound, scaledlobound,
hibound, scaledhibound);
return true;
}
/*
* Built-in time types
*/
case TIMESTAMPOID:
case TIMESTAMPTZOID:
case DATEOID:
case INTERVALOID:
case TIMEOID:
case TIMETZOID:
*scaledvalue = convert_timevalue_to_scalar(value, valuetypid,
&failure);
*scaledlobound = convert_timevalue_to_scalar(lobound, boundstypid,
&failure);
*scaledhibound = convert_timevalue_to_scalar(hibound, boundstypid,
&failure);
return !failure;
/*
* Built-in network types
*/
case INETOID:
case CIDROID:
case MACADDROID:
case MACADDR8OID:
*scaledvalue = convert_network_to_scalar(value, valuetypid,
&failure);
*scaledlobound = convert_network_to_scalar(lobound, boundstypid,
&failure);
*scaledhibound = convert_network_to_scalar(hibound, boundstypid,
&failure);
return !failure;
}
/* Don't know how to convert */
*scaledvalue = *scaledlobound = *scaledhibound = 0;
return false;
}
/*
* Do convert_to_scalar()'s work for any numeric data type.
*
* On failure (e.g., unsupported typid), set *failure to true;
* otherwise, that variable is not changed.
*/
static double
convert_numeric_to_scalar(Datum value, Oid typid, bool *failure)
{
switch (typid)
{
case BOOLOID:
return (double) DatumGetBool(value);
case INT2OID:
return (double) DatumGetInt16(value);
case INT4OID:
return (double) DatumGetInt32(value);
case INT8OID:
return (double) DatumGetInt64(value);
case FLOAT4OID:
return (double) DatumGetFloat4(value);
case FLOAT8OID:
return (double) DatumGetFloat8(value);
case NUMERICOID:
/* Note: out-of-range values will be clamped to +-HUGE_VAL */
return (double)
DatumGetFloat8(DirectFunctionCall1(numeric_float8_no_overflow,
value));
case OIDOID:
case REGPROCOID:
case REGPROCEDUREOID:
case REGOPEROID:
case REGOPERATOROID:
case REGCLASSOID:
case REGTYPEOID:
case REGCOLLATIONOID:
case REGCONFIGOID:
case REGDICTIONARYOID:
case REGROLEOID:
case REGNAMESPACEOID:
case REGDATABASEOID:
/* we can treat OIDs as integers... */
return (double) DatumGetObjectId(value);
}
*failure = true;
return 0;
}
/*
* Do convert_to_scalar()'s work for any character-string data type.
*
* String datatypes are converted to a scale that ranges from 0 to 1,
* where we visualize the bytes of the string as fractional digits.
*
* We do not want the base to be 256, however, since that tends to
* generate inflated selectivity estimates; few databases will have
* occurrences of all 256 possible byte values at each position.
* Instead, use the smallest and largest byte values seen in the bounds
* as the estimated range for each byte, after some fudging to deal with
* the fact that we probably aren't going to see the full range that way.
*
* An additional refinement is that we discard any common prefix of the
* three strings before computing the scaled values. This allows us to
* "zoom in" when we encounter a narrow data range. An example is a phone
* number database where all the values begin with the same area code.
* (Actually, the bounds will be adjacent histogram-bin-boundary values,
* so this is more likely to happen than you might think.)
*/
static void
convert_string_to_scalar(char *value,
double *scaledvalue,
char *lobound,
double *scaledlobound,
char *hibound,
double *scaledhibound)
{
int rangelo,
rangehi;
char *sptr;
rangelo = rangehi = (unsigned char) hibound[0];
for (sptr = lobound; *sptr; sptr++)
{
if (rangelo > (unsigned char) *sptr)
rangelo = (unsigned char) *sptr;
if (rangehi < (unsigned char) *sptr)
rangehi = (unsigned char) *sptr;
}
for (sptr = hibound; *sptr; sptr++)
{
if (rangelo > (unsigned char) *sptr)
rangelo = (unsigned char) *sptr;
if (rangehi < (unsigned char) *sptr)
rangehi = (unsigned char) *sptr;
}
/* If range includes any upper-case ASCII chars, make it include all */
if (rangelo <= 'Z' && rangehi >= 'A')
{
if (rangelo > 'A')
rangelo = 'A';
if (rangehi < 'Z')
rangehi = 'Z';
}
/* Ditto lower-case */
if (rangelo <= 'z' && rangehi >= 'a')
{
if (rangelo > 'a')
rangelo = 'a';
if (rangehi < 'z')
rangehi = 'z';
}
/* Ditto digits */
if (rangelo <= '9' && rangehi >= '0')
{
if (rangelo > '0')
rangelo = '0';
if (rangehi < '9')
rangehi = '9';
}
/*
* If range includes less than 10 chars, assume we have not got enough
* data, and make it include regular ASCII set.
*/
if (rangehi - rangelo < 9)
{
rangelo = ' ';
rangehi = 127;
}
/*
* Now strip any common prefix of the three strings.
*/
while (*lobound)
{
if (*lobound != *hibound || *lobound != *value)
break;
lobound++, hibound++, value++;
}
/*
* Now we can do the conversions.
*/
*scaledvalue = convert_one_string_to_scalar(value, rangelo, rangehi);
*scaledlobound = convert_one_string_to_scalar(lobound, rangelo, rangehi);
*scaledhibound = convert_one_string_to_scalar(hibound, rangelo, rangehi);
}
static double
convert_one_string_to_scalar(char *value, int rangelo, int rangehi)
{
int slen = strlen(value);
double num,
denom,
base;
if (slen <= 0)
return 0.0; /* empty string has scalar value 0 */
/*
* There seems little point in considering more than a dozen bytes from
* the string. Since base is at least 10, that will give us nominal
* resolution of at least 12 decimal digits, which is surely far more
* precision than this estimation technique has got anyway (especially in
* non-C locales). Also, even with the maximum possible base of 256, this
* ensures denom cannot grow larger than 256^13 = 2.03e31, which will not
* overflow on any known machine.
*/
if (slen > 12)
slen = 12;
/* Convert initial characters to fraction */
base = rangehi - rangelo + 1;
num = 0.0;
denom = base;
while (slen-- > 0)
{
int ch = (unsigned char) *value++;
if (ch < rangelo)
ch = rangelo - 1;
else if (ch > rangehi)
ch = rangehi + 1;
num += ((double) (ch - rangelo)) / denom;
denom *= base;
}
return num;
}
/*
* Convert a string-type Datum into a palloc'd, null-terminated string.
*
* On failure (e.g., unsupported typid), set *failure to true;
* otherwise, that variable is not changed. (We'll return NULL on failure.)
*
* When using a non-C locale, we must pass the string through pg_strxfrm()
* before continuing, so as to generate correct locale-specific results.
*/
static char *
convert_string_datum(Datum value, Oid typid, Oid collid, bool *failure)
{
char *val;
pg_locale_t mylocale;
switch (typid)
{
case CHAROID:
val = (char *) palloc(2);
val[0] = DatumGetChar(value);
val[1] = '\0';
break;
case BPCHAROID:
case VARCHAROID:
case TEXTOID:
val = TextDatumGetCString(value);
break;
case NAMEOID:
{
NameData *nm = (NameData *) DatumGetPointer(value);
val = pstrdup(NameStr(*nm));
break;
}
default:
*failure = true;
return NULL;
}
mylocale = pg_newlocale_from_collation(collid);
if (!mylocale->collate_is_c)
{
char *xfrmstr;
size_t xfrmlen;
size_t xfrmlen2 PG_USED_FOR_ASSERTS_ONLY;
/*
* XXX: We could guess at a suitable output buffer size and only call
* pg_strxfrm() twice if our guess is too small.
*
* XXX: strxfrm doesn't support UTF-8 encoding on Win32, it can return
* bogus data or set an error. This is not really a problem unless it
* crashes since it will only give an estimation error and nothing
* fatal.
*
* XXX: we do not check pg_strxfrm_enabled(). On some platforms and in
* some cases, libc strxfrm() may return the wrong results, but that
* will only lead to an estimation error.
*/
xfrmlen = pg_strxfrm(NULL, val, 0, mylocale);
#ifdef WIN32
/*
* On Windows, strxfrm returns INT_MAX when an error occurs. Instead
* of trying to allocate this much memory (and fail), just return the
* original string unmodified as if we were in the C locale.
*/
if (xfrmlen == INT_MAX)
return val;
#endif
xfrmstr = (char *) palloc(xfrmlen + 1);
xfrmlen2 = pg_strxfrm(xfrmstr, val, xfrmlen + 1, mylocale);
/*
* Some systems (e.g., glibc) can return a smaller value from the
* second call than the first; thus the Assert must be <= not ==.
*/
Assert(xfrmlen2 <= xfrmlen);
pfree(val);
val = xfrmstr;
}
return val;
}
/*
* Do convert_to_scalar()'s work for any bytea data type.
*
* Very similar to convert_string_to_scalar except we can't assume
* null-termination and therefore pass explicit lengths around.
*
* Also, assumptions about likely "normal" ranges of characters have been
* removed - a data range of 0..255 is always used, for now. (Perhaps
* someday we will add information about actual byte data range to
* pg_statistic.)
*/
static void
convert_bytea_to_scalar(Datum value,
double *scaledvalue,
Datum lobound,
double *scaledlobound,
Datum hibound,
double *scaledhibound)
{
bytea *valuep = DatumGetByteaPP(value);
bytea *loboundp = DatumGetByteaPP(lobound);
bytea *hiboundp = DatumGetByteaPP(hibound);
int rangelo,
rangehi,
valuelen = VARSIZE_ANY_EXHDR(valuep),
loboundlen = VARSIZE_ANY_EXHDR(loboundp),
hiboundlen = VARSIZE_ANY_EXHDR(hiboundp),
i,
minlen;
unsigned char *valstr = (unsigned char *) VARDATA_ANY(valuep);
unsigned char *lostr = (unsigned char *) VARDATA_ANY(loboundp);
unsigned char *histr = (unsigned char *) VARDATA_ANY(hiboundp);
/*
* Assume bytea data is uniformly distributed across all byte values.
*/
rangelo = 0;
rangehi = 255;
/*
* Now strip any common prefix of the three strings.
*/
minlen = Min(Min(valuelen, loboundlen), hiboundlen);
for (i = 0; i < minlen; i++)
{
if (*lostr != *histr || *lostr != *valstr)
break;
lostr++, histr++, valstr++;
loboundlen--, hiboundlen--, valuelen--;
}
/*
* Now we can do the conversions.
*/
*scaledvalue = convert_one_bytea_to_scalar(valstr, valuelen, rangelo, rangehi);
*scaledlobound = convert_one_bytea_to_scalar(lostr, loboundlen, rangelo, rangehi);
*scaledhibound = convert_one_bytea_to_scalar(histr, hiboundlen, rangelo, rangehi);
}
static double
convert_one_bytea_to_scalar(unsigned char *value, int valuelen,
int rangelo, int rangehi)
{
double num,
denom,
base;
if (valuelen <= 0)
return 0.0; /* empty string has scalar value 0 */
/*
* Since base is 256, need not consider more than about 10 chars (even
* this many seems like overkill)
*/
if (valuelen > 10)
valuelen = 10;
/* Convert initial characters to fraction */
base = rangehi - rangelo + 1;
num = 0.0;
denom = base;
while (valuelen-- > 0)
{
int ch = *value++;
if (ch < rangelo)
ch = rangelo - 1;
else if (ch > rangehi)
ch = rangehi + 1;
num += ((double) (ch - rangelo)) / denom;
denom *= base;
}
return num;
}
/*
* Do convert_to_scalar()'s work for any timevalue data type.
*
* On failure (e.g., unsupported typid), set *failure to true;
* otherwise, that variable is not changed.
*/
static double
convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
{
switch (typid)
{
case TIMESTAMPOID:
return DatumGetTimestamp(value);
case TIMESTAMPTZOID:
return DatumGetTimestampTz(value);
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
{
Interval *interval = DatumGetIntervalP(value);
/*
* Convert the month part of Interval to days using assumed
* average month length of 365.25/12.0 days. Not too
* accurate, but plenty good enough for our purposes.
*
* This also works for infinite intervals, which just have all
* fields set to INT_MIN/INT_MAX, and so will produce a result
* smaller/larger than any finite interval.
*/
return interval->time + interval->day * (double) USECS_PER_DAY +
interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
}
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
{
TimeTzADT *timetz = DatumGetTimeTzADTP(value);
/* use GMT-equivalent time */
return (double) (timetz->time + (timetz->zone * 1000000.0));
}
}
*failure = true;
return 0;
}
/*
* get_restriction_variable
* Examine the args of a restriction clause to see if it's of the
* form (variable op pseudoconstant) or (pseudoconstant op variable),
* where "variable" could be either a Var or an expression in vars of a
* single relation. If so, extract information about the variable,
* and also indicate which side it was on and the other argument.
*
* Inputs:
* root: the planner info
* args: clause argument list
* varRelid: see specs for restriction selectivity functions
*
* Outputs: (these are valid only if true is returned)
* *vardata: gets information about variable (see examine_variable)
* *other: gets other clause argument, aggressively reduced to a constant
* *varonleft: set true if variable is on the left, false if on the right
*
* Returns true if a variable is identified, otherwise false.
*
* Note: if there are Vars on both sides of the clause, we must fail, because
* callers are expecting that the other side will act like a pseudoconstant.
*/
bool
get_restriction_variable(PlannerInfo *root, List *args, int varRelid,
VariableStatData *vardata, Node **other,
bool *varonleft)
{
Node *left,
*right;
VariableStatData rdata;
/* Fail if not a binary opclause (probably shouldn't happen) */
if (list_length(args) != 2)
return false;
left = (Node *) linitial(args);
right = (Node *) lsecond(args);
/*
* Examine both sides. Note that when varRelid is nonzero, Vars of other
* relations will be treated as pseudoconstants.
*/
examine_variable(root, left, varRelid, vardata);
examine_variable(root, right, varRelid, &rdata);
/*
* If one side is a variable and the other not, we win.
*/
if (vardata->rel && rdata.rel == NULL)
{
*varonleft = true;
*other = estimate_expression_value(root, rdata.var);
/* Assume we need no ReleaseVariableStats(rdata) here */
return true;
}
if (vardata->rel == NULL && rdata.rel)
{
*varonleft = false;
*other = estimate_expression_value(root, vardata->var);
/* Assume we need no ReleaseVariableStats(*vardata) here */
*vardata = rdata;
return true;
}
/* Oops, clause has wrong structure (probably var op var) */
ReleaseVariableStats(*vardata);
ReleaseVariableStats(rdata);
return false;
}
/*
* get_join_variables
* Apply examine_variable() to each side of a join clause.
* Also, attempt to identify whether the join clause has the same
* or reversed sense compared to the SpecialJoinInfo.
*
* We consider the join clause "normal" if it is "lhs_var OP rhs_var",
* or "reversed" if it is "rhs_var OP lhs_var". In complicated cases
* where we can't tell for sure, we default to assuming it's normal.
*/
void
get_join_variables(PlannerInfo *root, List *args, SpecialJoinInfo *sjinfo,
VariableStatData *vardata1, VariableStatData *vardata2,
bool *join_is_reversed)
{
Node *left,
*right;
if (list_length(args) != 2)
elog(ERROR, "join operator should take two arguments");
left = (Node *) linitial(args);
right = (Node *) lsecond(args);
examine_variable(root, left, 0, vardata1);
examine_variable(root, right, 0, vardata2);
if (vardata1->rel &&
bms_is_subset(vardata1->rel->relids, sjinfo->syn_righthand))
*join_is_reversed = true; /* var1 is on RHS */
else if (vardata2->rel &&
bms_is_subset(vardata2->rel->relids, sjinfo->syn_lefthand))
*join_is_reversed = true; /* var2 is on LHS */
else
*join_is_reversed = false;
}
/* statext_expressions_load copies the tuple, so just pfree it. */
static void
ReleaseDummy(HeapTuple tuple)
{
pfree(tuple);
}
/*
* examine_variable
* Try to look up statistical data about an expression.
* Fill in a VariableStatData struct to describe the expression.
*
* Inputs:
* root: the planner info
* node: the expression tree to examine
* varRelid: see specs for restriction selectivity functions
*
* Outputs: *vardata is filled as follows:
* var: the input expression (with any phvs or binary relabeling stripped,
* if it is or contains a variable; but otherwise unchanged)
* rel: RelOptInfo for relation containing variable; NULL if expression
* contains no Vars (NOTE this could point to a RelOptInfo of a
* subquery, not one in the current query).
* statsTuple: the pg_statistic entry for the variable, if one exists;
* otherwise NULL.
* freefunc: pointer to a function to release statsTuple with.
* vartype: exposed type of the expression; this should always match
* the declared input type of the operator we are estimating for.
* atttype, atttypmod: actual type/typmod of the "var" expression. This is
* commonly the same as the exposed type of the variable argument,
* but can be different in binary-compatible-type cases.
* isunique: true if we were able to match the var to a unique index, a
* single-column DISTINCT or GROUP-BY clause, implying its values are
* unique for this query. (Caution: this should be trusted for
* statistical purposes only, since we do not check indimmediate nor
* verify that the exact same definition of equality applies.)
* acl_ok: true if current user has permission to read all table rows from
* the column(s) underlying the pg_statistic entry. This is consulted by
* statistic_proc_security_check().
*
* Caller is responsible for doing ReleaseVariableStats() before exiting.
*/
void
examine_variable(PlannerInfo *root, Node *node, int varRelid,
VariableStatData *vardata)
{
Node *basenode;
Relids varnos;
Relids basevarnos;
RelOptInfo *onerel;
/* Make sure we don't return dangling pointers in vardata */
MemSet(vardata, 0, sizeof(VariableStatData));
/* Save the exposed type of the expression */
vardata->vartype = exprType(node);
/*
* PlaceHolderVars are transparent for the purpose of statistics lookup;
* they do not alter the value distribution of the underlying expression.
* However, they can obscure the structure, preventing us from recognizing
* matches to base columns, index expressions, or extended statistics. So
* strip them out first.
*/
basenode = strip_all_phvs_deep(root, node);
/*
* Look inside any binary-compatible relabeling. We need to handle nested
* RelabelType nodes here, because the prior stripping of PlaceHolderVars
* may have brought separate RelabelTypes into adjacency.
*/
while (IsA(basenode, RelabelType))
basenode = (Node *) ((RelabelType *) basenode)->arg;
/* Fast path for a simple Var */
if (IsA(basenode, Var) &&
(varRelid == 0 || varRelid == ((Var *) basenode)->varno))
{
Var *var = (Var *) basenode;
/* Set up result fields other than the stats tuple */
vardata->var = basenode; /* return Var without phvs or relabeling */
vardata->rel = find_base_rel(root, var->varno);
vardata->atttype = var->vartype;
vardata->atttypmod = var->vartypmod;
vardata->isunique = has_unique_index(vardata->rel, var->varattno);
/* Try to locate some stats */
examine_simple_variable(root, var, vardata);
return;
}
/*
* Okay, it's a more complicated expression. Determine variable
* membership. Note that when varRelid isn't zero, only vars of that
* relation are considered "real" vars.
*/
varnos = pull_varnos(root, basenode);
basevarnos = bms_difference(varnos, root->outer_join_rels);
onerel = NULL;
if (bms_is_empty(basevarnos))
{
/* No Vars at all ... must be pseudo-constant clause */
}
else
{
int relid;
/* Check if the expression is in vars of a single base relation */
if (bms_get_singleton_member(basevarnos, &relid))
{
if (varRelid == 0 || varRelid == relid)
{
onerel = find_base_rel(root, relid);
vardata->rel = onerel;
node = basenode; /* strip any phvs or relabeling */
}
/* else treat it as a constant */
}
else
{
/* varnos has multiple relids */
if (varRelid == 0)
{
/* treat it as a variable of a join relation */
vardata->rel = find_join_rel(root, varnos);
node = basenode; /* strip any phvs or relabeling */
}
else if (bms_is_member(varRelid, varnos))
{
/* ignore the vars belonging to other relations */
vardata->rel = find_base_rel(root, varRelid);
node = basenode; /* strip any phvs or relabeling */
/* note: no point in expressional-index search here */
}
/* else treat it as a constant */
}
}
bms_free(basevarnos);
vardata->var = node;
vardata->atttype = exprType(node);
vardata->atttypmod = exprTypmod(node);
if (onerel)
{
/*
* We have an expression in vars of a single relation. Try to match
* it to expressional index columns, in hopes of finding some
* statistics.
*
* Note that we consider all index columns including INCLUDE columns,
* since there could be stats for such columns. But the test for
* uniqueness needs to be warier.
*
* 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;
ListCell *slist;
/*
* The nullingrels bits within the expression could prevent us from
* matching it to expressional index columns or to the expressions in
* extended statistics. So strip them out first.
*/
if (bms_overlap(varnos, root->outer_join_rels))
node = remove_nulling_relids(node, root->outer_join_rels, NULL);
foreach(ilist, onerel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
ListCell *indexpr_item;
int pos;
indexpr_item = list_head(index->indexprs);
if (indexpr_item == NULL)
continue; /* no expressions here... */
for (pos = 0; pos < index->ncolumns; pos++)
{
if (index->indexkeys[pos] == 0)
{
Node *indexkey;
if (indexpr_item == NULL)
elog(ERROR, "too few entries in indexprs list");
indexkey = (Node *) lfirst(indexpr_item);
if (indexkey && IsA(indexkey, RelabelType))
indexkey = (Node *) ((RelabelType *) indexkey)->arg;
if (equal(node, indexkey))
{
/*
* Found a match ... is it a unique index? Tests here
* should match has_unique_index().
*/
if (index->unique &&
index->nkeycolumns == 1 &&
pos == 0 &&
(index->indpred == NIL || index->predOK))
vardata->isunique = true;
/*
* Has it got stats? We only consider stats for
* non-partial indexes, since partial indexes probably
* don't reflect whole-relation statistics; the above
* check for uniqueness is the only info we take from
* a partial index.
*
* An index stats hook, however, must make its own
* decisions about what to do with partial indexes.
*/
if (get_index_stats_hook &&
(*get_index_stats_hook) (root, index->indexoid,
pos + 1, vardata))
{
/*
* The hook took control of acquiring a stats
* tuple. If it did supply a tuple, it'd better
* have supplied a freefunc.
*/
if (HeapTupleIsValid(vardata->statsTuple) &&
!vardata->freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
else if (index->indpred == NIL)
{
vardata->statsTuple =
SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(index->indexoid),
Int16GetDatum(pos + 1),
BoolGetDatum(false));
vardata->freefunc = ReleaseSysCache;
if (HeapTupleIsValid(vardata->statsTuple))
{
/*
* Test if user has permission to access all
* rows from the index's table.
*
* For simplicity, we insist on the whole
* table being selectable, rather than trying
* to identify which column(s) the index
* depends on.
*
* Note that for an inheritance child,
* permissions are checked on the inheritance
* root parent, and whole-table select
* privilege on the parent doesn't quite
* guarantee that the user could read all
* columns of the child. But in practice it's
* unlikely that any interesting security
* violation could result from allowing access
* to the expression index's stats, so we
* allow it anyway. See similar code in
* examine_simple_variable() for additional
* comments.
*/
vardata->acl_ok =
all_rows_selectable(root,
index->rel->relid,
NULL);
}
else
{
/* suppress leakproofness checks later */
vardata->acl_ok = true;
}
}
if (vardata->statsTuple)
break;
}
indexpr_item = lnext(index->indexprs, indexpr_item);
}
}
if (vardata->statsTuple)
break;
}
/*
* Search extended statistics for one with a matching expression.
* There might be multiple ones, so just grab the first one. In the
* future, we might consider the statistics target (and pick the most
* accurate statistics) and maybe some other parameters.
*/
foreach(slist, onerel->statlist)
{
StatisticExtInfo *info = (StatisticExtInfo *) lfirst(slist);
RangeTblEntry *rte = planner_rt_fetch(onerel->relid, root);
ListCell *expr_item;
int pos;
/*
* Stop once we've found statistics for the expression (either
* from extended stats, or for an index in the preceding loop).
*/
if (vardata->statsTuple)
break;
/* skip stats without per-expression stats */
if (info->kind != STATS_EXT_EXPRESSIONS)
continue;
/* skip stats with mismatching stxdinherit value */
if (info->inherit != rte->inh)
continue;
pos = 0;
foreach(expr_item, info->exprs)
{
Node *expr = (Node *) lfirst(expr_item);
Assert(expr);
/* strip RelabelType before comparing it */
if (expr && IsA(expr, RelabelType))
expr = (Node *) ((RelabelType *) expr)->arg;
/* found a match, see if we can extract pg_statistic row */
if (equal(node, expr))
{
/*
* XXX Not sure if we should cache the tuple somewhere.
* Now we just create a new copy every time.
*/
vardata->statsTuple =
statext_expressions_load(info->statOid, rte->inh, pos);
/* Nothing to release if no data found */
if (vardata->statsTuple != NULL)
{
vardata->freefunc = ReleaseDummy;
}
/*
* Test if user has permission to access all rows from the
* table.
*
* For simplicity, we insist on the whole table being
* selectable, rather than trying to identify which
* column(s) the statistics object depends on.
*
* Note that for an inheritance child, permissions are
* checked on the inheritance root parent, and whole-table
* select privilege on the parent doesn't quite guarantee
* that the user could read all columns of the child. But
* in practice it's unlikely that any interesting security
* violation could result from allowing access to the
* expression stats, so we allow it anyway. See similar
* code in examine_simple_variable() for additional
* comments.
*/
vardata->acl_ok = all_rows_selectable(root,
onerel->relid,
NULL);
break;
}
pos++;
}
}
}
bms_free(varnos);
}
/*
* strip_all_phvs_deep
* Deeply strip all PlaceHolderVars in an expression.
*
* As a performance optimization, we first use a lightweight walker to check
* for the presence of any PlaceHolderVars. The expensive mutator is invoked
* only if a PlaceHolderVar is found, avoiding unnecessary memory allocation
* and tree copying in the common case where no PlaceHolderVars are present.
*/
static Node *
strip_all_phvs_deep(PlannerInfo *root, Node *node)
{
/* If there are no PHVs anywhere, we needn't work hard */
if (root->glob->lastPHId == 0)
return node;
if (!contain_placeholder_walker(node, NULL))
return node;
return strip_all_phvs_mutator(node, NULL);
}
/*
* contain_placeholder_walker
* Lightweight walker to check if an expression contains any
* PlaceHolderVars
*/
static bool
contain_placeholder_walker(Node *node, void *context)
{
if (node == NULL)
return false;
if (IsA(node, PlaceHolderVar))
return true;
return expression_tree_walker(node, contain_placeholder_walker, context);
}
/*
* strip_all_phvs_mutator
* Mutator to deeply strip all PlaceHolderVars
*/
static Node *
strip_all_phvs_mutator(Node *node, void *context)
{
if (node == NULL)
return NULL;
if (IsA(node, PlaceHolderVar))
{
/* Strip it and recurse into its contained expression */
PlaceHolderVar *phv = (PlaceHolderVar *) node;
return strip_all_phvs_mutator((Node *) phv->phexpr, context);
}
return expression_tree_mutator(node, strip_all_phvs_mutator, context);
}
/*
* examine_simple_variable
* Handle a simple Var for examine_variable
*
* This is split out as a subroutine so that we can recurse to deal with
* Vars referencing subqueries (either sub-SELECT-in-FROM or CTE style).
*
* We already filled in all the fields of *vardata except for the stats tuple.
*/
static void
examine_simple_variable(PlannerInfo *root, Var *var,
VariableStatData *vardata)
{
RangeTblEntry *rte = root->simple_rte_array[var->varno];
Assert(IsA(rte, RangeTblEntry));
if (get_relation_stats_hook &&
(*get_relation_stats_hook) (root, rte, var->varattno, vardata))
{
/*
* The hook took control of acquiring a stats tuple. If it did supply
* a tuple, it'd better have supplied a freefunc.
*/
if (HeapTupleIsValid(vardata->statsTuple) &&
!vardata->freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
else if (rte->rtekind == RTE_RELATION)
{
/*
* 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;
if (HeapTupleIsValid(vardata->statsTuple))
{
/*
* Test if user has permission to read all rows from this column.
*
* This requires that the user has the appropriate SELECT
* privileges and that there are no securityQuals from security
* barrier views or RLS policies. If that's not the case, then we
* only permit leakproof functions to be passed pg_statistic data
* in vardata, otherwise the functions might reveal data that the
* user doesn't have permission to see --- see
* statistic_proc_security_check().
*/
vardata->acl_ok =
all_rows_selectable(root, var->varno,
bms_make_singleton(var->varattno - FirstLowInvalidHeapAttributeNumber));
}
else
{
/* suppress any possible leakproofness checks later */
vardata->acl_ok = true;
}
}
else if ((rte->rtekind == RTE_SUBQUERY && !rte->inh) ||
(rte->rtekind == RTE_CTE && !rte->self_reference))
{
/*
* Plain subquery (not one that was converted to an appendrel) or
* non-recursive CTE. In either case, we can try to find out what the
* Var refers to within the subquery. We skip this for appendrel and
* recursive-CTE cases because any column stats we did find would
* likely not be very relevant.
*/
PlannerInfo *subroot;
Query *subquery;
List *subtlist;
TargetEntry *ste;
/*
* Punt if it's a whole-row var rather than a plain column reference.
*/
if (var->varattno == InvalidAttrNumber)
return;
/*
* Otherwise, find the subquery's planner subroot.
*/
if (rte->rtekind == RTE_SUBQUERY)
{
RelOptInfo *rel;
/*
* Fetch RelOptInfo for subquery. Note that we don't change the
* rel returned in vardata, since caller expects it to be a rel of
* the caller's query level. Because we might already be
* recursing, we can't use that rel pointer either, but have to
* look up the Var's rel afresh.
*/
rel = find_base_rel(root, var->varno);
subroot = rel->subroot;
}
else
{
/* CTE case is more difficult */
PlannerInfo *cteroot;
Index levelsup;
int ndx;
int plan_id;
ListCell *lc;
/*
* Find the referenced CTE, and locate the subroot previously made
* for it.
*/
levelsup = rte->ctelevelsup;
cteroot = root;
while (levelsup-- > 0)
{
cteroot = cteroot->parent_root;
if (!cteroot) /* shouldn't happen */
elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
}
/*
* Note: cte_plan_ids can be shorter than cteList, if we are still
* working on planning the CTEs (ie, this is a side-reference from
* another CTE). So we mustn't use forboth here.
*/
ndx = 0;
foreach(lc, cteroot->parse->cteList)
{
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
if (strcmp(cte->ctename, rte->ctename) == 0)
break;
ndx++;
}
if (lc == NULL) /* shouldn't happen */
elog(ERROR, "could not find CTE \"%s\"", rte->ctename);
if (ndx >= list_length(cteroot->cte_plan_ids))
elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
plan_id = list_nth_int(cteroot->cte_plan_ids, ndx);
if (plan_id <= 0)
elog(ERROR, "no plan was made for CTE \"%s\"", rte->ctename);
subroot = list_nth(root->glob->subroots, plan_id - 1);
}
/* If the subquery hasn't been planned yet, we have to punt */
if (subroot == NULL)
return;
Assert(IsA(subroot, PlannerInfo));
/*
* We must use the subquery parsetree as mangled by the planner, not
* the raw version from the RTE, because we need a Var that will refer
* to the subroot's live RelOptInfos. For instance, if any subquery
* pullup happened during planning, Vars in the targetlist might have
* gotten replaced, and we need to see the replacement expressions.
*/
subquery = subroot->parse;
Assert(IsA(subquery, Query));
/*
* Punt if subquery uses set operations or grouping sets, as these
* will mash underlying columns' stats beyond recognition. (Set ops
* are particularly nasty; if we forged ahead, we would return stats
* relevant to only the leftmost subselect...) DISTINCT is also
* problematic, but we check that later because there is a possibility
* of learning something even with it.
*/
if (subquery->setOperations ||
subquery->groupingSets)
return;
/* Get the subquery output expression referenced by the upper Var */
if (subquery->returningList)
subtlist = subquery->returningList;
else
subtlist = subquery->targetList;
ste = get_tle_by_resno(subtlist, var->varattno);
if (ste == NULL || ste->resjunk)
elog(ERROR, "subquery %s does not have attribute %d",
rte->eref->aliasname, var->varattno);
var = (Var *) ste->expr;
/*
* If subquery uses DISTINCT, we can't make use of any stats for the
* variable ... but, if it's the only DISTINCT column, we are entitled
* to consider it unique. We do the test this way so that it works
* for cases involving DISTINCT ON.
*/
if (subquery->distinctClause)
{
if (list_length(subquery->distinctClause) == 1 &&
targetIsInSortList(ste, InvalidOid, subquery->distinctClause))
vardata->isunique = true;
/* cannot go further */
return;
}
/* The same idea as with DISTINCT clause works for a GROUP-BY too */
if (subquery->groupClause)
{
if (list_length(subquery->groupClause) == 1 &&
targetIsInSortList(ste, InvalidOid, subquery->groupClause))
vardata->isunique = true;
/* cannot go further */
return;
}
/*
* If the sub-query originated from a view with the security_barrier
* attribute, we must not look at the variable's statistics, though it
* seems all right to notice the existence of a DISTINCT clause. So
* stop here.
*
* This is probably a harsher restriction than necessary; it's
* certainly OK for the selectivity estimator (which is a C function,
* and therefore omnipotent anyway) to look at the statistics. But
* many selectivity estimators will happily *invoke the operator
* function* to try to work out a good estimate - and that's not OK.
* So for now, don't dig down for stats.
*/
if (rte->security_barrier)
return;
/* Can only handle a simple Var of subquery's query level */
if (var && IsA(var, Var) &&
var->varlevelsup == 0)
{
/*
* OK, recurse into the subquery. Note that the original setting
* of vardata->isunique (which will surely be false) is left
* unchanged in this situation. That's what we want, since even
* if the underlying column is unique, the subquery may have
* joined to other tables in a way that creates duplicates.
*/
examine_simple_variable(subroot, var, vardata);
}
}
else
{
/*
* Otherwise, the Var comes from a FUNCTION or VALUES RTE. (We won't
* see RTE_JOIN here because join alias Vars have already been
* flattened.) There's not much we can do with function outputs, but
* maybe someday try to be smarter about VALUES.
*/
}
}
/*
* all_rows_selectable
* Test whether the user has permission to select all rows from a given
* relation.
*
* Inputs:
* root: the planner info
* varno: the index of the relation (assumed to be an RTE_RELATION)
* varattnos: the attributes for which permission is required, or NULL if
* whole-table access is required
*
* Returns true if the user has the required select permissions, and there are
* no securityQuals from security barrier views or RLS policies.
*
* Note that if the relation is an inheritance child relation, securityQuals
* and access permissions are checked against the inheritance root parent (the
* relation actually mentioned in the query) --- see the comments in
* expand_single_inheritance_child() for an explanation of why it has to be
* done this way.
*
* If varattnos is non-NULL, its attribute numbers should be offset by
* FirstLowInvalidHeapAttributeNumber so that system attributes can be
* checked. If varattnos is NULL, only table-level SELECT privileges are
* checked, not any column-level privileges.
*
* Note: if the relation is accessed via a view, this function actually tests
* whether the view owner has permission to select from the relation. To
* ensure that the current user has permission, it is also necessary to check
* that the current user has permission to select from the view, which we do
* at planner-startup --- see subquery_planner().
*
* This is exported so that other estimation functions can use it.
*/
bool
all_rows_selectable(PlannerInfo *root, Index varno, Bitmapset *varattnos)
{
RelOptInfo *rel = find_base_rel_noerr(root, varno);
RangeTblEntry *rte = planner_rt_fetch(varno, root);
Oid userid;
int varattno;
Assert(rte->rtekind == RTE_RELATION);
/*
* Determine the user ID to use for privilege checks (either the current
* user or the view owner, if we're accessing the table via a view).
*
* Normally the relation will have an associated RelOptInfo from which we
* can find the userid, but it might not if it's a RETURNING Var for an
* INSERT target relation. In that case use the RTEPermissionInfo
* associated with the RTE.
*
* If we navigate up to a parent relation, we keep using the same userid,
* since it's the same in all relations of a given inheritance tree.
*/
if (rel)
userid = rel->userid;
else
{
RTEPermissionInfo *perminfo;
perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
userid = perminfo->checkAsUser;
}
if (!OidIsValid(userid))
userid = GetUserId();
/*
* Permissions and securityQuals must be checked on the table actually
* mentioned in the query, so if this is an inheritance child, navigate up
* to the inheritance root parent. If the user can read the whole table
* or the required columns there, then they can read from the child table
* too. For per-column checks, we must find out which of the root
* parent's attributes the child relation's attributes correspond to.
*/
if (root->append_rel_array != NULL)
{
AppendRelInfo *appinfo;
appinfo = root->append_rel_array[varno];
/*
* Partitions are mapped to their immediate parent, not the root
* parent, so must be ready to walk up multiple AppendRelInfos. But
* stop if we hit a parent that is not RTE_RELATION --- that's a
* flattened UNION ALL subquery, not an inheritance parent.
*/
while (appinfo &&
planner_rt_fetch(appinfo->parent_relid,
root)->rtekind == RTE_RELATION)
{
Bitmapset *parent_varattnos = NULL;
/*
* For each child attribute, find the corresponding parent
* attribute. In rare cases, the attribute may be local to the
* child table, in which case, we've got to live with having no
* access to this column.
*/
varattno = -1;
while ((varattno = bms_next_member(varattnos, varattno)) >= 0)
{
AttrNumber attno;
AttrNumber parent_attno;
attno = varattno + FirstLowInvalidHeapAttributeNumber;
if (attno == InvalidAttrNumber)
{
/*
* Whole-row reference, so must map each column of the
* child to the parent table.
*/
for (attno = 1; attno <= appinfo->num_child_cols; attno++)
{
parent_attno = appinfo->parent_colnos[attno - 1];
if (parent_attno == 0)
return false; /* attr is local to child */
parent_varattnos =
bms_add_member(parent_varattnos,
parent_attno - FirstLowInvalidHeapAttributeNumber);
}
}
else
{
if (attno < 0)
{
/* System attnos are the same in all tables */
parent_attno = attno;
}
else
{
if (attno > appinfo->num_child_cols)
return false; /* safety check */
parent_attno = appinfo->parent_colnos[attno - 1];
if (parent_attno == 0)
return false; /* attr is local to child */
}
parent_varattnos =
bms_add_member(parent_varattnos,
parent_attno - FirstLowInvalidHeapAttributeNumber);
}
}
/* If the parent is itself a child, continue up */
varno = appinfo->parent_relid;
varattnos = parent_varattnos;
appinfo = root->append_rel_array[varno];
}
/* Perform the access check on this parent rel */
rte = planner_rt_fetch(varno, root);
Assert(rte->rtekind == RTE_RELATION);
}
/*
* For all rows to be accessible, there must be no securityQuals from
* security barrier views or RLS policies.
*/
if (rte->securityQuals != NIL)
return false;
/*
* Test for table-level SELECT privilege.
*
* If varattnos is non-NULL, this is sufficient to give access to all
* requested attributes, even for a child table, since we have verified
* that all required child columns have matching parent columns.
*
* If varattnos is NULL (whole-table access requested), this doesn't
* necessarily guarantee that the user can read all columns of a child
* table, but we allow it anyway (see comments in examine_variable()) and
* don't bother checking any column privileges.
*/
if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) == ACLCHECK_OK)
return true;
if (varattnos == NULL)
return false; /* whole-table access requested */
/*
* Don't have table-level SELECT privilege, so check per-column
* privileges.
*/
varattno = -1;
while ((varattno = bms_next_member(varattnos, varattno)) >= 0)
{
AttrNumber attno = varattno + FirstLowInvalidHeapAttributeNumber;
if (attno == InvalidAttrNumber)
{
/* Whole-row reference, so must have access to all columns */
if (pg_attribute_aclcheck_all(rte->relid, userid, ACL_SELECT,
ACLMASK_ALL) != ACLCHECK_OK)
return false;
}
else
{
if (pg_attribute_aclcheck(rte->relid, attno, userid,
ACL_SELECT) != ACLCHECK_OK)
return false;
}
}
/* If we reach here, have all required column privileges */
return true;
}
/*
* examine_indexcol_variable
* Try to look up statistical data about an index column/expression.
* Fill in a VariableStatData struct to describe the column.
*
* Inputs:
* root: the planner info
* index: the index whose column we're interested in
* indexcol: 0-based index column number (subscripts index->indexkeys[])
*
* Outputs: *vardata is filled as follows:
* var: the input expression (with any binary relabeling stripped, if
* it is or contains a variable; but otherwise the type is preserved)
* rel: RelOptInfo for table relation containing variable.
* statsTuple: the pg_statistic entry for the variable, if one exists;
* otherwise NULL.
* freefunc: pointer to a function to release statsTuple with.
*
* Caller is responsible for doing ReleaseVariableStats() before exiting.
*/
static void
examine_indexcol_variable(PlannerInfo *root, IndexOptInfo *index,
int indexcol, VariableStatData *vardata)
{
AttrNumber colnum;
Oid relid;
if (index->indexkeys[indexcol] != 0)
{
/* Simple variable --- look to stats for the underlying table */
RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
Assert(rte->rtekind == RTE_RELATION);
relid = rte->relid;
Assert(relid != InvalidOid);
colnum = index->indexkeys[indexcol];
vardata->rel = index->rel;
if (get_relation_stats_hook &&
(*get_relation_stats_hook) (root, rte, colnum, vardata))
{
/*
* The hook took control of acquiring a stats tuple. If it did
* supply a tuple, it'd better have supplied a freefunc.
*/
if (HeapTupleIsValid(vardata->statsTuple) &&
!vardata->freefunc)
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;
}
}
else
{
/* Expression --- maybe there are stats for the index itself */
relid = index->indexoid;
colnum = indexcol + 1;
if (get_index_stats_hook &&
(*get_index_stats_hook) (root, relid, colnum, vardata))
{
/*
* The hook took control of acquiring a stats tuple. If it did
* supply a tuple, it'd better have supplied a freefunc.
*/
if (HeapTupleIsValid(vardata->statsTuple) &&
!vardata->freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
else
{
vardata->statsTuple = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(colnum),
BoolGetDatum(false));
vardata->freefunc = ReleaseSysCache;
}
}
}
/*
* Check whether it is permitted to call func_oid passing some of the
* pg_statistic data in vardata. We allow this if either of the following
* conditions is met: (1) the user has SELECT privileges on the table or
* column underlying the pg_statistic data and there are no securityQuals from
* security barrier views or RLS policies, or (2) the function is marked
* leakproof.
*/
bool
statistic_proc_security_check(VariableStatData *vardata, Oid func_oid)
{
if (vardata->acl_ok)
return true; /* have SELECT privs and no securityQuals */
if (!OidIsValid(func_oid))
return false;
if (get_func_leakproof(func_oid))
return true;
ereport(DEBUG2,
(errmsg_internal("not using statistics because function \"%s\" is not leakproof",
get_func_name(func_oid))));
return false;
}
/*
* get_variable_numdistinct
* Estimate the number of distinct values of a variable.
*
* vardata: results of examine_variable
* *isdefault: set to true if the result is a default rather than based on
* anything meaningful.
*
* NB: be careful to produce a positive integral result, since callers may
* compare the result to exact integer counts, or might divide by it.
*/
double
get_variable_numdistinct(VariableStatData *vardata, bool *isdefault)
{
double stadistinct;
double stanullfrac = 0.0;
double ntuples;
*isdefault = false;
/*
* Determine the stadistinct value to use. There are cases where we can
* get an estimate even without a pg_statistic entry, or can get a better
* value than is in pg_statistic. Grab stanullfrac too if we can find it
* (otherwise, assume no nulls, for lack of any better idea).
*/
if (HeapTupleIsValid(vardata->statsTuple))
{
/* Use the pg_statistic entry */
Form_pg_statistic stats;
stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
stadistinct = stats->stadistinct;
stanullfrac = stats->stanullfrac;
}
else if (vardata->vartype == BOOLOID)
{
/*
* Special-case boolean columns: presumably, two distinct values.
*
* Are there any other datatypes we should wire in special estimates
* for?
*/
stadistinct = 2.0;
}
else if (vardata->rel && vardata->rel->rtekind == RTE_VALUES)
{
/*
* If the Var represents a column of a VALUES RTE, assume it's unique.
* This could of course be very wrong, but it should tend to be true
* in well-written queries. We could consider examining the VALUES'
* contents to get some real statistics; but that only works if the
* entries are all constants, and it would be pretty expensive anyway.
*/
stadistinct = -1.0; /* unique (and all non null) */
}
else
{
/*
* We don't keep statistics for system columns, but in some cases we
* can infer distinctness anyway.
*/
if (vardata->var && IsA(vardata->var, Var))
{
switch (((Var *) vardata->var)->varattno)
{
case SelfItemPointerAttributeNumber:
stadistinct = -1.0; /* unique (and all non null) */
break;
case TableOidAttributeNumber:
stadistinct = 1.0; /* only 1 value */
break;
default:
stadistinct = 0.0; /* means "unknown" */
break;
}
}
else
stadistinct = 0.0; /* means "unknown" */
/*
* XXX consider using estimate_num_groups on expressions?
*/
}
/*
* If there is a unique index, DISTINCT or GROUP-BY clause for the
* variable, assume it is unique no matter what pg_statistic says; the
* statistics could be out of date, or we might have found a partial
* unique index that proves the var is unique for this query. However,
* we'd better still believe the null-fraction statistic.
*/
if (vardata->isunique)
stadistinct = -1.0 * (1.0 - stanullfrac);
/*
* If we had an absolute estimate, use that.
*/
if (stadistinct > 0.0)
return clamp_row_est(stadistinct);
/*
* Otherwise we need to get the relation size; punt if not available.
*/
if (vardata->rel == NULL)
{
*isdefault = true;
return DEFAULT_NUM_DISTINCT;
}
ntuples = vardata->rel->tuples;
if (ntuples <= 0.0)
{
*isdefault = true;
return DEFAULT_NUM_DISTINCT;
}
/*
* If we had a relative estimate, use that.
*/
if (stadistinct < 0.0)
return clamp_row_est(-stadistinct * ntuples);
/*
* With no data, estimate ndistinct = ntuples if the table is small, else
* use default. We use DEFAULT_NUM_DISTINCT as the cutoff for "small" so
* that the behavior isn't discontinuous.
*/
if (ntuples < DEFAULT_NUM_DISTINCT)
return clamp_row_est(ntuples);
*isdefault = true;
return DEFAULT_NUM_DISTINCT;
}
/*
* get_variable_range
* Estimate the minimum and maximum value of the specified variable.
* If successful, store values in *min and *max, and return true.
* If no data available, return false.
*
* sortop is the "<" comparison operator to use. This should generally
* be "<" not ">", as only the former is likely to be found in pg_statistic.
* The collation must be specified too.
*/
static bool
get_variable_range(PlannerInfo *root, VariableStatData *vardata,
Oid sortop, Oid collation,
Datum *min, Datum *max)
{
Datum tmin = 0;
Datum tmax = 0;
bool have_data = false;
int16 typLen;
bool typByVal;
Oid opfuncoid;
FmgrInfo opproc;
AttStatsSlot sslot;
/*
* XXX It's very tempting to try to use the actual column min and max, if
* we can get them relatively-cheaply with an index probe. However, since
* this function is called many times during join planning, that could
* have unpleasant effects on planning speed. Need more investigation
* before enabling this.
*/
#ifdef NOT_USED
if (get_actual_variable_range(root, vardata, sortop, collation, min, max))
return true;
#endif
if (!HeapTupleIsValid(vardata->statsTuple))
{
/* no stats available, so default result */
return false;
}
/*
* If we can't apply the sortop to the stats data, just fail. In
* principle, if there's a histogram and no MCVs, we could return the
* histogram endpoints without ever applying the sortop ... but it's
* probably not worth trying, because whatever the caller wants to do with
* the endpoints would likely fail the security check too.
*/
if (!statistic_proc_security_check(vardata,
(opfuncoid = get_opcode(sortop))))
return false;
opproc.fn_oid = InvalidOid; /* mark this as not looked up yet */
get_typlenbyval(vardata->atttype, &typLen, &typByVal);
/*
* If there is a histogram with the ordering we want, grab the first and
* last values.
*/
if (get_attstatsslot(&sslot, vardata->statsTuple,
STATISTIC_KIND_HISTOGRAM, sortop,
ATTSTATSSLOT_VALUES))
{
if (sslot.stacoll == collation && sslot.nvalues > 0)
{
tmin = datumCopy(sslot.values[0], typByVal, typLen);
tmax = datumCopy(sslot.values[sslot.nvalues - 1], typByVal, typLen);
have_data = true;
}
free_attstatsslot(&sslot);
}
/*
* Otherwise, if there is a histogram with some other ordering, scan it
* and get the min and max values according to the ordering we want. This
* of course may not find values that are really extremal according to our
* ordering, but it beats ignoring available data.
*/
if (!have_data &&
get_attstatsslot(&sslot, vardata->statsTuple,
STATISTIC_KIND_HISTOGRAM, InvalidOid,
ATTSTATSSLOT_VALUES))
{
get_stats_slot_range(&sslot, opfuncoid, &opproc,
collation, typLen, typByVal,
&tmin, &tmax, &have_data);
free_attstatsslot(&sslot);
}
/*
* If we have most-common-values info, look for extreme MCVs. This is
* needed even if we also have a histogram, since the histogram excludes
* the MCVs. However, if we *only* have MCVs and no histogram, we should
* be pretty wary of deciding that that is a full representation of the
* data. Proceed only if the MCVs represent the whole table (to within
* roundoff error).
*/
if (get_attstatsslot(&sslot, vardata->statsTuple,
STATISTIC_KIND_MCV, InvalidOid,
have_data ? ATTSTATSSLOT_VALUES :
(ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)))
{
bool use_mcvs = have_data;
if (!have_data)
{
double sumcommon = 0.0;
double nullfrac;
int i;
for (i = 0; i < sslot.nnumbers; i++)
sumcommon += sslot.numbers[i];
nullfrac = ((Form_pg_statistic) GETSTRUCT(vardata->statsTuple))->stanullfrac;
if (sumcommon + nullfrac > 0.99999)
use_mcvs = true;
}
if (use_mcvs)
get_stats_slot_range(&sslot, opfuncoid, &opproc,
collation, typLen, typByVal,
&tmin, &tmax, &have_data);
free_attstatsslot(&sslot);
}
*min = tmin;
*max = tmax;
return have_data;
}
/*
* get_stats_slot_range: scan sslot for min/max values
*
* Subroutine for get_variable_range: update min/max/have_data according
* to what we find in the statistics array.
*/
static void
get_stats_slot_range(AttStatsSlot *sslot, Oid opfuncoid, FmgrInfo *opproc,
Oid collation, int16 typLen, bool typByVal,
Datum *min, Datum *max, bool *p_have_data)
{
Datum tmin = *min;
Datum tmax = *max;
bool have_data = *p_have_data;
bool found_tmin = false;
bool found_tmax = false;
/* Look up the comparison function, if we didn't already do so */
if (opproc->fn_oid != opfuncoid)
fmgr_info(opfuncoid, opproc);
/* Scan all the slot's values */
for (int i = 0; i < sslot->nvalues; i++)
{
if (!have_data)
{
tmin = tmax = sslot->values[i];
found_tmin = found_tmax = true;
*p_have_data = have_data = true;
continue;
}
if (DatumGetBool(FunctionCall2Coll(opproc,
collation,
sslot->values[i], tmin)))
{
tmin = sslot->values[i];
found_tmin = true;
}
if (DatumGetBool(FunctionCall2Coll(opproc,
collation,
tmax, sslot->values[i])))
{
tmax = sslot->values[i];
found_tmax = true;
}
}
/*
* Copy the slot's values, if we found new extreme values.
*/
if (found_tmin)
*min = datumCopy(tmin, typByVal, typLen);
if (found_tmax)
*max = datumCopy(tmax, typByVal, typLen);
}
/*
* get_actual_variable_range
* Attempt to identify the current *actual* minimum and/or maximum
* of the specified variable, by looking for a suitable btree index
* and fetching its low and/or high values.
* If successful, store values in *min and *max, and return true.
* (Either pointer can be NULL if that endpoint isn't needed.)
* If unsuccessful, return false.
*
* sortop is the "<" comparison operator to use.
* collation is the required collation.
*/
static bool
get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
Oid sortop, Oid collation,
Datum *min, Datum *max)
{
bool have_data = false;
RelOptInfo *rel = vardata->rel;
RangeTblEntry *rte;
ListCell *lc;
/* No hope if no relation or it doesn't have indexes */
if (rel == NULL || rel->indexlist == NIL)
return false;
/* If it has indexes it must be a plain relation */
rte = root->simple_rte_array[rel->relid];
Assert(rte->rtekind == RTE_RELATION);
/* ignore partitioned tables. Any indexes here are not real indexes */
if (rte->relkind == RELKIND_PARTITIONED_TABLE)
return false;
/* Search through the indexes to see if any match our problem */
foreach(lc, rel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
ScanDirection indexscandir;
StrategyNumber strategy;
/* Ignore non-ordering indexes */
if (index->sortopfamily == NULL)
continue;
/*
* Ignore partial indexes --- we only want stats that cover the entire
* relation.
*/
if (index->indpred != NIL)
continue;
/*
* The index list might include hypothetical indexes inserted by a
* get_relation_info hook --- don't try to access them.
*/
if (index->hypothetical)
continue;
/*
* get_actual_variable_endpoint uses the index-only-scan machinery, so
* ignore indexes that can't use it on their first column.
*/
if (!index->canreturn[0])
continue;
/*
* The first index column must match the desired variable, sortop, and
* collation --- but we can use a descending-order index.
*/
if (collation != index->indexcollations[0])
continue; /* test first 'cause it's cheapest */
if (!match_index_to_operand(vardata->var, 0, index))
continue;
strategy = get_op_opfamily_strategy(sortop, index->sortopfamily[0]);
switch (IndexAmTranslateStrategy(strategy, index->relam, index->sortopfamily[0], true))
{
case COMPARE_LT:
if (index->reverse_sort[0])
indexscandir = BackwardScanDirection;
else
indexscandir = ForwardScanDirection;
break;
case COMPARE_GT:
if (index->reverse_sort[0])
indexscandir = ForwardScanDirection;
else
indexscandir = BackwardScanDirection;
break;
default:
/* index doesn't match the sortop */
continue;
}
/*
* Found a suitable index to extract data from. Set up some data that
* can be used by both invocations of get_actual_variable_endpoint.
*/
{
MemoryContext tmpcontext;
MemoryContext oldcontext;
Relation heapRel;
Relation indexRel;
TupleTableSlot *slot;
int16 typLen;
bool typByVal;
ScanKeyData scankeys[1];
/* Make sure any cruft gets recycled when we're done */
tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
"get_actual_variable_range workspace",
ALLOCSET_DEFAULT_SIZES);
oldcontext = MemoryContextSwitchTo(tmpcontext);
/*
* Open the table and index so we can read from them. We should
* already have some type of lock on each.
*/
heapRel = table_open(rte->relid, NoLock);
indexRel = index_open(index->indexoid, NoLock);
/* build some stuff needed for indexscan execution */
slot = table_slot_create(heapRel, NULL);
get_typlenbyval(vardata->atttype, &typLen, &typByVal);
/* set up an IS NOT NULL scan key so that we ignore nulls */
ScanKeyEntryInitialize(&scankeys[0],
SK_ISNULL | SK_SEARCHNOTNULL,
1, /* index col to scan */
InvalidStrategy, /* no strategy */
InvalidOid, /* no strategy subtype */
InvalidOid, /* no collation */
InvalidOid, /* no reg proc for this */
(Datum) 0); /* constant */
/* If min is requested ... */
if (min)
{
have_data = get_actual_variable_endpoint(heapRel,
indexRel,
indexscandir,
scankeys,
typLen,
typByVal,
slot,
oldcontext,
min);
}
else
{
/* If min not requested, still want to fetch max */
have_data = true;
}
/* If max is requested, and we didn't already fail ... */
if (max && have_data)
{
/* scan in the opposite direction; all else is the same */
have_data = get_actual_variable_endpoint(heapRel,
indexRel,
-indexscandir,
scankeys,
typLen,
typByVal,
slot,
oldcontext,
max);
}
/* Clean everything up */
ExecDropSingleTupleTableSlot(slot);
index_close(indexRel, NoLock);
table_close(heapRel, NoLock);
MemoryContextSwitchTo(oldcontext);
MemoryContextDelete(tmpcontext);
/* And we're done */
break;
}
}
return have_data;
}
/*
* Get one endpoint datum (min or max depending on indexscandir) from the
* specified index. Return true if successful, false if not.
* On success, endpoint value is stored to *endpointDatum (and copied into
* outercontext).
*
* scankeys is a 1-element scankey array set up to reject nulls.
* typLen/typByVal describe the datatype of the index's first column.
* tableslot is a slot suitable to hold table tuples, in case we need
* to probe the heap.
* (We could compute these values locally, but that would mean computing them
* twice when get_actual_variable_range needs both the min and the max.)
*
* Failure occurs either when the index is empty, or we decide that it's
* taking too long to find a suitable tuple.
*/
static bool
get_actual_variable_endpoint(Relation heapRel,
Relation indexRel,
ScanDirection indexscandir,
ScanKey scankeys,
int16 typLen,
bool typByVal,
TupleTableSlot *tableslot,
MemoryContext outercontext,
Datum *endpointDatum)
{
bool have_data = false;
SnapshotData SnapshotNonVacuumable;
IndexScanDesc index_scan;
Buffer vmbuffer = InvalidBuffer;
BlockNumber last_heap_block = InvalidBlockNumber;
int n_visited_heap_pages = 0;
ItemPointer tid;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
MemoryContext oldcontext;
/*
* We use the index-only-scan machinery for this. With mostly-static
* tables that's a win because it avoids a heap visit. It's also a win
* for dynamic data, but the reason is less obvious; read on for details.
*
* In principle, we should scan the index with our current active
* snapshot, which is the best approximation we've got to what the query
* will see when executed. But that won't be exact if a new snap is taken
* before running the query, and it can be very expensive if a lot of
* recently-dead or uncommitted rows exist at the beginning or end of the
* index (because we'll laboriously fetch each one and reject it).
* Instead, we use SnapshotNonVacuumable. That will accept recently-dead
* and uncommitted rows as well as normal visible rows. On the other
* hand, it will reject known-dead rows, and thus not give a bogus answer
* when the extreme value has been deleted (unless the deletion was quite
* recent); that case motivates not using SnapshotAny here.
*
* A crucial point here is that SnapshotNonVacuumable, with
* GlobalVisTestFor(heapRel) as horizon, yields the inverse of the
* condition that the indexscan will use to decide that index entries are
* killable (see heap_hot_search_buffer()). Therefore, if the snapshot
* rejects a tuple (or more precisely, all tuples of a HOT chain) and we
* have to continue scanning past it, we know that the indexscan will mark
* that index entry killed. That means that the next
* get_actual_variable_endpoint() call will not have to re-consider that
* index entry. In this way we avoid repetitive work when this function
* is used a lot during planning.
*
* But using SnapshotNonVacuumable creates a hazard of its own. In a
* recently-created index, some index entries may point at "broken" HOT
* chains in which not all the tuple versions contain data matching the
* index entry. The live tuple version(s) certainly do match the index,
* but SnapshotNonVacuumable can accept recently-dead tuple versions that
* don't match. Hence, if we took data from the selected heap tuple, we
* might get a bogus answer that's not close to the index extremal value,
* or could even be NULL. We avoid this hazard because we take the data
* from the index entry not the heap.
*
* Despite all this care, there are situations where we might find many
* non-visible tuples near the end of the index. We don't want to expend
* a huge amount of time here, so we give up once we've read too many heap
* pages. When we fail for that reason, the caller will end up using
* whatever extremal value is recorded in pg_statistic.
*/
InitNonVacuumableSnapshot(SnapshotNonVacuumable,
GlobalVisTestFor(heapRel));
index_scan = index_beginscan(heapRel, indexRel,
&SnapshotNonVacuumable, NULL,
1, 0,
SO_NONE);
/* Set it up for index-only scan */
index_scan->xs_want_itup = true;
index_rescan(index_scan, scankeys, 1, NULL, 0);
/* Fetch first/next tuple in specified direction */
while ((tid = index_getnext_tid(index_scan, indexscandir)) != NULL)
{
BlockNumber block = ItemPointerGetBlockNumber(tid);
if (!VM_ALL_VISIBLE(heapRel,
block,
&vmbuffer))
{
/* Rats, we have to visit the heap to check visibility */
if (!index_fetch_heap(index_scan, tableslot))
{
/*
* No visible tuple for this index entry, so we need to
* advance to the next entry. Before doing so, count heap
* page fetches and give up if we've done too many.
*
* We don't charge a page fetch if this is the same heap page
* as the previous tuple. This is on the conservative side,
* since other recently-accessed pages are probably still in
* buffers too; but it's good enough for this heuristic.
*/
#define VISITED_PAGES_LIMIT 100
if (block != last_heap_block)
{
last_heap_block = block;
n_visited_heap_pages++;
if (n_visited_heap_pages > VISITED_PAGES_LIMIT)
break;
}
continue; /* no visible tuple, try next index entry */
}
/* We don't actually need the heap tuple for anything */
ExecClearTuple(tableslot);
/*
* We don't care whether there's more than one visible tuple in
* the HOT chain; if any are visible, that's good enough.
*/
}
/*
* We expect that the index will return data in IndexTuple not
* HeapTuple format.
*/
if (!index_scan->xs_itup)
elog(ERROR, "no data returned for index-only scan");
/*
* We do not yet support recheck here.
*/
if (index_scan->xs_recheck)
break;
/* OK to deconstruct the index tuple */
index_deform_tuple(index_scan->xs_itup,
index_scan->xs_itupdesc,
values, isnull);
/* Shouldn't have got a null, but be careful */
if (isnull[0])
elog(ERROR, "found unexpected null value in index \"%s\"",
RelationGetRelationName(indexRel));
/* Copy the index column value out to caller's context */
oldcontext = MemoryContextSwitchTo(outercontext);
*endpointDatum = datumCopy(values[0], typByVal, typLen);
MemoryContextSwitchTo(oldcontext);
have_data = true;
break;
}
if (vmbuffer != InvalidBuffer)
ReleaseBuffer(vmbuffer);
index_endscan(index_scan);
return have_data;
}
/*
* find_join_input_rel
* Look up the input relation for a join.
*
* We assume that the input relation's RelOptInfo must have been constructed
* already.
*/
static RelOptInfo *
find_join_input_rel(PlannerInfo *root, Relids relids)
{
RelOptInfo *rel = NULL;
if (!bms_is_empty(relids))
{
int relid;
if (bms_get_singleton_member(relids, &relid))
rel = find_base_rel(root, relid);
else
rel = find_join_rel(root, relids);
}
if (rel == NULL)
elog(ERROR, "could not find RelOptInfo for given relids");
return rel;
}
/*-------------------------------------------------------------------------
*
* Index cost estimation functions
*
*-------------------------------------------------------------------------
*/
/*
* Extract the actual indexquals (as RestrictInfos) from an IndexClause list
*/
List *
get_quals_from_indexclauses(List *indexclauses)
{
List *result = NIL;
ListCell *lc;
foreach(lc, indexclauses)
{
IndexClause *iclause = lfirst_node(IndexClause, lc);
ListCell *lc2;
foreach(lc2, iclause->indexquals)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2);
result = lappend(result, rinfo);
}
}
return result;
}
/*
* Compute the total evaluation cost of the comparison operands in a list
* of index qual expressions. Since we know these will be evaluated just
* once per scan, there's no need to distinguish startup from per-row cost.
*
* This can be used either on the result of get_quals_from_indexclauses(),
* or directly on an indexorderbys list. In both cases, we expect that the
* index key expression is on the left side of binary clauses.
*/
Cost
index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
{
Cost qual_arg_cost = 0;
ListCell *lc;
foreach(lc, indexquals)
{
Expr *clause = (Expr *) lfirst(lc);
Node *other_operand;
QualCost index_qual_cost;
/*
* Index quals will have RestrictInfos, indexorderbys won't. Look
* through RestrictInfo if present.
*/
if (IsA(clause, RestrictInfo))
clause = ((RestrictInfo *) clause)->clause;
if (IsA(clause, OpExpr))
{
OpExpr *op = (OpExpr *) clause;
other_operand = (Node *) lsecond(op->args);
}
else if (IsA(clause, RowCompareExpr))
{
RowCompareExpr *rc = (RowCompareExpr *) clause;
other_operand = (Node *) rc->rargs;
}
else if (IsA(clause, ScalarArrayOpExpr))
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
other_operand = (Node *) lsecond(saop->args);
}
else if (IsA(clause, NullTest))
{
other_operand = NULL;
}
else
{
elog(ERROR, "unsupported indexqual type: %d",
(int) nodeTag(clause));
other_operand = NULL; /* keep compiler quiet */
}
cost_qual_eval_node(&index_qual_cost, other_operand, root);
qual_arg_cost += index_qual_cost.startup + index_qual_cost.per_tuple;
}
return qual_arg_cost;
}
/*
* Compute generic index access cost estimates.
*
* See struct GenericCosts in selfuncs.h for more info.
*/
void
genericcostestimate(PlannerInfo *root,
IndexPath *path,
double loop_count,
GenericCosts *costs)
{
IndexOptInfo *index = path->indexinfo;
List *indexQuals = get_quals_from_indexclauses(path->indexclauses);
List *indexOrderBys = path->indexorderbys;
Cost indexStartupCost;
Cost indexTotalCost;
Selectivity indexSelectivity;
double indexCorrelation;
double numIndexPages;
double numIndexTuples;
double spc_random_page_cost;
double num_sa_scans;
double num_outer_scans;
double num_scans;
double qual_op_cost;
double qual_arg_cost;
List *selectivityQuals;
ListCell *l;
/*
* If the index is partial, AND the index predicate with the explicitly
* given indexquals to produce a more accurate idea of the index
* selectivity.
*/
selectivityQuals = add_predicate_to_index_quals(index, indexQuals);
/*
* If caller didn't give us an estimate for ScalarArrayOpExpr index scans,
* just assume that the number of index descents is the number of distinct
* combinations of array elements from all of the scan's SAOP clauses.
*/
num_sa_scans = costs->num_sa_scans;
if (num_sa_scans < 1)
{
num_sa_scans = 1;
foreach(l, indexQuals)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
if (IsA(rinfo->clause, ScalarArrayOpExpr))
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause;
double alength = estimate_array_length(root, lsecond(saop->args));
if (alength > 1)
num_sa_scans *= alength;
}
}
}
/* Estimate the fraction of main-table tuples that will be visited */
indexSelectivity = clauselist_selectivity(root, selectivityQuals,
index->rel->relid,
JOIN_INNER,
NULL);
/*
* If caller didn't give us an estimate, estimate the number of index
* tuples that will be visited. We do it in this rather peculiar-looking
* way in order to get the right answer for partial indexes.
*/
numIndexTuples = costs->numIndexTuples;
if (numIndexTuples <= 0.0)
{
numIndexTuples = indexSelectivity * index->rel->tuples;
/*
* The above calculation counts all the tuples visited across all
* scans induced by ScalarArrayOpExpr nodes. We want to consider the
* average per-indexscan number, so adjust. This is a handy place to
* round to integer, too. (If caller supplied tuple estimate, it's
* responsible for handling these considerations.)
*/
numIndexTuples = rint(numIndexTuples / num_sa_scans);
}
/*
* We can bound the number of tuples by the index size in any case. Also,
* always estimate at least one tuple is touched, even when
* indexSelectivity estimate is tiny.
*/
if (numIndexTuples > index->tuples)
numIndexTuples = index->tuples;
if (numIndexTuples < 1.0)
numIndexTuples = 1.0;
/*
* Estimate the number of index pages that will be retrieved.
*
* We use the simplistic method of taking a pro-rata fraction of the total
* number of index leaf pages. We disregard any overhead such as index
* metapages or upper tree levels.
*
* In practice access to upper index levels is often nearly free because
* those tend to stay in cache under load; moreover, the cost involved is
* highly dependent on index type. We therefore ignore such costs here
* and leave it to the caller to add a suitable charge if needed.
*/
if (index->pages > costs->numNonLeafPages && index->tuples > 1)
numIndexPages =
ceil(numIndexTuples * (index->pages - costs->numNonLeafPages)
/ index->tuples);
else
numIndexPages = 1.0;
/* fetch estimated page cost for tablespace containing index */
get_tablespace_page_costs(index->reltablespace,
&spc_random_page_cost,
NULL);
/*
* Now compute the disk access costs.
*
* The above calculations are all per-index-scan. However, if we are in a
* nestloop inner scan, we can expect the scan to be repeated (with
* different search keys) for each row of the outer relation. Likewise,
* ScalarArrayOpExpr quals result in multiple index scans. This creates
* the potential for cache effects to reduce the number of disk page
* fetches needed. We want to estimate the average per-scan I/O cost in
* the presence of caching.
*
* We use the Mackert-Lohman formula (see costsize.c for details) to
* estimate the total number of page fetches that occur. While this
* wasn't what it was designed for, it seems a reasonable model anyway.
* Note that we are counting pages not tuples anymore, so we take N = T =
* index size, as if there were one "tuple" per page.
*/
num_outer_scans = loop_count;
num_scans = num_sa_scans * num_outer_scans;
if (num_scans > 1)
{
double pages_fetched;
/* total page fetches ignoring cache effects */
pages_fetched = numIndexPages * num_scans;
/* use Mackert and Lohman formula to adjust for cache effects */
pages_fetched = index_pages_fetched(pages_fetched,
index->pages,
(double) index->pages,
root);
/*
* Now compute the total disk access cost, and then report a pro-rated
* share for each outer scan. (Don't pro-rate for ScalarArrayOpExpr,
* since that's internal to the indexscan.)
*/
indexTotalCost = (pages_fetched * spc_random_page_cost)
/ num_outer_scans;
}
else
{
/*
* For a single index scan, we just charge spc_random_page_cost per
* page touched.
*/
indexTotalCost = numIndexPages * spc_random_page_cost;
}
/*
* CPU cost: any complex expressions in the indexquals will need to be
* evaluated once at the start of the scan to reduce them to runtime keys
* to pass to the index AM (see nodeIndexscan.c). We model the per-tuple
* CPU costs as cpu_index_tuple_cost plus one cpu_operator_cost per
* indexqual operator. Because we have numIndexTuples as a per-scan
* number, we have to multiply by num_sa_scans to get the correct result
* for ScalarArrayOpExpr cases. Similarly add in costs for any index
* ORDER BY expressions.
*
* Note: this neglects the possible costs of rechecking lossy operators.
* Detecting that that might be needed seems more expensive than it's
* worth, though, considering all the other inaccuracies here ...
*/
qual_arg_cost = index_other_operands_eval_cost(root, indexQuals) +
index_other_operands_eval_cost(root, indexOrderBys);
qual_op_cost = cpu_operator_cost *
(list_length(indexQuals) + list_length(indexOrderBys));
indexStartupCost = qual_arg_cost;
indexTotalCost += qual_arg_cost;
indexTotalCost += numIndexTuples * num_sa_scans * (cpu_index_tuple_cost + qual_op_cost);
/*
* Generic assumption about index correlation: there isn't any.
*/
indexCorrelation = 0.0;
/*
* Return everything to caller.
*/
costs->indexStartupCost = indexStartupCost;
costs->indexTotalCost = indexTotalCost;
costs->indexSelectivity = indexSelectivity;
costs->indexCorrelation = indexCorrelation;
costs->numIndexPages = numIndexPages;
costs->numIndexTuples = numIndexTuples;
costs->spc_random_page_cost = spc_random_page_cost;
costs->num_sa_scans = num_sa_scans;
}
/*
* If the index is partial, add its predicate to the given qual list.
*
* ANDing the index predicate with the explicitly given indexquals produces
* a more accurate idea of the index's selectivity. However, we need to be
* careful not to insert redundant clauses, because clauselist_selectivity()
* is easily fooled into computing a too-low selectivity estimate. Our
* approach is to add only the predicate clause(s) that cannot be proven to
* be implied by the given indexquals. This successfully handles cases such
* as a qual "x = 42" used with a partial index "WHERE x >= 40 AND x < 50".
* There are many other cases where we won't detect redundancy, leading to a
* too-low selectivity estimate, which will bias the system in favor of using
* partial indexes where possible. That is not necessarily bad though.
*
* Note that indexQuals contains RestrictInfo nodes while the indpred
* does not, so the output list will be mixed. This is OK for both
* predicate_implied_by() and clauselist_selectivity(), but might be
* problematic if the result were passed to other things.
*/
List *
add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals)
{
List *predExtraQuals = NIL;
ListCell *lc;
if (index->indpredExpand == NIL)
return indexQuals;
foreach(lc, index->indpredExpand)
{
Node *predQual = (Node *) lfirst(lc);
List *oneQual = list_make1(predQual);
if (!predicate_implied_by(oneQual, indexQuals, false))
predExtraQuals = list_concat(predExtraQuals, oneQual);
}
return list_concat(predExtraQuals, indexQuals);
}
/*
* Estimate correlation of btree index's first column.
*
* If we can get an estimate of the first column's ordering correlation C
* from pg_statistic, estimate the index correlation as C for a single-column
* index, or C * 0.75 for multiple columns. The idea here is that multiple
* columns dilute the importance of the first column's ordering, but don't
* negate it entirely.
*
* We already filled in the stats tuple for *vardata when called.
*/
static double
btcost_correlation(IndexOptInfo *index, VariableStatData *vardata)
{
Oid sortop;
AttStatsSlot sslot;
double indexCorrelation = 0;
Assert(HeapTupleIsValid(vardata->statsTuple));
sortop = get_opfamily_member(index->opfamily[0],
index->opcintype[0],
index->opcintype[0],
BTLessStrategyNumber);
if (OidIsValid(sortop) &&
get_attstatsslot(&sslot, vardata->statsTuple,
STATISTIC_KIND_CORRELATION, sortop,
ATTSTATSSLOT_NUMBERS))
{
double varCorrelation;
Assert(sslot.nnumbers == 1);
varCorrelation = sslot.numbers[0];
if (index->reverse_sort[0])
varCorrelation = -varCorrelation;
if (index->nkeycolumns > 1)
indexCorrelation = varCorrelation * 0.75;
else
indexCorrelation = varCorrelation;
free_attstatsslot(&sslot);
}
return indexCorrelation;
}
void
btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
Cost *indexStartupCost, Cost *indexTotalCost,
Selectivity *indexSelectivity, double *indexCorrelation,
double *indexPages)
{
IndexOptInfo *index = path->indexinfo;
GenericCosts costs = {0};
VariableStatData vardata = {0};
double numIndexTuples;
Cost descentCost;
List *indexBoundQuals;
List *indexSkipQuals;
int indexcol;
bool eqQualHere;
bool found_row_compare;
bool found_array;
bool found_is_null_op;
bool have_correlation = false;
double num_sa_scans;
double correlation = 0.0;
ListCell *lc;
/*
* For a btree scan, only leading '=' quals plus inequality quals for the
* immediately next attribute contribute to index selectivity (these are
* the "boundary quals" that determine the starting and stopping points of
* the index scan). Additional quals can suppress visits to the heap, so
* it's OK to count them in indexSelectivity, but they should not count
* for estimating numIndexTuples. So we must examine the given indexquals
* to find out which ones count as boundary quals. We rely on the
* knowledge that they are given in index column order. Note that nbtree
* preprocessing can add skip arrays that act as leading '=' quals in the
* absence of ordinary input '=' quals, so in practice _most_ input quals
* are able to act as index bound quals (which we take into account here).
*
* For a RowCompareExpr, we consider only the first column, just as
* rowcomparesel() does.
*
* If there's a SAOP or skip array in the quals, we'll actually perform up
* to N index descents (not just one), but the underlying array key's
* operator can be considered to act the same as it normally does.
*/
indexBoundQuals = NIL;
indexSkipQuals = NIL;
indexcol = 0;
eqQualHere = false;
found_row_compare = false;
found_array = false;
found_is_null_op = false;
num_sa_scans = 1;
foreach(lc, path->indexclauses)
{
IndexClause *iclause = lfirst_node(IndexClause, lc);
ListCell *lc2;
if (indexcol < iclause->indexcol)
{
double num_sa_scans_prev_cols = num_sa_scans;
/*
* Beginning of a new column's quals.
*
* Skip scans use skip arrays, which are ScalarArrayOp style
* arrays that generate their elements procedurally and on demand.
* Given a multi-column index on "(a, b)", and an SQL WHERE clause
* "WHERE b = 42", a skip scan will effectively use an indexqual
* "WHERE a = ANY('{every col a value}') AND b = 42". (Obviously,
* the array on "a" must also return "IS NULL" matches, since our
* WHERE clause used no strict operator on "a").
*
* Here we consider how nbtree will backfill skip arrays for any
* index columns that lacked an '=' qual. This maintains our
* num_sa_scans estimate, and determines if this new column (the
* "iclause->indexcol" column, not the prior "indexcol" column)
* can have its RestrictInfos/quals added to indexBoundQuals.
*
* We'll need to handle columns that have inequality quals, where
* the skip array generates values from a range constrained by the
* quals (not every possible value). We've been maintaining
* indexSkipQuals to help with this; it will now contain all of
* the prior column's quals (that is, indexcol's quals) when they
* might be used for this.
*/
if (found_row_compare)
{
/*
* Skip arrays can't be added after a RowCompare input qual
* due to limitations in nbtree
*/
break;
}
if (eqQualHere)
{
/*
* Don't need to add a skip array for an indexcol that already
* has an '=' qual/equality constraint
*/
indexcol++;
indexSkipQuals = NIL;
}
eqQualHere = false;
while (indexcol < iclause->indexcol)
{
double ndistinct;
bool isdefault = true;
found_array = true;
/*
* A skipped attribute's ndistinct forms the basis of our
* estimate of the total number of "array elements" used by
* its skip array at runtime. Look that up first.
*/
examine_indexcol_variable(root, index, indexcol, &vardata);
ndistinct = get_variable_numdistinct(&vardata, &isdefault);
if (indexcol == 0)
{
/*
* Get an estimate of the leading column's correlation in
* passing (avoids rereading variable stats below)
*/
if (HeapTupleIsValid(vardata.statsTuple))
correlation = btcost_correlation(index, &vardata);
have_correlation = true;
}
ReleaseVariableStats(vardata);
/*
* If ndistinct is a default estimate, conservatively assume
* that no skipping will happen at runtime
*/
if (isdefault)
{
num_sa_scans = num_sa_scans_prev_cols;
break; /* done building indexBoundQuals */
}
/*
* Apply indexcol's indexSkipQuals selectivity to ndistinct
*/
if (indexSkipQuals != NIL)
{
List *partialSkipQuals;
Selectivity ndistinctfrac;
/*
* If the index is partial, AND the index predicate with
* the index-bound quals to produce a more accurate idea
* of the number of distinct values for prior indexcol
*/
partialSkipQuals = add_predicate_to_index_quals(index,
indexSkipQuals);
ndistinctfrac = clauselist_selectivity(root, partialSkipQuals,
index->rel->relid,
JOIN_INNER,
NULL);
/*
* If ndistinctfrac is selective (on its own), the scan is
* unlikely to benefit from repositioning itself using
* later quals. Do not allow iclause->indexcol's quals to
* be added to indexBoundQuals (it would increase descent
* costs, without lowering numIndexTuples costs by much).
*/
if (ndistinctfrac < DEFAULT_RANGE_INEQ_SEL)
{
num_sa_scans = num_sa_scans_prev_cols;
break; /* done building indexBoundQuals */
}
/* Adjust ndistinct downward */
ndistinct = rint(ndistinct * ndistinctfrac);
ndistinct = Max(ndistinct, 1);
}
/*
* When there's no inequality quals, account for the need to
* find an initial value by counting -inf/+inf as a value.
*
* We don't charge anything extra for possible next/prior key
* index probes, which are sometimes used to find the next
* valid skip array element (ahead of using the located
* element value to relocate the scan to the next position
* that might contain matching tuples). It seems hard to do
* better here. Use of the skip support infrastructure often
* avoids most next/prior key probes. But even when it can't,
* there's a decent chance that most individual next/prior key
* probes will locate a leaf page whose key space overlaps all
* of the scan's keys (even the lower-order keys) -- which
* also avoids the need for a separate, extra index descent.
* Note also that these probes are much cheaper than non-probe
* primitive index scans: they're reliably very selective.
*/
if (indexSkipQuals == NIL)
ndistinct += 1;
/*
* Update num_sa_scans estimate by multiplying by ndistinct.
*
* We make the pessimistic assumption that there is no
* naturally occurring cross-column correlation. This is
* often wrong, but it seems best to err on the side of not
* expecting skipping to be helpful...
*/
num_sa_scans *= ndistinct;
/*
* ...but back out of adding this latest group of 1 or more
* skip arrays when num_sa_scans exceeds the total number of
* index pages (revert to num_sa_scans from before indexcol).
* This causes a sharp discontinuity in cost (as a function of
* the indexcol's ndistinct), but that is representative of
* actual runtime costs.
*
* Note that skipping is helpful when each primitive index
* scan only manages to skip over 1 or 2 irrelevant leaf pages
* on average. Skip arrays bring savings in CPU costs due to
* the scan not needing to evaluate indexquals against every
* tuple, which can greatly exceed any savings in I/O costs.
* This test is a test of whether num_sa_scans implies that
* we're past the point where the ability to skip ceases to
* lower the scan's costs (even qual evaluation CPU costs).
*/
if (index->pages < num_sa_scans)
{
num_sa_scans = num_sa_scans_prev_cols;
break; /* done building indexBoundQuals */
}
indexcol++;
indexSkipQuals = NIL;
}
/*
* Finished considering the need to add skip arrays to bridge an
* initial eqQualHere gap between the old and new index columns
* (or there was no initial eqQualHere gap in the first place).
*
* If an initial gap could not be bridged, then new column's quals
* (i.e. iclause->indexcol's quals) won't go into indexBoundQuals,
* and so won't affect our final numIndexTuples estimate.
*/
if (indexcol != iclause->indexcol)
break; /* done building indexBoundQuals */
}
Assert(indexcol == iclause->indexcol);
/* Examine each indexqual associated with this index clause */
foreach(lc2, iclause->indexquals)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2);
Expr *clause = rinfo->clause;
Oid clause_op = InvalidOid;
int op_strategy;
if (IsA(clause, OpExpr))
{
OpExpr *op = (OpExpr *) clause;
clause_op = op->opno;
}
else if (IsA(clause, RowCompareExpr))
{
RowCompareExpr *rc = (RowCompareExpr *) clause;
clause_op = linitial_oid(rc->opnos);
found_row_compare = true;
}
else if (IsA(clause, ScalarArrayOpExpr))
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
Node *other_operand = (Node *) lsecond(saop->args);
double alength = estimate_array_length(root, other_operand);
clause_op = saop->opno;
found_array = true;
/* estimate SA descents by indexBoundQuals only */
if (alength > 1)
num_sa_scans *= alength;
}
else if (IsA(clause, NullTest))
{
NullTest *nt = (NullTest *) clause;
if (nt->nulltesttype == IS_NULL)
{
found_is_null_op = true;
/* IS NULL is like = for selectivity/skip scan purposes */
eqQualHere = true;
}
}
else
elog(ERROR, "unsupported indexqual type: %d",
(int) nodeTag(clause));
/* check for equality operator */
if (OidIsValid(clause_op))
{
op_strategy = get_op_opfamily_strategy(clause_op,
index->opfamily[indexcol]);
Assert(op_strategy != 0); /* not a member of opfamily?? */
if (op_strategy == BTEqualStrategyNumber)
eqQualHere = true;
}
indexBoundQuals = lappend(indexBoundQuals, rinfo);
/*
* We apply inequality selectivities to estimate index descent
* costs with scans that use skip arrays. Save this indexcol's
* RestrictInfos if it looks like they'll be needed for that.
*/
if (!eqQualHere && !found_row_compare &&
indexcol < index->nkeycolumns - 1)
indexSkipQuals = lappend(indexSkipQuals, rinfo);
}
}
/*
* If index is unique and we found an '=' clause for each column, we can
* just assume numIndexTuples = 1 and skip the expensive
* clauselist_selectivity calculations. However, an array or NullTest
* always invalidates that theory (even when eqQualHere has been set).
*/
if (index->unique &&
indexcol == index->nkeycolumns - 1 &&
eqQualHere &&
!found_array &&
!found_is_null_op)
numIndexTuples = 1.0;
else
{
List *selectivityQuals;
Selectivity btreeSelectivity;
/*
* If the index is partial, AND the index predicate with the
* index-bound quals to produce a more accurate idea of the number of
* rows covered by the bound conditions.
*/
selectivityQuals = add_predicate_to_index_quals(index, indexBoundQuals);
btreeSelectivity = clauselist_selectivity(root, selectivityQuals,
index->rel->relid,
JOIN_INNER,
NULL);
numIndexTuples = btreeSelectivity * index->rel->tuples;
/*
* btree automatically combines individual array element primitive
* index scans whenever the tuples covered by the next set of array
* keys are close to tuples covered by the current set. That puts a
* natural ceiling on the worst case number of descents -- there
* cannot possibly be more than one descent per leaf page scanned.
*
* Clamp the number of descents to at most 1/3 the number of index
* pages. This avoids implausibly high estimates with low selectivity
* paths, where scans usually require only one or two descents. This
* is most likely to help when there are several SAOP clauses, where
* naively accepting the total number of distinct combinations of
* array elements as the number of descents would frequently lead to
* wild overestimates.
*
* We somewhat arbitrarily don't just make the cutoff the total number
* of leaf pages (we make it 1/3 the total number of pages instead) to
* give the btree code credit for its ability to continue on the leaf
* level with low selectivity scans.
*
* Note: num_sa_scans includes both ScalarArrayOp array elements and
* skip array elements whose qual affects our numIndexTuples estimate.
*/
num_sa_scans = Min(num_sa_scans, ceil(index->pages * 0.3333333));
num_sa_scans = Max(num_sa_scans, 1);
/*
* As in genericcostestimate(), we have to adjust for any array quals
* included in indexBoundQuals, and then round to integer.
*
* It is tempting to make genericcostestimate behave as if array
* clauses work in almost the same way as scalar operators during
* btree scans, making the top-level scan look like a continuous scan
* (as opposed to num_sa_scans-many primitive index scans). After
* all, btree scans mostly work like that at runtime. However, such a
* scheme would badly bias genericcostestimate's simplistic approach
* to calculating numIndexPages through prorating.
*
* Stick with the approach taken by non-native SAOP scans for now.
* genericcostestimate will use the Mackert-Lohman formula to
* compensate for repeat page fetches, even though that definitely
* won't happen during btree scans (not for leaf pages, at least).
* We're usually very pessimistic about the number of primitive index
* scans that will be required, but it's not clear how to do better.
*/
numIndexTuples = rint(numIndexTuples / num_sa_scans);
}
/*
* Now do generic index cost estimation.
*
* While we expended effort to make realistic estimates of numIndexTuples
* and num_sa_scans, we are content to count only the btree metapage as
* non-leaf. btree fanout is typically high enough that upper pages are
* few relative to leaf pages, so accounting for them would move the
* estimates at most a percent or two. Given the uncertainty in just how
* many upper pages exist in a particular index, we'll skip trying to
* handle that.
*/
costs.numIndexTuples = numIndexTuples;
costs.num_sa_scans = num_sa_scans;
costs.numNonLeafPages = 1;
genericcostestimate(root, path, loop_count, &costs);
/*
* Add a CPU-cost component to represent the costs of initial btree
* descent. We don't charge any I/O cost for touching upper btree levels,
* since they tend to stay in cache, but we still have to do about log2(N)
* comparisons to descend a btree of N leaf tuples. We charge one
* cpu_operator_cost per comparison.
*
* If there are SAOP or skip array keys, charge this once per estimated
* index descent. The ones after the first one are not startup cost so
* far as the overall plan goes, so just add them to "total" cost.
*/
if (index->tuples > 1) /* avoid computing log(0) */
{
descentCost = ceil(log(index->tuples) / log(2.0)) * cpu_operator_cost;
costs.indexStartupCost += descentCost;
costs.indexTotalCost += costs.num_sa_scans * descentCost;
}
/*
* Even though we're not charging I/O cost for touching upper btree pages,
* it's still reasonable to charge some CPU cost per page descended
* through. Moreover, if we had no such charge at all, bloated indexes
* would appear to have the same search cost as unbloated ones, at least
* in cases where only a single leaf page is expected to be visited. This
* cost is somewhat arbitrarily set at 50x cpu_operator_cost per page
* touched. The number of such pages is btree tree height plus one (ie,
* we charge for the leaf page too). As above, charge once per estimated
* SAOP/skip array descent.
*/
descentCost = (index->tree_height + 1) * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost;
costs.indexStartupCost += descentCost;
costs.indexTotalCost += costs.num_sa_scans * descentCost;
if (!have_correlation)
{
examine_indexcol_variable(root, index, 0, &vardata);
if (HeapTupleIsValid(vardata.statsTuple))
costs.indexCorrelation = btcost_correlation(index, &vardata);
ReleaseVariableStats(vardata);
}
else
{
/* btcost_correlation already called earlier on */
costs.indexCorrelation = correlation;
}
*indexStartupCost = costs.indexStartupCost;
*indexTotalCost = costs.indexTotalCost;
*indexSelectivity = costs.indexSelectivity;
*indexCorrelation = costs.indexCorrelation;
*indexPages = costs.numIndexPages;
}
void
hashcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
Cost *indexStartupCost, Cost *indexTotalCost,
Selectivity *indexSelectivity, double *indexCorrelation,
double *indexPages)
{
GenericCosts costs = {0};
/* As in btcostestimate, count only the metapage as non-leaf */
costs.numNonLeafPages = 1;
genericcostestimate(root, path, loop_count, &costs);
/*
* A hash index has no descent costs as such, since the index AM can go
* directly to the target bucket after computing the hash value. There
* are a couple of other hash-specific costs that we could conceivably add
* here, though:
*
* Ideally we'd charge spc_random_page_cost for each page in the target
* bucket, not just the numIndexPages pages that genericcostestimate
* thought we'd visit. However in most cases we don't know which bucket
* that will be. There's no point in considering the average bucket size
* because the hash AM makes sure that's always one page.
*
* Likewise, we could consider charging some CPU for each index tuple in
* the bucket, if we knew how many there were. But the per-tuple cost is
* just a hash value comparison, not a general datatype-dependent
* comparison, so any such charge ought to be quite a bit less than
* cpu_operator_cost; which makes it probably not worth worrying about.
*
* A bigger issue is that chance hash-value collisions will result in
* wasted probes into the heap. We don't currently attempt to model this
* cost on the grounds that it's rare, but maybe it's not rare enough.
* (Any fix for this ought to consider the generic lossy-operator problem,
* though; it's not entirely hash-specific.)
*/
*indexStartupCost = costs.indexStartupCost;
*indexTotalCost = costs.indexTotalCost;
*indexSelectivity = costs.indexSelectivity;
*indexCorrelation = costs.indexCorrelation;
*indexPages = costs.numIndexPages;
}
void
gistcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
Cost *indexStartupCost, Cost *indexTotalCost,
Selectivity *indexSelectivity, double *indexCorrelation,
double *indexPages)
{
IndexOptInfo *index = path->indexinfo;
GenericCosts costs = {0};
Cost descentCost;
/* GiST has no metapage, so we treat all pages as leaf pages */
genericcostestimate(root, path, loop_count, &costs);
/*
* We model index descent costs similarly to those for btree, but to do
* that we first need an idea of the tree height. We somewhat arbitrarily
* assume that the fanout is 100, meaning the tree height is at most
* log100(index->pages).
*
* Although this computation isn't really expensive enough to require
* caching, we might as well use index->tree_height to cache it.
*/
if (index->tree_height < 0) /* unknown? */
{
if (index->pages > 1) /* avoid computing log(0) */
index->tree_height = (int) (log(index->pages) / log(100.0));
else
index->tree_height = 0;
}
/*
* Add a CPU-cost component to represent the costs of initial descent. We
* just use log(N) here not log2(N) since the branching factor isn't
* necessarily two anyway. As for btree, charge once per SA scan.
*/
if (index->tuples > 1) /* avoid computing log(0) */
{
descentCost = ceil(log(index->tuples)) * cpu_operator_cost;
costs.indexStartupCost += descentCost;
costs.indexTotalCost += costs.num_sa_scans * descentCost;
}
/*
* Likewise add a per-page charge, calculated the same as for btrees.
*/
descentCost = (index->tree_height + 1) * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost;
costs.indexStartupCost += descentCost;
costs.indexTotalCost += costs.num_sa_scans * descentCost;
*indexStartupCost = costs.indexStartupCost;
*indexTotalCost = costs.indexTotalCost;
*indexSelectivity = costs.indexSelectivity;
*indexCorrelation = costs.indexCorrelation;
*indexPages = costs.numIndexPages;
}
void
spgcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
Cost *indexStartupCost, Cost *indexTotalCost,
Selectivity *indexSelectivity, double *indexCorrelation,
double *indexPages)
{
IndexOptInfo *index = path->indexinfo;
GenericCosts costs = {0};
Cost descentCost;
/* As in btcostestimate, count only the metapage as non-leaf */
costs.numNonLeafPages = 1;
genericcostestimate(root, path, loop_count, &costs);
/*
* We model index descent costs similarly to those for btree, but to do
* that we first need an idea of the tree height. We somewhat arbitrarily
* assume that the fanout is 100, meaning the tree height is at most
* log100(index->pages).
*
* Although this computation isn't really expensive enough to require
* caching, we might as well use index->tree_height to cache it.
*/
if (index->tree_height < 0) /* unknown? */
{
if (index->pages > 1) /* avoid computing log(0) */
index->tree_height = (int) (log(index->pages) / log(100.0));
else
index->tree_height = 0;
}
/*
* Add a CPU-cost component to represent the costs of initial descent. We
* just use log(N) here not log2(N) since the branching factor isn't
* necessarily two anyway. As for btree, charge once per SA scan.
*/
if (index->tuples > 1) /* avoid computing log(0) */
{
descentCost = ceil(log(index->tuples)) * cpu_operator_cost;
costs.indexStartupCost += descentCost;
costs.indexTotalCost += costs.num_sa_scans * descentCost;
}
/*
* Likewise add a per-page charge, calculated the same as for btrees.
*/
descentCost = (index->tree_height + 1) * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost;
costs.indexStartupCost += descentCost;
costs.indexTotalCost += costs.num_sa_scans * descentCost;
*indexStartupCost = costs.indexStartupCost;
*indexTotalCost = costs.indexTotalCost;
*indexSelectivity = costs.indexSelectivity;
*indexCorrelation = costs.indexCorrelation;
*indexPages = costs.numIndexPages;
}
/*
* Support routines for gincostestimate
*/
typedef struct
{
bool attHasFullScan[INDEX_MAX_KEYS];
bool attHasNormalScan[INDEX_MAX_KEYS];
double partialEntries;
double exactEntries;
double searchEntries;
double arrayScans;
} GinQualCounts;
/*
* Estimate the number of index terms that need to be searched for while
* testing the given GIN query, and increment the counts in *counts
* appropriately. If the query is unsatisfiable, return false.
*/
static bool
gincost_pattern(IndexOptInfo *index, int indexcol,
Oid clause_op, Datum query,
GinQualCounts *counts)
{
FmgrInfo flinfo;
Oid extractProcOid;
Oid collation;
int strategy_op;
Oid lefttype,
righttype;
int32 nentries = 0;
bool *partial_matches = NULL;
Pointer *extra_data = NULL;
bool *nullFlags = NULL;
int32 searchMode = GIN_SEARCH_MODE_DEFAULT;
int32 i;
Assert(indexcol < index->nkeycolumns);
/*
* Get the operator's strategy number and declared input data types within
* the index opfamily. (We don't need the latter, but we use
* get_op_opfamily_properties because it will throw error if it fails to
* find a matching pg_amop entry.)
*/
get_op_opfamily_properties(clause_op, index->opfamily[indexcol], false,
&strategy_op, &lefttype, &righttype);
/*
* GIN always uses the "default" support functions, which are those with
* lefttype == righttype == the opclass' opcintype (see
* IndexSupportInitialize in relcache.c).
*/
extractProcOid = get_opfamily_proc(index->opfamily[indexcol],
index->opcintype[indexcol],
index->opcintype[indexcol],
GIN_EXTRACTQUERY_PROC);
if (!OidIsValid(extractProcOid))
{
/* should not happen; throw same error as index_getprocinfo */
elog(ERROR, "missing support function %d for attribute %d of index \"%s\"",
GIN_EXTRACTQUERY_PROC, indexcol + 1,
get_rel_name(index->indexoid));
}
/*
* Choose collation to pass to extractProc (should match initGinState).
*/
if (OidIsValid(index->indexcollations[indexcol]))
collation = index->indexcollations[indexcol];
else
collation = DEFAULT_COLLATION_OID;
fmgr_info(extractProcOid, &flinfo);
set_fn_opclass_options(&flinfo, index->opclassoptions[indexcol]);
FunctionCall7Coll(&flinfo,
collation,
query,
PointerGetDatum(&nentries),
UInt16GetDatum(strategy_op),
PointerGetDatum(&partial_matches),
PointerGetDatum(&extra_data),
PointerGetDatum(&nullFlags),
PointerGetDatum(&searchMode));
if (nentries <= 0 && searchMode == GIN_SEARCH_MODE_DEFAULT)
{
/* No match is possible */
return false;
}
for (i = 0; i < nentries; i++)
{
/*
* For partial match we haven't any information to estimate number of
* matched entries in index, so, we just estimate it as 100
*/
if (partial_matches && partial_matches[i])
counts->partialEntries += 100;
else
counts->exactEntries++;
counts->searchEntries++;
}
if (searchMode == GIN_SEARCH_MODE_DEFAULT)
{
counts->attHasNormalScan[indexcol] = true;
}
else if (searchMode == GIN_SEARCH_MODE_INCLUDE_EMPTY)
{
/* Treat "include empty" like an exact-match item */
counts->attHasNormalScan[indexcol] = true;
counts->exactEntries++;
counts->searchEntries++;
}
else
{
/* It's GIN_SEARCH_MODE_ALL */
counts->attHasFullScan[indexcol] = true;
}
return true;
}
/*
* Estimate the number of index terms that need to be searched for while
* testing the given GIN index clause, and increment the counts in *counts
* appropriately. If the query is unsatisfiable, return false.
*/
static bool
gincost_opexpr(PlannerInfo *root,
IndexOptInfo *index,
int indexcol,
OpExpr *clause,
GinQualCounts *counts)
{
Oid clause_op = clause->opno;
Node *operand = (Node *) lsecond(clause->args);
/* aggressively reduce to a constant, and look through relabeling */
operand = estimate_expression_value(root, operand);
if (IsA(operand, RelabelType))
operand = (Node *) ((RelabelType *) operand)->arg;
/*
* It's impossible to call extractQuery method for unknown operand. So
* unless operand is a Const we can't do much; just assume there will be
* one ordinary search entry from the operand at runtime.
*/
if (!IsA(operand, Const))
{
counts->exactEntries++;
counts->searchEntries++;
return true;
}
/* If Const is null, there can be no matches */
if (((Const *) operand)->constisnull)
return false;
/* Otherwise, apply extractQuery and get the actual term counts */
return gincost_pattern(index, indexcol, clause_op,
((Const *) operand)->constvalue,
counts);
}
/*
* Estimate the number of index terms that need to be searched for while
* testing the given GIN index clause, and increment the counts in *counts
* appropriately. If the query is unsatisfiable, return false.
*
* A ScalarArrayOpExpr will give rise to N separate indexscans at runtime,
* each of which involves one value from the RHS array, plus all the
* non-array quals (if any). To model this, we average the counts across
* the RHS elements, and add the averages to the counts in *counts (which
* correspond to per-indexscan costs). We also multiply counts->arrayScans
* by N, causing gincostestimate to scale up its estimates accordingly.
*/
static bool
gincost_scalararrayopexpr(PlannerInfo *root,
IndexOptInfo *index,
int indexcol,
ScalarArrayOpExpr *clause,
double numIndexEntries,
GinQualCounts *counts)
{
Oid clause_op = clause->opno;
Node *rightop = (Node *) lsecond(clause->args);
ArrayType *arrayval;
int16 elmlen;
bool elmbyval;
char elmalign;
int numElems;
Datum *elemValues;
bool *elemNulls;
GinQualCounts arraycounts;
int numPossible = 0;
int i;
Assert(clause->useOr);
/* aggressively reduce to a constant, and look through relabeling */
rightop = estimate_expression_value(root, rightop);
if (IsA(rightop, RelabelType))
rightop = (Node *) ((RelabelType *) rightop)->arg;
/*
* It's impossible to call extractQuery method for unknown operand. So
* unless operand is a Const we can't do much; just assume there will be
* one ordinary search entry from each array entry at runtime, and fall
* back on a probably-bad estimate of the number of array entries.
*/
if (!IsA(rightop, Const))
{
counts->exactEntries++;
counts->searchEntries++;
counts->arrayScans *= estimate_array_length(root, rightop);
return true;
}
/* If Const is null, there can be no matches */
if (((Const *) rightop)->constisnull)
return false;
/* Otherwise, extract the array elements and iterate over them */
arrayval = DatumGetArrayTypeP(((Const *) rightop)->constvalue);
get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
&elmlen, &elmbyval, &elmalign);
deconstruct_array(arrayval,
ARR_ELEMTYPE(arrayval),
elmlen, elmbyval, elmalign,
&elemValues, &elemNulls, &numElems);
memset(&arraycounts, 0, sizeof(arraycounts));
for (i = 0; i < numElems; i++)
{
GinQualCounts elemcounts;
/* NULL can't match anything, so ignore, as the executor will */
if (elemNulls[i])
continue;
/* Otherwise, apply extractQuery and get the actual term counts */
memset(&elemcounts, 0, sizeof(elemcounts));
if (gincost_pattern(index, indexcol, clause_op, elemValues[i],
&elemcounts))
{
/* We ignore array elements that are unsatisfiable patterns */
numPossible++;
if (elemcounts.attHasFullScan[indexcol] &&
!elemcounts.attHasNormalScan[indexcol])
{
/*
* Full index scan will be required. We treat this as if
* every key in the index had been listed in the query; is
* that reasonable?
*/
elemcounts.partialEntries = 0;
elemcounts.exactEntries = numIndexEntries;
elemcounts.searchEntries = numIndexEntries;
}
arraycounts.partialEntries += elemcounts.partialEntries;
arraycounts.exactEntries += elemcounts.exactEntries;
arraycounts.searchEntries += elemcounts.searchEntries;
}
}
if (numPossible == 0)
{
/* No satisfiable patterns in the array */
return false;
}
/*
* Now add the averages to the global counts. This will give us an
* estimate of the average number of terms searched for in each indexscan,
* including contributions from both array and non-array quals.
*/
counts->partialEntries += arraycounts.partialEntries / numPossible;
counts->exactEntries += arraycounts.exactEntries / numPossible;
counts->searchEntries += arraycounts.searchEntries / numPossible;
counts->arrayScans *= numPossible;
return true;
}
/*
* GIN has search behavior completely different from other index types
*/
void
gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
Cost *indexStartupCost, Cost *indexTotalCost,
Selectivity *indexSelectivity, double *indexCorrelation,
double *indexPages)
{
IndexOptInfo *index = path->indexinfo;
List *indexQuals = get_quals_from_indexclauses(path->indexclauses);
List *selectivityQuals;
double numPages = index->pages,
numTuples = index->tuples;
double numEntryPages,
numDataPages,
numPendingPages,
numEntries;
GinQualCounts counts;
bool matchPossible;
bool fullIndexScan;
double partialScale;
double entryPagesFetched,
dataPagesFetched,
dataPagesFetchedBySel;
double qual_op_cost,
qual_arg_cost,
spc_random_page_cost,
outer_scans;
Cost descentCost;
Relation indexRel;
GinStatsData ginStats;
ListCell *lc;
int i;
/*
* Obtain statistical information from the meta page, if possible. Else
* set ginStats to zeroes, and we'll cope below.
*/
if (!index->hypothetical)
{
/* Lock should have already been obtained in plancat.c */
indexRel = index_open(index->indexoid, NoLock);
ginGetStats(indexRel, &ginStats);
index_close(indexRel, NoLock);
}
else
{
memset(&ginStats, 0, sizeof(ginStats));
}
/*
* Assuming we got valid (nonzero) stats at all, nPendingPages can be
* trusted, but the other fields are data as of the last VACUUM. We can
* scale them up to account for growth since then, but that method only
* goes so far; in the worst case, the stats might be for a completely
* empty index, and scaling them will produce pretty bogus numbers.
* Somewhat arbitrarily, set the cutoff for doing scaling at 4X growth; if
* it's grown more than that, fall back to estimating things only from the
* assumed-accurate index size. But we'll trust nPendingPages in any case
* so long as it's not clearly insane, ie, more than the index size.
*/
if (ginStats.nPendingPages < numPages)
numPendingPages = ginStats.nPendingPages;
else
numPendingPages = 0;
if (numPages > 0 && ginStats.nTotalPages <= numPages &&
ginStats.nTotalPages > numPages / 4 &&
ginStats.nEntryPages > 0 && ginStats.nEntries > 0)
{
/*
* OK, the stats seem close enough to sane to be trusted. But we
* still need to scale them by the ratio numPages / nTotalPages to
* account for growth since the last VACUUM.
*/
double scale = numPages / ginStats.nTotalPages;
numEntryPages = ceil(ginStats.nEntryPages * scale);
numDataPages = ceil(ginStats.nDataPages * scale);
numEntries = ceil(ginStats.nEntries * scale);
/* ensure we didn't round up too much */
numEntryPages = Min(numEntryPages, numPages - numPendingPages);
numDataPages = Min(numDataPages,
numPages - numPendingPages - numEntryPages);
}
else
{
/*
* We might get here because it's a hypothetical index, or an index
* created pre-9.1 and never vacuumed since upgrading (in which case
* its stats would read as zeroes), or just because it's grown too
* much since the last VACUUM for us to put our faith in scaling.
*
* Invent some plausible internal statistics based on the index page
* count (and clamp that to at least 10 pages, just in case). We
* estimate that 90% of the index is entry pages, and the rest is data
* pages. Estimate 100 entries per entry page; this is rather bogus
* since it'll depend on the size of the keys, but it's more robust
* than trying to predict the number of entries per heap tuple.
*/
numPages = Max(numPages, 10);
numEntryPages = floor((numPages - numPendingPages) * 0.90);
numDataPages = numPages - numPendingPages - numEntryPages;
numEntries = floor(numEntryPages * 100);
}
/* In an empty index, numEntries could be zero. Avoid divide-by-zero */
if (numEntries < 1)
numEntries = 1;
/*
* If the index is partial, AND the index predicate with the index-bound
* quals to produce a more accurate idea of the number of rows covered by
* the bound conditions.
*/
selectivityQuals = add_predicate_to_index_quals(index, indexQuals);
/* Estimate the fraction of main-table tuples that will be visited */
*indexSelectivity = clauselist_selectivity(root, selectivityQuals,
index->rel->relid,
JOIN_INNER,
NULL);
/* fetch estimated page cost for tablespace containing index */
get_tablespace_page_costs(index->reltablespace,
&spc_random_page_cost,
NULL);
/*
* Generic assumption about index correlation: there isn't any.
*/
*indexCorrelation = 0.0;
/*
* Examine quals to estimate number of search entries & partial matches
*/
memset(&counts, 0, sizeof(counts));
counts.arrayScans = 1;
matchPossible = true;
foreach(lc, path->indexclauses)
{
IndexClause *iclause = lfirst_node(IndexClause, lc);
ListCell *lc2;
foreach(lc2, iclause->indexquals)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2);
Expr *clause = rinfo->clause;
if (IsA(clause, OpExpr))
{
matchPossible = gincost_opexpr(root,
index,
iclause->indexcol,
(OpExpr *) clause,
&counts);
if (!matchPossible)
break;
}
else if (IsA(clause, ScalarArrayOpExpr))
{
matchPossible = gincost_scalararrayopexpr(root,
index,
iclause->indexcol,
(ScalarArrayOpExpr *) clause,
numEntries,
&counts);
if (!matchPossible)
break;
}
else
{
/* shouldn't be anything else for a GIN index */
elog(ERROR, "unsupported GIN indexqual type: %d",
(int) nodeTag(clause));
}
}
}
/* Fall out if there were any provably-unsatisfiable quals */
if (!matchPossible)
{
*indexStartupCost = 0;
*indexTotalCost = 0;
*indexSelectivity = 0;
return;
}
/*
* If attribute has a full scan and at the same time doesn't have normal
* scan, then we'll have to scan all non-null entries of that attribute.
* Currently, we don't have per-attribute statistics for GIN. Thus, we
* must assume the whole GIN index has to be scanned in this case.
*/
fullIndexScan = false;
for (i = 0; i < index->nkeycolumns; i++)
{
if (counts.attHasFullScan[i] && !counts.attHasNormalScan[i])
{
fullIndexScan = true;
break;
}
}
if (fullIndexScan || indexQuals == NIL)
{
/*
* Full index scan will be required. We treat this as if every key in
* the index had been listed in the query; is that reasonable?
*/
counts.partialEntries = 0;
counts.exactEntries = numEntries;
counts.searchEntries = numEntries;
}
/* Will we have more than one iteration of a nestloop scan? */
outer_scans = loop_count;
/*
* Compute cost to begin scan, first of all, pay attention to pending
* list.
*/
entryPagesFetched = numPendingPages;
/*
* Estimate number of entry pages read. We need to do
* counts.searchEntries searches. Use a power function as it should be,
* but tuples on leaf pages usually is much greater. Here we include all
* searches in entry tree, including search of first entry in partial
* match algorithm
*/
entryPagesFetched += ceil(counts.searchEntries * rint(pow(numEntryPages, 0.15)));
/*
* Add an estimate of entry pages read by partial match algorithm. It's a
* scan over leaf pages in entry tree. We haven't any useful stats here,
* so estimate it as proportion. Because counts.partialEntries is really
* pretty bogus (see code above), it's possible that it is more than
* numEntries; clamp the proportion to ensure sanity.
*/
partialScale = counts.partialEntries / numEntries;
partialScale = Min(partialScale, 1.0);
entryPagesFetched += ceil(numEntryPages * partialScale);
/*
* Partial match algorithm reads all data pages before doing actual scan,
* so it's a startup cost. Again, we haven't any useful stats here, so
* estimate it as proportion.
*/
dataPagesFetched = ceil(numDataPages * partialScale);
*indexStartupCost = 0;
*indexTotalCost = 0;
/*
* Add a CPU-cost component to represent the costs of initial entry btree
* descent. We don't charge any I/O cost for touching upper btree levels,
* since they tend to stay in cache, but we still have to do about log2(N)
* comparisons to descend a btree of N leaf tuples. We charge one
* cpu_operator_cost per comparison.
*
* If there are ScalarArrayOpExprs, charge this once per SA scan. The
* ones after the first one are not startup cost so far as the overall
* plan is concerned, so add them only to "total" cost.
*/
if (numEntries > 1) /* avoid computing log(0) */
{
descentCost = ceil(log(numEntries) / log(2.0)) * cpu_operator_cost;
*indexStartupCost += descentCost * counts.searchEntries;
*indexTotalCost += counts.arrayScans * descentCost * counts.searchEntries;
}
/*
* Add a cpu cost per entry-page fetched. This is not amortized over a
* loop.
*/
*indexStartupCost += entryPagesFetched * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost;
*indexTotalCost += entryPagesFetched * counts.arrayScans * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost;
/*
* Add a cpu cost per data-page fetched. This is also not amortized over a
* loop. Since those are the data pages from the partial match algorithm,
* charge them as startup cost.
*/
*indexStartupCost += DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost * dataPagesFetched;
/*
* Since we add the startup cost to the total cost later on, remove the
* initial arrayscan from the total.
*/
*indexTotalCost += dataPagesFetched * (counts.arrayScans - 1) * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost;
/*
* Calculate cache effects if more than one scan due to nestloops or array
* quals. The result is pro-rated per nestloop scan, but the array qual
* factor shouldn't be pro-rated (compare genericcostestimate).
*/
if (outer_scans > 1 || counts.arrayScans > 1)
{
entryPagesFetched *= outer_scans * counts.arrayScans;
entryPagesFetched = index_pages_fetched(entryPagesFetched,
(BlockNumber) numEntryPages,
numEntryPages, root);
entryPagesFetched /= outer_scans;
dataPagesFetched *= outer_scans * counts.arrayScans;
dataPagesFetched = index_pages_fetched(dataPagesFetched,
(BlockNumber) numDataPages,
numDataPages, root);
dataPagesFetched /= outer_scans;
}
/*
* Here we use random page cost because logically-close pages could be far
* apart on disk.
*/
*indexStartupCost += (entryPagesFetched + dataPagesFetched) * spc_random_page_cost;
/*
* Now compute the number of data pages fetched during the scan.
*
* We assume every entry to have the same number of items, and that there
* is no overlap between them. (XXX: tsvector and array opclasses collect
* statistics on the frequency of individual keys; it would be nice to use
* those here.)
*/
dataPagesFetched = ceil(numDataPages * counts.exactEntries / numEntries);
/*
* If there is a lot of overlap among the entries, in particular if one of
* the entries is very frequent, the above calculation can grossly
* under-estimate. As a simple cross-check, calculate a lower bound based
* on the overall selectivity of the quals. At a minimum, we must read
* one item pointer for each matching entry.
*
* The width of each item pointer varies, based on the level of
* compression. We don't have statistics on that, but an average of
* around 3 bytes per item is fairly typical.
*/
dataPagesFetchedBySel = ceil(*indexSelectivity *
(numTuples / (BLCKSZ / 3)));
if (dataPagesFetchedBySel > dataPagesFetched)
dataPagesFetched = dataPagesFetchedBySel;
/* Add one page cpu-cost to the startup cost */
*indexStartupCost += DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost * counts.searchEntries;
/*
* Add once again a CPU-cost for those data pages, before amortizing for
* cache.
*/
*indexTotalCost += dataPagesFetched * counts.arrayScans * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost;
/* Account for cache effects, the same as above */
if (outer_scans > 1 || counts.arrayScans > 1)
{
dataPagesFetched *= outer_scans * counts.arrayScans;
dataPagesFetched = index_pages_fetched(dataPagesFetched,
(BlockNumber) numDataPages,
numDataPages, root);
dataPagesFetched /= outer_scans;
}
/* And apply random_page_cost as the cost per page */
*indexTotalCost += *indexStartupCost +
dataPagesFetched * spc_random_page_cost;
/*
* Add on index qual eval costs, much as in genericcostestimate. We charge
* cpu but we can disregard indexorderbys, since GIN doesn't support
* those.
*/
qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);
qual_op_cost = cpu_operator_cost * list_length(indexQuals);
*indexStartupCost += qual_arg_cost;
*indexTotalCost += qual_arg_cost;
/*
* Add a cpu cost per search entry, corresponding to the actual visited
* entries.
*/
*indexTotalCost += (counts.searchEntries * counts.arrayScans) * (qual_op_cost);
/* Now add a cpu cost per tuple in the posting lists / trees */
*indexTotalCost += (numTuples * *indexSelectivity) * (cpu_index_tuple_cost);
*indexPages = dataPagesFetched;
}
/*
* BRIN has search behavior completely different from other index types
*/
void
brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
Cost *indexStartupCost, Cost *indexTotalCost,
Selectivity *indexSelectivity, double *indexCorrelation,
double *indexPages)
{
IndexOptInfo *index = path->indexinfo;
List *indexQuals = get_quals_from_indexclauses(path->indexclauses);
double numPages = index->pages;
RelOptInfo *baserel = index->rel;
RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
Cost spc_seq_page_cost;
Cost spc_random_page_cost;
double qual_arg_cost;
double qualSelectivity;
BrinStatsData statsData;
double indexRanges;
double minimalRanges;
double estimatedRanges;
double selec;
Relation indexRel;
ListCell *l;
VariableStatData vardata;
Assert(rte->rtekind == RTE_RELATION);
/* fetch estimated page cost for the tablespace containing the index */
get_tablespace_page_costs(index->reltablespace,
&spc_random_page_cost,
&spc_seq_page_cost);
/*
* Obtain some data from the index itself, if possible. Otherwise invent
* some plausible internal statistics based on the relation page count.
*/
if (!index->hypothetical)
{
/*
* A lock should have already been obtained on the index in plancat.c.
*/
indexRel = index_open(index->indexoid, NoLock);
brinGetStats(indexRel, &statsData);
index_close(indexRel, NoLock);
/* work out the actual number of ranges in the index */
indexRanges = Max(ceil((double) baserel->pages /
statsData.pagesPerRange), 1.0);
}
else
{
/*
* Assume default number of pages per range, and estimate the number
* of ranges based on that.
*/
indexRanges = Max(ceil((double) baserel->pages /
BRIN_DEFAULT_PAGES_PER_RANGE), 1.0);
statsData.pagesPerRange = BRIN_DEFAULT_PAGES_PER_RANGE;
statsData.revmapNumPages = (indexRanges / REVMAP_PAGE_MAXITEMS) + 1;
}
/*
* Compute index correlation
*
* Because we can use all index quals equally when scanning, we can use
* the largest correlation (in absolute value) among columns used by the
* query. Start at zero, the worst possible case. If we cannot find any
* correlation statistics, we will keep it as 0.
*/
*indexCorrelation = 0;
foreach(l, path->indexclauses)
{
IndexClause *iclause = lfirst_node(IndexClause, l);
AttrNumber attnum = index->indexkeys[iclause->indexcol];
/* attempt to lookup stats in relation for this index column */
if (attnum != 0)
{
/* Simple variable -- look to stats for the underlying table */
if (get_relation_stats_hook &&
(*get_relation_stats_hook) (root, rte, attnum, &vardata))
{
/*
* The hook took control of acquiring a stats tuple. If it
* did supply a tuple, it'd better have supplied a freefunc.
*/
if (HeapTupleIsValid(vardata.statsTuple) && !vardata.freefunc)
elog(ERROR,
"no function provided to release variable stats with");
}
else
{
vardata.statsTuple =
SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(rte->relid),
Int16GetDatum(attnum),
BoolGetDatum(false));
vardata.freefunc = ReleaseSysCache;
}
}
else
{
/*
* Looks like we've found an expression column in the index. Let's
* see if there's any stats for it.
*/
/* get the attnum from the 0-based index. */
attnum = iclause->indexcol + 1;
if (get_index_stats_hook &&
(*get_index_stats_hook) (root, index->indexoid, attnum, &vardata))
{
/*
* The hook took control of acquiring a stats tuple. If it
* did supply a tuple, it'd better have supplied a freefunc.
*/
if (HeapTupleIsValid(vardata.statsTuple) &&
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(index->indexoid),
Int16GetDatum(attnum),
BoolGetDatum(false));
vardata.freefunc = ReleaseSysCache;
}
}
if (HeapTupleIsValid(vardata.statsTuple))
{
AttStatsSlot sslot;
if (get_attstatsslot(&sslot, vardata.statsTuple,
STATISTIC_KIND_CORRELATION, InvalidOid,
ATTSTATSSLOT_NUMBERS))
{
double varCorrelation = 0.0;
if (sslot.nnumbers > 0)
varCorrelation = fabs(sslot.numbers[0]);
if (varCorrelation > *indexCorrelation)
*indexCorrelation = varCorrelation;
free_attstatsslot(&sslot);
}
}
ReleaseVariableStats(vardata);
}
qualSelectivity = clauselist_selectivity(root, indexQuals,
baserel->relid,
JOIN_INNER, NULL);
/*
* Now calculate the minimum possible ranges we could match with if all of
* the rows were in the perfect order in the table's heap.
*/
minimalRanges = ceil(indexRanges * qualSelectivity);
/*
* Now estimate the number of ranges that we'll touch by using the
* indexCorrelation from the stats. Careful not to divide by zero (note
* we're using the absolute value of the correlation).
*/
if (*indexCorrelation < 1.0e-10)
estimatedRanges = indexRanges;
else
estimatedRanges = Min(minimalRanges / *indexCorrelation, indexRanges);
/* we expect to visit this portion of the table */
selec = estimatedRanges / indexRanges;
CLAMP_PROBABILITY(selec);
*indexSelectivity = selec;
/*
* Compute the index qual costs, much as in genericcostestimate, to add to
* the index costs. We can disregard indexorderbys, since BRIN doesn't
* support those.
*/
qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);
/*
* Compute the startup cost as the cost to read the whole revmap
* sequentially, including the cost to execute the index quals.
*/
*indexStartupCost =
spc_seq_page_cost * statsData.revmapNumPages * loop_count;
*indexStartupCost += qual_arg_cost;
/*
* To read a BRIN index there might be a bit of back and forth over
* regular pages, as revmap might point to them out of sequential order;
* calculate the total cost as reading the whole index in random order.
*/
*indexTotalCost = *indexStartupCost +
spc_random_page_cost * (numPages - statsData.revmapNumPages) * loop_count;
/*
* Charge a small amount per range tuple which we expect to match to. This
* is meant to reflect the costs of manipulating the bitmap. The BRIN scan
* will set a bit for each page in the range when we find a matching
* range, so we must multiply the charge by the number of pages in the
* range.
*/
*indexTotalCost += 0.1 * cpu_operator_cost * estimatedRanges *
statsData.pagesPerRange;
*indexPages = index->pages;
}
./toasting.c 0000664 0001750 0001750 00000031705 15221603750 011710 0 ustar xman xman /*-------------------------------------------------------------------------
*
* toasting.c
* This file contains routines to support creation of toast tables
*
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/backend/catalog/toasting.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/toast_compression.h"
#include "access/xact.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/toasting.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"
#include "utils/syscache.h"
static void CheckAndCreateToastTable(Oid relOid, Datum reloptions,
LOCKMODE lockmode, bool check,
Oid OIDOldToast);
static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
Datum reloptions, LOCKMODE lockmode, bool check,
Oid OIDOldToast);
static bool needs_toast_table(Relation rel);
/*
* CreateToastTable variants
* If the table needs a toast table, and doesn't already have one,
* then create a toast table for it.
*
* reloptions for the toast table can be passed, too. Pass (Datum) 0
* for default reloptions.
*
* We expect the caller to have verified that the relation is a table and have
* already done any necessary permission checks. Callers expect this function
* to end with CommandCounterIncrement if it makes any changes.
*/
void
AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
{
CheckAndCreateToastTable(relOid, reloptions, lockmode, true, InvalidOid);
}
void
NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
Oid OIDOldToast)
{
CheckAndCreateToastTable(relOid, reloptions, lockmode, false, OIDOldToast);
}
void
NewRelationCreateToastTable(Oid relOid, Datum reloptions)
{
CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false,
InvalidOid);
}
static void
CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
bool check, Oid OIDOldToast)
{
Relation rel;
rel = table_open(relOid, lockmode);
/* create_toast_table does all the work */
(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode,
check, OIDOldToast);
table_close(rel, NoLock);
}
/*
* Create a toast table during bootstrap
*
* Here we need to prespecify the OIDs of the toast table and its index
*/
void
BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
{
Relation rel;
rel = table_openrv(makeRangeVar(NULL, relName, -1), AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_MATVIEW)
elog(ERROR, "\"%s\" is not a table or materialized view",
relName);
/* create_toast_table does all the work */
if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
AccessExclusiveLock, false, InvalidOid))
elog(ERROR, "\"%s\" does not require a toast table",
relName);
table_close(rel, NoLock);
}
/*
* create_toast_table --- internal workhorse
*
* rel is already opened and locked
* toastOid and toastIndexOid are normally InvalidOid, but during
* bootstrap they can be nonzero to specify hand-assigned OIDs
*/
static bool
create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
Datum reloptions, LOCKMODE lockmode, bool check,
Oid OIDOldToast)
{
Oid relOid = RelationGetRelid(rel);
HeapTuple reltup;
TupleDesc tupdesc;
bool shared_relation;
bool mapped_relation;
Relation toast_rel;
Relation class_rel;
Oid toast_relid;
Oid namespaceid;
char toast_relname[NAMEDATALEN];
char toast_idxname[NAMEDATALEN];
IndexInfo *indexInfo;
Oid collationIds[2];
Oid opclassIds[2];
int16 coloptions[2];
ObjectAddress baseobject,
toastobject;
/*
* Is it already toasted?
*/
if (rel->rd_rel->reltoastrelid != InvalidOid)
return false;
/*
* Check to see whether the table actually needs a TOAST table.
*/
if (!IsBinaryUpgrade)
{
/* Normal mode, normal check */
if (!needs_toast_table(rel))
return false;
}
else
{
/*
* In binary-upgrade mode, create a TOAST table if and only if
* pg_upgrade told us to (ie, a TOAST table OID has been provided).
*
* This indicates that the old cluster had a TOAST table for the
* current table. We must create a TOAST table to receive the old
* TOAST file, even if the table seems not to need one.
*
* Contrariwise, if the old cluster did not have a TOAST table, we
* should be able to get along without one even if the new version's
* needs_toast_table rules suggest we should have one. There is a lot
* of daylight between where we will create a TOAST table and where
* one is really necessary to avoid failures, so small cross-version
* differences in the when-to-create heuristic shouldn't be a problem.
* If we tried to create a TOAST table anyway, we would have the
* problem that it might take up an OID that will conflict with some
* old-cluster table we haven't seen yet.
*/
if (!OidIsValid(binary_upgrade_next_toast_pg_class_oid))
return false;
}
/*
* If requested check lockmode is sufficient. This is a cross check in
* case of errors or conflicting decisions in earlier code.
*/
if (check && lockmode != AccessExclusiveLock)
elog(ERROR, "AccessExclusiveLock required to add toast table.");
/*
* Create the toast table and its index
*/
snprintf(toast_relname, sizeof(toast_relname),
"pg_toast_%u", relOid);
snprintf(toast_idxname, sizeof(toast_idxname),
"pg_toast_%u_index", relOid);
/* this is pretty painful... need a tuple descriptor */
tupdesc = CreateTemplateTupleDesc(3);
TupleDescInitEntry(tupdesc, (AttrNumber) 1,
"chunk_id",
OIDOID,
-1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2,
"chunk_seq",
INT4OID,
-1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 3,
"chunk_data",
BYTEAOID,
-1, 0);
/*
* Ensure that the toast table doesn't itself get toasted, or we'll be
* toast :-(. This is essential for chunk_data because type bytea is
* toastable; hit the other two just to be sure.
*/
TupleDescAttr(tupdesc, 0)->attstorage = TYPSTORAGE_PLAIN;
TupleDescAttr(tupdesc, 1)->attstorage = TYPSTORAGE_PLAIN;
TupleDescAttr(tupdesc, 2)->attstorage = TYPSTORAGE_PLAIN;
/* Toast field should not be compressed */
TupleDescAttr(tupdesc, 0)->attcompression = InvalidCompressionMethod;
TupleDescAttr(tupdesc, 1)->attcompression = InvalidCompressionMethod;
TupleDescAttr(tupdesc, 2)->attcompression = InvalidCompressionMethod;
populate_compact_attribute(tupdesc, 0);
populate_compact_attribute(tupdesc, 1);
populate_compact_attribute(tupdesc, 2);
TupleDescFinalize(tupdesc);
/*
* Toast tables for regular relations go in pg_toast; those for temp
* relations go into the per-backend temp-toast-table namespace.
*/
if (isTempOrTempToastNamespace(rel->rd_rel->relnamespace))
namespaceid = GetTempToastNamespace();
else
namespaceid = PG_TOAST_NAMESPACE;
/* Toast table is shared if and only if its parent is. */
shared_relation = rel->rd_rel->relisshared;
/* It's mapped if and only if its parent is, too */
mapped_relation = RelationIsMapped(rel);
toast_relid = heap_create_with_catalog(toast_relname,
namespaceid,
rel->rd_rel->reltablespace,
toastOid,
InvalidOid,
InvalidOid,
rel->rd_rel->relowner,
table_relation_toast_am(rel),
tupdesc,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
reloptions,
false,
true,
true,
OIDOldToast,
NULL);
Assert(toast_relid != InvalidOid);
/* make the toast relation visible, else table_open will fail */
CommandCounterIncrement();
/* ShareLock is not really needed here, but take it anyway */
toast_rel = table_open(toast_relid, ShareLock);
/*
* Create unique index on chunk_id, chunk_seq.
*
* NOTE: the normal TOAST access routines could actually function with a
* single-column index on chunk_id only. However, the slice access
* routines use both columns for faster access to an individual chunk. In
* addition, we want it to be unique as a check against the possibility of
* duplicate TOAST chunk OIDs. The index might also be a little more
* efficient this way, since btree isn't all that happy with large numbers
* of equal keys.
*/
indexInfo = makeNode(IndexInfo);
indexInfo->ii_NumIndexAttrs = 2;
indexInfo->ii_NumIndexKeyAttrs = 2;
indexInfo->ii_IndexAttrNumbers[0] = 1;
indexInfo->ii_IndexAttrNumbers[1] = 2;
indexInfo->ii_Expressions = NIL;
indexInfo->ii_ExpressionsExpand = NIL;
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_Predicate = NIL;
indexInfo->ii_PredicateExpand = NIL;
indexInfo->ii_PredicateState = NULL;
indexInfo->ii_PredicateExpandState = NULL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
indexInfo->ii_Unique = true;
indexInfo->ii_NullsNotDistinct = false;
indexInfo->ii_ReadyForInserts = true;
indexInfo->ii_CheckedUnchanged = false;
indexInfo->ii_IndexUnchanged = false;
indexInfo->ii_Concurrent = false;
indexInfo->ii_BrokenHotChain = false;
indexInfo->ii_ParallelWorkers = 0;
indexInfo->ii_Am = BTREE_AM_OID;
indexInfo->ii_AmCache = NULL;
indexInfo->ii_Context = CurrentMemoryContext;
collationIds[0] = InvalidOid;
collationIds[1] = InvalidOid;
opclassIds[0] = OID_BTREE_OPS_OID;
opclassIds[1] = INT4_BTREE_OPS_OID;
coloptions[0] = 0;
coloptions[1] = 0;
index_create(toast_rel, toast_idxname, toastIndexOid, InvalidOid,
InvalidOid, InvalidOid,
indexInfo,
list_make2("chunk_id", "chunk_seq"),
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationIds, opclassIds, NULL, coloptions, NULL, (Datum) 0,
INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL);
table_close(toast_rel, NoLock);
/*
* Store the toast table's OID in the parent relation's pg_class row
*/
class_rel = table_open(RelationRelationId, RowExclusiveLock);
if (!IsBootstrapProcessingMode())
{
/* normal case, use a transactional update */
reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(reltup))
elog(ERROR, "cache lookup failed for relation %u", relOid);
((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
CatalogTupleUpdate(class_rel, &reltup->t_self, reltup);
}
else
{
/* While bootstrapping, we cannot UPDATE, so overwrite in-place */
ScanKeyData key[1];
void *state;
ScanKeyInit(&key[0],
Anum_pg_class_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relOid));
systable_inplace_update_begin(class_rel, ClassOidIndexId, true,
NULL, 1, key, &reltup, &state);
if (!HeapTupleIsValid(reltup))
elog(ERROR, "cache lookup failed for relation %u", relOid);
((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
systable_inplace_update_finish(state, reltup);
}
heap_freetuple(reltup);
table_close(class_rel, RowExclusiveLock);
/*
* Register dependency from the toast table to the main, so that the toast
* table will be deleted if the main is. Skip this in bootstrap mode.
*/
if (!IsBootstrapProcessingMode())
{
baseobject.classId = RelationRelationId;
baseobject.objectId = relOid;
baseobject.objectSubId = 0;
toastobject.classId = RelationRelationId;
toastobject.objectId = toast_relid;
toastobject.objectSubId = 0;
recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
}
/*
* Make changes visible
*/
CommandCounterIncrement();
return true;
}
/*
* Check to see whether the table needs a TOAST table.
*/
static bool
needs_toast_table(Relation rel)
{
/*
* No need to create a TOAST table for partitioned tables.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
return false;
/*
* We cannot allow toasting a shared relation after initdb (because
* there's no way to mark it toasted in other databases' pg_class).
*/
if (rel->rd_rel->relisshared && !IsBootstrapProcessingMode())
return false;
/*
* Ignore attempts to create toast tables on catalog tables after initdb.
* Which catalogs get toast tables is explicitly chosen in catalog/pg_*.h.
* (We could get here via some ALTER TABLE command if the catalog doesn't
* have a toast table.)
*/
if (IsCatalogRelation(rel) && !IsBootstrapProcessingMode())
return false;
/* Otherwise, let the AM decide. */
return table_relation_needs_toast_table(rel);
}