1use 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
13const SEQ_LOG_VALS: i64 = 32;
15
16#[derive(Debug, Default)]
18pub struct Sequences {
19 table: HashMap<u32, Elm>,
20 last_used: Option<u32>,
22}
23
24#[derive(Clone, Copy, Debug, Default)]
26struct Elm {
27 file: u32,
29 last_valid: bool,
31 last: i64,
33 cached: i64,
35 increment: i64,
37}
38
39impl Sequences {
40 pub fn discard(&mut self) {
42 *self = Sequences::default();
43 }
44
45 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
56fn oid_arg(args: &[Value], index: usize) -> Result<u32> {
58 args.get(index).and_then(Value::as_oid).ok_or_else(bad_value)
59}
60
61fn int8_arg(args: &[Value], index: usize) -> Result<i64> {
63 args.get(index).and_then(Value::as_i64).ok_or_else(bad_value)
64}
65
66fn 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
71fn 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
89pub(crate) fn row(info: &SequenceInfo, session: &dyn Session) -> SequenceData {
91 session.sequence_row(info.file).unwrap_or(info.data)
92}
93
94fn 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
105fn 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
116fn 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
206fn 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
223fn 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
241fn 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
271fn setval(call: &Call<'_>, args: &[Value]) -> Result<Value> {
273 set_sequence(call, oid_arg(args, 0)?, int8_arg(args, 1)?, true)
274}
275
276fn 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
285pub(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
300pub(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}