opys_engine/store/
projection.rs1use futures::executor::block_on;
12use gluesql::core::ast::Statement;
13use gluesql::prelude::Payload;
14use serde_json::Value as Json;
15
16use crate::commands::stats::{
17 cell, json_scalar, section_json, structured_section_json, yaml_to_json,
18};
19use crate::error::Result;
20use crate::project_config::{ProjectConfig, SectionKind};
21
22use super::{IntoParam, Store};
23
24impl Store {
25 pub fn refresh_projections(&mut self, pcfg: &ProjectConfig) -> Result<()> {
30 self.exec("DELETE FROM fields", vec![])?;
31 self.exec("DELETE FROM sections", vec![])?;
32 self.exec("DELETE FROM blocks", vec![])?;
33
34 let docs = self.all_docs()?;
35 let mut field_rows = Vec::new();
36 let mut section_rows = Vec::new();
37 let mut block_rows = Vec::new();
38 for (_, d) in &docs {
39 let Some(id) = d.id().map(str::to_string) else {
40 continue;
41 };
42 let secs: Vec<(String, String)> = crate::body::sections(&d.body)
45 .into_iter()
46 .filter_map(|(h, t)| {
47 let t = t.trim().to_string();
48 (!(h.is_empty() && t.is_empty())).then_some((h, t))
49 })
50 .collect();
51 for (seq, (heading, text)) in secs.into_iter().enumerate() {
52 block_rows.push(vec![
53 id.clone().into_param(),
54 (seq as i64).into_param(),
55 heading.into_param(),
56 text.into_param(),
57 ]);
58 }
59 let Some(tname) = pcfg.type_name_for_id(&id) else {
60 continue;
61 };
62 let t = &pcfg.types[tname];
63 for fname in t.fields.keys() {
64 if let Some(v) = d.frontmatter.get(fname) {
65 match yaml_to_json(v) {
66 Json::Array(items) => {
67 for it in &items {
68 field_rows.push(vec![
69 id.clone().into_param(),
70 fname.clone().into_param(),
71 json_scalar(it).into_param(),
72 ]);
73 }
74 }
75 other => field_rows.push(vec![
76 id.clone().into_param(),
77 fname.clone().into_param(),
78 json_scalar(&other).into_param(),
79 ]),
80 }
81 }
82 }
83 for sec in &t.sections {
84 let s = if sec.kind == SectionKind::Structured {
85 structured_section_json(d, sec.structure.as_deref(), &sec.heading)
86 } else {
87 section_json(d, sec.kind, &sec.heading)
88 };
89 if let Some(s) = s {
90 section_rows.push(vec![
91 id.clone().into_param(),
92 sec.heading.clone().into_param(),
93 s["kind"].as_str().unwrap_or("").to_string().into_param(),
94 s["items"].as_i64().unwrap_or(0).into_param(),
95 s["unchecked"].as_i64().unwrap_or(0).into_param(),
96 ]);
97 }
98 }
99 }
100 self.insert_batch("fields", 3, field_rows)?;
101 self.insert_batch("sections", 5, section_rows)?;
102 self.insert_batch("blocks", 4, block_rows)?;
103 Ok(())
104 }
105
106 pub fn run_user_query(
111 &mut self,
112 sql: &str,
113 params: &[String],
114 ) -> std::result::Result<(Vec<String>, Vec<Vec<String>>), String> {
115 let bound: Vec<_> = params.iter().map(|p| p.clone().into_param()).collect();
116 let stmts = block_on(self.glue.plan_with_params(sql, bound))
117 .map_err(|e| format!("query failed ({e})"))?;
118 if stmts.is_empty() {
119 return Err("query produced no result set".to_string());
120 }
121 for s in &stmts {
122 if !matches!(s, Statement::Query(_)) {
123 return Err(format!("query must be a SELECT (got {})", stmt_kind(s)));
124 }
125 }
126 let mut last = None;
127 for s in &stmts {
128 last = Some(
129 block_on(self.glue.execute_stmt(s)).map_err(|e| format!("query failed ({e})"))?,
130 );
131 }
132 match last {
133 Some(Payload::Select { labels, rows }) => {
134 let rows = rows.iter().map(|r| r.iter().map(cell).collect()).collect();
135 Ok((labels, rows))
136 }
137 other => Err(format!("query produced no result set ({other:?})")),
138 }
139 }
140
141 pub fn run_user_write(
153 &mut self,
154 sql: &str,
155 params: &[String],
156 ) -> std::result::Result<String, String> {
157 let bound: Vec<_> = params.iter().map(|p| p.clone().into_param()).collect();
158 let stmts = block_on(self.glue.plan_with_params(sql, bound))
159 .map_err(|e| format!("statement failed ({e})"))?;
160 if stmts.is_empty() {
161 return Err("no statements to run".to_string());
162 }
163 for s in &stmts {
164 if !matches!(
165 s,
166 Statement::Insert { .. } | Statement::Update { .. } | Statement::Delete { .. }
167 ) {
168 return Err(format!(
169 "query --write allows only INSERT/UPDATE/DELETE (got {})",
170 stmt_kind(s)
171 ));
172 }
173 }
174 let mut payloads = Vec::with_capacity(stmts.len());
175 for s in &stmts {
176 payloads.push(
177 block_on(self.glue.execute_stmt(s))
178 .map_err(|e| format!("statement failed ({e})"))?,
179 );
180 }
181 let parts: Vec<String> = payloads
182 .iter()
183 .map(|p| match p {
184 Payload::Insert(n) => format!("{n} inserted"),
185 Payload::Update(n) => format!("{n} updated"),
186 Payload::Delete(n) => format!("{n} deleted"),
187 Payload::Select { rows, .. } => format!("{} selected", rows.len()),
188 _ => "ok".to_string(),
189 })
190 .collect();
191 Ok(parts.join(", "))
192 }
193}
194
195fn stmt_kind(s: &Statement) -> &'static str {
197 match s {
198 Statement::Insert { .. } => "INSERT",
199 Statement::Update { .. } => "UPDATE",
200 Statement::Delete { .. } => "DELETE",
201 Statement::CreateTable { .. } => "CREATE TABLE",
202 Statement::DropTable { .. } => "DROP TABLE",
203 Statement::AlterTable { .. } => "ALTER TABLE",
204 Statement::CreateIndex { .. } => "CREATE INDEX",
205 Statement::DropIndex { .. } => "DROP INDEX",
206 Statement::Query(_) => "SELECT",
207 _ => "an unsupported statement",
208 }
209}