Skip to main content

rupg_catalog/
tsearch.rs

1//! The objects of text search: the templates of `pg_ts_template`, the parsers of `pg_ts_parser`, the dictionaries of `pg_ts_dict`, and the configurations of `pg_ts_config` with their rows of `pg_ts_config_map`. They change as `DefineTSTemplate`, `DefineTSParser`, `DefineTSDictionary`, `DefineTSConfiguration` and `AlterTSConfiguration` of `tsearchcmds.c` change them, with the same rows of `pg_depend`.
2
3use rupg_common::{Error, Object, Result, SqlState};
4
5use super::{Catalog, DepKind, ObjRef, PG_NAMESPACE, PG_PROC};
6
7/// The OID of `pg_ts_template`.
8pub const PG_TS_TEMPLATE: u32 = 3764;
9/// The OID of `pg_ts_dict`.
10pub const PG_TS_DICT: u32 = 3600;
11/// The OID of `pg_ts_parser`.
12pub const PG_TS_PARSER: u32 = 3601;
13/// The OID of `pg_ts_config`.
14pub const PG_TS_CONFIG: u32 = 3602;
15
16/// A text search template, one row of `pg_ts_template`.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct TsTemplate {
19    /// The OID. [`Catalog::create_ts_template`] sets it.
20    pub oid: u32,
21    /// The name.
22    pub name: String,
23    /// The OID of the schema.
24    pub namespace: u32,
25    /// `tmplinit`: the OID of the init function, or 0.
26    pub init: u32,
27    /// `tmpllexize`: the OID of the lexize function.
28    pub lexize: u32,
29}
30
31/// A text search parser, one row of `pg_ts_parser`.
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct TsParser {
34    /// The OID. [`Catalog::create_ts_parser`] sets it.
35    pub oid: u32,
36    /// The name.
37    pub name: String,
38    /// The OID of the schema.
39    pub namespace: u32,
40    /// `prsstart`: the OID of the start function.
41    pub start: u32,
42    /// `prstoken`: the OID of the function that gives the next token.
43    pub token: u32,
44    /// `prsend`: the OID of the end function.
45    pub end: u32,
46    /// `prsheadline`: the OID of the headline function, or 0.
47    pub headline: u32,
48    /// `prslextype`: the OID of the function that gives the token types.
49    pub lextype: u32,
50}
51
52/// A text search dictionary, one row of `pg_ts_dict`.
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct TsDictionary {
55    /// The OID. [`Catalog::create_ts_dictionary`] sets it.
56    pub oid: u32,
57    /// The name.
58    pub name: String,
59    /// The OID of the schema.
60    pub namespace: u32,
61    /// The OID of the owner role.
62    pub owner: u32,
63    /// `dicttemplate`: the OID of the template.
64    pub template: u32,
65    /// `dictinitoption`: the options in the form of `serialize_deflist`, or `None` for no option.
66    pub options: Option<String>,
67}
68
69/// A text search configuration, one row of `pg_ts_config`.
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct TsConfig {
72    /// The OID. [`Catalog::create_ts_config`] sets it.
73    pub oid: u32,
74    /// The name.
75    pub name: String,
76    /// The OID of the schema.
77    pub namespace: u32,
78    /// The OID of the owner role.
79    pub owner: u32,
80    /// `cfgparser`: the OID of the parser.
81    pub parser: u32,
82}
83
84/// One row of `pg_ts_config_map`: for a token type of a configuration, the dictionary with this place in the list.
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub struct TsMapping {
87    /// `mapcfg`: the OID of the configuration.
88    pub config: u32,
89    /// `maptokentype`: the number of the token type.
90    pub token: i32,
91    /// `mapseqno`: the place of the dictionary in the list of the token type, from 1.
92    pub seqno: i32,
93    /// `mapdict`: the OID of the dictionary.
94    pub dict: u32,
95}
96
97/// The error of the unique index on the name and the schema of a catalog of text search, such as `pg_ts_dict_dictname_index` on `dictname` and `dictnamespace`. The prefix of the columns is `prefix`.
98fn duplicate(catalog: &str, prefix: &str, name: &str, namespace: u32) -> Error {
99    Error::new(
100        SqlState::UNIQUE_VIOLATION,
101        format!("duplicate key value violates unique constraint \"{catalog}_{prefix}name_index\""),
102    )
103    .with_detail(format!(
104        "Key ({prefix}name, {prefix}namespace)=({name}, {namespace}) already exists."
105    ))
106    .with_object(Object::Schema, "pg_catalog")
107    .with_object(Object::Table, catalog)
108    .with_object(Object::Constraint, format!("{catalog}_{prefix}name_index"))
109}
110
111impl Catalog {
112    /// `DefineTSTemplate`: a new template, which gets the next OID. It depends on its schema, its lexize function and its init function.
113    ///
114    /// # Errors
115    ///
116    /// `23505` when the schema has a template with the name.
117    pub fn create_ts_template(&mut self, mut new: TsTemplate) -> Result<u32> {
118        let oid = self.new_oid();
119        if self.ts_template_by_name(new.namespace, &new.name).is_some() {
120            return Err(duplicate("pg_ts_template", "tmpl", &new.name, new.namespace));
121        }
122        new.oid = oid;
123        let mut refs =
124            vec![ObjRef::new(PG_NAMESPACE, new.namespace), ObjRef::new(PG_PROC, new.lexize)];
125        if new.init != 0 {
126            refs.push(ObjRef::new(PG_PROC, new.init));
127        }
128        self.depend_all(ObjRef::new(PG_TS_TEMPLATE, oid), refs);
129        self.ts_templates.insert(oid, new);
130        Ok(oid)
131    }
132
133    /// `DefineTSParser`: a new parser, which gets the next OID. It depends on its schema and its functions.
134    ///
135    /// # Errors
136    ///
137    /// `23505` when the schema has a parser with the name.
138    pub fn create_ts_parser(&mut self, mut new: TsParser) -> Result<u32> {
139        let oid = self.new_oid();
140        if self.ts_parser_by_name(new.namespace, &new.name).is_some() {
141            return Err(duplicate("pg_ts_parser", "prs", &new.name, new.namespace));
142        }
143        new.oid = oid;
144        let mut refs = vec![ObjRef::new(PG_NAMESPACE, new.namespace)];
145        for function in [new.start, new.token, new.end, new.lextype, new.headline] {
146            if function != 0 {
147                refs.push(ObjRef::new(PG_PROC, function));
148            }
149        }
150        self.depend_all(ObjRef::new(PG_TS_PARSER, oid), refs);
151        self.ts_parsers.insert(oid, new);
152        Ok(oid)
153    }
154
155    /// `DefineTSDictionary`: a new dictionary, which gets the next OID. It depends on its schema and on its template.
156    ///
157    /// # Errors
158    ///
159    /// `23505` when the schema has a dictionary with the name.
160    pub fn create_ts_dictionary(&mut self, mut new: TsDictionary) -> Result<u32> {
161        let oid = self.new_oid();
162        if self.ts_dictionary_by_name(new.namespace, &new.name).is_some() {
163            return Err(duplicate("pg_ts_dict", "dict", &new.name, new.namespace));
164        }
165        new.oid = oid;
166        let refs = vec![
167            ObjRef::new(PG_NAMESPACE, new.namespace),
168            ObjRef::new(PG_TS_TEMPLATE, new.template),
169        ];
170        self.depend_all(ObjRef::new(PG_TS_DICT, oid), refs);
171        self.ts_dictionaries.insert(oid, new);
172        Ok(oid)
173    }
174
175    /// `DefineTSConfiguration`: a new configuration, which gets the next OID, with a copy of the rows of `pg_ts_config_map` of `copy`.
176    ///
177    /// # Errors
178    ///
179    /// `23505` when the schema has a configuration with the name.
180    pub fn create_ts_config(&mut self, mut new: TsConfig, copy: &[TsMapping]) -> Result<u32> {
181        let oid = self.new_oid();
182        if self.ts_config_by_name(new.namespace, &new.name).is_some() {
183            return Err(duplicate("pg_ts_config", "cfg", &new.name, new.namespace));
184        }
185        new.oid = oid;
186        self.ts_configs.insert(oid, new);
187        self.ts_mappings.extend(copy.iter().map(|m| TsMapping { config: oid, ..*m }));
188        self.ts_config_depends(oid);
189        Ok(oid)
190    }
191
192    /// `makeConfigurationDependencies`: the dependencies of a configuration on its schema, its parser and the dictionaries of its map. The old dependencies go first.
193    fn ts_config_depends(&mut self, oid: u32) {
194        let Some(config) = self.ts_configs.get(&oid) else { return };
195        let me = ObjRef::new(PG_TS_CONFIG, oid);
196        let mut refs = vec![
197            ObjRef::new(PG_NAMESPACE, config.namespace),
198            ObjRef::new(PG_TS_PARSER, config.parser),
199        ];
200        refs.extend(
201            self.ts_mappings
202                .iter()
203                .filter(|m| m.config == oid)
204                .map(|m| ObjRef::new(PG_TS_DICT, m.dict)),
205        );
206        self.depends.retain(|d| d.object != me || d.kind == DepKind::Extension);
207        self.depend_all(me, refs);
208    }
209
210    /// `record_object_address_dependencies`: the normal dependencies on the objects without duplicates, in the order of `object_address_comparator`. The OIDs go down, then the OIDs of the catalogs go up.
211    pub(crate) fn depend_all(&mut self, me: ObjRef, mut refs: Vec<ObjRef>) {
212        refs.sort_unstable_by(|a, b| {
213            b.oid.cmp(&a.oid).then(a.class.cmp(&b.class)).then(a.sub.cmp(&b.sub))
214        });
215        refs.dedup();
216        for referenced in refs {
217            self.depend(me, referenced, DepKind::Normal);
218        }
219    }
220
221    /// The `ADD MAPPING` and `ALTER MAPPING` parts of `MakeConfigurationMapping`. With `replace`, the rows of the token types go first. Then each token type gets the dictionaries in order.
222    pub fn add_ts_mapping(&mut self, config: u32, tokens: &[i32], dicts: &[u32], replace: bool) {
223        if replace {
224            self.ts_mappings.retain(|m| m.config != config || !tokens.contains(&m.token));
225        }
226        for &token in tokens {
227            for (seqno, &dict) in (1..).zip(dicts) {
228                self.ts_mappings.push(TsMapping { config, token, seqno, dict });
229            }
230        }
231        self.ts_config_depends(config);
232    }
233
234    /// The `ALTER MAPPING REPLACE` part of `MakeConfigurationMapping`: each row of the token types, or of all token types when `tokens` is empty, that has the dictionary `old` gets the dictionary `new`.
235    pub fn replace_ts_dictionary(&mut self, config: u32, tokens: &[i32], old: u32, new: u32) {
236        for m in &mut self.ts_mappings {
237            if m.config == config
238                && m.dict == old
239                && (tokens.is_empty() || tokens.contains(&m.token))
240            {
241                m.dict = new;
242            }
243        }
244        self.ts_config_depends(config);
245    }
246
247    /// `DropConfigurationMapping` for one token type: false when the configuration has no row for it.
248    pub fn drop_ts_mapping(&mut self, config: u32, token: i32) -> bool {
249        let before = self.ts_mappings.len();
250        self.ts_mappings.retain(|m| m.config != config || m.token != token);
251        self.ts_mappings.len() != before
252    }
253
254    /// Updates the dependencies of a configuration after [`Catalog::drop_ts_mapping`].
255    pub fn update_ts_config_depends(&mut self, config: u32) {
256        self.ts_config_depends(config);
257    }
258
259    /// The templates, in the order of their OIDs.
260    pub fn ts_templates(&self) -> impl Iterator<Item = &TsTemplate> {
261        self.ts_templates.values()
262    }
263
264    /// The template with the OID.
265    pub fn ts_template(&self, oid: u32) -> Option<&TsTemplate> {
266        self.ts_templates.get(&oid)
267    }
268
269    /// The template with the name in the schema.
270    pub fn ts_template_by_name(&self, namespace: u32, name: &str) -> Option<&TsTemplate> {
271        self.ts_templates.values().find(|t| t.namespace == namespace && t.name == name)
272    }
273
274    /// The parsers, in the order of their OIDs.
275    pub fn ts_parsers(&self) -> impl Iterator<Item = &TsParser> {
276        self.ts_parsers.values()
277    }
278
279    /// The parser with the OID.
280    pub fn ts_parser(&self, oid: u32) -> Option<&TsParser> {
281        self.ts_parsers.get(&oid)
282    }
283
284    /// The parser with the name in the schema.
285    pub fn ts_parser_by_name(&self, namespace: u32, name: &str) -> Option<&TsParser> {
286        self.ts_parsers.values().find(|p| p.namespace == namespace && p.name == name)
287    }
288
289    /// The dictionaries, in the order of their OIDs.
290    pub fn ts_dictionaries(&self) -> impl Iterator<Item = &TsDictionary> {
291        self.ts_dictionaries.values()
292    }
293
294    /// The dictionary with the OID.
295    pub fn ts_dictionary(&self, oid: u32) -> Option<&TsDictionary> {
296        self.ts_dictionaries.get(&oid)
297    }
298
299    /// The dictionary with the name in the schema.
300    pub fn ts_dictionary_by_name(&self, namespace: u32, name: &str) -> Option<&TsDictionary> {
301        self.ts_dictionaries.values().find(|d| d.namespace == namespace && d.name == name)
302    }
303
304    /// The configurations, in the order of their OIDs.
305    pub fn ts_configs(&self) -> impl Iterator<Item = &TsConfig> {
306        self.ts_configs.values()
307    }
308
309    /// The configuration with the OID.
310    pub fn ts_config(&self, oid: u32) -> Option<&TsConfig> {
311        self.ts_configs.get(&oid)
312    }
313
314    /// The configuration with the name in the schema.
315    pub fn ts_config_by_name(&self, namespace: u32, name: &str) -> Option<&TsConfig> {
316        self.ts_configs.values().find(|c| c.namespace == namespace && c.name == name)
317    }
318
319    /// The rows of `pg_ts_config_map`, in the order that they were made.
320    pub fn ts_mappings(&self) -> &[TsMapping] {
321        &self.ts_mappings
322    }
323}