Commit a4d75c86 authored by Tomas Vondra's avatar Tomas Vondra

Extended statistics on expressions

Allow defining extended statistics on expressions, not just just on
simple column references.  With this commit, expressions are supported
by all existing extended statistics kinds, improving the same types of
estimates. A simple example may look like this:

  CREATE TABLE t (a int);
  CREATE STATISTICS s ON mod(a,10), mod(a,20) FROM t;
  ANALYZE t;

The collected statistics are useful e.g. to estimate queries with those
expressions in WHERE or GROUP BY clauses:

  SELECT * FROM t WHERE mod(a,10) = 0 AND mod(a,20) = 0;

  SELECT 1 FROM t GROUP BY mod(a,10), mod(a,20);

This introduces new internal statistics kind 'e' (expressions) which is
built automatically when the statistics object definition includes any
expressions. This represents single-expression statistics, as if there
was an expression index (but without the index maintenance overhead).
The statistics is stored in pg_statistics_ext_data as an array of
composite types, which is possible thanks to 79f6a942.

CREATE STATISTICS allows building statistics on a single expression, in
which case in which case it's not possible to specify statistics kinds.

A new system view pg_stats_ext_exprs can be used to display expression
statistics, similarly to pg_stats and pg_stats_ext views.

ALTER TABLE ... ALTER COLUMN ... TYPE now treats indexes the same way it
treats indexes, i.e. it drops and recreates the statistics. This means
all statistics are reset, and we no longer try to preserve at least the
functional dependencies. This should not be a major issue in practice,
as the functional dependencies actually rely on per-column statistics,
which were always reset anyway.

