From f62d28a631b4e40696623cb60ab4186f7328d44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20H=C3=B6ij?= Date: Sun, 22 Feb 2026 00:14:34 +0100 Subject: [PATCH 1/5] Enable range queries --- terminusdb_client/woqlquery/woql_query.py | 181 +++++++++++++++++++++- 1 file changed, 176 insertions(+), 5 deletions(-) diff --git a/terminusdb_client/woqlquery/woql_query.py b/terminusdb_client/woqlquery/woql_query.py index 79463df4..74dea147 100644 --- a/terminusdb_client/woqlquery/woql_query.py +++ b/terminusdb_client/woqlquery/woql_query.py @@ -1005,6 +1005,177 @@ def triple(self, sub, pred, obj, opt=False): self._cursor["object"] = self._clean_object(obj) return self + def triple_slice(self, sub, pred, obj, low, high): + """Creates a triple pattern matching rule for [S, P, O] with a half-open + value range [low, high) on the object. Returns triples whose typed object + value falls within the specified range. + + Parameters + ---------- + sub : str + Subject, has to be a node (URI) or variable + pred : str + Predicate, can be variable (prefix with "v:") or node + obj : str + Object, can be variable or node or value + low : object + The inclusive lower bound as a typed value + high : object + The exclusive upper bound as a typed value + + Returns + ------- + WOQLQuery object + query object that can be chained and/or execute + """ + if self._cursor.get("@type"): + self._wrap_cursor_with_and() + self._cursor["@type"] = "TripleSlice" + self._cursor["subject"] = self._clean_subject(sub) + self._cursor["predicate"] = self._clean_predicate(pred) + self._cursor["object"] = self._clean_object(obj) + self._cursor["low"] = self._clean_object(low) + self._cursor["high"] = self._clean_object(high) + return self + + def quad_slice(self, sub, pred, obj, low, high, graph): + """Creates a triple pattern matching rule for [S, P, O, G] with a half-open + value range [low, high) on the object and an explicit graph selector. + + Parameters + ---------- + sub : str + Subject, has to be a node (URI) or variable + pred : str + Predicate, can be variable (prefix with "v:") or node + obj : str + Object, can be variable or node or value + low : object + The inclusive lower bound as a typed value + high : object + The exclusive upper bound as a typed value + graph : str + Graph resource identifier (e.g. 'instance' or 'schema') + + Returns + ------- + WOQLQuery object + query object that can be chained and/or execute + """ + self.triple_slice(sub, pred, obj, low, high) + self._cursor["graph"] = self._clean_graph(graph) + return self + + def triple_next(self, sub, pred, obj, next_val): + """Finds the next object value after a reference for a given subject-predicate pair. + When object is bound and next is free, finds the smallest next > object. + When next is bound and object is free, finds the largest object < next. + + Parameters + ---------- + sub : str + Subject, has to be a node (URI) or variable + pred : str + Predicate, can be variable (prefix with "v:") or node + obj : str + Object value or variable + next_val : object + Next object value or variable + + Returns + ------- + WOQLQuery object + query object that can be chained and/or execute + """ + if self._cursor.get("@type"): + self._wrap_cursor_with_and() + self._cursor["@type"] = "TripleNext" + self._cursor["subject"] = self._clean_subject(sub) + self._cursor["predicate"] = self._clean_predicate(pred) + self._cursor["object"] = self._clean_object(obj) + self._cursor["next"] = self._clean_object(next_val) + return self + + def quad_next(self, sub, pred, obj, next_val, graph): + """Finds the next object value with an explicit graph selector. + + Parameters + ---------- + sub : str + Subject, has to be a node (URI) or variable + pred : str + Predicate, can be variable (prefix with "v:") or node + obj : str + Object value or variable + next_val : object + Next object value or variable + graph : str + Graph resource identifier (e.g. 'instance' or 'schema') + + Returns + ------- + WOQLQuery object + query object that can be chained and/or execute + """ + self.triple_next(sub, pred, obj, next_val) + self._cursor["graph"] = self._clean_graph(graph) + return self + + def triple_previous(self, sub, pred, obj, prev_val): + """Finds the previous object value before a reference for a given subject-predicate pair. + When object is bound and previous is free, finds the largest previous < object. + When previous is bound and object is free, finds the smallest object > previous. + + Parameters + ---------- + sub : str + Subject, has to be a node (URI) or variable + pred : str + Predicate, can be variable (prefix with "v:") or node + obj : str + Object value or variable + prev_val : object + Previous object value or variable + + Returns + ------- + WOQLQuery object + query object that can be chained and/or execute + """ + if self._cursor.get("@type"): + self._wrap_cursor_with_and() + self._cursor["@type"] = "TriplePrevious" + self._cursor["subject"] = self._clean_subject(sub) + self._cursor["predicate"] = self._clean_predicate(pred) + self._cursor["object"] = self._clean_object(obj) + self._cursor["previous"] = self._clean_object(prev_val) + return self + + def quad_previous(self, sub, pred, obj, prev_val, graph): + """Finds the previous object value with an explicit graph selector. + + Parameters + ---------- + sub : str + Subject, has to be a node (URI) or variable + pred : str + Predicate, can be variable (prefix with "v:") or node + obj : str + Object value or variable + prev_val : object + Previous object value or variable + graph : str + Graph resource identifier (e.g. 'instance' or 'schema') + + Returns + ------- + WOQLQuery object + query object that can be chained and/or execute + """ + self.triple_previous(sub, pred, obj, prev_val) + self._cursor["graph"] = self._clean_graph(graph) + return self + def added_triple(self, sub, pred, obj, opt=False): """Creates a triple pattern matching rule for the triple [S, P, O] (Subject, Predicate, Object) added to the current commit. @@ -3430,7 +3601,7 @@ def localize(self, param_spec): """Build a localized scope for variables to prevent leaking local variables to outer scope. Returns a tuple (localized_fn, v) where: - - localized_fn: function that wraps queries with select("") and eq() bindings + - localized_fn: function that wraps queries with select() (empty variable list) and eq() bindings - v: VarsUnique object with unique variable names for use in the inner query Parameters with non-None values are bound from outer scope via eq(). @@ -3460,7 +3631,7 @@ def localize(self, param_spec): v = VarsUnique(*param_names) def localized_fn(query=None): - # Create eq bindings for outer parameters OUTSIDE select("") + # Create eq bindings for outer parameters OUTSIDE select() # This ensures outer parameters are visible in query results outer_eq_bindings = [] for param_name in param_names: @@ -3474,17 +3645,17 @@ def localized_fn(query=None): outer_eq_bindings.append( WOQLQuery().eq(outer_value, outer_value) ) - # Bind the unique variable to the outer parameter OUTSIDE the select("") + # Bind the unique variable to the outer parameter OUTSIDE the select() outer_eq_bindings.append( WOQLQuery().eq(getattr(v, param_name), outer_value) ) if query is not None: - # Functional mode: wrap query in select(""), then add outer eq bindings + # Functional mode: wrap query in select() with empty variable list localized_query = WOQLQuery().select(query) if outer_eq_bindings: - # Wrap: eq(outer) AND select("") { query } + # Wrap: eq(outer) AND select() { query } return WOQLQuery().woql_and(*outer_eq_bindings, localized_query) return localized_query From 9303d4bd72c9becc59330dd5ab6cf61177ca93d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20H=C3=B6ij?= Date: Sun, 22 Feb 2026 00:20:13 +0100 Subject: [PATCH 2/5] Add small fix to support newer pandas --- terminusdb_client/scripts/scripts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminusdb_client/scripts/scripts.py b/terminusdb_client/scripts/scripts.py index fe23955c..deb2968b 100644 --- a/terminusdb_client/scripts/scripts.py +++ b/terminusdb_client/scripts/scripts.py @@ -461,6 +461,7 @@ def _df_to_schema(class_name, df): if k in vars(builtins) } np_to_buildin[np.datetime64] = dt.datetime + np_to_buildin[str] = str for col, dtype in dict(df.dtypes).items(): if embedded and col in embedded: converted_type = class_name From dd2cb69ca68fda510fbc8acf186df718034489e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20H=C3=B6ij?= Date: Mon, 23 Feb 2026 17:04:20 +0100 Subject: [PATCH 3/5] Support reverse triple slice WOQL --- terminusdb_client/woqlquery/woql_query.py | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/terminusdb_client/woqlquery/woql_query.py b/terminusdb_client/woqlquery/woql_query.py index 74dea147..01e06481 100644 --- a/terminusdb_client/woqlquery/woql_query.py +++ b/terminusdb_client/woqlquery/woql_query.py @@ -1066,6 +1066,69 @@ def quad_slice(self, sub, pred, obj, low, high, graph): self._cursor["graph"] = self._clean_graph(graph) return self + def triple_slice_rev(self, sub, pred, obj, low, high): + """Creates a triple pattern matching rule for [S, P, O] with a half-open + value range [low, high) on the object, returning results in reverse + (descending) object order. Same semantics as triple_slice but iterates + from highest to lowest value. + + Parameters + ---------- + sub : str + Subject, has to be a node (URI) or variable + pred : str + Predicate, can be variable (prefix with "v:") or node + obj : str + Object, can be variable or node or value + low : object + The inclusive lower bound as a typed value + high : object + The exclusive upper bound as a typed value + + Returns + ------- + WOQLQuery object + query object that can be chained and/or execute + """ + if self._cursor.get("@type"): + self._wrap_cursor_with_and() + self._cursor["@type"] = "TripleSliceRev" + self._cursor["subject"] = self._clean_subject(sub) + self._cursor["predicate"] = self._clean_predicate(pred) + self._cursor["object"] = self._clean_object(obj) + self._cursor["low"] = self._clean_object(low) + self._cursor["high"] = self._clean_object(high) + return self + + def quad_slice_rev(self, sub, pred, obj, low, high, graph): + """Creates a triple pattern matching rule for [S, P, O, G] with a half-open + value range [low, high) on the object in reverse order, with an explicit + graph selector. + + Parameters + ---------- + sub : str + Subject, has to be a node (URI) or variable + pred : str + Predicate, can be variable (prefix with "v:") or node + obj : str + Object, can be variable or node or value + low : object + The inclusive lower bound as a typed value + high : object + The exclusive upper bound as a typed value + graph : str + Graph resource identifier (e.g. 'instance' or 'schema') + + Returns + ------- + WOQLQuery object + query object that can be chained and/or execute + """ + self.triple_slice_rev(sub, pred, obj, low, high) + self._cursor["graph"] = self._clean_graph(graph) + return self + def triple_next(self, sub, pred, obj, next_val): """Finds the next object value after a reference for a given subject-predicate pair. When object is bound and next is free, finds the smallest next > object. From d4f0c51d2a84675470ea359ac4ce398bbcce0f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20H=C3=B6ij?= Date: Wed, 25 Feb 2026 12:01:03 +0100 Subject: [PATCH 4/5] fix-port --- .../tests/integration_tests/conftest.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/terminusdb_client/tests/integration_tests/conftest.py b/terminusdb_client/tests/integration_tests/conftest.py index 4a70bfed..05031c57 100644 --- a/terminusdb_client/tests/integration_tests/conftest.py +++ b/terminusdb_client/tests/integration_tests/conftest.py @@ -12,10 +12,11 @@ def is_local_server_running(): """Check if local TerminusDB server is running at http://127.0.0.1:6363""" try: - response = requests.get("http://127.0.0.1:6363", timeout=2) - # Server responds with 200 (success) or 404 (not found but server is up) - # 401 (unauthorized) also indicates server is running but needs auth - return response.status_code in [200, 404] + response = requests.get( + "http://127.0.0.1:6363", timeout=2, allow_redirects=False + ) + # Any HTTP response means the server is running + return response.status_code in [200, 302, 401, 404] except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): return False @@ -23,9 +24,11 @@ def is_local_server_running(): def is_docker_server_running(): """Check if Docker TerminusDB server is already running at http://127.0.0.1:6366""" try: - response = requests.get("http://127.0.0.1:6366", timeout=2) - # Server responds with 404 for root path, which means it's running - return response.status_code in [200, 404] + response = requests.get( + "http://127.0.0.1:6366", timeout=2, allow_redirects=False + ) + # Any HTTP response means the server is running + return response.status_code in [200, 302, 401, 404] except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): return False @@ -33,9 +36,11 @@ def is_docker_server_running(): def is_jwt_server_running(): """Check if JWT Docker TerminusDB server is already running at http://127.0.0.1:6367""" try: - response = requests.get("http://127.0.0.1:6367", timeout=2) - # Server responds with 404 for root path, which means it's running - return response.status_code in [200, 404] + response = requests.get( + "http://127.0.0.1:6367", timeout=2, allow_redirects=False + ) + # Any HTTP response means the server is running + return response.status_code in [200, 302, 401, 404] except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): return False From bf82c43a658a00fcb91c0c43deea45b55727e3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20H=C3=B6ij?= Date: Wed, 25 Feb 2026 12:51:49 +0100 Subject: [PATCH 5/5] Re-add numpy --- terminusdb_client/scripts/scripts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terminusdb_client/scripts/scripts.py b/terminusdb_client/scripts/scripts.py index 2506d11b..ffb4b365 100644 --- a/terminusdb_client/scripts/scripts.py +++ b/terminusdb_client/scripts/scripts.py @@ -486,6 +486,7 @@ def importcsv( embedded = [x.lower().replace(" ", "_") for x in embedded] try: pd = import_module("pandas") + np = import_module("numpy") except ImportError: raise ImportError( "Library 'pandas' is required to import csv, either install 'pandas' or install woqlDataframe requirements as follows: python -m pip install -U terminus-client-python[dataframe]"