1use rupg_common::{Error, Result, SqlState};
4use rupg_types::{Snapshot, Value};
5
6use crate::{Call, Kernel, bad_value, not_yet};
7
8const FIRST_NORMAL_XID: u32 = 3;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum XactState {
14 InProgress,
16 Committed,
18 Aborted,
20}
21
22pub(crate) fn by_src(src: &str) -> Option<Kernel> {
24 Some(match src {
25 "pg_snapshot_xmin" => snapshot_xmin,
26 "pg_snapshot_xmax" => snapshot_xmax,
27 "pg_visible_in_snapshot" => visible_in_snapshot,
28 "pg_current_xact_id" => current_xact_id,
29 "pg_current_xact_id_if_assigned" => current_xact_id_if_assigned,
30 "pg_current_snapshot" => current_snapshot,
31 "pg_xact_status" => xact_status,
32 "xid_age" => xid_age,
33 "mxid_age" => mxid_age,
34 _ => return None,
35 })
36}
37
38const FIRST_MULTI_XACT_ID: u32 = 1;
40
41fn xid_age(call: &Call<'_>, args: &[Value]) -> Result<Value> {
43 let Some(Value::Oid(xid)) = args.first() else { return Err(bad_value()) };
44 if *xid < FIRST_NORMAL_XID {
45 return Ok(Value::Int4(i32::MAX));
46 }
47 Ok(Value::Int4(call.session.stable_transaction_id().wrapping_sub(*xid).cast_signed()))
48}
49
50fn mxid_age(_: &Call<'_>, args: &[Value]) -> Result<Value> {
52 let Some(Value::Oid(xid)) = args.first() else { return Err(bad_value()) };
53 if *xid == 0 {
54 return Ok(Value::Int4(i32::MAX));
55 }
56 Ok(Value::Int4(FIRST_MULTI_XACT_ID.wrapping_sub(*xid).cast_signed()))
57}
58
59fn snapshot(args: &[Value], n: usize) -> Result<Snapshot> {
61 match args.get(n) {
62 Some(Value::Bytea(v)) => Snapshot::from_bytes(v).ok_or_else(bad_value),
63 _ => Err(bad_value()),
64 }
65}
66
67fn snapshot_xmin(_: &Call<'_>, args: &[Value]) -> Result<Value> {
68 Ok(Value::Int8(snapshot(args, 0)?.xmin.cast_signed()))
69}
70
71fn snapshot_xmax(_: &Call<'_>, args: &[Value]) -> Result<Value> {
72 Ok(Value::Int8(snapshot(args, 0)?.xmax.cast_signed()))
73}
74
75fn visible_in_snapshot(_: &Call<'_>, args: &[Value]) -> Result<Value> {
77 let Some(Value::Int8(xid)) = args.first() else { return Err(bad_value()) };
78 Ok(Value::Bool(snapshot(args, 1)?.is_visible(xid.cast_unsigned())))
79}
80
81pub(crate) fn snapshot_xip(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
83 let snapshot = snapshot(args, 0)?;
84 Ok(snapshot.xip.iter().map(|xid| vec![Value::Int8(xid.cast_signed())]).collect())
85}
86
87fn current_xact_id(call: &Call<'_>, _: &[Value]) -> Result<Value> {
89 let Some(xid) = call.session.transaction_id(true) else {
90 return Err(not_yet("a transaction ID in a session with no server"));
91 };
92 Ok(Value::Int8(xid.cast_signed()))
93}
94
95fn current_xact_id_if_assigned(call: &Call<'_>, _: &[Value]) -> Result<Value> {
97 Ok(call.session.transaction_id(false).map_or(Value::Null, |xid| Value::Int8(xid.cast_signed())))
98}
99
100fn current_snapshot(call: &Call<'_>, _: &[Value]) -> Result<Value> {
102 let Some(snapshot) = call.session.current_snapshot() else {
103 return Err(Error::internal("no active snapshot set"));
104 };
105 Ok(Value::Bytea(snapshot.to_bytes()))
106}
107
108fn xact_status(call: &Call<'_>, args: &[Value]) -> Result<Value> {
110 let Some(Value::Int8(full)) = args.first() else { return Err(bad_value()) };
111 let full = full.cast_unsigned();
112 let xid = u32::try_from(full & u64::from(u32::MAX)).unwrap_or_default();
113 if xid == 0 {
114 return Ok(Value::Null);
115 }
116 if xid < FIRST_NORMAL_XID {
117 return Ok(Value::Text("committed".into()));
118 }
119 if full >= call.session.next_transaction_id() {
120 return Err(Error::new(
121 SqlState::INVALID_PARAMETER_VALUE,
122 format!("transaction ID {full} is in the future"),
123 ));
124 }
125 Ok(match call.session.transaction_state(xid) {
126 None => Value::Null,
127 Some(XactState::InProgress) => Value::Text("in progress".into()),
128 Some(XactState::Committed) => Value::Text("committed".into()),
129 Some(XactState::Aborted) => Value::Text("aborted".into()),
130 })
131}