From 649a0dec6c82d0d0f7631b5ce2bf796aeff53e0c Mon Sep 17 00:00:00 2001 From: Alex Duan <51781608+DuanKuanJun@users.noreply.github.com> Date: Fri, 7 Feb 2025 04:45:55 +0800 Subject: [PATCH] feat: add TDengine.py driver to db_engine (#32041) Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com> --- README.md | 1 + docs/docs/configuration/databases.mdx | 19 ++++++ docs/static/img/tdengine.png | Bin 0 -> 43540 bytes pyproject.toml | 4 ++ superset/db_engine_specs/tdengine.py | 57 ++++++++++++++++++ .../db_engine_specs/test_tdengine.py | 34 +++++++++++ 6 files changed, 115 insertions(+) create mode 100644 docs/static/img/tdengine.png create mode 100644 superset/db_engine_specs/tdengine.py create mode 100644 tests/unit_tests/db_engine_specs/test_tdengine.py diff --git a/README.md b/README.md index 7928904a2..3c03e9ac1 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ Here are some of the major database solutions that are supported: oceanbase denodo ydb + TDengine

**A more comprehensive list of supported databases** along with the configuration instructions can be found [here](https://superset.apache.org/docs/configuration/databases). diff --git a/docs/docs/configuration/databases.mdx b/docs/docs/configuration/databases.mdx index 16af4451c..a9cbe0fa7 100644 --- a/docs/docs/configuration/databases.mdx +++ b/docs/docs/configuration/databases.mdx @@ -78,6 +78,7 @@ are compatible with Superset. | [Snowflake](/docs/configuration/databases#snowflake) | `pip install snowflake-sqlalchemy` | `snowflake://{user}:{password}@{account}.{region}/{database}?role={role}&warehouse={warehouse}` | | SQLite | No additional library needed | `sqlite://path/to/file.db?check_same_thread=false` | | [SQL Server](/docs/configuration/databases#sql-server) | `pip install pymssql` | `mssql+pymssql://` | +| [TDengine](/docs/configuration/databases#tdengine) | `pip install taospy` `pip install taos-ws-py` | `taosws://:@:` | | [Teradata](/docs/configuration/databases#teradata) | `pip install teradatasqlalchemy` | `teradatasql://{user}:{password}@{host}` | | [TimescaleDB](/docs/configuration/databases#timescaledb) | `pip install psycopg2` | `postgresql://:@:/` | | [Trino](/docs/configuration/databases#trino) | `pip install trino` | `trino://{username}:{password}@{hostname}:{port}/{catalog}` | @@ -1354,6 +1355,24 @@ starrocks://:@:/. StarRocks maintains their Superset docuementation [here](https://docs.starrocks.io/docs/integrations/BI_integrations/Superset/). ::: +#### TDengine + +[TDengine](https://www.tdengine.com) is a High-Performance, Scalable Time-Series Database for Industrial IoT and provides SQL-like query interface. + +The recommended connector library for TDengine is [taospy](https://pypi.org/project/taospy/) and [taos-ws-py](https://pypi.org/project/taos-ws-py/) + +The expected connection string is formatted as follows: + +``` +taosws://:@: +``` + +For example: + +``` +taosws://root:taosdata@127.0.0.1:6041 +``` + #### Teradata The recommended connector library is diff --git a/docs/static/img/tdengine.png b/docs/static/img/tdengine.png new file mode 100644 index 0000000000000000000000000000000000000000..9d008807815e5e0fb7d9369f26007cb166fe89c8 GIT binary patch literal 43540 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 literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml index 0d93f9e96..892a2fea5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -173,6 +173,10 @@ spark = [ "tableschema", "thrift>=0.14.1, <1", ] +tdengine = [ + "taospy>=2.7.21", + "taos-ws-py>=0.3.8" +] teradata = ["teradatasql>=16.20.0.23"] thumbnails = ["Pillow>=10.0.1, <11"] vertica = ["sqlalchemy-vertica-python>=0.5.9, < 0.6"] diff --git a/superset/db_engine_specs/tdengine.py b/superset/db_engine_specs/tdengine.py new file mode 100644 index 000000000..f2910e422 --- /dev/null +++ b/superset/db_engine_specs/tdengine.py @@ -0,0 +1,57 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from __future__ import annotations + +from typing import Any +from urllib import parse + +from sqlalchemy.engine.url import make_url, URL # noqa: F401 + +from superset.db_engine_specs.base import BaseEngineSpec + + +class TDengineEngineSpec(BaseEngineSpec): + engine = "taosws" + engine_name = "TDengine" + max_column_name_length = 64 + default_driver = "taosws" + sqlalchemy_uri_placeholder = ( + "taosws://user:******@host:port/dbname[?key=value&key=value...]" + ) + + # time grain + _time_grain_expressions = { + None: "{col}", + "PT1S": "TIMETRUNCATE({col}, 1s, 0)", + "PT1M": "TIMETRUNCATE({col}, 1m, 0)", + "PT1H": "TIMETRUNCATE({col}, 1h, 0)", + "P1D": "TIMETRUNCATE({col}, 1d, 0)", + "P1W": "TIMETRUNCATE({col}, 1w, 0)", + } + + @classmethod + def get_schema_from_engine_params( + cls, + sqlalchemy_uri: URL, + connect_args: dict[str, Any], + ) -> str | None: + """ + Return the configured schema. + + A TDengine database is a SQLAlchemy schema. + """ + return parse.unquote(sqlalchemy_uri.database) diff --git a/tests/unit_tests/db_engine_specs/test_tdengine.py b/tests/unit_tests/db_engine_specs/test_tdengine.py new file mode 100644 index 000000000..f33cef5d6 --- /dev/null +++ b/tests/unit_tests/db_engine_specs/test_tdengine.py @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + + +from sqlalchemy.engine.url import make_url, URL # noqa: F401 + + +# test get schema +def test_get_schema_from_engine_params() -> None: + """ + Test the ``get_schema_from_engine_params`` method. + """ + from superset.db_engine_specs.tdengine import TDengineEngineSpec + + assert ( + TDengineEngineSpec.get_schema_from_engine_params( + make_url("taosws://root:taosdata@127.0.0.1:6041/dbname"), {} + ) + == "dbname" + )