From 73d4332b511f1d2cf70c9eebcbc468a9c28bfcf8 Mon Sep 17 00:00:00 2001 From: Peng Ren Date: Mon, 26 Jan 2026 18:51:35 -0500 Subject: [PATCH] feat(database): SIP-195 Add MongoDB database engine support (#37368) Co-authored-by: Peng Ren --- docs/static/img/databases/mongodb.png | Bin 0 -> 22190 bytes superset/db_engine_specs/mongodb.py | 125 ++++++++++++++++++ .../db_engine_specs/test_mongodb.py | 125 ++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 docs/static/img/databases/mongodb.png create mode 100644 superset/db_engine_specs/mongodb.py create mode 100644 tests/unit_tests/db_engine_specs/test_mongodb.py diff --git a/docs/static/img/databases/mongodb.png b/docs/static/img/databases/mongodb.png new file mode 100644 index 0000000000000000000000000000000000000000..7b6492d0f6ee78c459c993577d566932627c34ac GIT binary patch literal 22190 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 z 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 str: + return "datetime({col}, 'unixepoch')" + + @classmethod + def convert_dttm( + cls, + target_type: str, + dttm: datetime, + db_extra: Optional[dict[str, Any]] = None, + ) -> Optional[str]: + """Convert Python datetime to MongoDB/SQL datetime string.""" + sqla_type = cls.get_sqla_column_type(target_type) + + if isinstance( + sqla_type, (types.String, types.DateTime, types.Date, types.TIMESTAMP) + ): + # Return ISO format datetime string for MongoDB compatibility + return f"""{dttm.isoformat(sep=" ", timespec="seconds")!r}""" + + return None diff --git a/tests/unit_tests/db_engine_specs/test_mongodb.py b/tests/unit_tests/db_engine_specs/test_mongodb.py new file mode 100644 index 00000000000..2272fd51610 --- /dev/null +++ b/tests/unit_tests/db_engine_specs/test_mongodb.py @@ -0,0 +1,125 @@ +# 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 Optional + +import pytest + +from superset.constants import TimeGrain +from tests.unit_tests.db_engine_specs.utils import assert_convert_dttm +from tests.unit_tests.fixtures.common import dttm # noqa: F401 + + +@pytest.mark.parametrize( + "target_type,expected_result", + [ + ("text", "'2019-01-02 03:04:05'"), + ("TEXT", "'2019-01-02 03:04:05'"), + ("dateTime", "'2019-01-02 03:04:05'"), + ("DateTime", "'2019-01-02 03:04:05'"), + ("DATETIME", "'2019-01-02 03:04:05'"), + ("string", "'2019-01-02 03:04:05'"), + ("String", "'2019-01-02 03:04:05'"), + ("STRING", "'2019-01-02 03:04:05'"), + ("integer", None), + ("number", None), + ("unknowntype", None), + ], +) +def test_convert_dttm( + target_type: str, + expected_result: Optional[str], + dttm: datetime, # noqa: F811 +) -> None: + """Test datetime conversion for various MongoDB column types.""" + from superset.db_engine_specs.mongodb import ( + MongoDBEngineSpec as spec, # noqa: N813 + ) + + assert_convert_dttm(spec, target_type, expected_result, dttm) + + +def test_epoch_to_dttm() -> None: + """Test epoch to datetime conversion.""" + from superset.db_engine_specs.mongodb import ( + MongoDBEngineSpec as spec, # noqa: N813 + ) + + # MongoDB engine just passes through the column expression + assert spec.epoch_to_dttm() == "datetime({col}, 'unixepoch')" + + +@pytest.mark.parametrize( + "grain,expected_expression", + [ + (None, "{col}"), + (TimeGrain.SECOND, "DATETIME(STRFTIME('%Y-%m-%dT%H:%M:%S', {col}))"), + (TimeGrain.MINUTE, "DATETIME(STRFTIME('%Y-%m-%dT%H:%M:00', {col}))"), + (TimeGrain.HOUR, "DATETIME(STRFTIME('%Y-%m-%dT%H:00:00', {col}))"), + (TimeGrain.DAY, "DATETIME({col}, 'start of day')"), + ( + TimeGrain.WEEK, + "DATETIME({col}, 'start of day', -strftime('%w', {col}) || ' days')", + ), + (TimeGrain.MONTH, "DATETIME({col}, 'start of month')"), + ( + TimeGrain.QUARTER, + "DATETIME({col}, 'start of month', " + "printf('-%d month', (strftime('%m', {col}) - 1) % 3))", + ), + (TimeGrain.YEAR, "DATETIME({col}, 'start of year')"), + ( + TimeGrain.WEEK_ENDING_SATURDAY, + "DATETIME({col}, 'start of day', 'weekday 6')", + ), + ( + TimeGrain.WEEK_ENDING_SUNDAY, + "DATETIME({col}, 'start of day', 'weekday 0')", + ), + ( + TimeGrain.WEEK_STARTING_SUNDAY, + "DATETIME({col}, 'start of day', 'weekday 0', '-7 days')", + ), + ( + TimeGrain.WEEK_STARTING_MONDAY, + "DATETIME({col}, 'start of day', 'weekday 1', '-7 days')", + ), + ], +) +def test_time_grain_expressions( + grain: Optional[TimeGrain], + expected_expression: str, +) -> None: + """Test time grain expressions for MongoDB.""" + from superset.db_engine_specs.mongodb import ( + MongoDBEngineSpec as spec, # noqa: N813 + ) + + # pylint: disable=protected-access + actual = spec._time_grain_expressions.get(grain) + assert actual == expected_expression + + +def test_engine_metadata() -> None: + """Test MongoDB engine specification metadata.""" + from superset.db_engine_specs.mongodb import ( + MongoDBEngineSpec as spec, # noqa: N813 + ) + + assert spec.engine == "mongodb" + assert spec.engine_name == "MongoDB" + assert spec.force_column_alias_quotes is False