From 29463c81c2a2299a1cb24f1ef9f3aa0c58c14e32 Mon Sep 17 00:00:00 2001 From: Jeroen van der Heijden Date: Wed, 24 Jan 2024 11:29:13 +0100 Subject: [PATCH] Added subset and superset operations for sets (#359) * Added subset testing * Added set operations for superser and subset checking --- CHANGELOG.md | 1 + inc/ti/opr/ge.h | 3 ++ inc/ti/opr/gt.h | 3 ++ inc/ti/opr/le.h | 3 ++ inc/ti/opr/lt.h | 3 ++ inc/ti/version.h | 2 +- inc/ti/vset.h | 20 ++++++++++++ inc/util/imap.h | 11 +++++++ itest/test_advanced.py | 12 ------- itest/test_operators.py | 29 +++++++++++++++++ src/util/imap.c | 72 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 146 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 335ed6770..789f8c8a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ * libcleri is now integrated into the core ThingsDB codebase, eliminating the need for separate installation. * The `new_backup()` and `new_token()` functions no longer accept `int`, `float` or `str` as time. _(This was marked as deprecated since v0.10.1)_ +* Added set operators `<`, `<=`, `>`, `>=` for subset, proper subset, superset and proper superset checking. # v1.4.16 diff --git a/inc/ti/opr/ge.h b/inc/ti/opr/ge.h index f303ca3aa..95bfc8ccd 100644 --- a/inc/ti/opr/ge.h +++ b/inc/ti/opr/ge.h @@ -64,6 +64,9 @@ static int opr__ge(ti_val_t * a, ti_val_t ** b, ex_t * e) case OPR_BYTES_BYTES: bool_ = ti_raw_cmp((ti_raw_t *) a, (ti_raw_t *) *b) >= 0; break; + case OPR_SET_SET: + bool_ = ti_vset_ge((ti_vset_t*) a, (ti_vset_t *) *b); + break; } ti_val_unsafe_drop(*b); diff --git a/inc/ti/opr/gt.h b/inc/ti/opr/gt.h index d9323dd90..9c26b0f2a 100644 --- a/inc/ti/opr/gt.h +++ b/inc/ti/opr/gt.h @@ -64,6 +64,9 @@ static int opr__gt(ti_val_t * a, ti_val_t ** b, ex_t * e) case OPR_BYTES_BYTES: bool_ = ti_raw_cmp((ti_raw_t *) a, (ti_raw_t *) *b) > 0; break; + case OPR_SET_SET: + bool_ = ti_vset_gt((ti_vset_t*) a, (ti_vset_t *) *b); + break; } ti_val_unsafe_drop(*b); diff --git a/inc/ti/opr/le.h b/inc/ti/opr/le.h index f0c262f7f..5412b88e3 100644 --- a/inc/ti/opr/le.h +++ b/inc/ti/opr/le.h @@ -64,6 +64,9 @@ static int opr__le(ti_val_t * a, ti_val_t ** b, ex_t * e) case OPR_BYTES_BYTES: bool_ = ti_raw_cmp((ti_raw_t *) a, (ti_raw_t *) *b) <= 0; break; + case OPR_SET_SET: + bool_ = ti_vset_le((ti_vset_t*) a, (ti_vset_t *) *b); + break; } ti_val_unsafe_drop(*b); diff --git a/inc/ti/opr/lt.h b/inc/ti/opr/lt.h index 61b5b0139..721ea88a7 100644 --- a/inc/ti/opr/lt.h +++ b/inc/ti/opr/lt.h @@ -64,6 +64,9 @@ static int opr__lt(ti_val_t * a, ti_val_t ** b, ex_t * e) case OPR_BYTES_BYTES: bool_ = ti_raw_cmp((ti_raw_t *) a, (ti_raw_t *) *b) < 0; break; + case OPR_SET_SET: + bool_ = ti_vset_lt((ti_vset_t*) a, (ti_vset_t *) *b); + break; } ti_val_unsafe_drop(*b); diff --git a/inc/ti/version.h b/inc/ti/version.h index b3864cbaf..e8551ac4e 100644 --- a/inc/ti/version.h +++ b/inc/ti/version.h @@ -25,7 +25,7 @@ * "-rc0" * "" */ -#define TI_VERSION_PRE_RELEASE "-alpha8" +#define TI_VERSION_PRE_RELEASE "-alpha9" #define TI_MAINTAINER \ "Jeroen van der Heijden " diff --git a/inc/ti/vset.h b/inc/ti/vset.h index 02646c3a0..77c196c44 100644 --- a/inc/ti/vset.h +++ b/inc/ti/vset.h @@ -72,6 +72,26 @@ static inline _Bool ti_vset_eq(ti_vset_t * va, ti_vset_t * vb) return imap_eq(va->imap, vb->imap); } +static inline _Bool ti_vset_le(ti_vset_t * va, ti_vset_t * vb) +{ + return imap_le(va->imap, vb->imap); +} + +static inline _Bool ti_vset_lt(ti_vset_t * va, ti_vset_t * vb) +{ + return imap_lt(va->imap, vb->imap); +} + +static inline _Bool ti_vset_ge(ti_vset_t * va, ti_vset_t * vb) +{ + return imap_le(vb->imap, va->imap); +} + +static inline _Bool ti_vset_gt(ti_vset_t * va, ti_vset_t * vb) +{ + return imap_lt(vb->imap, va->imap); +} + static inline void * ti_vset_key(ti_vset_t * vset) { return ti_thing_is_object(vset->parent) diff --git a/inc/util/imap.h b/inc/util/imap.h index d6b1f7ed9..3de1548fc 100644 --- a/inc/util/imap.h +++ b/inc/util/imap.h @@ -41,6 +41,7 @@ int imap_walk_cp( imap_destroy_cb destroy_cb); void imap_walkn(imap_t * imap, size_t * n, imap_cb cb, void * arg); _Bool imap__eq_(imap_t * a, imap_t * b); +_Bool imap__le_(imap_t * a, imap_t * b); static inline _Bool imap_eq(imap_t * a, imap_t * b); vec_t * imap_vec(imap_t * imap); vec_t * imap_vec_ref(imap_t * imap); @@ -83,4 +84,14 @@ static inline _Bool imap_eq(imap_t * a, imap_t * b) return a == b || (a->n == b->n && (!a->n || imap__eq_(a, b))); } +static inline _Bool imap_le(imap_t * a, imap_t * b) +{ + return a == b || !a->n || (a->n <= b->n && imap__le_(a, b)); +} + +static inline _Bool imap_lt(imap_t * a, imap_t * b) +{ + return (!a->n && b->n) || (a != b && a->n < b->n && imap__le_(a, b)); +} + #endif /* IMAP_H_ */ diff --git a/itest/test_advanced.py b/itest/test_advanced.py index 4646d003a..78813f624 100755 --- a/itest/test_advanced.py +++ b/itest/test_advanced.py @@ -2025,18 +2025,6 @@ async def test_reserved_enum_union(self, client): 'name `union` is reserved'): await client.query('new_type("union");') - async def test_reserved_enum_union(self, client): - # bug #294 - with self.assertRaisesRegex( - ValueError, - 'name `enum` is reserved'): - await client.query('new_type("enum");') - - with self.assertRaisesRegex( - ValueError, - 'name `union` is reserved'): - await client.query('new_type("union");') - async def test_loop_set_relation_error(self, client): # bug 302 with self.assertRaises(AssertionError): diff --git a/itest/test_operators.py b/itest/test_operators.py index b26d99ac3..de7fe5d84 100755 --- a/itest/test_operators.py +++ b/itest/test_operators.py @@ -237,6 +237,35 @@ async def test_set_operations(self, client): ''') self.assertEqual(set(res), {10, 20, 30, 41, 42, 50}) + res = await client.query(r"""//ti + [ + set() < set(), + set() <= set(), + set() < set(.x10), + set(.x10, .x40, .x41) < set(.x10, .x40, .x41), + set(.x10, .x40, .x41) <= set(.x10, .x40, .x41), + set(.x10, .x40, .x41) > set(.x10, .x40, .x41), + set(.x10, .x40, .x41) >= set(.x10, .x40, .x41), + set(.x10) <= set(), + set(.x10) < set(), + set(.x10, .x40, .x41) < set(.x10, .x41, .x42, .x43), + set(.x10, .x41) <= set(.x10, .x11, .x41, .x42, .x43), + ]; + """) + self.assertEqual(res, [ + False, + True, + True, + False, + True, + False, + True, + False, + False, + False, + True, + ]) + async def test_preopr(self, client): self.assertIs(await client.query(r''' !true; diff --git a/src/util/imap.c b/src/util/imap.c index da88d8628..58750db6d 100644 --- a/src/util/imap.c +++ b/src/util/imap.c @@ -560,6 +560,57 @@ static _Bool imap__eq(imap_node_t * nodea, imap_node_t * nodeb) } } +static _Bool imap__le(imap_node_t * nodea, imap_node_t * nodeb) +{ + if (nodea->key == nodeb->key) + { + imap_node_t + * nda = nodea->nodes, + * ndb = nodeb->nodes, + * end = nda + imap__node_size(nodea); + + for (; nda < end; ++nda, ++ndb) + if (nda->sz > ndb->sz || + (nda->data && !ndb->data) || + (nda->nodes && !ndb->nodes) || + (nda->nodes && !imap__le(nda, ndb))) + return false; + return true; + } + + if (nodea->key != IMAP_NODE_SZ && nodeb->key != IMAP_NODE_SZ) + return false; + + if (nodeb->key == IMAP_NODE_SZ) + { + imap_node_t + * nda = nodea->nodes, + * ndb = nodeb->nodes + nodea->key; + return !(nda->sz > ndb->sz || + (nda->data && !ndb->data) || + (nda->nodes && !ndb->nodes) || + (nda->nodes && !imap__le(nda, ndb))); + } + else + { + uint8_t key = 0; + imap_node_t + * nda = nodea->nodes, + * ndb = nodeb->nodes, + * end = nda + IMAP_NODE_SZ; + + for (; nda < end; ++nda, ++key) + if ((nodeb->key == key && ( + nda->sz > ndb->sz || + (nda->data && !ndb->data) || + (nda->nodes && !ndb->nodes) || + (nda->nodes && !imap__le(nda, ndb)) + )) || (nodeb->key != key && nda->sz)) + return false; + return true; + } +} + /* * Returns `true` if the given imap objects are equal */ @@ -581,6 +632,27 @@ _Bool imap__eq_(imap_t * a, imap_t * b) return true; } +/* + * Returns `true` if the given imap objects are equal + */ +_Bool imap__le_(imap_t * a, imap_t * b) +{ + imap_node_t + * nda = a->nodes, + * ndb = b->nodes, + * end = nda + IMAP_NODE_SZ; + + assert(a != b && a->n <= b->n && a->n); + + for (; nda < end; ++nda, ++ndb) + if ((nda->data && !ndb->data) || + (nda->nodes && !ndb->nodes) || + (nda->nodes && !imap__le(nda, ndb))) + return false; + + return true; +} + static void imap__vec(imap_node_t * node, vec_t * vec) { imap_node_t * nd = node->nodes, * end = nd + imap__node_size(node);