@@ -99,7 +99,7 @@ CREATE STATISTICS stat_wrong_col ON price FROM products WHERE category_id = 1
9999statement error pq: filter cannot be a contradiction
100100CREATE STATISTICS stat_contradiction ON price FROM products WHERE price < 10 AND price > 10
101101
102- statement error pq: table products does not contain a non-partial forward index with created_at as a prefix column
102+ statement error pq: table products does not contain a suitable index with created_at as a prefix column
103103CREATE STATISTICS stat_created_at ON created_at FROM products WHERE created_at > '2025-01-05'
104104
105105statement error column "nonexistent" does not exist
@@ -205,5 +205,91 @@ stats_from_table_id {price} 10 10 0 price
205205statement ok
206206CREATE TABLE infer (x INT NOT NULL, y INT NOT NULL AS (x % 5) STORED, PRIMARY KEY (y, x));
207207
208- statement error pq: table infer does not contain a non-partial forward index with x as a prefix column
208+ statement error pq: table infer does not contain a suitable index with x as a prefix column
209209CREATE STATISTICS inferred_stat ON x FROM infer WHERE x = 100;
210+
211+ # Test partial statistics with a WHERE clause on tables with partial indexes.
212+ statement ok
213+ CREATE TABLE partial_indexes (
214+ a INT,
215+ b INT,
216+ INDEX idx_partial (a) WHERE a > 5
217+ );
218+
219+ statement ok
220+ INSERT INTO partial_indexes SELECT g, g FROM generate_series(1, 10) AS g;
221+
222+ statement ok
223+ CREATE STATISTICS fullstat FROM partial_indexes;
224+
225+ # Clear the stat cache so that creating partial statistics has access to the
226+ # latest full statistic.
227+ statement ok
228+ SELECT crdb_internal.clear_table_stats_cache();
229+
230+ # Filter implies the partial index predicate, so it can be used.
231+ statement ok
232+ CREATE STATISTICS partial_stat_implied ON a FROM partial_indexes WHERE a > 7;
233+
234+ query TTIIIT colnames
235+ SELECT statistics_name, column_names, row_count, distinct_count, null_count, partial_predicate
236+ FROM [SHOW STATISTICS FOR TABLE partial_indexes]
237+ WHERE statistics_name = 'partial_stat_implied';
238+ ----
239+ statistics_name column_names row_count distinct_count null_count partial_predicate
240+ partial_stat_implied {a} 3 3 0 a > 7
241+
242+ # Filter equals partial index predicate, so it can be used.
243+ statement ok
244+ CREATE STATISTICS partial_stat_exact ON a FROM partial_indexes WHERE a > 5;
245+
246+ query TTIIIT colnames
247+ SELECT statistics_name, column_names, row_count, distinct_count, null_count, partial_predicate
248+ FROM [SHOW STATISTICS FOR TABLE partial_indexes]
249+ WHERE statistics_name = 'partial_stat_exact';
250+ ----
251+ statistics_name column_names row_count distinct_count null_count partial_predicate
252+ partial_stat_exact {a} 5 5 0 a > 5
253+
254+ # Filter does not imply the partial index predicate, so it can't be used.
255+ statement error pq: table partial_indexes does not contain a suitable index with a as a prefix column
256+ CREATE STATISTICS partial_stat_not_implied ON a FROM partial_indexes WHERE a > 3;
257+
258+ # Ensure that we use suitable full indexes when a partial index is not implied.
259+ statement ok
260+ CREATE INDEX idx_a ON partial_indexes (a);
261+
262+ statement ok
263+ CREATE STATISTICS partial_stat_not_implied ON a FROM partial_indexes WHERE a > 3;
264+
265+ query TTIIIT colnames
266+ SELECT statistics_name, column_names, row_count, distinct_count, null_count, partial_predicate
267+ FROM [SHOW STATISTICS FOR TABLE partial_indexes]
268+ WHERE statistics_name = 'partial_stat_not_implied';
269+ ----
270+ statistics_name column_names row_count distinct_count null_count partial_predicate
271+ partial_stat_not_implied {a} 7 7 0 a > 3
272+
273+ # Ensure that we only use partial indexes with the required prefix columns.
274+ statement ok
275+ DROP INDEX partial_indexes@idx_partial;
276+
277+ statement ok
278+ DROP INDEX partial_indexes@idx_a;
279+
280+ statement ok
281+ CREATE INDEX idx_b ON partial_indexes (b) WHERE a > 5;
282+
283+ statement error pq: table partial_indexes does not contain a suitable index with a as a prefix column
284+ CREATE STATISTICS partial_stat_implied ON a FROM partial_indexes WHERE a > 7;
285+
286+ statement ok
287+ DROP INDEX partial_indexes@idx_b;
288+
289+ # Ensure that we can't use an implied partial index when the filter doesn't
290+ # create a single constrained span.
291+ statement ok
292+ CREATE INDEX idx_b ON partial_indexes (b) WHERE b > 1 AND b % 2 = 0;
293+
294+ statement error pq: predicate could not become a constrained scan of an index
295+ CREATE STATISTICS partial_stat_implied ON b FROM partial_indexes WHERE b > 1 AND b % 2 = 0;
0 commit comments