From 74849e72c3612238d2551c6dbf858599d700a01a Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Mon, 25 Nov 2024 16:36:37 -0600 Subject: [PATCH 01/16] pg_dump: Add dumpSchema and dumpData derivative flags. Various parts of pg_dump consult the --schema-only and --data-only options to determine whether to run a section of code. While this is simple enough for two mutually-exclusive options, it will become progressively more complicated as more options are added. In anticipation of that, this commit introduces new internal flags called dumpSchema and dumpData, which are derivatives of --schema-only and --data-only. This commit also removes the schemaOnly and dataOnly members from the dump/restore options structs to prevent their use elsewhere. Note that this change neither adds new user-facing command-line options nor changes the existing --schema-only and --data-only options. Author: Corey Huinker Reviewed-by: Jeff Davis Discussion: https://postgr.es/m/CADkLM%3DcQgghMJOS8EcAVBwRO4s1dUVtxGZv5gLPfZkQ1nL1gzA%40mail.gmail.com --- src/bin/pg_dump/pg_backup.h | 10 +- src/bin/pg_dump/pg_backup_archiver.c | 34 +++-- src/bin/pg_dump/pg_dump.c | 202 ++++++++++++++------------- src/bin/pg_dump/pg_restore.c | 14 +- 4 files changed, 139 insertions(+), 121 deletions(-) diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 558a8f00abf..30dbc3b2df8 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -114,8 +114,6 @@ typedef struct _restoreOptions int strict_names; const char *filename; - int dataOnly; - int schemaOnly; int dumpSections; int verbose; int aclsSkip; @@ -156,6 +154,9 @@ typedef struct _restoreOptions int binary_upgrade; char *restrict_key; + /* flags derived from the user-settable flags */ + bool dumpSchema; + bool dumpData; } RestoreOptions; typedef struct _dumpOptions @@ -165,8 +166,6 @@ typedef struct _dumpOptions int binary_upgrade; /* various user-settable parameters */ - bool schemaOnly; - bool dataOnly; int dumpSections; /* bitmask of chosen sections */ bool aclsSkip; const char *lockWaitTimeout; @@ -204,6 +203,9 @@ typedef struct _dumpOptions int do_nothing; char *restrict_key; + /* flags derived from the user-settable flags */ + bool dumpSchema; + bool dumpData; } DumpOptions; /* diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 74e3c564972..8e87d5d0f8a 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -163,6 +163,8 @@ InitDumpOptions(DumpOptions *opts) opts->include_everything = true; opts->cparams.promptPassword = TRI_DEFAULT; opts->dumpSections = DUMP_UNSECTIONED; + opts->dumpSchema = true; + opts->dumpData = true; } /* @@ -181,8 +183,8 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->cparams.username = ropt->cparams.username ? pg_strdup(ropt->cparams.username) : NULL; dopt->cparams.promptPassword = ropt->cparams.promptPassword; dopt->outputClean = ropt->dropSchema; - dopt->dataOnly = ropt->dataOnly; - dopt->schemaOnly = ropt->schemaOnly; + dopt->dumpData = ropt->dumpData; + dopt->dumpSchema = ropt->dumpSchema; dopt->if_exists = ropt->if_exists; dopt->column_inserts = ropt->column_inserts; dopt->dumpSections = ropt->dumpSections; @@ -434,12 +436,12 @@ RestoreArchive(Archive *AHX) * Work out if we have an implied data-only restore. This can happen if * the dump was data only or if the user has used a toc list to exclude * all of the schema data. All we do is look for schema entries - if none - * are found then we set the dataOnly flag. + * are found then we unset the dumpSchema flag. * * We could scan for wanted TABLE entries, but that is not the same as - * dataOnly. At this stage, it seems unnecessary (6-Mar-2001). + * data-only. At this stage, it seems unnecessary (6-Mar-2001). */ - if (!ropt->dataOnly) + if (ropt->dumpSchema) { int impliedDataOnly = 1; @@ -453,7 +455,7 @@ RestoreArchive(Archive *AHX) } if (impliedDataOnly) { - ropt->dataOnly = impliedDataOnly; + ropt->dumpSchema = false; pg_log_info("implied data-only restore"); } } @@ -793,7 +795,7 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel) /* Dump any relevant dump warnings to stderr */ if (!ropt->suppressDumpWarnings && strcmp(te->desc, "WARNING") == 0) { - if (!ropt->dataOnly && te->defn != NULL && strlen(te->defn) != 0) + if (ropt->dumpSchema && te->defn != NULL && strlen(te->defn) != 0) pg_log_warning("warning from original dump file: %s", te->defn); else if (te->copyStmt != NULL && strlen(te->copyStmt) != 0) pg_log_warning("warning from original dump file: %s", te->copyStmt); @@ -1011,6 +1013,8 @@ NewRestoreOptions(void) opts->dumpSections = DUMP_UNSECTIONED; opts->compression_spec.algorithm = PG_COMPRESSION_NONE; opts->compression_spec.level = 0; + opts->dumpSchema = true; + opts->dumpData = true; return opts; } @@ -1021,7 +1025,7 @@ _disableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te) RestoreOptions *ropt = AH->public.ropt; /* This hack is only needed in a data-only restore */ - if (!ropt->dataOnly || !ropt->disable_triggers) + if (ropt->dumpSchema || !ropt->disable_triggers) return; pg_log_info("disabling triggers for %s", te->tag); @@ -1047,7 +1051,7 @@ _enableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te) RestoreOptions *ropt = AH->public.ropt; /* This hack is only needed in a data-only restore */ - if (!ropt->dataOnly || !ropt->disable_triggers) + if (ropt->dumpSchema || !ropt->disable_triggers) return; pg_log_info("enabling triggers for %s", te->tag); @@ -3090,13 +3094,13 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if ((strcmp(te->desc, "") == 0) && (strcmp(te->tag, "Max OID") == 0)) return 0; - /* Mask it if we only want schema */ - if (ropt->schemaOnly) + /* Mask it if we don't want data */ + if (!ropt->dumpData) { /* - * The sequence_data option overrides schemaOnly for SEQUENCE SET. + * The sequence_data option overrides dumpData for SEQUENCE SET. * - * In binary-upgrade mode, even with schemaOnly set, we do not mask + * In binary-upgrade mode, even with dumpData unset, we do not mask * out large objects. (Only large object definitions, comments and * other metadata should be generated in binary-upgrade mode, not the * actual data, but that need not concern us here.) @@ -3113,8 +3117,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) res = res & REQ_SCHEMA; } - /* Mask it if we only want data */ - if (ropt->dataOnly) + /* Mask it if we don't want schema */ + if (!ropt->dumpSchema) res = res & REQ_DATA; return res; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 597c828db81..09943d457c0 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -358,6 +358,8 @@ main(int argc, char **argv) char *compression_algorithm_str = "none"; char *error_detail = NULL; bool user_compression_defined = false; + bool data_only = false; + bool schema_only = false; static DumpOptions dopt; @@ -471,7 +473,7 @@ main(int argc, char **argv) switch (c) { case 'a': /* Dump data only */ - dopt.dataOnly = true; + data_only = true; break; case 'b': /* Dump LOs */ @@ -544,7 +546,7 @@ main(int argc, char **argv) break; case 's': /* dump schema only */ - dopt.schemaOnly = true; + schema_only = true; break; case 'S': /* Username for superuser in plain text output */ @@ -698,21 +700,25 @@ main(int argc, char **argv) if (dopt.binary_upgrade) dopt.sequence_data = 1; - if (dopt.dataOnly && dopt.schemaOnly) + if (data_only && schema_only) pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together"); - if (dopt.schemaOnly && foreign_servers_include_patterns.head != NULL) + if (schema_only && foreign_servers_include_patterns.head != NULL) pg_fatal("options -s/--schema-only and --include-foreign-data cannot be used together"); if (numWorkers > 1 && foreign_servers_include_patterns.head != NULL) pg_fatal("option --include-foreign-data is not supported with parallel backup"); - if (dopt.dataOnly && dopt.outputClean) + if (data_only && dopt.outputClean) pg_fatal("options -c/--clean and -a/--data-only cannot be used together"); if (dopt.if_exists && !dopt.outputClean) pg_fatal("option --if-exists requires option -c/--clean"); + /* set derivative flags */ + dopt.dumpSchema = (!data_only); + dopt.dumpData = (!schema_only); + /* * --inserts are already implied above if --column-inserts or * --rows-per-insert were specified. @@ -905,7 +911,7 @@ main(int argc, char **argv) * -s means "schema only" and LOs are data, not schema, so we never * include LOs when -s is used. */ - if (dopt.include_everything && !dopt.schemaOnly && !dopt.dontOutputLOs) + if (dopt.include_everything && dopt.dumpData && !dopt.dontOutputLOs) dopt.outputLOs = true; /* @@ -919,15 +925,15 @@ main(int argc, char **argv) */ tblinfo = getSchemaData(fout, &numTables); - if (!dopt.schemaOnly) + if (dopt.dumpData) { getTableData(&dopt, tblinfo, numTables, 0); buildMatViewRefreshDependencies(fout); - if (dopt.dataOnly) + if (!dopt.dumpSchema) getTableDataFKConstraints(); } - if (dopt.schemaOnly && dopt.sequence_data) + if (!dopt.dumpData && dopt.sequence_data) getTableData(&dopt, tblinfo, numTables, RELKIND_SEQUENCE); /* @@ -1012,8 +1018,8 @@ main(int argc, char **argv) ropt->cparams.username = dopt.cparams.username ? pg_strdup(dopt.cparams.username) : NULL; ropt->cparams.promptPassword = dopt.cparams.promptPassword; ropt->dropSchema = dopt.outputClean; - ropt->dataOnly = dopt.dataOnly; - ropt->schemaOnly = dopt.schemaOnly; + ropt->dumpData = dopt.dumpData; + ropt->dumpSchema = dopt.dumpSchema; ropt->if_exists = dopt.if_exists; ropt->column_inserts = dopt.column_inserts; ropt->dumpSections = dopt.dumpSections; @@ -1905,7 +1911,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout) * Mark a default ACL as to be dumped or not * * For per-schema default ACLs, dump if the schema is to be dumped. - * Otherwise dump if we are dumping "everything". Note that dataOnly + * Otherwise dump if we are dumping "everything". Note that dumpSchema * and aclsSkip are checked separately. */ static void @@ -4004,8 +4010,8 @@ dumpPolicy(Archive *fout, const PolicyInfo *polinfo) const char *cmd; char *tag; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; /* @@ -4223,8 +4229,8 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo) char *qpubname; bool first = true; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; delq = createPQExpBuffer(); @@ -4538,8 +4544,8 @@ dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo) PQExpBuffer query; char *tag; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name); @@ -4581,8 +4587,8 @@ dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo) PQExpBuffer query; char *tag; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; tag = psprintf("%s %s", pubinfo->dobj.name, tbinfo->dobj.name); @@ -4849,8 +4855,8 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo) int i; char two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'}; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; delq = createPQExpBuffer(); @@ -7025,8 +7031,8 @@ getPartitioningInfo(Archive *fout) /* hash partitioning didn't exist before v11 */ if (fout->remoteVersion < 110000) return; - /* needn't bother if schema-only dump */ - if (fout->dopt->schemaOnly) + /* needn't bother if not dumping data */ + if (!fout->dopt->dumpData) return; query = createPQExpBuffer(); @@ -8770,7 +8776,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) * Now get info about column defaults. This is skipped for a data-only * dump, as it is only needed for table schemas. */ - if (!dopt->dataOnly && tbloids->len > 1) + if (dopt->dumpSchema && tbloids->len > 1) { AttrDefInfo *attrdefs; int numDefaults; @@ -8900,7 +8906,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) * Get info about table CHECK constraints. This is skipped for a * data-only dump, as it is only needed for table schemas. */ - if (!dopt->dataOnly && checkoids->len > 2) + if (dopt->dumpSchema && checkoids->len > 2) { ConstraintInfo *constrs; int numConstrs; @@ -9852,13 +9858,13 @@ dumpCommentExtended(Archive *fout, const char *type, /* Comments are schema not data ... except LO comments are data */ if (strcmp(type, "LARGE OBJECT") != 0) { - if (dopt->dataOnly) + if (!dopt->dumpSchema) return; } else { /* We do dump LO comments in binary-upgrade mode */ - if (dopt->schemaOnly && !dopt->binary_upgrade) + if (!dopt->dumpData && !dopt->binary_upgrade) return; } @@ -9965,7 +9971,7 @@ dumpTableComment(Archive *fout, const TableInfo *tbinfo, return; /* Comments are SCHEMA not data */ - if (dopt->dataOnly) + if (!dopt->dumpSchema) return; /* Search for comments associated with relation, using table */ @@ -10402,8 +10408,8 @@ dumpNamespace(Archive *fout, const NamespaceInfo *nspinfo) PQExpBuffer delq; char *qnspname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -10479,8 +10485,8 @@ dumpExtension(Archive *fout, const ExtensionInfo *extinfo) PQExpBuffer delq; char *qextname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -10604,8 +10610,8 @@ dumpType(Archive *fout, const TypeInfo *tyinfo) { DumpOptions *dopt = fout->dopt; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; /* Dump out in proper style */ @@ -11720,8 +11726,8 @@ dumpShellType(Archive *fout, const ShellTypeInfo *stinfo) DumpOptions *dopt = fout->dopt; PQExpBuffer q; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -11772,8 +11778,8 @@ dumpProcLang(Archive *fout, const ProcLangInfo *plang) FuncInfo *inlineInfo = NULL; FuncInfo *validatorInfo = NULL; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; /* @@ -11980,8 +11986,8 @@ dumpFunc(Archive *fout, const FuncInfo *finfo) int nconfigitems = 0; const char *keyword; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; query = createPQExpBuffer(); @@ -12372,8 +12378,8 @@ dumpCast(Archive *fout, const CastInfo *cast) const char *sourceType; const char *targetType; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; /* Cannot dump if we don't have the cast function's info */ @@ -12478,8 +12484,8 @@ dumpTransform(Archive *fout, const TransformInfo *transform) char *lanname; const char *transformType; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; /* Cannot dump if we don't have the transform functions' info */ @@ -12627,8 +12633,8 @@ dumpOpr(Archive *fout, const OprInfo *oprinfo) char *oprregproc; char *oprref; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; /* @@ -12914,8 +12920,8 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *aminfo) PQExpBuffer delq; char *qamname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -13017,8 +13023,8 @@ dumpOpclass(Archive *fout, const OpclassInfo *opcinfo) bool needComma; int i; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; query = createPQExpBuffer(); @@ -13288,8 +13294,8 @@ dumpOpfamily(Archive *fout, const OpfamilyInfo *opfinfo) bool needComma; int i; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; query = createPQExpBuffer(); @@ -13495,8 +13501,8 @@ dumpCollation(Archive *fout, const CollInfo *collinfo) const char *colliculocale; const char *collicurules; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; query = createPQExpBuffer(); @@ -13735,8 +13741,8 @@ dumpConversion(Archive *fout, const ConvInfo *convinfo) const char *conproc; bool condefault; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; query = createPQExpBuffer(); @@ -13883,8 +13889,8 @@ dumpAgg(Archive *fout, const AggInfo *agginfo) const char *proparallel; char defaultfinalmodify; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; query = createPQExpBuffer(); @@ -14213,8 +14219,8 @@ dumpTSParser(Archive *fout, const TSParserInfo *prsinfo) PQExpBuffer delq; char *qprsname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -14281,8 +14287,8 @@ dumpTSDictionary(Archive *fout, const TSDictInfo *dictinfo) char *nspname; char *tmplname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -14357,8 +14363,8 @@ dumpTSTemplate(Archive *fout, const TSTemplateInfo *tmplinfo) PQExpBuffer delq; char *qtmplname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -14423,8 +14429,8 @@ dumpTSConfig(Archive *fout, const TSConfigInfo *cfginfo) int i_tokenname; int i_dictname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -14535,8 +14541,8 @@ dumpForeignDataWrapper(Archive *fout, const FdwInfo *fdwinfo) PQExpBuffer delq; char *qfdwname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -14608,8 +14614,8 @@ dumpForeignServer(Archive *fout, const ForeignServerInfo *srvinfo) char *qsrvname; char *fdwname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -14799,8 +14805,8 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo) PQExpBuffer tag; const char *type; - /* Do nothing in data-only dump, or if we're skipping ACLs */ - if (dopt->dataOnly || dopt->aclsSkip) + /* Do nothing if not dumping schema, or if we're skipping ACLs */ + if (!dopt->dumpSchema || dopt->aclsSkip) return; q = createPQExpBuffer(); @@ -14898,7 +14904,7 @@ dumpACL(Archive *fout, DumpId objDumpId, DumpId altDumpId, return InvalidDumpId; /* --data-only skips ACLs *except* large object ACLs */ - if (dopt->dataOnly && strcmp(type, "LARGE OBJECT") != 0) + if (!dopt->dumpSchema && strcmp(type, "LARGE OBJECT") != 0) return InvalidDumpId; sql = createPQExpBuffer(); @@ -15025,13 +15031,13 @@ dumpSecLabel(Archive *fout, const char *type, const char *name, */ if (strcmp(type, "LARGE OBJECT") != 0) { - if (dopt->dataOnly) + if (!dopt->dumpSchema) return; } else { /* We do dump large object security labels in binary-upgrade mode */ - if (dopt->schemaOnly && !dopt->binary_upgrade) + if (!dopt->dumpData && !dopt->binary_upgrade) return; } @@ -15099,7 +15105,7 @@ dumpTableSecLabel(Archive *fout, const TableInfo *tbinfo, const char *reltypenam return; /* SecLabel are SCHEMA not data */ - if (dopt->dataOnly) + if (!dopt->dumpSchema) return; /* Search for comments associated with relation, using table */ @@ -15338,8 +15344,8 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) DumpId tableAclDumpId = InvalidDumpId; char *namecopy; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; if (tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) @@ -16366,8 +16372,8 @@ dumpTableAttach(Archive *fout, const TableAttachInfo *attachinfo) PGresult *res; char *partbound; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -16438,8 +16444,8 @@ dumpAttrDef(Archive *fout, const AttrDefInfo *adinfo) char *tag; char *foreign; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; /* Skip if not "separate"; it was dumped in the table's definition */ @@ -16527,8 +16533,8 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) char *qindxname; char *qqindxname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -16670,8 +16676,8 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) static void dumpIndexAttach(Archive *fout, const IndexAttachInfo *attachinfo) { - /* Do nothing in data-only dump */ - if (fout->dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!fout->dopt->dumpSchema) return; if (attachinfo->partitionIdx->dobj.dump & DUMP_COMPONENT_DEFINITION) @@ -16721,8 +16727,8 @@ dumpStatisticsExt(Archive *fout, const StatsExtInfo *statsextinfo) PGresult *res; char *stxdef; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -16798,8 +16804,8 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) char *tag = NULL; char *foreign; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; q = createPQExpBuffer(); @@ -17451,8 +17457,8 @@ dumpTrigger(Archive *fout, const TriggerInfo *tginfo) int findx; char *tag; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; query = createPQExpBuffer(); @@ -17685,8 +17691,8 @@ dumpEventTrigger(Archive *fout, const EventTriggerInfo *evtinfo) PQExpBuffer delqry; char *qevtname; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; query = createPQExpBuffer(); @@ -17781,8 +17787,8 @@ dumpRule(Archive *fout, const RuleInfo *rinfo) PGresult *res; char *tag; - /* Do nothing in data-only dump */ - if (dopt->dataOnly) + /* Do nothing if not dumping schema */ + if (!dopt->dumpSchema) return; /* @@ -18048,7 +18054,7 @@ processExtensionTables(Archive *fout, ExtensionInfo extinfo[], * objects for them, ensuring their data will be dumped even though the * tables themselves won't be. * - * Note that we create TableDataInfo objects even in schemaOnly mode, ie, + * Note that we create TableDataInfo objects even in schema-only mode, ie, * user data in a configuration table is treated like schema data. This * seems appropriate since system data in a config table would get * reloaded by CREATE EXTENSION. If the extension is not listed in the diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index f05c24510ac..a79a8f031c2 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -74,6 +74,8 @@ main(int argc, char **argv) static int no_security_labels = 0; static int no_subscriptions = 0; static int strict_names = 0; + bool data_only = false; + bool schema_only = false; struct option cmdopts[] = { {"clean", 0, NULL, 'c'}, @@ -158,7 +160,7 @@ main(int argc, char **argv) switch (c) { case 'a': /* Dump data only */ - opts->dataOnly = 1; + data_only = true; break; case 'c': /* clean (i.e., drop) schema prior to create */ opts->dropSchema = 1; @@ -234,7 +236,7 @@ main(int argc, char **argv) simple_string_list_append(&opts->triggerNames, optarg); break; case 's': /* dump schema only */ - opts->schemaOnly = 1; + schema_only = true; break; case 'S': /* Superuser username */ if (strlen(optarg) != 0) @@ -345,10 +347,10 @@ main(int argc, char **argv) pg_fatal("invalid restrict key"); } - if (opts->dataOnly && opts->schemaOnly) + if (data_only && schema_only) pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together"); - if (opts->dataOnly && opts->dropSchema) + if (data_only && opts->dropSchema) pg_fatal("options -c/--clean and -a/--data-only cannot be used together"); /* @@ -362,6 +364,10 @@ main(int argc, char **argv) if (opts->single_txn && numWorkers > 1) pg_fatal("cannot specify both --single-transaction and multiple jobs"); + /* set derivative flags */ + opts->dumpSchema = (!data_only); + opts->dumpData = (!schema_only); + opts->disable_triggers = disable_triggers; opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; From cad405c4996fb4983b0047a59a2059770905bb05 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Mon, 1 Jul 2024 10:18:26 -0500 Subject: [PATCH 02/16] Add --no-sync to pg_upgrade's uses of pg_dump and pg_dumpall. There's no reason to ensure that the files pg_upgrade generates with pg_dump and pg_dumpall have been written safely to disk. If there is a crash during pg_upgrade, the upgrade must be restarted from the beginning; dump files left behind by previous pg_upgrade attempts cannot be reused. Reviewed-by: Peter Eisentraut, Tom Lane, Michael Paquier, Daniel Gustafsson Discussion: https://postgr.es/m/20240503171348.GA1731524%40nathanxps13 --- src/bin/pg_upgrade/dump.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c index 6c8c82dca89..006aa43d6d4 100644 --- a/src/bin/pg_upgrade/dump.c +++ b/src/bin/pg_upgrade/dump.c @@ -22,7 +22,7 @@ generate_old_dump(void) /* run new pg_dumpall binary for globals */ exec_prog(UTILITY_LOG_FILE, NULL, true, true, "\"%s/pg_dumpall\" %s --globals-only --quote-all-identifiers " - "--binary-upgrade %s -f \"%s/%s\"", + "--binary-upgrade %s --no-sync -f \"%s/%s\"", new_cluster.bindir, cluster_conn_opts(&old_cluster), log_opts.verbose ? "--verbose" : "", log_opts.dumpdir, @@ -53,7 +53,7 @@ generate_old_dump(void) parallel_exec_prog(log_file_name, NULL, "\"%s/pg_dump\" %s --schema-only --quote-all-identifiers " - "--binary-upgrade --format=custom %s --file=\"%s/%s\" %s", + "--binary-upgrade --format=custom %s --no-sync --file=\"%s/%s\" %s", new_cluster.bindir, cluster_conn_opts(&old_cluster), log_opts.verbose ? "--verbose" : "", log_opts.dumpdir, From ffbc5c90995e3baf927f2475dcd0ff87c43c07b1 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Fri, 11 Oct 2024 16:55:11 -0700 Subject: [PATCH 03/16] Create functions pg_set_relation_stats, pg_clear_relation_stats. These functions are used to tweak statistics on any relation, provided that the user has MAINTAIN privilege on the relation, or is the database owner. Bump catalog version. Author: Corey Huinker Discussion: https://postgr.es/m/CADkLM=eErgzn7ECDpwFcptJKOk9SxZEk5Pot4d94eVTZsvj3gw@mail.gmail.com --- doc/src/sgml/func.sgml | 94 +++++++++ src/backend/catalog/system_functions.sql | 10 + src/backend/statistics/Makefile | 4 +- src/backend/statistics/meson.build | 2 + src/backend/statistics/relation_stats.c | 210 +++++++++++++++++++++ src/backend/statistics/stat_utils.c | 94 +++++++++ src/include/catalog/pg_proc.dat | 37 ++++ src/include/statistics/stat_utils.h | 29 +++ src/test/regress/expected/stats_import.out | 143 ++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/stats_import.sql | 98 ++++++++++ 11 files changed, 721 insertions(+), 2 deletions(-) create mode 100644 src/backend/statistics/relation_stats.c create mode 100644 src/backend/statistics/stat_utils.c create mode 100644 src/include/statistics/stat_utils.h create mode 100644 src/test/regress/expected/stats_import.out create mode 100644 src/test/regress/sql/stats_import.sql diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index da1d87824d3..cffa5d92ed3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -28025,6 +28025,100 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset + + lists functions used to + manipulate statistics. + + + Changes made by these statistics manipulation functions are likely to be + overwritten by autovacuum (or manual + VACUUM or ANALYZE) and should be + considered temporary. + + + + + + Database Object Statistics Manipulation Functions + + + + + Function + + + Description + + + + + + + + + + pg_set_relation_stats + + pg_set_relation_stats ( + relation regclass + , relpages integer + , reltuples real + , relallvisible integer ) + boolean + + + Updates relation-level statistics for the given relation to the + specified values. The parameters correspond to columns in pg_class. Unspecified + or NULL values leave the setting + unchanged. Returns true if a change was made; + false otherwise. + + + Ordinarily, these statistics are collected automatically or updated + as a part of or , so it's not necessary to call this + function. However, it may be useful when testing the effects of + statistics on the planner to understand or anticipate plan changes. + + + The caller must have the MAINTAIN privilege on + the table or be the owner of the database. + + + The value of relpages must be greater than + or equal to 0, + reltuples must be greater than or equal to + -1.0, and relallvisible + must be greater than or equal to 0. + + + + + + + + + pg_clear_relation_stats + + pg_clear_relation_stats ( relation regclass ) + boolean + + + Clears table-level statistics for the given relation, as though the + table was newly created. Returns true if a change + was made; false otherwise. + + + The caller must have the MAINTAIN privilege on + the table or be the owner of the database. + + + + + +
+ lists functions that provide information about the structure of partitioned tables. diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 07c0d89c4f8..413e2551231 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -601,6 +601,16 @@ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE AS 'unicode_is_normalized'; +CREATE OR REPLACE FUNCTION + pg_set_relation_stats(relation regclass, + relpages integer DEFAULT NULL, + reltuples real DEFAULT NULL, + relallvisible integer DEFAULT NULL) +RETURNS bool +LANGUAGE INTERNAL +CALLED ON NULL INPUT VOLATILE +AS 'pg_set_relation_stats'; + -- -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather diff --git a/src/backend/statistics/Makefile b/src/backend/statistics/Makefile index 89cf8c27973..041f5f8a581 100644 --- a/src/backend/statistics/Makefile +++ b/src/backend/statistics/Makefile @@ -16,6 +16,8 @@ OBJS = \ dependencies.o \ extended_stats.o \ mcv.o \ - mvdistinct.o + mvdistinct.o \ + relation_stats.o \ + stat_utils.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/statistics/meson.build b/src/backend/statistics/meson.build index e12737b011e..11604e1bf46 100644 --- a/src/backend/statistics/meson.build +++ b/src/backend/statistics/meson.build @@ -5,4 +5,6 @@ backend_sources += files( 'extended_stats.c', 'mcv.c', 'mvdistinct.c', + 'relation_stats.c', + 'stat_utils.c' ) diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c new file mode 100644 index 00000000000..ffa3d83a878 --- /dev/null +++ b/src/backend/statistics/relation_stats.c @@ -0,0 +1,210 @@ +/*------------------------------------------------------------------------- + * relation_stats.c + * + * PostgreSQL relation statistics manipulation + * + * Code supporting the direct import of relation statistics, similar to + * what is done by the ANALYZE command. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/statistics/relation_stats.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/heapam.h" +#include "catalog/indexing.h" +#include "statistics/stat_utils.h" +#include "utils/fmgrprotos.h" +#include "utils/syscache.h" + +#define DEFAULT_RELPAGES Int32GetDatum(0) +#define DEFAULT_RELTUPLES Float4GetDatum(-1.0) +#define DEFAULT_RELALLVISIBLE Int32GetDatum(0) + +/* + * Positional argument numbers, names, and types for + * relation_statistics_update(). + */ + +enum relation_stats_argnum +{ + RELATION_ARG = 0, + RELPAGES_ARG, + RELTUPLES_ARG, + RELALLVISIBLE_ARG, + NUM_RELATION_STATS_ARGS +}; + +static struct StatsArgInfo relarginfo[] = +{ + [RELATION_ARG] = {"relation", REGCLASSOID}, + [RELPAGES_ARG] = {"relpages", INT4OID}, + [RELTUPLES_ARG] = {"reltuples", FLOAT4OID}, + [RELALLVISIBLE_ARG] = {"relallvisible", INT4OID}, + [NUM_RELATION_STATS_ARGS] = {0} +}; + +static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel); + +/* + * Internal function for modifying statistics for a relation. + */ +static bool +relation_statistics_update(FunctionCallInfo fcinfo, int elevel) +{ + Oid reloid; + Relation crel; + HeapTuple ctup; + Form_pg_class pgcform; + int replaces[3] = {0}; + Datum values[3] = {0}; + bool nulls[3] = {0}; + int ncols = 0; + TupleDesc tupdesc; + HeapTuple newtup; + + + stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG); + reloid = PG_GETARG_OID(RELATION_ARG); + + stats_lock_check_privileges(reloid); + + /* + * Take RowExclusiveLock on pg_class, consistent with + * vac_update_relstats(). + */ + crel = table_open(RelationRelationId, RowExclusiveLock); + + tupdesc = RelationGetDescr(crel); + ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid)); + if (!HeapTupleIsValid(ctup)) + { + ereport(elevel, + (errcode(ERRCODE_OBJECT_IN_USE), + errmsg("pg_class entry for relid %u not found", reloid))); + table_close(crel, RowExclusiveLock); + return false; + } + + pgcform = (Form_pg_class) GETSTRUCT(ctup); + + /* relpages */ + if (!PG_ARGISNULL(RELPAGES_ARG)) + { + int32 relpages = PG_GETARG_INT32(RELPAGES_ARG); + + if (relpages < -1) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("relpages cannot be < -1"))); + table_close(crel, RowExclusiveLock); + return false; + } + + if (relpages != pgcform->relpages) + { + replaces[ncols] = Anum_pg_class_relpages; + values[ncols] = Int32GetDatum(relpages); + ncols++; + } + } + + if (!PG_ARGISNULL(RELTUPLES_ARG)) + { + float reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG); + + if (reltuples < -1.0) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("reltuples cannot be < -1.0"))); + table_close(crel, RowExclusiveLock); + return false; + } + + if (reltuples != pgcform->reltuples) + { + replaces[ncols] = Anum_pg_class_reltuples; + values[ncols] = Float4GetDatum(reltuples); + ncols++; + } + } + + if (!PG_ARGISNULL(RELALLVISIBLE_ARG)) + { + int32 relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG); + + if (relallvisible < 0) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("relallvisible cannot be < 0"))); + table_close(crel, RowExclusiveLock); + return false; + } + + if (relallvisible != pgcform->relallvisible) + { + replaces[ncols] = Anum_pg_class_relallvisible; + values[ncols] = Int32GetDatum(relallvisible); + ncols++; + } + } + + /* only update pg_class if there is a meaningful change */ + if (ncols == 0) + { + table_close(crel, RowExclusiveLock); + return false; + } + + newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values, + nulls); + + CatalogTupleUpdate(crel, &newtup->t_self, newtup); + heap_freetuple(newtup); + + /* release the lock, consistent with vac_update_relstats() */ + table_close(crel, RowExclusiveLock); + + return true; +} + +/* + * Set statistics for a given pg_class entry. + */ +Datum +pg_set_relation_stats(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(relation_statistics_update(fcinfo, ERROR)); +} + +/* + * Clear statistics for a given pg_class entry; that is, set back to initial + * stats for a newly-created table. + */ +Datum +pg_clear_relation_stats(PG_FUNCTION_ARGS) +{ + LOCAL_FCINFO(newfcinfo, 4); + + InitFunctionCallInfoData(*newfcinfo, NULL, 4, InvalidOid, NULL, NULL); + + newfcinfo->args[0].value = PG_GETARG_OID(0); + newfcinfo->args[0].isnull = PG_ARGISNULL(0); + newfcinfo->args[1].value = DEFAULT_RELPAGES; + newfcinfo->args[1].isnull = false; + newfcinfo->args[2].value = DEFAULT_RELTUPLES; + newfcinfo->args[2].isnull = false; + newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE; + newfcinfo->args[3].isnull = false; + + PG_RETURN_BOOL(relation_statistics_update(newfcinfo, ERROR)); +} diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c new file mode 100644 index 00000000000..4babed2e5d7 --- /dev/null +++ b/src/backend/statistics/stat_utils.c @@ -0,0 +1,94 @@ +/*------------------------------------------------------------------------- + * stat_utils.c + * + * PostgreSQL statistics manipulation utilities. + * + * Code supporting the direct manipulation of statistics. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/statistics/stat_utils.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/relation.h" +#include "catalog/pg_database.h" +#include "miscadmin.h" +#include "statistics/stat_utils.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/acl.h" +#include "utils/rel.h" + +/* + * Ensure that a given argument is not null. + */ +void +stats_check_required_arg(FunctionCallInfo fcinfo, + struct StatsArgInfo *arginfo, + int argnum) +{ + if (PG_ARGISNULL(argnum)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" cannot be NULL", + arginfo[argnum].argname))); +} + +/* + * Lock relation in ShareUpdateExclusive mode, check privileges, and close the + * relation (but retain the lock). + * + * A role has privileges to set statistics on the relation if any of the + * following are true: + * - the role owns the current database and the relation is not shared + * - the role has the MAINTAIN privilege on the relation + */ +void +stats_lock_check_privileges(Oid reloid) +{ + Relation rel = relation_open(reloid, ShareUpdateExclusiveLock); + const char relkind = rel->rd_rel->relkind; + + /* All of the types that can be used with ANALYZE, plus indexes */ + switch (relkind) + { + case RELKIND_RELATION: + case RELKIND_INDEX: + case RELKIND_MATVIEW: + case RELKIND_FOREIGN_TABLE: + case RELKIND_PARTITIONED_TABLE: + case RELKIND_PARTITIONED_INDEX: + break; + default: + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot modify statistics for relation \"%s\"", + RelationGetRelationName(rel)), + errdetail_relkind_not_supported(rel->rd_rel->relkind))); + } + + if (rel->rd_rel->relisshared) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot modify statistics for shared relation"))); + + if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())) + { + AclResult aclresult = pg_class_aclcheck(RelationGetRelid(rel), + GetUserId(), + ACL_MAINTAIN); + + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, + get_relkind_objtype(rel->rd_rel->relkind), + NameStr(rel->rd_rel->relname)); + } + + relation_close(rel, NoLock); +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index bbae8d6ee47..b1223ba9d5d 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12041,6 +12041,43 @@ { oid => '6292', descr => 'aggregate transition function', proname => 'any_value_transfn', prorettype => 'anyelement', proargtypes => 'anyelement anyelement', prosrc => 'any_value_transfn' }, +{ oid => '6321', descr => 'list of available WAL summary files', + proname => 'pg_available_wal_summaries', prorows => '100', proretset => 't', + provolatile => 'v', prorettype => 'record', proargtypes => '', + proallargtypes => '{int8,pg_lsn,pg_lsn}', proargmodes => '{o,o,o}', + proargnames => '{tli,start_lsn,end_lsn}', + prosrc => 'pg_available_wal_summaries' }, +{ oid => '6322', descr => 'contents of a WAL summary file', + proname => 'pg_wal_summary_contents', prorows => '100', proretset => 't', + provolatile => 'v', prorettype => 'record', + proargtypes => 'int8 pg_lsn pg_lsn', + proallargtypes => '{int8,pg_lsn,pg_lsn,oid,oid,oid,int2,int8,bool}', + proargmodes => '{i,i,i,o,o,o,o,o,o}', + proargnames => '{tli,start_lsn,end_lsn,relfilenode,reltablespace,reldatabase,relforknumber,relblocknumber,is_limit_block}', + prosrc => 'pg_wal_summary_contents' }, +{ oid => '6323', descr => 'WAL summarizer state', + proname => 'pg_get_wal_summarizer_state', provolatile => 'v', + prorettype => 'record', proargtypes => '', + proallargtypes => '{int8,pg_lsn,pg_lsn,int4}', proargmodes => '{o,o,o,o}', + proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}', + prosrc => 'pg_get_wal_summarizer_state' }, + # Statistics Import +{ oid => '9944', + descr => 'set statistics on relation', + proname => 'pg_set_relation_stats', provolatile => 'v', proisstrict => 'f', + proparallel => 'u', prorettype => 'bool', + proargtypes => 'regclass int4 float4 int4', + proargnames => '{relation,relpages,reltuples,relallvisible}', + prosrc => 'pg_set_relation_stats' }, +{ oid => '9945', + descr => 'clear statistics on relation', + proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f', + proparallel => 'u', prorettype => 'bool', + proargtypes => 'regclass', + proargnames => '{relation}', + prosrc => 'pg_clear_relation_stats' }, + +# MDB { oid => '16383', descr => 'contains', proname => 'mdb_locale_enabled', prorettype => 'bool', proargtypes => '', prosrc => 'mdb_locale_enabled' }, diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h new file mode 100644 index 00000000000..46057c60c03 --- /dev/null +++ b/src/include/statistics/stat_utils.h @@ -0,0 +1,29 @@ +/*------------------------------------------------------------------------- + * + * stat_utils.h + * Extended statistics and selectivity estimation functions. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/statistics/stat_utils.h + * + *------------------------------------------------------------------------- + */ +#ifndef STATS_UTILS_H +#define STATS_UTILS_H + +#include "fmgr.h" + +struct StatsArgInfo +{ + const char *argname; + Oid argtype; +}; + +extern void stats_check_required_arg(FunctionCallInfo fcinfo, + struct StatsArgInfo *arginfo, + int argnum); +extern void stats_lock_check_privileges(Oid reloid); + +#endif /* STATS_UTILS_H */ diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out new file mode 100644 index 00000000000..cd1b80aa43a --- /dev/null +++ b/src/test/regress/expected/stats_import.out @@ -0,0 +1,143 @@ +CREATE SCHEMA stats_import; +CREATE TYPE stats_import.complex_type AS ( + a integer, + b real, + c text, + d date, + e jsonb); +CREATE TABLE stats_import.test( + id INTEGER PRIMARY KEY, + name text, + comp stats_import.complex_type, + arange int4range, + tags text[] +); +-- starting stats +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 0 | -1 | 0 +(1 row) + +-- error: regclass not found +SELECT + pg_catalog.pg_set_relation_stats( + relation => 0::Oid, + relpages => 17::integer, + reltuples => 400.0::real, + relallvisible => 4::integer); +ERROR: could not open relation with OID 0 +-- relpages default +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => NULL::integer, + reltuples => 400.0::real, + relallvisible => 4::integer); + pg_set_relation_stats +----------------------- + t +(1 row) + +-- reltuples default +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => 17::integer, + reltuples => NULL::real, + relallvisible => 4::integer); + pg_set_relation_stats +----------------------- + t +(1 row) + +-- relallvisible default +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => 17::integer, + reltuples => 400.0::real, + relallvisible => NULL::integer); + pg_set_relation_stats +----------------------- + f +(1 row) + +-- named arguments +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => 17::integer, + reltuples => 400.0::real, + relallvisible => 4::integer); + pg_set_relation_stats +----------------------- + f +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 17 | 400 | 4 +(1 row) + +-- positional arguments +SELECT + pg_catalog.pg_set_relation_stats( + 'stats_import.test'::regclass, + 18::integer, + 401.0::real, + 5::integer); + pg_set_relation_stats +----------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 18 | 401 | 5 +(1 row) + +-- clear +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.test'::regclass); + pg_clear_relation_stats +------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 0 | -1 | 0 +(1 row) + +-- invalid relkinds for statistics +CREATE SEQUENCE stats_import.testseq; +CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test; +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.testseq'::regclass); +ERROR: cannot modify statistics for relation "testseq" +DETAIL: This operation is not supported for sequences. +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.testview'::regclass); +ERROR: cannot modify statistics for relation "testview" +DETAIL: This operation is not supported for views. +DROP SCHEMA stats_import CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to type stats_import.complex_type +drop cascades to table stats_import.test +drop cascades to sequence stats_import.testseq +drop cascades to view stats_import.testview diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 982336efbfe..d27dec78b9d 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -40,7 +40,7 @@ test: strings md5 numerology point lseg line box path polygon circle date time t # geometry depends on point, lseg, line, box, path, polygon, circle # horology depends on date, time, timetz, timestamp, timestamptz, interval # ---------- -test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database +test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import # ---------- # Load huge amounts of data diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql new file mode 100644 index 00000000000..3e9f6d91241 --- /dev/null +++ b/src/test/regress/sql/stats_import.sql @@ -0,0 +1,98 @@ +CREATE SCHEMA stats_import; + +CREATE TYPE stats_import.complex_type AS ( + a integer, + b real, + c text, + d date, + e jsonb); + +CREATE TABLE stats_import.test( + id INTEGER PRIMARY KEY, + name text, + comp stats_import.complex_type, + arange int4range, + tags text[] +); + +-- starting stats +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- error: regclass not found +SELECT + pg_catalog.pg_set_relation_stats( + relation => 0::Oid, + relpages => 17::integer, + reltuples => 400.0::real, + relallvisible => 4::integer); + +-- relpages default +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => NULL::integer, + reltuples => 400.0::real, + relallvisible => 4::integer); + +-- reltuples default +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => 17::integer, + reltuples => NULL::real, + relallvisible => 4::integer); + +-- relallvisible default +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => 17::integer, + reltuples => 400.0::real, + relallvisible => NULL::integer); + +-- named arguments +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => 17::integer, + reltuples => 400.0::real, + relallvisible => 4::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- positional arguments +SELECT + pg_catalog.pg_set_relation_stats( + 'stats_import.test'::regclass, + 18::integer, + 401.0::real, + 5::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- clear +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.test'::regclass); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- invalid relkinds for statistics +CREATE SEQUENCE stats_import.testseq; +CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test; +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.testseq'::regclass); +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.testview'::regclass); + +DROP SCHEMA stats_import CASCADE; From 0d55f8c98cfd26591ef0fb8ae0c2e79aca23ad61 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Fri, 11 Oct 2024 16:55:11 -0700 Subject: [PATCH 04/16] Create functions pg_set_relation_stats, pg_clear_relation_stats. These functions are used to tweak statistics on any relation, provided that the user has MAINTAIN privilege on the relation, or is the database owner. Bump catalog version. Author: Corey Huinker Discussion: https://postgr.es/m/CADkLM=eErgzn7ECDpwFcptJKOk9SxZEk5Pot4d94eVTZsvj3gw@mail.gmail.com --- src/backend/catalog/system_functions.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 413e2551231..da18c04a825 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -611,6 +611,16 @@ LANGUAGE INTERNAL CALLED ON NULL INPUT VOLATILE AS 'pg_set_relation_stats'; +CREATE OR REPLACE FUNCTION + pg_set_relation_stats(relation regclass, + relpages integer DEFAULT NULL, + reltuples real DEFAULT NULL, + relallvisible integer DEFAULT NULL) +RETURNS bool +LANGUAGE INTERNAL +CALLED ON NULL INPUT VOLATILE +AS 'pg_set_relation_stats'; + -- -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather From 676fdc056f6389d04c6705deed788f78e311e313 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Fri, 18 Oct 2024 10:44:15 -0700 Subject: [PATCH 05/16] Allow pg_set_relation_stats() to set relpages to -1. While the default value for relpages is 0, if a partitioned table with at least one child has been analyzed, then the partititoned table will have a relpages value of -1. Author: Corey Huinker Discussion: https://postgr.es/m/CADkLM=fajh1Lpcyr_XsMmq-9Z=SGk-u+_Zeac7Pt0RAN3uiVCg@mail.gmail.com --- doc/src/sgml/func.sgml | 2 +- src/backend/statistics/relation_stats.c | 5 +++ src/test/regress/expected/stats_import.out | 38 +++++++++++++++++++++- src/test/regress/sql/stats_import.sql | 25 ++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index cffa5d92ed3..ebb27eab74c 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -28087,7 +28087,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset The value of relpages must be greater than - or equal to 0, + or equal to -1, reltuples must be greater than or equal to -1.0, and relallvisible must be greater than or equal to 0. diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c index ffa3d83a878..1a6d1640c30 100644 --- a/src/backend/statistics/relation_stats.c +++ b/src/backend/statistics/relation_stats.c @@ -99,6 +99,11 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) { int32 relpages = PG_GETARG_INT32(RELPAGES_ARG); + /* + * Partitioned tables may have relpages=-1. Note: for relations with + * no storage, relpages=-1 is not used consistently, but must be + * supported here. + */ if (relpages < -1) { ereport(elevel, diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index cd1b80aa43a..495e9b03485 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -135,9 +135,45 @@ SELECT 'stats_import.testview'::regclass); ERROR: cannot modify statistics for relation "testview" DETAIL: This operation is not supported for views. +-- relpages may be -1 for partitioned tables +CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i); +CREATE TABLE stats_import.part_child_1 + PARTITION OF stats_import.part_parent + FOR VALUES FROM (0) TO (10); +ANALYZE stats_import.part_parent; +SELECT relpages +FROM pg_class +WHERE oid = 'stats_import.part_parent'::regclass; + relpages +---------- + -1 +(1 row) + +-- although partitioned tables have no storage, setting relpages to a +-- positive value is still allowed +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.part_parent'::regclass, + relpages => 2::integer); + pg_set_relation_stats +----------------------- + t +(1 row) + +-- nothing stops us from setting it to -1 +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.part_parent'::regclass, + relpages => -1::integer); + pg_set_relation_stats +----------------------- + t +(1 row) + DROP SCHEMA stats_import CASCADE; -NOTICE: drop cascades to 4 other objects +NOTICE: drop cascades to 5 other objects DETAIL: drop cascades to type stats_import.complex_type drop cascades to table stats_import.test drop cascades to sequence stats_import.testseq drop cascades to view stats_import.testview +drop cascades to table stats_import.part_parent diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 3e9f6d91241..108b0c45586 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -95,4 +95,29 @@ SELECT pg_catalog.pg_clear_relation_stats( 'stats_import.testview'::regclass); +-- relpages may be -1 for partitioned tables +CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i); +CREATE TABLE stats_import.part_child_1 + PARTITION OF stats_import.part_parent + FOR VALUES FROM (0) TO (10); + +ANALYZE stats_import.part_parent; + +SELECT relpages +FROM pg_class +WHERE oid = 'stats_import.part_parent'::regclass; + +-- although partitioned tables have no storage, setting relpages to a +-- positive value is still allowed +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.part_parent'::regclass, + relpages => 2::integer); + +-- nothing stops us from setting it to -1 +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.part_parent'::regclass, + relpages => -1::integer); + DROP SCHEMA stats_import CASCADE; From f85dd46356414d6cc87d651c039098871527f949 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Fri, 18 Oct 2024 10:57:46 -0700 Subject: [PATCH 06/16] Disable autovacuum for tables in stats import tests. While we haven't observed any test instability, it seems like a good idea to disable autovacuum during the stats import tests. Author: Corey Huinker Discussion: https://postgr.es/m/CADkLM=fajh1Lpcyr_XsMmq-9Z=SGk-u+_Zeac7Pt0RAN3uiVCg@mail.gmail.com --- src/test/regress/expected/stats_import.out | 5 +++-- src/test/regress/sql/stats_import.sql | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index 495e9b03485..a4e3a0f3c48 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -11,7 +11,7 @@ CREATE TABLE stats_import.test( comp stats_import.complex_type, arange int4range, tags text[] -); +) WITH (autovacuum_enabled = false); -- starting stats SELECT relpages, reltuples, relallvisible FROM pg_class @@ -139,7 +139,8 @@ DETAIL: This operation is not supported for views. CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i); CREATE TABLE stats_import.part_child_1 PARTITION OF stats_import.part_parent - FOR VALUES FROM (0) TO (10); + FOR VALUES FROM (0) TO (10) + WITH (autovacuum_enabled = false); ANALYZE stats_import.part_parent; SELECT relpages FROM pg_class diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 108b0c45586..126b3ab9b9c 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -13,7 +13,7 @@ CREATE TABLE stats_import.test( comp stats_import.complex_type, arange int4range, tags text[] -); +) WITH (autovacuum_enabled = false); -- starting stats SELECT relpages, reltuples, relallvisible @@ -99,7 +99,8 @@ SELECT CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i); CREATE TABLE stats_import.part_child_1 PARTITION OF stats_import.part_parent - FOR VALUES FROM (0) TO (10); + FOR VALUES FROM (0) TO (10) + WITH (autovacuum_enabled = false); ANALYZE stats_import.part_parent; From 0e39dc63e637f682fac133733f59f7d5f79abcde Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Tue, 22 Oct 2024 12:48:01 -0700 Subject: [PATCH 07/16] Change pg_*_relation_stats() functions to return type to void. These functions will either raise an ERROR or run to normal completion, so no return value is necessary. Bump catalog version. Author: Corey Huinker Discussion: https://postgr.es/m/CADkLM=cBF8rnphuTyHFi3KYzB9ByDgx57HwK9Rz2yp7S+Om87w@mail.gmail.com --- doc/src/sgml/func.sgml | 11 ++++------- src/backend/catalog/system_functions.sql | 2 +- src/backend/statistics/relation_stats.c | 6 ++++-- src/include/catalog/pg_proc.dat | 4 ++-- src/test/regress/expected/stats_import.out | 12 ++++++------ 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index ebb27eab74c..8d6fc6bc45e 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -28064,15 +28064,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset , relpages integer , reltuples real , relallvisible integer ) - boolean + void Updates relation-level statistics for the given relation to the specified values. The parameters correspond to columns in pg_class. Unspecified - or NULL values leave the setting - unchanged. Returns true if a change was made; - false otherwise. + or NULL values leave the setting unchanged. Ordinarily, these statistics are collected automatically or updated @@ -28102,12 +28100,11 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset pg_clear_relation_stats pg_clear_relation_stats ( relation regclass ) - boolean + void Clears table-level statistics for the given relation, as though the - table was newly created. Returns true if a change - was made; false otherwise. + table was newly created. The caller must have the MAINTAIN privilege on diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index da18c04a825..2a3a47b1b0a 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -616,7 +616,7 @@ CREATE OR REPLACE FUNCTION relpages integer DEFAULT NULL, reltuples real DEFAULT NULL, relallvisible integer DEFAULT NULL) -RETURNS bool +RETURNS void LANGUAGE INTERNAL CALLED ON NULL INPUT VOLATILE AS 'pg_set_relation_stats'; diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c index 1a6d1640c30..b1eb8a9bbaf 100644 --- a/src/backend/statistics/relation_stats.c +++ b/src/backend/statistics/relation_stats.c @@ -188,7 +188,8 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) Datum pg_set_relation_stats(PG_FUNCTION_ARGS) { - PG_RETURN_BOOL(relation_statistics_update(fcinfo, ERROR)); + relation_statistics_update(fcinfo, ERROR); + PG_RETURN_VOID(); } /* @@ -211,5 +212,6 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS) newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE; newfcinfo->args[3].isnull = false; - PG_RETURN_BOOL(relation_statistics_update(newfcinfo, ERROR)); + relation_statistics_update(newfcinfo, ERROR); + PG_RETURN_VOID(); } diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index b1223ba9d5d..68b2d8386c1 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12065,14 +12065,14 @@ { oid => '9944', descr => 'set statistics on relation', proname => 'pg_set_relation_stats', provolatile => 'v', proisstrict => 'f', - proparallel => 'u', prorettype => 'bool', + proparallel => 'u', prorettype => 'void', proargtypes => 'regclass int4 float4 int4', proargnames => '{relation,relpages,reltuples,relallvisible}', prosrc => 'pg_set_relation_stats' }, { oid => '9945', descr => 'clear statistics on relation', proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f', - proparallel => 'u', prorettype => 'bool', + proparallel => 'u', prorettype => 'void', proargtypes => 'regclass', proargnames => '{relation}', prosrc => 'pg_clear_relation_stats' }, diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index a4e3a0f3c48..df39ac2b635 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -38,7 +38,7 @@ SELECT relallvisible => 4::integer); pg_set_relation_stats ----------------------- - t + (1 row) -- reltuples default @@ -50,7 +50,7 @@ SELECT relallvisible => 4::integer); pg_set_relation_stats ----------------------- - t + (1 row) -- relallvisible default @@ -62,7 +62,7 @@ SELECT relallvisible => NULL::integer); pg_set_relation_stats ----------------------- - f + (1 row) -- named arguments @@ -74,7 +74,7 @@ SELECT relallvisible => 4::integer); pg_set_relation_stats ----------------------- - f + (1 row) SELECT relpages, reltuples, relallvisible @@ -94,7 +94,7 @@ SELECT 5::integer); pg_set_relation_stats ----------------------- - t + (1 row) SELECT relpages, reltuples, relallvisible @@ -111,7 +111,7 @@ SELECT 'stats_import.test'::regclass); pg_clear_relation_stats ------------------------- - t + (1 row) SELECT relpages, reltuples, relallvisible From df148607b28d52851eece215d15c17bce5ad9424 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Tue, 22 Oct 2024 15:06:55 -0700 Subject: [PATCH 08/16] Add functions pg_set_attribute_stats() and pg_clear_attribute_stats(). Enable manipulation of attribute statistics. Only superficial validation is performed, so it's possible to add nonsense, and it's up to the planner (or other users of statistics) to behave reasonably in that case. Bump catalog version. Author: Corey Huinker Discussion: https://postgr.es/m/CADkLM=eErgzn7ECDpwFcptJKOk9SxZEk5Pot4d94eVTZsvj3gw@mail.gmail.com --- doc/src/sgml/func.sgml | 72 ++ src/backend/catalog/system_functions.sql | 22 + src/backend/statistics/Makefile | 1 + src/backend/statistics/attribute_stats.c | 869 +++++++++++++++++++++ src/backend/statistics/meson.build | 1 + src/backend/statistics/stat_utils.c | 73 ++ src/include/catalog/pg_proc.dat | 14 + src/include/statistics/stat_utils.h | 7 + src/test/regress/expected/stats_import.out | 663 +++++++++++++++- src/test/regress/sql/stats_import.sql | 545 +++++++++++++ 10 files changed, 2264 insertions(+), 3 deletions(-) create mode 100644 src/backend/statistics/attribute_stats.c diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 8d6fc6bc45e..95e76457585 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -28112,6 +28112,78 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset + + + + + + pg_set_attribute_stats + + pg_set_attribute_stats ( + relation regclass, + attname name, + inherited boolean + , null_frac real + , avg_width integer + , n_distinct real + , most_common_vals text, most_common_freqs real[] + , histogram_bounds text + , correlation real + , most_common_elems text, most_common_elem_freqs real[] + , elem_count_histogram real[] + , range_length_histogram text + , range_empty_frac real + , range_bounds_histogram text ) + void + + + Creates or updates attribute-level statistics for the given relation + and attribute name to the specified values. The parameters correspond + to to attributes of the same name found in the pg_stats + view. + + + Optional parameters default to NULL, which leave + the corresponding statistic unchanged. + + + Ordinarily, these statistics are collected automatically or updated + as a part of or , so it's not necessary to call this + function. However, it may be useful when testing the effects of + statistics on the planner to understand or anticipate plan changes. + + + The caller must have the MAINTAIN privilege on + the table or be the owner of the database. + + + + + + + + + pg_clear_attribute_stats + + pg_clear_attribute_stats ( + relation regclass, + attname name, + inherited boolean ) + boolean + + + Clears table-level statistics for the given relation attribute, as + though the table was newly created. + + + The caller must have the MAINTAIN privilege on + the table or be the owner of the database. + + + + diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 2a3a47b1b0a..59dd64f59e6 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -621,6 +621,28 @@ LANGUAGE INTERNAL CALLED ON NULL INPUT VOLATILE AS 'pg_set_relation_stats'; +CREATE OR REPLACE FUNCTION + pg_set_attribute_stats(relation regclass, + attname name, + inherited bool, + null_frac real DEFAULT NULL, + avg_width integer DEFAULT NULL, + n_distinct real DEFAULT NULL, + most_common_vals text DEFAULT NULL, + most_common_freqs real[] DEFAULT NULL, + histogram_bounds text DEFAULT NULL, + correlation real DEFAULT NULL, + most_common_elems text DEFAULT NULL, + most_common_elem_freqs real[] DEFAULT NULL, + elem_count_histogram real[] DEFAULT NULL, + range_length_histogram text DEFAULT NULL, + range_empty_frac real DEFAULT NULL, + range_bounds_histogram text DEFAULT NULL) +RETURNS void +LANGUAGE INTERNAL +CALLED ON NULL INPUT VOLATILE +AS 'pg_set_attribute_stats'; + -- -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather diff --git a/src/backend/statistics/Makefile b/src/backend/statistics/Makefile index 041f5f8a581..4672bd90f22 100644 --- a/src/backend/statistics/Makefile +++ b/src/backend/statistics/Makefile @@ -13,6 +13,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = \ + attribute_stats.o \ dependencies.o \ extended_stats.o \ mcv.o \ diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c new file mode 100644 index 00000000000..c920409680a --- /dev/null +++ b/src/backend/statistics/attribute_stats.c @@ -0,0 +1,869 @@ +/*------------------------------------------------------------------------- + * attribute_stats.c + * + * PostgreSQL relation attribute statistics manipulation. + * + * Code supporting the direct import of relation attribute statistics, similar + * to what is done by the ANALYZE command. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/statistics/attribute_stats.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/heapam.h" +#include "catalog/indexing.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_operator.h" +#include "nodes/nodeFuncs.h" +#include "statistics/statistics.h" +#include "statistics/stat_utils.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +#define DEFAULT_NULL_FRAC Float4GetDatum(0.0) +#define DEFAULT_AVG_WIDTH Int32GetDatum(0) /* unknown */ +#define DEFAULT_N_DISTINCT Float4GetDatum(0.0) /* unknown */ + +enum attribute_stats_argnum +{ + ATTRELATION_ARG = 0, + ATTNAME_ARG, + INHERITED_ARG, + NULL_FRAC_ARG, + AVG_WIDTH_ARG, + N_DISTINCT_ARG, + MOST_COMMON_VALS_ARG, + MOST_COMMON_FREQS_ARG, + HISTOGRAM_BOUNDS_ARG, + CORRELATION_ARG, + MOST_COMMON_ELEMS_ARG, + MOST_COMMON_ELEM_FREQS_ARG, + ELEM_COUNT_HISTOGRAM_ARG, + RANGE_LENGTH_HISTOGRAM_ARG, + RANGE_EMPTY_FRAC_ARG, + RANGE_BOUNDS_HISTOGRAM_ARG, + NUM_ATTRIBUTE_STATS_ARGS +}; + +static struct StatsArgInfo attarginfo[] = +{ + [ATTRELATION_ARG] = {"relation", REGCLASSOID}, + [ATTNAME_ARG] = {"attname", NAMEOID}, + [INHERITED_ARG] = {"inherited", BOOLOID}, + [NULL_FRAC_ARG] = {"null_frac", FLOAT4OID}, + [AVG_WIDTH_ARG] = {"avg_width", INT4OID}, + [N_DISTINCT_ARG] = {"n_distinct", FLOAT4OID}, + [MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTOID}, + [MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT4ARRAYOID}, + [HISTOGRAM_BOUNDS_ARG] = {"histogram_bounds", TEXTOID}, + [CORRELATION_ARG] = {"correlation", FLOAT4OID}, + [MOST_COMMON_ELEMS_ARG] = {"most_common_elems", TEXTOID}, + [MOST_COMMON_ELEM_FREQS_ARG] = {"most_common_elem_freqs", FLOAT4ARRAYOID}, + [ELEM_COUNT_HISTOGRAM_ARG] = {"elem_count_histogram", FLOAT4ARRAYOID}, + [RANGE_LENGTH_HISTOGRAM_ARG] = {"range_length_histogram", TEXTOID}, + [RANGE_EMPTY_FRAC_ARG] = {"range_empty_frac", FLOAT4OID}, + [RANGE_BOUNDS_HISTOGRAM_ARG] = {"range_bounds_histogram", TEXTOID}, + [NUM_ATTRIBUTE_STATS_ARGS] = {0} +}; + +static bool attribute_statistics_update(FunctionCallInfo fcinfo, int elevel); +static Node *get_attr_expr(Relation rel, int attnum); +static void get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel, + Oid *atttypid, int32 *atttypmod, + char *atttyptype, Oid *atttypcoll, + Oid *eq_opr, Oid *lt_opr); +static bool get_elem_stat_type(Oid atttypid, char atttyptype, int elevel, + Oid *elemtypid, Oid *elem_eq_opr); +static Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, + Oid typid, int32 typmod, int elevel, bool *ok); +static void set_stats_slot(Datum *values, bool *nulls, bool *replaces, + int16 stakind, Oid staop, Oid stacoll, + Datum stanumbers, bool stanumbers_isnull, + Datum stavalues, bool stavalues_isnull); +static void upsert_pg_statistic(Relation starel, HeapTuple oldtup, + Datum *values, bool *nulls, bool *replaces); +static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit); +static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited, + Datum *values, bool *nulls, bool *replaces); + +/* + * Insert or Update Attribute Statistics + * + * See pg_statistic.h for an explanation of how each statistic kind is + * stored. Custom statistics kinds are not supported. + * + * Depending on the statistics kind, we need to derive information from the + * attribute for which we're storing the stats. For instance, the MCVs are + * stored as an anyarray, and the representation of the array needs to store + * the correct element type, which must be derived from the attribute. + * + * Major errors, such as the table not existing, the attribute not existing, + * or a permissions failure are always reported at ERROR. Other errors, such + * as a conversion failure on one statistic kind, are reported at 'elevel', + * and other statistic kinds may still be updated. + */ +static bool +attribute_statistics_update(FunctionCallInfo fcinfo, int elevel) +{ + Oid reloid; + Name attname; + bool inherited; + AttrNumber attnum; + + Relation starel; + HeapTuple statup; + + Oid atttypid = InvalidOid; + int32 atttypmod; + char atttyptype; + Oid atttypcoll = InvalidOid; + Oid eq_opr = InvalidOid; + Oid lt_opr = InvalidOid; + + Oid elemtypid = InvalidOid; + Oid elem_eq_opr = InvalidOid; + + FmgrInfo array_in_fn; + + bool do_mcv = !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) && + !PG_ARGISNULL(MOST_COMMON_VALS_ARG); + bool do_histogram = !PG_ARGISNULL(HISTOGRAM_BOUNDS_ARG); + bool do_correlation = !PG_ARGISNULL(CORRELATION_ARG); + bool do_mcelem = !PG_ARGISNULL(MOST_COMMON_ELEMS_ARG) && + !PG_ARGISNULL(MOST_COMMON_ELEM_FREQS_ARG); + bool do_dechist = !PG_ARGISNULL(ELEM_COUNT_HISTOGRAM_ARG); + bool do_bounds_histogram = !PG_ARGISNULL(RANGE_BOUNDS_HISTOGRAM_ARG); + bool do_range_length_histogram = !PG_ARGISNULL(RANGE_LENGTH_HISTOGRAM_ARG) && + !PG_ARGISNULL(RANGE_EMPTY_FRAC_ARG); + + Datum values[Natts_pg_statistic] = {0}; + bool nulls[Natts_pg_statistic] = {0}; + bool replaces[Natts_pg_statistic] = {0}; + + bool result = true; + + stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG); + reloid = PG_GETARG_OID(ATTRELATION_ARG); + + /* lock before looking up attribute */ + stats_lock_check_privileges(reloid); + + stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); + attname = PG_GETARG_NAME(ATTNAME_ARG); + attnum = get_attnum(reloid, NameStr(*attname)); + + stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); + inherited = PG_GETARG_BOOL(INHERITED_ARG); + + /* + * Check argument sanity. If some arguments are unusable, emit at elevel + * and set the corresponding argument to NULL in fcinfo. + */ + + if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_FREQS_ARG, + elevel)) + { + do_mcv = false; + result = false; + } + + if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_ELEM_FREQS_ARG, + elevel)) + { + do_mcelem = false; + result = false; + } + if (!stats_check_arg_array(fcinfo, attarginfo, ELEM_COUNT_HISTOGRAM_ARG, + elevel)) + { + do_dechist = false; + result = false; + } + + if (!stats_check_arg_pair(fcinfo, attarginfo, + MOST_COMMON_VALS_ARG, MOST_COMMON_FREQS_ARG, + elevel)) + { + do_mcv = false; + result = false; + } + + if (!stats_check_arg_pair(fcinfo, attarginfo, + MOST_COMMON_ELEMS_ARG, + MOST_COMMON_ELEM_FREQS_ARG, elevel)) + { + do_mcelem = false; + result = false; + } + + if (!stats_check_arg_pair(fcinfo, attarginfo, + RANGE_LENGTH_HISTOGRAM_ARG, + RANGE_EMPTY_FRAC_ARG, elevel)) + { + do_range_length_histogram = false; + result = false; + } + + /* derive information from attribute */ + get_attr_stat_type(reloid, attnum, elevel, + &atttypid, &atttypmod, + &atttyptype, &atttypcoll, + &eq_opr, <_opr); + + /* if needed, derive element type */ + if (do_mcelem || do_dechist) + { + if (!get_elem_stat_type(atttypid, atttyptype, elevel, + &elemtypid, &elem_eq_opr)) + { + ereport(elevel, + (errmsg("unable to determine element type of attribute \"%s\"", NameStr(*attname)), + errdetail("Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST."))); + elemtypid = InvalidOid; + elem_eq_opr = InvalidOid; + + do_mcelem = false; + do_dechist = false; + result = false; + } + } + + /* histogram and correlation require less-than operator */ + if ((do_histogram || do_correlation) && !OidIsValid(lt_opr)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine less-than operator for attribute \"%s\"", NameStr(*attname)), + errdetail("Cannot set STATISTIC_KIND_HISTOGRAM or STATISTIC_KIND_CORRELATION."))); + + do_histogram = false; + do_correlation = false; + result = false; + } + + /* only range types can have range stats */ + if ((do_range_length_histogram || do_bounds_histogram) && + !(atttyptype == TYPTYPE_RANGE || atttyptype == TYPTYPE_MULTIRANGE)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("attribute \"%s\" is not a range type", NameStr(*attname)), + errdetail("Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM."))); + + do_bounds_histogram = false; + do_range_length_histogram = false; + result = false; + } + + fmgr_info(F_ARRAY_IN, &array_in_fn); + + starel = table_open(StatisticRelationId, RowExclusiveLock); + + statup = SearchSysCache3(STATRELATTINH, reloid, attnum, inherited); + + /* initialize from existing tuple if exists */ + if (HeapTupleIsValid(statup)) + heap_deform_tuple(statup, RelationGetDescr(starel), values, nulls); + else + init_empty_stats_tuple(reloid, attnum, inherited, values, nulls, + replaces); + + /* if specified, set to argument values */ + if (!PG_ARGISNULL(NULL_FRAC_ARG)) + { + values[Anum_pg_statistic_stanullfrac - 1] = PG_GETARG_DATUM(NULL_FRAC_ARG); + replaces[Anum_pg_statistic_stanullfrac - 1] = true; + } + if (!PG_ARGISNULL(AVG_WIDTH_ARG)) + { + values[Anum_pg_statistic_stawidth - 1] = PG_GETARG_DATUM(AVG_WIDTH_ARG); + replaces[Anum_pg_statistic_stawidth - 1] = true; + } + if (!PG_ARGISNULL(N_DISTINCT_ARG)) + { + values[Anum_pg_statistic_stadistinct - 1] = PG_GETARG_DATUM(N_DISTINCT_ARG); + replaces[Anum_pg_statistic_stadistinct - 1] = true; + } + + /* STATISTIC_KIND_MCV */ + if (do_mcv) + { + bool converted; + Datum stanumbers = PG_GETARG_DATUM(MOST_COMMON_FREQS_ARG); + Datum stavalues = text_to_stavalues("most_common_vals", + &array_in_fn, + PG_GETARG_DATUM(MOST_COMMON_VALS_ARG), + atttypid, atttypmod, + elevel, &converted); + + if (converted) + { + set_stats_slot(values, nulls, replaces, + STATISTIC_KIND_MCV, + eq_opr, atttypcoll, + stanumbers, false, stavalues, false); + } + else + result = false; + } + + /* STATISTIC_KIND_HISTOGRAM */ + if (do_histogram) + { + Datum stavalues; + bool converted = false; + + stavalues = text_to_stavalues("histogram_bounds", + &array_in_fn, + PG_GETARG_DATUM(HISTOGRAM_BOUNDS_ARG), + atttypid, atttypmod, elevel, + &converted); + + if (converted) + { + set_stats_slot(values, nulls, replaces, + STATISTIC_KIND_HISTOGRAM, + lt_opr, atttypcoll, + 0, true, stavalues, false); + } + else + result = false; + } + + /* STATISTIC_KIND_CORRELATION */ + if (do_correlation) + { + Datum elems[] = {PG_GETARG_DATUM(CORRELATION_ARG)}; + ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID); + Datum stanumbers = PointerGetDatum(arry); + + set_stats_slot(values, nulls, replaces, + STATISTIC_KIND_CORRELATION, + lt_opr, atttypcoll, + stanumbers, false, 0, true); + } + + /* STATISTIC_KIND_MCELEM */ + if (do_mcelem) + { + Datum stanumbers = PG_GETARG_DATUM(MOST_COMMON_ELEM_FREQS_ARG); + bool converted = false; + Datum stavalues; + + stavalues = text_to_stavalues("most_common_elems", + &array_in_fn, + PG_GETARG_DATUM(MOST_COMMON_ELEMS_ARG), + elemtypid, atttypmod, + elevel, &converted); + + if (converted) + { + set_stats_slot(values, nulls, replaces, + STATISTIC_KIND_MCELEM, + elem_eq_opr, atttypcoll, + stanumbers, false, stavalues, false); + } + else + result = false; + } + + /* STATISTIC_KIND_DECHIST */ + if (do_dechist) + { + Datum stanumbers = PG_GETARG_DATUM(ELEM_COUNT_HISTOGRAM_ARG); + + set_stats_slot(values, nulls, replaces, + STATISTIC_KIND_DECHIST, + elem_eq_opr, atttypcoll, + stanumbers, false, 0, true); + } + + /* + * STATISTIC_KIND_BOUNDS_HISTOGRAM + * + * This stakind appears before STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM even + * though it is numerically greater, and all other stakinds appear in + * numerical order. We duplicate this quirk for consistency. + */ + if (do_bounds_histogram) + { + bool converted = false; + Datum stavalues; + + stavalues = text_to_stavalues("range_bounds_histogram", + &array_in_fn, + PG_GETARG_DATUM(RANGE_BOUNDS_HISTOGRAM_ARG), + atttypid, atttypmod, + elevel, &converted); + + if (converted) + { + set_stats_slot(values, nulls, replaces, + STATISTIC_KIND_BOUNDS_HISTOGRAM, + InvalidOid, InvalidOid, + 0, true, stavalues, false); + } + else + result = false; + } + + /* STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM */ + if (do_range_length_histogram) + { + /* The anyarray is always a float8[] for this stakind */ + Datum elems[] = {PG_GETARG_DATUM(RANGE_EMPTY_FRAC_ARG)}; + ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID); + Datum stanumbers = PointerGetDatum(arry); + + bool converted = false; + Datum stavalues; + + stavalues = text_to_stavalues("range_length_histogram", + &array_in_fn, + PG_GETARG_DATUM(RANGE_LENGTH_HISTOGRAM_ARG), + FLOAT8OID, 0, elevel, &converted); + + if (converted) + { + set_stats_slot(values, nulls, replaces, + STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM, + Float8LessOperator, InvalidOid, + stanumbers, false, stavalues, false); + } + else + result = false; + } + + upsert_pg_statistic(starel, statup, values, nulls, replaces); + + if (HeapTupleIsValid(statup)) + ReleaseSysCache(statup); + table_close(starel, RowExclusiveLock); + + return result; +} + +/* + * If this relation is an index and that index has expressions in it, and + * the attnum specified is known to be an expression, then we must walk + * the list attributes up to the specified attnum to get the right + * expression. + */ +static Node * +get_attr_expr(Relation rel, int attnum) +{ + if ((rel->rd_rel->relkind == RELKIND_INDEX + || (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)) + && (rel->rd_indexprs != NIL) + && (rel->rd_index->indkey.values[attnum - 1] == 0)) + { + ListCell *indexpr_item = list_head(rel->rd_indexprs); + + for (int i = 0; i < attnum - 1; i++) + if (rel->rd_index->indkey.values[i] == 0) + indexpr_item = lnext(rel->rd_indexprs, indexpr_item); + + if (indexpr_item == NULL) /* shouldn't happen */ + elog(ERROR, "too few entries in indexprs list"); + + return (Node *) lfirst(indexpr_item); + } + return NULL; +} + +/* + * Derive type information from the attribute. + */ +static void +get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel, + Oid *atttypid, int32 *atttypmod, + char *atttyptype, Oid *atttypcoll, + Oid *eq_opr, Oid *lt_opr) +{ + Relation rel = relation_open(reloid, AccessShareLock); + Form_pg_attribute attr; + HeapTuple atup; + Node *expr; + TypeCacheEntry *typcache; + + atup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(reloid), + Int16GetDatum(attnum)); + + /* Attribute not found */ + if (!HeapTupleIsValid(atup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("attribute %d of relation \"%s\" does not exist", + attnum, RelationGetRelationName(rel)))); + + attr = (Form_pg_attribute) GETSTRUCT(atup); + + if (attr->attisdropped) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("attribute %d of relation \"%s\" does not exist", + attnum, RelationGetRelationName(rel)))); + + expr = get_attr_expr(rel, attr->attnum); + + /* + * 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. This + * mimics the behvior of examine_attribute(). + */ + if (expr == NULL) + { + *atttypid = attr->atttypid; + *atttypmod = attr->atttypmod; + *atttypcoll = attr->attcollation; + } + else + { + *atttypid = exprType(expr); + *atttypmod = exprTypmod(expr); + + if (OidIsValid(attr->attcollation)) + *atttypcoll = attr->attcollation; + else + *atttypcoll = exprCollation(expr); + } + ReleaseSysCache(atup); + + /* + * If it's a multirange, step down to the range type, as is done by + * multirange_typanalyze(). + */ + if (type_is_multirange(*atttypid)) + *atttypid = get_multirange_range(*atttypid); + + /* finds the right operators even if atttypid is a domain */ + typcache = lookup_type_cache(*atttypid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR); + *atttyptype = typcache->typtype; + *eq_opr = typcache->eq_opr; + *lt_opr = typcache->lt_opr; + + /* + * Special case: collation for tsvector is DEFAULT_COLLATION_OID. See + * compute_tsvector_stats(). + */ + if (*atttypid == TSVECTOROID) + *atttypcoll = DEFAULT_COLLATION_OID; + + relation_close(rel, NoLock); +} + +/* + * Derive element type information from the attribute type. + */ +static bool +get_elem_stat_type(Oid atttypid, char atttyptype, int elevel, + Oid *elemtypid, Oid *elem_eq_opr) +{ + TypeCacheEntry *elemtypcache; + + if (atttypid == TSVECTOROID) + { + /* + * Special case: element type for tsvector is text. See + * compute_tsvector_stats(). + */ + *elemtypid = TEXTOID; + } + else + { + /* find underlying element type through any domain */ + *elemtypid = get_base_element_type(atttypid); + } + + if (!OidIsValid(*elemtypid)) + return false; + + /* finds the right operator even if elemtypid is a domain */ + elemtypcache = lookup_type_cache(*elemtypid, TYPECACHE_EQ_OPR); + if (!OidIsValid(elemtypcache->eq_opr)) + return false; + + *elem_eq_opr = elemtypcache->eq_opr; + + return true; +} + +/* + * Cast a text datum into an array with element type elemtypid. + * + * If an error is encountered, capture it and re-throw at elevel, and set ok + * to false. If the resulting array contains NULLs, raise an error at elevel + * and set ok to false. Otherwise, set ok to true. + */ +static Datum +text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid, + int32 typmod, int elevel, bool *ok) +{ + LOCAL_FCINFO(fcinfo, 8); + char *s; + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + escontext.details_wanted = true; + + s = TextDatumGetCString(d); + + InitFunctionCallInfoData(*fcinfo, array_in, 3, InvalidOid, + (Node *) &escontext, NULL); + + fcinfo->args[0].value = CStringGetDatum(s); + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = ObjectIdGetDatum(typid); + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = Int32GetDatum(typmod); + fcinfo->args[2].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + pfree(s); + + if (SOFT_ERROR_OCCURRED(&escontext)) + { + if (elevel != ERROR) + escontext.error_data->elevel = elevel; + ThrowErrorData(escontext.error_data); + *ok = false; + return (Datum) 0; + } + + if (array_contains_nulls(DatumGetArrayTypeP(result))) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" array cannot contain NULL values", staname))); + *ok = false; + return (Datum) 0; + } + + *ok = true; + + return result; +} + +/* + * Find and update the slot with the given stakind, or use the first empty + * slot. + */ +static void +set_stats_slot(Datum *values, bool *nulls, bool *replaces, + int16 stakind, Oid staop, Oid stacoll, + Datum stanumbers, bool stanumbers_isnull, + Datum stavalues, bool stavalues_isnull) +{ + int slotidx; + int first_empty = -1; + AttrNumber stakind_attnum; + AttrNumber staop_attnum; + AttrNumber stacoll_attnum; + + /* find existing slot with given stakind */ + for (slotidx = 0; slotidx < STATISTIC_NUM_SLOTS; slotidx++) + { + stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx; + + if (first_empty < 0 && + DatumGetInt16(values[stakind_attnum]) == 0) + first_empty = slotidx; + if (DatumGetInt16(values[stakind_attnum]) == stakind) + break; + } + + if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0) + slotidx = first_empty; + + if (slotidx >= STATISTIC_NUM_SLOTS) + ereport(ERROR, + (errmsg("maximum number of statistics slots exceeded: %d", + slotidx + 1))); + + stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx; + staop_attnum = Anum_pg_statistic_staop1 - 1 + slotidx; + stacoll_attnum = Anum_pg_statistic_stacoll1 - 1 + slotidx; + + if (DatumGetInt16(values[stakind_attnum]) != stakind) + { + values[stakind_attnum] = Int16GetDatum(stakind); + replaces[stakind_attnum] = true; + } + if (DatumGetObjectId(values[staop_attnum]) != staop) + { + values[staop_attnum] = ObjectIdGetDatum(staop); + replaces[staop_attnum] = true; + } + if (DatumGetObjectId(values[stacoll_attnum]) != stacoll) + { + values[stacoll_attnum] = ObjectIdGetDatum(stacoll); + replaces[stacoll_attnum] = true; + } + if (!stanumbers_isnull) + { + values[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = stanumbers; + nulls[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = false; + replaces[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = true; + } + if (!stavalues_isnull) + { + values[Anum_pg_statistic_stavalues1 - 1 + slotidx] = stavalues; + nulls[Anum_pg_statistic_stavalues1 - 1 + slotidx] = false; + replaces[Anum_pg_statistic_stavalues1 - 1 + slotidx] = true; + } +} + +/* + * Upsert the pg_statistic record. + */ +static void +upsert_pg_statistic(Relation starel, HeapTuple oldtup, + Datum *values, bool *nulls, bool *replaces) +{ + HeapTuple newtup; + + if (HeapTupleIsValid(oldtup)) + { + newtup = heap_modify_tuple(oldtup, RelationGetDescr(starel), + values, nulls, replaces); + CatalogTupleUpdate(starel, &newtup->t_self, newtup); + } + else + { + newtup = heap_form_tuple(RelationGetDescr(starel), values, nulls); + CatalogTupleInsert(starel, newtup); + } + + heap_freetuple(newtup); +} + +/* + * Delete pg_statistic record. + */ +static bool +delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit) +{ + Relation sd = table_open(StatisticRelationId, RowExclusiveLock); + HeapTuple oldtup; + + /* Is there already a pg_statistic tuple for this attribute? */ + oldtup = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(reloid), + Int16GetDatum(attnum), + BoolGetDatum(stainherit)); + + if (HeapTupleIsValid(oldtup)) + { + CatalogTupleDelete(sd, &oldtup->t_self); + ReleaseSysCache(oldtup); + table_close(sd, RowExclusiveLock); + return true; + } + + table_close(sd, RowExclusiveLock); + return false; +} + +/* + * Initialize values and nulls for a new stats tuple. + */ +static void +init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited, + Datum *values, bool *nulls, bool *replaces) +{ + memset(nulls, true, sizeof(bool) * Natts_pg_statistic); + memset(replaces, true, sizeof(bool) * Natts_pg_statistic); + + /* must initialize non-NULL attributes */ + + values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(reloid); + nulls[Anum_pg_statistic_starelid - 1] = false; + values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(attnum); + nulls[Anum_pg_statistic_staattnum - 1] = false; + values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inherited); + nulls[Anum_pg_statistic_stainherit - 1] = false; + + values[Anum_pg_statistic_stanullfrac - 1] = DEFAULT_NULL_FRAC; + nulls[Anum_pg_statistic_stanullfrac - 1] = false; + values[Anum_pg_statistic_stawidth - 1] = DEFAULT_AVG_WIDTH; + nulls[Anum_pg_statistic_stawidth - 1] = false; + values[Anum_pg_statistic_stadistinct - 1] = DEFAULT_N_DISTINCT; + nulls[Anum_pg_statistic_stadistinct - 1] = false; + + /* initialize stakind, staop, and stacoll slots */ + for (int slotnum = 0; slotnum < STATISTIC_NUM_SLOTS; slotnum++) + { + values[Anum_pg_statistic_stakind1 + slotnum - 1] = (Datum) 0; + nulls[Anum_pg_statistic_stakind1 + slotnum - 1] = false; + values[Anum_pg_statistic_staop1 + slotnum - 1] = InvalidOid; + nulls[Anum_pg_statistic_staop1 + slotnum - 1] = false; + values[Anum_pg_statistic_stacoll1 + slotnum - 1] = InvalidOid; + nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false; + } +} + +/* + * Import statistics for a given relation attribute. + * + * Inserts or replaces a row in pg_statistic for the given relation and + * attribute name. It takes input parameters that correspond to columns in the + * view pg_stats. + * + * Parameters null_frac, avg_width, and n_distinct all correspond to NOT NULL + * columns in pg_statistic. The remaining parameters all belong to a specific + * stakind. Some stakinds require multiple parameters, which must be specified + * together (or neither specified). + * + * Parameters are only superficially validated. Omitting a parameter or + * passing NULL leaves the statistic unchanged. + * + * Parameters corresponding to ANYARRAY columns are instead passed in as text + * values, which is a valid input string for an array of the type or element + * type of the attribute. Any error generated by the array_in() function will + * in turn fail the function. + */ +Datum +pg_set_attribute_stats(PG_FUNCTION_ARGS) +{ + attribute_statistics_update(fcinfo, ERROR); + PG_RETURN_VOID(); +} + +/* + * Delete statistics for the given attribute. + */ +Datum +pg_clear_attribute_stats(PG_FUNCTION_ARGS) +{ + Oid reloid; + Name attname; + AttrNumber attnum; + bool inherited; + + stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG); + reloid = PG_GETARG_OID(ATTRELATION_ARG); + + stats_lock_check_privileges(reloid); + + stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); + attname = PG_GETARG_NAME(ATTNAME_ARG); + attnum = get_attnum(reloid, NameStr(*attname)); + + stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); + inherited = PG_GETARG_BOOL(INHERITED_ARG); + + delete_pg_statistic(reloid, attnum, inherited); + PG_RETURN_VOID(); +} diff --git a/src/backend/statistics/meson.build b/src/backend/statistics/meson.build index 11604e1bf46..2cd07904abb 100644 --- a/src/backend/statistics/meson.build +++ b/src/backend/statistics/meson.build @@ -1,6 +1,7 @@ # Copyright (c) 2022-2023, PostgreSQL Global Development Group backend_sources += files( + 'attribute_stats.c', 'dependencies.c', 'extended_stats.c', 'mcv.c', diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index 4babed2e5d7..e7997ec486e 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -40,6 +40,79 @@ stats_check_required_arg(FunctionCallInfo fcinfo, arginfo[argnum].argname))); } +/* + * Check that argument is either NULL or a one dimensional array with no + * NULLs. + * + * If a problem is found, emit at elevel, and return false. Otherwise return + * true. + */ +bool +stats_check_arg_array(FunctionCallInfo fcinfo, + struct StatsArgInfo *arginfo, + int argnum, int elevel) +{ + ArrayType *arr; + + if (PG_ARGISNULL(argnum)) + return true; + + arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum)); + + if (ARR_NDIM(arr) != 1) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" cannot be a multidimensional array", + arginfo[argnum].argname))); + return false; + } + + if (array_contains_nulls(arr)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" array cannot contain NULL values", + arginfo[argnum].argname))); + return false; + } + + return true; +} + +/* + * Enforce parameter pairs that must be specified together (or not at all) for + * a particular stakind, such as most_common_vals and most_common_freqs for + * STATISTIC_KIND_MCV. + * + * If a problem is found, emit at elevel, and return false. Otherwise return + * true. + */ +bool +stats_check_arg_pair(FunctionCallInfo fcinfo, + struct StatsArgInfo *arginfo, + int argnum1, int argnum2, int elevel) +{ + if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2)) + return true; + + if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2)) + { + int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2; + int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1; + + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" must be specified when \"%s\" is specified", + arginfo[nullarg].argname, + arginfo[otherarg].argname))); + + return false; + } + + return true; +} + /* * Lock relation in ShareUpdateExclusive mode, check privileges, and close the * relation (but retain the lock). diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 68b2d8386c1..f4d01f813a7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12062,6 +12062,20 @@ proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}', prosrc => 'pg_get_wal_summarizer_state' }, # Statistics Import +{ oid => '9162', + descr => 'set statistics on attribute', + proname => 'pg_set_attribute_stats', provolatile => 'v', proisstrict => 'f', + proparallel => 'u', prorettype => 'void', + proargtypes => 'regclass name bool float4 int4 float4 text _float4 text float4 text _float4 _float4 text float4 text', + proargnames => '{relation,attname,inherited,null_frac,avg_width,n_distinct,most_common_vals,most_common_freqs,histogram_bounds,correlation,most_common_elems,most_common_elem_freqs,elem_count_histogram,range_length_histogram,range_empty_frac,range_bounds_histogram}', + prosrc => 'pg_set_attribute_stats' }, +{ oid => '9163', + descr => 'clear statistics on attribute', + proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f', + proparallel => 'u', prorettype => 'void', + proargtypes => 'regclass name bool', + proargnames => '{relation,attname,inherited}', + prosrc => 'pg_clear_attribute_stats' }, { oid => '9944', descr => 'set statistics on relation', proname => 'pg_set_relation_stats', provolatile => 'v', proisstrict => 'f', diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h index 46057c60c03..e5f19d00609 100644 --- a/src/include/statistics/stat_utils.h +++ b/src/include/statistics/stat_utils.h @@ -24,6 +24,13 @@ struct StatsArgInfo extern void stats_check_required_arg(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum); +extern bool stats_check_arg_array(FunctionCallInfo fcinfo, + struct StatsArgInfo *arginfo, int argnum, + int elevel); +extern bool stats_check_arg_pair(FunctionCallInfo fcinfo, + struct StatsArgInfo *arginfo, + int argnum1, int argnum2, int elevel); + extern void stats_lock_check_privileges(Oid reloid); #endif /* STATS_UTILS_H */ diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index df39ac2b635..2868cc47f1d 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -158,7 +158,7 @@ SELECT relpages => 2::integer); pg_set_relation_stats ----------------------- - t + (1 row) -- nothing stops us from setting it to -1 @@ -168,13 +168,670 @@ SELECT relpages => -1::integer); pg_set_relation_stats ----------------------- - t + +(1 row) + +-- error: object doesn't exist +SELECT pg_catalog.pg_set_attribute_stats( + relation => '0'::oid, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); +ERROR: could not open relation with OID 0 +-- error: relation null +SELECT pg_catalog.pg_set_attribute_stats( + relation => NULL::oid, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); +ERROR: "relation" cannot be NULL +-- error: attname null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => NULL::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); +ERROR: "attname" cannot be NULL +-- error: inherited null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => NULL::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); +ERROR: "inherited" cannot be NULL +-- error: null_frac null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => NULL::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + pg_set_attribute_stats +------------------------ + +(1 row) + +-- error: avg_width null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => NULL::integer, + n_distinct => 0.3::real); + pg_set_attribute_stats +------------------------ + +(1 row) + +-- error: avg_width null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => NULL::real); + pg_set_attribute_stats +------------------------ + +(1 row) + +-- ok: no stakinds +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + pg_set_attribute_stats +------------------------ + +(1 row) + +SELECT stanullfrac, stawidth, stadistinct +FROM pg_statistic +WHERE starelid = 'stats_import.test'::regclass; + stanullfrac | stawidth | stadistinct +-------------+----------+------------- + 0.1 | 2 | 0.3 +(1 row) + +-- error: mcv / mcf null mismatch +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_freqs => '{0.1,0.2,0.3}'::real[] + ); +ERROR: "most_common_vals" must be specified when "most_common_freqs" is specified +-- error: mcv / mcf null mismatch part 2 +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{1,2,3}'::text + ); +ERROR: "most_common_freqs" must be specified when "most_common_vals" is specified +-- error: mcv / mcf type mismatch +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{2023-09-30,2024-10-31,3}'::text, + most_common_freqs => '{0.2,0.1}'::real[] + ); +ERROR: invalid input syntax for type integer: "2023-09-30" +-- warning: mcv cast failure +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{2,four,3}'::text, + most_common_freqs => '{0.3,0.25,0.05}'::real[] + ); +ERROR: invalid input syntax for type integer: "four" +-- ok: mcv+mcf +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{2,1,3}'::text, + most_common_freqs => '{0.3,0.25,0.05}'::real[] + ); + pg_set_attribute_stats +------------------------ + +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | | +(1 row) + +-- error: histogram elements null value +-- this generates no warnings, but perhaps it should +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + histogram_bounds => '{1,NULL,3,4}'::text + ); +ERROR: "histogram_bounds" array cannot contain NULL values +-- ok: histogram_bounds +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + histogram_bounds => '{1,2,3,4}'::text + ); + pg_set_attribute_stats +------------------------ + +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | | +(1 row) + +-- ok: correlation +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + correlation => 0.5::real); + pg_set_attribute_stats +------------------------ + +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | 0.5 | | | | | | +(1 row) + +-- error: scalars can't have mcelem +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_elems => '{1,3}'::text, + most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); +ERROR: unable to determine element type of attribute "id" +DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST. +-- error: mcelem / mcelem mismatch +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_elems => '{one,two}'::text + ); +ERROR: "most_common_elem_freqs" must be specified when "most_common_elems" is specified +-- error: mcelem / mcelem null mismatch part 2 +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_elem_freqs => '{0.3,0.2,0.2,0.3}'::real[] + ); +ERROR: "most_common_elems" must be specified when "most_common_elem_freqs" is specified +-- ok: mcelem +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_elems => '{one,three}'::text, + most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); + pg_set_attribute_stats +------------------------ + +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | | | | +(1 row) + +-- error: scalars can't have elem_count_histogram +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); +ERROR: unable to determine element type of attribute "id" +DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST. +-- error: elem_count_histogram null element +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + elem_count_histogram => '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); +ERROR: "elem_count_histogram" array cannot contain NULL values +-- ok: elem_count_histogram +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); + pg_set_attribute_stats +------------------------ + +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------ + stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | | +(1 row) + +-- error: scalars can't have range stats +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_empty_frac => 0.5::real, + range_length_histogram => '{399,499,Infinity}'::text + ); +ERROR: attribute "id" is not a range type +DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM. +-- error: range_empty_frac range_length_hist null mismatch +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_length_histogram => '{399,499,Infinity}'::text + ); +ERROR: "range_empty_frac" must be specified when "range_length_histogram" is specified +-- error: range_empty_frac range_length_hist null mismatch part 2 +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_empty_frac => 0.5::real + ); +ERROR: "range_length_histogram" must be specified when "range_empty_frac" is specified +-- ok: range_empty_frac + range_length_hist +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_empty_frac => 0.5::real, + range_length_histogram => '{399,499,Infinity}'::text + ); + pg_set_attribute_stats +------------------------ + +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | {399,499,Infinity} | 0.5 | +(1 row) + +-- error: scalars can't have range stats +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); +ERROR: attribute "id" is not a range type +DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM. +-- ok: range_bounds_histogram +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); + pg_set_attribute_stats +------------------------ + (1 row) +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+-------------------------------------- + stats_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"} +(1 row) + +-- error: cannot set most_common_elems for range type +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{"[2,3)","[1,2)","[3,4)"}'::text, + most_common_freqs => '{0.3,0.25,0.05}'::real[], + histogram_bounds => '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text, + correlation => 1.1::real, + most_common_elems => '{3,1}'::text, + most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[], + range_empty_frac => -0.5::real, + range_length_histogram => '{399,499,Infinity}'::text, + range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); +ERROR: unable to determine element type of attribute "arange" +DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST. +-- +-- Test the ability to exactly copy data from one table to an identical table, +-- correctly reconstructing the stakind order as well as the staopN and +-- stacollN values. Because oids are not stable across databases, we can only +-- test this when the source and destination are on the same database +-- instance. For that reason, we borrow and adapt a query found in fe_utils +-- and used by pg_dump/pg_upgrade. +-- +INSERT INTO stats_import.test +SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_import.complex_type, int4range(1,4), array['red','green'] +UNION ALL +SELECT 2, 'two', (2, 2.2, 'TWO', '2002-02-02', '[true, 4, "six"]')::stats_import.complex_type, int4range(1,4), array['blue','yellow'] +UNION ALL +SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type, int4range(-1,1), array['"orange"', 'purple', 'cyan'] +UNION ALL +SELECT 4, 'four', NULL, int4range(0,100), NULL; +CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1)); +-- Generate statistics on table with data +ANALYZE stats_import.test; +CREATE TABLE stats_import.test_clone ( LIKE stats_import.test ) + WITH (autovacuum_enabled = false); +CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1)); +-- +-- Copy stats from test to test_clone, and is_odd to is_odd_clone +-- +SELECT s.schemaname, s.tablename, s.attname, s.inherited +FROM pg_catalog.pg_stats AS s +CROSS JOIN LATERAL + pg_catalog.pg_set_attribute_stats( + relation => ('stats_import.' || s.tablename || '_clone')::regclass::oid, + attname => s.attname, + inherited => s.inherited, + null_frac => s.null_frac, + avg_width => s.avg_width, + n_distinct => s.n_distinct, + most_common_vals => s.most_common_vals::text, + most_common_freqs => s.most_common_freqs, + histogram_bounds => s.histogram_bounds::text, + correlation => s.correlation, + most_common_elems => s.most_common_elems::text, + most_common_elem_freqs => s.most_common_elem_freqs, + elem_count_histogram => s.elem_count_histogram, + range_bounds_histogram => s.range_bounds_histogram::text, + range_empty_frac => s.range_empty_frac, + range_length_histogram => s.range_length_histogram::text) AS r +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test', 'is_odd') +ORDER BY s.tablename, s.attname, s.inherited; + schemaname | tablename | attname | inherited +--------------+-----------+---------+----------- + stats_import | is_odd | expr | f + stats_import | test | arange | f + stats_import | test | comp | f + stats_import | test | id | f + stats_import | test | name | f + stats_import | test | tags | f +(6 rows) + +SELECT c.relname, COUNT(*) AS num_stats +FROM pg_class AS c +JOIN pg_statistic s ON s.starelid = c.oid +WHERE c.relnamespace = 'stats_import'::regnamespace +AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone') +GROUP BY c.relname +ORDER BY c.relname; + relname | num_stats +--------------+----------- + is_odd | 1 + is_odd_clone | 1 + test | 5 + test_clone | 5 +(4 rows) + +-- check test minus test_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- check test_clone minus test +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- check is_odd minus is_odd_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- check is_odd_clone minus is_odd +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + DROP SCHEMA stats_import CASCADE; -NOTICE: drop cascades to 5 other objects +NOTICE: drop cascades to 6 other objects DETAIL: drop cascades to type stats_import.complex_type drop cascades to table stats_import.test drop cascades to sequence stats_import.testseq drop cascades to view stats_import.testview drop cascades to table stats_import.part_parent +drop cascades to table stats_import.test_clone diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 126b3ab9b9c..9b6c9094862 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -121,4 +121,549 @@ SELECT relation => 'stats_import.part_parent'::regclass, relpages => -1::integer); +-- error: object doesn't exist +SELECT pg_catalog.pg_set_attribute_stats( + relation => '0'::oid, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + +-- error: relation null +SELECT pg_catalog.pg_set_attribute_stats( + relation => NULL::oid, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + +-- error: attname null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => NULL::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + +-- error: inherited null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => NULL::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + +-- error: null_frac null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => NULL::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + +-- error: avg_width null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => NULL::integer, + n_distinct => 0.3::real); + +-- error: avg_width null +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => NULL::real); + +-- ok: no stakinds +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + +SELECT stanullfrac, stawidth, stadistinct +FROM pg_statistic +WHERE starelid = 'stats_import.test'::regclass; + +-- error: mcv / mcf null mismatch +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_freqs => '{0.1,0.2,0.3}'::real[] + ); + +-- error: mcv / mcf null mismatch part 2 +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{1,2,3}'::text + ); + +-- error: mcv / mcf type mismatch +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{2023-09-30,2024-10-31,3}'::text, + most_common_freqs => '{0.2,0.1}'::real[] + ); + +-- warning: mcv cast failure +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{2,four,3}'::text, + most_common_freqs => '{0.3,0.25,0.05}'::real[] + ); + +-- ok: mcv+mcf +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{2,1,3}'::text, + most_common_freqs => '{0.3,0.25,0.05}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- error: histogram elements null value +-- this generates no warnings, but perhaps it should +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + histogram_bounds => '{1,NULL,3,4}'::text + ); + +-- ok: histogram_bounds +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + histogram_bounds => '{1,2,3,4}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- ok: correlation +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + correlation => 0.5::real); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- error: scalars can't have mcelem +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_elems => '{1,3}'::text, + most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); + +-- error: mcelem / mcelem mismatch +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_elems => '{one,two}'::text + ); + +-- error: mcelem / mcelem null mismatch part 2 +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_elem_freqs => '{0.3,0.2,0.2,0.3}'::real[] + ); + +-- ok: mcelem +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_elems => '{one,three}'::text, + most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + +-- error: scalars can't have elem_count_histogram +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); +-- error: elem_count_histogram null element +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + elem_count_histogram => '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); +-- ok: elem_count_histogram +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'tags'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + +-- error: scalars can't have range stats +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_empty_frac => 0.5::real, + range_length_histogram => '{399,499,Infinity}'::text + ); +-- error: range_empty_frac range_length_hist null mismatch +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_length_histogram => '{399,499,Infinity}'::text + ); +-- error: range_empty_frac range_length_hist null mismatch part 2 +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_empty_frac => 0.5::real + ); +-- ok: range_empty_frac + range_length_hist +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_empty_frac => 0.5::real, + range_length_histogram => '{399,499,Infinity}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- error: scalars can't have range stats +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'id'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); +-- ok: range_bounds_histogram +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- error: cannot set most_common_elems for range type +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'arange'::name, + inherited => false::boolean, + null_frac => 0.5::real, + avg_width => 2::integer, + n_distinct => -0.1::real, + most_common_vals => '{"[2,3)","[1,2)","[3,4)"}'::text, + most_common_freqs => '{0.3,0.25,0.05}'::real[], + histogram_bounds => '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text, + correlation => 1.1::real, + most_common_elems => '{3,1}'::text, + most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[], + range_empty_frac => -0.5::real, + range_length_histogram => '{399,499,Infinity}'::text, + range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); +-- +-- Test the ability to exactly copy data from one table to an identical table, +-- correctly reconstructing the stakind order as well as the staopN and +-- stacollN values. Because oids are not stable across databases, we can only +-- test this when the source and destination are on the same database +-- instance. For that reason, we borrow and adapt a query found in fe_utils +-- and used by pg_dump/pg_upgrade. +-- +INSERT INTO stats_import.test +SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_import.complex_type, int4range(1,4), array['red','green'] +UNION ALL +SELECT 2, 'two', (2, 2.2, 'TWO', '2002-02-02', '[true, 4, "six"]')::stats_import.complex_type, int4range(1,4), array['blue','yellow'] +UNION ALL +SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type, int4range(-1,1), array['"orange"', 'purple', 'cyan'] +UNION ALL +SELECT 4, 'four', NULL, int4range(0,100), NULL; + +CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1)); + +-- Generate statistics on table with data +ANALYZE stats_import.test; + +CREATE TABLE stats_import.test_clone ( LIKE stats_import.test ) + WITH (autovacuum_enabled = false); + +CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1)); + +-- +-- Copy stats from test to test_clone, and is_odd to is_odd_clone +-- +SELECT s.schemaname, s.tablename, s.attname, s.inherited +FROM pg_catalog.pg_stats AS s +CROSS JOIN LATERAL + pg_catalog.pg_set_attribute_stats( + relation => ('stats_import.' || s.tablename || '_clone')::regclass::oid, + attname => s.attname, + inherited => s.inherited, + null_frac => s.null_frac, + avg_width => s.avg_width, + n_distinct => s.n_distinct, + most_common_vals => s.most_common_vals::text, + most_common_freqs => s.most_common_freqs, + histogram_bounds => s.histogram_bounds::text, + correlation => s.correlation, + most_common_elems => s.most_common_elems::text, + most_common_elem_freqs => s.most_common_elem_freqs, + elem_count_histogram => s.elem_count_histogram, + range_bounds_histogram => s.range_bounds_histogram::text, + range_empty_frac => s.range_empty_frac, + range_length_histogram => s.range_length_histogram::text) AS r +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test', 'is_odd') +ORDER BY s.tablename, s.attname, s.inherited; + +SELECT c.relname, COUNT(*) AS num_stats +FROM pg_class AS c +JOIN pg_statistic s ON s.starelid = c.oid +WHERE c.relnamespace = 'stats_import'::regnamespace +AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone') +GROUP BY c.relname +ORDER BY c.relname; + +-- check test minus test_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass; + +-- check test_clone minus test +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass; + +-- check is_odd minus is_odd_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass; + +-- check is_odd_clone minus is_odd +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass; + DROP SCHEMA stats_import CASCADE; From 9e5bf6e87f11c4391bf5494a6a0a1e28b697df92 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Wed, 23 Oct 2024 16:11:45 -0700 Subject: [PATCH 09/16] Improve pg_set_attribute_stats() error message. Previously, an invalid attribute name was caught, but the error message was unhelpful. --- src/backend/statistics/attribute_stats.c | 10 ++++++++++ src/test/regress/expected/stats_import.out | 23 +++++++++++++++++++++- src/test/regress/sql/stats_import.sql | 23 +++++++++++++++++++++- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index c920409680a..e7c3bda99ea 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -161,6 +161,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel) stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); attname = PG_GETARG_NAME(ATTNAME_ARG); attnum = get_attnum(reloid, NameStr(*attname)); + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + NameStr(*attname), get_rel_name(reloid)))); stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); inherited = PG_GETARG_BOOL(INHERITED_ARG); @@ -860,6 +865,11 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS) stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); attname = PG_GETARG_NAME(ATTNAME_ARG); attnum = get_attnum(reloid, NameStr(*attname)); + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + NameStr(*attname), get_rel_name(reloid)))); stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); inherited = PG_GETARG_BOOL(INHERITED_ARG); diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index 2868cc47f1d..6ab21ca324c 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -180,6 +180,12 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); ERROR: could not open relation with OID 0 +-- error: object doesn't exist +SELECT pg_catalog.pg_clear_attribute_stats( + relation => '0'::oid, + attname => 'id'::name, + inherited => false::boolean); +ERROR: could not open relation with OID 0 -- error: relation null SELECT pg_catalog.pg_set_attribute_stats( relation => NULL::oid, @@ -189,6 +195,21 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); ERROR: "relation" cannot be NULL +-- error: attname doesn't exist +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'nope'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); +ERROR: column "nope" of relation "test" does not exist +-- error: attname doesn't exist +SELECT pg_catalog.pg_clear_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'nope'::name, + inherited => false::boolean); +ERROR: column "nope" of relation "test" does not exist -- error: attname null SELECT pg_catalog.pg_set_attribute_stats( relation => 'stats_import.test'::regclass, @@ -301,7 +322,7 @@ SELECT pg_catalog.pg_set_attribute_stats( most_common_freqs => '{0.2,0.1}'::real[] ); ERROR: invalid input syntax for type integer: "2023-09-30" --- warning: mcv cast failure +-- error: mcv cast failure SELECT pg_catalog.pg_set_attribute_stats( relation => 'stats_import.test'::regclass, attname => 'id'::name, diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 9b6c9094862..b5689ac8fb3 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -130,6 +130,12 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); +-- error: object doesn't exist +SELECT pg_catalog.pg_clear_attribute_stats( + relation => '0'::oid, + attname => 'id'::name, + inherited => false::boolean); + -- error: relation null SELECT pg_catalog.pg_set_attribute_stats( relation => NULL::oid, @@ -139,6 +145,21 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); +-- error: attname doesn't exist +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'nope'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + +-- error: attname doesn't exist +SELECT pg_catalog.pg_clear_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'nope'::name, + inherited => false::boolean); + -- error: attname null SELECT pg_catalog.pg_set_attribute_stats( relation => 'stats_import.test'::regclass, @@ -231,7 +252,7 @@ SELECT pg_catalog.pg_set_attribute_stats( most_common_freqs => '{0.2,0.1}'::real[] ); --- warning: mcv cast failure +-- error: mcv cast failure SELECT pg_catalog.pg_set_attribute_stats( relation => 'stats_import.test'::regclass, attname => 'id'::name, From 21d4d23b035289cec68b0f823bb9b3ce5dc601e2 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Thu, 24 Oct 2024 12:08:00 -0700 Subject: [PATCH 10/16] Add functions pg_restore_relation_stats(), pg_restore_attribute_stats(). Similar to the pg_set_*_stats() functions, except with a variadic signature that's designed to be more future-proof. Additionally, most problems are reported as WARNINGs rather than ERRORs, allowing most stats to be restored even if some cannot. These functions are intended to be called from pg_dump to avoid the need to run ANALYZE after an upgrade. Author: Corey Huinker Discussion: https://postgr.es/m/CADkLM=eErgzn7ECDpwFcptJKOk9SxZEk5Pot4d94eVTZsvj3gw@mail.gmail.com --- doc/src/sgml/func.sgml | 100 +++ src/backend/statistics/attribute_stats.c | 19 + src/backend/statistics/relation_stats.c | 60 +- src/backend/statistics/stat_utils.c | 126 +++ src/include/catalog/pg_proc.dat | 20 +- src/include/statistics/stat_utils.h | 5 + src/test/regress/expected/stats_import.out | 955 ++++++++++++++++++++- src/test/regress/sql/stats_import.sql | 696 ++++++++++++++- 8 files changed, 1890 insertions(+), 91 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 95e76457585..a2715b7c519 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -28113,6 +28113,55 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset + + + + pg_restore_relation_stats + + pg_restore_relation_stats ( + VARIADIC kwargs "any" ) + boolean + + + Similar to pg_set_relation_stats(), but intended + for bulk restore of relation statistics. The tracked statistics may + change from version to version, so the primary purpose of this + function is to maintain a consistent function signature to avoid + errors when restoring statistics from previous versions. + + + Arguments are passed as pairs of argname + and argvalue, where + argname corresponds to a named argument in + pg_set_relation_stats() and + argvalue is of the corresponding type. + + + Additionally, this function supports argument name + version of type integer, which + specifies the version from which the statistics originated, improving + intepretation of older statistics. + + + For example, to set the relpages and + reltuples of the table + mytable: + + SELECT pg_restore_relation_stats( + 'relation', 'mytable'::regclass, + 'relpages', 173::integer, + 'reltuples', 10000::float4); + + + + Minor errors are reported as a WARNING and + ignored, and remaining statistics will still be restored. If all + specified statistics are successfully restored, return + true, otherwise false. + + + + @@ -28184,6 +28233,57 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset + + + + pg_restore_attribute_stats + + pg_restore_attribute_stats ( + VARIADIC kwargs "any" ) + boolean + + + Similar to pg_set_attribute_stats(), but + intended for bulk restore of attribute statistics. The tracked + statistics may change from version to version, so the primary purpose + of this function is to maintain a consistent function signature to + avoid errors when restoring statistics from previous versions. + + + Arguments are passed as pairs of argname + and argvalue, where + argname corresponds to a named argument in + pg_set_attribute_stats() and + argvalue is of the corresponding type. + + + Additionally, this function supports argument name + version of type integer, which + specifies the version from which the statistics originated, improving + intepretation of older statistics. + + + For example, to set the avg_width and + null_frac for the attribute + col1 of the table + mytable: + + SELECT pg_restore_attribute_stats( + 'relation', 'mytable'::regclass, + 'attname', 'col1'::name, + 'inherited', false, + 'avg_width', 125::integer, + 'null_frac', 0.5::real); + + + + Minor errors are reported as a WARNING and + ignored, and remaining statistics will still be restored. If all + specified statistics are successfully restored, return + true, otherwise false. + + + diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index e7c3bda99ea..0da3bc2e3a7 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -877,3 +877,22 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS) delete_pg_statistic(reloid, attnum, inherited); PG_RETURN_VOID(); } + +Datum +pg_restore_attribute_stats(PG_FUNCTION_ARGS) +{ + LOCAL_FCINFO(positional_fcinfo, NUM_ATTRIBUTE_STATS_ARGS); + bool result = true; + + InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_ATTRIBUTE_STATS_ARGS, + InvalidOid, NULL, NULL); + + if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, + attarginfo, WARNING)) + result = false; + + if (!attribute_statistics_update(positional_fcinfo, WARNING)) + result = false; + + PG_RETURN_BOOL(result); +} diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c index b1eb8a9bbaf..5a2aabc921e 100644 --- a/src/backend/statistics/relation_stats.c +++ b/src/backend/statistics/relation_stats.c @@ -67,8 +67,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) bool nulls[3] = {0}; int ncols = 0; TupleDesc tupdesc; - HeapTuple newtup; - + bool result = true; stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG); reloid = PG_GETARG_OID(RELATION_ARG); @@ -109,11 +108,9 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) ereport(elevel, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("relpages cannot be < -1"))); - table_close(crel, RowExclusiveLock); - return false; + result = false; } - - if (relpages != pgcform->relpages) + else if (relpages != pgcform->relpages) { replaces[ncols] = Anum_pg_class_relpages; values[ncols] = Int32GetDatum(relpages); @@ -130,16 +127,15 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) ereport(elevel, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("reltuples cannot be < -1.0"))); - table_close(crel, RowExclusiveLock); - return false; + result = false; } - - if (reltuples != pgcform->reltuples) + else if (reltuples != pgcform->reltuples) { replaces[ncols] = Anum_pg_class_reltuples; values[ncols] = Float4GetDatum(reltuples); ncols++; } + } if (!PG_ARGISNULL(RELALLVISIBLE_ARG)) @@ -151,11 +147,9 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) ereport(elevel, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("relallvisible cannot be < 0"))); - table_close(crel, RowExclusiveLock); - return false; + result = false; } - - if (relallvisible != pgcform->relallvisible) + else if (relallvisible != pgcform->relallvisible) { replaces[ncols] = Anum_pg_class_relallvisible; values[ncols] = Int32GetDatum(relallvisible); @@ -164,22 +158,20 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) } /* only update pg_class if there is a meaningful change */ - if (ncols == 0) + if (ncols > 0) { - table_close(crel, RowExclusiveLock); - return false; - } - - newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values, - nulls); + HeapTuple newtup; - CatalogTupleUpdate(crel, &newtup->t_self, newtup); - heap_freetuple(newtup); + newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values, + nulls); + CatalogTupleUpdate(crel, &newtup->t_self, newtup); + heap_freetuple(newtup); + } /* release the lock, consistent with vac_update_relstats() */ table_close(crel, RowExclusiveLock); - return true; + return result; } /* @@ -215,3 +207,23 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS) relation_statistics_update(newfcinfo, ERROR); PG_RETURN_VOID(); } + +Datum +pg_restore_relation_stats(PG_FUNCTION_ARGS) +{ + LOCAL_FCINFO(positional_fcinfo, NUM_RELATION_STATS_ARGS); + bool result = true; + + InitFunctionCallInfoData(*positional_fcinfo, NULL, + NUM_RELATION_STATS_ARGS, + InvalidOid, NULL, NULL); + + if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, + relarginfo, WARNING)) + result = false; + + if (!relation_statistics_update(positional_fcinfo, WARNING)) + result = false; + + PG_RETURN_BOOL(result); +} diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index e7997ec486e..740038e3fe7 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -18,6 +18,7 @@ #include "access/relation.h" #include "catalog/pg_database.h" +#include "funcapi.h" #include "miscadmin.h" #include "statistics/stat_utils.h" #include "utils/array.h" @@ -165,3 +166,128 @@ stats_lock_check_privileges(Oid reloid) relation_close(rel, NoLock); } + +/* + * Find the argument number for the given argument name, returning -1 if not + * found. + */ +static int +get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel) +{ + int argnum; + + for (argnum = 0; arginfo[argnum].argname != NULL; argnum++) + if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0) + return argnum; + + ereport(elevel, + (errmsg("unrecognized argument name: \"%s\"", argname))); + + return -1; +} + +/* + * Ensure that a given argument matched the expected type. + */ +static bool +stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel) +{ + if (argtype != expectedtype) + { + ereport(elevel, + (errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"", + argname, format_type_be(argtype), + format_type_be(expectedtype)))); + return false; + } + + return true; +} + +/* + * Translate variadic argument pairs from 'pairs_fcinfo' into a + * 'positional_fcinfo' appropriate for calling relation_statistics_update() or + * attribute_statistics_update() with positional arguments. + * + * Caller should have already initialized positional_fcinfo with a size + * appropriate for calling the intended positional function, and arginfo + * should also match the intended positional function. + */ +bool +stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo, + FunctionCallInfo positional_fcinfo, + struct StatsArgInfo *arginfo, + int elevel) +{ + Datum *args; + bool *argnulls; + Oid *types; + int nargs; + bool result = true; + + /* clear positional args */ + for (int i = 0; arginfo[i].argname != NULL; i++) + { + positional_fcinfo->args[i].value = (Datum) 0; + positional_fcinfo->args[i].isnull = true; + } + + nargs = extract_variadic_args(pairs_fcinfo, 0, true, + &args, &types, &argnulls); + + if (nargs % 2 != 0) + ereport(ERROR, + errmsg("variadic arguments must be name/value pairs"), + errhint("Provide an even number of variadic arguments that can be divided into pairs.")); + + /* + * For each argument name/value pair, find corresponding positional + * argument for the argument name, and assign the argument value to + * postitional_fcinfo. + */ + for (int i = 0; i < nargs; i += 2) + { + int argnum; + char *argname; + + if (argnulls[i]) + ereport(ERROR, + (errmsg("name at variadic position %d is NULL", i + 1))); + + if (types[i] != TEXTOID) + ereport(ERROR, + (errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"", + i + 1, format_type_be(types[i]), + format_type_be(TEXTOID)))); + + if (argnulls[i + 1]) + continue; + + argname = TextDatumGetCString(args[i]); + + /* + * The 'version' argument is a special case, not handled by arginfo + * because it's not a valid positional argument. + * + * For now, 'version' is accepted but ignored. In the future it can be + * used to interpret older statistics properly. + */ + if (pg_strcasecmp(argname, "version") == 0) + continue; + + argnum = get_arg_by_name(argname, arginfo, elevel); + + if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1], + arginfo[argnum].argtype, + elevel)) + { + result = false; + continue; + } + + positional_fcinfo->args[argnum].value = args[i + 1]; + positional_fcinfo->args[argnum].isnull = false; + } + + return result; +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index f4d01f813a7..96a1314655f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12061,7 +12061,25 @@ proallargtypes => '{int8,pg_lsn,pg_lsn,int4}', proargmodes => '{o,o,o,o}', proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}', prosrc => 'pg_get_wal_summarizer_state' }, - # Statistics Import +# Statistics Import +{ oid => '6363', + descr => 'restore statistics on relation', + proname => 'pg_restore_relation_stats', provolatile => 'v', proisstrict => 'f', + provariadic => 'any', + proparallel => 'u', prorettype => 'bool', + proargtypes => 'any', + proargnames => '{kwargs}', + proargmodes => '{v}', + prosrc => 'pg_restore_relation_stats' }, +{ oid => '6398', + descr => 'restore statistics on attribute', + proname => 'pg_restore_attribute_stats', provolatile => 'v', proisstrict => 'f', + provariadic => 'any', + proparallel => 'u', prorettype => 'bool', + proargtypes => 'any', + proargnames => '{kwargs}', + proargmodes => '{v}', + prosrc => 'pg_restore_attribute_stats' }, { oid => '9162', descr => 'set statistics on attribute', proname => 'pg_set_attribute_stats', provolatile => 'v', proisstrict => 'f', diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h index e5f19d00609..61f1f6b3108 100644 --- a/src/include/statistics/stat_utils.h +++ b/src/include/statistics/stat_utils.h @@ -33,4 +33,9 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo, extern void stats_lock_check_privileges(Oid reloid); +extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo, + FunctionCallInfo positional_fcinfo, + struct StatsArgInfo *arginfo, + int elevel); + #endif /* STATS_UTILS_H */ diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index 6ab21ca324c..9186fc01ecc 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -228,45 +228,6 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); ERROR: "inherited" cannot be NULL --- error: null_frac null -SELECT pg_catalog.pg_set_attribute_stats( - relation => 'stats_import.test'::regclass, - attname => 'id'::name, - inherited => false::boolean, - null_frac => NULL::real, - avg_width => 2::integer, - n_distinct => 0.3::real); - pg_set_attribute_stats ------------------------- - -(1 row) - --- error: avg_width null -SELECT pg_catalog.pg_set_attribute_stats( - relation => 'stats_import.test'::regclass, - attname => 'id'::name, - inherited => false::boolean, - null_frac => 0.1::real, - avg_width => NULL::integer, - n_distinct => 0.3::real); - pg_set_attribute_stats ------------------------- - -(1 row) - --- error: avg_width null -SELECT pg_catalog.pg_set_attribute_stats( - relation => 'stats_import.test'::regclass, - attname => 'id'::name, - inherited => false::boolean, - null_frac => 0.1::real, - avg_width => 2::integer, - n_distinct => NULL::real); - pg_set_attribute_stats ------------------------- - -(1 row) - -- ok: no stakinds SELECT pg_catalog.pg_set_attribute_stats( relation => 'stats_import.test'::regclass, @@ -655,6 +616,711 @@ SELECT pg_catalog.pg_set_attribute_stats( ); ERROR: unable to determine element type of attribute "arange" DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST. +-- +-- Clear attribute stats to try again with restore functions +-- (relation stats were already cleared). +-- +SELECT + pg_catalog.pg_clear_attribute_stats( + 'stats_import.test'::regclass, + s.attname, + s.inherited) +FROM pg_catalog.pg_stats AS s +WHERE s.schemaname = 'stats_import' +AND s.tablename = 'test' +ORDER BY s.attname, s.inherited; + pg_clear_attribute_stats +-------------------------- + + + +(3 rows) + +-- reject: argument name is NULL +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + NULL, '17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); +ERROR: name at variadic position 5 is NULL +-- reject: argument name is an integer +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + 17, '17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); +ERROR: name at variadic position 5 has type "integer", expected type "text" +-- reject: odd number of variadic arguments cannot be pairs +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + 'relpages', '17'::integer, + 'reltuples', 400::real, + 'relallvisible'); +ERROR: variadic arguments must be name/value pairs +HINT: Provide an even number of variadic arguments that can be divided into pairs. +-- reject: object doesn't exist +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + 'relpages', '17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); +ERROR: could not open relation with OID 0 +-- ok: set all stats +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', '17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 17 | 400 | 4 +(1 row) + +-- ok: just relpages +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', '16'::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 16 | 400 | 4 +(1 row) + +-- ok: just reltuples +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'reltuples', '500'::real); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 16 | 500 | 4 +(1 row) + +-- ok: just relallvisible +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relallvisible', 5::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 16 | 500 | 5 +(1 row) + +-- warn and error: unrecognized argument name +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + 'relpages', '17'::integer, + 'reltuples', 400::real, + 'nope', 4::integer); +WARNING: unrecognized argument name: "nope" +ERROR: could not open relation with OID 0 +-- warn: bad relpages type +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', 'nope'::text, + 'reltuples', 400.0::real, + 'relallvisible', 4::integer); +WARNING: argument "relpages" has type "text", expected type "integer" + pg_restore_relation_stats +--------------------------- + f +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 16 | 400 | 4 +(1 row) + +-- error: object does not exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', '0'::oid::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); +ERROR: could not open relation with OID 0 +-- error: relation null +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', NULL::oid, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); +ERROR: "relation" cannot be NULL +-- error: attname null +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', NULL::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); +ERROR: "attname" cannot be NULL +-- error: attname doesn't exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'nope'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); +ERROR: column "nope" of relation "test" does not exist +-- error: inherited null +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', NULL::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); +ERROR: "inherited" cannot be NULL +-- ok: no stakinds +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.4::real, + 'avg_width', 5::integer, + 'n_distinct', 0.6::real); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.4 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- warn: unrecognized argument name +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', NULL::integer, + 'nope', 0.5::real); +WARNING: unrecognized argument name: "nope" + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- warn: mcv / mcf null mismatch part 1 +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.6::real, + 'avg_width', 7::integer, + 'n_distinct', -0.7::real, + 'most_common_freqs', '{0.1,0.2,0.3}'::real[] + ); +WARNING: "most_common_vals" must be specified when "most_common_freqs" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.6 | 7 | -0.7 | | | | | | | | | | +(1 row) + +-- warn: mcv / mcf null mismatch part 2 +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.7::real, + 'avg_width', 8::integer, + 'n_distinct', -0.8::real, + 'most_common_vals', '{1,2,3}'::text + ); +WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.7 | 8 | -0.8 | | | | | | | | | | +(1 row) + +-- warn: mcv / mcf type mismatch +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.8::real, + 'avg_width', 9::integer, + 'n_distinct', -0.9::real, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.2,0.1}'::double precision[] + ); +WARNING: argument "most_common_freqs" has type "double precision[]", expected type "real[]" +WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.8 | 9 | -0.9 | | | | | | | | | | +(1 row) + +-- warn: mcv cast failure +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.9::real, + 'avg_width', 10::integer, + 'n_distinct', -0.4::real, + 'most_common_vals', '{2,four,3}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); +WARNING: invalid input syntax for type integer: "four" + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.9 | 10 | -0.4 | | | | | | | | | | +(1 row) + +-- ok: mcv+mcf +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 1::integer, + 'n_distinct', -0.1::real, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.1 | 1 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | | +(1 row) + +-- warn: NULL in histogram array +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', 2::integer, + 'n_distinct', -0.2::real, + 'histogram_bounds', '{1,NULL,3,4}'::text + ); +WARNING: "histogram_bounds" array cannot contain NULL values + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.2 | 2 | -0.2 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | | +(1 row) + +-- ok: histogram_bounds +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.3::real, + 'avg_width', 3::integer, + 'n_distinct', -0.3::real, + 'histogram_bounds', '{1,2,3,4}'::text ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.3 | 3 | -0.3 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | | +(1 row) + +-- warn: elem_count_histogram null element +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'tags'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.4::real, + 'avg_width', 5::integer, + 'n_distinct', -0.4::real, + 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); +WARNING: "elem_count_histogram" array cannot contain NULL values + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | tags | f | 0.4 | 5 | -0.4 | | | | | | | | | | +(1 row) + +-- ok: elem_count_histogram +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'tags'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.5::real, + 'avg_width', 6::integer, + 'n_distinct', -0.55::real, + 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------ + stats_import | test | tags | f | 0.5 | 6 | -0.55 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | | +(1 row) + +-- range stats on a scalar type +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.6::real, + 'avg_width', 7::integer, + 'n_distinct', -0.15::real, + 'range_empty_frac', 0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); +WARNING: attribute "id" is not a range type +DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM. + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.6 | 7 | -0.15 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | | +(1 row) + +-- warn: range_empty_frac range_length_hist null mismatch +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.7::real, + 'avg_width', 8::integer, + 'n_distinct', -0.25::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); +WARNING: "range_empty_frac" must be specified when "range_length_histogram" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | arange | f | 0.7 | 8 | -0.25 | | | | | | | | | | +(1 row) + +-- warn: range_empty_frac range_length_hist null mismatch part 2 +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.8::real, + 'avg_width', 9::integer, + 'n_distinct', -0.35::real, + 'range_empty_frac', 0.5::real + ); +WARNING: "range_length_histogram" must be specified when "range_empty_frac" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | arange | f | 0.8 | 9 | -0.35 | | | | | | | | | | +(1 row) + +-- ok: range_empty_frac + range_length_hist +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.9::real, + 'avg_width', 1::integer, + 'n_distinct', -0.19::real, + 'range_empty_frac', 0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | arange | f | 0.9 | 1 | -0.19 | | | | | | | | {399,499,Infinity} | 0.5 | +(1 row) + +-- warn: range bounds histogram on scalar +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', -0.29::real, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); +WARNING: attribute "id" is not a range type +DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM. + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.1 | 2 | -0.29 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | | +(1 row) + +-- ok: range_bounds_histogram +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', 3::integer, + 'n_distinct', -0.39::real, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+-------------------------------------- + stats_import | test | arange | f | 0.2 | 3 | -0.39 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"} +(1 row) + +-- warn: too many stat kinds +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.5::real, + 'avg_width', 2::integer, + 'n_distinct', -0.1::real, + 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[], + 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text, + 'correlation', 1.1::real, + 'most_common_elems', '{3,1}'::text, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[], + 'range_empty_frac', -0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text); +WARNING: unable to determine element type of attribute "arange" +DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST. + pg_restore_attribute_stats +---------------------------- + f +(1 row) + -- -- Test the ability to exactly copy data from one table to an identical table, -- correctly reconstructing the stakind order as well as the staopN and @@ -848,6 +1514,217 @@ WHERE s.starelid = 'stats_import.is_odd'::regclass; ---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- (0 rows) +-- +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 1 | 4 | 0 +(1 row) + +-- +-- Clear clone stats to try again with pg_restore_attribute_stats +-- +SELECT + pg_catalog.pg_clear_attribute_stats( + ('stats_import.' || s.tablename)::regclass, + s.attname, + s.inherited) +FROM pg_catalog.pg_stats AS s +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test_clone', 'is_odd_clone') +ORDER BY s.tablename, s.attname, s.inherited; + pg_clear_attribute_stats +-------------------------- + + + + + + +(6 rows) + +SELECT +SELECT COUNT(*) +FROM pg_catalog.pg_stats AS s +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test_clone', 'is_odd_clone'); +ERROR: syntax error at or near "SELECT" +LINE 2: SELECT COUNT(*) + ^ +-- +-- Copy stats from test to test_clone, and is_odd to is_odd_clone +-- +SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.* +FROM pg_catalog.pg_stats AS s +CROSS JOIN LATERAL + pg_catalog.pg_restore_attribute_stats( + 'relation', ('stats_import.' || s.tablename || '_clone')::regclass, + 'attname', s.attname, + 'inherited', s.inherited, + 'version', 150000, + 'null_frac', s.null_frac, + 'avg_width', s.avg_width, + 'n_distinct', s.n_distinct, + 'most_common_vals', s.most_common_vals::text, + 'most_common_freqs', s.most_common_freqs, + 'histogram_bounds', s.histogram_bounds::text, + 'correlation', s.correlation, + 'most_common_elems', s.most_common_elems::text, + 'most_common_elem_freqs', s.most_common_elem_freqs, + 'elem_count_histogram', s.elem_count_histogram, + 'range_bounds_histogram', s.range_bounds_histogram::text, + 'range_empty_frac', s.range_empty_frac, + 'range_length_histogram', s.range_length_histogram::text) AS r +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test', 'is_odd') +ORDER BY s.tablename, s.attname, s.inherited; + schemaname | tablename | attname | inherited | r +--------------+-----------+---------+-----------+--- + stats_import | is_odd | expr | f | t + stats_import | test | arange | f | t + stats_import | test | comp | f | t + stats_import | test | id | f | t + stats_import | test | name | f | t + stats_import | test | tags | f | t +(6 rows) + +SELECT c.relname, COUNT(*) AS num_stats +FROM pg_class AS c +JOIN pg_statistic s ON s.starelid = c.oid +WHERE c.relnamespace = 'stats_import'::regnamespace +AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone') +GROUP BY c.relname +ORDER BY c.relname; + relname | num_stats +--------------+----------- + is_odd | 1 + is_odd_clone | 1 + test | 5 + test_clone | 5 +(4 rows) + +-- check test minus test_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- check test_clone minus test +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- check is_odd minus is_odd_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- check is_odd_clone minus is_odd +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + DROP SCHEMA stats_import CASCADE; NOTICE: drop cascades to 6 other objects DETAIL: drop cascades to type stats_import.complex_type diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index b5689ac8fb3..c7d5e017d90 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -178,33 +178,6 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); --- error: null_frac null -SELECT pg_catalog.pg_set_attribute_stats( - relation => 'stats_import.test'::regclass, - attname => 'id'::name, - inherited => false::boolean, - null_frac => NULL::real, - avg_width => 2::integer, - n_distinct => 0.3::real); - --- error: avg_width null -SELECT pg_catalog.pg_set_attribute_stats( - relation => 'stats_import.test'::regclass, - attname => 'id'::name, - inherited => false::boolean, - null_frac => 0.1::real, - avg_width => NULL::integer, - n_distinct => 0.3::real); - --- error: avg_width null -SELECT pg_catalog.pg_set_attribute_stats( - relation => 'stats_import.test'::regclass, - attname => 'id'::name, - inherited => false::boolean, - null_frac => 0.1::real, - avg_width => 2::integer, - n_distinct => NULL::real); - -- ok: no stakinds SELECT pg_catalog.pg_set_attribute_stats( relation => 'stats_import.test'::regclass, @@ -517,6 +490,507 @@ SELECT pg_catalog.pg_set_attribute_stats( range_length_histogram => '{399,499,Infinity}'::text, range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text ); + +-- +-- Clear attribute stats to try again with restore functions +-- (relation stats were already cleared). +-- +SELECT + pg_catalog.pg_clear_attribute_stats( + 'stats_import.test'::regclass, + s.attname, + s.inherited) +FROM pg_catalog.pg_stats AS s +WHERE s.schemaname = 'stats_import' +AND s.tablename = 'test' +ORDER BY s.attname, s.inherited; + +-- reject: argument name is NULL +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + NULL, '17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); + +-- reject: argument name is an integer +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + 17, '17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); + +-- reject: odd number of variadic arguments cannot be pairs +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + 'relpages', '17'::integer, + 'reltuples', 400::real, + 'relallvisible'); + +-- reject: object doesn't exist +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + 'relpages', '17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); + +-- ok: set all stats +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', '17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- ok: just relpages +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', '16'::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- ok: just reltuples +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'reltuples', '500'::real); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- ok: just relallvisible +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relallvisible', 5::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- warn and error: unrecognized argument name +SELECT pg_restore_relation_stats( + 'relation', '0'::oid::regclass, + 'version', 150000::integer, + 'relpages', '17'::integer, + 'reltuples', 400::real, + 'nope', 4::integer); + +-- warn: bad relpages type +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', 'nope'::text, + 'reltuples', 400.0::real, + 'relallvisible', 4::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- error: object does not exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', '0'::oid::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); + +-- error: relation null +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', NULL::oid, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); + +-- error: attname null +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', NULL::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); + +-- error: attname doesn't exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'nope'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); + +-- error: inherited null +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', NULL::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); + +-- ok: no stakinds +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.4::real, + 'avg_width', 5::integer, + 'n_distinct', 0.6::real); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: unrecognized argument name +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', NULL::integer, + 'nope', 0.5::real); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcv / mcf null mismatch part 1 +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.6::real, + 'avg_width', 7::integer, + 'n_distinct', -0.7::real, + 'most_common_freqs', '{0.1,0.2,0.3}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcv / mcf null mismatch part 2 +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.7::real, + 'avg_width', 8::integer, + 'n_distinct', -0.8::real, + 'most_common_vals', '{1,2,3}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcv / mcf type mismatch +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.8::real, + 'avg_width', 9::integer, + 'n_distinct', -0.9::real, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.2,0.1}'::double precision[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcv cast failure +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.9::real, + 'avg_width', 10::integer, + 'n_distinct', -0.4::real, + 'most_common_vals', '{2,four,3}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- ok: mcv+mcf +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 1::integer, + 'n_distinct', -0.1::real, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: NULL in histogram array +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', 2::integer, + 'n_distinct', -0.2::real, + 'histogram_bounds', '{1,NULL,3,4}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- ok: histogram_bounds +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.3::real, + 'avg_width', 3::integer, + 'n_distinct', -0.3::real, + 'histogram_bounds', '{1,2,3,4}'::text ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: elem_count_histogram null element +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'tags'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.4::real, + 'avg_width', 5::integer, + 'n_distinct', -0.4::real, + 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + +-- ok: elem_count_histogram +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'tags'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.5::real, + 'avg_width', 6::integer, + 'n_distinct', -0.55::real, + 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + +-- range stats on a scalar type +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.6::real, + 'avg_width', 7::integer, + 'n_distinct', -0.15::real, + 'range_empty_frac', 0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: range_empty_frac range_length_hist null mismatch +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.7::real, + 'avg_width', 8::integer, + 'n_distinct', -0.25::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- warn: range_empty_frac range_length_hist null mismatch part 2 +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.8::real, + 'avg_width', 9::integer, + 'n_distinct', -0.35::real, + 'range_empty_frac', 0.5::real + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- ok: range_empty_frac + range_length_hist +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.9::real, + 'avg_width', 1::integer, + 'n_distinct', -0.19::real, + 'range_empty_frac', 0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- warn: range bounds histogram on scalar +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', -0.29::real, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- ok: range_bounds_histogram +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', 3::integer, + 'n_distinct', -0.39::real, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- warn: too many stat kinds +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'arange'::name, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.5::real, + 'avg_width', 2::integer, + 'n_distinct', -0.1::real, + 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[], + 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text, + 'correlation', 1.1::real, + 'most_common_elems', '{3,1}'::text, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[], + 'range_empty_frac', -0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text); + -- -- Test the ability to exactly copy data from one table to an identical table, -- correctly reconstructing the stakind order as well as the staopN and @@ -687,4 +1161,172 @@ FROM pg_statistic s JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum WHERE s.starelid = 'stats_import.is_odd'::regclass; +-- +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- +-- Clear clone stats to try again with pg_restore_attribute_stats +-- +SELECT + pg_catalog.pg_clear_attribute_stats( + ('stats_import.' || s.tablename)::regclass, + s.attname, + s.inherited) +FROM pg_catalog.pg_stats AS s +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test_clone', 'is_odd_clone') +ORDER BY s.tablename, s.attname, s.inherited; +SELECT + +SELECT COUNT(*) +FROM pg_catalog.pg_stats AS s +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test_clone', 'is_odd_clone'); + +-- +-- Copy stats from test to test_clone, and is_odd to is_odd_clone +-- +SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.* +FROM pg_catalog.pg_stats AS s +CROSS JOIN LATERAL + pg_catalog.pg_restore_attribute_stats( + 'relation', ('stats_import.' || s.tablename || '_clone')::regclass, + 'attname', s.attname, + 'inherited', s.inherited, + 'version', 150000, + 'null_frac', s.null_frac, + 'avg_width', s.avg_width, + 'n_distinct', s.n_distinct, + 'most_common_vals', s.most_common_vals::text, + 'most_common_freqs', s.most_common_freqs, + 'histogram_bounds', s.histogram_bounds::text, + 'correlation', s.correlation, + 'most_common_elems', s.most_common_elems::text, + 'most_common_elem_freqs', s.most_common_elem_freqs, + 'elem_count_histogram', s.elem_count_histogram, + 'range_bounds_histogram', s.range_bounds_histogram::text, + 'range_empty_frac', s.range_empty_frac, + 'range_length_histogram', s.range_length_histogram::text) AS r +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test', 'is_odd') +ORDER BY s.tablename, s.attname, s.inherited; + +SELECT c.relname, COUNT(*) AS num_stats +FROM pg_class AS c +JOIN pg_statistic s ON s.starelid = c.oid +WHERE c.relnamespace = 'stats_import'::regnamespace +AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone') +GROUP BY c.relname +ORDER BY c.relname; + +-- check test minus test_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass; + +-- check test_clone minus test +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass; + +-- check is_odd minus is_odd_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass; + +-- check is_odd_clone minus is_odd +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass; + DROP SCHEMA stats_import CASCADE; From 9f7ece79e750ad19e129a991f8faea0e8b56d244 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Fri, 22 Nov 2024 12:07:46 -0800 Subject: [PATCH 11/16] Disallow modifying statistics on system columns. Reported-by: Heikki Linnakangas Discussion: https://postgr.es/m/df3e1c41-4e6c-40ad-9636-98deefe488cd@iki.fi --- src/backend/statistics/attribute_stats.c | 14 ++++++++++++++ src/test/regress/expected/stats_import.out | 15 +++++++++++++++ src/test/regress/sql/stats_import.sql | 15 +++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index 0da3bc2e3a7..4080a009a5e 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -161,6 +161,13 @@ attribute_statistics_update(FunctionCallInfo fcinfo, int elevel) stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); attname = PG_GETARG_NAME(ATTNAME_ARG); attnum = get_attnum(reloid, NameStr(*attname)); + + if (attnum < 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot modify statistics on system column \"%s\"", + NameStr(*attname)))); + if (attnum == InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), @@ -865,6 +872,13 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS) stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); attname = PG_GETARG_NAME(ATTNAME_ARG); attnum = get_attnum(reloid, NameStr(*attname)); + + if (attnum < 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot clear statistics on system column \"%s\"", + NameStr(*attname)))); + if (attnum == InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index 9186fc01ecc..aab862c97c7 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -195,6 +195,15 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); ERROR: "relation" cannot be NULL +-- error: attribute is system column +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'xmin'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); +ERROR: cannot modify statistics on system column "xmin" -- error: attname doesn't exist SELECT pg_catalog.pg_set_attribute_stats( relation => 'stats_import.test'::regclass, @@ -204,6 +213,12 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); ERROR: column "nope" of relation "test" does not exist +-- error: attribute is system column +SELECT pg_catalog.pg_clear_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'ctid'::name, + inherited => false::boolean); +ERROR: cannot clear statistics on system column "ctid" -- error: attname doesn't exist SELECT pg_catalog.pg_clear_attribute_stats( relation => 'stats_import.test'::regclass, diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index c7d5e017d90..31455b58c1d 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -145,6 +145,15 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); +-- error: attribute is system column +SELECT pg_catalog.pg_set_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'xmin'::name, + inherited => false::boolean, + null_frac => 0.1::real, + avg_width => 2::integer, + n_distinct => 0.3::real); + -- error: attname doesn't exist SELECT pg_catalog.pg_set_attribute_stats( relation => 'stats_import.test'::regclass, @@ -154,6 +163,12 @@ SELECT pg_catalog.pg_set_attribute_stats( avg_width => 2::integer, n_distinct => 0.3::real); +-- error: attribute is system column +SELECT pg_catalog.pg_clear_attribute_stats( + relation => 'stats_import.test'::regclass, + attname => 'ctid'::name, + inherited => false::boolean); + -- error: attname doesn't exist SELECT pg_catalog.pg_clear_attribute_stats( relation => 'stats_import.test'::regclass, From e496467fef77c476ebd987085e8b38ef63d23f51 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Tue, 10 Dec 2024 16:30:37 -0800 Subject: [PATCH 12/16] Use in-place updates for pg_restore_relation_stats(). This matches the behavior of vac_update_relstats(), which is important to avoid bloating pg_class. Author: Corey Huinker Discussion: https://postgr.es/m/CADkLM=fc3je+ufv3gsHqjjSSf+t8674RXpuXW62EL55MUEQd-g@mail.gmail.com --- doc/src/sgml/func.sgml | 8 + src/backend/statistics/relation_stats.c | 188 ++++++++++++++------- src/test/regress/expected/stats_import.out | 61 +++++++ src/test/regress/sql/stats_import.sql | 37 ++++ 4 files changed, 229 insertions(+), 65 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index a2715b7c519..0b377cc10a0 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -28129,6 +28129,14 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset function is to maintain a consistent function signature to avoid errors when restoring statistics from previous versions. + + To match the behavior of and when updating relation statistics, + pg_restore_relation_stats() does not follow MVCC + transactional semantics (see ). New relation + statistics may be durable even if the transaction aborts, and the + changes are not isolated from other transactions. + Arguments are passed as pairs of argname and argvalue, where diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c index 5a2aabc921e..bd336a1f2b3 100644 --- a/src/backend/statistics/relation_stats.c +++ b/src/backend/statistics/relation_stats.c @@ -20,6 +20,7 @@ #include "access/heapam.h" #include "catalog/indexing.h" #include "statistics/stat_utils.h" +#include "utils/fmgroids.h" #include "utils/fmgrprotos.h" #include "utils/syscache.h" @@ -50,53 +51,28 @@ static struct StatsArgInfo relarginfo[] = [NUM_RELATION_STATS_ARGS] = {0} }; -static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel); +static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel, + bool inplace); /* * Internal function for modifying statistics for a relation. */ static bool -relation_statistics_update(FunctionCallInfo fcinfo, int elevel) +relation_statistics_update(FunctionCallInfo fcinfo, int elevel, bool inplace) { Oid reloid; Relation crel; - HeapTuple ctup; - Form_pg_class pgcform; - int replaces[3] = {0}; - Datum values[3] = {0}; - bool nulls[3] = {0}; - int ncols = 0; - TupleDesc tupdesc; + int32 relpages = DEFAULT_RELPAGES; + bool update_relpages = false; + float reltuples = DEFAULT_RELTUPLES; + bool update_reltuples = false; + int32 relallvisible = DEFAULT_RELALLVISIBLE; + bool update_relallvisible = false; bool result = true; - stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG); - reloid = PG_GETARG_OID(RELATION_ARG); - - stats_lock_check_privileges(reloid); - - /* - * Take RowExclusiveLock on pg_class, consistent with - * vac_update_relstats(). - */ - crel = table_open(RelationRelationId, RowExclusiveLock); - - tupdesc = RelationGetDescr(crel); - ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid)); - if (!HeapTupleIsValid(ctup)) - { - ereport(elevel, - (errcode(ERRCODE_OBJECT_IN_USE), - errmsg("pg_class entry for relid %u not found", reloid))); - table_close(crel, RowExclusiveLock); - return false; - } - - pgcform = (Form_pg_class) GETSTRUCT(ctup); - - /* relpages */ if (!PG_ARGISNULL(RELPAGES_ARG)) { - int32 relpages = PG_GETARG_INT32(RELPAGES_ARG); + relpages = PG_GETARG_INT32(RELPAGES_ARG); /* * Partitioned tables may have relpages=-1. Note: for relations with @@ -110,17 +86,13 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) errmsg("relpages cannot be < -1"))); result = false; } - else if (relpages != pgcform->relpages) - { - replaces[ncols] = Anum_pg_class_relpages; - values[ncols] = Int32GetDatum(relpages); - ncols++; - } + else + update_relpages = true; } if (!PG_ARGISNULL(RELTUPLES_ARG)) { - float reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG); + reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG); if (reltuples < -1.0) { @@ -129,18 +101,13 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) errmsg("reltuples cannot be < -1.0"))); result = false; } - else if (reltuples != pgcform->reltuples) - { - replaces[ncols] = Anum_pg_class_reltuples; - values[ncols] = Float4GetDatum(reltuples); - ncols++; - } - + else + update_reltuples = true; } if (!PG_ARGISNULL(RELALLVISIBLE_ARG)) { - int32 relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG); + relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG); if (relallvisible < 0) { @@ -149,23 +116,114 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) errmsg("relallvisible cannot be < 0"))); result = false; } - else if (relallvisible != pgcform->relallvisible) + else + update_relallvisible = true; + } + + stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG); + reloid = PG_GETARG_OID(RELATION_ARG); + + stats_lock_check_privileges(reloid); + + /* + * Take RowExclusiveLock on pg_class, consistent with + * vac_update_relstats(). + */ + crel = table_open(RelationRelationId, RowExclusiveLock); + + if (inplace) + { + HeapTuple ctup = NULL; + ScanKeyData key[1]; + Form_pg_class pgcform; + void *inplace_state = NULL; + bool dirty = false; + + ScanKeyInit(&key[0], Anum_pg_class_oid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(reloid)); + systable_inplace_update_begin(crel, ClassOidIndexId, true, NULL, 1, key, + &ctup, &inplace_state); + if (!HeapTupleIsValid(ctup)) + elog(ERROR, "pg_class entry for relid %u vanished while updating statistics", + reloid); + pgcform = (Form_pg_class) GETSTRUCT(ctup); + + if (update_relpages && pgcform->relpages != relpages) { - replaces[ncols] = Anum_pg_class_relallvisible; - values[ncols] = Int32GetDatum(relallvisible); - ncols++; + pgcform->relpages = relpages; + dirty = true; } - } + if (update_reltuples && pgcform->reltuples != reltuples) + { + pgcform->reltuples = reltuples; + dirty = true; + } + if (update_relallvisible && pgcform->relallvisible != relallvisible) + { + pgcform->relallvisible = relallvisible; + dirty = true; + } + + if (dirty) + systable_inplace_update_finish(inplace_state, ctup); + else + systable_inplace_update_cancel(inplace_state); - /* only update pg_class if there is a meaningful change */ - if (ncols > 0) + heap_freetuple(ctup); + } + else { - HeapTuple newtup; + TupleDesc tupdesc = RelationGetDescr(crel); + HeapTuple ctup; + Form_pg_class pgcform; + int replaces[3] = {0}; + Datum values[3] = {0}; + bool nulls[3] = {0}; + int nreplaces = 0; + + ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid)); + if (!HeapTupleIsValid(ctup)) + { + ereport(elevel, + (errcode(ERRCODE_OBJECT_IN_USE), + errmsg("pg_class entry for relid %u not found", reloid))); + table_close(crel, RowExclusiveLock); + return false; + } + pgcform = (Form_pg_class) GETSTRUCT(ctup); + + if (update_relpages && relpages != pgcform->relpages) + { + replaces[nreplaces] = Anum_pg_class_relpages; + values[nreplaces] = Int32GetDatum(relpages); + nreplaces++; + } + + if (update_reltuples && reltuples != pgcform->reltuples) + { + replaces[nreplaces] = Anum_pg_class_reltuples; + values[nreplaces] = Float4GetDatum(reltuples); + nreplaces++; + } + + if (update_relallvisible && relallvisible != pgcform->relallvisible) + { + replaces[nreplaces] = Anum_pg_class_relallvisible; + values[nreplaces] = Int32GetDatum(relallvisible); + nreplaces++; + } + + if (nreplaces > 0) + { + HeapTuple newtup; + + newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces, + replaces, values, nulls); + CatalogTupleUpdate(crel, &newtup->t_self, newtup); + heap_freetuple(newtup); + } - newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values, - nulls); - CatalogTupleUpdate(crel, &newtup->t_self, newtup); - heap_freetuple(newtup); + ReleaseSysCache(ctup); } /* release the lock, consistent with vac_update_relstats() */ @@ -180,7 +238,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) Datum pg_set_relation_stats(PG_FUNCTION_ARGS) { - relation_statistics_update(fcinfo, ERROR); + relation_statistics_update(fcinfo, ERROR, false); PG_RETURN_VOID(); } @@ -204,7 +262,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS) newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE; newfcinfo->args[3].isnull = false; - relation_statistics_update(newfcinfo, ERROR); + relation_statistics_update(newfcinfo, ERROR, false); PG_RETURN_VOID(); } @@ -222,7 +280,7 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS) relarginfo, WARNING)) result = false; - if (!relation_statistics_update(positional_fcinfo, WARNING)) + if (!relation_statistics_update(positional_fcinfo, WARNING, true)) result = false; PG_RETURN_BOOL(result); diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index aab862c97c7..fb50da1cd83 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -105,6 +105,47 @@ WHERE oid = 'stats_import.test'::regclass; 18 | 401 | 5 (1 row) +-- test MVCC behavior: changes do not persist after abort (in contrast +-- to pg_restore_relation_stats(), which uses in-place updates). +BEGIN; +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => NULL::integer, + reltuples => 4000.0::real, + relallvisible => 4::integer); + pg_set_relation_stats +----------------------- + +(1 row) + +ABORT; +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 18 | 401 | 5 +(1 row) + +BEGIN; +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.test'::regclass); + pg_clear_relation_stats +------------------------- + +(1 row) + +ABORT; +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 18 | 401 | 5 +(1 row) + -- clear SELECT pg_catalog.pg_clear_relation_stats( @@ -705,6 +746,25 @@ WHERE oid = 'stats_import.test'::regclass; (1 row) -- ok: just relpages +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', '15'::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 15 | 400 | 4 +(1 row) + +-- test non-MVCC behavior: new value should persist after abort +BEGIN; SELECT pg_restore_relation_stats( 'relation', 'stats_import.test'::regclass, 'version', 150000::integer, @@ -714,6 +774,7 @@ SELECT pg_restore_relation_stats( t (1 row) +ABORT; SELECT relpages, reltuples, relallvisible FROM pg_class WHERE oid = 'stats_import.test'::regclass; diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 31455b58c1d..d3058bf8f6b 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -76,6 +76,31 @@ SELECT relpages, reltuples, relallvisible FROM pg_class WHERE oid = 'stats_import.test'::regclass; +-- test MVCC behavior: changes do not persist after abort (in contrast +-- to pg_restore_relation_stats(), which uses in-place updates). +BEGIN; +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => NULL::integer, + reltuples => 4000.0::real, + relallvisible => 4::integer); +ABORT; + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +BEGIN; +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.test'::regclass); +ABORT; + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + -- clear SELECT pg_catalog.pg_clear_relation_stats( @@ -565,10 +590,22 @@ FROM pg_class WHERE oid = 'stats_import.test'::regclass; -- ok: just relpages +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', '15'::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- test non-MVCC behavior: new value should persist after abort +BEGIN; SELECT pg_restore_relation_stats( 'relation', 'stats_import.test'::regclass, 'version', 150000::integer, 'relpages', '16'::integer); +ABORT; SELECT relpages, reltuples, relallvisible FROM pg_class From 032d2dc47965fe19192151618647a42c4e2ffb19 Mon Sep 17 00:00:00 2001 From: reshke Date: Wed, 19 Nov 2025 19:07:30 +0000 Subject: [PATCH 13/16] Compat fix --- src/backend/statistics/stat_utils.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index 740038e3fe7..f6ed593933d 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -154,14 +154,9 @@ stats_lock_check_privileges(Oid reloid) if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())) { - AclResult aclresult = pg_class_aclcheck(RelationGetRelid(rel), - GetUserId(), - ACL_MAINTAIN); - - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, - get_relkind_objtype(rel->rd_rel->relkind), - NameStr(rel->rd_rel->relname)); + aclcheck_error(ACLCHECK_NOT_OWNER, + get_relkind_objtype(rel->rd_rel->relkind), + NameStr(rel->rd_rel->relname)); } relation_close(rel, NoLock); From ff41cac360513730ac4d50419e329a586eed4173 Mon Sep 17 00:00:00 2001 From: reshke Date: Wed, 19 Nov 2025 19:23:34 +0000 Subject: [PATCH 14/16] Fix pgproc --- src/include/catalog/pg_proc.dat | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 96a1314655f..13dde527032 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12041,26 +12041,6 @@ { oid => '6292', descr => 'aggregate transition function', proname => 'any_value_transfn', prorettype => 'anyelement', proargtypes => 'anyelement anyelement', prosrc => 'any_value_transfn' }, -{ oid => '6321', descr => 'list of available WAL summary files', - proname => 'pg_available_wal_summaries', prorows => '100', proretset => 't', - provolatile => 'v', prorettype => 'record', proargtypes => '', - proallargtypes => '{int8,pg_lsn,pg_lsn}', proargmodes => '{o,o,o}', - proargnames => '{tli,start_lsn,end_lsn}', - prosrc => 'pg_available_wal_summaries' }, -{ oid => '6322', descr => 'contents of a WAL summary file', - proname => 'pg_wal_summary_contents', prorows => '100', proretset => 't', - provolatile => 'v', prorettype => 'record', - proargtypes => 'int8 pg_lsn pg_lsn', - proallargtypes => '{int8,pg_lsn,pg_lsn,oid,oid,oid,int2,int8,bool}', - proargmodes => '{i,i,i,o,o,o,o,o,o}', - proargnames => '{tli,start_lsn,end_lsn,relfilenode,reltablespace,reldatabase,relforknumber,relblocknumber,is_limit_block}', - prosrc => 'pg_wal_summary_contents' }, -{ oid => '6323', descr => 'WAL summarizer state', - proname => 'pg_get_wal_summarizer_state', provolatile => 'v', - prorettype => 'record', proargtypes => '', - proallargtypes => '{int8,pg_lsn,pg_lsn,int4}', proargmodes => '{o,o,o,o}', - proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}', - prosrc => 'pg_get_wal_summarizer_state' }, # Statistics Import { oid => '6363', descr => 'restore statistics on relation', From cf021fd2a488307de17806385dc51e6b18349a3a Mon Sep 17 00:00:00 2001 From: reshke Date: Wed, 19 Nov 2025 19:23:49 +0000 Subject: [PATCH 15/16] fix pg_set_relation_stats --- src/backend/catalog/system_functions.sql | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 59dd64f59e6..297c85691b7 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -601,16 +601,6 @@ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE AS 'unicode_is_normalized'; -CREATE OR REPLACE FUNCTION - pg_set_relation_stats(relation regclass, - relpages integer DEFAULT NULL, - reltuples real DEFAULT NULL, - relallvisible integer DEFAULT NULL) -RETURNS bool -LANGUAGE INTERNAL -CALLED ON NULL INPUT VOLATILE -AS 'pg_set_relation_stats'; - CREATE OR REPLACE FUNCTION pg_set_relation_stats(relation regclass, relpages integer DEFAULT NULL, From 8ae03cf19f2ac094625972182a25be504202876d Mon Sep 17 00:00:00 2001 From: reshke Date: Wed, 19 Nov 2025 19:24:04 +0000 Subject: [PATCH 16/16] fix include --- src/backend/statistics/attribute_stats.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index 4080a009a5e..a199ccdb9d6 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -22,6 +22,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_operator.h" #include "nodes/nodeFuncs.h" +#include "nodes/miscnodes.h" #include "statistics/statistics.h" #include "statistics/stat_utils.h" #include "utils/array.h" @@ -29,6 +30,7 @@ #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" +#include "utils/typcache.h" #define DEFAULT_NULL_FRAC Float4GetDatum(0.0) #define DEFAULT_AVG_WIDTH Int32GetDatum(0) /* unknown */