Skip to content

Commit

Permalink
* Added docs-text to some more Rust structs/functions.
Browse files Browse the repository at this point in the history
* Added new live-query filter: "containsAny" (matches if the source-array contains any of the target-array) [not yet tested]
  • Loading branch information
Venryx committed May 9, 2024
1 parent ae639ab commit 1ea4583
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::utils::type_aliases::PGClientObject;
use crate::{utils::{db::{sql_fragment::{SQLFragment}}}};

/// Use this struct to collect multiple queries and execute them in one go as a "batched query".
/// The main use-case for this is to increase the performance of the live-query system, by allowing the server to obtain the "initial results" for multiple live-queries with the same "form" within one SQL query.
/// It can also be used as a convenience wrapper around executing a single query; but for most standalone queries, `get_entries_in_collection[_basic]` will be more appropriate.
//#[derive(Default)]
pub struct LQBatch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ pub enum LQGroup_OutMsg {
LQInstanceIsInitialized(LQKey, Arc<LQInstance>, bool)
}

// sync docs with LQGroupImpl
/// A "live query group" is essentially a set of live-queries that all have the same "generic signature" (ie. table + filter operations with value slots), but which have different values assigned to each slot.
/// The `LQGroup` struct is the "public interface" for the lq-group. Its methods are mostly async, with each "sending a message" to the "inner" `LQGroupImpl` struct, which queues up a set of calls and then processes them as a batch.
/// When the batched processing in the `LQGroupImpl` completes, it sends a message back to the "waiting" `LQGroup`s, which then are able to have their async methods return.
pub struct LQGroup {
inner: Mutex<LQGroupImpl>,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use rust_shared::uuid::Uuid;

use crate::links::monitor_backend_link::MESSAGE_SENDER_TO_MONITOR_BACKEND;
use crate::store::live_queries_::lq_group::lq_group::LQGroup_OutMsg;
use crate::store::live_queries_::lq_key::LQKey;
use crate::store::live_queries_::lq_key::{filter_shape_from_filter, LQKey};
use crate::store::storage::AppStateArc;
use crate::utils::db::filter::{entry_matches_filter, QueryFilter, FilterOp};
use crate::utils::db::pg_stream_parsing::{LDChange};
Expand All @@ -59,21 +59,6 @@ use super::super::lq_instance::{LQInstance, LQEntryWatcher};
use super::lq_batch::lq_batch::LQBatch;
use super::lq_group::LQGroup_InMsg;

pub fn filter_shape_from_filter(filter: &QueryFilter) -> QueryFilter {
let mut filter_shape = filter.clone();
for (field_name, field_filter) in filter_shape.field_filters.clone().iter() {
let field_filter_mut = filter_shape.field_filters.get_mut(field_name).unwrap();
field_filter_mut.filter_ops = field_filter.filter_ops.clone().iter().map(|op| {
let op_with_vals_stripped = match op {
FilterOp::EqualsX(_val) => FilterOp::EqualsX(JSONValue::Null),
FilterOp::IsWithinX(vals) => FilterOp::IsWithinX(vals.iter().map(|_| JSONValue::Null).collect_vec()),
FilterOp::ContainsAllOfX(vals) => FilterOp::ContainsAllOfX(vals.iter().map(|_| JSONValue::Null).collect_vec()),
};
op_with_vals_stripped
}).collect_vec();
}
filter_shape
}
pub fn get_lq_group_key(table_name: &str, filter: &QueryFilter) -> String {
let filter_shape = filter_shape_from_filter(filter);
json!({
Expand Down Expand Up @@ -120,6 +105,10 @@ struct LQIAwaitingPopulationInfo {
prior_lqis_in_batch: usize,
}

// sync docs with LQGroup
/// A "live query group" is essentially a set of live-queries that all have the same "generic signature" (ie. table + filter operations with value slots), but which have different values assigned to each slot.
/// The `LQGroup` struct is the "public interface" for the lq-group. Its methods are mostly async, with each "sending a message" to the "inner" `LQGroupImpl` struct, which queues up a set of calls and then processes them as a batch.
/// When the batched processing in the `LQGroupImpl` completes, it sends a message back to the "waiting" `LQGroup`s, which then are able to have their async methods return.
pub(super) struct LQGroupImpl {
// shape
lq_key: LQKey,
Expand Down
4 changes: 4 additions & 0 deletions Packages/app-server/src/store/live_queries_/lq_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ pub fn filter_shape_from_filter(filter: &QueryFilter) -> QueryFilter {
FilterOp::EqualsX(_val) => FilterOp::EqualsX(JSONValue::Null),
FilterOp::IsWithinX(vals) => FilterOp::IsWithinX(vals.iter().map(|_| JSONValue::Null).collect_vec()),
FilterOp::ContainsAllOfX(vals) => FilterOp::ContainsAllOfX(vals.iter().map(|_| JSONValue::Null).collect_vec()),
FilterOp::ContainsAnyOfX(vals) => FilterOp::ContainsAnyOfX(vals.iter().map(|_| JSONValue::Null).collect_vec()),
};
op_with_vals_stripped
}).collect_vec();
}
filter_shape
}

/// A "live query key" is the "signature" of a live-query group or instance.
/// When used for a group, it represents the shape of the filter used in the group's instances. (eg. `{table:"maps",filter:{id:{equalTo:null}}}`)
/// When used for an instance, it represents the specific filter used in the instance. (eg. `{table:"maps",filter:{id:{equalTo:"SOME_MAP_ID_HERE"}}}`)
#[derive(Clone)]
pub struct LQKey {
pub table_name: String,
Expand Down
26 changes: 25 additions & 1 deletion Packages/app-server/src/utils/db/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub type FilterInput = JSONValue; // we use JSONValue, because it has the InputT

wrap_slow_macros!{

/// Structure specifying a set of filters used for rows in a table.
/// This struct may contain the actual values that are being filtered for, OR it may just contain the "shape" of a set of filters. (as checked by ensure_shape_only)
#[derive(Debug, Serialize, Deserialize)]
pub struct QueryFilter {
pub field_filters: IndexMap<String, FieldFilter>,
Expand Down Expand Up @@ -55,7 +57,11 @@ impl QueryFilter {
let vals = op_val_json_clone.as_array().ok_or(anyhow!("Filter-op of type \"contains\" requires an array value!"))?;
FilterOp::ContainsAllOfX(vals.to_vec())
},
_ => bail!(r#"Invalid filter-op "{op_json}" specified. Supported: equalTo, in, contains."#),
"containsAny" => {
let vals = op_val_json_clone.as_array().ok_or(anyhow!("Filter-op of type \"containsAny\" requires an array value!"))?;
FilterOp::ContainsAnyOfX(vals.to_vec())
},
_ => bail!(r#"Invalid filter-op "{op_json}" specified. Supported: equalTo, in, contains, containsAny."#),
};
field_filter.filter_ops.push(op);
}
Expand All @@ -76,6 +82,7 @@ impl QueryFilter {
FilterOp::EqualsX(val) => ensure!(val.is_null()),
FilterOp::IsWithinX(vals) => for val in vals { ensure!(val.is_null()); },
FilterOp::ContainsAllOfX(vals) => for val in vals { ensure!(val.is_null()); },
FilterOp::ContainsAnyOfX(vals) => for val in vals { ensure!(val.is_null()); },
};
}
}
Expand Down Expand Up @@ -147,11 +154,14 @@ pub enum FilterOp {
EqualsX(JSONValue),
IsWithinX(Vec<JSONValue>),
ContainsAllOfX(Vec<JSONValue>),
ContainsAnyOfX(Vec<JSONValue>),
}

}

impl FilterOp {
/// The job of this function is just to provide the SQL-fragment for the value being compared against.
/// The SQL for the "comparison operator" is provided in the "match" within the `get_sql_for_application` method below.
pub fn get_sql_for_value(&self) -> Result<SQLFragment, Error> {
Ok(match self {
FilterOp::EqualsX(val) => {
Expand All @@ -161,6 +171,7 @@ impl FilterOp {
},
FilterOp::IsWithinX(vals) => json_vals_to_sql_array_fragment(&vals)?,
FilterOp::ContainsAllOfX(vals) => json_vals_to_sql_array_fragment(&vals)?,
FilterOp::ContainsAnyOfX(vals) => json_vals_to_sql_array_fragment(&vals)?,
})
}

Expand Down Expand Up @@ -194,6 +205,11 @@ impl FilterOp {
bracket_plus_val_in_filter_op,
]),
//"contains_jsonb" => SF::new("\"$I\" @> $V", vec![field_name, filter_value_as_jsonb_str]),
FilterOp::ContainsAnyOfX(_) => SF::merge(vec![
bracket_plus_val_in_db,
SF::lit(" && "),
bracket_plus_val_in_filter_op,
]),
}
}
}
Expand Down Expand Up @@ -225,6 +241,14 @@ pub fn entry_matches_filter(entry: &RowData, filter: &QueryFilter) -> Result<boo
}
}
},
FilterOp::ContainsAnyOfX(vals) => {
for val in vals {
if field_value.as_array().with_context(|| "Field value was not an array!")?.contains(&val) {
return Ok(true);
}
}
return Ok(false);
},
}
}
}
Expand Down

0 comments on commit 1ea4583

Please sign in to comment.