mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 02:29:03 +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
	 harshsoni2024
						harshsoni2024