Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions terminusdb_client/tests/test_woqlQuery.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from .woqljson.woqlConcatJson import WOQL_CONCAT_JSON
from .woqljson.woqlIdgenJson import (
WOQL_IDGEN_JSON,
WOQL_RANDOM_IDGEN_JSON,
WOQL_RANDOM_KEY_JSON,
WOQL_UNIQUE_JSON,
)
from .woqljson.woqlJoinSplitJson import WOQL_JOIN_SPLIT_JSON
Expand Down Expand Up @@ -214,11 +214,9 @@ def test_idgen_method(self):
woql_object = WOQLQuery().idgen("Station", "v:Start_ID", "v:Start_Station_URL")
assert woql_object.to_dict() == WOQL_IDGEN_JSON

def test_random_idgen_method(self):
woql_object = WOQLQuery().random_idgen(
"Station", "v:Start_ID", "v:Start_Station_URL"
)
assert woql_object.to_dict() == WOQL_RANDOM_IDGEN_JSON
def test_idgen_random_method(self):
woql_object = WOQLQuery().idgen_random("Person/", "v:Person_ID")
assert woql_object.to_dict() == WOQL_RANDOM_KEY_JSON

def test_typecast_method(self):
woql_object = WOQLQuery().typecast(
Expand Down
122 changes: 122 additions & 0 deletions terminusdb_client/tests/test_woql_idgen_random.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""Unit tests for idgen_random WOQL method"""
from terminusdb_client.woqlquery.woql_query import WOQLQuery


class TestIdgenRandom:
"""Test idgen_random WOQL method"""

def test_idgen_random_basic(self):
"""Test basic idgen_random functionality"""
woql = WOQLQuery().idgen_random("Person/", "v:PersonID")
result = woql.to_dict()

assert result["@type"] == "RandomKey"
assert result["base"]["@type"] == "DataValue"
assert result["base"]["data"]["@type"] == "xsd:string"
assert result["base"]["data"]["@value"] == "Person/"
assert result["uri"]["@type"] == "NodeValue"
assert result["uri"]["variable"] == "PersonID"

def test_idgen_random_with_prefix(self):
"""Test idgen_random with different prefix formats"""
woql = WOQLQuery().idgen_random("http://example.org/Person/", "v:ID")
result = woql.to_dict()

assert result["base"]["data"]["@value"] == "http://example.org/Person/"
assert result["uri"]["variable"] == "ID"

def test_idgen_random_chaining(self):
"""Test idgen_random can be chained with other operations"""
woql = (WOQLQuery()
.triple("v:Person", "rdf:type", "@schema:Person")
.idgen_random("Person/", "v:PersonID"))

result = woql.to_dict()
assert result["@type"] == "And"
assert len(result["and"]) == 2
assert result["and"][0]["@type"] == "Triple"
assert result["and"][1]["@type"] == "RandomKey"

def test_idgen_random_multiple_calls(self):
"""Test multiple idgen_random calls in same query"""
woql = (WOQLQuery()
.idgen_random("Person/", "v:PersonID")
.idgen_random("Order/", "v:OrderID"))

result = woql.to_dict()
assert result["@type"] == "And"
assert result["and"][0]["@type"] == "RandomKey"
assert result["and"][0]["base"]["data"]["@value"] == "Person/"
assert result["and"][1]["@type"] == "RandomKey"
assert result["and"][1]["base"]["data"]["@value"] == "Order/"

def test_idgen_random_args_parameter(self):
"""Test idgen_random args parameter returns parameter list"""
result = WOQLQuery().idgen_random("args", "v:ID")

assert result == ["base", "uri"]

def test_idgen_random_empty_prefix(self):
"""Test idgen_random with empty prefix"""
woql = WOQLQuery().idgen_random("", "v:ID")
result = woql.to_dict()

assert result["@type"] == "RandomKey"
assert result["base"]["data"]["@value"] == ""

def test_idgen_random_variable_output(self):
"""Test idgen_random output variable format"""
woql = WOQLQuery().idgen_random("Test/", "v:MyVar")
result = woql.to_dict()

assert result["uri"]["variable"] == "MyVar"

def test_idgen_random_in_query_chain(self):
"""Test idgen_random in complex query chain"""
woql = (WOQLQuery()
.triple("v:Person", "rdf:type", "@schema:Person")
.idgen_random("Person/", "v:PersonID")
.triple("v:PersonID", "@schema:name", "v:Name"))

result = woql.to_dict()
assert result["@type"] == "And"
# WOQLQuery chains create nested And structures
# Verify RandomKey is present in the chain
has_random_key = False

def check_for_random_key(obj):
nonlocal has_random_key
if isinstance(obj, dict):
if obj.get("@type") == "RandomKey":
has_random_key = True
for value in obj.values():
check_for_random_key(value)
elif isinstance(obj, list):
for item in obj:
check_for_random_key(item)

check_for_random_key(result)
assert has_random_key, "RandomKey should be present in query chain"

def test_idgen_random_matches_expected_json(self):
"""Test idgen_random produces expected WOQL JSON structure"""
from terminusdb_client.tests.woqljson.woqlIdgenJson import WOQL_RANDOM_KEY_JSON

woql = WOQLQuery().idgen_random("Person/", "v:Person_ID")
result = woql.to_dict()

assert result == WOQL_RANDOM_KEY_JSON

def test_idgen_random_with_special_characters_in_prefix(self):
"""Test idgen_random handles special characters in prefix"""
woql = WOQLQuery().idgen_random("Test/2024-11/", "v:ID")
result = woql.to_dict()

assert result["base"]["data"]["@value"] == "Test/2024-11/"

def test_idgen_random_preserves_cursor_state(self):
"""Test idgen_random returns self for method chaining"""
woql = WOQLQuery()
result = woql.idgen_random("Test/", "v:ID")

assert result is woql # Should return same object for chaining
10 changes: 3 additions & 7 deletions terminusdb_client/tests/woqljson/woqlIdgenJson.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,14 @@
},
}

WOQL_RANDOM_IDGEN_JSON = {
WOQL_RANDOM_KEY_JSON = {
"@type": "RandomKey",
"base": {
"@type": "DataValue",
"data": {"@type": "xsd:string", "@value": "Station"},
},
"key_list": {
"@type": "DataValue",
"variable": "Start_ID",
"data": {"@type": "xsd:string", "@value": "Person/"},
},
"uri": {
"@type": "NodeValue",
"variable": "Start_Station_URL",
"variable": "Person_ID",
},
}
49 changes: 38 additions & 11 deletions terminusdb_client/woqlquery/woql_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -2207,38 +2207,65 @@ def idgen(self, prefix, input_var_list, output_var):
self._cursor["uri"] = self._clean_node_value(output_var)
return self

def random_idgen(self, prefix, key_list, uri):
"""Randomly generates an ID and appends to the end of the key_list.
def idgen_random(self, prefix, uri):
"""Generates a unique ID with cryptographically secure random suffix.

Uses base64 encoding to generate 16-character random IDs that are
guaranteed to be unique across executions. Matches the server's
idgen_random/2 predicate and maintains consistency with the JavaScript client.

Parameters
----------
prefix : str
prefix for the id
key_list : str
variable to generate id for
A prefix for the IDs to be generated (e.g. "Person/")
uri : str
the variable to hold the id
Variable name or output target for the generated ID

Returns
-------
WOQLQuery object
query object that can be chained and/or execute
query object that can be chained and/or executed

Examples
-------
>>> WOQLQuery().random_idgen("https://base.url",["page","1"],"v:obj_id").execute(client)
{'@type': 'api:WoqlResponse', 'api:status': 'api:success', 'api:variable_names': ['obj_id'], 'bindings': [{'obj_id': 'http://base.url_page_1_rv1mfa59ekisdutnxx6zdt2fkockgah'}], 'deletes': 0, 'inserts': 0, 'transaction_retry_count': 0}
>>> WOQLQuery().idgen_random("Person/", "v:person_id").execute(client)
{'@type': 'api:WoqlResponse', 'api:status': 'api:success', 'api:variable_names': ['person_id'], 'bindings': [{'person_id': 'Person/aB3dEf9GhI2jK4lM'}], 'deletes': 0, 'inserts': 0, 'transaction_retry_count': 0}
"""
if prefix and prefix == "args":
return ["base", "key_list", "uri"]
return ["base", "uri"]
if self._cursor.get("@type"):
self._wrap_cursor_with_and()
self._cursor["@type"] = "RandomKey"
self._cursor["base"] = self._clean_data_value(prefix)
self._cursor["key_list"] = self._data_list(key_list)
self._cursor["uri"] = self._clean_node_value(uri)
return self

def random_idgen(self, prefix, uri):
"""Generates a unique ID with cryptographically secure random suffix (alias for idgen_random).

This is an alias for idgen_random() for compatibility with older code.
Uses base64 encoding to generate 16-character random IDs that are
guaranteed to be unique across executions.

Parameters
----------
prefix : str
A prefix for the IDs to be generated (e.g. "Person/")
uri : str
Variable name or output target for the generated ID

Returns
-------
WOQLQuery object
query object that can be chained and/or executed

Examples
-------
>>> WOQLQuery().random_idgen("Person/", "v:person_id").execute(client)
{'@type': 'api:WoqlResponse', 'api:status': 'api:success', 'api:variable_names': ['person_id'], 'bindings': [{'person_id': 'Person/aB3dEf9GhI2jK4lM'}], 'deletes': 0, 'inserts': 0, 'transaction_retry_count': 0}
"""
return self.idgen_random(prefix, uri)

def upper(self, left, right):
"""Changes a string to upper-case - input is in left, output in right

Expand Down