Skip to main content

dk_engine/graph/
types.rs

1use dk_core::{SymbolId, TypeInfo};
2use sqlx::postgres::PgPool;
3use uuid::Uuid;
4
5/// Intermediate row type for mapping between database rows and `TypeInfo`.
6#[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
27/// Parse a JSONB value containing `[["name","type"], ...]` into `Vec<(String, String)>`.
28fn 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
47/// Serialize `Vec<(String, String)>` into a JSONB-compatible `serde_json::Value`.
48fn 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/// PostgreSQL-backed store for type information attached to symbols.
63#[derive(Clone)]
64pub struct TypeInfoStore {
65    pool: PgPool,
66}
67
68impl TypeInfoStore {
69    /// Create a new `TypeInfoStore` backed by the given connection pool.
70    pub fn new(pool: PgPool) -> Self {
71        Self { pool }
72    }
73
74    /// Insert or update type information for a symbol.
75    ///
76    /// Uses `ON CONFLICT (symbol_id) DO UPDATE` so that re-parsing the same
77    /// file replaces old type information.
78    ///
79    /// `params` and `fields` are serialized as JSONB arrays of `["name","type"]` pairs.
80    /// `implements` is stored as a PostgreSQL `TEXT[]` array.
81    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(&params_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    /// Get type information for a symbol by its ID.
108    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    /// Delete type information for a symbol. Returns the number of rows deleted.
127    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}