diff --git a/terminusdb_client/scripts/scripts.py b/terminusdb_client/scripts/scripts.py index 8ffb34cd..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]" @@ -501,10 +502,20 @@ def importcsv( def _df_to_schema(class_name, df): class_dict = {"@type": "Class", "@id": class_name} + np_to_buildin = { + v: getattr(builtins, k) + for k, v in np.sctypeDict.items() + 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 else: + converted_type = np_to_buildin[dtype.type] + if converted_type is object: + converted_type = str # pandas treats all string as objects # Map pandas/numpy dtype to Python type # Uses dtype.kind for compatibility with numpy 2.0+ and pandas 3.0+ dtype_kind = getattr(dtype, "kind", "O") diff --git a/terminusdb_client/woqlquery/woql_query.py b/terminusdb_client/woqlquery/woql_query.py index bb5692c5..f795e3bf 100644 --- a/terminusdb_client/woqlquery/woql_query.py +++ b/terminusdb_client/woqlquery/woql_query.py @@ -1009,6 +1009,240 @@ 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_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. + 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. @@ -3462,7 +3696,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(). @@ -3492,7 +3726,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: @@ -3506,17 +3740,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