1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//! v7.38.18 (S10) — `ANALYZE` could not see a table created earlier in
//! the same simple-query string.
//!
//! CREATE TABLE t (k INT); INSERT INTO t VALUES (1); ANALYZE t;
//!
//! sent as ONE string answered `relation "t" does not exist`, while the
//! INSERT in that same string had just succeeded and PostgreSQL 18.4
//! answers `ANALYZE`.
//!
//! A multi-statement simple query is an implicit transaction, so the new
//! table lives in the transaction's shadow catalog; `exec_analyze` read
//! the COMMITTED catalog alone, in four places.
//!
//! Seven other statement kinds in that position were already right —
//! SELECT, UPDATE, DELETE, CREATE INDEX, ALTER TABLE, TRUNCATE, DROP —
//! which is why it looked like a quirk of ANALYZE rather than a class.
//! Measured for each of the eight, with a FRESH table name per case:
//! reusing one name hid the defect completely, because the second run
//! found the table already committed.
//!
//! In-process, an `Engine::execute` call is one statement, so this test
//! is where the shape can be reproduced at all.
use spg_engine::{Engine, QueryResult};
fn count(e: &mut Engine, sql: &str) -> i64 {
match e
.execute(sql)
.unwrap_or_else(|err| panic!("{sql}: {err:?}"))
{
QueryResult::Rows { rows, .. } => match rows.first().map(|r| r.values[0].clone()) {
Some(spg_storage::Value::BigInt(n)) => n,
Some(spg_storage::Value::Int(n)) => i64::from(n),
other => panic!("{sql}: {other:?}"),
},
other => panic!("{sql}: {other:?}"),
}
}
/// The reported shape: inside an explicit transaction, which is what a
/// multi-statement simple query becomes.
#[test]
fn analyze_sees_a_table_created_in_the_same_transaction() {
let mut e = Engine::new();
e.execute("BEGIN").unwrap();
e.execute("CREATE TABLE fresh (k INT, s TEXT)").unwrap();
e.execute("INSERT INTO fresh VALUES (1,'a'),(2,'b')")
.unwrap();
// This is the call that answered `relation "fresh" does not exist`.
e.execute("ANALYZE fresh")
.expect("ANALYZE must see a table created in this transaction");
// And it must have done the WORK. "It returned OK" is not evidence:
// two columns analysed is two rows of statistics.
assert_eq!(
count(
&mut e,
"SELECT count(*) FROM pg_statistic WHERE starelid = \
(SELECT oid FROM pg_class WHERE relname = 'fresh')"
),
2,
"ANALYZE returned OK without populating anything"
);
e.execute("COMMIT").unwrap();
}
/// A bare `ANALYZE` covers the tables this transaction created too — the
/// same defect at the site that lists them rather than the one that
/// checks a name.
#[test]
fn bare_analyze_covers_a_table_created_in_the_same_transaction() {
let mut e = Engine::new();
e.execute("BEGIN").unwrap();
e.execute("CREATE TABLE bare_fresh (k INT)").unwrap();
e.execute("INSERT INTO bare_fresh VALUES (1)").unwrap();
e.execute("ANALYZE").unwrap();
assert_eq!(
count(
&mut e,
"SELECT count(*) FROM pg_statistic WHERE starelid = \
(SELECT oid FROM pg_class WHERE relname = 'bare_fresh')"
),
1,
"a bare ANALYZE skipped a table this transaction created"
);
e.execute("COMMIT").unwrap();
}