Skip to content

Joins on snowflake cause ValueError: Don't know how to handle type <class 'ibis.expr.types.relations.Table'> #221

@acircleda

Description

@acircleda

When writing BSL for a Snowflake single model (e.g. a single table), I can query it without an issue. However, when adding joins, I consistently get the error ValueError: Don't know how to handle type <class 'ibis.expr.types.relations.Table'>. I am not sure what is causing it, but Claude suggested a working fix. I will provide a simplified version of yaml semantic layer, code, stacktrace and fix below.

yaml Model

users:
  table: users__fact
  description: "users fact table"
  
  dimensions:
    # Surrogate keys
    user_key: _.USER_KEY
    program_key: _.PROGRAM_KEY

  measures:
    user_count: _.USER_KEY.nunique()

  joins:
    programs:
      model: programs
      type: one
      left_on: PROGRAM_KEY
      right_on: PROGRAM_KEY

# Supporting dimension tables
programs:
  table: programs_table
  dimensions:
    program_key: _.PROGRAM_KEY
    program: _.PROGRAM

Code

import ibis
from boring_semantic_layer import from_yaml

ibis_con = ibis.snowflake.connect(
    user="name@place.com",
    account=account,                          
    authenticator="externalbrowser",
    warehouse=warehouse,
    database=db,            
  )