Author: Tomas Vondra
Reviewed-by: Justin Pryzby, Dean Rasheed, Zhihong Yu
Discussion: https://postgr.es/m/ad7891d2-e90c-b446-9fe2-7419143847d7%40enterprisedb.com
parent 98376c18
This diff is collapsed.
...@@ -21,9 +21,13 @@ PostgreSQL documentation ...@@ -21,9 +21,13 @@ PostgreSQL documentation
<refsynopsisdiv> <refsynopsisdiv>
<synopsis> <synopsis>
CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_name</replaceable>
ON ( <replaceable class="parameter">expression</replaceable> )
FROM <replaceable class="parameter">table_name</replaceable>
CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_name</replaceable> CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_name</replaceable>
[ ( <replaceable class="parameter">statistics_kind</replaceable> [, ... ] ) ] [ ( <replaceable class="parameter">statistics_kind</replaceable> [, ... ] ) ]
ON <replaceable class="parameter">column_name</replaceable>, <replaceable class="parameter">column_name</replaceable> [, ...] ON { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) }, { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [, ...]
FROM <replaceable class="parameter">table_name</replaceable> FROM <replaceable class="parameter">table_name</replaceable>
</synopsis> </synopsis>
...@@ -39,6 +43,19 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na ...@@ -39,6 +43,19 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na
database and will be owned by the user issuing the command. database and will be owned by the user issuing the command.
</para> </para>
<para>
The <command>CREATE STATISTICS</command> command has two basic forms. The
first form allows univariate statistics for a single expression to be
collected, providing benefits similar to an expression index without the
overhead of index maintenance. This form does not allow the statistics
kind to be specified, since the various statistics kinds refer only to
multivariate statistics. The second form of the command allows
multivariate statistics on multiple columns and/or expressions to be
collected, optionally specifying which statistics kinds to include. This
form will also automatically cause univariate statistics to be collected on
any expressions included in the list.
</para>
<para> <para>
If a schema name is given (for example, <literal>CREATE STATISTICS If a schema name is given (for example, <literal>CREATE STATISTICS
myschema.mystat ...</literal>) then the statistics object is created in the myschema.mystat ...</literal>) then the statistics object is created in the
...@@ -79,14 +96,16 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na ...@@ -79,14 +96,16 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na
<term><replaceable class="parameter">statistics_kind</replaceable></term> <term><replaceable class="parameter">statistics_kind</replaceable></term>
<listitem> <listitem>
<para> <para>
A statistics kind to be computed in this statistics object. A multivariate statistics kind to be computed in this statistics object.
Currently supported kinds are Currently supported kinds are
<literal>ndistinct</literal>, which enables n-distinct statistics, <literal>ndistinct</literal>, which enables n-distinct statistics,
<literal>dependencies</literal>, which enables functional <literal>dependencies</literal>, which enables functional
dependency statistics, and <literal>mcv</literal> which enables dependency statistics, and <literal>mcv</literal> which enables
most-common values lists. most-common values lists.
If this clause is omitted, all supported statistics kinds are If this clause is omitted, all supported statistics kinds are
included in the statistics object. included in the statistics object. Univariate expression statistics are
built automatically if the statistics definition includes any complex
expressions rather than just simple column references.
For more information, see <xref linkend="planner-stats-extended"/> For more information, see <xref linkend="planner-stats-extended"/>
and <xref linkend="multivariate-statistics-examples"/>. and <xref linkend="multivariate-statistics-examples"/>.
</para> </para>
...@@ -98,8 +117,22 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na ...@@ -98,8 +117,22 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na
<listitem> <listitem>
<para> <para>
The name of a table column to be covered by the computed statistics. The name of a table column to be covered by the computed statistics.
At least two column names must be given; the order of the column names This is only allowed when building multivariate statistics. At least
is insignificant. two column names or expressions must be specified, and their order is
not significant.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">expression</replaceable></term>
<listitem>
<para>
An expression to be covered by the computed statistics. This may be
used to build univariate statistics on a single expression, or as part
of a list of multiple column names and/or expressions to build
multivariate statistics. In the latter case, separate univariate
statistics are built automatically for each expression in the list.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -125,6 +158,13 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na ...@@ -125,6 +158,13 @@ CREATE STATISTICS [ IF NOT EXISTS ] <replaceable class="parameter">statistics_na
reading it. Once created, however, the ownership of the statistics reading it. Once created, however, the ownership of the statistics
object is independent of the underlying table(s). object is independent of the underlying table(s).
</para> </para>
<para>
Expression statistics are per-expression and are similar to creating an
index on the expression, except that they avoid the overhead of index
maintenance. Expression statistics are built automatically for each
expression in the statistics object definition.
</para>
</refsect1> </refsect1>
<refsect1 id="sql-createstatistics-examples"> <refsect1 id="sql-createstatistics-examples">
...@@ -196,6 +236,72 @@ EXPLAIN ANALYZE SELECT * FROM t2 WHERE (a = 1) AND (b = 2); ...@@ -196,6 +236,72 @@ EXPLAIN ANALYZE SELECT * FROM t2 WHERE (a = 1) AND (b = 2);
in the table, allowing it to generate better estimates in both cases. in the table, allowing it to generate better estimates in both cases.
</para> </para>
<para>
Create table <structname>t3</structname> with a single timestamp column,
and run queries using expressions on that column. Without extended
statistics, the planner has no information about the data distribution for
the expressions, and uses default estimates. The planner also does not
realize that the value of the date truncated to the month is fully
determined by the value of the date truncated to the day. Then expression
and ndistinct statistics are built on those two expressions:
<programlisting>
CREATE TABLE t3 (
a timestamp
);
INSERT INTO t3 SELECT i FROM generate_series('2020-01-01'::timestamp,
'2020-12-31'::timestamp,
'1 minute'::interval) s(i);
ANALYZE t3;
-- the number of matching rows will be drastically underestimated:
EXPLAIN ANALYZE SELECT * FROM t3
WHERE date_trunc('month', a) = '2020-01-01'::timestamp;
EXPLAIN ANALYZE SELECT * FROM t3
WHERE date_trunc('day', a) BETWEEN '2020-01-01'::timestamp
AND '2020-06-30'::timestamp;
EXPLAIN ANALYZE SELECT date_trunc('month', a), date_trunc('day', a)
FROM t3 GROUP BY 1, 2;
-- build ndistinct statistics on the pair of expressions (per-expression
-- statistics are built automatically)
CREATE STATISTICS s3 (ndistinct) ON date_trunc('month', a), date_trunc('day', a) FROM t3;
ANALYZE t3;
-- now the row count estimates are more accurate:
EXPLAIN ANALYZE SELECT * FROM t3
WHERE date_trunc('month', a) = '2020-01-01'::timestamp;
EXPLAIN ANALYZE SELECT * FROM t3
WHERE date_trunc('day', a) BETWEEN '2020-01-01'::timestamp
AND '2020-06-30'::timestamp;
EXPLAIN ANALYZE SELECT date_trunc('month', a), date_trunc('day', a)
FROM t3 GROUP BY 1, 2;
</programlisting>
Without expression and ndistinct statistics, the planner has no information
about the number of distinct values for the expressions, and has to rely
on default estimates. The equality and range conditions are assumed to have
0.5% selectivity, and the number of distinct values in the expression is
assumed to be the same as for the column (i.e. unique). This results in a
significant underestimate of the row count in the first two queries. Moreover,
the planner has no information about the relationship between the expressions,
so it assumes the two <literal>WHERE</literal> and <literal>GROUP BY</literal>
conditions are independent, and multiplies their selectivities together to
arrive at a severe overestimate of the group count in the aggregate query.
This is further exacerbated by the lack of accurate statistics for the
expressions, forcing the planner to use a default ndistinct estimate for the
expression derived from ndistinct for the column. With such statistics, the
planner recognizes that the conditions are correlated, and arrives at much
more accurate estimates.
</para>
</refsect1> </refsect1>
<refsect1> <refsect1>
......
...@@ -49,15 +49,15 @@ include $(top_srcdir)/src/backend/common.mk ...@@ -49,15 +49,15 @@ include $(top_srcdir)/src/backend/common.mk
# Note: the order of this list determines the order in which the catalog # Note: the order of this list determines the order in which the catalog
# header files are assembled into postgres.bki. BKI_BOOTSTRAP catalogs # header files are assembled into postgres.bki. BKI_BOOTSTRAP catalogs
# must appear first, and there are reputedly other, undocumented ordering # must appear first, and pg_statistic before pg_statistic_ext_data, and
# dependencies. # there are reputedly other, undocumented ordering dependencies.
CATALOG_HEADERS := \ CATALOG_HEADERS := \
pg_proc.h pg_type.h pg_attribute.h pg_class.h \ pg_proc.h pg_type.h pg_attribute.h pg_class.h \
pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \ pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \ pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \ pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
pg_statistic_ext.h pg_statistic_ext_data.h \ pg_statistic.h pg_statistic_ext.h pg_statistic_ext_data.h \
pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \ pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \ pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h \ pg_database.h pg_db_role_setting.h pg_tablespace.h \
pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
......
...@@ -264,6 +264,7 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS ...@@ -264,6 +264,7 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
JOIN pg_attribute a JOIN pg_attribute a
ON (a.attrelid = s.stxrelid AND a.attnum = k) ON (a.attrelid = s.stxrelid AND a.attnum = k)
) AS attnames, ) AS attnames,
pg_get_statisticsobjdef_expressions(s.oid) as exprs,
s.stxkind AS kinds, s.stxkind AS kinds,
sd.stxdndistinct AS n_distinct, sd.stxdndistinct AS n_distinct,
sd.stxddependencies AS dependencies, sd.stxddependencies AS dependencies,
...@@ -290,6 +291,74 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS ...@@ -290,6 +291,74 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
WHERE NOT has_column_privilege(c.oid, a.attnum, 'select') ) WHERE NOT has_column_privilege(c.oid, a.attnum, 'select') )
AND (c.relrowsecurity = false OR NOT row_security_active(c.oid)); AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
SELECT cn.nspname AS schemaname,
c.relname AS tablename,
sn.nspname AS statistics_schemaname,
s.stxname AS statistics_name,
pg_get_userbyid(s.stxowner) AS statistics_owner,
stat.expr,
(stat.a).stanullfrac AS null_frac,
(stat.a).stawidth AS avg_width,
(stat.a).stadistinct AS n_distinct,
(CASE
WHEN (stat.a).stakind1 = 1 THEN (stat.a).stavalues1
WHEN (stat.a).stakind2 = 1 THEN (stat.a).stavalues2
WHEN (stat.a).stakind3 = 1 THEN (stat.a).stavalues3
WHEN (stat.a).stakind4 = 1 THEN (stat.a).stavalues4
WHEN (stat.a).stakind5 = 1 THEN (stat.a).stavalues5
END) AS most_common_vals,
(CASE
WHEN (stat.a).stakind1 = 1 THEN (stat.a).stanumbers1
WHEN (stat.a).stakind2 = 1 THEN (stat.a).stanumbers2
WHEN (stat.a).stakind3 = 1 THEN (stat.a).stanumbers3
WHEN (stat.a).stakind4 = 1 THEN (stat.a).stanumbers4
WHEN (stat.a).stakind5 = 1 THEN (stat.a).stanumbers5
END) AS most_common_freqs,
(CASE
WHEN (stat.a).stakind1 = 2 THEN (stat.a).stavalues1
WHEN (stat.a).stakind2 = 2 THEN (stat.a).stavalues2
WHEN (stat.a).stakind3 = 2 THEN (stat.a).stavalues3
WHEN (stat.a).stakind4 = 2 THEN (stat.a).stavalues4
WHEN (stat.a).stakind5 = 2 THEN (stat.a).stavalues5
END) AS histogram_bounds,
(CASE
WHEN (stat.a).stakind1 = 3 THEN (stat.a).stanumbers1[1]
WHEN (stat.a).stakind2 = 3 THEN (stat.a).stanumbers2[1]
WHEN (stat.a).stakind3 = 3 THEN (stat.a).stanumbers3[1]
WHEN (stat.a).stakind4 = 3 THEN (stat.a).stanumbers4[1]
WHEN (stat.a).stakind5 = 3 THEN (stat.a).stanumbers5[1]
END) correlation,
(CASE
WHEN (stat.a).stakind1 = 4 THEN (stat.a).stavalues1
WHEN (stat.a).stakind2 = 4 THEN (stat.a).stavalues2
WHEN (stat.a).stakind3 = 4 THEN (stat.a).stavalues3
WHEN (stat.a).stakind4 = 4 THEN (stat.a).stavalues4
WHEN (stat.a).stakind5 = 4 THEN (stat.a).stavalues5
END) AS most_common_elems,
(CASE
WHEN (stat.a).stakind1 = 4 THEN (stat.a).stanumbers1
WHEN (stat.a).stakind2 = 4 THEN (stat.a).stanumbers2
WHEN (stat.a).stakind3 = 4 THEN (stat.a).stanumbers3
WHEN (stat.a).stakind4 = 4 THEN (stat.a).stanumbers4
WHEN (stat.a).stakind5 = 4 THEN (stat.a).stanumbers5
END) AS most_common_elem_freqs,
(CASE
WHEN (stat.a).stakind1 = 5 THEN (stat.a).stanumbers1
WHEN (stat.a).stakind2 = 5 THEN (stat.a).stanumbers2
WHEN (stat.a).stakind3 = 5 THEN (stat.a).stanumbers3
WHEN (stat.a).stakind4 = 5 THEN (stat.a).stanumbers4
WHEN (stat.a).stakind5 = 5 THEN (stat.a).stanumbers5
END) AS elem_count_histogram
FROM pg_statistic_ext s JOIN pg_class c ON (c.oid = s.stxrelid)
LEFT JOIN pg_statistic_ext_data sd ON (s.oid = sd.stxoid)
LEFT JOIN pg_namespace cn ON (cn.oid = c.relnamespace)
LEFT JOIN pg_namespace sn ON (sn.oid = s.stxnamespace)
JOIN LATERAL (
SELECT unnest(pg_get_statisticsobjdef_expressions(s.oid)) AS expr,
unnest(sd.stxdexpr)::pg_statistic AS a
) stat ON (stat.expr IS NOT NULL);
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data -- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL on pg_statistic_ext_data FROM public; REVOKE ALL on pg_statistic_ext_data FROM public;
......
This diff is collapsed.
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
#include "catalog/pg_namespace.h" #include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h" #include "catalog/pg_opclass.h"
#include "catalog/pg_tablespace.h" #include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h" #include "catalog/pg_trigger.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "catalog/storage.h" #include "catalog/storage.h"
...@@ -188,6 +189,8 @@ typedef struct AlteredTableInfo ...@@ -188,6 +189,8 @@ typedef struct AlteredTableInfo
List *changedIndexDefs; /* string definitions of same */ List *changedIndexDefs; /* string definitions of same */
char *replicaIdentityIndex; /* index to reset as REPLICA IDENTITY */ char *replicaIdentityIndex; /* index to reset as REPLICA IDENTITY */
char *clusterOnIndex; /* index to use for CLUSTER */ char *clusterOnIndex; /* index to use for CLUSTER */
List *changedStatisticsOids; /* OIDs of statistics to rebuild */
List *changedStatisticsDefs; /* string definitions of same */
} AlteredTableInfo; } AlteredTableInfo;
/* Struct describing one new constraint to check in Phase 3 scan */ /* Struct describing one new constraint to check in Phase 3 scan */
...@@ -440,6 +443,8 @@ static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *c ...@@ -440,6 +443,8 @@ static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *c
ObjectAddresses *addrs); ObjectAddresses *addrs);
static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel, static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode); IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
static ObjectAddress ATExecAddConstraint(List **wqueue, static ObjectAddress ATExecAddConstraint(List **wqueue,
AlteredTableInfo *tab, Relation rel, AlteredTableInfo *tab, Relation rel,
Constraint *newConstraint, bool recurse, bool is_readd, Constraint *newConstraint, bool recurse, bool is_readd,
...@@ -496,6 +501,7 @@ static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, ...@@ -496,6 +501,7 @@ static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode); AlterTableCmd *cmd, LOCKMODE lockmode);
static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab); static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab); static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
static void RememberStatisticsForRebuilding(Oid indoid, AlteredTableInfo *tab);
static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
LOCKMODE lockmode); LOCKMODE lockmode);
static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
...@@ -4756,6 +4762,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, ...@@ -4756,6 +4762,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
lockmode); lockmode);
break; break;
case AT_ReAddStatistics: /* ADD STATISTICS */
address = ATExecAddStatistics(tab, rel, (CreateStatsStmt *) cmd->def,
true, lockmode);
break;
case AT_AddConstraint: /* ADD CONSTRAINT */ case AT_AddConstraint: /* ADD CONSTRAINT */
/* Transform the command only during initial examination */ /* Transform the command only during initial examination */
if (cur_pass == AT_PASS_ADD_CONSTR) if (cur_pass == AT_PASS_ADD_CONSTR)
...@@ -8283,6 +8293,29 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel, ...@@ -8283,6 +8293,29 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
return address; return address;
} }
/*
* ALTER TABLE ADD STATISTICS
*
* This is no such command in the grammar, but we use this internally to add
* AT_ReAddStatistics subcommands to rebuild extended statistics after a table
* column type change.
*/
static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
ObjectAddress address;
Assert(IsA(stmt, CreateStatsStmt));
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
address = CreateStatistics(stmt);
return address;
}
/* /*
* ALTER TABLE ADD CONSTRAINT USING INDEX * ALTER TABLE ADD CONSTRAINT USING INDEX
* *
...@@ -11830,9 +11863,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, ...@@ -11830,9 +11863,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
* Give the extended-stats machinery a chance to fix anything * Give the extended-stats machinery a chance to fix anything
* that this column type change would break. * that this column type change would break.
*/ */
UpdateStatisticsForTypeChange(foundObject.objectId, RememberStatisticsForRebuilding(foundObject.objectId, tab);
RelationGetRelid(rel), attnum,
attTup->atttypid, targettype);
break; break;
case OCLASS_PROC: case OCLASS_PROC:
...@@ -12202,6 +12233,32 @@ RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab) ...@@ -12202,6 +12233,32 @@ RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab)
} }
} }
/*
* Subroutine for ATExecAlterColumnType: remember that a statistics object
* needs to be rebuilt (which we might already know).
*/
static void
RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
{
/*
* This de-duplication check is critical for two independent reasons: we
* mustn't try to recreate the same statistics object twice, and if the
* statistics depends on more than one column whose type is to be altered,
* we must capture its definition string before applying any of the type
* changes. ruleutils.c will get confused if we ask again later.
*/
if (!list_member_oid(tab->changedStatisticsOids, stxoid))
{
/* OK, capture the index's existing definition string */
char *defstring = pg_get_statisticsobjdef_string(stxoid);
tab->changedStatisticsOids = lappend_oid(tab->changedStatisticsOids,
stxoid);
tab->changedStatisticsDefs = lappend(tab->changedStatisticsDefs,
defstring);
}
}
/* /*
* Cleanup after we've finished all the ALTER TYPE operations for a * Cleanup after we've finished all the ALTER TYPE operations for a
* particular relation. We have to drop and recreate all the indexes * particular relation. We have to drop and recreate all the indexes
...@@ -12306,6 +12363,22 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) ...@@ -12306,6 +12363,22 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
add_exact_object_address(&obj, objects); add_exact_object_address(&obj, objects);
} }
/* add dependencies for new statistics */
forboth(oid_item, tab->changedStatisticsOids,
def_item, tab->changedStatisticsDefs)
{
Oid oldId = lfirst_oid(oid_item);
Oid relid;
relid = StatisticsGetRelation(oldId, false);
ATPostAlterTypeParse(oldId, relid, InvalidOid,
(char *) lfirst(def_item),
wqueue, lockmode, tab->rewrite);
ObjectAddressSet(obj, StatisticExtRelationId, oldId);
add_exact_object_address(&obj, objects);
}
/* /*
* Queue up command to restore replica identity index marking * Queue up command to restore replica identity index marking
*/ */
...@@ -12354,9 +12427,9 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) ...@@ -12354,9 +12427,9 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
} }
/* /*
* Parse the previously-saved definition string for a constraint or index * Parse the previously-saved definition string for a constraint, index or
* against the newly-established column data type(s), and queue up the * statistics object against the newly-established column data type(s), and
* resulting command parsetrees for execution. * queue up the resulting command parsetrees for execution.
* *
* This might fail if, for example, you have a WHERE clause that uses an * This might fail if, for example, you have a WHERE clause that uses an
* operator that's not available for the new column type. * operator that's not available for the new column type.
...@@ -12402,6 +12475,11 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, ...@@ -12402,6 +12475,11 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt); querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts); querytree_list = list_concat(querytree_list, afterStmts);
} }
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
transformStatsStmt(oldRelId,
(CreateStatsStmt *) stmt,
cmd));
else else
querytree_list = lappend(querytree_list, stmt); querytree_list = lappend(querytree_list, stmt);
} }
...@@ -12540,6 +12618,20 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, ...@@ -12540,6 +12618,20 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
elog(ERROR, "unexpected statement subtype: %d", elog(ERROR, "unexpected statement subtype: %d",
(int) stmt->subtype); (int) stmt->subtype);
} }
else if (IsA(stm, CreateStatsStmt))
{
CreateStatsStmt *stmt = (CreateStatsStmt *) stm;
AlterTableCmd *newcmd;
/* keep the statistics object's comment */
stmt->stxcomment = GetComment(oldId, StatisticExtRelationId, 0);
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_ReAddStatistics;
newcmd->def = (Node *) stmt;
tab->subcmds[AT_PASS_MISC] =
lappend(tab->subcmds[AT_PASS_MISC], newcmd);
}
else else
elog(ERROR, "unexpected statement type: %d", elog(ERROR, "unexpected statement type: %d",
(int) nodeTag(stm)); (int) nodeTag(stm));
......
...@@ -2980,6 +2980,17 @@ _copyIndexElem(const IndexElem *from) ...@@ -2980,6 +2980,17 @@ _copyIndexElem(const IndexElem *from)
return newnode; return newnode;
} }
static StatsElem *
_copyStatsElem(const StatsElem *from)
{
StatsElem *newnode = makeNode(StatsElem);
COPY_STRING_FIELD(name);
COPY_NODE_FIELD(expr);
return newnode;
}
static ColumnDef * static ColumnDef *
_copyColumnDef(const ColumnDef *from) _copyColumnDef(const ColumnDef *from)
{ {
...@@ -5699,6 +5710,9 @@ copyObjectImpl(const void *from) ...@@ -5699,6 +5710,9 @@ copyObjectImpl(const void *from)
case T_IndexElem: case T_IndexElem:
retval = _copyIndexElem(from); retval = _copyIndexElem(from);
break; break;
case T_StatsElem:
retval = _copyStatsElem(from);
break;
case T_ColumnDef: case T_ColumnDef:
retval = _copyColumnDef(from); retval = _copyColumnDef(from);
break; break;
......
...@@ -2596,6 +2596,16 @@ _equalIndexElem(const IndexElem *a, const IndexElem *b) ...@@ -2596,6 +2596,16 @@ _equalIndexElem(const IndexElem *a, const IndexElem *b)
return true; return true;
} }
static bool
_equalStatsElem(const StatsElem *a, const StatsElem *b)
{
COMPARE_STRING_FIELD(name);
COMPARE_NODE_FIELD(expr);
return true;
}
static bool static bool
_equalColumnDef(const ColumnDef *a, const ColumnDef *b) _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
{ {
...@@ -3724,6 +3734,9 @@ equal(const void *a, const void *b) ...@@ -3724,6 +3734,9 @@ equal(const void *a, const void *b)
case T_IndexElem: case T_IndexElem:
retval = _equalIndexElem(a, b); retval = _equalIndexElem(a, b);
break; break;
case T_StatsElem:
retval = _equalStatsElem(a, b);
break;
case T_ColumnDef: case T_ColumnDef:
retval = _equalColumnDef(a, b); retval = _equalColumnDef(a, b);
break; break;
......
...@@ -2943,6 +2943,15 @@ _outIndexElem(StringInfo str, const IndexElem *node) ...@@ -2943,6 +2943,15 @@ _outIndexElem(StringInfo str, const IndexElem *node)
WRITE_ENUM_FIELD(nulls_ordering, SortByNulls); WRITE_ENUM_FIELD(nulls_ordering, SortByNulls);
} }
static void
_outStatsElem(StringInfo str, const StatsElem *node)
{
WRITE_NODE_TYPE("STATSELEM");
WRITE_STRING_FIELD(name);
WRITE_NODE_FIELD(expr);
}
static void static void
_outQuery(StringInfo str, const Query *node) _outQuery(StringInfo str, const Query *node)
{ {
...@@ -4286,6 +4295,9 @@ outNode(StringInfo str, const void *obj) ...@@ -4286,6 +4295,9 @@ outNode(StringInfo str, const void *obj)
case T_IndexElem: case T_IndexElem:
_outIndexElem(str, obj); _outIndexElem(str, obj);
break; break;
case T_StatsElem:
_outStatsElem(str, obj);
break;
case T_Query: case T_Query:
_outQuery(str, obj); _outQuery(str, obj);
break; break;
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
#include "foreign/fdwapi.h" #include "foreign/fdwapi.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h" #include "nodes/supportnodes.h"
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "optimizer/cost.h" #include "optimizer/cost.h"
...@@ -1308,6 +1309,7 @@ get_relation_constraints(PlannerInfo *root, ...@@ -1308,6 +1309,7 @@ get_relation_constraints(PlannerInfo *root,
static List * static List *
get_relation_statistics(RelOptInfo *rel, Relation relation) get_relation_statistics(RelOptInfo *rel, Relation relation)
{ {
Index varno = rel->relid;
List *statoidlist; List *statoidlist;
List *stainfos = NIL; List *stainfos = NIL;
ListCell *l; ListCell *l;
...@@ -1321,6 +1323,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation) ...@@ -1321,6 +1323,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
HeapTuple htup; HeapTuple htup;
HeapTuple dtup; HeapTuple dtup;
Bitmapset *keys = NULL; Bitmapset *keys = NULL;
List *exprs = NIL;
int i; int i;
htup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statOid)); htup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statOid));
...@@ -1340,6 +1343,49 @@ get_relation_statistics(RelOptInfo *rel, Relation relation) ...@@ -1340,6 +1343,49 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
for (i = 0; i < staForm->stxkeys.dim1; i++) for (i = 0; i < staForm->stxkeys.dim1; i++)
keys = bms_add_member(keys, staForm->stxkeys.values[i]); keys = bms_add_member(keys, staForm->stxkeys.values[i]);
/*
* Preprocess expressions (if any). We read the expressions, run them
* through eval_const_expressions, and fix the varnos.
*/
{
bool isnull;
Datum datum;
/* decode expression (if any) */
datum = SysCacheGetAttr(STATEXTOID, htup,
Anum_pg_statistic_ext_stxexprs, &isnull);
if (!isnull)
{
char *exprsString;
exprsString = TextDatumGetCString(datum);
exprs = (List *) stringToNode(exprsString);
pfree(exprsString);
/*
* Run the expressions through eval_const_expressions. This is
* not just an optimization, but is necessary, because the
* planner will be comparing them to similarly-processed qual
* clauses, and may fail to detect valid matches without this.
* We must not use canonicalize_qual, however, since these
* aren't qual expressions.
*/
exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
/* May as well fix opfuncids too */
fix_opfuncids((Node *) exprs);
/*
* Modify the copies we obtain from the relcache to have the
* correct varno for the parent relation, so that they match
* up correctly against qual clauses.
*/
if (varno != 1)
ChangeVarNodes((Node *) exprs, 1, varno, 0);
}
}
/* add one StatisticExtInfo for each kind built */ /* add one StatisticExtInfo for each kind built */
if (statext_is_kind_built(dtup, STATS_EXT_NDISTINCT)) if (statext_is_kind_built(dtup, STATS_EXT_NDISTINCT))
{ {
...@@ -1349,6 +1395,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation) ...@@ -1349,6 +1395,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
info->rel = rel; info->rel = rel;
info->kind = STATS_EXT_NDISTINCT; info->kind = STATS_EXT_NDISTINCT;
info->keys = bms_copy(keys); info->keys = bms_copy(keys);
info->exprs = exprs;
stainfos = lappend(stainfos, info); stainfos = lappend(stainfos, info);
} }
...@@ -1361,6 +1408,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation) ...@@ -1361,6 +1408,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
info->rel = rel; info->rel = rel;
info->kind = STATS_EXT_DEPENDENCIES; info->kind = STATS_EXT_DEPENDENCIES;
info->keys = bms_copy(keys); info->keys = bms_copy(keys);
info->exprs = exprs;
stainfos = lappend(stainfos, info); stainfos = lappend(stainfos, info);
} }
...@@ -1373,6 +1421,20 @@ get_relation_statistics(RelOptInfo *rel, Relation relation) ...@@ -1373,6 +1421,20 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
info->rel = rel; info->rel = rel;
info->kind = STATS_EXT_MCV; info->kind = STATS_EXT_MCV;
info->keys = bms_copy(keys); info->keys = bms_copy(keys);
info->exprs = exprs;
stainfos = lappend(stainfos, info);
}
if (statext_is_kind_built(dtup, STATS_EXT_EXPRESSIONS))
{
StatisticExtInfo *info = makeNode(StatisticExtInfo);
info->statOid = statOid;
info->rel = rel;
info->kind = STATS_EXT_EXPRESSIONS;
info->keys = bms_copy(keys);
info->exprs = exprs;
stainfos = lappend(stainfos, info); stainfos = lappend(stainfos, info);
} }
......
...@@ -239,6 +239,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -239,6 +239,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
WindowDef *windef; WindowDef *windef;
JoinExpr *jexpr; JoinExpr *jexpr;
IndexElem *ielem; IndexElem *ielem;
StatsElem *selem;
Alias *alias; Alias *alias;
RangeVar *range; RangeVar *range;
IntoClause *into; IntoClause *into;
...@@ -405,7 +406,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -405,7 +406,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
old_aggr_definition old_aggr_list old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list opt_column_list columnList opt_name_list
sort_clause opt_sort_clause sortby_list index_params sort_clause opt_sort_clause sortby_list index_params stats_params
opt_include opt_c_include index_including_params opt_include opt_c_include index_including_params
name_list role_list from_clause from_list opt_array_bounds name_list role_list from_clause from_list opt_array_bounds
qualified_name_list any_name any_name_list type_name_list qualified_name_list any_name any_name_list type_name_list
...@@ -512,6 +513,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -512,6 +513,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> func_alias_clause %type <list> func_alias_clause
%type <sortby> sortby %type <sortby> sortby
%type <ielem> index_elem index_elem_options %type <ielem> index_elem index_elem_options
%type <selem> stats_param
%type <node> table_ref %type <node> table_ref
%type <jexpr> joined_table %type <jexpr> joined_table
%type <range> relation_expr %type <range> relation_expr
...@@ -4097,7 +4099,7 @@ ExistingIndex: USING INDEX name { $$ = $3; } ...@@ -4097,7 +4099,7 @@ ExistingIndex: USING INDEX name { $$ = $3; }
CreateStatsStmt: CreateStatsStmt:
CREATE STATISTICS any_name CREATE STATISTICS any_name
opt_name_list ON expr_list FROM from_list opt_name_list ON stats_params FROM from_list
{ {
CreateStatsStmt *n = makeNode(CreateStatsStmt); CreateStatsStmt *n = makeNode(CreateStatsStmt);
n->defnames = $3; n->defnames = $3;
...@@ -4109,7 +4111,7 @@ CreateStatsStmt: ...@@ -4109,7 +4111,7 @@ CreateStatsStmt:
$$ = (Node *)n; $$ = (Node *)n;
} }
| CREATE STATISTICS IF_P NOT EXISTS any_name | CREATE STATISTICS IF_P NOT EXISTS any_name
opt_name_list ON expr_list FROM from_list opt_name_list ON stats_params FROM from_list
{ {
CreateStatsStmt *n = makeNode(CreateStatsStmt); CreateStatsStmt *n = makeNode(CreateStatsStmt);
n->defnames = $6; n->defnames = $6;
...@@ -4122,6 +4124,36 @@ CreateStatsStmt: ...@@ -4122,6 +4124,36 @@ CreateStatsStmt:
} }
; ;
/*
* Statistics attributes can be either simple column references, or arbitrary
* expressions in parens. For compatibility with index attributes permitted
* in CREATE INDEX, we allow an expression that's just a function call to be
* written without parens.
*/
stats_params: stats_param { $$ = list_make1($1); }
| stats_params ',' stats_param { $$ = lappend($1, $3); }
;
stats_param: ColId
{
$$ = makeNode(StatsElem);
$$->name = $1;
$$->expr = NULL;
}
| func_expr_windowless
{
$$ = makeNode(StatsElem);
$$->name = NULL;
$$->expr = $1;
}
| '(' a_expr ')'
{
$$ = makeNode(StatsElem);
$$->name = NULL;
$$->expr = $2;
}
;
/***************************************************************************** /*****************************************************************************
* *
......
...@@ -484,6 +484,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) ...@@ -484,6 +484,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
else else
err = _("grouping operations are not allowed in index predicates"); err = _("grouping operations are not allowed in index predicates");
break;
case EXPR_KIND_STATS_EXPRESSION:
if (isAgg)
err = _("aggregate functions are not allowed in statistics expressions");
else
err = _("grouping operations are not allowed in statistics expressions");
break; break;
case EXPR_KIND_ALTER_COL_TRANSFORM: case EXPR_KIND_ALTER_COL_TRANSFORM:
if (isAgg) if (isAgg)
...@@ -910,6 +917,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, ...@@ -910,6 +917,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_INDEX_EXPRESSION: case EXPR_KIND_INDEX_EXPRESSION:
err = _("window functions are not allowed in index expressions"); err = _("window functions are not allowed in index expressions");
break; break;
case EXPR_KIND_STATS_EXPRESSION:
err = _("window functions are not allowed in statistics expressions");
break;
case EXPR_KIND_INDEX_PREDICATE: case EXPR_KIND_INDEX_PREDICATE:
err = _("window functions are not allowed in index predicates"); err = _("window functions are not allowed in index predicates");
break; break;
......
...@@ -500,6 +500,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) ...@@ -500,6 +500,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_FUNCTION_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION: case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE: case EXPR_KIND_INDEX_PREDICATE:
case EXPR_KIND_STATS_EXPRESSION:
case EXPR_KIND_ALTER_COL_TRANSFORM: case EXPR_KIND_ALTER_COL_TRANSFORM:
case EXPR_KIND_EXECUTE_PARAMETER: case EXPR_KIND_EXECUTE_PARAMETER:
case EXPR_KIND_TRIGGER_WHEN: case EXPR_KIND_TRIGGER_WHEN:
...@@ -1741,6 +1742,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink) ...@@ -1741,6 +1742,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_INDEX_PREDICATE: case EXPR_KIND_INDEX_PREDICATE:
err = _("cannot use subquery in index predicate"); err = _("cannot use subquery in index predicate");
break; break;
case EXPR_KIND_STATS_EXPRESSION:
err = _("cannot use subquery in statistics expression");
break;
case EXPR_KIND_ALTER_COL_TRANSFORM: case EXPR_KIND_ALTER_COL_TRANSFORM:
err = _("cannot use subquery in transform expression"); err = _("cannot use subquery in transform expression");
break; break;
...@@ -3030,6 +3034,8 @@ ParseExprKindName(ParseExprKind exprKind) ...@@ -3030,6 +3034,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "index expression"; return "index expression";
case EXPR_KIND_INDEX_PREDICATE: case EXPR_KIND_INDEX_PREDICATE:
return "index predicate"; return "index predicate";
case EXPR_KIND_STATS_EXPRESSION:
return "statistics expression";
case EXPR_KIND_ALTER_COL_TRANSFORM: case EXPR_KIND_ALTER_COL_TRANSFORM:
return "USING"; return "USING";
case EXPR_KIND_EXECUTE_PARAMETER: case EXPR_KIND_EXECUTE_PARAMETER:
......
...@@ -2503,6 +2503,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) ...@@ -2503,6 +2503,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_INDEX_PREDICATE: case EXPR_KIND_INDEX_PREDICATE:
err = _("set-returning functions are not allowed in index predicates"); err = _("set-returning functions are not allowed in index predicates");
break; break;
case EXPR_KIND_STATS_EXPRESSION:
err = _("set-returning functions are not allowed in statistics expressions");
break;
case EXPR_KIND_ALTER_COL_TRANSFORM: case EXPR_KIND_ALTER_COL_TRANSFORM:
err = _("set-returning functions are not allowed in transform expressions"); err = _("set-returning functions are not allowed in transform expressions");
break; break;
......
...@@ -1917,6 +1917,9 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid, ...@@ -1917,6 +1917,9 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
stat_types = lappend(stat_types, makeString("dependencies")); stat_types = lappend(stat_types, makeString("dependencies"));
else if (enabled[i] == STATS_EXT_MCV) else if (enabled[i] == STATS_EXT_MCV)
stat_types = lappend(stat_types, makeString("mcv")); stat_types = lappend(stat_types, makeString("mcv"));
else if (enabled[i] == STATS_EXT_EXPRESSIONS)
/* expression stats are not exposed to users */
continue;
else else
elog(ERROR, "unrecognized statistics kind %c", enabled[i]); elog(ERROR, "unrecognized statistics kind %c", enabled[i]);
} }
...@@ -1924,14 +1927,47 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid, ...@@ -1924,14 +1927,47 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
/* Determine which columns the statistics are on */ /* Determine which columns the statistics are on */
for (i = 0; i < statsrec->stxkeys.dim1; i++) for (i = 0; i < statsrec->stxkeys.dim1; i++)
{ {
ColumnRef *cref = makeNode(ColumnRef); StatsElem *selem = makeNode(StatsElem);
AttrNumber attnum = statsrec->stxkeys.values[i]; AttrNumber attnum = statsrec->stxkeys.values[i];
cref->fields = list_make1(makeString(get_attname(heapRelid, selem->name = get_attname(heapRelid, attnum, false);
attnum, false))); selem->expr = NULL;
cref->location = -1;
def_names = lappend(def_names, selem);
}
/*
* Now handle expressions, if there are any. The order (with respect to
* regular attributes) does not really matter for extended stats, so we
* simply append them after simple column references.
*
* XXX Some places during build/estimation treat expressions as if they
* are before atttibutes, but for the CREATE command that's entirely
* irrelevant.
*/
datum = SysCacheGetAttr(STATEXTOID, ht_stats,
Anum_pg_statistic_ext_stxexprs, &isnull);
if (!isnull)
{
ListCell *lc;
List *exprs = NIL;
char *exprsString;
exprsString = TextDatumGetCString(datum);
exprs = (List *) stringToNode(exprsString);
foreach(lc, exprs)
{
StatsElem *selem = makeNode(StatsElem);
def_names = lappend(def_names, cref); selem->name = NULL;
selem->expr = (Node *) lfirst(lc);
def_names = lappend(def_names, selem);
}
pfree(exprsString);
} }
/* finally, build the output node */ /* finally, build the output node */
...@@ -1942,6 +1978,7 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid, ...@@ -1942,6 +1978,7 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
stats->relations = list_make1(heapRel); stats->relations = list_make1(heapRel);
stats->stxcomment = NULL; stats->stxcomment = NULL;
stats->if_not_exists = false; stats->if_not_exists = false;
stats->transformed = true; /* don't need transformStatsStmt again */
/* Clean up */ /* Clean up */
ReleaseSysCache(ht_stats); ReleaseSysCache(ht_stats);
...@@ -2866,6 +2903,84 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) ...@@ -2866,6 +2903,84 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
return stmt; return stmt;
} }
/*
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function rely only on
* the passed-in relid (and not on stmt->relation) to determine the target
* relation.
*/
CreateStatsStmt *
transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
return stmt;
/*
* We must not scribble on the passed-in CreateStatsStmt, so copy it.
* (This is overkill, but easy.)
*/
stmt = copyObject(stmt);
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
/*
* Put the parent table into the rtable so that the expressions can refer
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
/* no to join list, yes to namespaces */
addNSItemToQuery(pstate, nsitem, false, true, true);
/* take care of any expressions */
foreach(l, stmt->exprs)
{
StatsElem *selem = (StatsElem *) lfirst(l);
if (selem->expr)
{
/* Now do parse transformation of the expression */
selem->expr = transformExpr(pstate, selem->expr,
EXPR_KIND_STATS_EXPRESSION);
/* We have to fix its collations too */
assign_expr_collations(pstate, selem->expr);
}
}
/*
* Check that only the base rel is mentioned. (This should be dead code
* now that add_missing_from is history.)
*/
if (list_length(pstate->p_rtable) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("statistics expressions can refer only to the table being indexed")));
free_parsestate(pstate);
/* Close relation */
table_close(rel, NoLock);
/* Mark statement as successfully transformed */
stmt->transformed = true;
return stmt;
}
/* /*
* transformRuleStmt - * transformRuleStmt -
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -36,8 +36,7 @@ ...@@ -36,8 +36,7 @@
#include "utils/syscache.h" #include "utils/syscache.h"
#include "utils/typcache.h" #include "utils/typcache.h"
static double ndistinct_for_combination(double totalrows, int numrows, static double ndistinct_for_combination(double totalrows, StatsBuildData *data,
HeapTuple *rows, VacAttrStats **stats,
int k, int *combination); int k, int *combination);
static double estimate_ndistinct(double totalrows, int numrows, int d, int f1); static double estimate_ndistinct(double totalrows, int numrows, int d, int f1);
static int n_choose_k(int n, int k); static int n_choose_k(int n, int k);
...@@ -81,15 +80,18 @@ static void generate_combinations(CombinationGenerator *state); ...@@ -81,15 +80,18 @@ static void generate_combinations(CombinationGenerator *state);
* *
* This computes the ndistinct estimate using the same estimator used * This computes the ndistinct estimate using the same estimator used
* in analyze.c and then computes the coefficient. * in analyze.c and then computes the coefficient.
*
* To handle expressions easily, we treat them as system attributes with
* negative attnums, and offset everything by number of expressions to
* allow using Bitmapsets.
*/ */
MVNDistinct * MVNDistinct *
statext_ndistinct_build(double totalrows, int numrows, HeapTuple *rows, statext_ndistinct_build(double totalrows, StatsBuildData *data)
Bitmapset *attrs, VacAttrStats **stats)
{ {
MVNDistinct *result; MVNDistinct *result;
int k; int k;
int itemcnt; int itemcnt;
int numattrs = bms_num_members(attrs); int numattrs = data->nattnums;
int numcombs = num_combinations(numattrs); int numcombs = num_combinations(numattrs);
result = palloc(offsetof(MVNDistinct, items) + result = palloc(offsetof(MVNDistinct, items) +
...@@ -112,13 +114,19 @@ statext_ndistinct_build(double totalrows, int numrows, HeapTuple *rows, ...@@ -112,13 +114,19 @@ statext_ndistinct_build(double totalrows, int numrows, HeapTuple *rows,
MVNDistinctItem *item = &result->items[itemcnt]; MVNDistinctItem *item = &result->items[itemcnt];
int j; int j;
item->attrs = NULL; item->attributes = palloc(sizeof(AttrNumber) * k);
item->nattributes = k;
/* translate the indexes to attnums */
for (j = 0; j < k; j++) for (j = 0; j < k; j++)
item->attrs = bms_add_member(item->attrs, {
stats[combination[j]]->attr->attnum); item->attributes[j] = data->attnums[combination[j]];
Assert(AttributeNumberIsValid(item->attributes[j]));
}
item->ndistinct = item->ndistinct =
ndistinct_for_combination(totalrows, numrows, rows, ndistinct_for_combination(totalrows, data, k, combination);
stats, k, combination);
itemcnt++; itemcnt++;
Assert(itemcnt <= result->nitems); Assert(itemcnt <= result->nitems);
...@@ -189,7 +197,7 @@ statext_ndistinct_serialize(MVNDistinct *ndistinct) ...@@ -189,7 +197,7 @@ statext_ndistinct_serialize(MVNDistinct *ndistinct)
{ {
int nmembers; int nmembers;
nmembers = bms_num_members(ndistinct->items[i].attrs); nmembers = ndistinct->items[i].nattributes;
Assert(nmembers >= 2); Assert(nmembers >= 2);
len += SizeOfItem(nmembers); len += SizeOfItem(nmembers);
...@@ -214,22 +222,15 @@ statext_ndistinct_serialize(MVNDistinct *ndistinct) ...@@ -214,22 +222,15 @@ statext_ndistinct_serialize(MVNDistinct *ndistinct)
for (i = 0; i < ndistinct->nitems; i++) for (i = 0; i < ndistinct->nitems; i++)
{ {
MVNDistinctItem item = ndistinct->items[i]; MVNDistinctItem item = ndistinct->items[i];
int nmembers = bms_num_members(item.attrs); int nmembers = item.nattributes;
int x;
memcpy(tmp, &item.ndistinct, sizeof(double)); memcpy(tmp, &item.ndistinct, sizeof(double));
tmp += sizeof(double); tmp += sizeof(double);
memcpy(tmp, &nmembers, sizeof(int)); memcpy(tmp, &nmembers, sizeof(int));
tmp += sizeof(int); tmp += sizeof(int);
x = -1; memcpy(tmp, item.attributes, sizeof(AttrNumber) * nmembers);
while ((x = bms_next_member(item.attrs, x)) >= 0) tmp += nmembers * sizeof(AttrNumber);
{
AttrNumber value = (AttrNumber) x;
memcpy(tmp, &value, sizeof(AttrNumber));
tmp += sizeof(AttrNumber);
}
/* protect against overflows */ /* protect against overflows */
Assert(tmp <= ((char *) output + len)); Assert(tmp <= ((char *) output + len));
...@@ -301,27 +302,21 @@ statext_ndistinct_deserialize(bytea *data) ...@@ -301,27 +302,21 @@ statext_ndistinct_deserialize(bytea *data)
for (i = 0; i < ndistinct->nitems; i++) for (i = 0; i < ndistinct->nitems; i++)
{ {
MVNDistinctItem *item = &ndistinct->items[i]; MVNDistinctItem *item = &ndistinct->items[i];
int nelems;
item->attrs = NULL;
/* ndistinct value */ /* ndistinct value */
memcpy(&item->ndistinct, tmp, sizeof(double)); memcpy(&item->ndistinct, tmp, sizeof(double));
tmp += sizeof(double); tmp += sizeof(double);
/* number of attributes */ /* number of attributes */
memcpy(&nelems, tmp, sizeof(int)); memcpy(&item->nattributes, tmp, sizeof(int));
tmp += sizeof(int); tmp += sizeof(int);
Assert((nelems >= 2) && (nelems <= STATS_MAX_DIMENSIONS)); Assert((item->nattributes >= 2) && (item->nattributes <= STATS_MAX_DIMENSIONS));
while (nelems-- > 0) item->attributes
{ = (AttrNumber *) palloc(item->nattributes * sizeof(AttrNumber));
AttrNumber attno;
memcpy(&attno, tmp, sizeof(AttrNumber)); memcpy(item->attributes, tmp, sizeof(AttrNumber) * item->nattributes);
tmp += sizeof(AttrNumber); tmp += sizeof(AttrNumber) * item->nattributes;
item->attrs = bms_add_member(item->attrs, attno);
}
/* still within the bytea */ /* still within the bytea */
Assert(tmp <= ((char *) data + VARSIZE_ANY(data))); Assert(tmp <= ((char *) data + VARSIZE_ANY(data)));
...@@ -369,17 +364,17 @@ pg_ndistinct_out(PG_FUNCTION_ARGS) ...@@ -369,17 +364,17 @@ pg_ndistinct_out(PG_FUNCTION_ARGS)
for (i = 0; i < ndist->nitems; i++) for (i = 0; i < ndist->nitems; i++)
{ {
int j;
MVNDistinctItem item = ndist->items[i]; MVNDistinctItem item = ndist->items[i];
int x = -1;
bool first = true;
if (i > 0) if (i > 0)
appendStringInfoString(&str, ", "); appendStringInfoString(&str, ", ");
while ((x = bms_next_member(item.attrs, x)) >= 0) for (j = 0; j < item.nattributes; j++)
{ {
appendStringInfo(&str, "%s%d", first ? "\"" : ", ", x); AttrNumber attnum = item.attributes[j];
first = false;
appendStringInfo(&str, "%s%d", (j == 0) ? "\"" : ", ", attnum);
} }
appendStringInfo(&str, "\": %d", (int) item.ndistinct); appendStringInfo(&str, "\": %d", (int) item.ndistinct);
} }
...@@ -427,8 +422,8 @@ pg_ndistinct_send(PG_FUNCTION_ARGS) ...@@ -427,8 +422,8 @@ pg_ndistinct_send(PG_FUNCTION_ARGS)
* combination of multiple columns. * combination of multiple columns.
*/ */
static double static double
ndistinct_for_combination(double totalrows, int numrows, HeapTuple *rows, ndistinct_for_combination(double totalrows, StatsBuildData *data,
VacAttrStats **stats, int k, int *combination) int k, int *combination)
{ {
int i, int i,
j; j;
...@@ -439,6 +434,7 @@ ndistinct_for_combination(double totalrows, int numrows, HeapTuple *rows, ...@@ -439,6 +434,7 @@ ndistinct_for_combination(double totalrows, int numrows, HeapTuple *rows,
Datum *values; Datum *values;
SortItem *items; SortItem *items;
MultiSortSupport mss; MultiSortSupport mss;
int numrows = data->numrows;
mss = multi_sort_init(k); mss = multi_sort_init(k);
...@@ -467,25 +463,27 @@ ndistinct_for_combination(double totalrows, int numrows, HeapTuple *rows, ...@@ -467,25 +463,27 @@ ndistinct_for_combination(double totalrows, int numrows, HeapTuple *rows,
*/ */
for (i = 0; i < k; i++) for (i = 0; i < k; i++)
{ {
VacAttrStats *colstat = stats[combination[i]]; Oid typid;
TypeCacheEntry *type; TypeCacheEntry *type;
Oid collid = InvalidOid;
VacAttrStats *colstat = data->stats[combination[i]];
typid = colstat->attrtypid;
collid = colstat->attrcollid;
type = lookup_type_cache(colstat->attrtypid, TYPECACHE_LT_OPR); type = lookup_type_cache(typid, TYPECACHE_LT_OPR);
if (type->lt_opr == InvalidOid) /* shouldn't happen */ if (type->lt_opr == InvalidOid) /* shouldn't happen */
elog(ERROR, "cache lookup failed for ordering operator for type %u", elog(ERROR, "cache lookup failed for ordering operator for type %u",
colstat->attrtypid); typid);
/* prepare the sort function for this dimension */ /* prepare the sort function for this dimension */
multi_sort_add_dimension(mss, i, type->lt_opr, colstat->attrcollid); multi_sort_add_dimension(mss, i, type->lt_opr, collid);
/* accumulate all the data for this dimension into the arrays */ /* accumulate all the data for this dimension into the arrays */
for (j = 0; j < numrows; j++) for (j = 0; j < numrows; j++)
{ {
items[j].values[i] = items[j].values[i] = data->values[combination[i]][j];
heap_getattr(rows[j], items[j].isnull[i] = data->nulls[combination[i]][j];
colstat->attr->attnum,
colstat->tupDesc,
&items[j].isnull[i]);
} }
} }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 202103265 #define CATALOG_VERSION_NO 202103266
#endif #endif
This diff is collapsed.
This diff is collapsed.
...@@ -38,6 +38,7 @@ CATALOG(pg_statistic_ext_data,3429,StatisticExtDataRelationId) ...@@ -38,6 +38,7 @@ CATALOG(pg_statistic_ext_data,3429,StatisticExtDataRelationId)
pg_ndistinct stxdndistinct; /* ndistinct coefficients (serialized) */ pg_ndistinct stxdndistinct; /* ndistinct coefficients (serialized) */
pg_dependencies stxddependencies; /* dependencies (serialized) */ pg_dependencies stxddependencies; /* dependencies (serialized) */
pg_mcv_list stxdmcv; /* MCV (serialized) */ pg_mcv_list stxdmcv; /* MCV (serialized) */
pg_statistic stxdexpr[1]; /* stats for expressions */
#endif #endif
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment