1#![allow(clippy::result_large_err)]
2
3use std::{
4 cmp,
5 collections::{BTreeMap, HashMap, HashSet},
6 sync::Arc,
7 time::{Duration, Instant},
8};
9
10use crate::api::{ChangeId, ColumnName, ColumnType, RowId, SqliteValue, SqliteValueRef, TableName};
11use crate::spawn::spawn_counted;
12use crate::sqlite_pool::{self, RusqlitePool};
13use crate::tripwire::{Outcome, PreemptibleFutureExt, Tripwire};
14use bytes::{Buf, BufMut};
15use camino::{Utf8Path, Utf8PathBuf};
16use compact_str::{ToCompactString, format_compact};
17use enquote::unquote;
18use fallible_iterator::FallibleIterator;
19use indexmap::{IndexMap, IndexSet};
20use metrics::{counter, histogram};
21use parking_lot::{Condvar, Mutex, RwLock};
22use rusqlite::{
23 Connection, OptionalExtension, params_from_iter,
24 types::{FromSqlError, ValueRef},
25};
26use sqlite3_parser::{
27 ast::{
28 As, Cmd, Expr, FromClause, JoinConstraint, JoinOperator, JoinType, JoinedSelectTable, Name,
29 OneSelect, Operator, QualifiedName, ResultColumn, Select, SelectTable, Stmt,
30 },
31 lexer::sql::Parser,
32};
33use tokio::{
34 sync::{AcquireError, mpsc, watch},
35 task::block_in_place,
36};
37use tokio_util::sync::{CancellationToken, DropGuard, WaitForCancellationFuture};
38use tracing::{debug, error, info, trace, warn};
39use uuid::Uuid;
40
41use crate::{
42 agent::SplitPool,
43 api::QueryEvent,
44 change::Change,
45 schema::{Schema, Table},
46 sqlite::CrConn,
47 updates::HandleMetrics,
48};
49
50pub use crate::api::sqlite::ChangeType;
51use crate::updates::{Handle, Manager};
52
53#[derive(Debug, Default, Clone)]
54pub struct SubsManager(Arc<RwLock<InnerSubsManager>>);
55
56#[derive(Debug, Default)]
57struct InnerSubsManager {
58 handles: BTreeMap<Uuid, MatcherHandle>,
59 queries: HashMap<String, Uuid>,
60}
61
62pub struct MatcherCreated {
64 pub evt_rx: mpsc::Receiver<QueryEvent>,
65}
66
67const SUB_EVENT_CHANNEL_CAP: usize = 512;
68
69impl Manager<MatcherHandle> for SubsManager {
70 fn trait_type(&self) -> String {
71 "subs".to_string()
72 }
73
74 fn get(&self, id: &Uuid) -> Option<MatcherHandle> {
75 self.0.read().get(id)
76 }
77
78 fn remove(&self, id: &Uuid) -> Option<MatcherHandle> {
79 let mut inner = self.0.write();
80 inner.remove(id)
81 }
82
83 fn get_handles(&self) -> BTreeMap<Uuid, MatcherHandle> {
84 self.0.read().handles.clone()
85 }
86}
87
88impl SubsManager {
89 pub fn get(&self, id: &Uuid) -> Option<MatcherHandle> {
90 self.0.read().get(id)
91 }
92
93 pub fn get_by_query(&self, sql: &str) -> Option<MatcherHandle> {
94 self.0.read().get_by_query(sql)
95 }
96
97 pub fn get_by_hash(&self, hash: &str) -> Option<MatcherHandle> {
98 self.0.read().get_by_hash(hash)
99 }
100
101 pub fn get_handles(&self) -> BTreeMap<Uuid, MatcherHandle> {
102 self.0.read().handles.clone()
103 }
104
105 pub async fn drop_handles(&self) {
106 let handles = {
107 let mut inner = self.0.write();
108 std::mem::take(&mut inner.handles)
109 };
110 for (_, handle) in handles.iter() {
111 handle.cleanup().await;
112 }
113 }
114
115 pub fn get_or_insert(
116 &self,
117 sql: &str,
118 subs_path: &Utf8Path,
119 schema: &Schema,
120 pool: &SplitPool,
121 tripwire: Tripwire,
122 ) -> Result<(MatcherHandle, Option<MatcherCreated>), MatcherError> {
123 if let Some(handle) = self.get_by_query(sql) {
124 return Ok((handle, None));
125 }
126
127 let mut inner = self.0.write();
128 if let Some(handle) = inner.get_by_query(sql) {
129 return Ok((handle, None));
130 }
131
132 let id = Uuid::new_v4();
133 let (evt_tx, evt_rx) = mpsc::channel(SUB_EVENT_CHANNEL_CAP);
134
135 let handle_res = Matcher::create(
136 id,
137 subs_path.to_path_buf(),
138 schema,
139 pool.client_dedicated()?,
140 evt_tx,
141 sql,
142 tripwire,
143 );
144
145 let handle = match handle_res {
146 Ok(handle) => handle,
147 Err(e) => {
148 error!(sub_id = %id, "could not create subscription: {e}");
149 if let Err(e) = Matcher::cleanup(id, Matcher::sub_path(subs_path, id)) {
150 error!("could not cleanup subscription: {e}");
151 }
152
153 return Err(e);
154 }
155 };
156
157 inner.handles.insert(id, handle.clone());
158 inner.queries.insert(sql.to_owned(), id);
159
160 Ok((handle, Some(MatcherCreated { evt_rx })))
161 }
162
163 #[allow(clippy::too_many_arguments)]
164 pub fn restore(
165 &self,
166 id: Uuid,
167 subs_path: &Utf8Path,
168 schema: &Schema,
169 pool: &SplitPool,
170 tripwire: Tripwire,
171 ) -> Result<(MatcherHandle, MatcherCreated), MatcherError> {
172 let mut inner = self.0.write();
173
174 if inner.handles.contains_key(&id) {
175 return Err(MatcherError::CannotRestoreExisting);
176 }
177
178 let (evt_tx, evt_rx) = mpsc::channel(SUB_EVENT_CHANNEL_CAP);
179
180 let handle = Matcher::restore(
181 id,
182 subs_path.to_path_buf(),
183 schema,
184 pool.client_dedicated()?,
185 evt_tx,
186 tripwire,
187 )?;
188
189 inner.handles.insert(id, handle.clone());
190 inner.queries.insert(handle.inner.sql.clone(), id);
191
192 Ok((handle, MatcherCreated { evt_rx }))
193 }
194
195 pub fn remove(&self, id: &Uuid) -> Option<MatcherHandle> {
196 let mut inner = self.0.write();
197 inner.remove(id)
198 }
199}
200
201#[derive(Debug)]
202pub struct MatchableChange<'a> {
203 pub table: &'a TableName,
204 pub pk: &'a [u8],
205 pub column: &'a ColumnName,
206 pub cl: i64,
207}
208
209impl<'a> From<&'a Change> for MatchableChange<'a> {
210 fn from(value: &'a Change) -> Self {
211 MatchableChange {
212 table: &value.table,
213 pk: &value.pk,
214 column: &value.cid,
215 cl: value.cl,
216 }
217 }
218}
219
220impl InnerSubsManager {
221 fn get(&self, id: &Uuid) -> Option<MatcherHandle> {
222 self.handles.get(id).cloned()
223 }
224
225 fn get_by_query(&self, sql: &str) -> Option<MatcherHandle> {
226 self.queries
227 .get(sql)
228 .and_then(|id| self.handles.get(id).cloned())
229 }
230
231 pub fn get_by_hash(&self, hash: &str) -> Option<MatcherHandle> {
232 self.handles
233 .values()
234 .find(|x| x.inner.hash == hash)
235 .cloned()
236 }
237
238 fn remove(&mut self, id: &Uuid) -> Option<MatcherHandle> {
239 let handle = self.handles.remove(id)?;
240 self.queries.remove(&handle.inner.sql);
241 Some(handle)
242 }
243}
244
245#[derive(Debug, Clone, Copy)]
246pub enum MatcherState {
247 Created,
248 Running,
249}
250
251impl MatcherState {
252 fn is_running(&self) -> bool {
253 matches!(self, MatcherState::Running)
254 }
255}
256
257#[derive(Clone, Debug)]
258pub struct MatcherHandle {
259 inner: Arc<InnerMatcherHandle>,
260 state: StateLock,
261}
262
263#[derive(Debug)]
264struct InnerMatcherHandle {
265 id: Uuid,
266 sql: String,
267 hash: String,
268 pool: sqlite_pool::RusqlitePool,
269 parsed: ParsedSelect,
270 col_names: Vec<ColumnName>,
271 cancel: CancellationToken,
272 changes_tx: mpsc::Sender<MatchCandidates>,
273 last_change_rx: watch::Receiver<ChangeId>,
274 subs_path: String,
276 cached_statements: HashMap<String, MatcherStmt>,
277 metrics: HashMap<String, HandleMetrics>,
278}
279
280pub type MatchCandidates = IndexMap<TableName, IndexMap<Vec<u8>, i64>>;
281
282impl Handle for MatcherHandle {
283 fn id(&self) -> Uuid {
284 self.inner.id
285 }
286
287 fn cancelled(&self) -> WaitForCancellationFuture<'_> {
288 self.inner.cancel.cancelled()
289 }
290
291 fn changes_tx(&self) -> mpsc::Sender<MatchCandidates> {
292 self.inner.changes_tx.clone()
293 }
294
295 fn cleanup(&self) -> impl std::future::Future<Output = ()> + Send {
296 let cancel = self.inner.cancel.clone();
297 let id = self.inner.id;
298
299 async move {
300 cancel.cancel();
301 info!(sub_id = %id, "Canceled subscription");
302 }
303 }
304
305 fn filter_matchable_change(
306 &self,
307 candidates: &mut MatchCandidates,
308 change: MatchableChange,
309 ) -> bool {
310 trace!("filtering change {change:?}");
311 if candidates
313 .get(change.table)
314 .map(|pks| pks.contains_key(change.pk))
315 .unwrap_or_default()
316 {
317 trace!("already contained key");
318 return false;
319 }
320
321 if !self
323 .inner
324 .parsed
325 .table_columns
326 .get(change.table.as_str())
327 .map(|cols| change.column.is_crsql_sentinel() || cols.contains(change.column.as_str()))
328 .unwrap_or_default()
329 {
330 trace!("could not match against parsed query table and columns");
331 return false;
332 }
333
334 if let Some(v) = candidates.get_mut(change.table) {
335 v.insert(change.pk.to_vec(), change.cl).is_none()
336 } else {
337 candidates.insert(
338 change.table.clone(),
339 [(change.pk.to_vec(), change.cl)].into(),
340 );
341 true
342 }
343 }
344
345 fn get_counter(&self, table: &str) -> &HandleMetrics {
346 self.inner.metrics.get(table).unwrap_or_else(|| {
347 panic!(
348 "metrics counter for table '{}' missing. subs hash {}!",
349 self.inner.hash, table
350 )
351 })
352 }
353}
354
355impl MatcherHandle {
356 pub fn sql(&self) -> &String {
357 &self.inner.sql
358 }
359
360 pub fn hash(&self) -> &str {
361 &self.inner.hash
362 }
363
364 pub fn parsed_columns(&self) -> &[ResultColumn] {
365 &self.inner.parsed.columns
366 }
367
368 pub fn col_names(&self) -> &[ColumnName] {
369 &self.inner.col_names
370 }
371
372 pub fn subs_path(&self) -> &String {
373 &self.inner.subs_path
374 }
375
376 pub fn cached_stmts(&self) -> &HashMap<String, MatcherStmt> {
377 &self.inner.cached_statements
378 }
379
380 pub fn pool(&self) -> &RusqlitePool {
381 &self.inner.pool
382 }
383
384 fn wait_for_running_state(&self) {
385 let (lock, cvar) = &*self.state;
386 let mut state = lock.lock();
387 while !state.is_running() {
388 cvar.wait(&mut state);
389 }
390 }
391
392 pub fn max_change_id(&self, conn: &Connection) -> rusqlite::Result<ChangeId> {
393 self.wait_for_running_state();
394 let mut prepped = conn.prepare_cached("SELECT COALESCE(MAX(id), 0) FROM changes")?;
395 prepped.query_row([], |row| row.get(0))
396 }
397
398 pub fn last_change_id_sent(&self) -> ChangeId {
399 *self.inner.last_change_rx.borrow()
400 }
401
402 pub fn max_row_id(&self, conn: &Connection) -> rusqlite::Result<RowId> {
403 self.wait_for_running_state();
404 let mut prepped =
405 conn.prepare_cached("SELECT COALESCE(MAX(__corro_rowid), 0) FROM query")?;
406 prepped.query_row([], |row| row.get(0))
407 }
408
409 pub fn changes_since(
410 &self,
411 since: ChangeId,
412 conn: &Connection,
413 tx: mpsc::Sender<QueryEvent>,
414 ) -> rusqlite::Result<ChangeId> {
415 self.wait_for_running_state();
416
417 let mut prepped = conn.prepare_cached("SELECT MIN(id) FROM changes")?;
418 let min_change_id: u64 = prepped.query_row([], |row| row.get(0))?;
419
420 if since.0 < min_change_id {
421 return Err(rusqlite::Error::ModuleError(format!(
422 "subscription already deleted older changes, min change id: {min_change_id}",
423 )));
424 }
425
426 let mut query_cols = vec![];
427 for i in 0..(self.parsed_columns().len()) {
428 query_cols.push(format!("col_{i}"));
429 }
430 let mut prepped = conn.prepare_cached(&format!(
431 "SELECT id, type, __corro_rowid, {} FROM changes WHERE id > ? ORDER BY id ASC",
432 query_cols.join(",")
433 ))?;
434
435 let col_count = prepped.column_count();
436
437 let mut max_change_id = since;
438
439 let mut rows = prepped.query([since])?;
440
441 loop {
442 let row = match rows.next()? {
443 Some(row) => row,
444 None => break,
445 };
446
447 let change_id: ChangeId = row.get(0)?;
448 if change_id.0 > max_change_id.0 {
449 max_change_id = change_id;
450 }
451
452 if let Err(e) = tx.blocking_send(QueryEvent::Change(
453 row.get(1)?,
454 row.get(2)?,
455 (3..col_count)
456 .map(|i| row.get::<_, SqliteValue>(i))
457 .collect::<rusqlite::Result<Vec<_>>>()?,
458 change_id,
459 )) {
460 error!("could not send change to channel: {e}");
461 break;
462 }
463 }
464
465 Ok(max_change_id)
466 }
467
468 pub fn all_rows(
469 &self,
470 conn: &Connection,
471 tx: mpsc::Sender<QueryEvent>,
472 ) -> Result<ChangeId, MatcherError> {
473 self.wait_for_running_state();
474 let mut query_cols = vec![];
475 for i in 0..(self.parsed_columns().len()) {
476 query_cols.push(format!("col_{i}"));
477 }
478 let mut prepped = conn.prepare_cached(&format!(
479 "SELECT __corro_rowid, {} FROM query",
480 query_cols.join(",")
481 ))?;
482
483 let col_count = prepped.column_count();
484
485 tx.blocking_send(QueryEvent::Columns(self.col_names().to_vec()))
486 .map_err(|_| MatcherError::EventReceiverClosed)?;
487
488 let start = Instant::now();
489 let mut rows = prepped.query([])?;
490 let elapsed = start.elapsed();
491
492 let mut count = 0;
493
494 loop {
495 let row = match rows.next()? {
496 Some(row) => row,
497 None => break,
498 };
499
500 tx.blocking_send(QueryEvent::Row(
501 row.get(0)?,
502 (1..col_count)
503 .map(|i| row.get::<_, SqliteValue>(i))
504 .collect::<rusqlite::Result<Vec<_>>>()?,
505 ))
506 .map_err(|_| MatcherError::EventReceiverClosed)?;
507 count += 1;
508 }
509
510 trace!("sent {count} rows");
511
512 let max_change_id = conn
513 .prepare("SELECT COALESCE(MAX(id),0) FROM changes")?
514 .query_row([], |row| row.get(0))?;
515
516 tx.blocking_send(QueryEvent::EndOfQuery {
517 time: elapsed.as_secs_f64(),
518 change_id: Some(max_change_id),
519 })
520 .map_err(|_| MatcherError::EventReceiverClosed)?;
521
522 Ok(max_change_id)
523 }
524}
525
526type StateLock = Arc<(Mutex<MatcherState>, Condvar)>;
527
528pub struct Matcher {
529 pub id: Uuid,
530 pub hash: String,
531 pub query: Stmt,
532 pub cached_statements: HashMap<String, MatcherStmt>,
533 pub pks: IndexMap<String, Vec<String>>,
534 pub parsed: ParsedSelect,
535 pub evt_tx: mpsc::Sender<QueryEvent>,
536 pub col_names: Vec<ColumnName>,
537 pub last_rowid: u64,
538 conn: Connection,
539 base_path: Utf8PathBuf,
540 cancel: CancellationToken,
541 state: StateLock,
542 last_change_tx: watch::Sender<ChangeId>,
543 changes_rx: mpsc::Receiver<MatchCandidates>,
544}
545
546#[derive(Debug, Clone)]
547pub struct MatcherStmt {
548 new_query: String,
549 temp_query: String,
550}
551
552impl MatcherStmt {
553 pub fn new_query(&self) -> &String {
554 &self.new_query
555 }
556}
557
558const CHANGE_ID_COL: &str = "id";
559const CHANGE_TYPE_COL: &str = "type";
560
561pub const QUERY_TABLE_NAME: &str = "query";
562
563pub const SUB_DB_PATH: &str = "sub.sqlite";
564
565impl Matcher {
566 fn new(
567 id: Uuid,
568 subs_path: Utf8PathBuf,
569 schema: &Schema,
570 state_conn: &Connection,
571 evt_tx: mpsc::Sender<QueryEvent>,
572 sql: &str,
573 ) -> Result<(Matcher, MatcherHandle), MatcherError> {
574 let sub_path = Self::sub_path(subs_path.as_path(), id);
575 let sql_hash = hex::encode(seahash::hash(sql.as_bytes()).to_be_bytes());
576
577 info!(%sql_hash, sub_id = %id, "Initializing subscription at {sub_path}");
578
579 std::fs::create_dir_all(&sub_path)?;
580
581 let sub_db_path = sub_path.join(SUB_DB_PATH);
582
583 let col_names: Vec<ColumnName> = {
584 state_conn
585 .prepare(sql)?
586 .column_names()
587 .into_iter()
588 .map(|s| ColumnName(s.to_compact_string()))
589 .collect()
590 };
591
592 let conn = Connection::open(&sub_db_path)?;
593 conn.execute_batch(
594 r#"
595 PRAGMA journal_mode = WAL;
596 PRAGMA synchronous = NORMAL;
597 PRAGMA temp_store = memory;
598 PRAGMA cache_size = -32000; -- 32MB
599 PRAGMA mmap_size = 536870912; -- 512MB
600 "#,
601 )?;
602
603 let mut parser = Parser::new(sql.as_bytes());
604
605 let (mut stmt, parsed) = match parser.next()?.ok_or(MatcherError::StatementRequired)? {
606 Cmd::Stmt(stmt) => {
607 let parsed = match stmt {
608 Stmt::Select(ref select) => extract_select_columns(select, schema)?,
609 _ => return Err(MatcherError::UnsupportedStatement),
610 };
611
612 (stmt, parsed)
613 }
614 _ => return Err(MatcherError::StatementRequired),
615 };
616
617 if parsed.table_columns.is_empty() {
618 return Err(MatcherError::TableRequired);
619 }
620
621 let mut statements = HashMap::new();
622
623 let mut pks = IndexMap::default();
624
625 match &mut stmt {
626 Stmt::Select(select) => match &mut select.body.select {
627 OneSelect::Select { columns, .. } => {
628 let mut new_cols = parsed
629 .table_columns
630 .iter()
631 .filter_map(|(tbl_name, _cols)| {
632 schema.tables.get(tbl_name).map(|table| {
633 let tbl_name = parsed
634 .aliases
635 .iter()
636 .find_map(|(alias, actual)| {
637 (actual == tbl_name).then_some(alias)
638 })
639 .unwrap_or(tbl_name);
640 table
641 .pk
642 .iter()
643 .map(|pk| {
644 let alias = format!("__corro_pk_{tbl_name}_{pk}");
645 let entry: &mut Vec<String> =
646 pks.entry(table.name.clone()).or_default();
647 entry.push(alias.clone());
648
649 ResultColumn::Expr(
650 Expr::Qualified(
651 Name(tbl_name.clone().into()),
652 Name(pk.clone().into()),
653 ),
654 Some(As::As(Name(alias.into()))),
655 )
656 })
657 .collect::<Vec<_>>()
658 })
659 })
660 .flatten()
661 .collect::<Vec<_>>();
662
663 new_cols.append(&mut parsed.columns.clone());
664 *columns = new_cols;
665 }
666 _ => unreachable!(),
667 },
668 _ => unreachable!(),
669 }
670
671 for (idx, (tbl_name, _cols)) in parsed.table_columns.iter().enumerate() {
672 let expr = table_to_expr(
673 &parsed.aliases,
674 schema
675 .tables
676 .get(tbl_name)
677 .expect("this should not happen, missing table in schema"),
678 tbl_name,
679 )?;
680
681 let mut stmt = stmt.clone();
682
683 if let Stmt::Select(select) = &mut stmt
684 && let OneSelect::Select {
685 where_clause, from, ..
686 } = &mut select.body.select
687 {
688 *where_clause = if let Some(prev) = where_clause.take() {
689 Some(Box::new(Expr::Binary(
690 Box::new(expr),
691 Operator::And,
692 Box::new(Expr::parenthesized(*prev)),
693 )))
694 } else {
695 Some(Box::new(expr))
696 };
697
698 match from {
699 Some(FromClause {
700 joins: Some(joins), ..
701 }) if idx > 0 => {
702 if let Some(joined_table) = joins.get_mut(idx - 1)
704 && let JoinOperator::TypedJoin(Some(join_type)) = &joined_table.operator
705 && join_type.contains(JoinType::LEFT)
706 {
707 joined_table.operator = JoinOperator::TypedJoin(Some(JoinType::INNER));
708 };
709
710 if let Some(JoinedSelectTable {
713 table: SelectTable::Table(_, _, indexed @ Some(_)),
714 ..
715 }) = joins.get_mut(idx - 1)
716 {
717 *indexed = None
718 };
719 }
720 _ => (),
721 };
722 }
723
724 let mut new_query = Cmd::Stmt(stmt).to_string();
725 new_query.pop();
726
727 let mut all_cols = pks.values().flatten().cloned().collect::<Vec<String>>();
728 for i in 0..(parsed.columns.len()) {
729 all_cols.push(format!("col_{i}"));
730 }
731
732 let temp_query = format!(
733 "SELECT {} FROM query WHERE ({}) IN temp_{tbl_name}",
734 all_cols.join(","),
735 pks.get(tbl_name)
736 .cloned()
737 .ok_or(MatcherError::MissingPrimaryKeys)?
738 .into_iter()
739 .map(|pk| format!("coalesce({pk}, \"\")"))
740 .collect::<Vec<_>>()
741 .join(","),
742 );
743
744 info!(%sql_hash, sub_id = %id, "modified query for table '{tbl_name}': {new_query}");
745
746 statements.insert(
747 tbl_name.clone(),
748 MatcherStmt {
749 new_query,
750 temp_query,
751 },
752 );
753 }
754
755 let cancel = CancellationToken::new();
756
757 let state = Arc::new((Mutex::new(MatcherState::Created), Condvar::new()));
758
759 let (last_change_tx, last_change_rx) = watch::channel(ChangeId(0));
760
761 let (changes_tx, changes_rx) = mpsc::channel(20480);
763
764 let mut counter_map = HashMap::new();
766 for table in parsed.table_columns.keys() {
767 counter_map.insert(table.clone(), HandleMetrics{
768 matched_count: counter!("corro.subs.changes.matched.count", "sql_hash" => sql_hash.clone(), "table" => table.to_string()),
769 });
770 }
771
772 let handle = MatcherHandle {
773 inner: Arc::new(InnerMatcherHandle {
774 id,
775 sql: sql.to_owned(),
776 hash: sql_hash.clone(),
777 pool: sqlite_pool::Config::new(sub_db_path.into_std_path_buf())
778 .max_size(5)
779 .read_only()
780 .create_pool()
781 .expect("could not build pool, this can't fail because we specified a runtime"),
782 parsed: parsed.clone(),
783 col_names: col_names.clone(),
784 cancel: cancel.clone(),
785 last_change_rx,
786 changes_tx,
787 cached_statements: statements.clone(),
788 subs_path: sub_path.to_string(),
789 metrics: counter_map,
790 }),
791 state: state.clone(),
792 };
793
794 let matcher = Self {
795 id,
796 hash: sql_hash,
797 query: stmt,
798 cached_statements: statements,
799 pks,
800 parsed,
801 evt_tx,
802 col_names,
803 last_rowid: 0,
804 conn,
805 base_path: sub_path,
806 cancel,
807 state,
808 last_change_tx,
809 changes_rx,
810 };
811
812 Ok((matcher, handle))
813 }
814
815 pub fn cleanup(id: Uuid, sub_path: Utf8PathBuf) -> rusqlite::Result<()> {
816 info!(sub_id = %id, "Attempting to cleanup... {}", sub_path);
817
818 block_in_place(|| {
819 if let Err(e) = std::fs::remove_dir_all(&sub_path) {
820 error!(sub_id = %id, "could not delete subscription base path {} due to: {e}", sub_path);
821 }
822
823 Ok(())
824 })
825 }
826
827 pub fn sub_path(subs_path: &Utf8Path, id: Uuid) -> Utf8PathBuf {
828 subs_path.join(id.as_simple().to_string())
829 }
830
831 pub fn sub_db_path(subs_path: &Utf8Path, id: Uuid) -> Utf8PathBuf {
832 Self::sub_path(subs_path, id).join(SUB_DB_PATH)
833 }
834
835 #[allow(clippy::too_many_arguments)]
836 pub fn restore(
837 id: Uuid,
838 subs_path: Utf8PathBuf,
839 schema: &Schema,
840 state_conn: CrConn,
841 evt_tx: mpsc::Sender<QueryEvent>,
842 tripwire: Tripwire,
843 ) -> Result<MatcherHandle, MatcherError> {
844 let sql: String = block_in_place(|| {
845 let conn = Connection::open(Matcher::sub_db_path(&subs_path, id))?;
846 let state: Option<String> = conn
847 .query_row("SELECT value FROM meta WHERE key = 'state'", [], |row| {
848 row.get(0)
849 })
850 .optional()?;
851 if !matches!(state.as_deref(), Some("completed")) {
852 return Err(MatcherError::NotRunning);
853 }
854
855 let sql: Option<String> = conn
856 .query_row("SELECT value FROM meta WHERE key = 'sql'", [], |row| {
857 row.get(0)
858 })
859 .optional()?;
860
861 match sql {
862 Some(sql) => Ok(sql),
863 None => Err(MatcherError::MissingSql),
864 }
865 })?;
866
867 let (matcher, handle) = Self::new(id, subs_path, schema, &state_conn, evt_tx, &sql)?;
868
869 spawn_counted(matcher.run_restore(state_conn, tripwire));
870
871 Ok(handle)
872 }
873
874 #[allow(clippy::too_many_arguments)]
875 pub fn create(
876 id: Uuid,
877 subs_path: Utf8PathBuf,
878 schema: &Schema,
879 state_conn: CrConn,
880 evt_tx: mpsc::Sender<QueryEvent>,
881 sql: &str,
882 tripwire: Tripwire,
883 ) -> Result<MatcherHandle, MatcherError> {
884 let (mut matcher, handle) = Self::new(id, subs_path, schema, &state_conn, evt_tx, sql)?;
885
886 let pk_cols = matcher
887 .pks
888 .values()
889 .flatten()
890 .cloned()
891 .collect::<Vec<String>>();
892
893 let mut all_cols = pk_cols.clone();
894
895 let mut query_cols = vec![];
896
897 for i in 0..(matcher.parsed.columns.len()) {
898 let col_name = format!("col_{i}");
899 all_cols.push(col_name.clone());
900 query_cols.push(col_name);
901 }
902
903 block_in_place(|| {
904 let tx = matcher.conn.transaction()?;
905
906 info!(sub_id = %id, "Creating subscription database schema");
907 let create_temp_table = format!(
908 r#"
909 CREATE TABLE query (__corro_rowid INTEGER PRIMARY KEY AUTOINCREMENT, {columns});
910
911 CREATE UNIQUE INDEX index_{id}_pk ON query ({pks_coalesced});
912
913 CREATE TABLE changes (
914 {CHANGE_ID_COL} INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
915 __corro_rowid INTEGER NOT NULL,
916 {CHANGE_TYPE_COL} INTEGER NOT NULL,
917 {actual_columns}
918 );
919
920 CREATE TABLE meta (
921 key TEXT PRIMARY KEY NOT NULL,
922 value
923 ) WITHOUT ROWID;
924
925 CREATE TABLE columns (
926 "table" TEXT NOT NULL,
927 cid TEXT,
928
929 PRIMARY KEY ("table", cid)
930 );
931 "#,
932 columns = all_cols.join(","),
933 id = id.as_simple(),
934 pks_coalesced = pk_cols
935 .iter()
936 .map(|pk| format!("coalesce({pk}, \"\")"))
937 .collect::<Vec<_>>()
938 .join(","),
939 actual_columns = query_cols.join(","),
940 );
941
942 tx.execute_batch(&create_temp_table)?;
943 trace!("created sub tables");
944
945 for (table, pks) in matcher.pks.iter() {
946 tx.execute(
947 &format!(
948 "CREATE INDEX index_{id}_{table}_pk ON query ({pks})",
949 id = id.as_simple(),
950 table = table,
951 pks = pks.to_vec().join(","),
952 ),
953 [],
954 )?;
955 }
956 trace!("created query indexes");
957
958 for (table, columns) in matcher.parsed.table_columns.iter() {
959 tx.execute(
960 r#"INSERT INTO columns ("table", cid) VALUES (?, '-1')"#,
961 [table.as_str()],
962 )?;
963 for column in columns.iter() {
964 trace!("inserting sub column {} => {}", table, column);
965 tx.execute(
966 r#"INSERT INTO columns ("table", cid) VALUES (?, ?)"#,
967 [table.as_str(), column.as_str()],
968 )?;
969 }
970 }
971 trace!("inserted sub columns");
972
973 tx.execute("INSERT INTO meta (key, value) VALUES ('sql', ?)", [sql])?;
974 tx.execute(
975 "INSERT INTO meta (key, value) VALUES ('state', 'created')",
976 [],
977 )?;
978
979 tx.commit()?;
980 trace!("committed subscription");
981
982 Ok::<_, MatcherError>(())
983 })?;
984
985 spawn_counted(matcher.run(state_conn, tripwire));
986
987 Ok(handle)
988 }
989
990 async fn run_restore(mut self, mut state_conn: CrConn, tripwire: Tripwire) {
991 info!(sub_id = %self.id, "Restoring subscription");
992 let init_res = block_in_place(|| {
993 self.last_rowid = self
994 .conn
995 .query_row(
996 "SELECT COALESCE(MAX(__corro_rowid), 0) FROM query",
997 (),
998 |row| row.get(0),
999 )
1000 .optional()?
1001 .unwrap_or_default();
1002
1003 let max_change_id = self
1004 .conn
1005 .prepare_cached("SELECT COALESCE(MAX(id), 0) FROM changes")?
1006 .query_row([], |row| row.get(0))?;
1007
1008 _ = self.last_change_tx.send(max_change_id);
1009
1010 Ok::<_, MatcherError>(())
1011 });
1012
1013 if let Err(e) = init_res {
1014 error!(sub_id = %self.id, "could not re-initialize subscription: {e}");
1015 return;
1016 }
1017
1018 if let Err(e) = block_in_place(|| self.setup(&mut state_conn)) {
1019 error!(sub_id = %self.id, "could not setup connection: {e}");
1020 return;
1021 }
1022
1023 if let Err(e) = self.set_status("running") {
1024 error!(sub_id = %self.id, "could not set status: {e}");
1025 return;
1026 }
1027
1028 self.cmd_loop(state_conn, tripwire).await
1029 }
1030
1031 fn set_status(&self, status: &str) -> Result<(), MatcherError> {
1032 self.conn.execute(
1033 "INSERT OR REPLACE INTO meta (key, value) VALUES ('state', ?)",
1034 [status],
1035 )?;
1036 Ok(())
1037 }
1038
1039 fn setup(&self, state_conn: &mut Connection) -> Result<(), MatcherError> {
1040 info!(sub_id = %self.id, "Attaching __corro_sub to state db");
1041 if let Err(e) = state_conn.execute_batch(&format!(
1042 "ATTACH DATABASE {} AS __corro_sub",
1043 enquote::enquote('\'', self.base_path.join(SUB_DB_PATH).as_str()),
1044 )) {
1045 error!(sub_id = %self.id, "could not ATTACH sub db as __corro_sub on state db: {e}");
1046 _ = self.evt_tx.try_send(QueryEvent::Error(format_compact!(
1047 "could not ATTACH subscription db: {e}"
1048 )));
1049 return Err(e.into());
1050 }
1051
1052 info!(sub_id = %self.id, "Attached __corro_sub to state db");
1053
1054 for (tbl_name, pks) in &self.pks {
1055 if let Ok(plan) = dump_query_plan(
1056 state_conn,
1057 &self
1058 .cached_statements
1059 .get(tbl_name)
1060 .ok_or(MatcherError::StatementRequired)?
1061 .new_query,
1062 tbl_name,
1063 pks,
1064 ) {
1065 info!(sub_id = %self.id, sql_hash = %self.hash, "query plan for table '{tbl_name}':\n{plan}");
1066 }
1067 }
1068
1069 Ok(())
1070 }
1071
1072 async fn cmd_loop(mut self, mut state_conn: CrConn, mut tripwire: Tripwire) {
1073 const PROCESS_CHANGES_THRESHOLD: usize = 1000;
1074 const PROCESSING_WARN_THRESHOLD: Duration = Duration::from_secs(5);
1075 const PROCESS_BUFFER_DEADLINE: Duration = Duration::from_millis(600);
1076
1077 info!(sub_id = %self.id, "Starting loop to run the subscription");
1078 {
1079 let (lock, cvar) = &*self.state;
1080 let mut state = lock.lock();
1081 *state = MatcherState::Running;
1082 cvar.notify_all();
1083 info!(sub_id = %self.id, "Notified condvar that the subscription is 'running'");
1084 }
1085 trace!("set state!");
1086
1087 let mut buf = MatchCandidates::new();
1088 let mut buf_count = 0;
1089
1090 let mut purge_changes_interval = tokio::time::interval(Duration::from_secs(300));
1091
1092 let process_changes_deadline = tokio::time::sleep(PROCESS_BUFFER_DEADLINE);
1094 tokio::pin!(process_changes_deadline);
1095
1096 loop {
1097 enum Branch {
1098 NewCandidates(MatchCandidates),
1099 PurgeOldChanges,
1100 }
1101
1102 let branch = tokio::select! {
1105 biased;
1106 _ = self.cancel.cancelled() => {
1107 info!(sub_id = %self.id, "Acknowledged subscription cancellation, breaking loop.");
1108 if let Err(e) = self.set_status("cancelled") {
1109 error!(sub_id = %self.id, "could not set status during cancellation: {e}");
1110 }
1111 break;
1112 }
1113 Some(candidates) = self.changes_rx.recv() => {
1114 for (table, pks) in candidates {
1115 let buffed = buf.entry(table).or_default();
1116 for (pk, cl) in pks {
1117 if buffed.insert(pk, cl).is_none() {
1118 buf_count += 1;
1119 }
1120 }
1121 }
1122
1123 if buf_count >= PROCESS_CHANGES_THRESHOLD {
1124 buf_count = 0;
1125 Branch::NewCandidates(std::mem::take(&mut buf))
1126 } else {
1127 continue;
1128 }
1129 },
1130 _ = process_changes_deadline.as_mut() => {
1131 process_changes_deadline
1132 .as_mut()
1133 .reset((Instant::now() + PROCESS_BUFFER_DEADLINE).into());
1134 if buf_count == 0 {
1135 continue;
1136 }
1137 Branch::NewCandidates(std::mem::take(&mut buf))
1138 },
1139 _ = &mut tripwire => {
1140 info!(sub_id = %self.id, "tripped cmd_loop, returning");
1141 break;
1143 }
1144 _ = purge_changes_interval.tick() => Branch::PurgeOldChanges,
1145 else => {
1146 return;
1147 }
1148 };
1149
1150 match branch {
1151 Branch::NewCandidates(candidates) => {
1152 let start = Instant::now();
1153 if let Err(e) = block_in_place(|| {
1154 self.handle_candidates(&mut state_conn, candidates, false)
1155 }) {
1156 if !matches!(e, MatcherError::EventReceiverClosed) {
1157 error!(sub_id = %self.id, "could not handle change: {e}");
1158 }
1159 info!(sub_id = %self.id, "attempting to cleanup");
1160 if let Err(e) = Self::cleanup(self.id, self.base_path.clone()) {
1161 error!("could not handle cleanup: {e}");
1162 }
1163 return;
1164 }
1165 let elapsed = start.elapsed();
1166
1167 histogram!("corro.subs.changes.processing.duration.seconds", "sql_hash" => self.hash.clone()).record(elapsed);
1168
1169 if elapsed >= PROCESSING_WARN_THRESHOLD {
1170 warn!(sub_id = %self.id, "processed {buf_count} changes (very slowly) for subscription in {elapsed:?}");
1171 } else {
1172 debug!(sub_id = %self.id, "processed {buf_count} changes for subscription in {elapsed:?}");
1173 }
1174 buf_count = 0;
1175
1176 process_changes_deadline
1178 .as_mut()
1179 .reset((Instant::now() + PROCESS_BUFFER_DEADLINE).into());
1180 }
1181 Branch::PurgeOldChanges => {
1182 let start = Instant::now();
1183 let res = block_in_place(|| {
1184 let tx = self.conn.transaction()?;
1185
1186 let deleted = tx
1187 .prepare_cached(
1188 "DELETE FROM changes WHERE id < (SELECT COALESCE(MAX(id),0) - 500 FROM changes)"
1189 )?
1190 .execute([])?;
1191
1192 tx.commit().map(|_| deleted)
1193 });
1194
1195 match res {
1196 Ok(deleted) => {
1197 info!(sub_id = %self.id, "Deleted {deleted} old changes row in {:?}", start.elapsed());
1198 }
1199 Err(e) => {
1200 error!(sub_id = %self.id, "could not delete old changes: {e}");
1201 }
1202 }
1203 }
1204 }
1205 }
1206
1207 info!(sub_id = %self.id, "draining changes channel");
1208 while let Some(candidates) = self.changes_rx.recv().await {
1209 for (table, pks) in candidates {
1210 let buffed = buf.entry(table).or_default();
1211 for (pk, cl) in pks {
1212 buffed.insert(pk, cl);
1213 }
1214 }
1215 }
1216
1217 if !buf.is_empty() {
1218 info!(sub_id = %self.id, "handling buffered candidates");
1219 let start = Instant::now();
1220 if let Err(e) = block_in_place(|| self.handle_candidates(&mut state_conn, buf, true)) {
1221 error!(sub_id = %self.id, "could not handle final buffered candidates: {e}");
1222 if let Err(e) = Self::cleanup(self.id, self.base_path.clone()) {
1223 error!(sub_id = %self.id, "could not handle cleanup: {e}");
1224 }
1225 return;
1226 }
1227 let elapsed = start.elapsed();
1228 info!(sub_id = %self.id, "handled final buffered candidates in {elapsed:?}");
1229 }
1230
1231 if let Err(e) = self.set_status("completed") {
1232 error!(sub_id = %self.id, "could not set status: {e}");
1233 };
1234
1235 info!(sub_id = %self.id, "matcher loop is done");
1236 }
1237
1238 async fn run(mut self, mut state_conn: CrConn, tripwire: Tripwire) {
1239 info!(sub_id = %self.id, "Running initial query");
1240 if let Err(e) = self
1241 .evt_tx
1242 .send(QueryEvent::Columns(self.col_names.clone()))
1243 .await
1244 {
1245 error!(sub_id = %self.id, "could not send back columns, probably means no receivers! {e}");
1246 return;
1247 }
1248
1249 let mut query_cols = vec![];
1250 for i in 0..(self.parsed.columns.len()) {
1251 query_cols.push(format!("col_{i}"));
1252 }
1253
1254 let res = block_in_place(|| {
1255 let tx = self.conn.transaction()?;
1256
1257 let mut stmt_str = Cmd::Stmt(self.query.clone()).to_string();
1258 stmt_str.pop(); let mut all_cols = self
1261 .pks
1262 .values()
1263 .flatten()
1264 .cloned()
1265 .collect::<Vec<String>>();
1266
1267 for i in 0..(self.parsed.columns.len()) {
1268 let col_name = format!("col_{i}");
1269 all_cols.push(col_name.clone());
1270 }
1271
1272 let mut last_rowid = 0;
1273
1274 tx.execute_batch(&format!(
1276 "DROP TABLE IF EXISTS state_rows;
1277 CREATE TEMP TABLE state_rows ({})",
1278 all_cols.join(",")
1279 ))?;
1280
1281 info!(sub_id = %self.id, "Starting state conn read transaction for initial query");
1282 let state_tx = state_conn.transaction()?;
1284
1285 let elapsed = {
1286 debug!("select stmt: {stmt_str:?}");
1287
1288 let mut select = state_tx.prepare(&stmt_str)?;
1289 let start = Instant::now();
1290 let mut select_rows = {
1291 let _guard = interrupt_deadline_guard(&state_tx, Duration::from_secs(15));
1292 select.query(())?
1293 };
1294 let elapsed = start.elapsed();
1295 info!(sub_id = %self.id, "Initial query done in {elapsed:?}");
1296
1297 let insert_into = format!(
1298 "INSERT INTO query ({}) VALUES ({}) RETURNING __corro_rowid,{}",
1299 all_cols.join(","),
1300 all_cols
1301 .iter()
1302 .map(|_| "?".to_string())
1303 .collect::<Vec<_>>()
1304 .join(","),
1305 query_cols.join(","),
1306 );
1307 trace!("insert stmt: {insert_into:?}");
1308
1309 {
1310 let mut insert = tx.prepare(&insert_into)?;
1311
1312 loop {
1313 match select_rows.next() {
1314 Ok(Some(row)) => {
1315 for i in 0..all_cols.len() {
1316 insert.raw_bind_parameter(
1317 i + 1,
1318 SqliteValueRef::from(row.get_ref(i)?),
1319 )?;
1320 }
1321
1322 let mut rows = insert.raw_query();
1323
1324 let row = match rows.next()? {
1325 Some(row) => row,
1326 None => continue,
1327 };
1328
1329 let rowid = row.get(0)?;
1330 let cells = (1..=query_cols.len())
1331 .map(|i| row.get::<_, SqliteValue>(i))
1332 .collect::<rusqlite::Result<Vec<_>>>()?;
1333
1334 if let Err(e) = self
1335 .evt_tx
1336 .blocking_send(QueryEvent::Row(RowId(rowid), cells))
1337 {
1338 error!(sub_id = %self.id, "could not send back row: {e}");
1339 return Err(MatcherError::EventReceiverClosed);
1340 }
1341
1342 last_rowid = cmp::max(rowid, last_rowid);
1343 }
1344 Ok(None) => {
1345 info!(sub_id = %self.id, "Done iterating through rows for initial query");
1346 break;
1348 }
1349 Err(e) => {
1350 return Err(e.into());
1351 }
1352 }
1353 }
1354 }
1355
1356 tx.execute_batch("DROP TABLE IF EXISTS state_rows;")?;
1357
1358 tx.execute(
1359 "INSERT OR REPLACE INTO meta (key, value) VALUES ('state', 'running')",
1360 [],
1361 )?;
1362
1363 tx.commit()?;
1364
1365 elapsed
1366 };
1367
1368 self.last_rowid = last_rowid;
1369
1370 Ok::<_, MatcherError>(elapsed)
1371 });
1372
1373 match res {
1374 Ok(elapsed) => {
1375 trace!(
1376 "done w/ block_in_place for initial query, elapsed: {:?}",
1377 elapsed
1378 );
1379 if let Err(e) = self
1380 .evt_tx
1381 .send(QueryEvent::EndOfQuery {
1382 time: elapsed.as_secs_f64(),
1383 change_id: Some(ChangeId(0)),
1384 })
1385 .await
1386 {
1387 error!(sub_id = %self.id, "could not return end of query event: {e}");
1388 return;
1389 }
1390 }
1392 Err(e) => {
1393 warn!(sub_id = %self.id, "could not complete initial query: {e}");
1394 _ = self
1395 .evt_tx
1396 .send(QueryEvent::Error(e.to_compact_string()))
1397 .await;
1398
1399 return;
1400 }
1401 };
1402
1403 if let Err(e) = block_in_place(|| self.setup(&mut state_conn)) {
1404 error!(sub_id = %self.id, "could not setup connection: {e}");
1405 return;
1406 }
1407
1408 self.cmd_loop(state_conn, tripwire).await
1409 }
1410
1411 fn handle_candidates(
1412 &mut self,
1413 state_conn: &mut Connection,
1414 candidates: MatchCandidates,
1415 skip_send: bool,
1416 ) -> Result<(), MatcherError> {
1417 let mut tables = IndexSet::new();
1418
1419 if candidates.is_empty() {
1420 return Ok(());
1421 }
1422
1423 trace!(
1424 "got some candidates! {:?}",
1425 candidates.keys().collect::<Vec<_>>()
1426 );
1427
1428 let tx = self.conn.transaction()?;
1429 for (table, pks) in candidates {
1430 let pks = pks
1431 .iter()
1432 .map(|(pk, _)| unpack_columns(pk))
1433 .collect::<Result<Vec<Vec<SqliteValueRef>>, _>>()?;
1434
1435 let tmp_table_name = format!("temp_{table}");
1436 if tables.insert(table.clone()) {
1437 tx.prepare_cached(
1439 &format!(
1443 "CREATE TABLE IF NOT EXISTS {tmp_table_name} ({})",
1444 self.pks
1445 .get(table.as_str())
1446 .ok_or_else(|| MatcherError::MissingPrimaryKeys)?
1447 .to_vec()
1448 .join(",")
1449 ),
1450 )?
1451 .execute(())?;
1452 }
1453
1454 for pks in pks {
1455 tx.prepare_cached(&format!(
1456 "INSERT INTO {tmp_table_name} VALUES ({})",
1457 (0..pks.len())
1458 .map(|_i| "coalesce(?, \"\")")
1459 .collect::<Vec<_>>()
1460 .join(",")
1461 ))?
1462 .execute(params_from_iter(pks))?;
1463 }
1464 }
1465 tx.commit()?;
1467 trace!("committed temp pk tables");
1468
1469 let mut query_cols = vec![];
1470
1471 let pk_cols = self
1472 .pks
1473 .values()
1474 .flatten()
1475 .cloned()
1476 .collect::<Vec<String>>();
1477
1478 let mut all_cols = pk_cols.clone();
1479 for i in 0..(self.parsed.columns.len()) {
1480 let col_name = format!("col_{i}");
1481 all_cols.push(col_name.clone());
1482 query_cols.push(col_name);
1483 }
1484
1485 let tx = self.conn.transaction()?;
1487
1488 tx.execute_batch(&format!(
1490 "CREATE TEMP TABLE IF NOT EXISTS state_results ({})",
1491 all_cols.join(",")
1492 ))?;
1493
1494 let mut new_last_rowid = self.last_rowid;
1495
1496 {
1497 let state_tx = state_conn.transaction()?;
1499 let mut tmp_insert_prepped = tx.prepare_cached(&format!(
1500 "INSERT INTO state_results VALUES ({})",
1501 (0..all_cols.len())
1502 .map(|_| "?")
1503 .collect::<Vec<_>>()
1504 .join(",")
1505 ))?;
1506
1507 for table in tables.iter() {
1508 let start = Instant::now();
1509 let stmt = match self.cached_statements.get(table.as_str()) {
1510 Some(stmt) => stmt,
1511 None => {
1512 warn!(sub_id = %self.id, "no statements pre-computed for table {table}");
1513 continue;
1514 }
1515 };
1516
1517 trace!("SELECT SQL: {}", stmt.new_query);
1518
1519 let mut prepped = state_tx.prepare_cached(&stmt.new_query)?;
1520
1521 let col_count = prepped.column_count();
1522
1523 let mut rows = prepped.raw_query();
1524
1525 while let Some(row) = rows.next()? {
1527 for i in 0..col_count {
1528 tmp_insert_prepped
1529 .raw_bind_parameter(i + 1, SqliteValueRef::from(row.get_ref(i)?))?;
1530 }
1531 tmp_insert_prepped.raw_execute()?;
1532 }
1533
1534 let coalesced_pks = pk_cols
1535 .iter()
1536 .map(|pk| format!("coalesce({pk},\"\")"))
1537 .collect::<Vec<_>>()
1538 .join(",");
1539 let sql = format!(
1540 "INSERT INTO query ({insert_cols})
1541 SELECT * FROM (
1542 SELECT * FROM state_results
1543 EXCEPT
1544 {query_query}
1545 ) WHERE 1
1546 ON CONFLICT({conflict_clause})
1547 DO UPDATE SET
1548 {excluded}
1549 WHERE {excluded_not_same}
1550 RETURNING __corro_rowid,{return_cols}",
1551 insert_cols = all_cols.join(","),
1553 query_query = stmt.temp_query,
1554 conflict_clause = coalesced_pks,
1555 excluded = (0..(self.parsed.columns.len()))
1556 .map(|i| format!("col_{i} = excluded.col_{i}"))
1557 .collect::<Vec<_>>()
1558 .join(","),
1559 excluded_not_same = (0..(self.parsed.columns.len()))
1560 .map(|i| format!("col_{i} IS NOT excluded.col_{i}"))
1561 .collect::<Vec<_>>()
1562 .join(" OR "),
1563 return_cols = query_cols.join(",")
1564 );
1565
1566 trace!("INSERT SQL: {sql}");
1567
1568 let insert_prepped = tx.prepare_cached(&sql)?;
1569
1570 let sql = format!(
1571 "
1572 DELETE FROM query WHERE ({pks}) IN (SELECT {select_pks} FROM (
1573 {query_query}
1574 EXCEPT
1575 SELECT * FROM state_results
1576 )) RETURNING __corro_rowid,{return_cols}
1577 ",
1578 pks = coalesced_pks,
1580 select_pks = coalesced_pks,
1581 query_query = stmt.temp_query,
1582 return_cols = query_cols.join(",")
1583 );
1584
1585 trace!("DELETE SQL: {sql}");
1586
1587 let delete_prepped = tx.prepare_cached(&sql)?;
1588
1589 let mut change_insert_stmt = tx.prepare_cached(&format!(
1590 "INSERT INTO changes (__corro_rowid, {CHANGE_TYPE_COL}, {}) VALUES (?, ?, {}) RETURNING {CHANGE_ID_COL}",
1591 query_cols.join(","),
1592 (0..query_cols.len())
1593 .map(|_i| "?")
1594 .collect::<Vec<_>>()
1595 .join(",")
1596 ))?;
1597
1598 for (change_type, mut prepped) in [
1599 (None, insert_prepped),
1600 (Some(ChangeType::Delete), delete_prepped),
1601 ] {
1602 let col_count = prepped.column_count();
1603
1604 let mut rows = prepped.raw_query();
1605
1606 while let Ok(Some(row)) = rows.next() {
1607 let rowid: RowId = row.get(0)?;
1608
1609 let change_type = change_type.unwrap_or({
1610 if rowid.0 > self.last_rowid {
1611 ChangeType::Insert
1612 } else {
1613 ChangeType::Update
1614 }
1615 });
1616
1617 new_last_rowid = cmp::max(new_last_rowid, rowid.0);
1618
1619 match (1..col_count)
1620 .map(|i| row.get::<_, SqliteValue>(i))
1621 .collect::<rusqlite::Result<Vec<_>>>()
1622 {
1623 Ok(cells) => {
1624 change_insert_stmt.raw_bind_parameter(1, rowid)?;
1625 change_insert_stmt.raw_bind_parameter(2, change_type)?;
1626 for (i, cell) in cells.iter().enumerate() {
1627 change_insert_stmt.raw_bind_parameter(i + 3, cell)?;
1629 }
1630
1631 let mut change_rows = change_insert_stmt.raw_query();
1632
1633 let change_id: ChangeId = change_rows
1634 .next()?
1635 .ok_or(MatcherError::NoChangeInserted)?
1636 .get(0)?;
1637
1638 trace!("got change id: {change_id}");
1639
1640 if !skip_send
1641 && let Err(e) = self.evt_tx.blocking_send(QueryEvent::Change(
1642 change_type,
1643 rowid,
1644 cells,
1645 change_id,
1646 ))
1647 {
1648 warn!("could not send back row to matcher sub sender: {e}");
1649 return Err(MatcherError::EventReceiverClosed);
1650 }
1651 _ = self.last_change_tx.send(change_id);
1652 }
1653 Err(e) => {
1654 error!("could not deserialize row's cells: {e}");
1655 return Ok(());
1656 }
1657 }
1658 }
1659 }
1660 tx.execute_batch("DELETE FROM state_results")?;
1662
1663 let elapsed = start.elapsed();
1664 histogram!("corro.subs.changes.processing.table.duration.seconds", "sql_hash" => self.hash.clone(), "table" => table.0.to_string()).record(elapsed);
1665 }
1666
1667 for table in tables {
1669 tx.prepare_cached(&format!("DELETE FROM temp_{table}",))?
1671 .execute(())?;
1672 trace!("cleaned up temp_{table}");
1673 }
1674 }
1675
1676 tx.commit()?;
1677
1678 trace!("committed!");
1679
1680 self.last_rowid = new_last_rowid;
1681
1682 Ok(())
1683 }
1684}
1685
1686fn dump_query_plan(
1687 conn: &mut Connection,
1688 query: &str,
1689 table_name: &str,
1690 pks: &[String],
1691) -> Result<String, MatcherError> {
1692 let tx = conn.transaction()?;
1693
1694 tx.prepare_cached(&format!(
1695 "CREATE TABLE IF NOT EXISTS __corro_sub.temp_{table_name} ({})",
1696 pks.join(",")
1697 ))?
1698 .execute(())?;
1699
1700 let mut prepped = tx.prepare(&format!("EXPLAIN QUERY PLAN {query}"))?;
1701 let mut rows = prepped.query(())?;
1702
1703 let mut output = String::new();
1704 while let Some(row) = rows.next()? {
1705 output.push_str(&row.get::<_, String>("detail")?);
1706 output.push('\n');
1707 }
1708
1709 Ok(output)
1710}
1711
1712fn interrupt_deadline_guard(conn: &Connection, dur: Duration) -> DropGuard {
1713 let int_handle = conn.get_interrupt_handle();
1714 let cancel = CancellationToken::new();
1715 tokio::spawn({
1716 let cancel = cancel.clone();
1717 async move {
1718 match tokio::time::sleep(dur)
1719 .preemptible(cancel.cancelled())
1720 .await
1721 {
1722 Outcome::Completed(_) => {
1723 warn!("subscription query deadline reached, interrupting!");
1724 int_handle.interrupt();
1725 }
1728 Outcome::Preempted(_) => {
1729 debug!("deadline was canceled, not interrupting query");
1730 }
1731 }
1732 }
1733 });
1734 cancel.drop_guard()
1735}
1736
1737#[derive(Debug, Default, Clone)]
1738pub struct ParsedSelect {
1739 table_columns: IndexMap<String, HashSet<String>>,
1740 aliases: HashMap<String, String>,
1741 pub columns: Vec<ResultColumn>,
1742 children: Vec<ParsedSelect>,
1743}
1744
1745fn extract_select_columns(select: &Select, schema: &Schema) -> Result<ParsedSelect, MatcherError> {
1746 let mut parsed = ParsedSelect::default();
1747
1748 if let OneSelect::Select {
1749 ref from,
1750 ref columns,
1751 ref where_clause,
1752 ..
1753 } = select.body.select
1754 {
1755 let from_table = match from {
1756 Some(from) => {
1757 let from_table = match &from.select {
1758 Some(table) => match table.as_ref() {
1759 SelectTable::Table(name, alias, _) => {
1760 if schema.tables.contains_key(name.name.0.as_ref()) {
1761 if let Some(As::As(alias) | As::Elided(alias)) = alias {
1762 parsed
1763 .aliases
1764 .insert(alias.0.to_string(), name.name.0.to_string());
1765 } else if let Some(ref alias) = name.alias {
1766 parsed
1767 .aliases
1768 .insert(alias.0.to_string(), name.name.0.to_string());
1769 }
1770 parsed
1771 .table_columns
1772 .entry(name.name.0.to_string())
1773 .or_default();
1774 Some(&name.name)
1775 } else {
1776 return Err(MatcherError::TableNotFound(name.name.0.to_string()));
1777 }
1778 }
1779 t => {
1784 warn!("ignoring {t:?}");
1785 None
1786 }
1787 },
1788 _ => {
1789 unreachable!()
1792 }
1793 };
1794 if let Some(ref joins) = from.joins {
1795 for join in joins.iter() {
1796 let tbl_name = match &join.table {
1798 SelectTable::Table(name, alias, _) => {
1799 if let Some(As::As(alias) | As::Elided(alias)) = alias {
1800 parsed
1801 .aliases
1802 .insert(alias.0.to_string(), name.name.0.to_string());
1803 } else if let Some(ref alias) = name.alias {
1804 parsed
1805 .aliases
1806 .insert(alias.0.to_string(), name.name.0.to_string());
1807 }
1808 parsed
1809 .table_columns
1810 .entry(name.name.0.to_string())
1811 .or_default();
1812 &name.name
1813 }
1814 t => {
1819 warn!("ignoring JOIN's non-SelectTable::Table: {t:?}");
1820 continue;
1821 }
1822 };
1823 if let Some(constraint) = &join.constraint {
1825 match constraint {
1826 JoinConstraint::On(expr) => {
1827 extract_expr_columns(expr, schema, &mut parsed)?;
1828 }
1829 JoinConstraint::Using(names) => {
1830 let entry = parsed
1831 .table_columns
1832 .entry(tbl_name.0.to_string())
1833 .or_default();
1834 for name in names.iter() {
1835 insert_col(entry, schema, &tbl_name.0, &name.0);
1836 }
1837 }
1838 }
1839 }
1840 }
1841 }
1842 if let Some(expr) = where_clause {
1843 extract_expr_columns(expr, schema, &mut parsed)?;
1844 }
1845 from_table
1846 }
1847 _ => None,
1848 };
1849
1850 extract_columns(columns.as_slice(), from_table, schema, &mut parsed)?;
1851 }
1852
1853 Ok(parsed)
1854}
1855
1856fn insert_col(set: &mut HashSet<String>, schema: &Schema, tbl_name: &str, name: &str) {
1857 let table = schema.tables.get(tbl_name);
1858 if let Some(generated) =
1859 table.and_then(|tbl| tbl.columns.get(name).and_then(|col| col.generated.as_ref()))
1860 {
1861 for name in generated.from.iter() {
1863 insert_col(set, schema, tbl_name, name);
1864 }
1865 } else {
1866 set.insert(name.to_owned());
1867 }
1868}
1869
1870fn extract_expr_columns(
1871 expr: &Expr,
1872 schema: &Schema,
1873 parsed: &mut ParsedSelect,
1874) -> Result<(), MatcherError> {
1875 match expr {
1876 Expr::Qualified(tblname, colname) => {
1878 let resolved_name = parsed
1879 .aliases
1880 .get(tblname.0.as_ref())
1881 .map(|s| s.as_str())
1882 .unwrap_or(tblname.0.as_ref());
1883 insert_col(
1885 parsed
1886 .table_columns
1887 .entry(resolved_name.to_string())
1888 .or_default(),
1889 schema,
1890 resolved_name,
1891 &colname.0,
1892 );
1893 }
1894 Expr::DoublyQualified(schema_name, tblname, colname)
1896 if schema_name.0.as_ref() == "main" =>
1897 {
1898 let resolved_name = parsed
1899 .aliases
1900 .get(tblname.0.as_ref())
1901 .map(|s| s.as_str())
1902 .unwrap_or(tblname.0.as_ref());
1903 insert_col(
1905 parsed
1906 .table_columns
1907 .entry(resolved_name.to_string())
1908 .or_default(),
1909 schema,
1910 resolved_name,
1911 &colname.0,
1912 );
1913 }
1914
1915 Expr::Name(colname) => {
1916 let check_col_name = unquote(&colname.0).ok().unwrap_or(colname.0.to_string());
1917
1918 let mut found = None;
1919 for tbl in parsed.table_columns.keys() {
1920 if let Some(tbl) = schema.tables.get(tbl)
1921 && tbl.columns.contains_key(&check_col_name)
1922 {
1923 if found.is_some() {
1924 return Err(MatcherError::QualificationRequired {
1925 col_name: check_col_name,
1926 });
1927 }
1928 found = Some(tbl.name.as_str());
1929 }
1930 }
1931
1932 if let Some(found) = found {
1933 insert_col(
1934 parsed.table_columns.entry(found.to_owned()).or_default(),
1935 schema,
1936 found,
1937 &check_col_name,
1938 );
1939 } else {
1940 return Err(MatcherError::TableForColumnNotFound {
1941 col_name: check_col_name,
1942 });
1943 }
1944 }
1945
1946 Expr::Id(colname) => {
1947 let check_col_name = unquote(&colname.0).ok().unwrap_or(colname.0.to_string());
1948
1949 let mut found = None;
1950 for tbl in parsed.table_columns.keys() {
1951 if let Some(tbl) = schema.tables.get(tbl)
1952 && tbl.columns.contains_key(&check_col_name)
1953 {
1954 if found.is_some() {
1955 return Err(MatcherError::QualificationRequired {
1956 col_name: check_col_name,
1957 });
1958 }
1959 found = Some(tbl.name.as_str());
1960 }
1961 }
1962
1963 if let Some(found) = found {
1964 insert_col(
1965 parsed.table_columns.entry(found.to_owned()).or_default(),
1966 schema,
1967 found,
1968 &colname.0,
1969 );
1970 } else {
1971 if colname.0.starts_with('"') {
1972 return Ok(());
1973 }
1974 return Err(MatcherError::TableForColumnNotFound {
1975 col_name: colname.0.to_string(),
1976 });
1977 }
1978 }
1979
1980 Expr::Between { lhs, .. } => extract_expr_columns(lhs, schema, parsed)?,
1981 Expr::Binary(lhs, _, rhs) => {
1982 extract_expr_columns(lhs, schema, parsed)?;
1983 extract_expr_columns(rhs, schema, parsed)?;
1984 }
1985 Expr::Case {
1986 base,
1987 when_then_pairs,
1988 else_expr,
1989 } => {
1990 if let Some(expr) = base {
1991 extract_expr_columns(expr, schema, parsed)?;
1992 }
1993 for (when_expr, _then_expr) in when_then_pairs.iter() {
1994 extract_expr_columns(when_expr, schema, parsed)?;
1996 }
1997 if let Some(expr) = else_expr {
1998 extract_expr_columns(expr, schema, parsed)?;
1999 }
2000 }
2001 Expr::Cast { expr, .. } => extract_expr_columns(expr, schema, parsed)?,
2002 Expr::Collate(expr, _) => extract_expr_columns(expr, schema, parsed)?,
2003 Expr::Exists(select) => {
2004 parsed
2005 .children
2006 .push(extract_select_columns(select, schema)?);
2007 }
2008 Expr::FunctionCall { args, .. } => {
2009 if let Some(args) = args {
2010 for expr in args.iter() {
2011 extract_expr_columns(expr, schema, parsed)?;
2012 }
2013 }
2014 }
2015 Expr::InList { lhs, rhs, .. } => {
2016 extract_expr_columns(lhs, schema, parsed)?;
2017 if let Some(rhs) = rhs {
2018 for expr in rhs.iter() {
2019 extract_expr_columns(expr, schema, parsed)?;
2020 }
2021 }
2022 }
2023 Expr::InSelect { lhs, rhs, .. } => {
2024 extract_expr_columns(lhs, schema, parsed)?;
2025 parsed.children.push(extract_select_columns(rhs, schema)?);
2026 }
2027 expr @ Expr::InTable { .. } => {
2028 return Err(MatcherError::UnsupportedExpr { expr: expr.clone() });
2029 }
2030 Expr::IsNull(expr) => {
2031 extract_expr_columns(expr, schema, parsed)?;
2032 }
2033 Expr::Like { lhs, rhs, .. } => {
2034 extract_expr_columns(lhs, schema, parsed)?;
2035 extract_expr_columns(rhs, schema, parsed)?;
2036 }
2037
2038 Expr::NotNull(expr) => {
2039 extract_expr_columns(expr, schema, parsed)?;
2040 }
2041 Expr::Parenthesized(parens) => {
2042 for expr in parens.iter() {
2043 extract_expr_columns(expr, schema, parsed)?;
2044 }
2045 }
2046 Expr::Subquery(select) => {
2047 parsed
2048 .children
2049 .push(extract_select_columns(select, schema)?);
2050 }
2051 Expr::Unary(_, expr) => {
2052 extract_expr_columns(expr, schema, parsed)?;
2053 }
2054
2055 _ => {}
2062 }
2063
2064 Ok(())
2065}
2066
2067fn extract_columns(
2068 columns: &[ResultColumn],
2069 from: Option<&Name>,
2070 schema: &Schema,
2071 parsed: &mut ParsedSelect,
2072) -> Result<(), MatcherError> {
2073 let mut i = 0;
2074 for col in columns.iter() {
2075 match col {
2076 ResultColumn::Expr(expr, _) => {
2077 extract_expr_columns(expr, schema, parsed)?;
2079 parsed.columns.push(ResultColumn::Expr(
2080 expr.clone(),
2081 Some(As::As(Name(format!("col_{i}").into()))),
2082 ));
2083 i += 1;
2084 }
2085 ResultColumn::Star => {
2086 if let Some(tbl_name) = from {
2087 if let Some(table) = schema.tables.get(tbl_name.0.as_ref()) {
2088 let entry = parsed.table_columns.entry(table.name.clone()).or_default();
2089 for col in table.columns.keys() {
2090 entry.insert(col.clone());
2091 parsed.columns.push(ResultColumn::Expr(
2092 Expr::Name(Name(col.clone().into())),
2093 Some(As::As(Name(format!("col_{i}").into()))),
2094 ));
2095 i += 1;
2096 }
2097 } else {
2098 return Err(MatcherError::TableStarNotFound {
2099 tbl_name: tbl_name.0.to_string(),
2100 });
2101 }
2102 } else {
2103 unreachable!()
2104 }
2105 }
2106 ResultColumn::TableStar(tbl_name) => {
2107 let name = parsed
2108 .aliases
2109 .get(tbl_name.0.as_ref())
2110 .map(|s| s.as_str())
2111 .unwrap_or(tbl_name.0.as_ref());
2112 if let Some(table) = schema.tables.get(name) {
2113 let entry = parsed.table_columns.entry(table.name.clone()).or_default();
2114 for col in table.columns.keys() {
2115 entry.insert(col.clone());
2116 parsed.columns.push(ResultColumn::Expr(
2117 Expr::Qualified(tbl_name.clone(), Name(col.clone().into())),
2118 Some(As::As(Name(format!("col_{i}").into()))),
2119 ));
2120 i += 1;
2121 }
2122 } else {
2123 return Err(MatcherError::TableStarNotFound {
2124 tbl_name: name.to_string(),
2125 });
2126 }
2127 }
2128 }
2129 }
2130 Ok(())
2131}
2132
2133fn table_to_expr(
2134 aliases: &HashMap<String, String>,
2135 tbl: &Table,
2136 table: &str,
2137) -> Result<Expr, MatcherError> {
2138 let tbl_name = aliases
2139 .iter()
2140 .find_map(|(alias, actual)| (actual == table).then_some(alias))
2141 .cloned()
2142 .unwrap_or_else(|| table.to_owned());
2143
2144 let expr = Expr::in_table(
2145 Expr::Parenthesized(
2146 tbl.pk
2147 .iter()
2148 .map(|pk| {
2149 Expr::Qualified(Name(tbl_name.clone().into()), Name(pk.to_owned().into()))
2150 })
2151 .collect(),
2152 ),
2153 false,
2154 QualifiedName::fullname(
2155 Name("__corro_sub".into()),
2156 Name(format!("temp_{table}").into()),
2157 ),
2158 None,
2159 );
2160
2161 Ok(expr)
2162}
2163
2164#[derive(Debug, thiserror::Error)]
2165pub enum MatcherError {
2166 #[error(transparent)]
2167 Lexer(#[from] sqlite3_parser::lexer::sql::Error),
2168 #[error("one statement is required for matching")]
2169 StatementRequired,
2170 #[error("unsupported statement")]
2171 UnsupportedStatement,
2172 #[error("at least 1 table is required in FROM / JOIN clause")]
2173 TableRequired,
2174 #[error(transparent)]
2175 Sqlite(#[from] rusqlite::Error),
2176 #[error("table not found in schema: {0}")]
2177 TableNotFound(String),
2178 #[error("no primary key for table: {0}")]
2179 NoPrimaryKey(String),
2180 #[error("aggregate missing primary key {0}.{1}")]
2181 AggPrimaryKeyMissing(String, String),
2182 #[error("JOIN .. ON expression is not supported for join on table '{table}': {expr:?}")]
2183 JoinOnExprUnsupported { table: String, expr: Box<Expr> },
2184 #[error("expression is not supported: {expr:?}")]
2185 UnsupportedExpr { expr: Expr },
2186 #[error("could not find table for {tbl_name}.* in corrosion's schema")]
2187 TableStarNotFound { tbl_name: String },
2188 #[error("<tbl>.{col_name} qualification required for ambiguous column name")]
2189 QualificationRequired { col_name: String },
2190 #[error("could not find table for column {col_name}")]
2191 TableForColumnNotFound { col_name: String },
2192 #[error("missing primary keys, this shouldn't happen")]
2193 MissingPrimaryKeys,
2194 #[error("change queue has been closed or is full")]
2195 ChangeQueueClosedOrFull,
2196 #[error("no change was inserted, this is not supposed to happen")]
2197 NoChangeInserted,
2198 #[error("change receiver is closed")]
2199 EventReceiverClosed,
2200 #[error(transparent)]
2201 Unpack(#[from] UnpackError),
2202 #[error("did not insert subscription")]
2203 InsertSub,
2204 #[error(transparent)]
2205 FromSql(#[from] FromSqlError),
2206 #[error(transparent)]
2207 Io(#[from] std::io::Error),
2208 #[error("could not encode changeset for queue: {0}")]
2209 ChangesetEncode(#[from] speedy::Error),
2210 #[error("queue is full")]
2211 QueueFull,
2212 #[error("cannot restore existing subscription")]
2213 CannotRestoreExisting,
2214 #[error("could not acquire write permit")]
2215 WritePermitAcquire(#[from] AcquireError),
2216 #[error("subscription is not running")]
2217 NotRunning,
2218 #[error("subscription restore is missing SQL query")]
2219 MissingSql,
2220}
2221
2222impl MatcherError {
2223 pub fn is_event_recv_closed(&self) -> bool {
2224 matches!(self, MatcherError::EventReceiverClosed)
2225 }
2226}
2227
2228#[derive(Debug, thiserror::Error)]
2229#[allow(clippy::large_enum_variant)]
2230pub enum NormalizeStatementError {
2231 #[error(transparent)]
2232 Parse(#[from] sqlite3_parser::lexer::sql::Error),
2233 #[error("unexpected statement: {0}")]
2234 UnexpectedStatement(Box<Cmd>),
2235 #[error("only 1 statement is supported")]
2236 Multiple,
2237 #[error("at least 1 statement is required")]
2238 NoStatement,
2239}
2240
2241pub fn normalize_sql(sql: &str) -> Result<String, NormalizeStatementError> {
2242 let mut parser = Parser::new(sql.as_bytes());
2243
2244 let stmt = match parser.next()? {
2245 Some(Cmd::Stmt(stmt)) => stmt,
2246 Some(cmd) => {
2247 return Err(NormalizeStatementError::UnexpectedStatement(Box::new(cmd)));
2248 }
2249 None => {
2250 return Err(NormalizeStatementError::NoStatement);
2251 }
2252 };
2253
2254 if parser.next()?.is_some() {
2255 return Err(NormalizeStatementError::Multiple);
2256 }
2257
2258 Ok(Cmd::Stmt(stmt).to_string())
2259}
2260
2261#[derive(Debug, thiserror::Error)]
2262pub enum PackError {
2263 #[error("abort")]
2264 Abort,
2265}
2266
2267pub fn pack_columns(args: &[SqliteValue]) -> Result<Vec<u8>, PackError> {
2268 let mut buf = vec![];
2269 let len_result: Result<u8, _> = args.len().try_into();
2282 if let Ok(len) = len_result {
2283 buf.put_u8(len);
2284 for value in args {
2285 match value {
2286 SqliteValue::Null => {
2287 buf.put_u8(ColumnType::Null as u8);
2288 }
2289 SqliteValue::Integer(val) => {
2290 let num_bytes_for_int = num_bytes_needed_i64(*val);
2291 let type_byte = num_bytes_for_int << 3 | (ColumnType::Integer as u8);
2292 buf.put_u8(type_byte);
2293 buf.put_int(*val, num_bytes_for_int as usize);
2294 }
2295 SqliteValue::Real(v) => {
2296 buf.put_u8(ColumnType::Float as u8);
2297 buf.put_f64(v.0);
2298 }
2299 SqliteValue::Text(value) => {
2300 let len = value.len() as i32;
2301 let num_bytes_for_len = num_bytes_needed_i32(len);
2302 let type_byte = num_bytes_for_len << 3 | (ColumnType::Text as u8);
2303 buf.put_u8(type_byte);
2304 buf.put_int(len as i64, num_bytes_for_len as usize);
2305 buf.put_slice(value.as_bytes());
2306 }
2307 SqliteValue::Blob(value) => {
2308 let len = value.len() as i32;
2309 let num_bytes_for_len = num_bytes_needed_i32(len);
2310 let type_byte = num_bytes_for_len << 3 | (ColumnType::Blob as u8);
2311 buf.put_u8(type_byte);
2312 buf.put_int(len as i64, num_bytes_for_len as usize);
2313 buf.put_slice(value);
2314 }
2315 }
2316 }
2317 Ok(buf)
2318 } else {
2319 Err(PackError::Abort)
2320 }
2321}
2322
2323fn num_bytes_needed_i64(val: i64) -> u8 {
2324 if val & 0xFF00000000000000u64 as i64 != 0 {
2325 8
2326 } else if val & 0x00FF000000000000 != 0 {
2327 7
2328 } else if val & 0x0000FF0000000000 != 0 {
2329 6
2330 } else if val & 0x000000FF00000000 != 0 {
2331 5
2332 } else {
2333 num_bytes_needed_i32(val as i32)
2334 }
2335}
2336
2337fn num_bytes_needed_i32(val: i32) -> u8 {
2338 if val & 0xFF000000u32 as i32 != 0 {
2339 4
2340 } else if val & 0x00FF0000 != 0 {
2341 3
2342 } else if val & 0x0000FF00 != 0 {
2343 2
2344 } else if val * 0x000000FF != 0 {
2345 1
2346 } else {
2347 0
2348 }
2349}
2350
2351#[derive(Debug, thiserror::Error)]
2352pub enum UnpackError {
2353 #[error("abort")]
2354 Abort,
2355 #[error("misuse")]
2356 Misuse,
2357}
2358
2359pub fn unpack_columns(mut buf: &[u8]) -> Result<Vec<SqliteValueRef<'_>>, UnpackError> {
2360 let mut ret = vec![];
2361 let num_columns = buf.get_u8();
2362
2363 for _i in 0..num_columns {
2364 if !buf.has_remaining() {
2365 return Err(UnpackError::Abort);
2366 }
2367 let column_type_and_maybe_intlen = buf.get_u8();
2368 let column_type = ColumnType::from_u8(column_type_and_maybe_intlen & 0x07);
2369 let intlen = (column_type_and_maybe_intlen >> 3) as usize;
2370
2371 match column_type {
2372 Some(ColumnType::Blob) => {
2373 if buf.remaining() < intlen {
2374 return Err(UnpackError::Abort);
2375 }
2376 let len = buf.get_int(intlen) as usize;
2377 if buf.remaining() < len {
2378 return Err(UnpackError::Abort);
2379 }
2380 ret.push(SqliteValueRef(ValueRef::Blob(&buf[0..len])));
2381 buf.advance(len);
2382 }
2383 Some(ColumnType::Float) => {
2384 if buf.remaining() < 8 {
2385 return Err(UnpackError::Abort);
2386 }
2387 ret.push(SqliteValueRef(ValueRef::Real(buf.get_f64())));
2388 }
2389 Some(ColumnType::Integer) => {
2390 if buf.remaining() < intlen {
2391 return Err(UnpackError::Abort);
2392 }
2393 ret.push(SqliteValueRef(ValueRef::Integer(buf.get_int(intlen))));
2394 }
2395 Some(ColumnType::Null) => {
2396 ret.push(SqliteValueRef(ValueRef::Null));
2397 }
2398 Some(ColumnType::Text) => {
2399 if buf.remaining() < intlen {
2400 return Err(UnpackError::Abort);
2401 }
2402 let len = buf.get_int(intlen) as usize;
2403 if buf.remaining() < len {
2404 return Err(UnpackError::Abort);
2405 }
2406 ret.push(SqliteValueRef(ValueRef::Text(&buf[0..len])));
2407 buf.advance(len);
2408 }
2409 None => return Err(UnpackError::Misuse),
2410 }
2411 }
2412
2413 Ok(ret)
2414}
2415
2416#[cfg(test)]
2417mod tests {
2418 use std::net::Ipv4Addr;
2419
2420 use crate::spawn::wait_for_all_pending_handles;
2421 use camino::Utf8PathBuf;
2422 use rusqlite::params;
2423 use tokio::sync::Semaphore;
2424
2425 use crate::{
2426 actor::ActorId,
2427 agent::migrate,
2428 base::CrsqlDbVersion,
2429 change::row_to_change,
2430 schema::{apply_schema, parse_sql},
2431 sqlite::{CrConn, setup_conn},
2432 };
2433 use klukai_tests::tempdir::TempDir;
2434
2435 use super::*;
2436
2437 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2438 async fn test_matcher() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
2439 _ = tracing_subscriber::fmt::try_init();
2440 let (tripwire, tripwire_worker, tripwire_tx) = Tripwire::new_simple();
2441 let schema_sql = "CREATE TABLE sw (pk TEXT NOT NULL PRIMARY KEY, sandwich TEXT);";
2442 let mut schema = parse_sql(schema_sql)?;
2443
2444 let sql = "SELECT sandwich FROM sw WHERE pk=\"mad\"";
2445
2446 let subs = SubsManager::default();
2447
2448 let tmpdir = TempDir::new(tempfile::tempdir()?);
2449 let db_path = tmpdir.path().join("test.db");
2450 println!("db_path: {}", db_path.display());
2451 let subscriptions_path: Utf8PathBuf =
2452 tmpdir.path().join("subs").display().to_string().into();
2453
2454 let pool = SplitPool::create(db_path, Arc::new(Semaphore::new(1))).await?;
2455 let clock = Arc::new(uhlc::HLC::default());
2456
2457 {
2458 let mut conn = pool.write_priority().await?;
2459 setup_conn(&conn)?;
2460 migrate(clock, &mut conn)?;
2461 let tx = conn.transaction()?;
2462 apply_schema(&tx, &Schema::default(), &mut schema)?;
2463 tx.commit()?;
2464 }
2465
2466 {
2467 let (handle, maybe_created) = subs.get_or_insert(
2468 sql,
2469 subscriptions_path.as_path(),
2470 &schema,
2471 &pool,
2472 tripwire.clone(),
2473 )?;
2474
2475 assert!(maybe_created.is_some());
2476
2477 handle.cleanup().await;
2478 subs.remove(&handle.id());
2479 }
2480
2481 tripwire_tx.send(()).await.ok();
2482 tripwire_worker.await;
2483 wait_for_all_pending_handles().await;
2484
2485 Ok(())
2486 }
2487
2488 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2489 async fn test_diff() {
2490 _ = tracing_subscriber::fmt::try_init();
2491
2492 let (tripwire, tripwire_worker, tripwire_tx) = Tripwire::new_simple();
2493
2494 let sql = "SELECT json_object(
2495 'targets', json_array(cs.address||':'||cs.port),
2496 'labels', json_object(
2497 '__metrics_path__', JSON_EXTRACT(cs.meta, '$.path'),
2498 'app', cs.app_name,
2499 'vm_account_id', cs.organization_id,
2500 'instance', cs.instance_id
2501 )
2502 )
2503 FROM consul_services cs
2504 LEFT JOIN machines m ON m.id = cs.instance_id
2505 LEFT JOIN machine_versions mv ON m.id = mv.machine_id AND m.machine_version_id = mv.id
2506 LEFT JOIN machine_version_statuses mvs ON m.id = mvs.machine_id AND m.machine_version_id = mvs.id
2507 WHERE cs.node = 'test-hostname'
2508 AND (mvs.status IS NULL OR mvs.status = 'started')
2509 AND cs.name == 'app-prometheus'";
2510
2511 let schema_sql = "
2512 CREATE TABLE consul_services (
2513 node TEXT NOT NULL,
2514 id TEXT NOT NULL,
2515 name TEXT NOT NULL DEFAULT '',
2516 tags TEXT NOT NULL DEFAULT '[]',
2517 meta TEXT NOT NULL DEFAULT '{}',
2518 port INTEGER NOT NULL DEFAULT 0,
2519 address TEXT NOT NULL DEFAULT '',
2520 updated_at INTEGER NOT NULL DEFAULT 0,
2521 app_id INTEGER AS (CAST(JSON_EXTRACT(meta, '$.app_id') AS INTEGER)), network_id INTEGER AS (
2522 CAST(JSON_EXTRACT(meta, '$.network_id') AS INTEGER)
2523 ), app_name TEXT AS (JSON_EXTRACT(meta, '$.app_name')), instance_id TEXT AS (
2524 COALESCE(
2525 JSON_EXTRACT(meta, '$.machine_id'),
2526 SUBSTR(JSON_EXTRACT(meta, '$.alloc_id'), 1, 8),
2527 CASE
2528 WHEN INSTR(id, '_nomad-task-') = 1 THEN SUBSTR(id, 13, 8)
2529 ELSE NULL
2530 END
2531 )
2532 ), organization_id INTEGER AS (
2533 CAST(
2534 JSON_EXTRACT(meta, '$.organization_id') AS INTEGER
2535 )
2536 ), protocol TEXT
2537 AS (JSON_EXTRACT(meta, '$.protocol')),
2538 PRIMARY KEY (node, id)
2539 );
2540
2541 CREATE TABLE machines (
2542 id TEXT NOT NULL PRIMARY KEY,
2543 node TEXT NOT NULL DEFAULT '',
2544 name TEXT NOT NULL DEFAULT '',
2545 machine_version_id TEXT NOT NULL DEFAULT '',
2546 app_id INTEGER NOT NULL DEFAULT 0,
2547 organization_id INTEGER NOT NULL DEFAULT 0,
2548 network_id INTEGER NOT NULL DEFAULT 0,
2549 updated_at INTEGER NOT NULL DEFAULT 0
2550 );
2551
2552 CREATE TABLE machine_versions (
2553 machine_id TEXT NOT NULL,
2554 id TEXT NOT NULL DEFAULT '',
2555 config TEXT NOT NULL DEFAULT '{}',
2556 updated_at INTEGER NOT NULL DEFAULT 0,
2557 PRIMARY KEY (machine_id, id)
2558 );
2559
2560 CREATE TABLE machine_version_statuses (
2561 machine_id TEXT NOT NULL,
2562 id TEXT NOT NULL,
2563 status TEXT NOT NULL DEFAULT '',
2564 updated_at INTEGER NOT NULL DEFAULT 0,
2565 PRIMARY KEY (machine_id, id)
2566 );
2567 ";
2568
2569 let mut schema = parse_sql(schema_sql).unwrap();
2570 let subs = SubsManager::default();
2571
2572 let tmpdir = TempDir::new(tempfile::tempdir().unwrap());
2573 let db_path = tmpdir.path().join("test.db");
2574 let subscriptions_path: Utf8PathBuf =
2575 tmpdir.path().join("subs").display().to_string().into();
2576
2577 let pool = SplitPool::create(&db_path, Arc::new(Semaphore::new(1)))
2578 .await
2579 .unwrap();
2580 let mut conn = pool.write_priority().await.unwrap();
2581 let clock = Arc::new(uhlc::HLC::default());
2582 {
2583 setup_conn(&conn).unwrap();
2584 migrate(clock.clone(), &mut conn).unwrap();
2585 let tx = conn.transaction().unwrap();
2586 apply_schema(&tx, &Schema::default(), &mut schema).unwrap();
2587 tx.commit().unwrap();
2588 }
2589
2590 {
2592 let tx = conn.transaction().unwrap();
2593 tx.execute_batch(r#"
2594 INSERT INTO consul_services (node, id, name, address, port, meta) VALUES ('test-hostname', 'service-1', 'app-prometheus', '127.0.0.1', 1, '{"path": "/1", "machine_id": "m-1"}');
2595
2596 INSERT INTO machines (id, machine_version_id) VALUES ('m-1', 'mv-1');
2597
2598 INSERT INTO machine_versions (machine_id, id) VALUES ('m-1', 'mv-1');
2599
2600 INSERT INTO machine_version_statuses (machine_id, id, status) VALUES ('m-1', 'mv-1', 'started');
2601
2602 INSERT INTO consul_services (node, id, name, address, port, meta) VALUES ('test-hostname', 'service-2', 'not-app-prometheus', '127.0.0.1', 1, '{"path": "/1", "machine_id": "m-2"}');
2603
2604 INSERT INTO machines (id, machine_version_id) VALUES ('m-2', 'mv-2');
2605
2606 INSERT INTO machine_versions (machine_id, id) VALUES ('m-2', 'mv-2');
2607
2608 INSERT INTO machine_version_statuses (machine_id, id, status) VALUES ('m-2', 'mv-2', 'started');
2609 "#).unwrap();
2610 tx.commit().unwrap();
2611 }
2612
2613 {
2614 let mut conn2 = CrConn::init(
2615 rusqlite::Connection::open(tmpdir.path().join("test2.db"))
2616 .expect("could not open conn"),
2617 )
2618 .expect("could not init crsql");
2619
2620 setup_conn(&conn2).unwrap();
2621 migrate(clock.clone(), &mut conn2).unwrap();
2622
2623 {
2624 let tx = conn2.transaction().unwrap();
2625 apply_schema(&tx, &Schema::default(), &mut schema).unwrap();
2626 tx.commit().unwrap();
2627 }
2628
2629 let changes = {
2630 let mut prepped = conn.prepare_cached(r#"SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl FROM crsql_changes WHERE db_version = ? ORDER BY seq ASC"#).unwrap();
2631 let rows = prepped.query_map([1], row_to_change).unwrap();
2632
2633 let mut changes = vec![];
2634
2635 for row in rows {
2636 changes.push(row.unwrap());
2637 }
2638 changes
2639 };
2640
2641 let tx = conn2.transaction().unwrap();
2642
2643 for change in changes {
2644 debug!("change: {change:?}");
2645 tx.prepare_cached(
2646 r#"
2647 INSERT INTO crsql_changes
2648 ("table", pk, cid, val, col_version, db_version, site_id, cl, seq)
2649 VALUES
2650 (?, ?, ?, ?, ?, ?, ?, ?, ?)
2651 "#,
2652 )
2653 .unwrap()
2654 .execute(params![
2655 change.table.as_str(),
2656 change.pk,
2657 change.cid.as_str(),
2658 &change.val,
2659 change.col_version,
2660 change.db_version,
2661 &change.site_id,
2662 change.cl,
2663 change.seq
2664 ])
2665 .unwrap();
2666 }
2667 }
2668
2669 let matcher_conn =
2670 CrConn::init(rusqlite::Connection::open(&db_path).expect("could not open conn"))
2671 .expect("could not init crconn");
2672
2673 setup_conn(&matcher_conn).unwrap();
2674
2675 let mut last_change_id = None;
2676
2677 let id = {
2678 let (matcher, maybe_created) = subs
2681 .get_or_insert(
2682 sql,
2683 subscriptions_path.as_path(),
2684 &schema,
2685 &pool,
2686 tripwire.clone(),
2687 )
2688 .unwrap();
2689
2690 let mut rx = maybe_created.unwrap().evt_rx;
2691
2692 println!("matcher created w/ id: {}", matcher.id().as_simple());
2693 println!("parsed: {:?}", matcher.inner.parsed);
2694
2695 assert!(matches!(rx.recv().await.unwrap(), QueryEvent::Columns(_)));
2696
2697 let cells = vec![SqliteValue::Text("{\"targets\":[\"127.0.0.1:1\"],\"labels\":{\"__metrics_path__\":\"/1\",\"app\":null,\"vm_account_id\":null,\"instance\":\"m-1\"}}".into())];
2698
2699 assert_eq!(rx.recv().await.unwrap(), QueryEvent::Row(RowId(1), cells));
2700 println!("received a row");
2701 assert!(matches!(
2702 rx.recv().await.unwrap(),
2703 QueryEvent::EndOfQuery { .. }
2704 ));
2705 println!("received end of query");
2706
2707 {
2709 let tx = conn.transaction().unwrap();
2710 tx.execute_batch(r#"
2711 INSERT INTO consul_services (node, id, name, address, port, meta) VALUES ('test-hostname', 'service-3', 'app-prometheus', '127.0.0.1', 1, '{"path": "/1", "machine_id": "m-3"}');
2712
2713 INSERT INTO machines (id, machine_version_id) VALUES ('m-3', 'mv-3');
2714
2715 INSERT INTO machine_versions (machine_id, id) VALUES ('m-3', 'mv-3');
2716
2717 INSERT INTO machine_version_statuses (machine_id, id, status) VALUES ('m-3', 'mv-3', 'started');
2718 "#).unwrap();
2719 tx.commit().unwrap();
2720 }
2721
2722 println!("processing change...");
2723 filter_changes_from_db(&matcher, &conn, None, CrsqlDbVersion(2)).unwrap();
2724 println!("processed changes");
2725
2726 let cells = vec![SqliteValue::Text("{\"targets\":[\"127.0.0.1:1\"],\"labels\":{\"__metrics_path__\":\"/1\",\"app\":null,\"vm_account_id\":null,\"instance\":\"m-3\"}}".into())];
2727
2728 assert_eq!(
2729 rx.recv().await.unwrap(),
2730 QueryEvent::Change(ChangeType::Insert, RowId(2), cells, ChangeId(1))
2731 );
2732
2733 println!("received change");
2734
2735 {
2737 let tx = conn.transaction().unwrap();
2738 tx.execute_batch(r#"
2739 DELETE FROM consul_services where node = 'test-hostname' AND id = 'service-1';
2740 "#).unwrap();
2741 tx.commit().unwrap();
2742 }
2743
2744 filter_changes_from_db(&matcher, &conn, None, CrsqlDbVersion(3)).unwrap();
2745
2746 let cells = vec![SqliteValue::Text("{\"targets\":[\"127.0.0.1:1\"],\"labels\":{\"__metrics_path__\":\"/1\",\"app\":null,\"vm_account_id\":null,\"instance\":\"m-1\"}}".into())];
2747
2748 println!("waiting for a change (A)");
2749
2750 assert_eq!(
2751 rx.recv().await.unwrap(),
2752 QueryEvent::Change(ChangeType::Delete, RowId(1), cells, ChangeId(2))
2753 );
2754
2755 println!("got change (A)");
2756
2757 {
2759 let tx = conn.transaction().unwrap();
2760 let n = tx.execute(r#"
2761 UPDATE consul_services SET address = '127.0.0.2' WHERE node = 'test-hostname' AND id = 'service-3';
2762 "#, ()).unwrap();
2763 assert_eq!(n, 1);
2764 tx.commit().unwrap();
2765 }
2766
2767 filter_changes_from_db(&matcher, &conn, None, CrsqlDbVersion(4)).unwrap();
2768
2769 let cells = vec![SqliteValue::Text("{\"targets\":[\"127.0.0.2:1\"],\"labels\":{\"__metrics_path__\":\"/1\",\"app\":null,\"vm_account_id\":null,\"instance\":\"m-3\"}}".into())];
2770
2771 println!("waiting for a change (B)");
2772
2773 assert_eq!(
2774 rx.recv().await.unwrap(),
2775 QueryEvent::Change(ChangeType::Update, RowId(2), cells, ChangeId(3))
2776 );
2777
2778 println!("got change (B)");
2779
2780 assert_eq!(rx.try_recv(), Err(mpsc::error::TryRecvError::Empty));
2784
2785 let range = 4u32..1000u32;
2788
2789 {
2790 let tx = conn.transaction().unwrap();
2791
2792 for n in range.clone() {
2793 let svc_id = format!("service-{n}");
2794 let ip = Ipv4Addr::from(n).to_string();
2795 let port = n;
2796 let machine_id = format!("m-{n}");
2797 let mv = format!("mv-{n}");
2798 let meta =
2799 serde_json::json!({"path": format!("/path-{n}"), "machine_id": machine_id});
2800 tx.execute("
2801 INSERT INTO consul_services (node, id, name, address, port, meta) VALUES ('test-hostname', ?, 'app-prometheus', ?, ?, ?);
2802 ", params![svc_id, ip, port, meta]).unwrap();
2803 tx.execute(
2804 "
2805 INSERT INTO machines (id, machine_version_id) VALUES (?, ?);
2806 ",
2807 params![machine_id, mv],
2808 )
2809 .unwrap();
2810
2811 tx.execute(
2812 "
2813 INSERT INTO machine_versions (machine_id, id) VALUES (?, ?);
2814 ",
2815 params![machine_id, mv],
2816 )
2817 .unwrap();
2818
2819 tx.execute("
2820 INSERT INTO machine_version_statuses (machine_id, id, status) VALUES (?, ?, 'started');", params![machine_id, mv]).unwrap();
2821 }
2822
2823 tx.commit().unwrap();
2824 }
2825
2826 filter_changes_from_db(&matcher, &conn, None, CrsqlDbVersion(5)).unwrap();
2827
2828 let start = Instant::now();
2829 for _ in range {
2830 if let QueryEvent::Change(change_type, _, _, change_id) = rx.recv().await.unwrap() {
2831 assert_eq!(change_type, ChangeType::Insert);
2832 last_change_id = Some(change_id);
2833 }
2834 }
2835
2836 assert_eq!(last_change_id, Some(ChangeId(999)));
2837
2838 println!("took: {:?}", start.elapsed());
2839 {
2840 let conn = rusqlite::Connection::open(
2841 subscriptions_path
2842 .join(matcher.id().as_simple().to_string())
2843 .join("sub.sqlite"),
2844 )
2845 .unwrap();
2846 let mut prepped = conn.prepare("SELECT * FROM changes").unwrap();
2847 let cols = prepped
2848 .column_names()
2849 .iter()
2850 .map(|s| s.to_string())
2851 .collect::<Vec<_>>();
2852 let mut rows = prepped.query([]).unwrap();
2853 loop {
2854 let row = rows.next().unwrap();
2855 let row = match row {
2856 None => break,
2857 Some(row) => row,
2858 };
2859
2860 let s = cols
2861 .iter()
2862 .enumerate()
2863 .map(|(idx, col)| {
2864 format!("{col} = {}", row.get::<_, SqliteValue>(idx).unwrap())
2865 })
2866 .collect::<Vec<_>>();
2867 println!("{}", s.join(", "));
2868 }
2869 }
2870 if let Some(handle) = subs.remove(&matcher.id()) {
2871 handle.cleanup().await;
2872 }
2873 matcher.id()
2874 };
2875
2876 tokio::time::sleep(Duration::from_secs(1)).await;
2877
2878 let matcher_id = {
2880 let (matcher, created) = subs
2881 .restore(id, &subscriptions_path, &schema, &pool, tripwire.clone())
2882 .unwrap();
2883 let mut rx = created.evt_rx;
2884
2885 let matcher_id = matcher.id().as_simple().to_string();
2886 println!("matcher restored w/ id: {matcher_id}");
2887
2888 let (catch_up_tx, mut catch_up_rx) = mpsc::channel(1);
2889
2890 tokio::spawn({
2891 let matcher = matcher.clone();
2892 async move {
2893 let mut conn = matcher.pool().get().await.unwrap();
2894 block_in_place(|| {
2895 let conn_tx = conn.transaction()?;
2896
2897 println!(
2898 "max change id: {}",
2899 matcher.max_change_id(&conn_tx).unwrap()
2900 );
2901 matcher.all_rows(&conn_tx, catch_up_tx)?;
2902 Ok::<_, MatcherError>(())
2903 })
2904 }
2905 });
2906
2907 assert!(matches!(
2908 catch_up_rx.recv().await.unwrap(),
2909 QueryEvent::Columns(_)
2910 ));
2911
2912 let mut rows_count = 0;
2913 let mut eoq_change_id = None;
2914
2915 while eoq_change_id.is_none() {
2916 match catch_up_rx.recv().await.unwrap() {
2917 QueryEvent::EndOfQuery { change_id, .. } => eoq_change_id = Some(change_id),
2918 QueryEvent::Row(_, _) => rows_count += 1,
2919 QueryEvent::Change(_, _, _, _) => {
2920 panic!("received a change intertwined w/ rows");
2921 }
2922 _ => {}
2923 }
2924 }
2925
2926 assert_eq!(rows_count, 997);
2927 assert_eq!(eoq_change_id, Some(Some(ChangeId(999))));
2928
2929 {
2930 let tx = conn.transaction().unwrap();
2931 tx.execute_batch(
2932 r#"
2933 DELETE FROM consul_services where node = 'test-hostname' AND id = 'service-3';
2934 "#,
2935 )
2936 .unwrap();
2937 tx.commit().unwrap();
2938 }
2939 filter_changes_from_db(&matcher, &conn, None, CrsqlDbVersion(6)).unwrap();
2940 println!("waiting for a change (A)");
2941
2942 let cells = vec![SqliteValue::Text("{\"targets\":[\"127.0.0.2:1\"],\"labels\":{\"__metrics_path__\":\"/1\",\"app\":null,\"vm_account_id\":null,\"instance\":\"m-3\"}}".into())];
2943
2944 assert_eq!(
2945 rx.recv().await.unwrap(),
2946 QueryEvent::Change(ChangeType::Delete, RowId(2), cells, ChangeId(1000))
2947 );
2948
2949 assert!(rx.try_recv().is_err());
2950
2951 {
2952 let tx = conn.transaction().unwrap();
2953 for n in 1001..=1005 {
2954 tx.execute_batch(format!(r#"
2955 INSERT INTO consul_services (node, id, name, address, port, meta) VALUES ('test-hostname', 'service-{n}', 'app-prometheus', '127.0.0.{n}', 1, '{{"path": "/1", "machine_id": "m-{n}"}}');
2956
2957 INSERT INTO machines (id, machine_version_id) VALUES ('m-{n}', 'mv-{n}');
2958
2959 INSERT INTO machine_versions (machine_id, id) VALUES ('m-{n}', 'mv-{n}');
2960
2961 INSERT INTO machine_version_statuses (machine_id, id, status) VALUES ('m-{n}', 'mv-{n}', 'started');
2962 "#).as_str()).unwrap();
2963 }
2964 tx.commit().unwrap();
2965 }
2966 filter_changes_from_db(&matcher, &conn, None, CrsqlDbVersion(7)).unwrap();
2967
2968 tripwire_tx.send(()).await.ok();
2970 tripwire_worker.await;
2971
2972 {
2973 let tx = conn.transaction().unwrap();
2974 for n in 1006..=1010 {
2975 tx.execute_batch(format!(r#"
2976 INSERT INTO consul_services (node, id, name, address, port, meta) VALUES ('test-hostname', 'service-{n}', 'app-prometheus', '127.0.0.{n}', 1, '{{"path": "/1", "machine_id": "m-{n}"}}');
2977
2978 INSERT INTO machines (id, machine_version_id) VALUES ('m-{n}', 'mv-{n}');
2979
2980 INSERT INTO machine_versions (machine_id, id) VALUES ('m-{n}', 'mv-{n}');
2981
2982 INSERT INTO machine_version_statuses (machine_id, id, status) VALUES ('m-{n}', 'mv-{n}', 'started');
2983 "#).as_str()).unwrap();
2984 }
2985 tx.commit().unwrap();
2986 }
2987 filter_changes_from_db(&matcher, &conn, None, CrsqlDbVersion(8)).unwrap();
2988
2989 matcher_id
2990 };
2991
2992 subs.drop_handles().await;
2993 wait_for_all_pending_handles().await;
2994 info!("tripwire worker finished");
2995
2996 {
2999 let conn = rusqlite::Connection::open(
3000 subscriptions_path
3001 .join(matcher_id.clone())
3002 .join("sub.sqlite"),
3003 )
3004 .unwrap();
3005
3006 let mut prepped = conn.prepare("SELECT max(id) FROM changes").unwrap();
3007 let max_id = prepped.query_row([], |row| row.get::<_, i64>(0)).unwrap();
3008 assert_eq!(max_id, 1010);
3009
3010 for n in 1001..=1010 {
3011 let mut prepped = conn
3012 .prepare(
3013 format!("SELECT 1 from query where __corro_pk_cs_id = 'service-{n}'")
3014 .as_str(),
3015 )
3016 .unwrap();
3017 let max_db_version = prepped.query_row([], |row| row.get::<_, i64>(0)).unwrap();
3018 assert_eq!(max_db_version, 1);
3019 }
3020 }
3021
3022 {
3024 let res = subs.restore(id, &subscriptions_path, &schema, &pool, tripwire.clone());
3025 assert!(res.is_ok());
3026 }
3027
3028 {
3030 let conn =
3031 rusqlite::Connection::open(subscriptions_path.join(matcher_id).join("sub.sqlite"))
3032 .unwrap();
3033 conn.execute(
3034 "INSERT OR REPLACE INTO meta (key, value) VALUES ('state', 'running')",
3035 (),
3036 )
3037 .unwrap();
3038
3039 let res = subs.restore(id, &subscriptions_path, &schema, &pool, tripwire.clone());
3040 assert!(res.is_err());
3041 }
3042 }
3043
3044 fn filter_changes_from_db(
3045 matcher: &MatcherHandle,
3046 state_conn: &Connection,
3047 actor_id: Option<ActorId>,
3048 db_version: CrsqlDbVersion,
3049 ) -> rusqlite::Result<()> {
3050 let mut candidates = MatchCandidates::new();
3051
3052 let mut prepped = state_conn.prepare_cached(r#"SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl FROM crsql_changes WHERE site_id = COALESCE(?, crsql_site_id()) AND db_version = ? ORDER BY seq ASC"#)?;
3053 let rows = prepped
3054 .query_map(params![actor_id, db_version], row_to_change)
3055 .unwrap();
3056
3057 for row in rows {
3058 let change = row?;
3059 matcher.filter_matchable_change(&mut candidates, (&change).into());
3060 }
3061
3062 if let Err(e) = matcher.inner.changes_tx.try_send(candidates) {
3063 error!(sub_id = %matcher.inner.id, "could not send candidates to matcher: {e}");
3064 }
3065 Ok(())
3066 }
3067}