tables = {
    "users":     ibis_con.table("USERS",     database=(db, schema).limit(100),
    "programs": ibis_con.table("PROGRAMS",  database=(db, schema))
}

models = from_yaml("users.yaml", tables=tables)

users_model = models["users"]

activity = (
    users_model
    .group_by("programs.program")
    .aggregate("users.user_count")
    .execute()
)

Error

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[22], [line 5](vscode-notebook-cell:?execution_count=22&line=5)
      1 teacher_activity = (
      2     users_model
      3     .group_by("programs.program")
      4     .aggregate("users.user_count")
----> [5](vscode-notebook-cell:?execution_count=22&line=5)     .execute()
      6 )

File ~/path/to/repo/.venv/lib/python3.12/site-packages/library1/expr.py:260, in SemanticTable.execute(self, **kwargs)
    256 def execute(self, **kwargs):
    257     # Accept kwargs for ibis compatibility (params, limit, etc)
    258     from .ops import _unify_backends
--> 260     return _unify_backends(to_untagged(self)).execute(**kwargs)

File ~/path/to/repo/.venv/lib/python3.12/site-packages/library1/expr.py:110, in to_untagged(expr)
    108 def to_untagged(expr):
    109     if isinstance(expr, SemanticTable):
--> 110         return expr.op().to_untagged()

File ~/path/to/repo/.venv/lib/python3.12/site-packages/library1/ops.py:2173, in SemanticAggregateOp.to_untagged(self)
   2170 # Only use the join optimization if there are no filters after the join
   2171 # Otherwise we'd skip the filter operations
   2172 if join_op is not None and not collected_filters:
-> 2173     tbl = join_op.to_untagged(parent_requirements=self.required_columns)

File ~/path/to/repo/.venv/lib/python3.12/site-packages/library1/ops.py:3663, in SemanticJoinOp.to_untagged(self, parent_requirements)
   3653 right_tbl = (
   3654     _to_untagged(self.right)
   3655     if not isinstance(self.right, SemanticJoinOp)
   3656     else self.right.to_untagged()
   3657 )
   3659 # Rebind right side's DatabaseTable ops to use the same backend as
   3660 # the left side.  from_ibis() creates a separate Backend object per
   3661 # call; xorq >=0.3.11 raises "Multiple backends found" unless all
   3662 # tables in a join share the same backend instance.
-> 3663 left_tbl, right_tbl = self._rebind_join_backends(left_tbl, right_tbl)

File ~/path/to/repo/.venv/lib/python3.12/site-packages/library1/ops.py:3719, in SemanticJoinOp._rebind_join_backends(left_tbl, right_tbl)
   3716 from library2.vendor.ibis.expr.operations import relations as xorq_rel
   3718 # Find a canonical backend from the left tree.
-> 3719 db_tables = list(walk_nodes((xorq_rel.DatabaseTable,), left_tbl))
   3720 canonical = db_tables[0].source if db_tables else None

File ~/path/to/repo/.venv/lib/python3.12/site-packages/library2/common/utils/graph_utils.py:74, in walk_nodes(node_types, expr)
     71 def walk_nodes(node_types, expr):
     72     # TODO should this function use an ordered set
     73     visited = set()
---> 74     to_visit = [to_node(expr)]

File ~/path/to/repo/.venv/lib/python3.12/site-packages/library2/common/utils/graph_utils.py:27, in to_node(maybe_expr)
     25     return maybe_expr.op()
     26 case _:
---> 27     raise ValueError(f"Don't know how to handle type {type(maybe_expr)}")

ValueError: Don't know how to handle type <class 'ibis.expr.types.relations.Table'>

Workaround

Claude came up with the following workaround:

import ibis
from boring_semantic_layer import from_yaml
from boring_semantic_layer.ops import SemanticJoinOp

# Workaround for BSL bug: _rebind_join_backends lacks try/except for non-xorq backends (e.g. Snowflake)
_orig_rebind = SemanticJoinOp._rebind_join_backends

@staticmethod
def _safe_rebind(left_tbl, right_tbl):
    try:
        return _orig_rebind(left_tbl, right_tbl)
    except Exception:
        return left_tbl, right_tbl

SemanticJoinOp._rebind_join_backends = _safe_rebind

pip list

Package                                  Version
---------------------------------------- ------------
appnope                                  0.1.4
asn1crypto                               1.5.1
asttokens                                3.0.1
atpublic                                 7.0.0
attrs                                    25.4.0
beniget                                  0.4.2.post1
boring-semantic-layer                    0.3.10
boto3                                    1.42.78
botocore                                 1.42.78
certifi                                  2026.2.25
cffi                                     2.0.0
charset-normalizer                       3.4.6
cityhash                                 0.4.10
click                                    8.3.1
cloudpickle                              3.1.2
comm                                     0.2.3
cryptography                             46.0.6
dask                                     2025.1.0
debugpy                                  1.8.20
decorator                                5.2.1
duckdb                                   1.3.2
envyaml                                  1.10.211231
executing                                2.2.1
filelock                                 3.25.2
fsspec                                   2026.3.0
gast                                     0.6.0
geoarrow-types                           0.3.0
gitdb                                    4.0.12
gitpython                                3.1.46
googleapis-common-protos                 1.73.1
grpcio                                   1.78.0
ibis-framework                           12.0.0
idna                                     3.11
importlib-metadata                       8.7.1
iniconfig                                2.3.0
ipykernel                                7.2.0
ipython                                  9.12.0
ipython-pygments-lexers                  1.1.1
jaraco-classes                           3.4.0
jaraco-context                           6.1.2
jaraco-functools                         4.4.0
jedi                                     0.19.2
jmespath                                 1.1.0
jupyter-client                           8.8.0
jupyter-core                             5.9.1
keyring                                  25.7.0
linkify-it-py                            2.1.0
locket                                   1.0.0
markdown-it-py                           4.0.0
matplotlib-inline                        0.2.1
mdit-py-plugins                          0.5.0
mdurl                                    0.1.2
more-itertools                           10.8.0
nest-asyncio                             1.6.0
numpy                                    2.4.3
opentelemetry-api                        1.40.0
opentelemetry-exporter-otlp              1.40.0
opentelemetry-exporter-otlp-proto-common 1.40.0
opentelemetry-exporter-otlp-proto-grpc   1.40.0
opentelemetry-exporter-otlp-proto-http   1.40.0
opentelemetry-exporter-prometheus        0.61b0
opentelemetry-proto                      1.40.0
opentelemetry-sdk                        1.40.0
opentelemetry-semantic-conventions       0.61b0
packaging                                26.0
pandas                                   2.3.3
parso                                    0.8.6
parsy                                    2.2
partd                                    1.4.2
pexpect                                  4.9.0
platformdirs                             4.9.4
pluggy                                   1.6.0
ply                                      3.11
prometheus-client                        0.24.1
prompt-toolkit                           3.0.52
protobuf                                 6.33.6
psutil                                   7.2.2
ptyprocess                               0.7.0
pure-eval                                0.2.3
pyarrow                                  21.0.0
pyarrow-hotfix                           0.7
pycparser                                3.0
pygments                                 2.19.2
pyjwt                                    2.12.1
pyopenssl                                26.0.0
pytest                                   9.0.2
pytest-mock                              3.15.1
python-dateutil                          2.9.0.post0
pythran                                  0.18.1
pytz                                     2026.1.post1
pyyaml                                   6.0.3
pyzmq                                    27.1.0
requests                                 2.33.0
returns                                  0.26.0
rich                                     14.3.3
s3transfer                               0.16.0
setuptools                               82.0.1
six                                      1.17.0
smmap                                    5.0.3
snowflake-connector-python               4.4.0
sortedcontainers                         2.4.0
sqlglot                                  28.6.0
stack-data                               0.6.3
structlog                                25.5.0
textual                                  8.2.0
tomlkit                                  0.14.0
toolz                                    1.1.0
tornado                                  6.5.5
traitlets                                5.14.3
typing-extensions                        4.15.0
tzdata                                   2025.3
uc-micro-py                              2.0.0
urllib3                                  2.6.3
uv                                       0.11.2
wcwidth                                  0.6.0
xorq                                     0.3.16
xorq-datafusion                          0.2.5
zipp                                     3.23.0

Python 3.12.11

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions