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