rupg-analyze 0.1.84

Name resolution, type resolution, the query tree, the rewriter for views and rules.
Documentation
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
439
440
441
442
443
//! `transformIndirection` and `transformContainerSubscripts` of `parse_expr.c` and `parse_node.c`, with `array_subscript_transform` of `arraysubs.c`: the subscripts of an array, such as `a[1]` and `a[2:3]`.

use rupg_common::{Error, Result, SqlState};
use rupg_sql::nodes::{A_Indices, A_Indirection, Node};
use rupg_types::{MAXDIM, Value, oid};

use crate::Analyzer;
use crate::coerce::{AtOpt, Context};
use crate::expr::{Expr, ExprKind, FieldStore, Subscript};
use crate::types;

/// `array_subscript_handler`, the `typsubscript` of a true array type.
const ARRAY_SUBSCRIPT_HANDLER: u32 = 6179;
/// `raw_array_subscript_handler`, the `typsubscript` of a type of fixed length that is an array of its elements: `name`, `point`, `lseg`, `box` and `line`.
const RAW_ARRAY_SUBSCRIPT_HANDLER: u32 = 6180;

impl Analyzer<'_> {
    /// `transformIndirection`: the subscripts and the field selections after an expression. The subscripts before a field apply first. `.*` is not supported here.
    pub(crate) fn transform_indirection(&mut self, ind: &A_Indirection) -> Result<Expr> {
        self.indirection(ind.arg.as_ref(), &ind.indirection)
    }

    /// `ExpandIndirectionStar` and `ExpandRowReference`: the fields of `(expr).*` in a target list, with their names, or `None` when the indirection does not end with `*`. A whole-row reference gives the columns of its relation. Each other field is a field selection of its own copy of the expression, so the expression runs once for each field, as in PostgreSQL.
    ///
    /// # Errors
    ///
    /// `42809` for an expression of a type that is not composite, or of type `record` with fields that the analyzer cannot find.
    pub(crate) fn expand_indirection_star(
        &mut self,
        ind: &A_Indirection,
    ) -> Result<Option<Vec<(String, Expr)>>> {
        let Some((Some(Node::A_Star(_)), rest)) = ind.indirection.split_last() else {
            return Ok(None);
        };
        let expr = self.indirection(ind.arg.as_ref(), rest)?;
        let at = expr.place();
        if let ExprKind::Var(var) = expr.kind
            && var.attnum == 0
        {
            return self.whole_row_columns(var, at).map(Some);
        }
        if let ExprKind::JoinRow(row) = &expr.kind
            && row.alias.is_some()
        {
            return Ok(Some(row.names.iter().cloned().zip(row.fields.iter().cloned()).collect()));
        }
        self.composite_fields(expr).map(Some)
    }

    /// The fields of the composite value `expr`, each as a field selection of its own copy of `expr`.
    ///
    /// # Errors
    ///
    /// `42809` for an expression of a type that is not composite, or of type `record` with fields that the analyzer cannot find.
    pub(crate) fn composite_fields(&mut self, expr: Expr) -> Result<Vec<(String, Expr)>> {
        let ty = expr.ty;
        // `lookup_rowtype_tupdesc_domain`: a domain over a composite type has the fields of the type.
        let columns = if ty == oid::RECORD {
            self.record_columns(&expr)?
        } else {
            self.row_columns(types::base(ty))
        };
        let Some(columns) = columns else {
            let message = if ty == oid::RECORD {
                "record type has not been registered".to_string()
            } else {
                format!("type {} is not composite", types::name(ty))
            };
            return Err(Error::new(SqlState::WRONG_OBJECT_TYPE, message));
        };
        let fields = columns
            .into_iter()
            .enumerate()
            .map(|(i, column)| {
                let mut field =
                    Expr::new(ExprKind::FieldSelect(Box::new(expr.clone()), i), column.ty);
                field.typmod = column.typmod;
                (column.name, field)
            })
            .collect();
        Ok(fields)
    }

    /// The subscripts and the field selections of the items after the expression `arg`.
    fn indirection(&mut self, arg: Option<&Node>, items: &[Option<Node>]) -> Result<Expr> {
        let mut result = self.transform(arg)?;
        let location = result.place();
        let mut subscripts: Vec<&A_Indices> = Vec::new();
        for item in items {
            match item {
                Some(Node::A_Indices(indices)) => subscripts.push(indices),
                Some(Node::A_Star(_)) => {
                    return Err(Error::new(
                        SqlState::FEATURE_NOT_SUPPORTED,
                        "row expansion via \"*\" is not supported here",
                    )
                    .at_opt(location));
                }
                Some(Node::String(field)) => {
                    if !subscripts.is_empty() {
                        result = self.container_subscripts(result, &subscripts)?;
                        subscripts.clear();
                    }
                    result = self.field_select(result, field, location)?;
                }
                _ => return Err(Error::internal("unrecognized indirection")),
            }
        }
        if !subscripts.is_empty() {
            result = self.container_subscripts(result, &subscripts)?;
        }
        Ok(result)
    }

    /// `ParseComplexProjection` and `unknown_attribute`: the field of a row value with the name. A field of the whole row of a relation is the column of the relation. A value of type `record` has the fields that [`crate::record_fields`] finds. A name that is no field can be a function in the functional notation, which rupg does not have yet.
    pub(crate) fn field_select(
        &mut self,
        arg: Expr,
        name: &str,
        at: Option<usize>,
    ) -> Result<Expr> {
        if let ExprKind::Var(var) = arg.kind
            && var.attnum == 0
        {
            return self.whole_row_field(var, name, at);
        }
        if let ExprKind::JoinRow(row) = &arg.kind
            && let Some(alias) = &row.alias
        {
            let Some(i) = row.names.iter().position(|n| n == name) else {
                return Err(Error::new(
                    SqlState::UNDEFINED_COLUMN,
                    format!("column {alias}.{name} does not exist"),
                )
                .at_opt(at));
            };
            return Ok(row.fields[i].clone().at(at));
        }
        let ty = arg.ty;
        let columns = if ty == oid::RECORD {
            self.record_columns(&arg)?
        } else {
            self.row_columns(types::base(ty))
        };
        if let Some(columns) = &columns
            && let Some(i) = columns.iter().position(|c| c.name == name)
        {
            let mut expr = Expr::new(ExprKind::FieldSelect(Box::new(arg), i), columns[i].ty);
            expr.typmod = columns[i].typmod;
            return Ok(expr);
        }
        if rupg_pgcatalog::builtin::procs_named(name).any(|p| p.nargs == 1) {
            return Err(Error::new(
                SqlState::FEATURE_NOT_SUPPORTED,
                format!("the call of the function {name} as a field is not supported yet"),
            )
            .at_opt(at));
        }
        let (state, message) = match (columns, ty) {
            (_, oid::RECORD) => (
                SqlState::UNDEFINED_COLUMN,
                format!("could not identify column \"{name}\" in record data type"),
            ),
            (Some(_), _) => (
                SqlState::UNDEFINED_COLUMN,
                format!("column \"{name}\" not found in data type {}", types::name(ty)),
            ),
            (None, _) => (
                SqlState::WRONG_OBJECT_TYPE,
                format!(
                    "column notation .{name} applied to type {}, which is not a composite type",
                    types::name(ty)
                ),
            ),
        };
        Err(Error::new(state, message).at_opt(at))
    }

    /// `transformContainerSubscripts` for a fetch. A domain over an array subscripts its base type, and `int2vector` and `oidvector` are `int2[]` and `oid[]`. A `name` is an array of `"char"`, a `point` and a `line` are arrays of `float8`, and an `lseg` and a `box` are arrays of `point`. The result is the element, or an array for a slice, with the typmod of the container.
    fn container_subscripts(&mut self, container: Expr, subscripts: &[&A_Indices]) -> Result<Expr> {
        let (ty, typmod) = container_type(container.ty, container.typmod);
        let row = types::row(ty);
        let element = match row {
            Some(row) if row.subscript == ARRAY_SUBSCRIPT_HANDLER && row.elem != 0 => row.elem,
            Some(row) if row.subscript == RAW_ARRAY_SUBSCRIPT_HANDLER && row.elem != 0 => row.elem,
            Some(row) if row.subscript != 0 => {
                return Err(Error::new(
                    SqlState::FEATURE_NOT_SUPPORTED,
                    format!("subscripts of type {} are not supported yet", types::name(ty)),
                )
                .at_opt(container.place()));
            }
            _ => {
                return Err(Error::new(
                    SqlState::DATATYPE_MISMATCH,
                    format!(
                        "cannot subscript type {} because it does not support subscripting",
                        types::name(ty)
                    ),
                )
                .at_opt(container.place()));
            }
        };
        let slice = subscripts.iter().any(|s| s.is_slice);
        let mut upper = Vec::with_capacity(subscripts.len());
        let mut lower = Vec::with_capacity(if slice { subscripts.len() } else { 0 });
        for indices in subscripts {
            if slice {
                lower.push(match &indices.lidx {
                    Some(node) => Some(self.subscript_int4(node)?),
                    None if !indices.is_slice => {
                        Some(Expr::constant(Value::Int4(1), oid::INT4, -1, None))
                    }
                    None => None,
                });
            }
            upper.push(match &indices.uidx {
                Some(node) => Some(self.subscript_int4(node)?),
                None => None,
            });
        }
        if upper.len() > MAXDIM {
            return Err(Error::new(
                SqlState::PROGRAM_LIMIT_EXCEEDED,
                format!(
                    "number of array dimensions ({}) exceeds the maximum allowed ({MAXDIM})",
                    upper.len()
                ),
            ));
        }
        let result = if slice { ty } else { element };
        let kind = ExprKind::Subscript(Box::new(Subscript {
            container,
            upper,
            lower: slice.then_some(lower),
            assign: None,
        }));
        Ok(Expr { kind, ty: result, typmod, location: None })
    }

    /// `transformAssignmentIndirection`: the assignment of `rhs` to the part of `base` that the subscripts and the fields of `indirection` name, as an expression whose value is the whole value with the new part. `name` is the name of the target in the errors, and the type and the typmod are the type of `base`.
    pub(crate) fn assignment_indirection(
        &mut self,
        (base, name, target): (Expr, &str, (u32, i32)),
        indirection: &[Option<Node>],
        rhs: Expr,
        context: Context,
        at: Option<usize>,
    ) -> Result<Expr> {
        let assigning = Assigning { context, at };
        self.assign_indirect(Some(base), (name, false), target, indirection, rhs, &assigning)
    }

    /// `transformAssignmentIndirection` for the items of `indirection` after the first: with no `base`, `CaseTest` is the old value of the part. `subscripted` tells that the part is an element or a slice.
    fn assign_indirect(
        &mut self,
        base: Option<Expr>,
        (name, subscripted): (&str, bool),
        (ty, typmod): (u32, i32),
        indirection: &[Option<Node>],
        rhs: Expr,
        assigning: &Assigning,
    ) -> Result<Expr> {
        let at = assigning.at;
        // `CaseTest` is the old value of the part, which the executor gives.
        let base = base.unwrap_or_else(|| {
            let mut test = Expr::new(ExprKind::CaseTest, ty);
            test.typmod = typmod;
            test
        });
        let mut subscripts: Vec<&A_Indices> = Vec::new();
        for (i, item) in indirection.iter().enumerate() {
            let field = match item {
                Some(Node::A_Indices(indices)) => {
                    subscripts.push(indices);
                    continue;
                }
                Some(Node::A_Star(_)) => {
                    return Err(Error::new(
                        SqlState::FEATURE_NOT_SUPPORTED,
                        "row expansion via \"*\" is not supported here",
                    )
                    .at_opt(at));
                }
                Some(Node::String(field)) => &**field,
                _ => return Err(Error::internal("unrecognized indirection")),
            };
            let target = (name, (ty, typmod));
            if !subscripts.is_empty() {
                let rest = &indirection[i..];
                return self.assign_subscripts(base, target, &subscripts, rest, rhs, assigning);
            }
            let rest = &indirection[i + 1..];
            return self.assign_field(base, target, field, rest, rhs, assigning);
        }
        if !subscripts.is_empty() {
            let target = (name, (ty, typmod));
            return self.assign_subscripts(base, target, &subscripts, &[], rhs, assigning);
        }
        let source = rhs.ty;
        let rhs = self.coerce_to_target(rhs, ty, typmod, assigning.context, None)?;
        rhs.ok_or_else(|| {
            let message = if subscripted {
                format!(
                    "subscripted assignment to \"{name}\" requires type {} but expression is of type {}",
                    types::name(ty),
                    types::name(source)
                )
            } else {
                format!(
                    "subfield \"{name}\" is of type {} but expression is of type {}",
                    types::name(ty),
                    types::name(source)
                )
            };
            Error::new(SqlState::DATATYPE_MISMATCH, message)
                .with_hint("You will need to rewrite or cast the expression.")
                .at_opt(at)
        })
    }

    /// The assignment to the field `field` of the row value `base`, as a `FieldStore`. A domain over a row type checks the new row.
    fn assign_field(
        &mut self,
        base: Expr,
        (name, (ty, typmod)): (&str, (u32, i32)),
        field: &str,
        rest: &[Option<Node>],
        rhs: Expr,
        assigning: &Assigning,
    ) -> Result<Expr> {
        let at = assigning.at;
        let row = types::base(ty);
        let Some(columns) = self.row_columns(row) else {
            return Err(Error::new(
                SqlState::DATATYPE_MISMATCH,
                format!(
                    "cannot assign to field \"{field}\" of column \"{name}\" because its type {} is not a composite type",
                    types::name(ty)
                ),
            )
            .at_opt(at));
        };
        let Some(index) = columns.iter().position(|c| c.name == field) else {
            let message = if SYSTEM_COLUMNS.contains(&field) {
                format!("cannot assign to system column \"{field}\"")
            } else {
                format!(
                    "cannot assign to field \"{field}\" of column \"{name}\" because there is no such column in data type {}",
                    types::name(ty)
                )
            };
            return Err(Error::new(SqlState::UNDEFINED_COLUMN, message).at_opt(at));
        };
        let column = &columns[index];
        let target = (column.ty, column.typmod);
        let value = self.assign_indirect(None, (field, false), target, rest, rhs, assigning)?;
        let types = columns.iter().map(|c| c.ty).collect();
        let store = FieldStore { arg: base, fields: vec![(index, value)], types };
        let result = Expr::new(ExprKind::FieldStore(Box::new(store)), row);
        if row == ty {
            return Ok(result);
        }
        self.coerce_to_target(result, ty, typmod, Context::Implicit, None)?.ok_or_else(|| {
            Error::new(
                SqlState::CANNOT_COERCE,
                format!("cannot cast type {} to {}", types::name(row), types::name(ty)),
            )
            .at_opt(at)
        })
    }

    /// `transformAssignmentSubscripts`: the assignment to the element or the slice of `base` that `subscripts` name. The value takes the type of the element, or the type of the array for a slice, and the items of `rest` name a part of it.
    fn assign_subscripts(
        &mut self,
        base: Expr,
        (name, (ty, typmod)): (&str, (u32, i32)),
        subscripts: &[&A_Indices],
        rest: &[Option<Node>],
        rhs: Expr,
        assigning: &Assigning,
    ) -> Result<Expr> {
        let (container, container_mod) = container_type(ty, typmod);
        let mut result = self.container_subscripts(base, subscripts)?;
        let needed = (result.ty, result.typmod);
        let rhs = self.assign_indirect(None, (name, true), needed, rest, rhs, assigning)?;
        let ExprKind::Subscript(sub) = &mut result.kind else {
            return Err(Error::internal(
                "the subscripts of an assignment are not a SubscriptingRef",
            ));
        };
        sub.assign = Some(rhs);
        (result.ty, result.typmod) = (container, container_mod);
        if container == ty {
            return Ok(result);
        }
        // A domain over an array checks the new array.
        let context = assigning.context;
        self.coerce_to_target(result, ty, typmod, context, None)?.ok_or_else(|| {
            Error::new(
                SqlState::CANNOT_COERCE,
                format!("cannot cast type {} to {}", types::name(container), types::name(ty)),
            )
            .at_opt(assigning.at)
        })
    }

    /// A subscript, cast to `int4` as an assignment cast.
    fn subscript_int4(&mut self, node: &Node) -> Result<Expr> {
        let expr = self.transform(Some(node))?;
        let at = expr.place();
        self.coerce_to_target(expr, oid::INT4, -1, Context::Assignment, None)?.ok_or_else(|| {
            Error::new(SqlState::DATATYPE_MISMATCH, "array subscript must have type integer")
                .at_opt(at)
        })
    }
}

/// `transformContainerType`: the base type and the typmod of a domain, with `int2vector` as `int2[]` and `oidvector` as `oid[]`.
fn container_type(ty: u32, typmod: i32) -> (u32, i32) {
    let (mut ty, mut typmod) = (ty, typmod);
    while let Some(row) = types::row(ty)
        && row.kind == b'd'
        && row.base != 0
    {
        typmod = row.typmod;
        ty = row.base;
    }
    match ty {
        oid::INT2VECTOR => (oid::INT2_ARRAY, typmod),
        oid::OIDVECTOR => (oid::OID_ARRAY, typmod),
        _ => (ty, typmod),
    }
}

/// The names of the system columns, which an assignment cannot set.
const SYSTEM_COLUMNS: [&str; 6] = ["tableoid", "ctid", "xmin", "cmin", "xmax", "cmax"];

/// The parts of an assignment that stay the same in each step of `transformAssignmentIndirection`: the kind of the cast of the new value and the place of the target.
struct Assigning {
    context: Context,
    at: Option<usize>,
}