pg_tviews 0.1.0-beta.27

Transactional materialized views with incremental refresh for PostgreSQL
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
//! Read-only collectors over expression trees.

use super::{
    FromDatum, HashMap, HashSet, Oid, cstr, elements, in_clause, pg_guard, pg_sys, strip_relabel,
    tag,
};

/// The columns `query` reads from each of its range table entries, by rtindex:
/// `Some(columns)`, or `None` when it reads the whole row. An entry it doesn't
/// read is absent. Vars of nested subqueries that point at this level count; a
/// reference through a join alias counts as one to the columns behind it.
///
/// SAFETY: `query` is a valid Query.
pub(super) unsafe fn referenced_columns(
    query: *mut pg_sys::Query,
) -> HashMap<usize, Option<HashSet<i16>>> {
    struct Refs {
        depth: u32,
        vars: Vec<(usize, i16)>,
    }
    #[pg_guard]
    unsafe extern "C-unwind" fn walker(
        node: *mut pg_sys::Node,
        ctx: *mut std::ffi::c_void,
    ) -> bool {
        // SAFETY: `ctx` is the Refs passed below; `node` is valid.
        unsafe {
            let refs = &mut *ctx.cast::<Refs>();
            match tag(node) {
                None => false,
                Some(pg_sys::NodeTag::T_Var) => {
                    let var = node.cast::<pg_sys::Var>();
                    if (*var).varlevelsup == refs.depth {
                        refs.vars
                            .push((super::index((*var).varno), (*var).varattno));
                    }
                    false
                }
                Some(pg_sys::NodeTag::T_Query) => {
                    refs.depth += 1;
                    let done = pg_sys::query_tree_walker(node.cast(), Some(walker), ctx, 0);
                    refs.depth -= 1;
                    done
                }
                Some(_) => pg_sys::expression_tree_walker(node, Some(walker), ctx),
            }
        }
    }
    let mut refs = Refs {
        depth: 0,
        vars: Vec::new(),
    };
    // Join aliases (and PostgreSQL 18 GROUP entries) are resolved below, not
    // walked: they name every column of the join.
    #[cfg(feature = "pg18")]
    let flags = pg_sys::QTW_IGNORE_JOINALIASES | pg_sys::QTW_IGNORE_GROUPEXPRS;
    #[cfg(not(feature = "pg18"))]
    let flags = pg_sys::QTW_IGNORE_JOINALIASES;
    // SAFETY: a read-only walk of a valid Query.
    unsafe {
        pg_sys::query_tree_walker(
            query,
            Some(walker),
            std::ptr::from_mut(&mut refs).cast(),
            flags.cast_signed(),
        );
    }
    // SAFETY: the range table of a valid Query.
    let rtable = unsafe { elements::<pg_sys::RangeTblEntry>((*query).rtable) };
    let mut read: HashMap<usize, Option<HashSet<i16>>> = HashMap::new();
    let mut pending = refs.vars;
    let mut seen: HashSet<(usize, i16)> = HashSet::new();
    while let Some((varno, attno)) = pending.pop() {
        if !seen.insert((varno, attno)) {
            continue;
        }
        let Some(&rte) = rtable.get(varno.wrapping_sub(1)) else {
            continue;
        };
        // SAFETY: fields of a valid RTE of this level.
        let behind = unsafe {
            match (*rte).rtekind {
                pg_sys::RTEKind::RTE_JOIN => Some((*rte).joinaliasvars),
                #[cfg(feature = "pg18")]
                pg_sys::RTEKind::RTE_GROUP => Some((*rte).groupexprs),
                _ => None,
            }
        };
        if let Some(list) = behind {
            // SAFETY: the alias expressions of the entry; Vars there point at
            // this level.
            let exprs = unsafe { elements::<pg_sys::Node>(list) };
            let chosen: Vec<*mut pg_sys::Node> = if attno == 0 {
                exprs
            } else {
                exprs
                    .get(usize::try_from(attno - 1).unwrap_or(usize::MAX))
                    .copied()
                    .into_iter()
                    .collect()
            };
            for expr in chosen {
                let mut vars = Vec::new();
                // SAFETY: a valid expression.
                unsafe { collect_vars(expr, &mut vars) };
                for var in vars {
                    // SAFETY: a Var collected above.
                    unsafe {
                        if (*var).varlevelsup == 0 {
                            pending.push((super::index((*var).varno), (*var).varattno));
                        }
                    }
                }
            }
            continue;
        }
        let entry = read.entry(varno).or_insert_with(|| Some(HashSet::new()));
        if attno == 0 {
            *entry = None;
        } else if let Some(columns) = entry {
            columns.insert(attno);
        }
    }
    read
}

