Skip to main content

rupg_func/
sequence.rs

1//! The sequence functions of `sequence.c`: `nextval`, `currval`, `lastval` and `setval`, with the cache of each session, which is `SeqTable` of PostgreSQL.
2//!
3//! The row of the file of a sequence is not in a transaction. The session changes it with [`Session::change_sequence`], so a value that a transaction fetched stays used after a rollback, as in PostgreSQL. rupg keeps no WAL and makes no checkpoints, so `log_cnt` follows the rules of `nextval_internal` for a server without a checkpoint.
4
5use std::collections::HashMap;
6
7use rupg_catalog::{RelKind, SequenceData, SequenceInfo};
8use rupg_common::{Error, Result, SqlState};
9use rupg_types::Value;
10
11use crate::{Call, Kernel, Session, bad_value, not_yet, reg};
12
13/// `SEQ_LOG_VALS`: the number of values that `nextval` fetches ahead each time it writes a WAL record.
14const SEQ_LOG_VALS: i64 = 32;
15
16/// The cache of the sequences of a session, `seqhashtab` and `last_used_seq` of `sequence.c`.
17#[derive(Debug, Default)]
18pub struct Sequences {
19    table: HashMap<u32, Elm>,
20    /// The OID of the sequence of the last call of `nextval`.
21    last_used: Option<u32>,
22}
23
24/// An entry of the cache, `SeqTableData`.
25#[derive(Clone, Copy, Debug, Default)]
26struct Elm {
27    /// The file of the sequence when the session last read it. A new file drops the values of the cache.
28    file: u32,
29    /// True when `last` is the value of `currval`.
30    last_valid: bool,
31    /// The last value that `nextval` gave.
32    last: i64,
33    /// The last value that the session fetched into its cache.
34    cached: i64,
35    /// The increment of the sequence when the session fetched the values.
36    increment: i64,
37}
38
39impl Sequences {
40    /// `ResetSequenceCaches`: `DISCARD SEQUENCES` and `DISCARD ALL` drop the cache and the state of `currval` and `lastval`.
41    pub fn discard(&mut self) {
42        *self = Sequences::default();
43    }
44
45    /// The part of `init_sequence` that finds the entry of the cache. When the file of the sequence changed, the values of the cache are dropped, but the state of `currval` stays.
46    fn init(&mut self, relid: u32, file: u32) -> &mut Elm {
47        let elm = self.table.entry(relid).or_default();
48        if elm.file != file {
49            elm.file = file;
50            elm.cached = elm.last;
51        }
52        elm
53    }
54}
55
56/// The OID argument at `index`.
57fn oid_arg(args: &[Value], index: usize) -> Result<u32> {
58    args.get(index).and_then(Value::as_oid).ok_or_else(bad_value)
59}
60
61/// The `bigint` argument at `index`.
62fn int8_arg(args: &[Value], index: usize) -> Result<i64> {
63    args.get(index).and_then(Value::as_i64).ok_or_else(bad_value)
64}
65
66/// The cache of the session, or `0A000` for a session with no server.
67fn local(session: &dyn Session) -> Result<&std::cell::RefCell<Sequences>> {
68    session.local_sequences().ok_or_else(|| not_yet("a sequence in a session with no server"))
69}
70
71/// `sequence_open`: the name and the `pg_sequence` row of a sequence. A relation of another kind is `42809`.
72fn open(relid: u32, session: &dyn Session) -> Result<(&str, &SequenceInfo)> {
73    let Some((name, _)) = reg::class_name(relid, session) else {
74        return Err(Error::internal(format!("could not open relation with OID {relid}")));
75    };
76    let info = session
77        .catalog()
78        .and_then(|c| c.relation(relid))
79        .filter(|r| r.kind == RelKind::Sequence)
80        .and_then(|r| r.sequence.as_ref());
81    match info {
82        Some(info) => Ok((name, info)),
83        None => {
84            Err(Error::new(SqlState::WRONG_OBJECT_TYPE, format!("\"{name}\" is not a sequence")))
85        }
86    }
87}
88
89/// The row of the file of a sequence: the row that the sessions changed, or the row of the file when it was made.
90pub(crate) fn row(info: &SequenceInfo, session: &dyn Session) -> SequenceData {
91    session.sequence_row(info.file).unwrap_or(info.data)
92}
93
94/// The check of `pg_class_aclcheck` for the privileges of `mask` on a sequence.
95fn check(relid: u32, name: &str, mask: u32, session: &dyn Session) -> Result<()> {
96    if crate::acl::sequence_allowed(relid, mask, session)? {
97        return Ok(());
98    }
99    Err(Error::new(
100        SqlState::INSUFFICIENT_PRIVILEGE,
101        format!("permission denied for sequence {name}"),
102    ))
103}
104
105/// `PreventCommandIfReadOnly`: a read-only transaction cannot change a sequence.
106fn check_writable(command: &str, session: &dyn Session) -> Result<()> {
107    if session.setting("transaction_read_only").as_deref() == Some("on") {
108        return Err(Error::new(
109            SqlState::READ_ONLY_SQL_TRANSACTION,
110            format!("cannot execute {command} in a read-only transaction"),
111        ));
112    }
113    Ok(())
114}
115
116/// `nextval_oid`: `nextval_internal` with the check of the privileges. The analyzer gives a second argument `true` for the default of an identity column, which is `NextValueExpr`: that call does not check the privileges.
117fn nextval(call: &Call<'_>, args: &[Value]) -> Result<Value> {
118    let relid = oid_arg(args, 0)?;
119    let session = call.session;
120    let (name, info) = open(relid, session)?;
121    let mut sequences = local(session)?.borrow_mut();
122    let elm = *sequences.init(relid, info.file);
123    if !matches!(args.get(1), Some(Value::Bool(true))) {
124        check(relid, name, crate::acl::USAGE_UPDATE, session)?;
125    }
126    check_writable("nextval()", session)?;
127    if elm.last != elm.cached {
128        let elm = sequences.init(relid, info.file);
129        elm.last += elm.increment;
130        let last = elm.last;
131        sequences.last_used = Some(relid);
132        return Ok(Value::Int8(last));
133    }
134    let (incby, maxv, minv, cache, cycle) =
135        (info.increment, info.max, info.min, info.cache, info.cycle);
136    let mut fetched = (0, 0);
137    session.change_sequence(info.file, info.data, &mut |seq| {
138        let (mut last, mut next, mut result) = (seq.last_value, seq.last_value, seq.last_value);
139        let mut fetch = cache;
140        let mut log = seq.log_cnt;
141        let mut rescnt = 0;
142        if !seq.is_called {
143            rescnt += 1;
144            fetch -= 1;
145        }
146        if log < fetch || !seq.is_called {
147            fetch += SEQ_LOG_VALS;
148            log = fetch;
149        }
150        while fetch > 0 {
151            if incby > 0 {
152                if (maxv >= 0 && next > maxv - incby) || (maxv < 0 && next + incby > maxv) {
153                    if rescnt > 0 {
154                        break;
155                    }
156                    if !cycle {
157                        return Err(Error::new(
158                            SqlState::SEQUENCE_GENERATOR_LIMIT_EXCEEDED,
159                            format!(
160                                "nextval: reached maximum value of sequence \"{name}\" ({maxv})"
161                            ),
162                        ));
163                    }
164                    next = minv;
165                } else {
166                    next += incby;
167                }
168            } else if (minv < 0 && next < minv - incby) || (minv >= 0 && next + incby < minv) {
169                if rescnt > 0 {
170                    break;
171                }
172                if !cycle {
173                    return Err(Error::new(
174                        SqlState::SEQUENCE_GENERATOR_LIMIT_EXCEEDED,
175                        format!("nextval: reached minimum value of sequence \"{name}\" ({minv})"),
176                    ));
177                }
178                next = maxv;
179            } else {
180                next += incby;
181            }
182            fetch -= 1;
183            if rescnt < cache {
184                log -= 1;
185                rescnt += 1;
186                last = next;
187                if rescnt == 1 {
188                    result = next;
189                }
190            }
191        }
192        log -= fetch;
193        *seq = SequenceData { last_value: last, log_cnt: log, is_called: true };
194        fetched = (result, last);
195        Ok(())
196    })?;
197    let elm = sequences.init(relid, info.file);
198    elm.increment = incby;
199    elm.last = fetched.0;
200    elm.cached = fetched.1;
201    elm.last_valid = true;
202    sequences.last_used = Some(relid);
203    Ok(Value::Int8(fetched.0))
204}
205
206/// `currval_oid`: the last value of `nextval` for the sequence in this session.
207fn currval(call: &Call<'_>, args: &[Value]) -> Result<Value> {
208    let relid = oid_arg(args, 0)?;
209    let session = call.session;
210    let (name, info) = open(relid, session)?;
211    let mut sequences = local(session)?.borrow_mut();
212    let elm = *sequences.init(relid, info.file);
213    check(relid, name, crate::acl::SELECT_USAGE, session)?;
214    if !elm.last_valid {
215        return Err(Error::new(
216            SqlState::OBJECT_NOT_IN_PREREQUISITE_STATE,
217            format!("currval of sequence \"{name}\" is not yet defined in this session"),
218        ));
219    }
220    Ok(Value::Int8(elm.last))
221}
222
223/// `lastval`: the last value of `nextval` for any sequence in this session. A sequence that was dropped after the call has no last value.
224fn lastval(call: &Call<'_>, _: &[Value]) -> Result<Value> {
225    let session = call.session;
226    let sequences = local(session)?.borrow();
227    let undefined = || {
228        Error::new(
229            SqlState::OBJECT_NOT_IN_PREREQUISITE_STATE,
230            "lastval is not yet defined in this session",
231        )
232    };
233    let relid = sequences.last_used.ok_or_else(undefined)?;
234    let Some((name, _)) = reg::class_name(relid, session) else {
235        return Err(undefined());
236    };
237    check(relid, name, crate::acl::SELECT_USAGE, session)?;
238    Ok(Value::Int8(sequences.table.get(&relid).map_or(0, |elm| elm.last)))
239}
240
241/// `SetSequence`: the next call of `nextval` gives the value after `next`, or `next` itself when `iscalled` is false.
242fn set_sequence(call: &Call<'_>, relid: u32, next: i64, iscalled: bool) -> Result<Value> {
243    let session = call.session;
244    let (name, info) = open(relid, session)?;
245    let mut sequences = local(session)?.borrow_mut();
246    sequences.init(relid, info.file);
247    check(relid, name, crate::acl::UPDATE, session)?;
248    check_writable("setval()", session)?;
249    if next < info.min || next > info.max {
250        return Err(Error::new(
251            SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
252            format!(
253                "setval: value {next} is out of bounds for sequence \"{name}\" ({}..{})",
254                info.min, info.max
255            ),
256        ));
257    }
258    let elm = sequences.init(relid, info.file);
259    if iscalled {
260        elm.last = next;
261        elm.last_valid = true;
262    }
263    elm.cached = elm.last;
264    session.change_sequence(info.file, info.data, &mut |seq| {
265        *seq = SequenceData { last_value: next, log_cnt: 0, is_called: iscalled };
266        Ok(())
267    })?;
268    Ok(Value::Int8(next))
269}
270
271/// `setval_oid`: `setval(regclass, bigint)`.
272fn setval(call: &Call<'_>, args: &[Value]) -> Result<Value> {
273    set_sequence(call, oid_arg(args, 0)?, int8_arg(args, 1)?, true)
274}
275
276/// `setval3_oid`: `setval(regclass, bigint, boolean)`, which `pg_dump` calls.
277fn setval3(call: &Call<'_>, args: &[Value]) -> Result<Value> {
278    let iscalled = match args.get(2) {
279        Some(Value::Bool(b)) => *b,
280        _ => return Err(bad_value()),
281    };
282    set_sequence(call, oid_arg(args, 0)?, int8_arg(args, 1)?, iscalled)
283}
284
285/// `pg_sequence_last_value(regclass)`: the last value of the file of a sequence when `is_called` is true, else null. The result is null also for a sequence on which the current user has no `SELECT` and no `USAGE`, for the view `pg_sequences`. The errors are those of `init_sequence`.
286pub(crate) fn last_value(call: &Call<'_>, args: &[Value]) -> Result<Value> {
287    let relid = oid_arg(args, 0)?;
288    let session = call.session;
289    let (_, info) = open(relid, session)?;
290    if let Some(sequences) = session.local_sequences() {
291        sequences.borrow_mut().init(relid, info.file);
292    }
293    if !crate::acl::sequence_allowed(relid, crate::acl::SELECT_USAGE, session)? {
294        return Ok(Value::Null);
295    }
296    let seq = row(info, session);
297    Ok(if seq.is_called { Value::Int8(seq.last_value) } else { Value::Null })
298}
299
300/// The kernels of the sequence functions by `prosrc`.
301pub(crate) fn by_src(src: &str) -> Option<Kernel> {
302    let kernel: Kernel = match src {
303        "nextval_oid" => nextval,
304        "currval_oid" => currval,
305        "lastval" => lastval,
306        "setval_oid" => setval,
307        "setval3_oid" => setval3,
308        "pg_sequence_last_value" => last_value,
309        _ => return None,
310    };
311    Some(kernel)
312}