1use std::collections::BTreeMap;
7
8use serde_norway::{Mapping, Value as Yaml};
9
10use crate::body;
11use crate::doc::Doc;
12use crate::error::{OpysError, Result};
13use crate::frontmatter::Frontmatter;
14
15use super::{g_i64, g_str, IntoParam, Store};
16
17pub(crate) struct FullRow {
19 pub dkey: i64,
20 pub relpath: String,
21 pub orig_relpath: Option<String>,
22 pub orig_text: Option<String>,
23 #[allow(dead_code)] pub orig_mtime: Option<String>,
25 pub doc: Doc,
26}
27
28#[derive(Default)]
30struct Parts {
31 tags: Vec<(i64, String)>,
32 relations: Vec<(String, i64, String, String)>, fm_fields: Vec<(String, String)>, }
35
36impl Store {
37 pub fn doc(&mut self, dkey: i64) -> Result<Doc> {
39 let (_, rows) = self.select(
40 "SELECT dkey, id, status, created, updated, body, path FROM docs WHERE dkey = $1",
41 vec![dkey.into_param()],
42 )?;
43 let row = rows
44 .into_iter()
45 .next()
46 .ok_or_else(|| OpysError::Store(format!("no docs row for dkey {dkey}")))?;
47 let parts = self
48 .parts_for(Some(dkey))?
49 .remove(&dkey)
50 .unwrap_or_default();
51 self.assemble(&row, parts).map(|f| f.doc)
52 }
53
54 pub fn all_docs(&mut self) -> Result<Vec<(i64, Doc)>> {
56 Ok(self
57 .full_rows()?
58 .into_iter()
59 .map(|f| (f.dkey, f.doc))
60 .collect())
61 }
62
63 pub(crate) fn full_rows(&mut self) -> Result<Vec<FullRow>> {
65 let (_, rows) = self.select(
66 "SELECT dkey, id, status, created, updated, body, path, \
67 orig_path, orig_text, orig_mtime FROM docs ORDER BY dkey",
68 vec![],
69 )?;
70 let mut parts = self.parts_for(None)?;
71 rows.into_iter()
72 .map(|row| {
73 let dkey = g_i64(&row[0])
74 .ok_or_else(|| OpysError::Store("docs.dkey is not an integer".into()))?;
75 let p = parts.remove(&dkey).unwrap_or_default();
76 self.assemble(&row, p)
77 })
78 .collect()
79 }
80
81 fn parts_for(&mut self, dkey: Option<i64>) -> Result<BTreeMap<i64, Parts>> {
83 let mut out: BTreeMap<i64, Parts> = BTreeMap::new();
84 let (filter, params) = match dkey {
85 Some(k) => (" WHERE dkey = $1", vec![k.into_param()]),
86 None => ("", vec![]),
87 };
88
89 let (_, rows) = self.select(
90 &format!("SELECT dkey, seq, tag FROM tags{filter} ORDER BY dkey, seq"),
91 params.clone(),
92 )?;
93 for r in rows {
94 let (k, seq) = (need_i64(&r[0])?, need_i64(&r[1])?);
95 out.entry(k).or_default().tags.push((seq, need_str(&r[2])?));
96 }
97
98 let (_, rows) = self.select(
99 &format!(
100 "SELECT dkey, field, seq, ref_id, raw_value FROM relations{filter} \
101 ORDER BY dkey, seq"
102 ),
103 params.clone(),
104 )?;
105 for r in rows {
106 let k = need_i64(&r[0])?;
107 out.entry(k).or_default().relations.push((
108 need_str(&r[1])?,
109 need_i64(&r[2])?,
110 need_str(&r[3])?,
111 need_str(&r[4])?,
112 ));
113 }
114
115 let not_elem = if filter.is_empty() {
118 " WHERE value_yaml IS NOT NULL"
119 } else {
120 " AND value_yaml IS NOT NULL"
121 };
122 let (_, rows) = self.select(
123 &format!("SELECT dkey, key, value_yaml FROM fm_fields{filter}{not_elem} ORDER BY dkey"),
124 params,
125 )?;
126 for r in rows {
127 let k = need_i64(&r[0])?;
128 out.entry(k)
129 .or_default()
130 .fm_fields
131 .push((need_str(&r[1])?, need_str(&r[2])?));
132 }
133 Ok(out)
134 }
135
136 fn assemble(&self, row: &[gluesql::prelude::Value], parts: Parts) -> Result<FullRow> {
139 let dkey =
140 g_i64(&row[0]).ok_or_else(|| OpysError::Store("docs.dkey not integer".into()))?;
141 let mut map = Mapping::new();
142 for (i, key) in [(1, "id"), (2, "status"), (3, "created"), (4, "updated")] {
143 if let Some(s) = g_str(&row[i]) {
144 map.insert(Yaml::String(key.to_string()), Yaml::String(s));
145 }
146 }
147 if !parts.tags.is_empty() {
148 let mut tags = parts.tags;
149 tags.sort_by_key(|(seq, _)| *seq);
150 let seq = tags.into_iter().map(|(_, t)| Yaml::String(t)).collect();
151 map.insert(Yaml::String("tags".to_string()), Yaml::Sequence(seq));
152 }
153 for field in crate::refs::RELATION_FIELDS {
154 let mut entries: Vec<_> = parts.relations.iter().filter(|e| e.0 == field).collect();
155 if entries.is_empty() {
156 continue;
157 }
158 entries.sort_by_key(|e| e.1);
159 let mut m = Mapping::new();
160 for e in entries {
161 m.insert(Yaml::String(e.2.clone()), Yaml::String(e.3.clone()));
162 }
163 map.insert(Yaml::String(field.to_string()), Yaml::Mapping(m));
164 }
165 for (key, value_yaml) in parts.fm_fields {
166 let value: Yaml = serde_norway::from_str(&value_yaml)
167 .map_err(|e| OpysError::Store(format!("cannot parse stored field '{key}': {e}")))?;
168 map.insert(Yaml::String(key), value);
169 }
170
171 let body = need_str(&row[5])?;
172 let relpath = need_str(&row[6])?;
173 let title = body::title(&body);
174 Ok(FullRow {
175 dkey,
176 orig_relpath: row.get(7).and_then(g_str),
177 orig_text: row.get(8).and_then(g_str),
178 orig_mtime: row.get(9).and_then(g_str),
179 doc: Doc {
180 path: self.abspath(&relpath),
181 frontmatter: Frontmatter { map },
182 body,
183 title,
184 },
185 relpath,
186 })
187 }
188}
189
190fn need_str(v: &gluesql::prelude::Value) -> Result<String> {
191 g_str(v).ok_or_else(|| OpysError::Store(format!("expected TEXT value, got {v:?}")))
192}
193
194fn need_i64(v: &gluesql::prelude::Value) -> Result<i64> {
195 g_i64(v).ok_or_else(|| OpysError::Store(format!("expected INTEGER value, got {v:?}")))
196}