Skip to main content

reifydb_routine/procedure/set/
config.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{str::FromStr, sync::LazyLock};
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, r#type::Type},
14};
15
16use crate::routine::{Routine, RoutineInfo, context::ProcedureContext, error::RoutineError};
17
18static INFO: LazyLock<RoutineInfo> = LazyLock::new(|| RoutineInfo::new("system::config::set"));
19
20/// Native procedure that sets a configuration value.
21///
22/// Accepts 2 positional arguments: key (Utf8) and value (any).
23pub struct SetConfigProcedure;
24
25impl Default for SetConfigProcedure {
26	fn default() -> Self {
27		Self::new()
28	}
29}
30
31impl SetConfigProcedure {
32	pub fn new() -> Self {
33		Self
34	}
35}
36
37impl<'a, 'tx> Routine<ProcedureContext<'a, 'tx>> for SetConfigProcedure {
38	fn info(&self) -> &RoutineInfo {
39		&INFO
40	}
41
42	fn return_type(&self, _input_types: &[Type]) -> Type {
43		Type::Any
44	}
45
46	fn execute(&self, ctx: &mut ProcedureContext<'a, 'tx>, _args: &Columns) -> Result<Columns, RoutineError> {
47		let (key, value) = match ctx.params {
48			Params::Positional(args) if args.len() == 2 => (args[0].clone(), args[1].clone()),
49			Params::Positional(args) => {
50				return Err(RoutineError::ProcedureArityMismatch {
51					procedure: Fragment::internal("system::config::set"),
52					expected: 2,
53					actual: args.len(),
54				});
55			}
56			_ => {
57				return Err(RoutineError::ProcedureArityMismatch {
58					procedure: Fragment::internal("system::config::set"),
59					expected: 2,
60					actual: 0,
61				});
62			}
63		};
64
65		let key_str = match &key {
66			Value::Utf8(s) => s.as_str().to_string(),
67			_ => {
68				return Err(RoutineError::ProcedureInvalidArgumentType {
69					procedure: Fragment::internal("system::config::set"),
70					argument_index: 0,
71					expected: vec![Type::Utf8],
72					actual: key.get_type(),
73				});
74			}
75		};
76
77		if matches!(value, Value::None { .. }) {
78			return Err(CatalogError::ConfigValueInvalid(key_str).into());
79		}
80
81		let config_key = match ConfigKey::from_str(&key_str) {
82			Ok(k) => k,
83			Err(_) => {
84				return Err(CatalogError::ConfigStorageKeyNotFound(key_str).into());
85			}
86		};
87
88		let coerced_value = config_key.accept(value).map_err(|e| {
89			RoutineError::Wrapped(Box::new(TypeError::from(CatalogError::from((config_key, e)))))
90		})?;
91
92		let value_clone = coerced_value.clone();
93
94		match ctx.tx {
95			Transaction::Admin(admin) => ctx.catalog.set_config(admin, config_key, coerced_value)?,
96			Transaction::Test(t) => ctx.catalog.set_config(t.inner, config_key, coerced_value)?,
97			_ => {
98				return Err(RoutineError::ProcedureExecutionFailed {
99					procedure: Fragment::internal("system::config::set"),
100					reason: "must run in an admin transaction".to_string(),
101				});
102			}
103		}
104
105		Ok(Columns::single_row([("key", Value::Utf8(key_str)), ("value", value_clone)]))
106	}
107}