From b6b337e09a05101506a5faba4b45d370cc3c9fc8 Mon Sep 17 00:00:00 2001 From: chyueyi <35141460+chyueyi@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:27:52 +0800 Subject: [PATCH] feat: add support for doris datasource (#14087) * feat: add support for doris datasource * fix: fix python style check * fix: add pydoris dependency * fix: add pydoris dependency * fix: py_format_check * fix: parse error when doris view column is VARCHAR(*), check data length if not digit then return 1 --------- Co-authored-by: Sriharsha Chintalapani --- ingestion/setup.py | 1 + .../source/database/doris/__init__.py | 0 .../source/database/doris/connection.py | 72 ++++ .../source/database/doris/metadata.py | 315 ++++++++++++++++++ .../source/database/doris/queries.py | 54 +++ .../ingestion/source/database/doris/utils.py | 64 ++++ .../data/testConnections/database/doris.json | 33 ++ .../json/schema/entity/data/table.json | 7 +- .../connections/database/dorisConnection.json | 102 ++++++ .../entity/services/databaseService.json | 9 +- .../ui/src/assets/img/service-icon-doris.png | Bin 0 -> 18959 bytes .../ui/src/constants/Services.constant.ts | 2 + .../ui/src/utils/DatabaseServiceUtils.ts | 7 + .../ui/src/utils/ServiceUtilClassBase.ts | 4 + 14 files changed, 668 insertions(+), 2 deletions(-) create mode 100644 ingestion/src/metadata/ingestion/source/database/doris/__init__.py create mode 100644 ingestion/src/metadata/ingestion/source/database/doris/connection.py create mode 100644 ingestion/src/metadata/ingestion/source/database/doris/metadata.py create mode 100644 ingestion/src/metadata/ingestion/source/database/doris/queries.py create mode 100644 ingestion/src/metadata/ingestion/source/database/doris/utils.py create mode 100644 openmetadata-service/src/main/resources/json/data/testConnections/database/doris.json create mode 100644 openmetadata-spec/src/main/resources/json/schema/entity/services/connections/database/dorisConnection.json create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-doris.png diff --git a/ingestion/setup.py b/ingestion/setup.py index b3194a3b41c..a0b4580a8fd 100644 --- a/ingestion/setup.py +++ b/ingestion/setup.py @@ -174,6 +174,7 @@ plugins: Dict[str, Set[str]] = { "deltalake": {"delta-spark<=2.3.0"}, "docker": {"python_on_whales==0.55.0"}, "domo": {VERSIONS["pydomo"]}, + "doris": {"pydoris==1.0.2"}, "druid": {"pydruid>=0.6.5"}, "dynamodb": {VERSIONS["boto3"]}, "elasticsearch": { diff --git a/ingestion/src/metadata/ingestion/source/database/doris/__init__.py b/ingestion/src/metadata/ingestion/source/database/doris/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ingestion/src/metadata/ingestion/source/database/doris/connection.py b/ingestion/src/metadata/ingestion/source/database/doris/connection.py new file mode 100644 index 00000000000..cd8f204fff9 --- /dev/null +++ b/ingestion/src/metadata/ingestion/source/database/doris/connection.py @@ -0,0 +1,72 @@ +# Copyright 2021 Collate +# Licensed 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. + +""" +Source connection handler +""" +from typing import Optional + +from sqlalchemy.engine import Engine + +from metadata.generated.schema.entity.automations.workflow import ( + Workflow as AutomationWorkflow, +) +from metadata.generated.schema.entity.services.connections.database.dorisConnection import ( + DorisConnection, +) +from metadata.ingestion.connections.builders import ( + create_generic_db_connection, + get_connection_args_common, + get_connection_url_common, + init_empty_connection_options, +) +from metadata.ingestion.connections.test_connections import ( + test_connection_db_schema_sources, +) +from metadata.ingestion.ometa.ometa_api import OpenMetadata + + +def get_connection(connection: DorisConnection) -> Engine: + """ + Create connection + """ + if connection.sslCA or connection.sslCert or connection.sslKey: + if not connection.connectionOptions: + connection.connectionOptions = init_empty_connection_options() + if connection.sslCA: + connection.connectionOptions.__root__["ssl_ca"] = connection.sslCA + if connection.sslCert: + connection.connectionOptions.__root__["ssl_cert"] = connection.sslCert + if connection.sslKey: + connection.connectionOptions.__root__["ssl_key"] = connection.sslKey + return create_generic_db_connection( + connection=connection, + get_connection_url_fn=get_connection_url_common, + get_connection_args_fn=get_connection_args_common, + ) + + +def test_connection( + metadata: OpenMetadata, + engine: Engine, + service_connection: DorisConnection, + automation_workflow: Optional[AutomationWorkflow] = None, +) -> None: + """ + Test connection. This can be executed either as part + of a metadata workflow or during an Automation Workflow + """ + test_connection_db_schema_sources( + metadata=metadata, + engine=engine, + service_connection=service_connection, + automation_workflow=automation_workflow, + ) diff --git a/ingestion/src/metadata/ingestion/source/database/doris/metadata.py b/ingestion/src/metadata/ingestion/source/database/doris/metadata.py new file mode 100644 index 00000000000..cdf7c196f32 --- /dev/null +++ b/ingestion/src/metadata/ingestion/source/database/doris/metadata.py @@ -0,0 +1,315 @@ +# Copyright 2021 Collate +# Licensed 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. +"""Mysql source module""" +import re +import traceback +from typing import Dict, Iterable, List, Optional, Tuple, cast + +from pydoris.sqlalchemy import datatype +from pydoris.sqlalchemy.dialect import DorisDialect +from sqlalchemy import sql +from sqlalchemy.dialects.mysql.reflection import MySQLTableDefinitionParser +from sqlalchemy.engine.reflection import Inspector + +from metadata.generated.schema.entity.data.table import ( + Column, + IntervalType, + TableConstraint, + TablePartition, + TableType, +) +from metadata.generated.schema.entity.services.connections.database.dorisConnection import ( + DorisConnection, +) +from metadata.generated.schema.metadataIngestion.workflow import ( + Source as WorkflowSource, +) +from metadata.ingestion.api.steps import InvalidSourceException +from metadata.ingestion.ometa.ometa_api import OpenMetadata +from metadata.ingestion.source.database.common_db_source import ( + CommonDbSourceService, + TableNameAndType, +) +from metadata.ingestion.source.database.doris.queries import ( + DORIS_GET_TABLE_NAMES, + DORIS_PARTITION_DETAILS, + DORIS_SHOW_FULL_COLUMNS, +) +from metadata.ingestion.source.database.doris.utils import ( + get_table_comment, + get_table_names_and_type, +) +from metadata.ingestion.source.database.mysql.utils import parse_column +from metadata.utils.logger import ingestion_logger + +MySQLTableDefinitionParser._parse_column = ( # pylint: disable=protected-access + parse_column +) + +RELKIND_MAP = { + "Doris": TableType.Regular, + "View": TableType.View, + "MEMORY": TableType.View, +} + +DorisDialect.get_table_names_and_type = get_table_names_and_type +DorisDialect.get_table_comment = get_table_comment + +logger = ingestion_logger() + + +def extract_number(data): + """ + extract data type length for CHAR, VARCHAR, DECIMAL, such as CHAR(1), return ['1'], + DECIMAL[9,0] return ['9', '0'] + """ + result = re.findall(r"\((.*?)\)", data) + # doris view column may be VARCHAR(*), check data length if not digit then return 1 + if result: + result = [i.strip() if i.strip().isdigit() else 1 for i in result[0].split(",")] + return result + return [] + + +def extract_child(data): + """ + extract_child for ARRAY and Struct, such as ARRAY, then return INT(11) + """ + result = re.findall(r"(?<=<).+(?=>)", data) + return result[0] + + +def _parse_type(_type): + """ + parse raw type to system_data_type like CHAR(1) -> CHAR, STRUCT -> STRUCT, + DECIMALV3(9, 0) -> DECIMAL, DATEV2 -> DATE + """ + parse_type = _type.split("(")[0].split("<")[0] + if parse_type[-2] == "v": + system_data_type = parse_type.split("v")[0].upper() + else: + system_data_type = parse_type.upper() + return system_data_type + + +def _get_column(ordinal, field, _type, null, default, comment): + _type = _type.lower() + system_data_type = _parse_type(_type) + data_type = datatype.parse_sqltype(_type) + if system_data_type in ["VARCHAR", "CHAR"]: + data_type.length = extract_number(_type)[0] + if system_data_type == "DECIMAL": + number = extract_number(_type) + if len(number) == 2: + data_type.precision = number[0] + data_type.scale = number[1] + arr_data_type = None + children = None + if system_data_type == "ARRAY": + arr_data_type = _parse_type(extract_child(_type)) + if system_data_type == "STRUCT": + children = [] + for k, child in enumerate(extract_child(_type).split(",")): + name_type = child.split(":") + children.append( + _get_column(k, name_type[0], name_type[1], "YES", None, None) + ) + return { + "name": field, + "default": default, + "nullable": True if null == "YES" else None, + "type": _type, + "data_type": data_type, + "display_type": _type.lower(), + "system_data_type": system_data_type, + "comment": comment, + "ordinalPosition": ordinal, + "arr_data_type": arr_data_type, + "children": children, + } + + +class DorisSource(CommonDbSourceService): + """ + Implements the necessary methods to extract + Database metadata from Mysql Source + """ + + @classmethod + def create(cls, config_dict, metadata: OpenMetadata): + config: WorkflowSource = WorkflowSource.parse_obj(config_dict) + if config.serviceConnection is None: + raise InvalidSourceException("Missing service connection") + connection = cast(DorisConnection, config.serviceConnection.__root__.config) + if not isinstance(connection, DorisConnection): + raise InvalidSourceException( + f"Expected DorisConnection, but got {connection}" + ) + return cls(config, metadata) + + def query_table_names_and_types( + self, schema_name: str + ) -> Iterable[TableNameAndType]: + """ + Connect to the source database to get the table + name and type. By default, use the inspector method + to get the names and pass the Regular type. + + This is useful for sources where we need fine-grained + logic on how to handle table types, e.g., external, foreign,... + """ + tables = [ + TableNameAndType(name=name, type_=RELKIND_MAP.get(engine)) + for name, engine in self.connection.execute( + sql.text(DORIS_GET_TABLE_NAMES), {"schema": schema_name} + ) + or [] + ] + return tables + + @staticmethod + def get_table_description( + schema_name: str, table_name: str, inspector: Inspector + ) -> str: + description = None + try: + table_info: dict = inspector.get_table_comment(table_name, schema_name) + # Catch any exception without breaking the ingestion + except Exception as exc: # pylint: disable=broad-except + logger.debug(traceback.format_exc()) + logger.warning( + f"Table description error for table [{schema_name}.{table_name}]: {exc}" + ) + else: + description = table_info.get("text") + + return description[0] + + def _get_columns(self, table_name, schema=None): + """ + Overriding the dialect method to add raw_data_type in response + """ + + table_columns = [] + primary_columns = [] + # row schema: Field, Type, Collation, Null, Key, Default, Extra, Privileges, Comment + for i, row in enumerate( + self.connection.execute( + sql.text(DORIS_SHOW_FULL_COLUMNS.format(schema, table_name)) + ) + ): + table_columns.append(_get_column(i, row[0], row[1], row[3], row[5], row[8])) + if row[4] == "YES": + primary_columns.append(row[0]) + + return table_columns, primary_columns + + def get_columns_and_constraints( + self, schema_name: str, table_name: str, db_name: str, inspector: Inspector + ) -> Tuple[ + Optional[List[Column]], Optional[List[TableConstraint]], Optional[List[Dict]] + ]: + """ + :param schema_name: + :param table_name: + :param db_name: + :param inspector: + :return: + """ + table_constraints = [] + table_columns = [] + columns, primary_columns = self._get_columns(table_name, schema_name) + for column in columns: + try: + children = None + if column["children"]: + children = [ + Column( + name=child["name"] if child["name"] else " ", + description=child.get("comment"), + dataType=child["system_data_type"], + dataTypeDisplay=child["display_type"], + dataLength=self._check_col_length( + child["system_data_type"], child["data_type"] + ), + constraint=None, + children=child["children"], + arrayDataType=child["arr_data_type"], + ordinalPosition=child.get("ordinalPosition"), + ) + for child in column["children"] + ] + self.process_additional_table_constraints( + column=column, table_constraints=table_constraints + ) + + col_constraint = self._get_column_constraints( + column, primary_columns, [] + ) + col_data_length = self._check_col_length( + column["system_data_type"], column["data_type"] + ) + if col_data_length is None: + col_data_length = 1 + if column["system_data_type"] is None: + logger.warning( + f"Unknown type {repr(column['type'])}: {column['name']}" + ) + om_column = Column( + name=column["name"] if column["name"] else " ", + description=column.get("comment"), + dataType=column["system_data_type"], + dataTypeDisplay=column.get("type"), + dataLength=col_data_length, + constraint=col_constraint, + children=children, + arrayDataType=column["arr_data_type"], + ordinalPosition=column.get("ordinalPosition"), + ) + if column["system_data_type"] == "DECIMAL": + om_column.precision = column["data_type"].precision + om_column.scale = column["data_type"].scale + + om_column.tags = self.get_column_tag_labels( + table_name=table_name, column=column + ) + except Exception as exc: + logger.debug(traceback.format_exc()) + logger.warning( + f"Unexpected exception processing column [{column}]: {exc}" + ) + continue + table_columns.append(om_column) + return table_columns, [], [] + + def get_table_partition_details( + self, + table_name: str, + schema_name: str, + inspector: Inspector, + ) -> Tuple[bool, Optional[TablePartition]]: + """ + check if the table is partitioned table and return the partition details + """ + try: + result = self.engine.execute( + sql.text(DORIS_PARTITION_DETAILS.format(schema_name, table_name)) + ).all() + + if result and result[0].PartitionKey != "": + partition_details = TablePartition( + intervalType=IntervalType.TIME_UNIT.value, + columns=result[0].PartitionKey.split(", "), + ) + return True, partition_details + return False, None + except Exception: + return False, None diff --git a/ingestion/src/metadata/ingestion/source/database/doris/queries.py b/ingestion/src/metadata/ingestion/source/database/doris/queries.py new file mode 100644 index 00000000000..165e589d379 --- /dev/null +++ b/ingestion/src/metadata/ingestion/source/database/doris/queries.py @@ -0,0 +1,54 @@ +""" +SQL Queries used during ingestion +""" + +import textwrap + +DORIS_GET_SCHEMA_COLUMN_INFO = textwrap.dedent( + """ +SELECT COLUMN_NAME,COLUMN_DEFAULT,IS_NULLABLE,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH, +NUMERIC_PRECISION,NUMERIC_SCALE,COLUMN_TYPE,COLUMN_KEY,COLUMN_COMMENT,ORDINAL_POSITION +from information_schema.`columns` t +where TABLE_SCHEMA = :schema +AND TABLE_NAME = :table_name + """ +) + +DORIS_SHOW_FULL_COLUMNS = textwrap.dedent( + """ +SHOW FULL COLUMNS FROM {}.{} + """ +) + +DORIS_GET_TABLE_NAMES = textwrap.dedent( + """ + select TABLE_NAME as name, `ENGINE` as engine + from INFORMATION_SCHEMA.tables + where TABLE_SCHEMA = :schema + """ +) + +DORIS_TABLE_COMMENTS = textwrap.dedent( + """ +SELECT TABLE_COMMENT +FROM information_schema.tables +WHERE TABLE_SCHEMA = :schema +AND TABLE_NAME = :table_name +""" +) + +DORIS_VIEW_DEFINITIONS = textwrap.dedent( + """ +select + TABLE_NAME as view_name, + TABLE_SCHEMA as schema, + '' as view_def +from information_schema.tables where engine in ['MaterializedView', 'View'] +""" +) + +DORIS_PARTITION_DETAILS = textwrap.dedent( + """ +SHOW PARTITIONS FROM {}.{} + """ +) diff --git a/ingestion/src/metadata/ingestion/source/database/doris/utils.py b/ingestion/src/metadata/ingestion/source/database/doris/utils.py new file mode 100644 index 00000000000..c9f201991b8 --- /dev/null +++ b/ingestion/src/metadata/ingestion/source/database/doris/utils.py @@ -0,0 +1,64 @@ +# Copyright 2021 Collate +# Licensed 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. + +""" +MySQL SQLAlchemy Helper Methods +""" +import textwrap + +from sqlalchemy import sql +from sqlalchemy.engine import reflection + +from metadata.ingestion.source.database.doris.queries import ( + DORIS_TABLE_COMMENTS, + DORIS_VIEW_DEFINITIONS, +) +from metadata.utils.sqlalchemy_utils import get_view_definition_wrapper + +query = textwrap.dedent( + """ + select TABLE_NAME as name, `ENGINE` as engine + from INFORMATION_SCHEMA.tables + """ +) + + +@reflection.cache +def get_view_definition(self, connection, table_name, schema=None): + return get_view_definition_wrapper( + self, + connection, + table_name=table_name, + schema=schema, + query=DORIS_VIEW_DEFINITIONS, + ) + + +def get_table_names_and_type(_, connection, schema=None, **kw): + if schema: + query_sql = query + f" WHERE TABLE_SCHEMA = '{schema}'" + database = schema or connection.engine.url.database + rows = connection.execute(query_sql, database=database, **kw) + return list(rows) + + +@reflection.cache +def get_table_comment(_, connection, table_name, schema=None, **kw): + comment = None + rows = connection.execute( + sql.text(DORIS_TABLE_COMMENTS), + {"table_name": table_name, "schema": schema}, + **kw, + ) + for table_comment in rows: + comment = table_comment + break + return {"text": comment} diff --git a/openmetadata-service/src/main/resources/json/data/testConnections/database/doris.json b/openmetadata-service/src/main/resources/json/data/testConnections/database/doris.json new file mode 100644 index 00000000000..c2621984eda --- /dev/null +++ b/openmetadata-service/src/main/resources/json/data/testConnections/database/doris.json @@ -0,0 +1,33 @@ +{ + "name": "Doris", + "displayName": "Doris Test Connection", + "description": "This Test Connection validates the access against the database and basic metadata extraction of schemas and tables.", + "steps": [ + { + "name": "CheckAccess", + "description": "Validate that we can properly reach the database and authenticate with the given credentials.", + "errorMessage": "Failed to connect to doris, please validate the credentials", + "shortCircuit": true, + "mandatory": true + }, + { + "name": "GetSchemas", + "description": "List all the schemas available to the user.", + "errorMessage": "Failed to fetch schemas, please validate if the user has enough privilege to fetch schemas.", + "mandatory": true + }, + { + "name": "GetTables", + "description": "From a given schema, list the tables belonging to that schema. If no schema is specified, we'll list the tables of a random schema.", + "errorMessage": "Failed to fetch tables, please validate if the user has enough privilege to fetch tables.", + "mandatory": true + }, + { + "name": "GetViews", + "description": "From a given schema, list the views belonging to that schema. If no schema is specified, we'll list the tables of a random schema.", + "errorMessage": "Failed to fetch views, please validate if the user has enough privilege to fetch views.", + "mandatory": false + } + ] +} + diff --git a/openmetadata-spec/src/main/resources/json/schema/entity/data/table.json b/openmetadata-spec/src/main/resources/json/schema/entity/data/table.json index 98c80fe3d62..fbb5e8dcfae 100644 --- a/openmetadata-spec/src/main/resources/json/schema/entity/data/table.json +++ b/openmetadata-spec/src/main/resources/json/schema/entity/data/table.json @@ -151,7 +151,12 @@ "IMAGE", "IPV4", "IPV6", - "DATETIMERANGE" + "DATETIMERANGE", + "HLL", + "LARGEINT", + "QUANTILE_STATE", + "AGG_STATE", + "BITMAP" ] }, "constraint": { diff --git a/openmetadata-spec/src/main/resources/json/schema/entity/services/connections/database/dorisConnection.json b/openmetadata-spec/src/main/resources/json/schema/entity/services/connections/database/dorisConnection.json new file mode 100644 index 00000000000..e0d30a4abe9 --- /dev/null +++ b/openmetadata-spec/src/main/resources/json/schema/entity/services/connections/database/dorisConnection.json @@ -0,0 +1,102 @@ +{ + "$id": "https://open-metadata.org/schema/entity/services/connections/database/dorisConnection.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DorisConnection", + "description": "Doris Database Connection Config", + "type": "object", + "javaType": "org.openmetadata.schema.services.connections.database.DorisConnection", + "definitions": { + "dorisType": { + "description": "Service type.", + "type": "string", + "enum": ["Doris"], + "default": "Doris" + }, + "dorisScheme": { + "description": "SQLAlchemy driver scheme options.", + "type": "string", + "enum": ["doris"], + "default": "doris" + } + }, + "properties": { + "type": { + "title": "Service Type", + "description": "Service Type", + "$ref": "#/definitions/dorisType", + "default": "Doris" + }, + "scheme": { + "title": "Connection Scheme", + "description": "SQLAlchemy driver scheme options.", + "$ref": "#/definitions/dorisScheme", + "default": "doris" + }, + "username": { + "title": "Username", + "description": "Username to connect to Doris. This user should have privileges to read all the metadata in Doris.", + "type": "string" + }, + "password": { + "title": "Password", + "description": "Password to connect to Doris.", + "type": "string", + "format": "password" + }, + "hostPort": { + "title": "Host and Port", + "description": "Host and port of the Doris service.", + "type": "string" + }, + "databaseName": { + "title": "Database Name", + "description": "Optional name to give to the database in OpenMetadata. If left blank, we will use default as the database name.", + "type": "string" + }, + "databaseSchema": { + "title": "Database Schema", + "description": "Database Schema of the data source. This is optional parameter, if you would like to restrict the metadata reading to a single schema. When left blank, OpenMetadata Ingestion attempts to scan all the schemas.", + "type": "string" + }, + "sslCA": { + "title": "SSL CA", + "description": "Provide the path to ssl ca file", + "type": "string" + }, + "sslCert": { + "title": "SSL Client Certificate File", + "description": "Provide the path to ssl client certificate file (ssl_cert)", + "type": "string" + }, + "sslKey": { + "title": "SSL Client Key File", + "description": "Provide the path to ssl client certificate file (ssl_key)", + "type": "string" + }, + "connectionOptions": { + "title": "Connection Options", + "$ref": "../connectionBasicType.json#/definitions/connectionOptions" + }, + "connectionArguments": { + "title": "Connection Arguments", + "$ref": "../connectionBasicType.json#/definitions/connectionArguments" + }, + "supportsMetadataExtraction": { + "title": "Supports Metadata Extraction", + "$ref": "../connectionBasicType.json#/definitions/supportsMetadataExtraction" + }, + "supportsDBTExtraction": { + "$ref": "../connectionBasicType.json#/definitions/supportsDBTExtraction" + }, + "supportsProfiler": { + "title": "Supports Profiler", + "$ref": "../connectionBasicType.json#/definitions/supportsProfiler" + }, + "supportsQueryComment": { + "title": "Supports Query Comment", + "$ref": "../connectionBasicType.json#/definitions/supportsQueryComment" + } + }, + "additionalProperties": false, + "required": ["hostPort", "username"] +} diff --git a/openmetadata-spec/src/main/resources/json/schema/entity/services/databaseService.json b/openmetadata-spec/src/main/resources/json/schema/entity/services/databaseService.json index a27959dd9c0..d32b573d3a3 100644 --- a/openmetadata-spec/src/main/resources/json/schema/entity/services/databaseService.json +++ b/openmetadata-spec/src/main/resources/json/schema/entity/services/databaseService.json @@ -49,7 +49,8 @@ "SapHana", "MongoDB", "Couchbase", - "Greenplum" + "Greenplum", + "Doris" ], "javaEnums": [ { @@ -156,6 +157,9 @@ }, { "name": "Greenplum" + }, + { + "name": "Doris" } ] }, @@ -267,6 +271,9 @@ }, { "$ref": "./connections/database/greenplumConnection.json" + }, + { + "$ref": "./connections/database/dorisConnection.json" } ] } diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-doris.png b/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-doris.png new file mode 100644 index 0000000000000000000000000000000000000000..1c0fa86c1cb493ecd5d5edccabcd498badeae497 GIT binary patch literal 18959 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 nk00; { break; } + case DatabaseServiceType.Doris: { + schema = dorisConnection; + + break; + } case DatabaseServiceType.Druid: { schema = druidConnection; break; } + case DatabaseServiceType.DynamoDB: { schema = dynamoDBConnection; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts index 9440357ad08..2e1be04270f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts @@ -33,6 +33,7 @@ import { DEFAULT_SERVICE, DELTALAKE, DOMO, + DORIS, DRUID, DYNAMODB, ELASTIC_SEARCH, @@ -201,6 +202,9 @@ class ServiceUtilClassBase { case DatabaseServiceType.Db2: return IBMDB2; + case DatabaseServiceType.Doris: + return DORIS; + case DatabaseServiceType.Druid: return DRUID;