reifydb_routine/procedure/set/
config.rs1use std::str::FromStr;
5
6use reifydb_catalog::error::CatalogError;
7use reifydb_core::{interface::catalog::config::ConfigKey, value::column::columns::Columns};
8use reifydb_transaction::transaction::Transaction;
9use reifydb_type::{
10 error::Error as TypeError,
11 fragment::Fragment,
12 params::Params,
13 value::{Value, duration::Duration, r#type::Type},
14};
15
16use crate::procedure::{Procedure, context::ProcedureContext, error::ProcedureError};
17
18pub struct SetConfigProcedure;
22
23impl Default for SetConfigProcedure {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl SetConfigProcedure {
30 pub fn new() -> Self {
31 Self
32 }
33}
34
35impl Procedure for SetConfigProcedure {
36 fn call(&self, ctx: &ProcedureContext, tx: &mut Transaction<'_>) -> Result<Columns, ProcedureError> {
37 let (key, value) = match ctx.params {
38 Params::Positional(args) if args.len() == 2 => (args[0].clone(), args[1].clone()),
39 Params::Positional(args) => {
40 return Err(ProcedureError::ArityMismatch {
41 procedure: Fragment::internal("system::config::set"),
42 expected: 2,
43 actual: args.len(),
44 });
45 }
46 _ => {
47 return Err(ProcedureError::ArityMismatch {
48 procedure: Fragment::internal("system::config::set"),
49 expected: 2,
50 actual: 0,
51 });
52 }
53 };
54
55 let key_str = match &key {
56 Value::Utf8(s) => s.as_str().to_string(),
57 _ => {
58 return Err(ProcedureError::InvalidArgumentType {
59 procedure: Fragment::internal("system::config::set"),
60 argument_index: 0,
61 expected: vec![Type::Utf8],
62 actual: key.get_type(),
63 });
64 }
65 };
66
67 if matches!(value, Value::None { .. }) {
68 return Err(CatalogError::ConfigValueInvalid(key_str).into());
69 }
70
71 let config_key = match ConfigKey::from_str(&key_str) {
72 Ok(k) => k,
73 Err(_) => {
74 return Err(CatalogError::ConfigStorageKeyNotFound(key_str).into());
75 }
76 };
77
78 let coerced_value = coerce_config_value(config_key, value)
79 .map_err(|e| ProcedureError::Wrapped(Box::new(TypeError::from(*e))))?;
80
81 let value_clone = coerced_value.clone();
82
83 match tx {
84 Transaction::Admin(admin) => ctx.catalog.set_config(admin, config_key, coerced_value)?,
85 Transaction::Test(t) => ctx.catalog.set_config(t.inner, config_key, coerced_value)?,
86 _ => {
87 return Err(ProcedureError::ExecutionFailed {
88 procedure: Fragment::internal("system::config::set"),
89 reason: "must run in an admin transaction".to_string(),
90 });
91 }
92 }
93
94 Ok(Columns::single_row([("key", Value::Utf8(key_str)), ("value", value_clone)]))
95 }
96}
97
98fn coerce_config_value(key: ConfigKey, value: Value) -> Result<Value, Box<CatalogError>> {
99 let expected_types = key.expected_types();
100 if expected_types.contains(&value.get_type()) {
101 return Ok(value);
102 }
103
104 for expected in expected_types {
106 match expected {
107 Type::Uint8 => {
108 if let Some(v) = value.to_usize()
109 && v <= u64::MAX as usize
110 {
111 return Ok(Value::Uint8(v as u64));
112 }
113 }
114 Type::Uint4 => {
115 if let Some(v) = value.to_usize()
116 && v <= u32::MAX as usize
117 {
118 return Ok(Value::Uint4(v as u32));
119 }
120 }
121 Type::Uint2 => {
122 if let Some(v) = value.to_usize()
123 && v <= u16::MAX as usize
124 {
125 return Ok(Value::Uint2(v as u16));
126 }
127 }
128 Type::Uint1 => {
129 if let Some(v) = value.to_usize()
130 && v <= u8::MAX as usize
131 {
132 return Ok(Value::Uint1(v as u8));
133 }
134 }
135 Type::Int8 => {
136 if let Some(v) = value.to_usize()
137 && v <= i64::MAX as usize
138 {
139 return Ok(Value::Int8(v as i64));
140 }
141 }
142 Type::Int4 => {
143 if let Some(v) = value.to_usize()
144 && v <= i32::MAX as usize
145 {
146 return Ok(Value::Int4(v as i32));
147 }
148 }
149 Type::Int2 => {
150 if let Some(v) = value.to_usize()
151 && v <= i16::MAX as usize
152 {
153 return Ok(Value::Int2(v as i16));
154 }
155 }
156 Type::Int1 => {
157 if let Some(v) = value.to_usize()
158 && v <= i8::MAX as usize
159 {
160 return Ok(Value::Int1(v as i8));
161 }
162 }
163 Type::Duration => {
164 if let Value::Duration(v) = value {
165 return Ok(Value::Duration(v));
166 }
167 if let Some(v) = value.to_usize()
168 && let Ok(d) = Duration::from_seconds(v as i64)
169 {
170 return Ok(Value::Duration(d));
171 }
172 }
173 _ => {}
174 }
175 }
176
177 Err(Box::new(CatalogError::ConfigTypeMismatch {
178 key: key.to_string(),
179 expected: expected_types.to_vec(),
180 actual: value.get_type(),
181 }))
182}