commit 2e0bc517499df7efebda9eaf6a25ca82038daec2
parent 53d95b42f1a66297d3ffb2e01aaeab53873b8a1b
Author: triesap <tyson@radroots.org>
Date: Wed, 24 Dec 2025 23:08:31 +0000
sql: support rel-based location_gcs lookups
- Extend ILocationGcsFindOne to accept rel selector variants
- Implement rel_query and find_one_by_rel SQL helpers
- Update create/update/delete paths to use enum-based find/delete args
- Add trade_listing_dvm_tags helper with unit tests for tag assembly
Diffstat:
4 files changed, 129 insertions(+), 46 deletions(-)
diff --git a/tangle-schema/bindings/ts/src/types.ts b/tangle-schema/bindings/ts/src/types.ts
@@ -54,7 +54,7 @@ export type ILocationGcsFindMany = { filter: ILocationGcsFieldsFilter | null, }
export type ILocationGcsFindManyResolve = IResultList<LocationGcs>;
-export type ILocationGcsFindOne = { on: LocationGcsQueryBindValues, };
+export type ILocationGcsFindOne = { on: LocationGcsQueryBindValues, } | { rel: LocationGcsFindManyRel, };
export type ILocationGcsFindOneResolve = IResult<LocationGcs>;
diff --git a/tangle-schema/src/models/location_gcs.rs b/tangle-schema/src/models/location_gcs.rs
@@ -209,16 +209,25 @@ pub type ILocationGcsCreate = ILocationGcsFields;
)]
pub struct ILocationGcsCreateResolveTs;
pub type ILocationGcsCreateResolve = IResult<LocationGcs>;
+#[derive(Deserialize, Serialize)]
+pub struct ILocationGcsFindOneArgs {
+ pub on: LocationGcsQueryBindValues,
+}
#[cfg_attr(feature = "ts-rs", derive(TS))]
#[cfg_attr(
feature = "ts-rs",
ts(export, export_to = "types.ts", rename = "ILocationGcsFindOne")
)]
#[derive(Deserialize, Serialize)]
-pub struct ILocationGcsFindOneArgs {
- pub on: LocationGcsQueryBindValues,
+#[serde(untagged)]
+pub enum ILocationGcsFindOne {
+ On {
+ on: LocationGcsQueryBindValues,
+ },
+ Rel {
+ rel: LocationGcsFindManyRel,
+ },
}
-pub type ILocationGcsFindOne = ILocationGcsFindOneArgs;
#[cfg_attr(feature = "ts-rs", derive(TS))]
#[cfg_attr(
feature = "ts-rs",
@@ -269,7 +278,7 @@ pub type ILocationGcsFindManyResolve = IResultList<LocationGcs>;
)
)]
pub struct ILocationGcsDeleteTs;
-pub type ILocationGcsDelete = ILocationGcsFindOneArgs;
+pub type ILocationGcsDelete = ILocationGcsFindOne;
#[cfg_attr(feature = "ts-rs", derive(TS))]
#[cfg_attr(
feature = "ts-rs",
@@ -304,4 +313,4 @@ pub type ILocationGcsUpdate = ILocationGcsUpdateArgs;
)
)]
pub struct ILocationGcsUpdateResolveTs;
-pub type ILocationGcsUpdateResolve = IResult<LocationGcs>;
-\ No newline at end of file
+pub type ILocationGcsUpdateResolve = IResult<LocationGcs>;
diff --git a/tangle-sql/src/models/location_gcs.rs b/tangle-sql/src/models/location_gcs.rs
@@ -36,7 +36,7 @@ pub fn create<E: SqlExecutor>(
let (sql, bind_values) = utils::build_insert_query_with_meta(TABLE_NAME, &meta, &field_map);
let params_json = utils::to_params_json(bind_values)?;
let _ = exec.exec(&sql, ¶ms_json)?;
- let args = ILocationGcsFindOne {
+ let args = ILocationGcsFindOne::On {
on: LocationGcsQueryBindValues::Id { id: id.clone() },
};
let found = find_one(exec, &args)?;
@@ -50,12 +50,17 @@ pub fn find_one<E: SqlExecutor>(
exec: &E,
opts: &ILocationGcsFindOne,
) -> Result<ILocationGcsFindOneResolve, IError<SqlError>> {
- let (column, value) = opts.on.to_filter_param();
- let sql = format!("SELECT * FROM {TABLE_NAME} WHERE {column} = ? LIMIT 1;");
- let params_json = utils::to_params_json(vec![value])?;
- let json = exec.query_raw(&sql, ¶ms_json)?;
- let mut rows: Vec<LocationGcs> = utils::parse_json(&json)?;
- let result = rows.pop();
+ let result = match opts {
+ ILocationGcsFindOne::On { on } => {
+ let (column, value) = on.to_filter_param();
+ let sql = format!("SELECT * FROM {TABLE_NAME} WHERE {column} = ? LIMIT 1;");
+ let params_json = utils::to_params_json(vec![value])?;
+ let json = exec.query_raw(&sql, ¶ms_json)?;
+ let mut rows: Vec<LocationGcs> = utils::parse_json(&json)?;
+ rows.pop()
+ }
+ ILocationGcsFindOne::Rel { rel } => find_one_by_rel(exec, rel)?,
+ };
Ok(IResult { result })
}
@@ -81,33 +86,46 @@ fn find_many_filter<E: SqlExecutor>(
Ok(rows)
}
+fn rel_query(rel: &LocationGcsFindManyRel) -> (&'static str, Vec<Value>) {
+ match rel {
+ LocationGcsFindManyRel::OnTradeProduct(args) => (
+ "SELECT lg.* FROM location_gcs lg JOIN trade_product_location tp_lg ON lg.id = tp_lg.tb_lg WHERE tp_lg.tb_tp = ?",
+ vec![Value::from(args.id.clone())],
+ ),
+ LocationGcsFindManyRel::OffTradeProduct(args) => (
+ "SELECT lg.* FROM location_gcs lg WHERE NOT EXISTS (SELECT 1 FROM trade_product_location tp_lg WHERE tp_lg.tb_lg = lg.id AND tp_lg.tb_tp = ?)",
+ vec![Value::from(args.id.clone())],
+ ),
+ LocationGcsFindManyRel::OnFarm(args) => (
+ "SELECT lg.* FROM location_gcs lg JOIN farm_location farm_lg ON lg.id = farm_lg.tb_lg WHERE farm_lg.tb_farm = ?",
+ vec![Value::from(args.id.clone())],
+ ),
+ LocationGcsFindManyRel::OffFarm(args) => (
+ "SELECT lg.* FROM location_gcs lg WHERE NOT EXISTS (SELECT 1 FROM farm_location farm_lg WHERE farm_lg.tb_lg = lg.id AND farm_lg.tb_farm = ?)",
+ vec![Value::from(args.id.clone())],
+ ),
+ }
+}
+
+fn find_one_by_rel<E: SqlExecutor>(
+ exec: &E,
+ rel: &LocationGcsFindManyRel,
+) -> Result<Option<LocationGcs>, IError<SqlError>> {
+ let (sql, bind_values) = rel_query(rel);
+ let params_json = utils::to_params_json(bind_values)?;
+ let sql = format!("{sql} LIMIT 1;");
+ let json = exec.query_raw(&sql, ¶ms_json)?;
+ let mut rows: Vec<LocationGcs> = utils::parse_json(&json)?;
+ Ok(rows.pop())
+}
+
fn find_many_by_rel<E: SqlExecutor>(
exec: &E,
rel: &LocationGcsFindManyRel,
) -> Result<Vec<LocationGcs>, IError<SqlError>> {
- let (sql, bind_values): (String, Vec<Value>) = match rel {
- LocationGcsFindManyRel::OnTradeProduct(args) => {
- let sql = String::from("SELECT lg.* FROM location_gcs lg JOIN trade_product_location tp_lg ON lg.id = tp_lg.tb_lg WHERE tp_lg.tb_tp = ?;");
- let binds = vec![Value::from(args.id.clone())];
- (sql, binds)
- }
- LocationGcsFindManyRel::OffTradeProduct(args) => {
- let sql = String::from("SELECT lg.* FROM location_gcs lg WHERE NOT EXISTS (SELECT 1 FROM trade_product_location tp_lg WHERE tp_lg.tb_lg = lg.id AND tp_lg.tb_tp = ?);");
- let binds = vec![Value::from(args.id.clone())];
- (sql, binds)
- }
- LocationGcsFindManyRel::OnFarm(args) => {
- let sql = String::from("SELECT lg.* FROM location_gcs lg JOIN farm_location farm_lg ON lg.id = farm_lg.tb_lg WHERE farm_lg.tb_farm = ?;");
- let binds = vec![Value::from(args.id.clone())];
- (sql, binds)
- }
- LocationGcsFindManyRel::OffFarm(args) => {
- let sql = String::from("SELECT lg.* FROM location_gcs lg WHERE NOT EXISTS (SELECT 1 FROM farm_location farm_lg WHERE farm_lg.tb_lg = lg.id AND farm_lg.tb_farm = ?);");
- let binds = vec![Value::from(args.id.clone())];
- (sql, binds)
- }
- };
+ let (sql, bind_values) = rel_query(rel);
let params_json = utils::to_params_json(bind_values)?;
+ let sql = format!("{sql};");
let json = exec.query_raw(&sql, ¶ms_json)?;
let rows: Vec<LocationGcs> = utils::parse_json(&json)?;
Ok(rows)
@@ -143,7 +161,7 @@ pub fn update<E: SqlExecutor>(
let id_for_lookup = match opts.on.primary_key() {
Some(id) => id,
None => {
- let find_opts = ILocationGcsFindOne {
+ let find_opts = ILocationGcsFindOne::On {
on: opts.on.clone(),
};
let found = find_one(exec, &find_opts)?;
@@ -166,14 +184,21 @@ pub fn delete<E: SqlExecutor>(
exec: &E,
opts: &ILocationGcsDelete,
) -> Result<ILocationGcsDeleteResolve, IError<SqlError>> {
- let id_for_lookup = match opts.on.primary_key() {
- Some(id) => id,
- None => {
- let find_opts = ILocationGcsFindOne {
- on: opts.on.clone(),
- };
- let found = find_one(exec, &find_opts)?;
- let model = found.result.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?;
+ let id_for_lookup = match opts {
+ ILocationGcsDelete::On { on } => match on.primary_key() {
+ Some(id) => id,
+ None => {
+ let find_opts = ILocationGcsFindOne::On { on: on.clone() };
+ let found = find_one(exec, &find_opts)?;
+ let model = found
+ .result
+ .ok_or_else(|| IError::from(SqlError::NotFound(on.lookup_key())))?;
+ model.id
+ }
+ },
+ ILocationGcsDelete::Rel { rel } => {
+ let found = find_one_by_rel(exec, rel)?;
+ let model = found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(rel))))?;
model.id
}
};
@@ -185,3 +210,12 @@ pub fn delete<E: SqlExecutor>(
}
Ok(IResult { result: id_for_lookup })
}
+
+fn rel_lookup_key(rel: &LocationGcsFindManyRel) -> String {
+ match rel {
+ LocationGcsFindManyRel::OnTradeProduct(args) => format!("on_trade_product:{}", args.id.as_str()),
+ LocationGcsFindManyRel::OffTradeProduct(args) => format!("off_trade_product:{}", args.id.as_str()),
+ LocationGcsFindManyRel::OnFarm(args) => format!("on_farm:{}", args.id.as_str()),
+ LocationGcsFindManyRel::OffFarm(args) => format!("off_farm:{}", args.id.as_str()),
+ }
+}
diff --git a/trade/src/listing/tags.rs b/trade/src/listing/tags.rs
@@ -13,6 +13,26 @@ fn push_tag(tags: &mut Vec<Vec<String>>, name: &'static str, value: impl Into<St
}
#[inline]
+pub fn trade_listing_dvm_tags<P, A, D>(
+ recipient_pubkey: P,
+ listing_addr: A,
+ order_id: Option<D>,
+) -> Vec<Vec<String>>
+where
+ P: Into<String>,
+ A: Into<String>,
+ D: Into<String>,
+{
+ let mut tags = Vec::with_capacity(2 + usize::from(order_id.is_some()));
+ push_tag(&mut tags, "p", recipient_pubkey);
+ push_tag(&mut tags, "a", listing_addr);
+ if let Some(order_id) = order_id {
+ push_tag(&mut tags, TAG_D, order_id);
+ }
+ tags
+}
+
+#[inline]
pub fn push_trade_listing_chain_tags(
tags: &mut Vec<Vec<String>>,
e_root_id: impl Into<String>,
@@ -81,7 +101,7 @@ pub fn validate_trade_listing_chain(tags: &[Vec<String>]) -> Result<(), JobParse
#[cfg(test)]
mod tests {
- use super::validate_trade_listing_chain;
+ use super::{trade_listing_dvm_tags, validate_trade_listing_chain};
use radroots_events::tags::{TAG_D, TAG_E_ROOT};
use radroots_events_codec::job::error::JobParseError;
@@ -118,4 +138,25 @@ mod tests {
other => panic!("expected invalid root tag, got {other:?}"),
}
}
+
+ #[test]
+ fn trade_listing_dvm_tags_builds_expected_tags() {
+ let tags = trade_listing_dvm_tags("pubkey", "30402:pubkey:listing", Some("order-1"));
+ let expected: Vec<Vec<String>> = vec![
+ vec![String::from("p"), String::from("pubkey")],
+ vec![String::from("a"), String::from("30402:pubkey:listing")],
+ vec![String::from(TAG_D), String::from("order-1")],
+ ];
+ assert_eq!(tags, expected);
+ }
+
+ #[test]
+ fn trade_listing_dvm_tags_omit_order_id_when_missing() {
+ let tags = trade_listing_dvm_tags("pubkey", "30402:pubkey:listing", None::<String>);
+ let expected: Vec<Vec<String>> = vec![
+ vec![String::from("p"), String::from("pubkey")],
+ vec![String::from("a"), String::from("30402:pubkey:listing")],
+ ];
+ assert_eq!(tags, expected);
+ }
}