Skip to content

Commit c3ff6ae

Browse files
authored
feat(sql): enable cross-database joins (#9849)
1 parent 60103cb commit c3ff6ae

File tree

3 files changed

+49
-5
lines changed

3 files changed

+49
-5
lines changed

docker/mysql/startup.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
CREATE USER 'ibis'@'localhost' IDENTIFIED BY 'ibis';
22
CREATE SCHEMA IF NOT EXISTS test_schema;
3-
GRANT CREATE,SELECT,DROP ON *.* TO 'ibis'@'%';
3+
GRANT CREATE,SELECT,DROP,INSERT ON *.* TO 'ibis'@'%';
44
FLUSH PRIVILEGES;

ibis/backends/tests/test_client.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,3 +1726,39 @@ def test_no_accidental_cross_database_table_load(con_create_database):
17261726
# Clean up
17271727
con.drop_table(table, database=dbname)
17281728
con.drop_database(dbname)
1729+
1730+
1731+
@pytest.mark.notyet(["druid"], reason="can't create tables")
1732+
@pytest.mark.notyet(
1733+
["flink"], reason="can't create non-temporary tables from in-memory data"
1734+
)
1735+
def test_cross_database_join(con_create_database, monkeypatch):
1736+
con = con_create_database
1737+
1738+
monkeypatch.setattr(ibis.options, "default_backend", con)
1739+
1740+
left = ibis.memtable({"a": [1], "b": [2]})
1741+
right = ibis.memtable({"a": [1], "c": [3]})
1742+
1743+
# Create an extra database
1744+
con.create_database(dbname := gen_name("dummy_db"))
1745+
1746+
# Insert left into current_database
1747+
left = con.create_table(left_table := gen_name("left"), obj=left)
1748+
1749+
# Insert right into new database
1750+
right = con.create_table(
1751+
right_table := gen_name("right"), obj=right, database=dbname
1752+
)
1753+
1754+
expr = left.join(right, "a")
1755+
assert expr.columns == ["a", "b", "c"]
1756+
1757+
result = expr.to_pyarrow()
1758+
expected = pa.Table.from_pydict({"a": [1], "b": [2], "c": [3]})
1759+
1760+
assert result.equals(expected)
1761+
1762+
con.drop_table(left_table)
1763+
con.drop_table(right_table, database=dbname)
1764+
con.drop_database(dbname)

ibis/backends/trino/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,12 @@ def create_table(
415415
The schema of the table to create; optional, but one of `obj` or
416416
`schema` must be specified
417417
database
418-
Not yet implemented.
418+
The database to insert the table into.
419+
If not provided, the current database is used.
420+
You can provide a single database name, like `"mydb"`. For
421+
multi-level hierarchies, you can pass in a dotted string path like
422+
`"catalog.database"` or a tuple of strings like `("catalog",
423+
"database")`.
419424
temp
420425
This parameter is not yet supported in the Trino backend, because
421426
Trino doesn't implement temporary tables
@@ -436,13 +441,16 @@ def create_table(
436441
"Temporary tables are not supported in the Trino backend"
437442
)
438443

444+
table_loc = self._to_sqlglot_table(database)
445+
catalog, db = self._to_catalog_db_tuple(table_loc)
446+
439447
quoted = self.compiler.quoted
440-
orig_table_ref = sg.to_identifier(name, quoted=quoted)
448+
orig_table_ref = sg.table(name, catalog=catalog, db=db, quoted=quoted)
441449

442450
if overwrite:
443451
name = util.gen_name(f"{self.name}_overwrite")
444452

445-
table_ref = sg.table(name, catalog=database, quoted=quoted)
453+
table_ref = sg.table(name, catalog=catalog, db=db, quoted=quoted)
446454

447455
if schema is not None and obj is None:
448456
column_defs = [
@@ -524,7 +532,7 @@ def create_table(
524532
if temp_memtable_view is not None:
525533
self.drop_table(temp_memtable_view)
526534

527-
return self.table(orig_table_ref.name)
535+
return self.table(orig_table_ref.name, database=(catalog, db))
528536

529537
def _fetch_from_cursor(self, cursor, schema: sch.Schema) -> pd.DataFrame:
530538
import pandas as pd

0 commit comments

Comments
 (0)