/// A text constant (a `jsonb_build_object` key): its value.
///
/// SAFETY: `node` is null or a valid expression.
pub(super) unsafe fn const_text(node: *mut pg_sys::Node) -> Option<String> {
    // SAFETY: checked by tag before the cast; a non-null text datum.
    unsafe {
        let node = strip_relabel(node);
        if tag(node) != Some(pg_sys::NodeTag::T_Const) {
            return None;
        }
        let c = node.cast::<pg_sys::Const>();
        if (*c).constisnull {
            return None;
        }
        match (*c).consttype {
            pg_sys::TEXTOID | pg_sys::VARCHAROID => String::from_datum((*c).constvalue, false),
            pg_sys::UNKNOWNOID => Some(cstr((*c).constvalue.cast_mut_ptr())),
            _ => None,
        }
    }
}

/// How many times `query` reads each base column of its own level, `(rtindex,
/// attno)`, through join aliases (and PostgreSQL 18 group entries), in every
/// clause and subquery.
///
/// SAFETY: `query` is a valid Query.
pub(super) unsafe fn column_read_counts(
    query: *mut pg_sys::Query,
    skip_group_entries: bool,
) -> HashMap<(usize, i16), usize> {
    struct Refs {
        depth: u32,
        vars: Vec<*mut pg_sys::Var>,
    }
    #[pg_guard]
    unsafe extern "C-unwind" fn walker(
        node: *mut pg_sys::Node,
        ctx: *mut std::ffi::c_void,
    ) -> bool {
        // SAFETY: `ctx` is the Refs passed below; `node` is valid.
        unsafe {
            let refs = &mut *ctx.cast::<Refs>();
            match tag(node) {
                None => false,
                Some(pg_sys::NodeTag::T_Var) => {
                    let var = node.cast::<pg_sys::Var>();
                    if (*var).varlevelsup == refs.depth {
                        refs.vars.push(var);
                    }
                    false
                }
                Some(pg_sys::NodeTag::T_Query) => {
                    refs.depth += 1;
                    let done = pg_sys::query_tree_walker(node.cast(), Some(walker), ctx, 0);
                    refs.depth -= 1;
                    done
                }
                Some(_) => pg_sys::expression_tree_walker(node, Some(walker), ctx),
            }
        }
    }
    let mut refs = Refs {
        depth: 0,
        vars: Vec::new(),
    };
    #[cfg(feature = "pg18")]
    let flags = pg_sys::QTW_IGNORE_JOINALIASES | pg_sys::QTW_IGNORE_GROUPEXPRS;
    #[cfg(not(feature = "pg18"))]
    let flags = pg_sys::QTW_IGNORE_JOINALIASES;
    // SAFETY: a read-only walk of a valid Query.
    unsafe {
        pg_sys::query_tree_walker(
            query,
            Some(walker),
            std::ptr::from_mut(&mut refs).cast(),
            flags.cast_signed(),
        );
    }
    // The hidden target entries a GROUP BY adds only name the groups.
    let mut skipped: Vec<*mut pg_sys::Var> = Vec::new();
    if skip_group_entries {
        // SAFETY: the target list of a valid Query.
        unsafe {
            for tle in elements::<pg_sys::TargetEntry>((*query).targetList) {
                if (*tle).resjunk && in_clause((*tle).ressortgroupref, (*query).groupClause) {
                    collect_vars((*tle).expr.cast(), &mut skipped);
                }
            }
        }
    }
    let mut counts: HashMap<(usize, i16), usize> = HashMap::new();
    for var in refs.vars.into_iter().filter(|v| !skipped.contains(v)) {
        // SAFETY: a Var collected above, of `query`'s level as seen from its own.
        let (varno, attno) = unsafe { (super::index((*var).varno), (*var).varattno) };
        for read in base_reads(query, varno, attno) {
            *counts.entry(read).or_default() += 1;
        }
    }
    counts
}

/// Whether entry `rtindex` of `query`'s range table is a table (not a view, whose
/// other clauses may read the column too).
pub(super) fn relation_entry(query: *mut pg_sys::Query, rtindex: usize) -> bool {
    // SAFETY: the range table of a valid Query.
    unsafe {
        elements::<pg_sys::RangeTblEntry>((*query).rtable)
            .get(rtindex.wrapping_sub(1))
            .is_some_and(|rte| {
                (**rte).rtekind == pg_sys::RTEKind::RTE_RELATION
                    && matches!(
                        (**rte).relkind.cast_unsigned(),
                        pg_sys::RELKIND_RELATION | pg_sys::RELKIND_PARTITIONED_TABLE
                    )
            })
    }
}

/// The base column a Var of `query`'s own level reads, seen through join
/// aliases; `None` when it stands for several (or for none).
///
/// SAFETY: `var` is a valid Var.
pub(super) unsafe fn base_read(
    query: *mut pg_sys::Query,
    var: *mut pg_sys::Var,
) -> Option<(usize, i16)> {
    // SAFETY: fields of a valid Var.
    let (varno, attno) = unsafe { (super::index((*var).varno), (*var).varattno) };
    match base_reads(query, varno, attno)[..] {
        [read] => Some(read),
        _ => None,
    }
}

/// The base columns `(rtindex, attno)` of `query`'s level that `(varno, attno)`
/// stands for: itself, or what a join alias or group entry expands to.
pub(super) fn base_reads(query: *mut pg_sys::Query, varno: usize, attno: i16) -> Vec<(usize, i16)> {
    // SAFETY: the range table of a valid Query.
    let rtable = unsafe { elements::<pg_sys::RangeTblEntry>((*query).rtable) };
    let Some(&rte) = rtable.get(varno.wrapping_sub(1)) else {
        return Vec::new();
    };
    // SAFETY: fields of a valid RTE of this level.
    let behind = unsafe {
        match (*rte).rtekind {
            pg_sys::RTEKind::RTE_JOIN => Some((*rte).joinaliasvars),
            #[cfg(feature = "pg18")]
            pg_sys::RTEKind::RTE_GROUP => Some((*rte).groupexprs),
            _ => None,
        }
    };
    let Some(list) = behind else {
        return vec![(varno, attno)];
    };
    // SAFETY: the alias expressions of the entry; Vars there point at this level.
    let exprs = unsafe { elements::<pg_sys::Node>(list) };
    let chosen: Vec<*mut pg_sys::Node> = if attno <= 0 {
        exprs
    } else {
        exprs
            .get(usize::try_from(attno - 1).unwrap_or(usize::MAX))
            .copied()
            .into_iter()
            .collect()
    };
    let mut out = Vec::new();
    for expr in chosen {
        let mut vars = Vec::new();
        // SAFETY: a valid expression.
        unsafe { collect_vars(expr, &mut vars) };
        for var in vars {
            // SAFETY: a Var collected above.
            unsafe {
                if (*var).varlevelsup == 0 {
                    out.extend(base_reads(
                        query,
                        super::index((*var).varno),
                        (*var).varattno,
                    ));
                }
            }
        }
    }
    out
}

/// The Vars of an expression, in walk order, not descending into subqueries.
///
/// SAFETY: `node` is null or a valid expression.
pub(super) unsafe fn collect_vars(node: *mut pg_sys::Node, out: &mut Vec<*mut pg_sys::Var>) {
    #[pg_guard]
    unsafe extern "C-unwind" fn walker(
        node: *mut pg_sys::Node,
        ctx: *mut std::ffi::c_void,
    ) -> bool {
        // SAFETY: `ctx` is the Vec passed below; `node` is valid.
        unsafe {
            match tag(node) {
                None => false,
                Some(pg_sys::NodeTag::T_Var) => {
                    (*ctx.cast::<Vec<*mut pg_sys::Var>>()).push(node.cast());
                    false
                }
                Some(pg_sys::NodeTag::T_SubLink | pg_sys::NodeTag::T_Query) => false,
                Some(_) => pg_sys::expression_tree_walker(node, Some(walker), ctx),
            }
        }
    }
    // SAFETY: `out` outlives the walk.
    unsafe { walker(node, std::ptr::from_mut(out).cast()) };
}

