This is an automated email from the ASF dual-hosted git repository. rusackas pushed a commit to branch feat/add-phoenix-iotdb-engine-specs in repository https://gitbox.apache.org/repos/asf/superset.git
commit 1b2b5eec238affcc39ec7d5b3de7532e50b2fc76 Author: Evan Rusackas <[email protected]> AuthorDate: Sat Jan 31 14:49:30 2026 +0100 feat(db_engine_specs): add Apache Phoenix and Apache IoTDB engine specs Add engine specs for two ASF database projects: - Apache Phoenix: SQL layer over Apache HBase, using phoenixdb driver (phoenix:// dialect, default port 8765) - Apache IoTDB: Time series database for IoT data, using apache-iotdb driver (iotdb:// dialect, default port 6667) Both include metadata for docs generation, logos, and connection info. Co-Authored-By: Claude Opus 4.5 <[email protected]> --- docs/static/img/databases/apache-iotdb.svg | Bin 0 -> 4646 bytes docs/static/img/databases/apache-phoenix.png | Bin 0 -> 9552 bytes superset/db_engine_specs/iotdb.py | 57 +++++++++++++++++ superset/db_engine_specs/phoenix.py | 90 +++++++++++++++++++++++++++ 4 files changed, 147 insertions(+) diff --git a/docs/static/img/databases/apache-iotdb.svg b/docs/static/img/databases/apache-iotdb.svg new file mode 100644 index 0000000000..d742630737 Binary files /dev/null and b/docs/static/img/databases/apache-iotdb.svg differ diff --git a/docs/static/img/databases/apache-phoenix.png b/docs/static/img/databases/apache-phoenix.png new file mode 100644 index 0000000000..d5f32ec9fd Binary files /dev/null and b/docs/static/img/databases/apache-phoenix.png differ diff --git a/superset/db_engine_specs/iotdb.py b/superset/db_engine_specs/iotdb.py new file mode 100644 index 0000000000..69049c05a4 --- /dev/null +++ b/superset/db_engine_specs/iotdb.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 superset.db_engine_specs.base import BaseEngineSpec, DatabaseCategory + + +class IoTDBEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method + """Dialect for Apache IoTDB""" + + engine = "iotdb" + engine_name = "Apache IoTDB" + + metadata = { + "description": ( + "Apache IoTDB is a time series database designed for IoT data, " + "with efficient storage and query capabilities for massive " + "time series data." + ), + "logo": "apache-iotdb.svg", + "homepage_url": "https://iotdb.apache.org/", + "categories": [ + DatabaseCategory.APACHE_PROJECTS, + DatabaseCategory.TIME_SERIES, + DatabaseCategory.OPEN_SOURCE, + ], + "pypi_packages": ["apache-iotdb"], + "connection_string": ("iotdb://{username}:{password}@{hostname}:{port}/"), + "default_port": 6667, + "parameters": { + "username": "Database username (default: root)", + "password": "Database password (default: root)", + "hostname": "IP address or hostname", + "port": "Default 6667", + }, + "notes": ( + "The IoTDB SQLAlchemy dialect was written to integrate with " + "Apache Superset. IoTDB uses a hierarchical data model, which " + "is reorganized into a relational model for SQL queries." + ), + } + + _time_grain_expressions = { + None: "{col}", + } diff --git a/superset/db_engine_specs/phoenix.py b/superset/db_engine_specs/phoenix.py new file mode 100644 index 0000000000..85df239613 --- /dev/null +++ b/superset/db_engine_specs/phoenix.py @@ -0,0 +1,90 @@ +# 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 datetime import datetime +from typing import Any, Optional + +from sqlalchemy import types + +from superset.constants import TimeGrain +from superset.db_engine_specs.base import BaseEngineSpec, DatabaseCategory + + +class PhoenixEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method + """Dialect for Apache Phoenix""" + + engine = "phoenix" + engine_name = "Apache Phoenix" + + metadata = { + "description": ( + "Apache Phoenix is a relational database layer over Apache HBase, " + "providing low-latency SQL queries over HBase data." + ), + "logo": "apache-phoenix.png", + "homepage_url": "https://phoenix.apache.org/", + "categories": [ + DatabaseCategory.APACHE_PROJECTS, + DatabaseCategory.ANALYTICAL_DATABASES, + DatabaseCategory.OPEN_SOURCE, + ], + "pypi_packages": ["phoenixdb"], + "connection_string": "phoenix://{hostname}:{port}/", + "default_port": 8765, + "notes": ( + "Phoenix provides a SQL interface to Apache HBase. " + "The phoenixdb driver connects via the Phoenix Query Server " + "and supports a subset of SQLAlchemy." + ), + } + + _time_grain_expressions = { + None: "{col}", + TimeGrain.SECOND: ( + "CAST(DATE_TRUNC('SECOND', CAST({col} AS TIMESTAMP)) AS TIMESTAMP)" + ), + TimeGrain.MINUTE: ( + "CAST(DATE_TRUNC('MINUTE', CAST({col} AS TIMESTAMP)) AS TIMESTAMP)" + ), + TimeGrain.HOUR: ( + "CAST(DATE_TRUNC('HOUR', CAST({col} AS TIMESTAMP)) AS TIMESTAMP)" + ), + TimeGrain.DAY: "CAST(DATE_TRUNC('DAY', CAST({col} AS TIMESTAMP)) AS DATE)", + TimeGrain.WEEK: "CAST(DATE_TRUNC('WEEK', CAST({col} AS TIMESTAMP)) AS DATE)", + TimeGrain.MONTH: ( + "CAST(DATE_TRUNC('MONTH', CAST({col} AS TIMESTAMP)) AS DATE)" + ), + TimeGrain.QUARTER: ( + "CAST(DATE_TRUNC('QUARTER', CAST({col} AS TIMESTAMP)) AS DATE)" + ), + TimeGrain.YEAR: "CAST(DATE_TRUNC('YEAR', CAST({col} AS TIMESTAMP)) AS DATE)", + } + + @classmethod + def convert_dttm( + cls, + target_type: str, + dttm: datetime, + db_extra: Optional[dict[str, Any]] = None, + ) -> Optional[str]: + sqla_type = cls.get_sqla_column_type(target_type) + + if isinstance(sqla_type, types.Date): + return f"TO_DATE('{dttm.date().isoformat()}', 'yyyy-MM-dd')" + if isinstance(sqla_type, (types.DateTime, types.TIMESTAMP)): + datetime_formatted = dttm.isoformat(sep=" ", timespec="seconds") + return f"TO_TIMESTAMP('{datetime_formatted}', 'yyyy-MM-dd HH:mm:ss')" + return None
