From 3e0fae2c3355512f1cb40220c1ec06e4b9227a5d Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 8 Sep 2023 17:25:36 +0100 Subject: [PATCH] Merger --- packages/system-query/.gitignore | 3 +- packages/system-query/Cargo.lock | 84 +++ packages/system-query/Cargo.toml | 2 + packages/system-query/pkg/system_query.d.ts | 7 + packages/system-query/pkg/system_query.js | 21 + .../system-query/pkg/system_query_bg.wasm | Bin 83956 -> 89560 bytes .../pkg/system_query_bg.wasm.d.ts | 1 + packages/system-query/src/diff.rs | 176 +++--- packages/system-query/src/expand.rs | 323 +++++++++-- packages/system-query/src/lib.rs | 136 ++++- packages/system-query/src/merge.rs | 512 ++++++++++++++++++ packages/system-query/system-query.iml | 12 + packages/system/src/nostr-system.ts | 2 +- packages/system/src/query.ts | 10 +- packages/system/src/request-builder.ts | 15 +- 15 files changed, 1156 insertions(+), 148 deletions(-) create mode 100644 packages/system-query/src/merge.rs create mode 100644 packages/system-query/system-query.iml diff --git a/packages/system-query/.gitignore b/packages/system-query/.gitignore index e673575a..d6c6ec7e 100644 --- a/packages/system-query/.gitignore +++ b/packages/system-query/.gitignore @@ -1,2 +1,3 @@ .idea/ -target/ \ No newline at end of file +target/ +*.txt \ No newline at end of file diff --git a/packages/system-query/Cargo.lock b/packages/system-query/Cargo.lock index 036190d1..c1a9a84a 100644 --- a/packages/system-query/Cargo.lock +++ b/packages/system-query/Cargo.lock @@ -30,6 +30,17 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "itertools" version = "0.11.0" @@ -39,6 +50,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + [[package]] name = "js-sys" version = "0.3.64" @@ -48,6 +65,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + [[package]] name = "log" version = "0.4.20" @@ -60,6 +83,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.66" @@ -78,6 +107,42 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + [[package]] name = "scoped-tls" version = "1.0.1" @@ -115,6 +180,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "syn" version = "2.0.31" @@ -131,8 +207,10 @@ name = "system-query" version = "0.1.0" dependencies = [ "itertools", + "rand", "serde", "serde-wasm-bindgen", + "serde_json", "wasm-bindgen", "wasm-bindgen-test", ] @@ -143,6 +221,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.87" diff --git a/packages/system-query/Cargo.toml b/packages/system-query/Cargo.toml index 0ced6b86..17f087db 100644 --- a/packages/system-query/Cargo.toml +++ b/packages/system-query/Cargo.toml @@ -9,9 +9,11 @@ crate-type = ["cdylib"] [dependencies] itertools = "0.11.0" +rand = "0.8.5" serde = { version = "1.0.188", features = ["derive"] } serde-wasm-bindgen = "0.5.0" wasm-bindgen = "0.2.87" +serde_json = "1.0.105" [dev-dependencies] wasm-bindgen-test = "0.3.37" diff --git a/packages/system-query/pkg/system_query.d.ts b/packages/system-query/pkg/system_query.d.ts index 3935fee6..1b244910 100644 --- a/packages/system-query/pkg/system_query.d.ts +++ b/packages/system-query/pkg/system_query.d.ts @@ -11,6 +11,12 @@ export function diff_filters(prev: any, next: any): any; * @returns {any} */ export function expand_filter(val: any): any; +/** +* @param {any} prev +* @param {any} next +* @returns {any} +*/ +export function get_diff(prev: any, next: any): any; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; @@ -18,6 +24,7 @@ export interface InitOutput { readonly memory: WebAssembly.Memory; readonly diff_filters: (a: number, b: number, c: number) => void; readonly expand_filter: (a: number, b: number) => void; + readonly get_diff: (a: number, b: number, c: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; diff --git a/packages/system-query/pkg/system_query.js b/packages/system-query/pkg/system_query.js index 1b85b0e1..2cb5f0aa 100644 --- a/packages/system-query/pkg/system_query.js +++ b/packages/system-query/pkg/system_query.js @@ -229,6 +229,27 @@ export function expand_filter(val) { } } +/** +* @param {any} prev +* @param {any} next +* @returns {any} +*/ +export function get_diff(prev, next) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.get_diff(retptr, addHeapObject(prev), addHeapObject(next)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + function handleError(f, args) { try { return f.apply(this, args); diff --git a/packages/system-query/pkg/system_query_bg.wasm b/packages/system-query/pkg/system_query_bg.wasm index a01a0ca886b0f301fb5e3330e4afcdffbc850b31..1a585083228ae3cf7c94f2dfd83a96e351b62f94 100644 GIT binary patch delta 35689 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 Ee^#u-8vp, next: &Vec) -> Vec:: { - let mut added: Vec:: = vec![]; +pub fn diff_filter(prev: &Vec, next: &Vec) -> Vec { + let mut added: Vec = vec![]; for n in next.iter() { if !prev.iter().contains(&n) { @@ -19,38 +19,34 @@ mod tests { #[test] fn simple_diff_same() { - let prev = vec![ - FlatReqFilter { - id: Some("a".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - } - ]; - let next = vec![ - FlatReqFilter { - id: Some("a".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - } - ]; + let prev = vec![FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; + let next = vec![FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; let result = diff_filter(&prev, &next); assert_eq!(result, vec![]) @@ -58,22 +54,20 @@ mod tests { #[test] fn simple_diff_add() { - let prev = vec![ - FlatReqFilter { - id: Some("a".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - } - ]; + let prev = vec![FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; let next = vec![ FlatReqFilter { id: Some("a".to_owned()), @@ -106,8 +100,9 @@ mod tests { ]; let result = diff_filter(&prev, &next); - assert_eq!(result, vec![ - FlatReqFilter { + assert_eq!( + result, + vec![FlatReqFilter { id: Some("b".to_owned()), author: None, kind: None, @@ -120,48 +115,45 @@ mod tests { since: None, until: None, limit: None, - } - ]) + }] + ) } #[test] fn simple_diff_replace() { - let prev = vec![ - FlatReqFilter { - id: Some("a".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - } - ]; - let next = vec![ - FlatReqFilter { - id: Some("b".to_owned()), - author: None, - kind: None, - e_tag: None, - p_tag: None, - t_tag: None, - d_tag: None, - r_tag: None, - search: None, - since: None, - until: None, - limit: None, - }, - ]; + let prev = vec![FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; + let next = vec![FlatReqFilter { + id: Some("b".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }]; let result = diff_filter(&prev, &next); - assert_eq!(result, vec![ - FlatReqFilter { + assert_eq!( + result, + vec![FlatReqFilter { id: Some("b".to_owned()), author: None, kind: None, @@ -174,7 +166,7 @@ mod tests { since: None, until: None, limit: None, - } - ]) + }] + ) } -} \ No newline at end of file +} diff --git a/packages/system-query/src/expand.rs b/packages/system-query/src/expand.rs index ea619c12..46eb37f3 100644 --- a/packages/system-query/src/expand.rs +++ b/packages/system-query/src/expand.rs @@ -1,5 +1,5 @@ -use itertools::Itertools; use crate::{FlatReqFilter, ReqFilter}; +use itertools::Itertools; #[derive(Clone)] enum StringOrNumberEntry<'a> { @@ -12,39 +12,66 @@ pub fn expand_filter(filter: &ReqFilter) -> Vec { let mut inputs: Vec> = vec![]; if let Some(ids) = &filter.ids { - let t_ids = ids.iter().map(|z| StringOrNumberEntry::String(("id", z))).collect_vec(); + let t_ids = ids + .iter() + .map(|z| StringOrNumberEntry::String(("id", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(authors) = &filter.authors { - let t_ids = authors.iter().map(|z| StringOrNumberEntry::String(("author", z))).collect_vec(); + let t_ids = authors + .iter() + .map(|z| StringOrNumberEntry::String(("author", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(kinds) = &filter.kinds { - let t_ids = kinds.iter().map(|z| StringOrNumberEntry::Number(("kind", z))).collect_vec(); + let t_ids = kinds + .iter() + .map(|z| StringOrNumberEntry::Number(("kind", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(e_tags) = &filter.e_tag { - let t_ids = e_tags.iter().map(|z| StringOrNumberEntry::String(("e_tag", z))).collect_vec(); + let t_ids = e_tags + .iter() + .map(|z| StringOrNumberEntry::String(("e_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(p_tags) = &filter.p_tag { - let t_ids = p_tags.iter().map(|z| StringOrNumberEntry::String(("p_tag", z))).collect_vec(); + let t_ids = p_tags + .iter() + .map(|z| StringOrNumberEntry::String(("p_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(d_tags) = &filter.d_tag { - let t_ids = d_tags.iter().map(|z| StringOrNumberEntry::String(("d_tag", z))).collect_vec(); + let t_ids = d_tags + .iter() + .map(|z| StringOrNumberEntry::String(("d_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(t_tags) = &filter.t_tag { - let t_ids = t_tags.iter().map(|z| StringOrNumberEntry::String(("t_tag", z))).collect_vec(); + let t_ids = t_tags + .iter() + .map(|z| StringOrNumberEntry::String(("t_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(r_tags) = &filter.r_tag { - let t_ids = r_tags.iter().map(|z| StringOrNumberEntry::String(("r_tag", z))).collect_vec(); + let t_ids = r_tags + .iter() + .map(|z| StringOrNumberEntry::String(("r_tag", z))) + .collect_vec(); inputs.push(t_ids); } if let Some(search) = &filter.search { - let t_ids = search.iter().map(|z| StringOrNumberEntry::String(("search", z))).collect_vec(); + let t_ids = search + .iter() + .map(|z| StringOrNumberEntry::String(("search", z))) + .collect_vec(); inputs.push(t_ids); } @@ -155,28 +182,260 @@ mod tests { let output = expand_filter(&input); output.iter().take(5).for_each(|x| println!("{:?}", x)); let expected = vec![ - FlatReqFilter { author: Some("a".to_owned()), kind: Some(1), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(1), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(2), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(2), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(3), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("a".to_owned()), kind: Some(3), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(1), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(1), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(2), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(2), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(3), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("b".to_owned()), kind: Some(3), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(1), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(1), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(2), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(2), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(3), id: Some("x".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, - FlatReqFilter { author: Some("c".to_owned()), kind: Some(3), id: Some("y".to_owned()), p_tag: Some("a".to_owned()), t_tag: None, d_tag: None, r_tag: None, search: None, since: Some(99), until: None, limit: Some(10), e_tag: None }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(1), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(1), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(2), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(2), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(3), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("a".to_owned()), + kind: Some(3), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(1), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(1), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(2), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(2), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(3), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("b".to_owned()), + kind: Some(3), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(1), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(1), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(2), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(2), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(3), + id: Some("x".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, + FlatReqFilter { + author: Some("c".to_owned()), + kind: Some(3), + id: Some("y".to_owned()), + p_tag: Some("a".to_owned()), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: Some(99), + until: None, + limit: Some(10), + e_tag: None, + }, ]; assert_eq!(output.len(), expected.len()); - output - .iter() - .for_each(|a| assert!(expected.contains(a))); + output.iter().for_each(|a| assert!(expected.contains(a))); } -} \ No newline at end of file +} diff --git a/packages/system-query/src/lib.rs b/packages/system-query/src/lib.rs index bcdc5f46..75a8f131 100644 --- a/packages/system-query/src/lib.rs +++ b/packages/system-query/src/lib.rs @@ -1,12 +1,12 @@ -extern crate wasm_bindgen; - +use std::fmt::{Debug}; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -mod expand; mod diff; +mod expand; +mod merge; -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Clone, Serialize, Deserialize)] pub struct ReqFilter { #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] pub ids: Option>, @@ -34,7 +34,13 @@ pub struct ReqFilter { pub limit: Option, } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +impl Debug for ReqFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&serde_json::to_string(self).unwrap().to_owned()) + } +} + +#[derive(PartialEq, Clone, Serialize, Deserialize)] pub struct FlatReqFilter { #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] id: Option, @@ -62,6 +68,12 @@ pub struct FlatReqFilter { limit: Option, } +impl Debug for FlatReqFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&serde_json::to_string(self).unwrap().to_owned()) + } +} + #[wasm_bindgen] pub fn diff_filters(prev: JsValue, next: JsValue) -> Result { let prev_parsed: Vec = serde_wasm_bindgen::from_value(prev)?; @@ -75,4 +87,116 @@ pub fn expand_filter(val: JsValue) -> Result { let parsed: ReqFilter = serde_wasm_bindgen::from_value(val)?; let result = expand::expand_filter(&parsed); Ok(serde_wasm_bindgen::to_value(&result)?) -} \ No newline at end of file +} + +#[wasm_bindgen] +pub fn get_diff(prev: JsValue, next: JsValue) -> Result { + let prev_parsed: Vec = serde_wasm_bindgen::from_value(prev)?; + let next_parsed: Vec = serde_wasm_bindgen::from_value(next)?; + let expanded_prev: Vec = prev_parsed + .iter() + .flat_map(|v| expand::expand_filter(v)) + .collect(); + let expanded_next: Vec = next_parsed + .iter() + .flat_map(|v| expand::expand_filter(v)) + .collect(); + let result = diff::diff_filter(&expanded_prev, &expanded_next); + Ok(serde_wasm_bindgen::to_value(&result)?) +} + +#[wasm_bindgen] +pub fn flat_merge(val: JsValue) -> Result { + let val_parsed: Vec = serde_wasm_bindgen::from_value(val)?; + let result = merge::flat_merge(&val_parsed); + Ok(serde_wasm_bindgen::to_value(&result)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use itertools::Itertools; + use std::cmp::Ordering; + + #[test] + fn flat_merge_expanded() { + let input = vec![ + ReqFilter { + ids: None, + kinds: Some(vec![1, 6969, 6]), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + authors: Some(vec![ + "kieran".to_string(), + "snort".to_string(), + "c".to_string(), + "d".to_string(), + "e".to_string(), + ]), + since: Some(1), + until: Some(100), + search: None, + limit: None, + }, + ReqFilter { + ids: None, + kinds: Some(vec![4]), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + authors: Some(vec!["kieran".to_string()]), + limit: None, + }, + ReqFilter { + ids: None, + authors: None, + kinds: Some(vec![4]), + e_tag: None, + p_tag: Some(vec!["kieran".to_string()]), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ReqFilter { + ids: None, + kinds: Some(vec![1000]), + authors: Some(vec!["snort".to_string()]), + p_tag: Some(vec!["kieran".to_string()]), + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + e_tag: None, + limit: None, + }, + ]; + + let expanded = input + .iter() + .flat_map(|v| expand::expand_filter(v)) + .sorted_by(|_, _| { + if rand::random() { + Ordering::Less + } else { + Ordering::Greater + } + }) + .collect_vec(); + let expanded_flat = merge::flat_merge(&expanded); + assert_eq!(expanded_flat, input); + } +} diff --git a/packages/system-query/src/merge.rs b/packages/system-query/src/merge.rs new file mode 100644 index 00000000..47a8ba81 --- /dev/null +++ b/packages/system-query/src/merge.rs @@ -0,0 +1,512 @@ +use crate::{FlatReqFilter, ReqFilter}; +use itertools::Itertools; +use std::cmp::Ordering; + +pub fn flat_merge(all: &Vec) -> Vec { + let mut ret: Vec = vec![]; + + let merge_sets: Vec> = vec![vec![all.first().unwrap()]]; + let merge_sets = all + .iter() + .skip(1) + .sorted_by(|a, b| match distance(&a, &b) { + 0 => Ordering::Equal, + 1 => Ordering::Less, + _ => Ordering::Greater, + }) + .fold(merge_sets, |mut acc, x| { + let mut did_match = false; + for y in acc.iter_mut() { + if y.iter().all(|z| can_merge_filters(z, x)) { + y.push(x); + did_match = true; + break; + } + } + if !did_match { + acc.push(vec![x]); + } + acc + }); + + for s in merge_sets.iter() { + ret.push(merge_set(s)); + } + ret +} + +fn merge_set(set: &Vec<&FlatReqFilter>) -> ReqFilter { + let ret = ReqFilter { + ids: None, + authors: None, + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + set.iter().fold(ret, |mut acc, x| { + array_prop_append(&x.id, &mut acc.ids); + array_prop_append(&x.author, &mut acc.authors); + array_prop_append(&x.kind, &mut acc.kinds); + array_prop_append(&x.e_tag, &mut acc.e_tag); + array_prop_append(&x.p_tag, &mut acc.p_tag); + array_prop_append(&x.t_tag, &mut acc.t_tag); + array_prop_append(&x.d_tag, &mut acc.d_tag); + array_prop_append(&x.r_tag, &mut acc.r_tag); + array_prop_append(&x.search, &mut acc.search); + acc.since = x.since; + acc.until = x.until; + acc.limit = x.limit; + + acc + }) +} + +fn can_merge_filters(a: &FlatReqFilter, b: &FlatReqFilter) -> bool { + if a.since != b.since || a.until != b.until || a.limit != b.limit || a.search != b.search { + return false; + } + + distance(a, b) <= 1 +} + +/// Calculate the distance in terms of similarity for merging +/// +/// The goal of this function is to find 2 filters which are very similar where +/// one filter may have a single property change like so: +/// +/// ```javascript +/// const a = { "kinds": 1, "authors": "a", "since": 99 }; +/// const b = { "kinds": 1, "authors": "b", "since": 99 }; +/// ``` +/// In this case these 2 filters could be merged because their distance is `1` +/// ```javascript +/// const result = { "kinds": [1], "authors": ["a", "b"], "since": 99 }; +/// ``` +fn distance(a: &FlatReqFilter, b: &FlatReqFilter) -> u32 { + let mut ret = 0u32; + + ret += prop_dist(&a.id, &b.id); + ret += prop_dist(&a.kind, &b.kind); + ret += prop_dist(&a.author, &b.author); + ret += prop_dist(&a.e_tag, &b.e_tag); + ret += prop_dist(&a.p_tag, &b.p_tag); + ret += prop_dist(&a.d_tag, &b.d_tag); + ret += prop_dist(&a.r_tag, &b.r_tag); + ret += prop_dist(&a.t_tag, &b.t_tag); + ret += prop_dist(&a.search, &b.search); + + ret +} + +#[inline(always)] +fn prop_dist(a: &Option, b: &Option) -> u32 { + if (a.is_some() && b.is_none()) || (a.is_none() && b.is_some()) { + return 10; + } else if a.is_some() && a != b { + return 1; + } + 0 +} + +#[inline(always)] +fn array_prop_append(val: &Option, arr: &mut Option>) { + if let Some(ap) = val { + if arr.is_none() { + *arr = Some(vec![ap.clone()]) + } else if !arr.as_ref().unwrap().contains(ap) { + arr.as_mut().unwrap().push(ap.clone()); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn distance() { + let a = FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + let b = FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + let c = FlatReqFilter { + id: Some("c".to_owned()), + author: None, + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + let d = FlatReqFilter { + id: Some("a".to_owned()), + author: None, + kind: Some(1), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + let e = FlatReqFilter { + id: Some("e".to_owned()), + author: None, + kind: Some(1), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }; + assert_eq!(super::distance(&a, &b), 0); + assert_eq!(super::distance(&a, &c), 1); + assert_eq!(super::distance(&a, &d), 10); + assert_eq!(super::distance(&a, &e), 11); + } + + #[test] + fn merge_set() { + let a = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("a".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + let b = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("b".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + + let output = ReqFilter { + ids: Some(vec!["0".to_owned()]), + authors: Some(vec!["a".to_owned(), "b".to_owned()]), + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + assert_eq!(super::merge_set(&vec![&a, &b]), output); + } + + #[test] + fn can_merge_filters() { + let a = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("a".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + let b = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("b".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(10), + }; + let c = FlatReqFilter { + id: Some("0".to_owned()), + author: Some("b".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(100), + }; + assert!(super::can_merge_filters(&a, &b)); + assert!(!super::can_merge_filters(&b, &c)); + } + + #[test] + fn flat_merge() { + let input = vec![ + FlatReqFilter { + id: Some("0".to_owned()), + author: Some("a".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: Some("0".to_owned()), + author: Some("b".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: None, + kind: Some(1), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: None, + kind: Some(2), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: None, + kind: Some(2), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: Some("0".to_owned()), + author: Some("c".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: Some("c".to_owned()), + kind: Some(1), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + FlatReqFilter { + id: None, + author: Some("c".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(100), + }, + FlatReqFilter { + id: Some("1".to_owned()), + author: Some("c".to_owned()), + kind: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ]; + let output = vec![ + ReqFilter { + ids: Some(vec!["0".to_owned()]), + authors: Some(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]), + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ReqFilter { + ids: None, + authors: None, + kinds: Some(vec![1, 2]), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ReqFilter { + ids: None, + authors: Some(vec!["c".to_owned()]), + kinds: Some(vec![1]), + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ReqFilter { + ids: None, + authors: Some(vec!["c".to_owned()]), + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: Some(100), + }, + ReqFilter { + ids: Some(vec!["1".to_owned()]), + authors: Some(vec!["c".to_owned()]), + kinds: None, + e_tag: None, + p_tag: None, + t_tag: None, + d_tag: None, + r_tag: None, + search: None, + since: None, + until: None, + limit: None, + }, + ]; + + assert_eq!(super::flat_merge(&input), output) + } +} diff --git a/packages/system-query/system-query.iml b/packages/system-query/system-query.iml new file mode 100644 index 00000000..2fecef3b --- /dev/null +++ b/packages/system-query/system-query.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index b0786757..90f8ecf6 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -241,7 +241,7 @@ export class NostrSystem extends ExternalStore implements System return existing; } const filters = !req.options?.skipDiff - ? req.buildDiff(this.#relayCache, existing.flatFilters) + ? req.buildDiff(this.#relayCache, existing.filters) : req.build(this.#relayCache); if (filters.length === 0 && !!req.options?.skipDiff) { return existing; diff --git a/packages/system/src/query.ts b/packages/system/src/query.ts index 0be31a46..065b0468 100644 --- a/packages/system/src/query.ts +++ b/packages/system/src/query.ts @@ -4,9 +4,7 @@ import { unixNowMs, unwrap } from "@snort/shared"; import { Connection, ReqFilter, Nips, TaggedNostrEvent } from "."; import { NoteStore } from "./note-collection"; -import { flatMerge } from "./request-merger"; import { BuiltRawReqFilter } from "./request-builder"; -import { FlatReqFilter, expandFilter } from "./request-expander"; import { eventMatchesFilter } from "./request-matcher"; /** @@ -19,7 +17,6 @@ class QueryTrace { eose?: number; close?: number; #wasForceClosed = false; - readonly flatFilters: Array; readonly #fnClose: (id: string) => void; readonly #fnProgress: () => void; @@ -34,7 +31,6 @@ class QueryTrace { this.start = unixNowMs(); this.#fnClose = fnClose; this.#fnProgress = fnProgress; - this.flatFilters = filters.flatMap(expandFilter); } sentToRelay() { @@ -166,11 +162,7 @@ export class Query implements QueryBase { * Recompute the complete set of compressed filters from all query traces */ get filters() { - return flatMerge(this.flatFilters); - } - - get flatFilters() { - return this.#tracing.flatMap(a => a.flatFilters); + return this.#tracing.flatMap(a => a.filters); } get feed() { diff --git a/packages/system/src/request-builder.ts b/packages/system/src/request-builder.ts index 747a97a4..311d41e5 100644 --- a/packages/system/src/request-builder.ts +++ b/packages/system/src/request-builder.ts @@ -1,12 +1,12 @@ import debug from "debug"; import { v4 as uuid } from "uuid"; import { appendDedupe, sanitizeRelayUrl, unixNowMs } from "@snort/shared"; +import { get_diff }from "@snort/system-query"; import { ReqFilter, u256, HexKey, EventKind } from "."; -import { diffFilters } from "./request-splitter"; import { RelayCache, splitByWriteRelays, splitFlatByWriteRelays } from "./gossip-model"; import { flatMerge, mergeSimilar } from "./request-merger"; -import { FlatReqFilter, expandFilter } from "./request-expander"; +import { FlatReqFilter } from "./request-expander"; /** * Which strategy is used when building REQ filters @@ -103,15 +103,16 @@ export class RequestBuilder { /** * Detects a change in request from a previous set of filters */ - buildDiff(relays: RelayCache, prev: Array): Array { + buildDiff(relays: RelayCache, prev: Array): Array { const start = unixNowMs(); - const next = this.#builders.flatMap(f => expandFilter(f.filter)); - const diff = diffFilters(prev, next); + //const next = this.#builders.flatMap(f => expandFilter(f.filter)); + //const diff = diffFilters(prev, next); + const diff = get_diff(prev, this.buildRaw()) as Array; const ts = unixNowMs() - start; this.#log("buildDiff %s %d ms", this.id, ts); - if (diff.changed) { - return splitFlatByWriteRelays(relays, diff.added).map(a => { + if (diff.length > 0) { + return splitFlatByWriteRelays(relays, diff).map(a => { return { strategy: RequestStrategy.AuthorsRelays, filters: flatMerge(a.filters),