/// The `PARAM_SUBLINK` Params of an expression, in walk order.
///
/// SAFETY: `node` is null or a valid expression.
pub(super) unsafe fn collect_params(node: *mut pg_sys::Node, out: &mut Vec<*mut pg_sys::Param>) {
    #[pg_guard]
    unsafe extern "C-unwind" fn walker(
        node: *mut pg_sys::Node,
        ctx: *mut std::ffi::c_void,
    ) -> bool {
        // SAFETY: `ctx` is the Vec passed below; `node` is valid.
        unsafe {
            match tag(node) {
                None => false,
                Some(pg_sys::NodeTag::T_Param) => {
                    (*ctx.cast::<Vec<*mut pg_sys::Param>>()).push(node.cast());
                    false
                }
                Some(pg_sys::NodeTag::T_SubLink | pg_sys::NodeTag::T_Query) => false,
                Some(_) => pg_sys::expression_tree_walker(node, Some(walker), ctx),
            }
        }
    }
    // SAFETY: `out` outlives the walk.
    unsafe { walker(node, std::ptr::from_mut(out).cast()) };
}

/// The `SubLink`s of an expression, not those nested inside them.
///
/// SAFETY: `node` is null or a valid expression.
pub(super) unsafe fn collect_sublinks(
    node: *mut pg_sys::Node,
    out: &mut Vec<*mut pg_sys::SubLink>,
) {
    #[pg_guard]
    unsafe extern "C-unwind" fn walker(
        node: *mut pg_sys::Node,
        ctx: *mut std::ffi::c_void,
    ) -> bool {
        // SAFETY: `ctx` is the Vec passed below; `node` is valid.
        unsafe {
            match tag(node) {
                None => false,
                Some(pg_sys::NodeTag::T_SubLink) => {
                    (*ctx.cast::<Vec<*mut pg_sys::SubLink>>()).push(node.cast());
                    false
                }
                Some(pg_sys::NodeTag::T_Query) => false,
                Some(_) => pg_sys::expression_tree_walker(node, Some(walker), ctx),
            }
        }
    }
    // SAFETY: `out` outlives the walk.
    unsafe { walker(node, std::ptr::from_mut(out).cast()) };
}

/// Whether an expression holds a `SubLink`.
///
/// SAFETY: `node` is null or a valid expression.
pub(super) unsafe fn has_sublink(node: *mut pg_sys::Node) -> bool {
    let mut found = Vec::new();
    // SAFETY: forwarded.
    unsafe { collect_sublinks(node, &mut found) };
    !found.is_empty()
}

/// The functions called by a query level's own expressions (`FuncExpr`, and the
/// functions of `RTE_FUNCTION` entries), not those of nested subqueries.
///
/// SAFETY: `node` is a valid Query.
pub(super) unsafe fn collect_functions(node: *mut pg_sys::Node, out: &mut Vec<Oid>) {
    #[pg_guard]
    unsafe extern "C-unwind" fn walker(
        node: *mut pg_sys::Node,
        ctx: *mut std::ffi::c_void,
    ) -> bool {
        // SAFETY: `ctx` is the Vec passed below; `node` is valid.
        unsafe {
            match tag(node) {
                None => false,
                Some(pg_sys::NodeTag::T_FuncExpr) => {
                    (*ctx.cast::<Vec<Oid>>()).push((*node.cast::<pg_sys::FuncExpr>()).funcid);
                    pg_sys::expression_tree_walker(node, Some(walker), ctx)
                }
                Some(pg_sys::NodeTag::T_SubLink | pg_sys::NodeTag::T_Query) => false,
                Some(_) => pg_sys::expression_tree_walker(node, Some(walker), ctx),
            }
        }
    }
    // SAFETY: walk the level's expressions; QTW flags skip subqueries and range
    // table subqueries (their own level records theirs).
    unsafe {
        pg_sys::query_tree_walker(
            node.cast(),
            Some(walker),
            std::ptr::from_mut(out).cast(),
            (pg_sys::QTW_IGNORE_RT_SUBQUERIES | pg_sys::QTW_IGNORE_CTE_SUBQUERIES).cast_signed(),
        );
    }
}