Skip to content

Commit

Permalink
[db] Support InsertOrUpdate #1092 (#1110)
Browse files Browse the repository at this point in the history
* wip

* update docs & coverage

* wip

* impl insert edges with ids

* update docs

* add ids to api
  • Loading branch information
michaelvlach authored Jun 9, 2024
1 parent 0ff3647 commit 3c32b48
Show file tree
Hide file tree
Showing 14 changed files with 607 additions and 45 deletions.
57 changes: 50 additions & 7 deletions agdb/src/query/insert_edges_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ use crate::StorageData;
/// origin to every destination. By default it would connect only
/// the pairs. For asymmetric inserts `each` is assumed.
///
/// The result will contain number of edges inserted and elements with
/// their ids but no properties.
/// If the `ids` member is empty the query will insert new edges
/// otherwise it will update the existing edges. The rules for length
/// of `values` still apply and the search yield or static list must
/// have equal length to the `values` (or the `Single` variant must
/// be used).
///
/// The result will contain number of edges inserted or udpated and elements
/// with their ids, origin and destination, but no properties.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[derive(Debug, PartialEq)]
Expand All @@ -30,6 +36,10 @@ pub struct InsertEdgesQuery {
/// Destinations
pub to: QueryIds,

/// Optional ids of edges (optionally a search sub-query).
/// This can be empty.
pub ids: QueryIds,

/// Key value pairs to be associated with
/// the new edges.
pub values: QueryValues,
Expand All @@ -46,13 +56,46 @@ impl QueryMut for InsertEdgesQuery {
) -> Result<QueryResult, QueryError> {
let mut result = QueryResult::default();

let from = Self::db_ids(&self.from, db)?;
let to: Vec<DbId> = Self::db_ids(&self.to, db)?;
let query_ids = match &self.ids {
QueryIds::Ids(ids) => ids
.iter()
.map(|query_id| db.db_id(query_id))
.collect::<Result<Vec<DbId>, QueryError>>()?,
QueryIds::Search(search_query) => search_query.search(db)?,
};

let ids = if !query_ids.is_empty() {
query_ids.iter().try_for_each(|db_id| {
if db_id.0 > 0 {
Err(QueryError::from(format!(
"The ids for insert or update must all refer to edges - node id '{}' found",
db_id.0
)))
} else {
Ok(())
}
})?;

let mut ids = vec![];

let ids = if self.each || from.len() != to.len() {
self.many_to_many_each(db, &from, &to)?
for (db_id, key_values) in query_ids.iter().zip(self.values(query_ids.len())?) {
for key_value in key_values {
db.insert_or_replace_key_value(*db_id, key_value)?;
}

ids.push(*db_id);
}

ids
} else {
self.many_to_many(db, &from, &to)?
let from = Self::db_ids(&self.from, db)?;
let to: Vec<DbId> = Self::db_ids(&self.to, db)?;

if self.each || from.len() != to.len() {
self.many_to_many_each(db, &from, &to)?
} else {
self.many_to_many(db, &from, &to)?
}
};

result.result = ids.len() as i64;
Expand Down
101 changes: 77 additions & 24 deletions agdb/src/query/insert_nodes_query.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::query::query_values::QueryValues;
use crate::DbElement;
use crate::DbId;
use crate::DbImpl;
use crate::QueryError;
use crate::QueryId;
use crate::QueryIds;
use crate::QueryMut;
use crate::QueryResult;
use crate::StorageData;
Expand All @@ -15,8 +17,14 @@ use crate::StorageData;
/// set to `Single` there must be enough value for `count/aliases`
/// unless they are not se and the count is derived from `values.
///
/// The result will contain number of nodes inserted and elements with
/// their ids but no properties.
/// If the `ids` member is empty the query will insert new nodes
/// otherwise it will update the existing nodes. The rules for length
/// of `values` still apply and the search yield or static list must
/// have equal length to the `values` (or the `Single` variant must
/// be used).
///
/// The result will contain number of nodes inserted or updated and elements
/// with their ids but no properties.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[derive(Debug, PartialEq)]
Expand All @@ -30,6 +38,10 @@ pub struct InsertNodesQuery {

/// Aliases of the new nodes.
pub aliases: Vec<String>,

/// Optional ids of nodes (optionally a search sub-query).
/// This can be empty.
pub ids: QueryIds,
}

impl QueryMut for InsertNodesQuery {
Expand All @@ -45,36 +57,77 @@ impl QueryMut for InsertNodesQuery {
QueryValues::Multi(v) => v.iter().collect(),
};

if !self.aliases.is_empty() && values.len() != self.aliases.len() {
return Err(QueryError::from(format!(
"Values ({}) and aliases ({}) must have the same length",
values.len(),
self.aliases.len()
)));
}
let query_ids = match &self.ids {
QueryIds::Ids(ids) => ids
.iter()
.map(|query_id| db.db_id(query_id))
.collect::<Result<Vec<DbId>, QueryError>>()?,
QueryIds::Search(search_query) => search_query.search(db)?,
};

for (index, key_values) in values.iter().enumerate() {
if let Some(alias) = self.aliases.get(index) {
if let Ok(db_id) = db.db_id(&QueryId::Alias(alias.to_string())) {
ids.push(db_id);
if !query_ids.is_empty() {
query_ids.iter().try_for_each(|db_id| {
if db_id.0 < 0 {
Err(QueryError::from(format!(
"The ids for insert or update must all refer to nodes - edge id '{}' found",
db_id.0
)))
} else {
Ok(())
}
})?;

for key_value in *key_values {
db.insert_or_replace_key_value(db_id, key_value)?;
}
if values.len() != query_ids.len() {
return Err(QueryError::from(format!(
"Values ({}) and ids ({}) must have the same length",
values.len(),
query_ids.len()
)));
}

continue;
for ((index, db_id), key_values) in query_ids.iter().enumerate().zip(values) {
for key_value in key_values {
db.insert_or_replace_key_value(*db_id, key_value)?;
}
}

let db_id = db.insert_node()?;
ids.push(db_id);
if let Some(alias) = self.aliases.get(index) {
db.insert_new_alias(*db_id, alias)?;
}

if let Some(alias) = self.aliases.get(index) {
db.insert_new_alias(db_id, alias)?;
ids.push(*db_id);
}
} else {
if !self.aliases.is_empty() && values.len() != self.aliases.len() {
return Err(QueryError::from(format!(
"Values ({}) and aliases ({}) must have the same length",
values.len(),
self.aliases.len()
)));
}

for key_value in *key_values {
db.insert_key_value(db_id, key_value)?;
for (index, key_values) in values.iter().enumerate() {
if let Some(alias) = self.aliases.get(index) {
if let Ok(db_id) = db.db_id(&QueryId::Alias(alias.to_string())) {
ids.push(db_id);

for key_value in *key_values {
db.insert_or_replace_key_value(db_id, key_value)?;
}

continue;
}
}

let db_id = db.insert_node()?;
ids.push(db_id);

if let Some(alias) = self.aliases.get(index) {
db.insert_new_alias(db_id, alias)?;
}

for key_value in *key_values {
db.insert_key_value(db_id, key_value)?;
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions agdb/src/query_builder/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ impl Insert {
/// QueryBuilder::insert().edges().from(1);
/// QueryBuilder::insert().edges().from(vec![1, 2]);
/// QueryBuilder::insert().edges().from(QueryBuilder::search().from(1).query());
/// QueryBuilder::insert().edges().ids(-3);
/// QueryBuilder::insert().edges().ids(vec![-3, -4]);
/// QueryBuilder::insert().edges().ids(QueryBuilder::search().from(1).where_().edge().query());
/// ```
pub fn edges(self) -> InsertEdges {
InsertEdges(InsertEdgesQuery {
from: QueryIds::Ids(vec![]),
to: QueryIds::Ids(vec![]),
ids: QueryIds::Ids(vec![]),
values: QueryValues::Single(vec![]),
each: false,
})
Expand Down Expand Up @@ -107,13 +111,19 @@ impl Insert {
/// QueryBuilder::insert().nodes().count(1);
/// QueryBuilder::insert().nodes().aliases("a");
/// QueryBuilder::insert().nodes().aliases(vec!["a", "b"]);
/// QueryBuilder::insert().nodes().ids(1);
/// QueryBuilder::insert().nodes().ids(vec![1, 2]);
/// QueryBuilder::insert().nodes().ids("a");
/// QueryBuilder::insert().nodes().ids(vec!["a", "b"]);
/// QueryBuilder::insert().nodes().ids(QueryBuilder::search().from(1).query());
/// QueryBuilder::insert().nodes().values(vec![vec![("k", 1).into()]]);
/// ```
pub fn nodes(self) -> InsertNodes {
InsertNodes(InsertNodesQuery {
count: 0,
values: QueryValues::Single(vec![]),
aliases: vec![],
ids: QueryIds::Ids(vec![]),
})
}

Expand Down
44 changes: 44 additions & 0 deletions agdb/src/query_builder/insert_edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ pub struct InsertEdgesFrom(pub InsertEdgesQuery);
/// or set `each`.
pub struct InsertEdgesFromTo(pub InsertEdgesQuery);

/// Insert edges builder with ids allowing insert
/// or update semantics that lets you add `from`
/// (origin) nodes.
pub struct InsertEdgesIds(pub InsertEdgesQuery);

/// Final builder that lets you create
/// an actual query object.
pub struct InsertEdgesValues(pub InsertEdgesQuery);
Expand All @@ -40,6 +45,45 @@ impl InsertEdges {

InsertEdgesFrom(self.0)
}

/// Optional ids of edges (can be search sub-query) to be
/// inserted or updated. If the list is empty the nodes will be
/// inserted. If the list is not empty all ids must exist in the
/// database and will be updated instead:
///
/// Options:
///
/// ```
/// use agdb::QueryBuilder;
///
/// QueryBuilder::insert().edges().ids(1).from(1);
/// QueryBuilder::insert().edges().ids(1).from(vec![1, 2]);
/// QueryBuilder::insert().edges().ids(1).from(QueryBuilder::search().from(1).query());
/// ```
pub fn ids<T: Into<QueryIds>>(mut self, ids: T) -> InsertEdgesIds {
self.0.ids = ids.into();

InsertEdgesIds(self.0)
}
}

impl InsertEdgesIds {
/// An id or list of ids or search query from where the edges should come from (origin).
///
/// Options:
///
/// ```
/// use agdb::QueryBuilder;
///
/// QueryBuilder::insert().edges().from(1).to(2);
/// QueryBuilder::insert().edges().from(1).to(vec![2, 3]);
/// QueryBuilder::insert().edges().from(1).to(QueryBuilder::search().from(1).query());
/// ```
pub fn from<T: Into<QueryIds>>(mut self, ids: T) -> InsertEdgesFrom {
self.0.from = ids.into();

InsertEdgesFrom(self.0)
}
}

impl InsertEdgesEach {
Expand Down
Loading

0 comments on commit 3c32b48

Please sign in to comment.