1use dk_core::{SymbolId, TypeInfo};
2use sqlx::postgres::PgPool;
3use uuid::Uuid;
4
5#[derive(sqlx::FromRow)]
7struct TypeInfoRow {
8 symbol_id: Uuid,
9 params: Option<serde_json::Value>,
10 return_type: Option<String>,
11 fields: Option<serde_json::Value>,
12 implements: Option<Vec<String>>,
13}
14
15impl TypeInfoRow {
16 fn into_type_info(self) -> TypeInfo {
17 TypeInfo {
18 symbol_id: self.symbol_id,
19 params: parse_string_pair_array(self.params),
20 return_type: self.return_type,
21 fields: parse_string_pair_array(self.fields),
22 implements: self.implements.unwrap_or_default(),
23 }
24 }
25}
26
27fn parse_string_pair_array(value: Option<serde_json::Value>) -> Vec<(String, String)> {
29 match value {
30 Some(serde_json::Value::Array(arr)) => arr
31 .into_iter()
32 .filter_map(|item| {
33 if let serde_json::Value::Array(pair) = item {
34 if pair.len() == 2 {
35 let a = pair[0].as_str()?.to_string();
36 let b = pair[1].as_str()?.to_string();
37 return Some((a, b));
38 }
39 }
40 None
41 })
42 .collect(),
43 _ => Vec::new(),
44 }
45}
46
47fn pairs_to_json(pairs: &[(String, String)]) -> serde_json::Value {
49 serde_json::Value::Array(
50 pairs
51 .iter()
52 .map(|(a, b)| {
53 serde_json::Value::Array(vec![
54 serde_json::Value::String(a.clone()),
55 serde_json::Value::String(b.clone()),
56 ])
57 })
58 .collect(),
59 )
60}
61
62#[derive(Clone)]
64pub struct TypeInfoStore {
65 pool: PgPool,
66}
67
68impl TypeInfoStore {
69 pub fn new(pool: PgPool) -> Self {
71 Self { pool }
72 }
73
74 pub async fn upsert_type_info(&self, info: &TypeInfo) -> dk_core::Result<()> {
82 let params_json = pairs_to_json(&info.params);
83 let fields_json = pairs_to_json(&info.fields);
84
85 sqlx::query(
86 r#"
87 INSERT INTO type_info (symbol_id, params, return_type, fields, implements)
88 VALUES ($1, $2, $3, $4, $5)
89 ON CONFLICT (symbol_id) DO UPDATE SET
90 params = EXCLUDED.params,
91 return_type = EXCLUDED.return_type,
92 fields = EXCLUDED.fields,
93 implements = EXCLUDED.implements
94 "#,
95 )
96 .bind(info.symbol_id)
97 .bind(¶ms_json)
98 .bind(&info.return_type)
99 .bind(&fields_json)
100 .bind(&info.implements)
101 .execute(&self.pool)
102 .await?;
103
104 Ok(())
105 }
106
107 pub async fn get_by_symbol_id(
109 &self,
110 symbol_id: SymbolId,
111 ) -> dk_core::Result<Option<TypeInfo>> {
112 let row = sqlx::query_as::<_, TypeInfoRow>(
113 r#"
114 SELECT symbol_id, params, return_type, fields, implements
115 FROM type_info
116 WHERE symbol_id = $1
117 "#,
118 )
119 .bind(symbol_id)
120 .fetch_optional(&self.pool)
121 .await?;
122
123 Ok(row.map(TypeInfoRow::into_type_info))
124 }
125
126 pub async fn delete_by_symbol_id(&self, symbol_id: SymbolId) -> dk_core::Result<u64> {
128 let result = sqlx::query("DELETE FROM type_info WHERE symbol_id = $1")
129 .bind(symbol_id)
130 .execute(&self.pool)
131 .await?;
132
133 Ok(result.rows_affected())
134 }
135}