Skip to main content

rupg_analyze/
typename.rs

1//! The type of a `TypeName`, as `typenameTypeIdAndMod` of `parse_type.c` finds it: the OID from the name and the array bounds, and the typmod from the type modifiers and the `typmodin` function of the type.
2
3use rupg_common::{Error, Result, SqlState};
4use rupg_pgcatalog::builtin::{self, TypeRow};
5use rupg_sql::nodes::{Node, RangeVar, TypeName};
6use rupg_types::typmod::{
7    INTERVAL_FULL_PRECISION, INTERVAL_FULL_RANGE, MAX_TIME_PRECISION, VARHDRSZ, interval_typmod,
8    numeric_typmod,
9};
10
11use crate::coerce::AtOpt;
12use crate::{Analyzer, Env, Params};
13
14/// `MaxAttrSize`, the largest length of `varchar(n)` and `character(n)`.
15const MAX_ATTR_SIZE: i32 = 10 * 1024 * 1024;
16/// `NUMERIC_MAX_PRECISION`.
17const NUMERIC_MAX_PRECISION: i32 = 1000;
18/// `NUMERIC_MIN_SCALE` and `NUMERIC_MAX_SCALE`.
19const NUMERIC_SCALE: (i32, i32) = (-1000, 1000);
20
21/// The bits of the fields of an interval range, as `INTERVAL_MASK` gives them.
22const fn mask(bits: &[u8]) -> i32 {
23    let mut mask = 0;
24    let mut i = 0;
25    while i < bits.len() {
26        mask |= 1 << bits[i];
27        i += 1;
28    }
29    mask
30}
31
32/// The range masks that `intervaltypmodin` takes.
33const INTERVAL_RANGES: [i32; 14] = [
34    mask(&[2]),
35    mask(&[1]),
36    mask(&[3]),
37    mask(&[10]),
38    mask(&[11]),
39    mask(&[12]),
40    mask(&[2, 1]),
41    mask(&[3, 10]),
42    mask(&[3, 10, 11]),
43    mask(&[3, 10, 11, 12]),
44    mask(&[10, 11]),
45    mask(&[10, 11, 12]),
46    mask(&[11, 12]),
47    INTERVAL_FULL_RANGE,
48];
49
50/// `TypeNameToString`: the name as the query wrote it, for an error message.
51pub(crate) fn type_name_text(name: &TypeName) -> String {
52    let mut text = names(&name.names).join(".");
53    if name.pct_type {
54        text.push_str("%TYPE");
55    }
56    if !name.arrayBounds.is_empty() {
57        text.push_str("[]");
58    }
59    text
60}
61
62/// The strings of a list of `String` nodes.
63pub(crate) fn names(list: &[Option<Node>]) -> Vec<&str> {
64    list.iter()
65        .filter_map(|n| match n {
66            Some(Node::String(s)) => Some(&**s),
67            _ => None,
68        })
69        .collect()
70}
71
72/// The location of a node in the raw tree as a byte offset, or `None` for -1.
73pub(crate) fn place(location: i32) -> Option<usize> {
74    usize::try_from(location).ok()
75}
76
77/// The bytes that `scanner_isspace` takes as white space.
78fn is_space(b: u8) -> bool {
79    matches!(b, b' ' | b'\t' | b'\n' | b'\r' | 0x0b | 0x0c)
80}
81
82/// `parseTypeString`: the OID and the typmod of the type that a string names, as the input of `regtype` and `to_regtypemod` read it.
83///
84/// # Errors
85///
86/// The outer error is an error for any caller: a syntax error, `SETOF`, a name with too many dots and a bad type modifier. The inner error is a type or a schema that does not exist, which `to_regtype` gives as a null. No error has a place, except a syntax error, whose place is in `text`.
87pub fn parse_type(text: &str, env: &dyn Env) -> Result<Result<(u32, i32)>> {
88    let invalid = || Error::new(SqlState::SYNTAX_ERROR, format!("invalid type name \"{text}\""));
89    if text.bytes().all(is_space) {
90        return Err(invalid());
91    }
92    let name = rupg_sql::parse_type_name(text).map_err(|e| {
93        // `scanner_errposition` counts characters, and the session reads the place as a byte offset in the query text.
94        let at = e.location.map(|at| text[..at.min(text.len())].chars().count());
95        Error::from(e).with_position(at).with_context(format!("invalid type name \"{text}\""))
96    })?;
97    if name.setof {
98        return Err(invalid());
99    }
100    let mut analyzer = Analyzer::new(env, &Params::default());
101    match analyzer.type_name(&name) {
102        Ok(found) => Ok(Ok(found)),
103        Err(e) if e.state() == SqlState::UNDEFINED_SCHEMA => Ok(Err(e.with_position(None))),
104        Err(e) if e.state() == SqlState::UNDEFINED_OBJECT => Ok(Err(Error::new(
105            SqlState::UNDEFINED_OBJECT,
106            format!("type \"{}\" does not exist", type_name_text(&name)),
107        ))),
108        Err(e) => Err(e.with_position(None)),
109    }
110}
111
112/// The facts of a type that a type name reads.
113struct Found {
114    oid: u32,
115    /// The OID of the array type, or 0.
116    array: u32,
117    /// The OID of the `typmodin` function, or 0.
118    modin: u32,
119}
120
121impl Found {
122    fn builtin(row: &TypeRow) -> Found {
123        Found { oid: row.oid, array: row.array, modin: row.modin }
124    }
125
126    /// A row type or an array type of the catalog, which takes no type modifier.
127    fn user(ty: &rupg_catalog::Type) -> Found {
128        Found { oid: ty.oid, array: ty.array, modin: 0 }
129    }
130}
131
132impl Analyzer<'_> {
133    /// `LookupTypeNameExtended` for `%TYPE`: the type of the column that the name gives, with the typmod -1 and the notice of PostgreSQL.
134    fn column_type_of(&mut self, name: &TypeName) -> Result<(u32, i32)> {
135        let at = place(name.location);
136        let parts = names(&name.names);
137        let mut rv = RangeVar { location: name.location, ..RangeVar::default() };
138        let field = match parts[..] {
139            [_] => {
140                return Err(Error::new(
141                    SqlState::SYNTAX_ERROR,
142                    format!("improper %TYPE reference (too few dotted names): {}", parts.join(".")),
143                )
144                .at_opt(at));
145            }
146            [rel, field] => {
147                rv.relname = Some(rel.into());
148                field
149            }
150            [schema, rel, field] => {
151                rv.schemaname = Some(schema.into());
152                rv.relname = Some(rel.into());
153                field
154            }
155            [database, schema, rel, field] => {
156                rv.catalogname = Some(database.into());
157                rv.schemaname = Some(schema.into());
158                rv.relname = Some(rel.into());
159                field
160            }
161            _ => {
162                return Err(Error::new(
163                    SqlState::SYNTAX_ERROR,
164                    format!(
165                        "improper %TYPE reference (too many dotted names): {}",
166                        parts.join(".")
167                    ),
168                )
169                .at_opt(at));
170            }
171        };
172        let relname = rv.relname.as_deref().unwrap_or_default().to_owned();
173        let Some(relid) = self.lookup_relation(&rv)? else {
174            let shown = match &rv.schemaname {
175                Some(schema) => format!("{schema}.{relname}"),
176                None => relname,
177            };
178            return Err(Error::new(
179                SqlState::UNDEFINED_TABLE,
180                format!("relation \"{shown}\" does not exist"),
181            ));
182        };
183        let columns = self.open_relation(relid, &relname, at)?;
184        let Some(column) = columns.iter().find(|c| c.name == field) else {
185            return Err(Error::new(
186                SqlState::UNDEFINED_COLUMN,
187                format!("column \"{field}\" of relation \"{relname}\" does not exist"),
188            )
189            .at_opt(at));
190        };
191        self.notices.push(
192            Error::new(
193                SqlState::SUCCESSFUL_COMPLETION,
194                format!(
195                    "type reference {} converted to {}",
196                    type_name_text(name),
197                    crate::types::name(column.ty)
198                ),
199            )
200            .as_notice(),
201        );
202        Ok((column.ty, -1))
203    }
204
205    /// `typenameTypeIdAndMod`: the OID and the typmod of a type name.
206    pub(crate) fn type_name(&mut self, name: &TypeName) -> Result<(u32, i32)> {
207        let at = place(name.location);
208        if name.pct_type {
209            return self.column_type_of(name);
210        }
211        let mut row = if name.typeOid != 0 {
212            self.type_by_oid(name.typeOid)
213        } else {
214            self.lookup_type(&names(&name.names), at)?
215        };
216        if !name.arrayBounds.is_empty() {
217            // `get_array_type`: an array type has no array type, so the name finds no type.
218            row = row.and_then(|element| self.type_by_oid(element.array));
219        }
220        let Some(row) = row else {
221            return Err(Error::new(
222                SqlState::UNDEFINED_OBJECT,
223                format!("type \"{}\" does not exist", type_name_text(name)),
224            )
225            .at_opt(at));
226        };
227        let first = self.notices.len();
228        let typmod = self.type_modifier(name, &row).map_err(|e| e.at_opt(at))?;
229        for notice in &mut self.notices[first..] {
230            *notice = notice.clone().at_opt(at);
231        }
232        Ok((row.oid, typmod))
233    }
234
235    /// `LookupTypeName` for a statement that names a type as an object, such as `DROP TYPE`: the OID of the type, or `None` when the schema has no type with the name. The type modifiers do not count, and the errors have no position.
236    pub(crate) fn lookup_type_name(&self, name: &TypeName) -> Result<Option<u32>> {
237        let mut row = self.lookup_type(&names(&name.names), None)?;
238        if !name.arrayBounds.is_empty() {
239            row = row.and_then(|element| self.type_by_oid(element.array));
240        }
241        Ok(row.map(|row| row.oid))
242    }
243
244    /// The type with this OID, a built-in type or a type of the catalog.
245    fn type_by_oid(&self, oid: u32) -> Option<Found> {
246        if oid == 0 {
247            return None;
248        }
249        if let Some(row) = builtin::type_by_oid(oid) {
250            return Some(Found::builtin(row));
251        }
252        self.env.catalog()?.type_by_oid(oid).map(Found::user)
253    }
254
255    /// The type with this name in the schema.
256    fn type_in(&self, namespace: u32, name: &str) -> Option<Found> {
257        if let Some(row) = builtin::type_by_name(namespace, name) {
258            return Some(Found::builtin(row));
259        }
260        self.env.catalog()?.type_by_name(namespace, name).map(Found::user)
261    }
262
263    /// `LookupTypeName`: the type with this name, in the schema that the name gives or in the search path.
264    fn lookup_type(&self, names: &[&str], at: Option<usize>) -> Result<Option<Found>> {
265        match names {
266            [name] => Ok(self.temp_path.iter().find_map(|&ns| self.type_in(ns, name))),
267            [schema, name] => {
268                let ns = self.schema(schema).ok_or_else(|| {
269                    Error::new(
270                        SqlState::UNDEFINED_SCHEMA,
271                        format!("schema \"{schema}\" does not exist"),
272                    )
273                    .at_opt(at)
274                })?;
275                Ok(self.type_in(ns, name))
276            }
277            [catalog, schema, name] => {
278                if *catalog != self.env.database() {
279                    return Err(Error::new(
280                        SqlState::FEATURE_NOT_SUPPORTED,
281                        format!(
282                            "cross-database references are not implemented: {}",
283                            names.join(".")
284                        ),
285                    )
286                    .at_opt(at));
287                }
288                self.lookup_type(&[schema, name], at)
289            }
290            _ => Err(Error::new(
291                SqlState::SYNTAX_ERROR,
292                format!("improper qualified name (too many dotted names): {}", names.join(".")),
293            )
294            .at_opt(at)),
295        }
296    }
297
298    /// `typenameTypeMod`: the typmod from the type modifiers of the name, with the `typmodin` function of the type.
299    fn type_modifier(&mut self, name: &TypeName, row: &Found) -> Result<i32> {
300        if name.typemod != -1 {
301            return Ok(name.typemod);
302        }
303        if name.typmods.is_empty() {
304            return Ok(-1);
305        }
306        let modin = builtin::proc_by_oid(row.modin).map(|p| p.src);
307        let Some(modin) = modin else {
308            return Err(Error::new(
309                SqlState::SYNTAX_ERROR,
310                format!("type modifier is not allowed for type \"{}\"", type_name_text(name)),
311            ));
312        };
313        let mut texts = Vec::new();
314        for modifier in &name.typmods {
315            let text = match modifier {
316                Some(Node::A_Const(c)) => match &c.val {
317                    Some(Node::Integer(v)) => Some(v.to_string()),
318                    Some(Node::Float(v) | Node::String(v)) => Some(v.to_string()),
319                    _ => None,
320                },
321                Some(Node::ColumnRef(c)) => match c.fields.as_slice() {
322                    [Some(Node::String(s))] => Some(s.to_string()),
323                    _ => None,
324                },
325                _ => None,
326            };
327            let Some(text) = text else {
328                return Err(Error::new(
329                    SqlState::SYNTAX_ERROR,
330                    "type modifiers must be simple constants or identifiers",
331                ));
332            };
333            texts.push(text);
334        }
335        let mut values = Vec::with_capacity(texts.len());
336        for text in &texts {
337            // `ArrayGetIntegerTypmods` reads each modifier with `pg_strtoint32`.
338            let value = rupg_types::int4_in(text).map_err(Error::from)?;
339            values.push(value);
340        }
341        self.typmod_in(modin, &values)
342    }
343
344    /// The `typmodin` functions of the built-in types.
345    fn typmod_in(&mut self, modin: &str, values: &[i32]) -> Result<i32> {
346        let invalid = |what: &str| {
347            Error::new(SqlState::INVALID_PARAMETER_VALUE, format!("invalid {what}type modifier"))
348        };
349        match modin {
350            "varchartypmodin" | "bpchartypmodin" => {
351                let name = if modin == "varchartypmodin" { "varchar" } else { "char" };
352                let &[n] = values else { return Err(invalid("")) };
353                if n < 1 {
354                    return Err(Error::new(
355                        SqlState::INVALID_PARAMETER_VALUE,
356                        format!("length for type {name} must be at least 1"),
357                    ));
358                }
359                if n > MAX_ATTR_SIZE {
360                    return Err(Error::new(
361                        SqlState::INVALID_PARAMETER_VALUE,
362                        format!("length for type {name} cannot exceed {MAX_ATTR_SIZE}"),
363                    ));
364                }
365                Ok(VARHDRSZ + n)
366            }
367            "bittypmodin" | "varbittypmodin" => {
368                let name = if modin == "bittypmodin" { "bit" } else { "varbit" };
369                let &[n] = values else { return Err(invalid("")) };
370                if n < 1 {
371                    return Err(Error::new(
372                        SqlState::INVALID_PARAMETER_VALUE,
373                        format!("length for type {name} must be at least 1"),
374                    ));
375                }
376                if n > MAX_ATTR_SIZE * 8 {
377                    return Err(Error::new(
378                        SqlState::INVALID_PARAMETER_VALUE,
379                        format!("length for type {name} cannot exceed {}", MAX_ATTR_SIZE * 8),
380                    ));
381                }
382                Ok(n)
383            }
384            "numerictypmodin" => {
385                let (precision, scale) = match *values {
386                    [p] => (p, 0),
387                    [p, s] => (p, s),
388                    _ => return Err(invalid("NUMERIC ")),
389                };
390                if !(1..=NUMERIC_MAX_PRECISION).contains(&precision) {
391                    return Err(Error::new(
392                        SqlState::INVALID_PARAMETER_VALUE,
393                        format!(
394                            "NUMERIC precision {precision} must be between 1 and {NUMERIC_MAX_PRECISION}"
395                        ),
396                    ));
397                }
398                if !(NUMERIC_SCALE.0..=NUMERIC_SCALE.1).contains(&scale) {
399                    return Err(Error::new(
400                        SqlState::INVALID_PARAMETER_VALUE,
401                        format!(
402                            "NUMERIC scale {scale} must be between {} and {}",
403                            NUMERIC_SCALE.0, NUMERIC_SCALE.1
404                        ),
405                    ));
406                }
407                Ok(numeric_typmod(precision, scale))
408            }
409            "timetypmodin" | "timetztypmodin" | "timestamptypmodin" | "timestamptztypmodin" => {
410                let &[n] = values else { return Err(invalid("")) };
411                let what = if modin.starts_with("timestamp") { "TIMESTAMP" } else { "TIME" };
412                let tz = if modin.contains("tz") { " WITH TIME ZONE" } else { "" };
413                self.precision(n, &format!("{what}({n}){tz}"))
414            }
415            "intervaltypmodin" => {
416                if let Some(range) = values.first()
417                    && !INTERVAL_RANGES.contains(range)
418                {
419                    return Err(invalid("INTERVAL "));
420                }
421                match *values {
422                    [range] if range == INTERVAL_FULL_RANGE => Ok(-1),
423                    [range] => Ok(interval_typmod(INTERVAL_FULL_PRECISION, range)),
424                    [range, precision] => Ok(interval_typmod(
425                        self.precision(precision, &format!("INTERVAL({precision})"))?,
426                        range,
427                    )),
428                    _ => Err(invalid("INTERVAL ")),
429                }
430            }
431            _ => Err(Error::new(
432                SqlState::FEATURE_NOT_SUPPORTED,
433                format!("the type modifier function {modin} is not supported yet"),
434            )),
435        }
436    }
437
438    /// The check of a fractional second precision: an error below zero, and a warning and the largest precision above it.
439    pub(crate) fn precision(&mut self, n: i32, what: &str) -> Result<i32> {
440        if n < 0 {
441            return Err(Error::new(
442                SqlState::INVALID_PARAMETER_VALUE,
443                format!("{what} precision must not be negative"),
444            ));
445        }
446        if n > MAX_TIME_PRECISION {
447            self.notices.push(Error::new(
448                SqlState::INVALID_PARAMETER_VALUE,
449                format!("{what} precision reduced to maximum allowed, {MAX_TIME_PRECISION}"),
450            ));
451            return Ok(MAX_TIME_PRECISION);
452        }
453        Ok(n)
454    }
455}