diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index e90289e4ab1..51d09b27267 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -12927,6 +12927,43 @@ ANALYZE dtest_table; ANALYZE VERBOSE dtest_ftable; -- should work INFO: importing statistics for foreign table "public.dtest_ftable" INFO: finished importing statistics for foreign table "public.dtest_ftable" +-- dtest_ftable's stats should now exactly match dtest_table's +-- compare values, should match +SELECT relpages, reltuples FROM pg_class +WHERE oid = 'public.dtest_table'::regclass +EXCEPT +SELECT relpages, reltuples FROM pg_class +WHERE oid = 'public.dtest_ftable'::regclass; + relpages | reltuples +----------+----------- +(0 rows) + +-- compare the rowcounts, should get 0 rows back +SELECT COUNT(*) FROM pg_stats +WHERE schemaname = 'public' AND tablename = 'dtest_table' +EXCEPT +SELECT COUNT(*) FROM pg_stats +WHERE schemaname = 'public' AND tablename = 'dtest_ftable'; + count +------- +(0 rows) + +-- test only a few stats columns common to integer types +SELECT attname, inherited, null_frac, avg_width, n_distinct, + most_common_vals::text as mcv, most_common_freqs, + histogram_bounds::text as hb, correlation +FROM pg_stats +WHERE schemaname = 'public' AND tablename = 'dtest_table' +EXCEPT +SELECT attname, inherited, null_frac, avg_width, n_distinct, + most_common_vals::text as mcv, most_common_freqs, + histogram_bounds::text as hb, correlation +FROM pg_stats +WHERE schemaname = 'public' AND tablename = 'dtest_ftable'; + attname | inherited | null_frac | avg_width | n_distinct | mcv | most_common_freqs | hb | correlation +---------+-----------+-----------+-----------+------------+-----+-------------------+----+------------- +(0 rows) + -- cleanup DROP FOREIGN TABLE simport_ftable; DROP FOREIGN TABLE simport_fview; diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 6dbae583ecc..b143e7369c0 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -24,7 +24,6 @@ #include "commands/vacuum.h" #include "executor/execAsync.h" #include "executor/instrument.h" -#include "executor/spi.h" #include "foreign/fdwapi.h" #include "funcapi.h" #include "miscadmin.h" @@ -43,6 +42,7 @@ #include "parser/parsetree.h" #include "postgres_fdw.h" #include "statistics/statistics.h" +#include "statistics/stat_utils.h" #include "storage/latch.h" #include "utils/builtins.h" #include "utils/float.h" @@ -335,7 +335,6 @@ typedef struct { PGresult *rel; PGresult *att; - int server_version_num; } RemoteStatsResults; /* Column order in relation stats query */ @@ -367,136 +366,6 @@ enum AttStatsColumns ATTSTATS_NUM_FIELDS, }; -/* Relation stats import query */ -static const char *relimport_sql = -"SELECT pg_catalog.pg_restore_relation_stats(\n" -"\t'version', $1,\n" -"\t'schemaname', $2,\n" -"\t'relname', $3,\n" -"\t'relpages', $4::integer,\n" -"\t'reltuples', $5::real)"; - -/* Argument order in relation stats import query */ -enum RelImportSqlArgs -{ - RELIMPORT_SQL_VERSION = 0, - RELIMPORT_SQL_SCHEMANAME, - RELIMPORT_SQL_RELNAME, - RELIMPORT_SQL_RELPAGES, - RELIMPORT_SQL_RELTUPLES, - RELIMPORT_SQL_NUM_FIELDS -}; - -/* Argument types in relation stats import query */ -static const Oid relimport_argtypes[RELIMPORT_SQL_NUM_FIELDS] = -{ - INT4OID, TEXTOID, TEXTOID, TEXTOID, - TEXTOID, -}; - -/* Attribute stats import query */ -static const char *attimport_sql = -"SELECT pg_catalog.pg_restore_attribute_stats(\n" -"\t'version', $1,\n" -"\t'schemaname', $2,\n" -"\t'relname', $3,\n" -"\t'attnum', $4,\n" -"\t'inherited', false::boolean,\n" -"\t'null_frac', $5::real,\n" -"\t'avg_width', $6::integer,\n" -"\t'n_distinct', $7::real,\n" -"\t'most_common_vals', $8,\n" -"\t'most_common_freqs', $9::real[],\n" -"\t'histogram_bounds', $10,\n" -"\t'correlation', $11::real,\n" -"\t'most_common_elems', $12,\n" -"\t'most_common_elem_freqs', $13::real[],\n" -"\t'elem_count_histogram', $14::real[],\n" -"\t'range_length_histogram', $15,\n" -"\t'range_empty_frac', $16::real,\n" -"\t'range_bounds_histogram', $17)"; - -/* Argument order in attribute stats import query */ -enum AttImportSqlArgs -{ - ATTIMPORT_SQL_VERSION = 0, - ATTIMPORT_SQL_SCHEMANAME, - ATTIMPORT_SQL_RELNAME, - ATTIMPORT_SQL_ATTNUM, - ATTIMPORT_SQL_NULL_FRAC, - ATTIMPORT_SQL_AVG_WIDTH, - ATTIMPORT_SQL_N_DISTINCT, - ATTIMPORT_SQL_MOST_COMMON_VALS, - ATTIMPORT_SQL_MOST_COMMON_FREQS, - ATTIMPORT_SQL_HISTOGRAM_BOUNDS, - ATTIMPORT_SQL_CORRELATION, - ATTIMPORT_SQL_MOST_COMMON_ELEMS, - ATTIMPORT_SQL_MOST_COMMON_ELEM_FREQS, - ATTIMPORT_SQL_ELEM_COUNT_HISTOGRAM, - ATTIMPORT_SQL_RANGE_LENGTH_HISTOGRAM, - ATTIMPORT_SQL_RANGE_EMPTY_FRAC, - ATTIMPORT_SQL_RANGE_BOUNDS_HISTOGRAM, - ATTIMPORT_SQL_NUM_FIELDS -}; - -/* Argument types in attribute stats import query */ -static const Oid attimport_argtypes[ATTIMPORT_SQL_NUM_FIELDS] = -{ - INT4OID, TEXTOID, TEXTOID, INT2OID, - TEXTOID, TEXTOID, TEXTOID, TEXTOID, - TEXTOID, TEXTOID, TEXTOID, TEXTOID, - TEXTOID, TEXTOID, TEXTOID, TEXTOID, - TEXTOID, -}; - -/* - * The mapping of attribute stats query columns to the positional arguments in - * the prepared pg_restore_attribute_stats() statement. - */ -typedef struct -{ - enum AttStatsColumns res_field; - enum AttImportSqlArgs arg_num; -} AttrResultArgMap; - -#define NUM_MAPPED_ATTIMPORT_ARGS 13 - -static const AttrResultArgMap attr_result_arg_map[NUM_MAPPED_ATTIMPORT_ARGS] = -{ - {ATTSTATS_NULL_FRAC, ATTIMPORT_SQL_NULL_FRAC}, - {ATTSTATS_AVG_WIDTH, ATTIMPORT_SQL_AVG_WIDTH}, - {ATTSTATS_N_DISTINCT, ATTIMPORT_SQL_N_DISTINCT}, - {ATTSTATS_MOST_COMMON_VALS, ATTIMPORT_SQL_MOST_COMMON_VALS}, - {ATTSTATS_MOST_COMMON_FREQS, ATTIMPORT_SQL_MOST_COMMON_FREQS}, - {ATTSTATS_HISTOGRAM_BOUNDS, ATTIMPORT_SQL_HISTOGRAM_BOUNDS}, - {ATTSTATS_CORRELATION, ATTIMPORT_SQL_CORRELATION}, - {ATTSTATS_MOST_COMMON_ELEMS, ATTIMPORT_SQL_MOST_COMMON_ELEMS}, - {ATTSTATS_MOST_COMMON_ELEM_FREQS, ATTIMPORT_SQL_MOST_COMMON_ELEM_FREQS}, - {ATTSTATS_ELEM_COUNT_HISTOGRAM, ATTIMPORT_SQL_ELEM_COUNT_HISTOGRAM}, - {ATTSTATS_RANGE_LENGTH_HISTOGRAM, ATTIMPORT_SQL_RANGE_LENGTH_HISTOGRAM}, - {ATTSTATS_RANGE_EMPTY_FRAC, ATTIMPORT_SQL_RANGE_EMPTY_FRAC}, - {ATTSTATS_RANGE_BOUNDS_HISTOGRAM, ATTIMPORT_SQL_RANGE_BOUNDS_HISTOGRAM}, -}; - -/* Attribute stats clear query */ -static const char *attclear_sql = -"SELECT pg_catalog.pg_clear_attribute_stats($1, $2, $3, false)"; - -/* Argument order in attribute stats clear query */ -enum AttClearSqlArgs -{ - ATTCLEAR_SQL_SCHEMANAME = 0, - ATTCLEAR_SQL_RELNAME, - ATTCLEAR_SQL_ATTNAME, - ATTCLEAR_SQL_NUM_FIELDS -}; - -/* Argument types in attribute stats clear query */ -static const Oid attclear_argtypes[ATTCLEAR_SQL_NUM_FIELDS] = -{ - TEXTOID, TEXTOID, TEXTOID, -}; - /* * SQL functions */ @@ -714,14 +583,13 @@ static bool match_attrmap(PGresult *res, const char *remote_relname, int attrcnt, RemoteAttributeMapping *remattrmap); -static bool import_fetched_statistics(const char *schemaname, +static bool import_fetched_statistics(Relation relation, + const char *schemaname, const char *relname, int attrcnt, const RemoteAttributeMapping *remattrmap, RemoteStatsResults *remstats); -static void map_field_to_arg(PGresult *res, int row, int field, - int arg, Datum *values, char *nulls); -static bool import_spi_query_ok(void); +static char *get_opt_value(PGresult *res, int row, int col); static void produce_tuple_asynchronously(AsyncRequest *areq, bool fetch); static void fetch_more_data_begin(AsyncRequest *areq); static void complete_pending_request(AsyncRequest *areq); @@ -5661,7 +5529,7 @@ postgresImportForeignStatistics(Relation relation, List *va_cols, int elevel) &attrcnt, &remattrmap, &remstats); if (ok) - ok = import_fetched_statistics(schemaname, relname, + ok = import_fetched_statistics(relation, schemaname, relname, attrcnt, remattrmap, &remstats); if (ok) @@ -5725,7 +5593,7 @@ fetch_remote_statistics(Relation relation, */ user = GetUserMapping(GetUserId(), table->serverid); conn = GetConnection(user, false, NULL); - remstats->server_version_num = server_version_num = PQserverVersion(conn); + server_version_num = PQserverVersion(conn); /* Fetch relation stats. */ remstats->rel = relstats = fetch_relstats(conn, relation); @@ -6128,58 +5996,31 @@ match_attrmap(PGresult *res, * Import fetched statistics into the local statistics tables. */ static bool -import_fetched_statistics(const char *schemaname, +import_fetched_statistics(Relation relation, + const char *schemaname, const char *relname, int attrcnt, const RemoteAttributeMapping *remattrmap, RemoteStatsResults *remstats) { - SPIPlanPtr attimport_plan = NULL; - SPIPlanPtr attclear_plan = NULL; - Datum values[ATTIMPORT_SQL_NUM_FIELDS]; - char nulls[ATTIMPORT_SQL_NUM_FIELDS]; - int spirc; - bool ok = false; - - /* Assign all the invariant parameters common to relation/attribute stats */ - values[ATTIMPORT_SQL_VERSION] = Int32GetDatum(remstats->server_version_num); - nulls[ATTIMPORT_SQL_VERSION] = ' '; - - values[ATTIMPORT_SQL_SCHEMANAME] = CStringGetTextDatum(schemaname); - nulls[ATTIMPORT_SQL_SCHEMANAME] = ' '; - - values[ATTIMPORT_SQL_RELNAME] = CStringGetTextDatum(relname); - nulls[ATTIMPORT_SQL_RELNAME] = ' '; - - SPI_connect(); + PGresult *res; + NullableDatum args[ATTSTATS_NUM_FIELDS - 1]; /* * We import attribute statistics first, if any, because those are more * prone to errors. This avoids making a modification of pg_class that * will just get rolled back by a failed attribute import. */ - if (remstats->att != NULL) + res = remstats->att; + if (res != NULL) { - Assert(PQnfields(remstats->att) == ATTSTATS_NUM_FIELDS); - Assert(PQntuples(remstats->att) >= 1); - - attimport_plan = SPI_prepare(attimport_sql, ATTIMPORT_SQL_NUM_FIELDS, - (Oid *) attimport_argtypes); - if (attimport_plan == NULL) - elog(ERROR, "failed to prepare attimport_sql query"); - - attclear_plan = SPI_prepare(attclear_sql, ATTCLEAR_SQL_NUM_FIELDS, - (Oid *) attclear_argtypes); - if (attclear_plan == NULL) - elog(ERROR, "failed to prepare attclear_sql query"); - - nulls[ATTIMPORT_SQL_ATTNUM] = ' '; + Assert(PQnfields(res) == ATTSTATS_NUM_FIELDS); + Assert(PQntuples(res) >= 1); for (int mapidx = 0; mapidx < attrcnt; mapidx++) { int row = remattrmap[mapidx].res_index; - Datum *values2 = values + 1; - char *nulls2 = nulls + 1; + AttrNumber attnum = remattrmap[mapidx].local_attnum; /* All mappings should have been assigned a result set row. */ Assert(row >= 0); @@ -6191,128 +6032,90 @@ import_fetched_statistics(const char *schemaname, /* * First, clear existing attribute stats. - * - * We can re-use the values/nulls because the number of parameters - * is less and the first two params are the same as the second and - * third ones in attimport_sql. */ - values2[ATTCLEAR_SQL_ATTNAME] = - CStringGetTextDatum(remattrmap[mapidx].local_attname); - - spirc = SPI_execute_plan(attclear_plan, values2, nulls2, false, 1); - if (spirc != SPI_OK_SELECT) - elog(ERROR, "failed to execute attclear_sql query for column \"%s\" of foreign table \"%s.%s\"", - remattrmap[mapidx].local_attname, schemaname, relname); - - values[ATTIMPORT_SQL_ATTNUM] = - Int16GetDatum(remattrmap[mapidx].local_attnum); - - /* Loop through all mappable columns to set remaining arguments */ - for (int i = 0; i < NUM_MAPPED_ATTIMPORT_ARGS; i++) - map_field_to_arg(remstats->att, row, - attr_result_arg_map[i].res_field, - attr_result_arg_map[i].arg_num, - values, nulls); - - spirc = SPI_execute_plan(attimport_plan, values, nulls, false, 1); - if (spirc != SPI_OK_SELECT) - elog(ERROR, "failed to execute attimport_sql query for column \"%s\" of foreign table \"%s.%s\"", - remattrmap[mapidx].local_attname, schemaname, relname); - - if (!import_spi_query_ok()) + delete_attribute_statistics(relation, attnum, false); + + stats_set_float_arg(&args[0], + get_opt_value(res, row, ATTSTATS_NULL_FRAC)); + stats_set_int32_arg(&args[1], + get_opt_value(res, row, ATTSTATS_AVG_WIDTH)); + stats_set_float_arg(&args[2], + get_opt_value(res, row, ATTSTATS_N_DISTINCT)); + stats_set_text_arg(&args[3], + get_opt_value(res, row, ATTSTATS_MOST_COMMON_VALS)); + stats_set_floatarr_arg(&args[4], + get_opt_value(res, row, ATTSTATS_MOST_COMMON_FREQS)); + stats_set_text_arg(&args[5], + get_opt_value(res, row, ATTSTATS_HISTOGRAM_BOUNDS)); + stats_set_float_arg(&args[6], + get_opt_value(res, row, ATTSTATS_CORRELATION)); + stats_set_text_arg(&args[7], + get_opt_value(res, row, ATTSTATS_MOST_COMMON_ELEMS)); + stats_set_floatarr_arg(&args[8], + get_opt_value(res, row, ATTSTATS_MOST_COMMON_ELEM_FREQS)); + stats_set_floatarr_arg(&args[9], + get_opt_value(res, row, ATTSTATS_ELEM_COUNT_HISTOGRAM)); + stats_set_text_arg(&args[10], + get_opt_value(res, row, ATTSTATS_RANGE_LENGTH_HISTOGRAM)); + stats_set_float_arg(&args[11], + get_opt_value(res, row, ATTSTATS_RANGE_EMPTY_FRAC)); + stats_set_text_arg(&args[12], + get_opt_value(res, row, ATTSTATS_RANGE_BOUNDS_HISTOGRAM)); + + if (!import_attribute_statistics(relation, attnum, false, + &args[0], &args[1], &args[2], + &args[3], &args[4], &args[5], + &args[6], &args[7], &args[8], + &args[9], &args[10], &args[11], + &args[12])) { ereport(WARNING, errmsg("could not import statistics for foreign table \"%s.%s\" --- attribute statistics import failed for column \"%s\" of this foreign table", schemaname, relname, remattrmap[mapidx].local_attname)); - goto import_cleanup; + return false; } } } /* - * Import relation stats. We only perform this once, so there is no point - * in preparing the statement. - * - * We can re-use the values/nulls because the number of parameters is less - * and the first three params are the same as attimport_sql. - */ - Assert(remstats->rel != NULL); - Assert(PQnfields(remstats->rel) == RELSTATS_NUM_FIELDS); - Assert(PQntuples(remstats->rel) == 1); - map_field_to_arg(remstats->rel, 0, RELSTATS_RELPAGES, - RELIMPORT_SQL_RELPAGES, values, nulls); - map_field_to_arg(remstats->rel, 0, RELSTATS_RELTUPLES, - RELIMPORT_SQL_RELTUPLES, values, nulls); - - spirc = SPI_execute_with_args(relimport_sql, - RELIMPORT_SQL_NUM_FIELDS, - (Oid *) relimport_argtypes, - values, nulls, false, 1); - if (spirc != SPI_OK_SELECT) - elog(ERROR, "failed to execute relimport_sql query for foreign table \"%s.%s\"", - schemaname, relname); - - if (!import_spi_query_ok()) + * Import relation stats. + */ + res = remstats->rel; + Assert(res != NULL); + Assert(PQnfields(res) == RELSTATS_NUM_FIELDS); + Assert(PQntuples(res) == 1); + + stats_set_uint32_arg(&args[0], get_opt_value(res, 0, RELSTATS_RELPAGES)); + Assert(!args[0].isnull); + stats_set_float_arg(&args[1], get_opt_value(res, 0, RELSTATS_RELTUPLES)); + Assert(!args[1].isnull); + args[2].value = (Datum) 0; + args[2].isnull = true; + args[3].value = (Datum) 0; + args[3].isnull = true; + + if (!import_relation_statistics(relation, + &args[0], &args[1], &args[2], &args[3])) { ereport(WARNING, errmsg("could not import statistics for foreign table \"%s.%s\" --- relation statistics import failed for this foreign table", schemaname, relname)); - goto import_cleanup; - } - - ok = true; - -import_cleanup: - if (attimport_plan) - SPI_freeplan(attimport_plan); - if (attclear_plan) - SPI_freeplan(attclear_plan); - SPI_finish(); - return ok; -} - -/* - * Move a string value from a result set to a Text value of a Datum array. - */ -static void -map_field_to_arg(PGresult *res, int row, int field, - int arg, Datum *values, char *nulls) -{ - if (PQgetisnull(res, row, field)) - { - values[arg] = (Datum) 0; - nulls[arg] = 'n'; + return false; } - else - { - const char *s = PQgetvalue(res, row, field); - values[arg] = CStringGetTextDatum(s); - nulls[arg] = ' '; - } + return true; } /* - * Check the 1x1 result set of a pg_restore_*_stats() command for success. + * Conenience routine to fetch */ -static bool -import_spi_query_ok(void) +static char * +get_opt_value(PGresult *res, int row, int col) { - TupleDesc tupdesc; - Datum dat; - bool isnull; - - Assert(SPI_tuptable != NULL); - Assert(SPI_processed == 1); - - tupdesc = SPI_tuptable->tupdesc; - Assert(tupdesc->natts == 1); - Assert(TupleDescAttr(tupdesc, 0)->atttypid == BOOLOID); - dat = SPI_getbinval(SPI_tuptable->vals[0], tupdesc, 1, &isnull); - Assert(!isnull); - - return DatumGetBool(dat); + if (PQgetisnull(res, row, col)) + return NULL; + return PQgetvalue(res, row, col); } /* diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index dfc58beb0d2..0d0622b31ce 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -4584,6 +4584,34 @@ ANALYZE dtest_table; ANALYZE VERBOSE dtest_ftable; -- should work +-- dtest_ftable's stats should now exactly match dtest_table's +-- compare values, should match +SELECT relpages, reltuples FROM pg_class +WHERE oid = 'public.dtest_table'::regclass +EXCEPT +SELECT relpages, reltuples FROM pg_class +WHERE oid = 'public.dtest_ftable'::regclass; + +-- compare the rowcounts, should get 0 rows back +SELECT COUNT(*) FROM pg_stats +WHERE schemaname = 'public' AND tablename = 'dtest_table' +EXCEPT +SELECT COUNT(*) FROM pg_stats +WHERE schemaname = 'public' AND tablename = 'dtest_ftable'; + +-- test only a few stats columns common to integer types +SELECT attname, inherited, null_frac, avg_width, n_distinct, + most_common_vals::text as mcv, most_common_freqs, + histogram_bounds::text as hb, correlation +FROM pg_stats +WHERE schemaname = 'public' AND tablename = 'dtest_table' +EXCEPT +SELECT attname, inherited, null_frac, avg_width, n_distinct, + most_common_vals::text as mcv, most_common_freqs, + histogram_bounds::text as hb, correlation +FROM pg_stats +WHERE schemaname = 'public' AND tablename = 'dtest_ftable'; + -- cleanup DROP FOREIGN TABLE simport_ftable; DROP FOREIGN TABLE simport_fview; diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index 1cc4d657231..7c3259861ee 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -105,6 +105,11 @@ static struct StatsArgInfo cleararginfo[] = }; static bool attribute_statistics_update(FunctionCallInfo fcinfo); +static bool attribute_statistics_update_internal(Oid reloid, + const char *attname, + AttrNumber attnum, + bool inherited, + FunctionCallInfo fcinfo); static void upsert_pg_statistic(Relation starel, HeapTuple oldtup, const Datum *values, const bool *nulls, const bool *replaces); static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit); @@ -136,38 +141,6 @@ attribute_statistics_update(FunctionCallInfo fcinfo) bool inherited; Oid locked_table = InvalidOid; - 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, ATTRELSCHEMA_ARG); stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG); @@ -231,6 +204,47 @@ attribute_statistics_update(FunctionCallInfo fcinfo) stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); inherited = PG_GETARG_BOOL(INHERITED_ARG); + return attribute_statistics_update_internal(reloid, attname, attnum, + inherited, fcinfo); +} + +static bool +attribute_statistics_update_internal(Oid reloid, + const char *attname, AttrNumber attnum, + bool inherited, FunctionCallInfo fcinfo) +{ + 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; + /* * Check argument sanity. If some arguments are unusable, emit a WARNING * and set the corresponding argument to NULL in fcinfo. @@ -688,3 +702,79 @@ pg_restore_attribute_stats(PG_FUNCTION_ARGS) PG_RETURN_BOOL(result); } + +/* + * Import attribute statistics from NullableDatum inputs for all statitical + * values. + */ +bool +import_attribute_statistics(Relation rel, AttrNumber attnum, bool inherited, + const NullableDatum *null_frac, + const NullableDatum *avg_width, + const NullableDatum *n_distinct, + const NullableDatum *most_common_vals, + const NullableDatum *most_common_freqs, + const NullableDatum *histogram_bounds, + const NullableDatum *correlation, + const NullableDatum *most_common_elems, + const NullableDatum *most_common_elem_freqs, + const NullableDatum *elem_count_histogram, + const NullableDatum *range_length_histogram, + const NullableDatum *range_empty_frac, + const NullableDatum *range_bounds_histogram) +{ + LOCAL_FCINFO(newfcinfo, NUM_ATTRIBUTE_STATS_ARGS); + Oid reloid = RelationGetRelid(rel); + char *relname = RelationGetRelationName(rel); + char *attname = get_attname(reloid, attnum, true); + + InitFunctionCallInfoData(*newfcinfo, NULL, NUM_ATTRIBUTE_STATS_ARGS, + InvalidOid, NULL, NULL); + + newfcinfo->args[ATTRELSCHEMA_ARG].value = + CStringGetTextDatum(get_namespace_name(RelationGetNamespace(rel))); + newfcinfo->args[ATTRELSCHEMA_ARG].isnull = false; + newfcinfo->args[ATTRELNAME_ARG].value = CStringGetTextDatum(relname); + newfcinfo->args[ATTRELNAME_ARG].isnull = false; + + /* annoyingly, get_attname doesn't check attisdropped */ + if (attname == NULL || + !SearchSysCacheExistsAttName(reloid, attname)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %d of relation \"%s\" does not exist", + attnum, relname))); + + newfcinfo->args[ATTNAME_ARG].value = CStringGetTextDatum(attname); + newfcinfo->args[ATTNAME_ARG].isnull = false; + newfcinfo->args[ATTNUM_ARG].value = Int16GetDatum(attnum); + newfcinfo->args[ATTNUM_ARG].isnull = false; + newfcinfo->args[INHERITED_ARG].value = BoolGetDatum(inherited); + newfcinfo->args[INHERITED_ARG].isnull = false; + + newfcinfo->args[NULL_FRAC_ARG] = *null_frac; + newfcinfo->args[AVG_WIDTH_ARG] = *avg_width; + newfcinfo->args[N_DISTINCT_ARG] = *n_distinct; + newfcinfo->args[MOST_COMMON_VALS_ARG] = *most_common_vals; + newfcinfo->args[MOST_COMMON_FREQS_ARG] = *most_common_freqs; + newfcinfo->args[HISTOGRAM_BOUNDS_ARG] = *histogram_bounds; + newfcinfo->args[CORRELATION_ARG] = *correlation; + newfcinfo->args[MOST_COMMON_ELEMS_ARG] = *most_common_elems; + newfcinfo->args[MOST_COMMON_ELEM_FREQS_ARG] = *most_common_elem_freqs; + newfcinfo->args[ELEM_COUNT_HISTOGRAM_ARG] = *elem_count_histogram; + newfcinfo->args[RANGE_LENGTH_HISTOGRAM_ARG] = *range_length_histogram; + newfcinfo->args[RANGE_EMPTY_FRAC_ARG] = *range_empty_frac; + newfcinfo->args[RANGE_BOUNDS_HISTOGRAM_ARG] = *range_bounds_histogram; + + return attribute_statistics_update_internal(reloid, attname, attnum, + inherited, newfcinfo); +} + +/* + * Delete attribute statistics. + */ +bool +delete_attribute_statistics(Relation rel, AttrNumber attnum, bool inherited) +{ + return delete_pg_statistic(RelationGetRelid(rel), attnum, inherited); +} diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c index d6631e9a9a4..1442fdfc589 100644 --- a/src/backend/statistics/relation_stats.c +++ b/src/backend/statistics/relation_stats.c @@ -21,6 +21,7 @@ #include "catalog/indexing.h" #include "catalog/namespace.h" #include "nodes/makefuncs.h" +#include "statistics/statistics.h" #include "statistics/stat_utils.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -57,6 +58,8 @@ static struct StatsArgInfo relarginfo[] = }; static bool relation_statistics_update(FunctionCallInfo fcinfo); +static bool relation_statistics_update_internal(Oid reloid, + FunctionCallInfo fcinfo); /* * Internal function for modifying statistics for a relation. @@ -64,25 +67,9 @@ static bool relation_statistics_update(FunctionCallInfo fcinfo); static bool relation_statistics_update(FunctionCallInfo fcinfo) { - bool result = true; char *nspname; char *relname; Oid reloid; - Relation crel; - BlockNumber relpages = 0; - bool update_relpages = false; - float reltuples = 0; - bool update_reltuples = false; - BlockNumber relallvisible = 0; - bool update_relallvisible = false; - BlockNumber relallfrozen = 0; - bool update_relallfrozen = false; - HeapTuple ctup; - Form_pg_class pgcform; - int replaces[4] = {0}; - Datum values[4] = {0}; - bool nulls[4] = {0}; - int nreplaces = 0; Oid locked_table = InvalidOid; stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG); @@ -101,6 +88,29 @@ relation_statistics_update(FunctionCallInfo fcinfo) ShareUpdateExclusiveLock, 0, RangeVarCallbackForStats, &locked_table); + return relation_statistics_update_internal(reloid, fcinfo); +} + +static bool +relation_statistics_update_internal(Oid reloid, FunctionCallInfo fcinfo) +{ + BlockNumber relpages = 0; + bool update_relpages = false; + float reltuples = 0; + bool update_reltuples = false; + BlockNumber relallvisible = 0; + bool update_relallvisible = false; + BlockNumber relallfrozen = 0; + bool update_relallfrozen = false; + Relation crel; + HeapTuple ctup; + Form_pg_class pgcform; + int replaces[4] = {0}; + Datum values[4] = {0}; + bool nulls[4] = {0}; + int nreplaces = 0; + bool result = true; + if (!PG_ARGISNULL(RELPAGES_ARG)) { relpages = PG_GETARG_UINT32(RELPAGES_ARG); @@ -241,3 +251,35 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS) PG_RETURN_BOOL(result); } + +/* + * Import relation statistics from NullableDatum inputs for all statitical + * values. + */ +bool +import_relation_statistics(Relation rel, + const NullableDatum *relpages, + const NullableDatum *reltuples, + const NullableDatum *relallvisible, + const NullableDatum *relallfrozen) +{ + LOCAL_FCINFO(newfcinfo, NUM_RELATION_STATS_ARGS); + + InitFunctionCallInfoData(*newfcinfo, NULL, NUM_RELATION_STATS_ARGS, + InvalidOid, NULL, NULL); + + newfcinfo->args[RELSCHEMA_ARG].value = + CStringGetTextDatum(get_namespace_name(RelationGetNamespace(rel))); + newfcinfo->args[RELSCHEMA_ARG].isnull = false; + newfcinfo->args[RELNAME_ARG].value = + CStringGetTextDatum(RelationGetRelationName(rel)); + newfcinfo->args[RELNAME_ARG].isnull = false; + + newfcinfo->args[RELPAGES_ARG] = *relpages; + newfcinfo->args[RELTUPLES_ARG] = *reltuples; + newfcinfo->args[RELALLVISIBLE_ARG] = *relallvisible; + newfcinfo->args[RELALLFROZEN_ARG] = *relallfrozen; + + return relation_statistics_update_internal(RelationGetRelid(rel), + newfcinfo); +} diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index a673e3c704b..0d906f5fa76 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -32,6 +32,8 @@ #include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/float.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" @@ -753,3 +755,105 @@ statatt_init_empty_tuple(Oid reloid, int16 attnum, bool inherited, nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false; } } + +/* + * Convenience routine for setting optional text arguments + */ +void +stats_set_text_arg(NullableDatum *arg, const char *s) +{ + if (s) + { + arg->value = CStringGetTextDatum(s); + arg->isnull = false; + } + else + { + arg->value = (Datum) 0; + arg->isnull = true; + } +} + +/* + * Convenience routine for setting optional int32 arguments + */ +void +stats_set_int32_arg(NullableDatum *arg, const char *s) +{ + if (s) + { + int32 val = pg_strtoint32(s); + + arg->value = Int32GetDatum(val); + arg->isnull = false; + } + else + { + arg->value = (Datum) 0; + arg->isnull = true; + } +} + +/* + * Convenience routine for setting optional uint32 arguments + */ +void +stats_set_uint32_arg(NullableDatum *arg, const char *s) +{ + if (s) + { + uint32 val = uint32in_subr(s, NULL, "uint32", NULL); + + arg->value = UInt32GetDatum(val); + arg->isnull = false; + } + else + { + arg->value = (Datum) 0; + arg->isnull = true; + } +} + +/* + * Convenience routine for setting optional float arguments + */ +void +stats_set_float_arg(NullableDatum *arg, const char *s) +{ + if (s) + { + float4 val = float4in_internal((char *) s, NULL, "float", s, NULL); + + arg->value = Float4GetDatum(val); + arg->isnull = false; + } + else + { + arg->value = (Datum) 0; + arg->isnull = true; + } +} + +/* + * Convenience routine for setting optional float[] arguments + */ +void +stats_set_floatarr_arg(NullableDatum *arg, const char *s) +{ + if (s) + { + FmgrInfo flinfo; + Datum val; + + fmgr_info(F_ARRAY_IN, &flinfo); + val = InputFunctionCall(&flinfo, (char *) s, FLOAT4OID, -1); + + arg->value = val; + arg->isnull = false; + } + else + { + arg->value = (Datum) 0; + arg->isnull = true; + } +} diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h index 74da7790579..17ca9f18c53 100644 --- a/src/include/statistics/stat_utils.h +++ b/src/include/statistics/stat_utils.h @@ -58,4 +58,10 @@ extern Datum statatt_build_stavalues(const char *staname, FmgrInfo *array_in, Da extern bool statatt_get_elem_type(Oid atttypid, char atttyptype, Oid *elemtypid, Oid *elem_eq_opr); +extern void stats_set_text_arg(NullableDatum *arg, const char *s); +extern void stats_set_int32_arg(NullableDatum *arg, const char *s); +extern void stats_set_uint32_arg(NullableDatum *arg, const char *s); +extern void stats_set_float_arg(NullableDatum *arg, const char *s); +extern void stats_set_floatarr_arg(NullableDatum *arg, const char *s); + #endif /* STATS_UTILS_H */ diff --git a/src/include/statistics/statistics.h b/src/include/statistics/statistics.h index 8f9b9d237fd..7224e7a4e32 100644 --- a/src/include/statistics/statistics.h +++ b/src/include/statistics/statistics.h @@ -128,4 +128,27 @@ extern StatisticExtInfo *choose_best_statistics(List *stats, char requiredkind, int nclauses); extern HeapTuple statext_expressions_load(Oid stxoid, bool inh, int idx); +extern bool import_relation_statistics(Relation rel, + const NullableDatum *relpages, + const NullableDatum *reltuples, + const NullableDatum *relallvisible, + const NullableDatum *relallfrozen); +extern bool import_attribute_statistics(Relation rel, + AttrNumber attnum, bool inherited, + const NullableDatum *null_frac, + const NullableDatum *avg_width, + const NullableDatum *n_distinct, + const NullableDatum *most_common_vals, + const NullableDatum *most_common_freqs, + const NullableDatum *histogram_bounds, + const NullableDatum *correlation, + const NullableDatum *most_common_elems, + const NullableDatum *most_common_elem_freqs, + const NullableDatum *elem_count_histogram, + const NullableDatum *range_length_histogram, + const NullableDatum *range_empty_frac, + const NullableDatum *range_bounds_histogram); +extern bool delete_attribute_statistics(Relation rel, + AttrNumber attnum, bool inherited); + #endif /* STATISTICS_H */