Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: member clauses handle struct fields & operators & recursive #2420

Merged
merged 9 commits into from
Sep 16, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 124 additions & 103 deletions crates/torii/grpc/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
use self::subscriptions::model_diff::{ModelDiffRequest, StateDiffManager};
use crate::proto::types::clause::ClauseType;
use crate::proto::types::member_value::ValueType;
use crate::proto::types::LogicalOperator;
use crate::proto::world::world_server::WorldServer;
use crate::proto::world::{
SubscribeEntitiesRequest, SubscribeEntityResponse, SubscribeEventsResponse,
Expand Down Expand Up @@ -260,7 +261,6 @@
// total count of rows without limit and offset
let total_count: u32 =
sqlx::query_scalar(&count_query).fetch_optional(&self.pool).await?.unwrap_or(0);

if total_count == 0 {
return Ok((Vec::new(), 0));
}
Expand Down Expand Up @@ -382,7 +382,6 @@
.fetch_optional(&self.pool)
.await?
.unwrap_or(0);

if total_count == 0 {
return Ok((Vec::new(), 0));
}
Expand Down Expand Up @@ -531,15 +530,13 @@
"#,
compute_selector_from_names(namespace, model)
);

let models_result: Option<(String,)> =
sqlx::query_as(&models_query).fetch_optional(&self.pool).await?;
// we return an empty array of entities if the table is empty
if models_result.is_none() {
let models_str: Option<String> =
sqlx::query_scalar(&models_query).fetch_optional(&self.pool).await?;
if models_str.is_none() {

Check warning on line 535 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L533-L535

Added lines #L533 - L535 were not covered by tests
return Ok((Vec::new(), 0));
}

let (models_str,) = models_result.unwrap();
let models_str = models_str.unwrap();

Check warning on line 539 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L539

Added line #L539 was not covered by tests

let model_ids = models_str
.split(',')
Expand All @@ -549,8 +546,14 @@
let schemas =
self.model_cache.models(&model_ids).await?.into_iter().map(|m| m.schema).collect();

let table_name = member_clause.model;
let column_name = format!("external_{}", member_clause.member);
let model = member_clause.model.clone();
let parts: Vec<&str> = member_clause.member.split('.').collect();
let (table_name, column_name) = if parts.len() > 1 {
let nested_table = parts[..parts.len() - 1].join("$");
(format!("{model}${nested_table}"), format!("external_{}", parts.last().unwrap()))

Check warning on line 553 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L549-L553

Added lines #L549 - L553 were not covered by tests
} else {
(model, format!("external_{}", member_clause.member))

Check warning on line 555 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L555

Added line #L555 was not covered by tests
};
let (entity_query, arrays_queries, count_query) = build_sql_query(
&schemas,
table,
Expand All @@ -566,7 +569,6 @@
.fetch_optional(&self.pool)
.await?
.unwrap_or(0);

let db_entities = sqlx::query(&entity_query)
.bind(comparison_value.clone())
.bind(limit)
Expand All @@ -587,7 +589,7 @@
Ok((entities_collection, total_count))
}

async fn query_by_composite(
pub(crate) async fn query_by_composite(

Check warning on line 592 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L592

Added line #L592 was not covered by tests
&self,
table: &str,
model_relation_table: &str,
Expand All @@ -596,102 +598,17 @@
limit: Option<u32>,
offset: Option<u32>,
) -> Result<(Vec<proto::types::Entity>, u32), Error> {
// different types of clauses
let mut where_clauses = Vec::new();
let mut model_clauses: HashMap<String, Vec<(String, ComparisonOperator, String)>> =
HashMap::new();
let mut having_clauses = Vec::new();

// bind valeus for prepared statement
let mut bind_values = Vec::new();

for clause in composite.clauses {
match clause.clause_type.unwrap() {
ClauseType::HashedKeys(hashed_keys) => {
let ids = hashed_keys
.hashed_keys
.iter()
.map(|id| {
Ok(format!("{table}.id = '{:#x}'", Felt::from_bytes_be_slice(id)))
})
.collect::<Result<Vec<_>, Error>>()?;
where_clauses.push(format!("({})", ids.join(" OR ")));
}
ClauseType::Keys(keys) => {
let keys_pattern = build_keys_pattern(&keys)?;
where_clauses.push(format!("{table}.keys REGEXP '{keys_pattern}'"));
}
ClauseType::Member(member) => {
let comparison_operator =
ComparisonOperator::from_repr(member.operator as usize)
.expect("invalid comparison operator");
let comparison_value = match member
.value
.ok_or(QueryError::MissingParam("value".into()))?
.value_type
{
Some(ValueType::String(value)) => value,
Some(ValueType::Primitive(value)) => {
let primitive: Primitive = value.try_into()?;
primitive.to_sql_value()?
}
None => return Err(QueryError::MissingParam("value_type".into()).into()),
};

let column_name = format!("external_{}", member.member);

model_clauses.entry(member.model.clone()).or_default().push((
column_name,
comparison_operator,
comparison_value,
));

let (namespace, model) = member
.model
.split_once('-')
.ok_or(QueryError::InvalidNamespacedModel(member.model.clone()))?;
let model_id: Felt = compute_selector_from_names(namespace, model);
having_clauses.push(format!("INSTR(model_ids, '{:#x}') > 0", model_id));
}
_ => return Err(QueryError::UnsupportedQuery.into()),
}
}

let mut join_clauses = Vec::new();
for (model, clauses) in model_clauses {
let model_conditions = clauses
.into_iter()
.map(|(column, op, value)| {
bind_values.push(value);
format!("[{}].{} {} ?", model, column, op)
})
.collect::<Vec<_>>()
.join(" AND ");

join_clauses.push(format!(
"JOIN [{}] ON [{}].id = [{}].entity_id AND ({})",
model, table, model, model_conditions
));
}

let join_clause = join_clauses.join(" ");
let where_clause = if !where_clauses.is_empty() {
format!("WHERE {}", where_clauses.join(" AND "))
} else {
String::new()
};
let having_clause = if !having_clauses.is_empty() {
format!("HAVING {}", having_clauses.join(" AND "))
} else {
String::new()
};
let (where_clause, having_clause, join_clause, bind_values) =
build_composite_clause(table, model_relation_table, &composite)?;

Check warning on line 602 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L601-L602

Added lines #L601 - L602 were not covered by tests

let count_query = format!(
r#"
SELECT COUNT(DISTINCT [{table}].id)
FROM [{table}]
JOIN {model_relation_table} ON [{table}].id = {model_relation_table}.entity_id

Check warning on line 608 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L608

Added line #L608 was not covered by tests
{join_clause}
{where_clause}
{having_clause}

Check warning on line 611 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L611

Added line #L611 was not covered by tests
"#
);

Expand All @@ -701,7 +618,6 @@
}

let total_count = count_query.fetch_optional(&self.pool).await?.unwrap_or(0);

if total_count == 0 {
return Ok((Vec::new(), 0));
}
Expand All @@ -721,7 +637,7 @@
);

let mut db_query = sqlx::query_as(&query);
for value in bind_values {
for value in &bind_values {

Check warning on line 640 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L640

Added line #L640 was not covered by tests
db_query = db_query.bind(value);
}
db_query = db_query.bind(limit.unwrap_or(u32::MAX)).bind(offset.unwrap_or(0));
Expand Down Expand Up @@ -1042,6 +958,111 @@
Ok(keys_pattern)
}

// builds a composite clause for a query
fn build_composite_clause(
table: &str,
model_relation_table: &str,
composite: &proto::types::CompositeClause,
) -> Result<(String, String, String, Vec<String>), Error> {
let is_or = composite.operator == LogicalOperator::Or as i32;
let mut where_clauses = Vec::new();
let mut join_clauses = Vec::new();
let mut having_clauses = Vec::new();
let mut bind_values = Vec::new();

Check warning on line 971 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L962-L971

Added lines #L962 - L971 were not covered by tests

for clause in &composite.clauses {
match clause.clause_type.as_ref().unwrap() {
ClauseType::HashedKeys(hashed_keys) => {
let ids = hashed_keys
.hashed_keys
.iter()
.map(|id| {
bind_values.push(Felt::from_bytes_be_slice(id).to_string());
"?".to_string()
})
.collect::<Vec<_>>()
.join(", ");
where_clauses.push(format!("{table}.id IN ({})", ids));
}
ClauseType::Keys(keys) => {
let keys_pattern = build_keys_pattern(keys)?;
bind_values.push(keys_pattern);
where_clauses.push(format!("{table}.keys REGEXP ?"));

Check warning on line 990 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L973-L990

Added lines #L973 - L990 were not covered by tests
}
ClauseType::Member(member) => {
let comparison_operator = ComparisonOperator::from_repr(member.operator as usize)
.expect("invalid comparison operator");
let value = member.value.clone();
let comparison_value =
match value.ok_or(QueryError::MissingParam("value".into()))?.value_type {
Some(ValueType::String(value)) => value,
Some(ValueType::Primitive(value)) => {
let primitive: Primitive = value.try_into()?;
primitive.to_sql_value()?

Check warning on line 1001 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L992-L1001

Added lines #L992 - L1001 were not covered by tests
}
None => return Err(QueryError::MissingParam("value_type".into()).into()),

Check warning on line 1003 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1003

Added line #L1003 was not covered by tests
};
bind_values.push(comparison_value);

let model = member.model.clone();
let parts: Vec<&str> = member.member.split('.').collect();
let (table_name, column_name) = if parts.len() > 1 {
let nested_table = parts[..parts.len() - 1].join("$");
(
format!("[{model}${nested_table}]"),
format!("external_{}", parts.last().unwrap()),
)

Check warning on line 1014 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1005-L1014

Added lines #L1005 - L1014 were not covered by tests
} else {
(format!("[{model}]"), format!("external_{}", member.member))

Check warning on line 1016 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1016

Added line #L1016 was not covered by tests
};

let (namespace, model) = member
.model
.split_once('-')
.ok_or(QueryError::InvalidNamespacedModel(member.model.clone()))?;
let model_id = compute_selector_from_names(namespace, model);
join_clauses.push(format!(
"LEFT JOIN {table_name} ON [{table}].id = {table_name}.entity_id"
));
where_clauses.push(format!("{table_name}.{column_name} {comparison_operator} ?"));
having_clauses.push(format!(
"INSTR(group_concat({model_relation_table}.model_id), '{:#x}') > 0",
model_id
));

Check warning on line 1031 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1019-L1031

Added lines #L1019 - L1031 were not covered by tests
}
ClauseType::Composite(nested_composite) => {
let (nested_where, nested_having, nested_join, nested_values) =
build_composite_clause(table, model_relation_table, nested_composite)?;
where_clauses.push(format!("({})", nested_where.trim_start_matches("WHERE ")));
if !nested_having.is_empty() {
having_clauses.push(nested_having.trim_start_matches("HAVING ").to_string());
}
join_clauses.extend(
nested_join
.split_whitespace()
.filter(|&s| s.starts_with("LEFT"))
.map(String::from),
);
bind_values.extend(nested_values);
}

Check warning on line 1047 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1033-L1047

Added lines #L1033 - L1047 were not covered by tests
}
}

let join_clause = join_clauses.join(" ");
let where_clause = if !where_clauses.is_empty() {
format!("WHERE {}", where_clauses.join(if is_or { " OR " } else { " AND " }))

Check warning on line 1053 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1051-L1053

Added lines #L1051 - L1053 were not covered by tests
} else {
String::new()

Check warning on line 1055 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1055

Added line #L1055 was not covered by tests
};
let having_clause = if !having_clauses.is_empty() {
format!("HAVING {}", having_clauses.join(if is_or { " OR " } else { " AND " }))

Check warning on line 1058 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1057-L1058

Added lines #L1057 - L1058 were not covered by tests
} else {
String::new()

Check warning on line 1060 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1060

Added line #L1060 was not covered by tests
};

Ok((where_clause, having_clause, join_clause, bind_values))
}

Check warning on line 1064 in crates/torii/grpc/src/server/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/grpc/src/server/mod.rs#L1063-L1064

Added lines #L1063 - L1064 were not covered by tests

type ServiceResult<T> = Result<Response<T>, Status>;
type SubscribeModelsResponseStream =
Pin<Box<dyn Stream<Item = Result<SubscribeModelsResponse, Status>> + Send>>;
Expand Down
Loading