-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #460 from dlt-hub/rfix/adds-motherduck-destination
adds motherduck destination
- Loading branch information
Showing
29 changed files
with
3,087 additions
and
2,823 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from typing import Type | ||
|
||
from dlt.common.schema.schema import Schema | ||
from dlt.common.configuration import with_config, known_sections | ||
from dlt.common.configuration.accessors import config | ||
from dlt.common.data_writers.escape import escape_postgres_identifier, escape_duckdb_literal | ||
from dlt.common.destination import DestinationCapabilitiesContext | ||
from dlt.common.destination.reference import JobClientBase, DestinationClientConfiguration | ||
|
||
from dlt.destinations.motherduck.configuration import MotherDuckClientConfiguration | ||
|
||
|
||
@with_config(spec=MotherDuckClientConfiguration, sections=(known_sections.DESTINATION, "motherduck",)) | ||
def _configure(config: MotherDuckClientConfiguration = config.value) -> MotherDuckClientConfiguration: | ||
return config | ||
|
||
|
||
def capabilities() -> DestinationCapabilitiesContext: | ||
caps = DestinationCapabilitiesContext() | ||
caps.preferred_loader_file_format = "parquet" | ||
caps.supported_loader_file_formats = ["parquet", "insert_values", "sql"] | ||
caps.escape_identifier = escape_postgres_identifier | ||
caps.escape_literal = escape_duckdb_literal | ||
caps.max_identifier_length = 65536 | ||
caps.max_column_identifier_length = 65536 | ||
caps.naming_convention = "duck_case" | ||
caps.max_query_length = 512 * 1024 | ||
caps.is_max_query_length_in_bytes = True | ||
caps.max_text_data_type_length = 1024 * 1024 * 1024 | ||
caps.is_max_text_data_type_length_in_bytes = True | ||
caps.supports_ddl_transactions = False | ||
caps.alter_add_multi_column = False | ||
|
||
return caps | ||
|
||
|
||
def client(schema: Schema, initial_config: DestinationClientConfiguration = config.value) -> JobClientBase: | ||
# import client when creating instance so capabilities and config specs can be accessed without dependencies installed | ||
from dlt.destinations.motherduck.motherduck import MotherDuckClient | ||
|
||
return MotherDuckClient(schema, _configure(initial_config)) # type: ignore | ||
|
||
|
||
def spec() -> Type[DestinationClientConfiguration]: | ||
return MotherDuckClientConfiguration |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from typing import Any, ClassVar, Final, List | ||
|
||
from dlt.common.configuration import configspec | ||
from dlt.common.destination.reference import DestinationClientDwhConfiguration | ||
from dlt.common.typing import TSecretValue | ||
from dlt.common.configuration.exceptions import ConfigurationValueError | ||
|
||
from dlt.destinations.duckdb.configuration import DuckDbBaseCredentials | ||
|
||
MOTHERDUCK_DRIVERNAME = "md" | ||
|
||
|
||
@configspec | ||
class MotherDuckCredentials(DuckDbBaseCredentials): | ||
drivername: Final[str] = "md" # type: ignore | ||
username: str = "motherduck" | ||
|
||
read_only: bool = False # open database read/write | ||
|
||
__config_gen_annotations__: ClassVar[List[str]] = ["password", "database"] | ||
|
||
def _conn_str(self) -> str: | ||
return f"{MOTHERDUCK_DRIVERNAME}:{self.database}?token={self.password}" | ||
|
||
def _token_to_password(self) -> None: | ||
# could be motherduck connection | ||
if self.query and "token" in self.query: | ||
self.password = TSecretValue(self.query.pop("token")) | ||
|
||
def parse_native_representation(self, native_value: Any) -> None: | ||
super().parse_native_representation(native_value) | ||
self._token_to_password() | ||
|
||
def on_resolved(self) -> None: | ||
self._token_to_password() | ||
if self.drivername == MOTHERDUCK_DRIVERNAME and not self.password: | ||
raise ConfigurationValueError("Motherduck schema 'md' was specified without corresponding token or password. The required format of connection string is: md:///<database_name>?token=<token>") | ||
|
||
|
||
@configspec(init=True) | ||
class MotherDuckClientConfiguration(DestinationClientDwhConfiguration): | ||
destination_name: Final[str] = "motherduck" # type: ignore | ||
credentials: MotherDuckCredentials | ||
|
||
create_indexes: bool = False # should unique indexes be created, this slows loading down massively |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from typing import ClassVar | ||
|
||
from dlt.common.destination import DestinationCapabilitiesContext | ||
from dlt.common.schema import Schema | ||
|
||
|
||
from dlt.destinations.duckdb.duck import DuckDbClient | ||
from dlt.destinations.motherduck import capabilities | ||
from dlt.destinations.motherduck.sql_client import MotherDuckSqlClient | ||
from dlt.destinations.motherduck.configuration import MotherDuckClientConfiguration | ||
|
||
|
||
class MotherDuckClient(DuckDbClient): | ||
|
||
capabilities: ClassVar[DestinationCapabilitiesContext] = capabilities() | ||
|
||
def __init__(self, schema: Schema, config: MotherDuckClientConfiguration) -> None: | ||
super().__init__(schema, config) # type: ignore | ||
sql_client = MotherDuckSqlClient( | ||
self.make_dataset_name(schema, config.dataset_name, config.default_schema_name), | ||
config.credentials | ||
) | ||
self.config: MotherDuckClientConfiguration = config # type: ignore | ||
self.sql_client: MotherDuckSqlClient = sql_client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import duckdb | ||
|
||
from contextlib import contextmanager | ||
from typing import Any, AnyStr, ClassVar, Iterator, Optional, Sequence | ||
from dlt.common.destination import DestinationCapabilitiesContext | ||
|
||
from dlt.destinations.exceptions import DatabaseTerminalException, DatabaseTransientException, DatabaseUndefinedRelation | ||
from dlt.destinations.typing import DBApi, DBApiCursor, DBTransaction, DataFrame | ||
from dlt.destinations.sql_client import SqlClientBase, DBApiCursorImpl, raise_database_error, raise_open_connection_error | ||
|
||
from dlt.destinations.duckdb.sql_client import DuckDbSqlClient, DuckDBDBApiCursorImpl | ||
from dlt.destinations.motherduck import capabilities | ||
from dlt.destinations.motherduck.configuration import MotherDuckCredentials | ||
|
||
|
||
class MotherDuckSqlClient(DuckDbSqlClient): | ||
|
||
capabilities: ClassVar[DestinationCapabilitiesContext] = capabilities() | ||
|
||
def __init__(self, dataset_name: str, credentials: MotherDuckCredentials) -> None: | ||
super().__init__(dataset_name, credentials) | ||
self.database_name = credentials.database | ||
|
||
def fully_qualified_dataset_name(self, escape: bool = True) -> str: | ||
database_name = self.capabilities.escape_identifier(self.database_name) if escape else self.database_name | ||
dataset_name = self.capabilities.escape_identifier(self.dataset_name) if escape else self.dataset_name | ||
return f"{database_name}.{dataset_name}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.