Skip to content

Commit 044854f

Browse files
Fix(motherduck): Attach multiple catalogs in MotherDuck (#4178)
1 parent cf3b0fa commit 044854f

File tree

3 files changed

+72
-39
lines changed

3 files changed

+72
-39
lines changed

docs/integrations/engines/motherduck.md

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
# MotherDuck
22

3-
This page provides information about how to use SQLMesh with MotherDuck.
3+
This page provides information about how to use SQLMesh with MotherDuck.
44

55
It begins with a [Connection Quickstart](#connection-quickstart) that demonstrates how to connect to MotherDuck, or you can skip directly to information about using MotherDuck with the built-in scheduler.
66

77
## Connection quickstart
88

99
Connecting to cloud warehouses involves a few steps, so this connection quickstart provides the info you need to get up and running with MotherDuck.
1010

11-
It demonstrates connecting to MotherDuck with the `duckdb` library bundled with SQLMesh.
11+
It demonstrates connecting to MotherDuck with the `duckdb` library bundled with SQLMesh.
1212

1313
MotherDuck provides a single way to authorize a connection. This quickstart demonstrates authenticating with a token.
1414

15-
!!! tip
15+
!!! tip
1616
This quick start assumes you are familiar with basic SQLMesh commands and functionality.
1717

18-
If you’re not familiar, work through the [SQLMesh Quickstart](../../quick_start.md) before continuing.
18+
If you’re not familiar, work through the [SQLMesh Quickstart](../../quick_start.md) before continuing.
1919

2020
### Prerequisites
2121

2222
Before working through this quickstart guide, ensure that:
2323

24-
1. You have a motherduck account and an access token.
25-
2. Your computer has SQLMesh installed with the DuckDB extra available.
26-
1. Install from command line with the command `pip install “sqlmesh[duckdb]”`
27-
3. You have initialized a SQLMesh example project on your computer
28-
1. Open a command line interface and navigate to the directory where the project files should go.
29-
2. Initialize the project with the command `sqlmesh init motherduck`
24+
1. You have a motherduck account and an access token.
25+
2. Your computer has SQLMesh installed with the DuckDB extra available.
26+
1. Install from command line with the command `pip install “sqlmesh[duckdb]”`
27+
3. You have initialized a SQLMesh example project on your computer
28+
1. Open a command line interface and navigate to the directory where the project files should go.
29+
2. Initialize the project with the command `sqlmesh init duckdb`, since `duckdb` is the dialect.
3030

3131
#### Access control permissions
3232

@@ -38,30 +38,32 @@ We now have what is required to configure SQLMesh’s connection to MotherDuck.
3838

3939
We start the configuration by adding a gateway named `motherduck` to our example project’s config.yaml file and making it our `default gateway`, as well as adding our token, persistent, and ephemeral catalogs.
4040

41-
```yaml
42-
gateways:
43-
motherduck:
44-
connection:
45-
type: motherduck
46-
catalogs:
47-
persistent: md:
48-
ephemeral: :memory:
49-
token: \<your\_token\>
50-
51-
default\_gateway: motherduck
41+
```yaml
42+
gateways:
43+
motherduck:
44+
connection:
45+
type: motherduck
46+
catalogs:
47+
persistent: "md:"
48+
ephemeral: ":memory:"
49+
token: <your_token>
50+
51+
default_gateway: motherduck
5252
```
5353
54+
Catalogs can be defined to connect to anything that [DuckDB can be attached to](./duckdb.md#other-connection-catalogs-example).
55+
5456
!!! warning
5557
Best practice for storing secrets like tokens is placing them in [environment variables that the configuration file loads dynamically](../../guides/configuration.md#environment-variables). For simplicity, this guide instead places the value directly in the configuration file.
5658
5759
This code demonstrates how to use the environment variable `MOTHERDUCK_TOKEN` for the configuration's `token` parameter:
5860

5961
```yaml linenums="1" hl_lines="5"
60-
gateways:
61-
motherduck:
62-
connection:
63-
type: motherduck
64-
token: {{ env_var('MOTHERDUCK_TOKEN') }}
62+
gateways:
63+
motherduck:
64+
connection:
65+
type: motherduck
66+
token: {{ env_var('MOTHERDUCK_TOKEN') }}
6567
```
6668

6769
### Check connection

sqlmesh/core/config/connection.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,12 @@ def to_sql(self, alias: str) -> str:
150150
options_sql = f" ({', '.join(options)})" if options else ""
151151
alias_sql = ""
152152
# TODO: Add support for Postgres schema. Currently adding it blocks access to the information_schema
153-
if self.type == "motherduck":
154-
# MotherDuck does not support aliasing
155-
md_db = self.path.replace("md:", "")
156-
if md_db != alias.replace('"', ""):
157-
raise ConfigError(
158-
f"MotherDuck does not support assigning an alias different from the database name {md_db}."
159-
)
160-
else:
161-
alias_sql += f" AS {alias}"
162-
return f"ATTACH '{self.path}'{alias_sql}{options_sql}"
153+
154+
# MotherDuck does not support aliasing
155+
alias_sql = (
156+
f" AS {alias}" if not (self.type == "motherduck" or self.path.startswith("md:")) else ""
157+
)
158+
return f"ATTACH IF NOT EXISTS '{self.path}'{alias_sql}{options_sql}"
163159

164160

165161
class BaseDuckDBConnectionConfig(ConnectionConfig):
@@ -264,7 +260,7 @@ def init(cursor: duckdb.DuckDBPyConnection) -> None:
264260
if isinstance(path_options, DuckDBAttachOptions):
265261
query = path_options.to_sql(alias)
266262
else:
267-
query = f"ATTACH '{path_options}'"
263+
query = f"ATTACH IF NOT EXISTS '{path_options}'"
268264
if not path_options.startswith("md:"):
269265
query += f" AS {alias}"
270266
cursor.execute(query)

tests/core/test_connection_config.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -597,12 +597,47 @@ def test_duckdb_attach_options():
597597

598598
assert (
599599
options.to_sql(alias="db")
600-
== "ATTACH 'dbname=postgres user=postgres host=127.0.0.1' AS db (TYPE POSTGRES, READ_ONLY)"
600+
== "ATTACH IF NOT EXISTS 'dbname=postgres user=postgres host=127.0.0.1' AS db (TYPE POSTGRES, READ_ONLY)"
601601
)
602602

603603
options = DuckDBAttachOptions(type="duckdb", path="test.db", read_only=False)
604604

605-
assert options.to_sql(alias="db") == "ATTACH 'test.db' AS db"
605+
assert options.to_sql(alias="db") == "ATTACH IF NOT EXISTS 'test.db' AS db"
606+
607+
608+
def test_motherduck_attach_catalog(make_config):
609+
config = make_config(
610+
type="motherduck",
611+
catalogs={
612+
"test1": "md:test1",
613+
"test2": DuckDBAttachOptions(
614+
type="motherduck",
615+
path="md:test2",
616+
),
617+
},
618+
)
619+
assert isinstance(config, MotherDuckConnectionConfig)
620+
assert config.get_catalog() == "test1"
621+
622+
assert config.catalogs.get("test2").read_only is False
623+
assert config.catalogs.get("test2").path == "md:test2"
624+
assert not config.is_recommended_for_state_sync
625+
626+
627+
def test_motherduck_attach_options():
628+
options = DuckDBAttachOptions(
629+
type="postgres", path="dbname=postgres user=postgres host=127.0.0.1", read_only=True
630+
)
631+
632+
assert (
633+
options.to_sql(alias="db")
634+
== "ATTACH IF NOT EXISTS 'dbname=postgres user=postgres host=127.0.0.1' AS db (TYPE POSTGRES, READ_ONLY)"
635+
)
636+
637+
options = DuckDBAttachOptions(type="motherduck", path="md:test.db", read_only=False)
638+
639+
# Here the alias should be ignored compared to duckdb
640+
assert options.to_sql(alias="db") == "ATTACH IF NOT EXISTS 'md:test.db'"
606641

607642

608643
def test_duckdb_multithreaded_connection_factory(make_config):

0 commit comments

Comments
 (0)