xvc_pipeline/pipeline/deps/
sqlite_query.rs1use crate::XvcDependency;
5use fallible_iterator::FallibleIterator;
6use rusqlite::{Connection, OpenFlags};
7use serde::{Deserialize, Serialize};
8
9use crate::Result;
10use xvc_core::types::diff::Diffable;
11use xvc_core::{ContentDigest, Diff, HashAlgorithm, XvcDigest, XvcMetadata, XvcPath, XvcRoot};
12
13use xvc_core::persist;
14
15#[derive(Debug, PartialOrd, Ord, Clone, Eq, PartialEq, Serialize, Deserialize)]
17pub struct SqliteQueryDep {
18 pub path: XvcPath,
20 pub query: String,
22 pub query_digest: Option<ContentDigest>,
24 pub xvc_metadata: Option<XvcMetadata>,
26}
27
28persist!(SqliteQueryDep, "sqlite-query-dependency");
29
30impl From<SqliteQueryDep> for XvcDependency {
31 fn from(val: SqliteQueryDep) -> Self {
32 XvcDependency::SqliteQueryDigest(val)
33 }
34}
35
36impl SqliteQueryDep {
37 pub fn new(path: XvcPath, query: String) -> Self {
39 Self {
40 path,
41 query,
42 query_digest: None,
43 xvc_metadata: None,
44 }
45 }
46 pub fn update_metadata(self, xvc_metadata: Option<XvcMetadata>) -> Self {
48 Self {
49 xvc_metadata,
50 ..self
51 }
52 }
53
54 pub fn update_digest(self, xvc_root: &XvcRoot, algorithm: HashAlgorithm) -> Result<Self> {
56 let path = self.path.to_absolute_path(xvc_root);
57 let flags = OpenFlags::SQLITE_OPEN_READ_ONLY;
58 let sqlite = Connection::open_with_flags(path, flags)?;
59 let mut prepared = sqlite.prepare(&self.query)?;
60 let query_res = prepared.raw_query();
62 let query_lines = query_res
63 .map(|row| {
64 let mut i = 0;
65 let mut els = String::new();
67 while let Ok(col) = row.get_ref(i) {
68 match col.data_type() {
69 rusqlite::types::Type::Text => {
70 els.push_str(col.as_str()?);
71 }
72 rusqlite::types::Type::Integer => {
73 els.push_str(col.as_i64()?.to_string().as_str());
74 }
75 rusqlite::types::Type::Real => {
76 els.push_str(col.as_f64()?.to_string().as_str());
77 }
78 _ => {
79 els.push_str(col.as_str()?);
80 }
81 }
82 i += 1;
83 }
84 Ok(els)
85 })
86 .collect::<Vec<String>>()?
87 .join("\n");
88
89 let query_digest = Some(XvcDigest::from_content(&query_lines, algorithm).into());
90 Ok(Self {
91 query_digest,
92 ..self
93 })
94 }
95}
96
97impl Diffable for SqliteQueryDep {
98 type Item = Self;
99
100 fn diff_superficial(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
102 assert!(record.path == actual.path);
103
104 match (record.xvc_metadata, actual.xvc_metadata) {
105 (Some(rec_md), Some(act_md)) => {
106 if rec_md == act_md {
107 Diff::Identical
108 } else {
109 Diff::Different {
110 record: record.clone(),
111 actual: actual.clone(),
112 }
113 }
114 }
115 (None, Some(_)) => Diff::RecordMissing {
116 actual: actual.clone(),
117 },
118 (Some(_), None) => Diff::ActualMissing {
119 record: record.clone(),
120 },
121 (None, None) => unreachable!("Either record or actual should have metadata"),
122 }
123 }
124
125 fn diff_thorough(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
127 assert!(record.path == actual.path);
128 if record.query_digest == actual.query_digest {
129 Diff::Identical
130 } else {
131 Diff::Different {
132 record: record.clone(),
133 actual: actual.clone(),
134 }
135 }
136 }
137}