Skip to content
Open
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies = [
"pyyaml>=6.0",
"returns>=0.26.0",
"toolz>=1.0.0",
"xorq>=0.3.11",
"xorq>=0.3.14",
]
urls = { Homepage = "https://github.com/boringdata/boring-semantic-layer/tree/main" }
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion src/boring_semantic_layer/serialization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def extract_path_from_view(table_name):
if aggregate_cache_storage is not None and isinstance(op, SemanticAggregateOp):
xorq_table = xorq_table.cache(storage=aggregate_cache_storage)

xorq_table = xorq_table.tag(tag="bsl", **tag_data)
xorq_table = xorq_table.hashing_tag(tag="bsl", **tag_data)

return xorq_table

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ def test_xorq_caching_with_malloy_model(self):
tagged_expr = to_tagged(data_st)

# Apply xorq-specific tags (caching hints)
cached_expr = tagged_expr.tag(tag="cache", strategy="memory", ttl="3600")
cached_expr = tagged_expr.hashing_tag(tag="cache", strategy="memory", ttl="3600")
assert cached_expr is not None

# Should still be convertible back
Expand Down
2 changes: 1 addition & 1 deletion src/boring_semantic_layer/tests/test_xorq_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def test_backend_caching_with_semantic_model(self):
tagged_expr = to_tagged(model)

# Tag for caching (noop for execution but useful for optimization layers)
cached_expr = tagged_expr.tag(tag="cache", cache_ttl="3600")
cached_expr = tagged_expr.hashing_tag(tag="cache", cache_ttl="3600")

df = xo.execute(cached_expr)

Expand Down
66 changes: 65 additions & 1 deletion src/boring_semantic_layer/tests/test_xorq_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def test_from_xorq_with_tagged_table():
from xorq.api import memtable

# Use nested tuples format (following xorq sklearn pipeline pattern)
xorq_table = memtable({"a": [1, 2, 3]}).tag(
xorq_table = memtable({"a": [1, 2, 3]}).hashing_tag(
tag="bsl_test",
bsl_op_type="SemanticTableOp",
bsl_version="1.0",
Expand All @@ -195,6 +195,70 @@ def test_from_xorq_with_tagged_table():
assert hasattr(bsl_expr, "dimensions")


@pytest.mark.skipif(not xorq, reason="xorq not available")
def test_different_measures_produce_different_hashes():
"""Two SemanticModels on the same table with different measures should hash differently."""
import ibis

from boring_semantic_layer import SemanticModel
from xorq.caching.strategy import SnapshotStrategy
from xorq.common.utils.node_utils import compute_expr_hash

table = ibis.table({"a": "int64", "b": "float64"}, name="test_table")

model_sum = SemanticModel(
table=table,
dimensions={"a": lambda t: t.a},
measures={"agg_b": lambda t: t.b.sum()},
)
model_mean = SemanticModel(
table=table,
dimensions={"a": lambda t: t.a},
measures={"agg_b": lambda t: t.b.mean()},
)

tagged_sum = to_tagged(model_sum)
tagged_mean = to_tagged(model_mean)

strategy = SnapshotStrategy()
hash_sum = compute_expr_hash(tagged_sum, strategy=strategy)
hash_mean = compute_expr_hash(tagged_mean, strategy=strategy)

assert hash_sum != hash_mean, "Same table with different measures should produce different hashes"


@pytest.mark.skipif(not xorq, reason="xorq not available")
def test_same_model_produces_same_hash():
"""Two identical SemanticModels should produce the same hash."""
import ibis

from boring_semantic_layer import SemanticModel
from xorq.caching.strategy import SnapshotStrategy
from xorq.common.utils.node_utils import compute_expr_hash

table = ibis.table({"a": "int64", "b": "float64"}, name="test_table")

model1 = SemanticModel(
table=table,
dimensions={"a": lambda t: t.a},
measures={"sum_b": lambda t: t.b.sum()},
)
model2 = SemanticModel(
table=table,
dimensions={"a": lambda t: t.a},
measures={"sum_b": lambda t: t.b.sum()},
)

tagged1 = to_tagged(model1)
tagged2 = to_tagged(model2)

strategy = SnapshotStrategy()
hash1 = compute_expr_hash(tagged1, strategy=strategy)
hash2 = compute_expr_hash(tagged2, strategy=strategy)

assert hash1 == hash2, "Identical models should produce the same hash"


@pytest.mark.skipif(not xorq, reason="xorq not available")
def test_from_xorq_without_tags():
from xorq.api import memtable
Expand Down
10 changes: 5 additions & 5 deletions src/boring_semantic_layer/tests/test_xorq_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,10 @@ def test_xorq_caching_feature(self):

# Verify xorq-specific methods are available
# (These are xorq features not available in regular ibis)
assert hasattr(tagged_expr, "tag"), "Xorq tables should have tag method"
assert hasattr(tagged_expr, "hashing_tag"), "Xorq tables should have hashing_tag method"

# We can add more xorq tags (e.g., for caching hints)
cached_expr = tagged_expr.tag(tag="cache", cache_strategy="aggressive")
cached_expr = tagged_expr.hashing_tag(tag="cache", cache_strategy="aggressive")
assert cached_expr is not None

def test_filtered_expression_to_xorq(self):
Expand Down Expand Up @@ -223,8 +223,8 @@ def test_multi_tag_support(self):
tagged_expr = to_tagged(model)

# Add multiple tags
tagged = tagged_expr.tag(tag="cache", cache_ttl="3600")
tagged = tagged.tag(tag="monitoring", track_queries="true")
tagged = tagged_expr.hashing_tag(tag="cache", cache_ttl="3600")
tagged = tagged.hashing_tag(tag="monitoring", track_queries="true")

# Both tags should be preserved
# (This tests xorq's ability to nest tags)
Expand All @@ -238,7 +238,7 @@ def test_xorq_noop_tag_preservation(self):
xorq_table = memtable({"a": [1, 2, 3]})

# Tag is a noop - shouldn't affect query results
tagged_table = xorq_table.tag(tag="test", metadata="example")
tagged_table = xorq_table.hashing_tag(tag="test", metadata="example")

df_untagged = execute(xorq_table)
df_tagged = execute(tagged_table)
Expand Down
67 changes: 62 additions & 5 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.