You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2023/08/09 23:42:22 UTC

[superset] branch master updated: feat: add MotherDuck DB engine spec (#24934)

This is an automated email from the ASF dual-hosted git repository.

beto pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 9c54280d85 feat: add MotherDuck DB engine spec (#24934)
9c54280d85 is described below

commit 9c54280d8520f81a7928150194c4fb6c7b04e324
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Aug 9 16:42:14 2023 -0700

    feat: add MotherDuck DB engine spec (#24934)
---
 setup.py                                           |  1 +
 .../databases/DatabaseModal/SqlAlchemyForm.tsx     | 25 ++++++++++------------
 .../src/features/databases/DatabaseModal/index.tsx | 11 ++++++++--
 superset-frontend/src/features/databases/types.ts  |  1 +
 superset/databases/api.py                          |  4 ++++
 superset/db_engine_specs/base.py                   | 11 +++++-----
 superset/db_engine_specs/duckdb.py                 |  7 ++++++
 tests/integration_tests/databases/api_tests.py     |  3 +++
 8 files changed, 42 insertions(+), 21 deletions(-)

diff --git a/setup.py b/setup.py
index b3f225bce5..24b1b890d5 100644
--- a/setup.py
+++ b/setup.py
@@ -150,6 +150,7 @@ setup(
         "dremio": ["sqlalchemy-dremio>=1.1.5, <1.3"],
         "drill": ["sqlalchemy-drill==0.1.dev"],
         "druid": ["pydruid>=0.6.5,<0.7"],
+        "duckdb": ["duckdb-engine==0.8.1"],
         "dynamodb": ["pydynamodb>=0.4.2"],
         "solr": ["sqlalchemy-solr >= 0.2.0"],
         "elasticsearch": ["elasticsearch-dbapi>=0.2.9, <0.3.0"],
diff --git a/superset-frontend/src/features/databases/DatabaseModal/SqlAlchemyForm.tsx b/superset-frontend/src/features/databases/DatabaseModal/SqlAlchemyForm.tsx
index 5d50625cdd..0dd4cd97d0 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/SqlAlchemyForm.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/SqlAlchemyForm.tsx
@@ -38,17 +38,13 @@ const SqlAlchemyTab = ({
   testInProgress?: boolean;
   children?: ReactNode;
 }) => {
-  let fallbackDocsUrl;
-  let fallbackDisplayText;
-  if (SupersetText) {
-    fallbackDocsUrl =
-      SupersetText.DB_MODAL_SQLALCHEMY_FORM?.SQLALCHEMY_DOCS_URL;
-    fallbackDisplayText =
-      SupersetText.DB_MODAL_SQLALCHEMY_FORM?.SQLALCHEMY_DISPLAY_TEXT;
-  } else {
-    fallbackDocsUrl = 'https://docs.sqlalchemy.org/en/13/core/engines.html';
-    fallbackDisplayText = 'SQLAlchemy docs';
-  }
+  const fallbackDocsUrl =
+    SupersetText?.DB_MODAL_SQLALCHEMY_FORM?.SQLALCHEMY_DOCS_URL ||
+    'https://docs.sqlalchemy.org/en/13/core/engines.html';
+  const fallbackDisplayText =
+    SupersetText?.DB_MODAL_SQLALCHEMY_FORM?.SQLALCHEMY_DISPLAY_TEXT ||
+    'SQLAlchemy docs';
+
   return (
     <>
       <StyledInputContainer>
@@ -82,9 +78,10 @@ const SqlAlchemyTab = ({
             data-test="sqlalchemy-uri-input"
             value={db?.sqlalchemy_uri || ''}
             autoComplete="off"
-            placeholder={t(
-              'dialect+driver://username:password@host:port/database',
-            )}
+            placeholder={
+              db?.sqlalchemy_uri_placeholder ||
+              t('dialect+driver://username:password@host:port/database')
+            }
             onChange={onInputChange}
           />
         </div>
diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
index dd2e405350..68dcfd4fed 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
@@ -202,6 +202,7 @@ export type DBReducerActionType =
         configuration_method: CONFIGURATION_METHOD;
         engine_information?: {};
         driver?: string;
+        sqlalchemy_uri_placeholder?: string;
       };
     }
   | {
@@ -946,8 +947,13 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
       const selectedDbModel = availableDbs?.databases.filter(
         (db: DatabaseObject) => db.name === database_name,
       )[0];
-      const { engine, parameters, engine_information, default_driver } =
-        selectedDbModel;
+      const {
+        engine,
+        parameters,
+        engine_information,
+        default_driver,
+        sqlalchemy_uri_placeholder,
+      } = selectedDbModel;
       const isDynamic = parameters !== undefined;
       setDB({
         type: ActionType.dbSelected,
@@ -959,6 +965,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
             : CONFIGURATION_METHOD.SQLALCHEMY_URI,
           engine_information,
           driver: default_driver,
+          sqlalchemy_uri_placeholder,
         },
       });
 
diff --git a/superset-frontend/src/features/databases/types.ts b/superset-frontend/src/features/databases/types.ts
index a7e4f59b58..e7089425bc 100644
--- a/superset-frontend/src/features/databases/types.ts
+++ b/superset-frontend/src/features/databases/types.ts
@@ -52,6 +52,7 @@ export type DatabaseObject = {
   name: string; // synonym to database_name
   paramProperties?: Record<string, any>;
   sqlalchemy_uri?: string;
+  sqlalchemy_uri_placeholder?: string;
   parameters?: {
     access_token?: string;
     database_name?: string;
diff --git a/superset/databases/api.py b/superset/databases/api.py
index ae9cd60188..116e2ddb1f 100644
--- a/superset/databases/api.py
+++ b/superset/databases/api.py
@@ -1293,6 +1293,9 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
                           type: array
                           items:
                             type: string
+                        sqlalchemy_uri_placeholder:
+                          description: Placeholder for the SQLAlchemy URI
+                          type: string
                         default_driver:
                           description: Default driver for the engine
                           type: string
@@ -1330,6 +1333,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
                 "name": engine_spec.engine_name,
                 "engine": engine_spec.engine,
                 "available_drivers": sorted(drivers),
+                "sqlalchemy_uri_placeholder": engine_spec.sqlalchemy_uri_placeholder,
                 "preferred": engine_spec.engine_name in preferred_databases,
                 "engine_information": engine_spec.get_public_information(),
             }
diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py
index 2e1d5598ff..d3ffce018a 100644
--- a/superset/db_engine_specs/base.py
+++ b/superset/db_engine_specs/base.py
@@ -185,6 +185,12 @@ class BaseEngineSpec:  # pylint: disable=too-many-public-methods
     engine_aliases: set[str] = set()
     drivers: dict[str, str] = {}
     default_driver: str | None = None
+
+    # placeholder with the SQLAlchemy URI template
+    sqlalchemy_uri_placeholder = (
+        "engine+driver://user:password@host:port/dbname[?key=value&key=value...]"
+    )
+
     disable_ssh_tunneling = False
 
     _date_trunc_functions: dict[str, str] = {}
@@ -1958,11 +1964,6 @@ class BasicParametersMixin:
     # recommended driver name for the DB engine spec
     default_driver = ""
 
-    # placeholder with the SQLAlchemy URI template
-    sqlalchemy_uri_placeholder = (
-        "engine+driver://user:password@host:port/dbname[?key=value&key=value...]"
-    )
-
     # query parameter to enable encryption in the database connection
     # for Postgres this would be `{"sslmode": "verify-ca"}`, eg.
     encryption_parameters: dict[str, str] = {}
diff --git a/superset/db_engine_specs/duckdb.py b/superset/db_engine_specs/duckdb.py
index fa2f01f50a..291b5521ef 100644
--- a/superset/db_engine_specs/duckdb.py
+++ b/superset/db_engine_specs/duckdb.py
@@ -80,3 +80,10 @@ class DuckDBEngineSpec(BaseEngineSpec):
         cls, database: Database, inspector: Inspector, schema: str | None
     ) -> set[str]:
         return set(inspector.get_table_names(schema))
+
+
+class MotherDuckEngineSpec(DuckDBEngineSpec):
+    engine = "duckdb"
+    engine_name = "MotherDuck"
+
+    sqlalchemy_uri_placeholder = "duckdb:///md:{SERVICE_TOKEN}@{database_name}"
diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py
index 8bf4867d01..4709a11377 100644
--- a/tests/integration_tests/databases/api_tests.py
+++ b/tests/integration_tests/databases/api_tests.py
@@ -3217,6 +3217,7 @@ class TestDatabaseApi(SupersetTestCase):
                     "engine": "hana",
                     "name": "SAP HANA",
                     "preferred": False,
+                    "sqlalchemy_uri_placeholder": "engine+driver://user:password@host:port/dbname[?key=value&key=value...]",
                     "engine_information": {
                         "supports_file_upload": True,
                         "disable_ssh_tunneling": False,
@@ -3248,6 +3249,7 @@ class TestDatabaseApi(SupersetTestCase):
                     "engine": "mysql",
                     "name": "MySQL",
                     "preferred": True,
+                    "sqlalchemy_uri_placeholder": "mysql://user:password@host:port/dbname[?key=value&key=value...]",
                     "engine_information": {
                         "supports_file_upload": True,
                         "disable_ssh_tunneling": False,
@@ -3258,6 +3260,7 @@ class TestDatabaseApi(SupersetTestCase):
                     "engine": "hana",
                     "name": "SAP HANA",
                     "preferred": False,
+                    "sqlalchemy_uri_placeholder": "engine+driver://user:password@host:port/dbname[?key=value&key=value...]",
                     "engine_information": {
                         "supports_file_upload": True,
                         "disable_ssh_tunneling": False,