1use std::{
2 collections::{hash_map::DefaultHasher, BTreeMap},
3 hash::{Hash, Hasher},
4 sync::{Arc, RwLock},
5 time::Duration,
6};
7
8use ankurah_core::{
9 error::{MutationError, RetrievalError, StateError},
10 property::backend::backend_from_string,
11 storage::{StorageCollection, StorageEngine},
12};
13use ankurah_proto::{Attestation, AttestationSet, Attested, EntityState, EventId, OperationSet, State, StateBuffers};
14
15use futures_util::{pin_mut, TryStreamExt};
16
17mod dump;
18pub mod sql_builder;
19pub mod value;
20
21use value::PGValue;
22
23use ankurah_proto::{Clock, CollectionId, EntityId, Event};
24use async_trait::async_trait;
25use bb8_postgres::{tokio_postgres::NoTls, PostgresConnectionManager};
26use tokio_postgres::{error::SqlState, types::ToSql};
27use tracing::{debug, error, info, warn};
28
29pub const DEFAULT_POOL_SIZE: u32 = 15;
32
33pub const DEFAULT_CONNECTION_TIMEOUT_SECS: u64 = 30;
35
36pub struct Postgres {
37 pool: bb8::Pool<PostgresConnectionManager<NoTls>>,
38}
39
40impl Postgres {
41 pub fn new(pool: bb8::Pool<PostgresConnectionManager<NoTls>>) -> anyhow::Result<Self> { Ok(Self { pool }) }
42
43 pub async fn open(uri: &str) -> anyhow::Result<Self> {
44 let manager = PostgresConnectionManager::new_from_stringlike(uri, NoTls)?;
45 let pool = bb8::Pool::builder()
46 .max_size(DEFAULT_POOL_SIZE)
47 .connection_timeout(Duration::from_secs(DEFAULT_CONNECTION_TIMEOUT_SECS))
48 .build(manager)
49 .await?;
50 Self::new(pool)
51 }
52
53 pub fn sane_name(collection: &str) -> bool {
56 for char in collection.chars() {
57 match char {
58 char if char.is_alphanumeric() => {}
59 char if char.is_numeric() => {}
60 '_' | '.' | ':' => {}
61 _ => return false,
62 }
63 }
64
65 true
66 }
67}
68
69fn advisory_lock_key(identifier: &str) -> i64 {
71 let mut hasher = DefaultHasher::new();
72 identifier.hash(&mut hasher);
73 hasher.finish() as i64
74}
75
76async fn acquire_ddl_lock(client: &tokio_postgres::Client, collection_id: &str) -> Result<i64, StateError> {
78 let lock_key = advisory_lock_key(&format!("ankurah_ddl:{}", collection_id));
79 debug!("Acquiring advisory lock {} for collection {}", lock_key, collection_id);
80 client.execute("SELECT pg_advisory_lock($1)", &[&lock_key]).await.map_err(|err| {
81 error!("Failed to acquire advisory lock for {}: {:?}", collection_id, err);
82 StateError::DDLError(Box::new(err))
83 })?;
84 Ok(lock_key)
85}
86
87async fn release_ddl_lock(client: &tokio_postgres::Client, lock_key: i64) -> Result<(), StateError> {
89 debug!("Releasing advisory lock {}", lock_key);
90 client.execute("SELECT pg_advisory_unlock($1)", &[&lock_key]).await.map_err(|err| {
91 error!("Failed to release advisory lock {}: {:?}", lock_key, err);
92 StateError::DDLError(Box::new(err))
93 })?;
94 Ok(())
95}
96
97#[async_trait]
98impl StorageEngine for Postgres {
99 type Value = PGValue;
100
101 async fn collection(&self, collection_id: &CollectionId) -> Result<std::sync::Arc<dyn StorageCollection>, RetrievalError> {
102 if !Postgres::sane_name(collection_id.as_str()) {
103 return Err(RetrievalError::InvalidBucketName);
104 }
105
106 let mut client = self.pool.get().await.map_err(RetrievalError::storage)?;
107
108 let schema = client.query_one("SELECT current_database()", &[]).await.map_err(RetrievalError::storage)?;
110 let schema = schema.get("current_database");
111
112 let bucket = PostgresBucket {
113 pool: self.pool.clone(),
114 schema,
115 collection_id: collection_id.clone(),
116 columns: Arc::new(RwLock::new(Vec::new())),
117 #[cfg(debug_assertions)]
118 last_spilled_predicate: Arc::new(RwLock::new(None)),
119 };
120
121 let lock_key = acquire_ddl_lock(&client, collection_id.as_str()).await?;
123
124 let result = async {
126 bucket.create_state_table(&mut client).await?;
127 bucket.create_event_table(&mut client).await?;
128 bucket.rebuild_columns_cache(&mut client).await?;
129 Ok::<_, StateError>(())
130 }
131 .await;
132
133 release_ddl_lock(&client, lock_key).await?;
135
136 result?;
137 Ok(Arc::new(bucket))
138 }
139
140 async fn delete_all_collections(&self) -> Result<bool, MutationError> {
141 let mut client = self.pool.get().await.map_err(|err| MutationError::General(Box::new(err)))?;
142
143 let query = r#"
145 SELECT table_name
146 FROM information_schema.tables
147 WHERE table_schema = 'public'
148 "#;
149
150 let rows = client.query(query, &[]).await.map_err(|err| MutationError::General(Box::new(err)))?;
151 if rows.is_empty() {
152 return Ok(false);
153 }
154
155 let transaction = client.transaction().await.map_err(|err| MutationError::General(Box::new(err)))?;
157
158 for row in rows {
160 let table_name: String = row.get("table_name");
161 let drop_query = format!(r#"DROP TABLE IF EXISTS "{}""#, table_name);
162 transaction.execute(&drop_query, &[]).await.map_err(|err| MutationError::General(Box::new(err)))?;
163 }
164
165 transaction.commit().await.map_err(|err| MutationError::General(Box::new(err)))?;
167
168 Ok(true)
169 }
170}
171
172#[derive(Clone, Debug)]
173pub struct PostgresColumn {
174 pub name: String,
175 pub is_nullable: bool,
176 pub data_type: String,
177}
178
179pub struct PostgresBucket {
180 pool: bb8::Pool<PostgresConnectionManager<NoTls>>,
181 collection_id: CollectionId,
182 schema: String,
183 columns: Arc<RwLock<Vec<PostgresColumn>>>,
184 #[cfg(debug_assertions)]
186 last_spilled_predicate: Arc<RwLock<Option<ankql::ast::Predicate>>>,
187}
188
189impl PostgresBucket {
190 fn state_table(&self) -> String { self.collection_id.as_str().to_string() }
191
192 pub fn event_table(&self) -> String { format!("{}_event", self.collection_id.as_str()) }
193
194 #[cfg(debug_assertions)]
202 pub fn last_spilled_predicate(&self) -> Option<ankql::ast::Predicate> { self.last_spilled_predicate.read().unwrap().clone() }
203
204 pub async fn rebuild_columns_cache(&self, client: &mut tokio_postgres::Client) -> Result<(), StateError> {
206 debug!("PostgresBucket({}).rebuild_columns_cache", self.collection_id);
207 let column_query =
208 r#"SELECT column_name, is_nullable, data_type FROM information_schema.columns WHERE table_catalog = $1 AND table_name = $2;"#
209 .to_string();
210 let mut new_columns = Vec::new();
211 debug!("Querying existing columns: {:?}, [{:?}, {:?}]", column_query, &self.schema, &self.collection_id.as_str());
212 let rows = client
213 .query(&column_query, &[&self.schema, &self.collection_id.as_str()])
214 .await
215 .map_err(|err| StateError::DDLError(Box::new(err)))?;
216 for row in rows {
217 let is_nullable: String = row.get("is_nullable");
218 new_columns.push(PostgresColumn {
219 name: row.get("column_name"),
220 is_nullable: is_nullable.eq("YES"),
221 data_type: row.get("data_type"),
222 })
223 }
224
225 let mut columns = self.columns.write().unwrap();
226 *columns = new_columns;
227 drop(columns);
228
229 Ok(())
230 }
231
232 pub fn existing_columns(&self) -> Vec<String> {
233 let columns = self.columns.read().unwrap();
234 columns.iter().map(|column| column.name.clone()).collect()
235 }
236
237 pub fn column(&self, column_name: &String) -> Option<PostgresColumn> {
238 let columns = self.columns.read().unwrap();
239 columns.iter().find(|column| column.name == *column_name).cloned()
240 }
241
242 pub fn has_column(&self, column_name: &String) -> bool { self.column(column_name).is_some() }
243
244 pub async fn create_event_table(&self, client: &mut tokio_postgres::Client) -> Result<(), StateError> {
245 let create_query = format!(
246 r#"CREATE TABLE IF NOT EXISTS "{}"(
247 "id" character(43) PRIMARY KEY,
248 "entity_id" character(22),
249 "operations" bytea,
250 "parent" character(43)[],
251 "attestations" bytea
252 )"#,
253 self.event_table()
254 );
255
256 debug!("{create_query}");
257 client.execute(&create_query, &[]).await.map_err(|e| StateError::DDLError(Box::new(e)))?;
258 Ok(())
259 }
260
261 pub async fn create_state_table(&self, client: &mut tokio_postgres::Client) -> Result<(), StateError> {
262 let create_query = format!(
263 r#"CREATE TABLE IF NOT EXISTS "{}"(
264 "id" character(22) PRIMARY KEY,
265 "state_buffer" BYTEA,
266 "head" character(43)[],
267 "attestations" BYTEA[]
268 )"#,
269 self.state_table()
270 );
271
272 debug!("{create_query}");
273 match client.execute(&create_query, &[]).await {
274 Ok(_) => Ok(()),
275 Err(err) => {
276 if let Some(db_err) = err.as_db_error() {
278 error!("PostgresBucket({}).create_state_table error: {} (code: {:?})", self.collection_id, db_err, db_err.code());
279 } else {
280 error!("PostgresBucket({}).create_state_table error: {:?}", self.collection_id, err);
281 }
282 Err(StateError::DDLError(Box::new(err)))
283 }
284 }
285 }
286
287 pub async fn add_missing_columns(
288 &self,
289 client: &mut tokio_postgres::Client,
290 missing: Vec<(String, &'static str)>, ) -> Result<(), StateError> {
292 if missing.is_empty() {
293 return Ok(());
294 }
295
296 let lock_key = acquire_ddl_lock(client, self.collection_id.as_str()).await?;
298
299 let result = async {
300 self.rebuild_columns_cache(client).await?;
302
303 for (column, datatype) in missing {
304 if Postgres::sane_name(&column) && !self.has_column(&column) {
305 let alter_query = format!(r#"ALTER TABLE "{}" ADD COLUMN "{}" {}"#, self.state_table(), column, datatype);
306 info!("PostgresBucket({}).add_missing_columns: {}", self.collection_id, alter_query);
307 match client.execute(&alter_query, &[]).await {
308 Ok(_) => {}
309 Err(err) => {
310 if let Some(db_err) = err.as_db_error() {
312 warn!(
313 "Error adding column {} to table {}: {} (code: {:?})",
314 column,
315 self.state_table(),
316 db_err,
317 db_err.code()
318 );
319 } else {
320 warn!("Error adding column {} to table {}: {:?}", column, self.state_table(), err);
321 }
322 self.rebuild_columns_cache(client).await?;
323 return Err(StateError::DDLError(Box::new(err)));
324 }
325 }
326 }
327 }
328
329 self.rebuild_columns_cache(client).await?;
330 Ok(())
331 }
332 .await;
333
334 release_ddl_lock(client, lock_key).await?;
336
337 result
338 }
339}
340
341#[async_trait]
342impl StorageCollection for PostgresBucket {
343 async fn set_state(&self, state: Attested<EntityState>) -> Result<bool, MutationError> {
344 let state_buffers = bincode::serialize(&state.payload.state.state_buffers)?;
345 let attestations: Vec<Vec<u8>> = state.attestations.iter().map(bincode::serialize).collect::<Result<Vec<_>, _>>()?;
346 let id = state.payload.entity_id;
347
348 if state.payload.state.head.is_empty() {
350 warn!("Warning: Empty head detected for entity {}", id);
351 }
352
353 let mut client = self.pool.get().await.map_err(|err| MutationError::General(err.into()))?;
354
355 let mut columns: Vec<String> = vec!["id".to_owned(), "state_buffer".to_owned(), "head".to_owned(), "attestations".to_owned()];
356 let mut params: Vec<&(dyn ToSql + Sync)> = Vec::new();
357 params.push(&id);
358 params.push(&state_buffers);
359 params.push(&state.payload.state.head);
360 params.push(&attestations);
361
362 let mut materialized: Vec<(String, Option<PGValue>)> = Vec::new();
363 let mut seen_properties = std::collections::HashSet::new();
364
365 for (name, state_buffer) in state.payload.state.state_buffers.iter() {
367 let backend = backend_from_string(name, Some(state_buffer))?;
368 for (column, value) in backend.property_values() {
369 if !seen_properties.insert(column.clone()) {
370 continue;
375 }
376
377 let pg_value: Option<PGValue> = value.map(|value| value.into());
378 if !self.has_column(&column) {
379 if let Some(ref pg_value) = pg_value {
381 self.add_missing_columns(&mut client, vec![(column.clone(), pg_value.postgres_type())]).await?;
382 } else {
383 continue;
387 }
388 }
389
390 materialized.push((column.clone(), pg_value));
391 }
392 }
393
394 for (name, parameter) in &materialized {
395 columns.push(name.clone());
396
397 match ¶meter {
398 Some(value) => match value {
399 PGValue::CharacterVarying(string) => params.push(string),
400 PGValue::SmallInt(number) => params.push(number),
401 PGValue::Integer(number) => params.push(number),
402 PGValue::BigInt(number) => params.push(number),
403 PGValue::DoublePrecision(float) => params.push(float),
404 PGValue::Bytea(bytes) => params.push(bytes),
405 PGValue::Boolean(bool) => params.push(bool),
406 PGValue::Jsonb(json_val) => params.push(json_val),
407 },
408 None => params.push(&UntypedNull),
409 }
410 }
411 let columns_str = columns.iter().map(|name| format!("\"{}\"", name)).collect::<Vec<String>>().join(", ");
412 let values_str = params.iter().enumerate().map(|(index, _)| format!("${}", index + 1)).collect::<Vec<String>>().join(", ");
413 let columns_update_str = columns
414 .iter()
415 .enumerate()
416 .skip(1) .map(|(index, name)| format!("\"{}\" = ${}", name, index + 1))
418 .collect::<Vec<String>>()
419 .join(", ");
420
421 let query = format!(
423 r#"WITH old_state AS (
424 SELECT "head" FROM "{0}" WHERE "id" = $1
425 )
426 INSERT INTO "{0}"({1}) VALUES({2})
427 ON CONFLICT("id") DO UPDATE SET {3}
428 RETURNING (SELECT "head" FROM old_state) as old_head"#,
429 self.state_table(),
430 columns_str,
431 values_str,
432 columns_update_str
433 );
434
435 debug!("PostgresBucket({}).set_state: {}", self.collection_id, query);
436 let mut created_table = false;
437 let row = loop {
438 match client.query_one(&query, params.as_slice()).await {
439 Ok(row) => break row,
440 Err(err) => {
441 let kind = error_kind(&err);
442 if let ErrorKind::UndefinedTable { table } = kind {
443 if table == self.state_table() && !created_table {
444 self.create_state_table(&mut client).await?;
445 created_table = true;
446 continue; }
448 }
449 return Err(StateError::DDLError(Box::new(err)).into());
450 }
451 }
452 };
453
454 let old_head: Option<Clock> = row.get("old_head");
456 let changed = match old_head {
457 None => true, Some(old_head) => old_head != state.payload.state.head,
459 };
460
461 debug!("PostgresBucket({}).set_state: Changed: {}", self.collection_id, changed);
462 Ok(changed)
463 }
464
465 async fn get_state(&self, id: EntityId) -> Result<Attested<EntityState>, RetrievalError> {
466 let query = format!(r#"SELECT "id", "state_buffer", "head", "attestations" FROM "{}" WHERE "id" = $1"#, self.state_table());
468
469 let mut client = match self.pool.get().await {
470 Ok(client) => client,
471 Err(err) => {
472 return Err(RetrievalError::StorageError(err.into()));
473 }
474 };
475
476 debug!("PostgresBucket({}).get_state: {}", self.collection_id, query);
477 let rows = match client.query(&query, &[&id]).await {
478 Ok(rows) => rows,
479 Err(err) => {
480 let kind = error_kind(&err);
481 if let ErrorKind::UndefinedTable { table } = kind {
482 if table == self.state_table() {
483 self.create_state_table(&mut client).await.map_err(|e| RetrievalError::StorageError(e.into()))?;
484 return Err(RetrievalError::EntityNotFound(id));
485 }
486 }
487 return Err(RetrievalError::StorageError(err.into()));
488 }
489 };
490
491 let row = match rows.into_iter().next() {
492 Some(row) => row,
493 None => return Err(RetrievalError::EntityNotFound(id)),
494 };
495
496 debug!("PostgresBucket({}).get_state: Row: {:?}", self.collection_id, row);
497 let row_id: EntityId = row.try_get("id").map_err(RetrievalError::storage)?;
498 assert_eq!(row_id, id);
499
500 let serialized_buffers: Vec<u8> = row.try_get("state_buffer").map_err(RetrievalError::storage)?;
501 let state_buffers: BTreeMap<String, Vec<u8>> = bincode::deserialize(&serialized_buffers).map_err(RetrievalError::storage)?;
502 let head: Clock = row.try_get("head").map_err(RetrievalError::storage)?;
503 let attestation_bytes: Vec<Vec<u8>> = row.try_get("attestations").map_err(RetrievalError::storage)?;
504 let attestations = attestation_bytes
505 .into_iter()
506 .map(|bytes| bincode::deserialize(&bytes))
507 .collect::<Result<Vec<Attestation>, _>>()
508 .map_err(RetrievalError::storage)?;
509
510 Ok(Attested {
511 payload: EntityState {
512 entity_id: id,
513 collection: self.collection_id.clone(),
514 state: State { state_buffers: StateBuffers(state_buffers), head },
515 },
516 attestations: AttestationSet(attestations),
517 })
518 }
519
520 async fn fetch_states(&self, selection: &ankql::ast::Selection) -> Result<Vec<Attested<EntityState>>, RetrievalError> {
521 debug!("fetch_states: {:?}", selection);
522 let mut client = self.pool.get().await.map_err(|err| RetrievalError::StorageError(Box::new(err)))?;
523
524 let referenced = selection.referenced_columns();
529 let cached = self.existing_columns();
530 let unknown_to_cache: Vec<&String> = referenced.iter().filter(|col| !cached.contains(col)).collect();
531
532 if !unknown_to_cache.is_empty() {
534 debug!("PostgresBucket({}).fetch_states: Unknown columns {:?}, refreshing schema cache", self.collection_id, unknown_to_cache);
535 self.rebuild_columns_cache(&mut client).await.map_err(|e| RetrievalError::StorageError(e.into()))?;
536 }
537
538 let existing = self.existing_columns();
540 let missing: Vec<String> = referenced.into_iter().filter(|col| !existing.contains(col)).collect();
541
542 let effective_selection = if missing.is_empty() {
543 selection.clone()
544 } else {
545 debug!("PostgresBucket({}).fetch_states: Columns {:?} don't exist, treating as NULL", self.collection_id, missing);
546 selection.assume_null(&missing)
547 };
548
549 let split = sql_builder::split_predicate_for_postgres(&effective_selection.predicate);
551 let needs_post_filter = split.needs_post_filter();
552 let remaining_predicate = split.remaining_predicate; debug!(
554 "PostgresBucket({}).fetch_states: SQL predicate: {:?}, remaining: {:?}, needs_post_filter: {}",
555 self.collection_id, split.sql_predicate, remaining_predicate, needs_post_filter
556 );
557
558 #[cfg(debug_assertions)]
560 {
561 let mut spilled = self.last_spilled_predicate.write().unwrap();
562 *spilled = if needs_post_filter { Some(remaining_predicate.clone()) } else { None };
563 }
564
565 #[cfg(debug_assertions)]
567 {
568 let spilled = if needs_post_filter { Some(remaining_predicate.clone()) } else { None };
569 *self.last_spilled_predicate.write().unwrap() = spilled;
570 }
571
572 let sql_selection = ankql::ast::Selection {
574 predicate: split.sql_predicate,
575 order_by: effective_selection.order_by.clone(),
576 limit: if needs_post_filter {
577 None } else {
579 effective_selection.limit
580 },
581 };
582
583 let mut results = Vec::new();
584 let mut builder = SqlBuilder::with_fields(vec!["id", "state_buffer", "head", "attestations"]);
585 builder.table_name(self.state_table());
586 builder.selection(&sql_selection)?;
587
588 let (sql, args) = builder.build()?;
589 debug!("PostgresBucket({}).fetch_states: SQL: {} with args: {:?}", self.collection_id, sql, args);
590
591 let stream = match client.query_raw(&sql, args).await {
592 Ok(stream) => stream,
593 Err(err) => {
594 let kind = error_kind(&err);
595 if let ErrorKind::UndefinedTable { table } = kind {
596 if table == self.state_table() {
597 return Ok(Vec::new());
599 }
600 }
601 return Err(RetrievalError::StorageError(err.into()));
602 }
603 };
604 pin_mut!(stream);
605
606 while let Some(row) = stream.try_next().await.map_err(RetrievalError::storage)? {
607 let id: EntityId = row.try_get(0).map_err(RetrievalError::storage)?;
608 let state_buffer: Vec<u8> = row.try_get(1).map_err(RetrievalError::storage)?;
609 let state_buffers: BTreeMap<String, Vec<u8>> = bincode::deserialize(&state_buffer).map_err(RetrievalError::storage)?;
610 let head: Clock = row.try_get("head").map_err(RetrievalError::storage)?;
611 let attestation_bytes: Vec<Vec<u8>> = row.try_get("attestations").map_err(RetrievalError::storage)?;
612 let attestations = attestation_bytes
613 .into_iter()
614 .map(|bytes| bincode::deserialize(&bytes))
615 .collect::<Result<Vec<Attestation>, _>>()
616 .map_err(RetrievalError::storage)?;
617
618 results.push(Attested {
619 payload: EntityState {
620 entity_id: id,
621 collection: self.collection_id.clone(),
622 state: State { state_buffers: StateBuffers(state_buffers), head },
623 },
624 attestations: AttestationSet(attestations),
625 });
626 }
627
628 let results = if needs_post_filter {
630 debug!(
631 "PostgresBucket({}).fetch_states: Post-filtering {} results with remaining predicate",
632 self.collection_id,
633 results.len()
634 );
635 let filtered = post_filter_states(&results, &remaining_predicate, &self.collection_id);
636
637 if let Some(limit) = effective_selection.limit {
639 filtered.into_iter().take(limit as usize).collect()
640 } else {
641 filtered
642 }
643 } else {
644 results
645 };
646
647 Ok(results)
648 }
649
650 async fn add_event(&self, entity_event: &Attested<Event>) -> Result<bool, MutationError> {
651 let operations = bincode::serialize(&entity_event.payload.operations)?;
652 let attestations = bincode::serialize(&entity_event.attestations)?;
653
654 let query = format!(
655 r#"INSERT INTO "{0}"("id", "entity_id", "operations", "parent", "attestations") VALUES($1, $2, $3, $4, $5)
656 ON CONFLICT ("id") DO NOTHING"#,
657 self.event_table(),
658 );
659
660 let mut client = self.pool.get().await.map_err(|err| MutationError::General(err.into()))?;
661 debug!("PostgresBucket({}).add_event: {}", self.collection_id, query);
662 let mut created_table = false;
663 let affected = loop {
664 match client
665 .execute(
666 &query,
667 &[
668 &entity_event.payload.id(),
669 &entity_event.payload.entity_id,
670 &operations,
671 &entity_event.payload.parent,
672 &attestations,
673 ],
674 )
675 .await
676 {
677 Ok(affected) => break affected,
678 Err(err) => {
679 let kind = error_kind(&err);
680 if let ErrorKind::UndefinedTable { table } = kind {
681 if table == self.event_table() && !created_table {
682 self.create_event_table(&mut client).await?;
683 created_table = true;
684 continue; }
686 }
687 error!("PostgresBucket({}).add_event: Error: {:?}", self.collection_id, err);
688 return Err(StateError::DMLError(Box::new(err)).into());
689 }
690 }
691 };
692
693 Ok(affected > 0)
694 }
695
696 async fn get_events(&self, event_ids: Vec<EventId>) -> Result<Vec<Attested<Event>>, RetrievalError> {
697 if event_ids.is_empty() {
698 return Ok(Vec::new());
699 }
700
701 let query = format!(
702 r#"SELECT "id", "entity_id", "operations", "parent", "attestations" FROM "{0}" WHERE "id" = ANY($1)"#,
703 self.event_table(),
704 );
705
706 let client = self.pool.get().await.map_err(RetrievalError::storage)?;
707 let rows = match client.query(&query, &[&event_ids]).await {
708 Ok(rows) => rows,
709 Err(err) => {
710 let kind = error_kind(&err);
711 match kind {
712 ErrorKind::UndefinedTable { table } if table == self.event_table() => return Ok(Vec::new()),
713 _ => return Err(RetrievalError::storage(err)),
714 }
715 }
716 };
717
718 let mut events = Vec::new();
719 for row in rows {
720 let entity_id: EntityId = row.try_get("entity_id").map_err(RetrievalError::storage)?;
721 let operations: OperationSet = row.try_get("operations").map_err(RetrievalError::storage)?;
722 let parent: Clock = row.try_get("parent").map_err(RetrievalError::storage)?;
723 let attestations_binary: Vec<u8> = row.try_get("attestations").map_err(RetrievalError::storage)?;
724 let attestations: Vec<Attestation> = bincode::deserialize(&attestations_binary).map_err(RetrievalError::storage)?;
725
726 let event = Attested {
727 payload: Event { collection: self.collection_id.clone(), entity_id, operations, parent },
728 attestations: AttestationSet(attestations),
729 };
730 events.push(event);
731 }
732 Ok(events)
733 }
734
735 async fn dump_entity_events(&self, entity_id: EntityId) -> Result<Vec<Attested<Event>>, ankurah_core::error::RetrievalError> {
736 let query =
737 format!(r#"SELECT "id", "operations", "parent", "attestations" FROM "{0}" WHERE "entity_id" = $1"#, self.event_table(),);
738
739 let client = self.pool.get().await.map_err(RetrievalError::storage)?;
740 debug!("PostgresBucket({}).get_events: {}", self.collection_id, query);
741 let rows = match client.query(&query, &[&entity_id]).await {
742 Ok(rows) => rows,
743 Err(err) => {
744 let kind = error_kind(&err);
745 if let ErrorKind::UndefinedTable { table } = kind {
746 if table == self.event_table() {
747 return Ok(Vec::new());
748 }
749 }
750
751 return Err(RetrievalError::storage(err));
752 }
753 };
754
755 let mut events = Vec::new();
756 for row in rows {
757 let operations_binary: Vec<u8> = row.try_get("operations").map_err(RetrievalError::storage)?;
759 let operations = bincode::deserialize(&operations_binary).map_err(RetrievalError::storage)?;
760 let parent: Clock = row.try_get("parent").map_err(RetrievalError::storage)?;
761 let attestations_binary: Vec<u8> = row.try_get("attestations").map_err(RetrievalError::storage)?;
762 let attestations: Vec<Attestation> = bincode::deserialize(&attestations_binary).map_err(RetrievalError::storage)?;
763
764 events.push(Attested {
765 payload: Event { collection: self.collection_id.clone(), entity_id, operations, parent },
766 attestations: AttestationSet(attestations),
767 });
768 }
769
770 Ok(events)
771 }
772}
773
774#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
778pub enum ErrorKind {
779 RowCount,
780 UndefinedTable { table: String },
781 UndefinedColumn { table: Option<String>, column: String },
782 Unknown,
783 PostgresError(String),
784}
785
786pub fn error_kind(err: &tokio_postgres::Error) -> ErrorKind {
787 let string = err.as_db_error().map(|e| e.message()).unwrap_or_default().trim().to_owned();
788 let _db_error = err.as_db_error();
789 let sql_code = err.code().cloned();
790
791 let err_string = err.to_string();
793 if err_string.contains("query returned an unexpected number of rows") || string == "query returned an unexpected number of rows" {
794 return ErrorKind::RowCount;
795 }
796
797 debug!("postgres error: {:?}", err);
804
805 let quote_indices = |s: &str| {
806 let mut quotes = Vec::new();
807 for (index, char) in s.char_indices() {
808 if char == '"' {
809 quotes.push(index)
810 }
811 }
812 quotes
813 };
814
815 match sql_code {
816 Some(SqlState::UNDEFINED_TABLE) => {
817 let quotes = quote_indices(&string);
819 if quotes.len() >= 2 {
820 let table = &string[quotes[0] + 1..quotes[1]];
821 ErrorKind::UndefinedTable { table: table.to_owned() }
822 } else {
823 ErrorKind::PostgresError(string.clone())
824 }
825 }
826 Some(SqlState::UNDEFINED_COLUMN) => {
827 let quotes = quote_indices(&string);
831 if quotes.len() >= 2 {
832 let column = string[quotes[0] + 1..quotes[1]].to_owned();
833
834 let table = if quotes.len() >= 4 {
835 Some(string[quotes[2] + 1..quotes[3]].to_owned())
837 } else {
838 None
840 };
841
842 ErrorKind::UndefinedColumn { table, column }
843 } else {
844 ErrorKind::PostgresError(string.clone())
845 }
846 }
847 _ => ErrorKind::Unknown,
848 }
849}
850
851#[allow(unused)]
852pub struct MissingMaterialized {
853 pub name: String,
854}
855
856use bytes::BytesMut;
857use tokio_postgres::types::{to_sql_checked, IsNull, Type};
858
859use crate::sql_builder::SqlBuilder;
860
861fn post_filter_states(
866 states: &[Attested<EntityState>],
867 predicate: &ankql::ast::Predicate,
868 collection_id: &CollectionId,
869) -> Vec<Attested<EntityState>> {
870 use ankurah_core::entity::TemporaryEntity;
871 use ankurah_core::selection::filter::evaluate_predicate;
872
873 states
874 .iter()
875 .filter(|attested| {
876 match TemporaryEntity::new(attested.payload.entity_id, collection_id.clone(), &attested.payload.state) {
878 Ok(temp_entity) => {
879 match evaluate_predicate(&temp_entity, predicate) {
881 Ok(true) => true,
882 Ok(false) => false,
883 Err(e) => {
884 warn!("Post-filter evaluation error for entity {}: {}", attested.payload.entity_id, e);
885 false }
887 }
888 }
889 Err(e) => {
890 warn!("Failed to create TemporaryEntity for post-filtering {}: {}", attested.payload.entity_id, e);
891 false }
893 }
894 })
895 .cloned()
896 .collect()
897}
898
899#[derive(Debug)]
900struct UntypedNull;
901
902impl ToSql for UntypedNull {
903 fn to_sql(&self, _ty: &Type, _out: &mut BytesMut) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> { Ok(IsNull::Yes) }
904
905 fn accepts(_ty: &Type) -> bool {
906 true }
908
909 to_sql_checked!();
910}