fts_sqlite/impl/
product.rs

1use crate::Db;
2use fts_core::ports::ProductRepository;
3
4impl<ProductData: Send + Unpin + 'static + serde::Serialize + serde::de::DeserializeOwned>
5    ProductRepository<ProductData> for Db
6{
7    async fn create_product(
8        &self,
9        product_id: Self::ProductId,
10        app_data: ProductData,
11        as_of: Self::DateTime,
12    ) -> Result<(), Self::Error> {
13        // the triggers manage all the details of updating the product tree
14        // TODO: errors about temporary value being dropped if we do not
15        //       explicitly bind Json(app_data) to a variable first.
16        //       research this!
17        let app_data = sqlx::types::Json(app_data);
18        sqlx::query!(
19            r#"
20            insert into
21                product (id, as_of, app_data)
22            values
23                ($1, $2, jsonb($3))
24            "#,
25            product_id,
26            as_of,
27            app_data,
28        )
29        .execute(&self.writer)
30        .await?;
31
32        Ok(())
33    }
34
35    async fn partition_product<T: Send + IntoIterator<Item = (Self::ProductId, ProductData, f64)>>(
36        &self,
37        product_id: Self::ProductId,
38        children: T,
39        as_of: Self::DateTime,
40    ) -> Result<usize, Self::Error>
41    where
42        T::IntoIter: Send + ExactSizeIterator,
43    {
44        let children = children.into_iter();
45        if children.len() == 0 {
46            return Ok(0);
47        }
48
49        let mut query_builder = sqlx::QueryBuilder::new(
50            "insert into product (id, as_of, app_data, parent_id, parent_ratio) ",
51        );
52        // TODO: we should partition the iterator to play nice with DB limits
53        query_builder.push_values(children, |mut b, (child_id, app_data, child_ratio)| {
54            b.push_bind(child_id)
55                .push_bind(as_of)
56                .push("jsonb(")
57                .push_bind_unseparated(sqlx::types::Json(app_data))
58                .push_unseparated(")")
59                .push_bind(product_id)
60                .push_bind(child_ratio);
61        });
62
63        let result = query_builder.build().execute(&self.writer).await?;
64
65        Ok(result.rows_affected() as usize)
66    }
67
68    async fn get_product(
69        &self,
70        product_id: Self::ProductId,
71        _as_of: Self::DateTime,
72    ) -> Result<Option<ProductData>, Self::Error> {
73        let product_data = sqlx::query_scalar!(
74            r#"
75            select
76                json(app_data) as "data!: sqlx::types::Json::<ProductData>"
77            from
78                product
79            where
80                id = $1
81            "#,
82            product_id
83        )
84        .fetch_optional(&self.reader)
85        .await?;
86
87        // TODO: We're not presently using as_of, but it should be used
88        //       to gather some sort of tree-relationships.
89        // To research:
90        // Either as part of this function or as a dedicated, separate function,
91        // we want all paths ending in self and starting at self.
92        // The former is linear, the latter is a tree -- maybe there is a
93        // nice infix traversal that, coupled with `depth`, can be used to (de)serialize the trees uniquely?
94
95        Ok(product_data.map(|x| x.0))
96    }
97}