mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-07 16:11:30 +00:00
GEN-1552: Postgres stored procedures support (#18083)
This commit is contained in:
parent
a9a37cfd0b
commit
83bfda5229
@ -13,14 +13,19 @@ Postgres source module
|
|||||||
"""
|
"""
|
||||||
import traceback
|
import traceback
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from typing import Iterable, Optional, Tuple
|
from typing import Dict, Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
from sqlalchemy import String as SqlAlchemyString
|
from sqlalchemy import String as SqlAlchemyString
|
||||||
from sqlalchemy import sql
|
from sqlalchemy import sql
|
||||||
from sqlalchemy.dialects.postgresql.base import PGDialect, ischema_names
|
from sqlalchemy.dialects.postgresql.base import PGDialect, ischema_names
|
||||||
from sqlalchemy.engine import Inspector
|
from sqlalchemy.engine import Inspector
|
||||||
|
|
||||||
|
from metadata.generated.schema.api.data.createStoredProcedure import (
|
||||||
|
CreateStoredProcedureRequest,
|
||||||
|
)
|
||||||
from metadata.generated.schema.entity.data.database import Database
|
from metadata.generated.schema.entity.data.database import Database
|
||||||
|
from metadata.generated.schema.entity.data.databaseSchema import DatabaseSchema
|
||||||
|
from metadata.generated.schema.entity.data.storedProcedure import StoredProcedureCode
|
||||||
from metadata.generated.schema.entity.data.table import (
|
from metadata.generated.schema.entity.data.table import (
|
||||||
PartitionColumnDetails,
|
PartitionColumnDetails,
|
||||||
PartitionIntervalTypes,
|
PartitionIntervalTypes,
|
||||||
@ -36,7 +41,7 @@ from metadata.generated.schema.entity.services.ingestionPipelines.status import
|
|||||||
from metadata.generated.schema.metadataIngestion.workflow import (
|
from metadata.generated.schema.metadataIngestion.workflow import (
|
||||||
Source as WorkflowSource,
|
Source as WorkflowSource,
|
||||||
)
|
)
|
||||||
from metadata.generated.schema.type.basic import FullyQualifiedEntityName
|
from metadata.generated.schema.type.basic import EntityName, FullyQualifiedEntityName
|
||||||
from metadata.ingestion.api.models import Either
|
from metadata.ingestion.api.models import Either
|
||||||
from metadata.ingestion.api.steps import InvalidSourceException
|
from metadata.ingestion.api.steps import InvalidSourceException
|
||||||
from metadata.ingestion.models.ometa_classification import OMetaTagAndClassification
|
from metadata.ingestion.models.ometa_classification import OMetaTagAndClassification
|
||||||
@ -46,10 +51,13 @@ from metadata.ingestion.source.database.common_db_source import (
|
|||||||
CommonDbSourceService,
|
CommonDbSourceService,
|
||||||
TableNameAndType,
|
TableNameAndType,
|
||||||
)
|
)
|
||||||
|
from metadata.ingestion.source.database.mssql.models import STORED_PROC_LANGUAGE_MAP
|
||||||
from metadata.ingestion.source.database.multi_db_source import MultiDBSource
|
from metadata.ingestion.source.database.multi_db_source import MultiDBSource
|
||||||
|
from metadata.ingestion.source.database.postgres.models import PostgresStoredProcedure
|
||||||
from metadata.ingestion.source.database.postgres.queries import (
|
from metadata.ingestion.source.database.postgres.queries import (
|
||||||
POSTGRES_GET_ALL_TABLE_PG_POLICY,
|
POSTGRES_GET_ALL_TABLE_PG_POLICY,
|
||||||
POSTGRES_GET_DB_NAMES,
|
POSTGRES_GET_DB_NAMES,
|
||||||
|
POSTGRES_GET_STORED_PROCEDURES,
|
||||||
POSTGRES_GET_TABLE_NAMES,
|
POSTGRES_GET_TABLE_NAMES,
|
||||||
POSTGRES_PARTITION_DETAILS,
|
POSTGRES_PARTITION_DETAILS,
|
||||||
POSTGRES_SCHEMA_COMMENTS,
|
POSTGRES_SCHEMA_COMMENTS,
|
||||||
@ -63,6 +71,10 @@ from metadata.ingestion.source.database.postgres.utils import (
|
|||||||
get_table_owner,
|
get_table_owner,
|
||||||
get_view_definition,
|
get_view_definition,
|
||||||
)
|
)
|
||||||
|
from metadata.ingestion.source.database.stored_procedures_mixin import (
|
||||||
|
QueryByProcedure,
|
||||||
|
StoredProcedureMixin,
|
||||||
|
)
|
||||||
from metadata.utils import fqn
|
from metadata.utils import fqn
|
||||||
from metadata.utils.filters import filter_by_database
|
from metadata.utils.filters import filter_by_database
|
||||||
from metadata.utils.importer import import_side_effects
|
from metadata.utils.importer import import_side_effects
|
||||||
@ -142,7 +154,7 @@ Inspector.get_table_owner = get_etable_owner
|
|||||||
PGDialect.get_foreign_keys = get_foreign_keys
|
PGDialect.get_foreign_keys = get_foreign_keys
|
||||||
|
|
||||||
|
|
||||||
class PostgresSource(CommonDbSourceService, MultiDBSource):
|
class PostgresSource(CommonDbSourceService, MultiDBSource, StoredProcedureMixin):
|
||||||
"""
|
"""
|
||||||
Implements the necessary methods to extract
|
Implements the necessary methods to extract
|
||||||
Database metadata from Postgres Source
|
Database metadata from Postgres Source
|
||||||
@ -298,3 +310,62 @@ class PostgresSource(CommonDbSourceService, MultiDBSource):
|
|||||||
stackTrace=traceback.format_exc(),
|
stackTrace=traceback.format_exc(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_stored_procedures(self) -> Iterable[PostgresStoredProcedure]:
|
||||||
|
"""List stored procedures"""
|
||||||
|
if self.source_config.includeStoredProcedures:
|
||||||
|
results = self.engine.execute(POSTGRES_GET_STORED_PROCEDURES).all()
|
||||||
|
for row in results:
|
||||||
|
try:
|
||||||
|
stored_procedure = PostgresStoredProcedure.model_validate(
|
||||||
|
dict(row._mapping)
|
||||||
|
)
|
||||||
|
yield stored_procedure
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error()
|
||||||
|
self.status.failed(
|
||||||
|
error=StackTraceError(
|
||||||
|
name=dict(row).get("name", "UNKNOWN"),
|
||||||
|
error=f"Error parsing Stored Procedure payload: {exc}",
|
||||||
|
stackTrace=traceback.format_exc(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def yield_stored_procedure(
|
||||||
|
self, stored_procedure
|
||||||
|
) -> Iterable[Either[CreateStoredProcedureRequest]]:
|
||||||
|
"""Prepare the stored procedure payload"""
|
||||||
|
try:
|
||||||
|
stored_procedure_request = CreateStoredProcedureRequest(
|
||||||
|
name=EntityName(stored_procedure.name),
|
||||||
|
description=None,
|
||||||
|
storedProcedureCode=StoredProcedureCode(
|
||||||
|
language=STORED_PROC_LANGUAGE_MAP.get(stored_procedure.language),
|
||||||
|
code=stored_procedure.definition,
|
||||||
|
),
|
||||||
|
databaseSchema=fqn.build(
|
||||||
|
metadata=self.metadata,
|
||||||
|
entity_type=DatabaseSchema,
|
||||||
|
service_name=self.context.get().database_service,
|
||||||
|
database_name=self.context.get().database,
|
||||||
|
schema_name=self.context.get().database_schema,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
yield Either(right=stored_procedure_request)
|
||||||
|
self.register_record_stored_proc_request(stored_procedure_request)
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
yield Either(
|
||||||
|
left=StackTraceError(
|
||||||
|
name=stored_procedure.name,
|
||||||
|
error=f"Error yielding Stored Procedure [{stored_procedure.name}] due to [{exc}]",
|
||||||
|
stackTrace=traceback.format_exc(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_stored_procedure_queries_dict(self) -> Dict[str, List[QueryByProcedure]]:
|
||||||
|
"""
|
||||||
|
Return the dictionary associating stored procedures to the
|
||||||
|
queries they triggered
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2024 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.
|
||||||
|
"""
|
||||||
|
Postgres models
|
||||||
|
"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class PostgresStoredProcedure(BaseModel):
|
||||||
|
"""Postgres stored procedure list query results"""
|
||||||
|
|
||||||
|
name: str = Field(alias="procedure_name")
|
||||||
|
schema: str = Field(alias="schema_name")
|
||||||
|
definition: str
|
||||||
|
language: Optional[str] = None
|
@ -211,3 +211,99 @@ POSTGRES_FETCH_FK = """
|
|||||||
n.oid = c.relnamespace
|
n.oid = c.relnamespace
|
||||||
ORDER BY 1
|
ORDER BY 1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
POSTGRES_GET_JSON_FIELDS = """
|
||||||
|
WITH RECURSIVE json_hierarchy AS (
|
||||||
|
SELECT
|
||||||
|
key AS path,
|
||||||
|
json_typeof(value) AS type,
|
||||||
|
value,
|
||||||
|
json_build_object() AS properties,
|
||||||
|
key AS title
|
||||||
|
FROM
|
||||||
|
{table_name} tbd,
|
||||||
|
LATERAL json_each({column_name}::json)
|
||||||
|
),
|
||||||
|
build_hierarchy AS (
|
||||||
|
SELECT
|
||||||
|
path,
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
CASE
|
||||||
|
WHEN type = 'object' THEN
|
||||||
|
json_build_object(
|
||||||
|
'title', title,
|
||||||
|
'type', 'object',
|
||||||
|
'properties', (
|
||||||
|
SELECT json_object_agg(
|
||||||
|
key,
|
||||||
|
json_build_object(
|
||||||
|
'title', key,
|
||||||
|
'type', json_typeof(value),
|
||||||
|
'properties', (
|
||||||
|
CASE
|
||||||
|
WHEN json_typeof(value) = 'object' THEN
|
||||||
|
(
|
||||||
|
SELECT json_object_agg(
|
||||||
|
key,
|
||||||
|
json_build_object(
|
||||||
|
'title', key,
|
||||||
|
'type', json_typeof(value),
|
||||||
|
'properties',
|
||||||
|
json_build_object()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
FROM json_each(value::json) AS sub_key_value
|
||||||
|
)
|
||||||
|
ELSE json_build_object()
|
||||||
|
END
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
FROM json_each(value::json) AS key_value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WHEN type = 'array' THEN
|
||||||
|
json_build_object(
|
||||||
|
'title', title,
|
||||||
|
'type', 'array',
|
||||||
|
'properties', json_build_object()
|
||||||
|
)
|
||||||
|
ELSE
|
||||||
|
json_build_object(
|
||||||
|
'title', title,
|
||||||
|
'type', type
|
||||||
|
)
|
||||||
|
END AS hierarchy
|
||||||
|
FROM
|
||||||
|
json_hierarchy
|
||||||
|
),
|
||||||
|
aggregate_hierarchy AS (
|
||||||
|
select
|
||||||
|
json_build_object(
|
||||||
|
'title','{column_name}',
|
||||||
|
'type','object',
|
||||||
|
'properties',
|
||||||
|
json_object_agg(
|
||||||
|
path,
|
||||||
|
hierarchy
|
||||||
|
)) AS result
|
||||||
|
FROM
|
||||||
|
build_hierarchy
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
result
|
||||||
|
FROM
|
||||||
|
aggregate_hierarchy;
|
||||||
|
"""
|
||||||
|
|
||||||
|
POSTGRES_GET_STORED_PROCEDURES = """
|
||||||
|
SELECT proname AS procedure_name,
|
||||||
|
nspname AS schema_name,
|
||||||
|
proargtypes AS argument_types,
|
||||||
|
prorettype::regtype AS return_type,
|
||||||
|
prosrc AS definition
|
||||||
|
FROM pg_proc
|
||||||
|
JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
|
||||||
|
WHERE prokind = 'p';
|
||||||
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user