{"id":3198,"date":"2026-04-21T19:42:49","date_gmt":"2026-04-21T11:42:49","guid":{"rendered":"https:\/\/www.dpriver.com\/blog\/?p=3198"},"modified":"2026-04-21T20:10:35","modified_gmt":"2026-04-21T12:10:35","slug":"openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how","status":"publish","type":"post","link":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/","title":{"rendered":"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It"},"content":{"rendered":"\n<p><strong>Author:<\/strong> James <strong>Date:<\/strong> 2026-04-21 <strong>Categories:<\/strong> gsp, SQLFlow, OpenMetadata <strong>Keywords:<\/strong> column-lineage, data-governance, openmetadata, mssql, stored-procedure, sql-server, gsp-openmetadata-sidecar, temp-tables, merge<\/p>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<p>OpenMetadata issues <a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/16737\">#16737<\/a>, <a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/25299\">#25299<\/a>, and <a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/17586\">#17586<\/a> all report the same frustrating problem: <strong>MSSQL stored procedures produce zero lineage in OpenMetadata<\/strong>. No tables. No columns. The lineage graph just stops at the procedure boundary.<\/p>\n\n\n\n<p>These issues have been open since June 2024 \u2014 over two years \u2014 with users confirming the same behavior across OpenMetadata 1.4.x through 1.11.x. We analyzed the real SQL from these issues, identified three distinct failure patterns, and built a sidecar tool that solves all of them. Here&#8217;s what&#8217;s happening and how to fix it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why OpenMetadata Can&#8217;t Parse Stored Procedures<\/h2>\n\n\n\n<p>OpenMetadata extracts lineage by reading SQL query logs from your database, then passing each statement through a three-parser chain: <strong>sqlglot \u2192 sqlfluff \u2192 sqlparse<\/strong> (via the <code>collate-sqllineage<\/code> library). If all three parsers fail, the statement is <strong>silently skipped<\/strong> \u2014 no error in the UI, just empty lineage.<\/p>\n\n\n\n<p>The problem is that MSSQL stored procedures aren&#8217;t plain SQL statements. They&#8217;re wrapped in T-SQL procedural syntax that none of the three parsers can handle:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><code>CREATE PROCEDURE ... AS BEGIN ... END<\/code> \u2014 the procedure declaration wrapper<\/li>\n\n<li><code>DECLARE<\/code> \u2014 variable declarations<\/li>\n\n<li><code>#tempTable<\/code> \u2014 temporary tables used as intermediate staging<\/li>\n\n<li><code>MERGE ... WHEN MATCHED ... WHEN NOT MATCHED<\/code> \u2014 upsert patterns<\/li>\n\n<li><code>[dbo].[table_name]<\/code> \u2014 square bracket identifiers<\/li>\n<\/ul>\n\n\n\n<p>These are <strong>standard patterns in every MSSQL environment<\/strong> \u2014 not edge cases. If your data warehouse uses stored procedures for ETL (and most MSSQL shops do), OpenMetadata is missing your lineage.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Three Failure Patterns, All Solved<\/h2>\n\n\n\n<p>The issues in #16737, #25299, and #17586 document three distinct failure modes. Our <a href=\"https:\/\/github.com\/gudusoftware\/gsp-openmetadata-sidecar\">gsp-openmetadata-sidecar<\/a> tool handles all of them:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table style=\"border-collapse:collapse;width:100%\">\n<thead><tr><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">Pattern<\/th><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">Issues<\/th><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">What Happens<\/th><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">With GSP<\/th><\/tr><\/thead>\n<tbody>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd\"><strong>Pattern A<\/strong> \u2014 BEGIN\/END blocks<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\">#16737, #25299, #17586<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><code>CREATE PROCEDURE ... BEGIN ... END<\/code> breaks all three parsers. Lineage silently dropped.<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><strong>Full lineage recovered<\/strong> \u2014 procedure wrapper parsed correctly<\/td><\/tr>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><strong>Pattern B<\/strong> \u2014 Temp table chains<\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\">#25299<\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><code>source \u2192 #tempTable \u2192 target<\/code> 3-hop lineage. Parser can&#8217;t follow data through temp tables.<\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><strong>Multi-hop lineage recovered<\/strong> \u2014 temp tables resolved as intermediates<\/td><\/tr>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd\"><strong>Pattern C<\/strong> \u2014 Square brackets<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\">#16424<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><code>[database].[schema].[table]<\/code> identifiers break regex-based bracket handling<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><strong>Bracket identifiers resolved<\/strong> \u2014 cross-database FQNs extracted correctly<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pattern A: CREATE PROCEDURE with BEGIN\/END (Issues #16737, #25299, #17586)<\/h2>\n\n\n\n<p>This is the most common failure. Here&#8217;s the exact SQL from <a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/17586\">issue #17586<\/a> \u2014 a minimal stored procedure that OpenMetadata cannot parse:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE PROCEDURE myproc\nAS\nBEGIN\n    INSERT INTO test2 SELECT * FROM test1\nEND<\/code><\/pre>\n\n\n\n<p><strong>What OpenMetadata sees:<\/strong> The ingestion pipeline reads this from <code>sys.dm_exec_cached_plans<\/code>. The SQL text starts with <code>CREATE PROCEDURE<\/code> \u2014 and historically, OpenMetadata&#8217;s query log reader <a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/17586\">explicitly filtered these out<\/a> with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>AND lower(t.text) NOT LIKE '%%create%%procedure%%'<\/code><\/pre>\n\n\n\n<p>That filter was removed in <a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/pull\/14586\">PR #14586<\/a>, but even after removal, the parser chain (sqlglot \u2192 sqlfluff \u2192 sqlparse) still cannot handle the <code>BEGIN...END<\/code> wrapper. As one user in <a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/16737\">#16737<\/a> confirmed: <em>&#8220;commenting commands like <code>CREATE PROCEDURE<\/code>, <code>BEGIN<\/code>, <code>END<\/code>, <code>DECLARE<\/code>, <code>AS<\/code> gives correct lineage&#8221;<\/em> \u2014 proving the SQL inside is valid, but the parser rejects the T-SQL wrapper.<\/p>\n\n\n\n<p><strong>What GSP extracts:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ gsp-openmetadata-sidecar --sql-file om_issue_17586_procedure_filtered.sql --dry-run\n\nAnalyzing SQL (544 chars)...\nExtracted 1 table-level lineage relationships\n  test1 --&gt; test2 (1 column: *)\n\n[DRY RUN] Would emit lineage: mssql.dbo.test1 --&gt; mssql.dbo.test2<\/code><\/pre>\n\n\n\n<p>GSP&#8217;s T-SQL parser strips the <code>CREATE PROCEDURE ... AS BEGIN ... END<\/code> wrapper, recognizes the <code>INSERT INTO ... SELECT<\/code> inside, and extracts the lineage: <strong>test1 \u2192 test2<\/strong>.<\/p>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pattern B: Temp Table Multi-Hop Chains (Issue #25299)<\/h2>\n\n\n\n<p>This is the most technically interesting failure. Here&#8217;s the exact SQL from <a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/25299\">issue #25299<\/a> \u2014 a stored procedure that stages data through a temp table:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE PROCEDURE schName.procName\nAS\nBEGIN\n    DROP TABLE IF EXISTS #tempTable\n\n    CREATE TABLE #tempTable (columnName int)\n\n    INSERT INTO #tempTable (columnName)\n    SELECT columnName FROM schName.sourceTable\n\n    INSERT INTO schName.targetTable (columnName)\n    SELECT columnName FROM #tempTable\nEND<\/code><\/pre>\n\n\n\n<p><strong>The data flow is:<\/strong> <code>schName.sourceTable<\/code> \u2192 <code>#tempTable<\/code> \u2192 <code>schName.targetTable<\/code><\/p>\n\n\n\n<p>The reporter in #25299 identified this as a 3-hop lineage problem: <em>&#8220;Procedures using temp tables (<code>#tempTable<\/code>) in the transformation chain: <code>source \u2192 #temp \u2192 target<\/code> \u2014 only two-hop lineage works, not three-hop through temp tables.&#8221;<\/em><\/p>\n\n\n\n<p><strong>What GSP extracts:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ gsp-openmetadata-sidecar --sql-file om_issue_25299_create_procedure_begin_end.sql --dry-run\n\nAnalyzing SQL (1007 chars)...\nExtracted 2 table-level lineage relationships\n  schName.sourceTable --&gt; #tempTable (1 column: columnName)\n  #tempTable --&gt; schName.targetTable (1 column: columnName)\n\n[DRY RUN] Would emit lineage:\n  mssql.schname.sourcetable --&gt; mssql.dbo.#temptable (columnName \u2192 columnName)\n  mssql.dbo.#temptable --&gt; mssql.schname.targettable (columnName \u2192 columnName)<\/code><\/pre>\n\n\n\n<p>GSP extracts the complete chain: <strong>sourceTable \u2192 #tempTable \u2192 targetTable<\/strong> with column-level lineage at each hop. OpenMetadata&#8217;s lineage graph would show the full data flow through the temp table intermediate.<\/p>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pattern C: Square Bracket Cross-Database Identifiers (Issue #16424)<\/h2>\n\n\n\n<p>MSSQL uses square brackets for identifier quoting, and cross-database queries use 3-part or 4-part names. <a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/16424\">Issue #16424<\/a> reported that views using <code>[database].[schema].[table]<\/code> syntax produce no lineage.<\/p>\n\n\n\n<p>The root cause was a greedy regex <code>r\"\\[(.*)\\]\"<\/code> in OpenMetadata&#8217;s <code>parser.py<\/code> that matched across multiple bracket pairs, returning <code>\"db].[schema\"<\/code> instead of separate identifiers. Here&#8217;s a reconstructed example based on the issue description:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE VIEW [ReportDB].[dbo].[vw_CustomerOrders]\nAS\nSELECT\n    [SalesDB].[dbo].[Customers].[CustomerID],\n    [SalesDB].[dbo].[Customers].[CustomerName],\n    [SalesDB].[dbo].[Orders].[OrderID],\n    [SalesDB].[dbo].[Orders].[OrderDate],\n    [SalesDB].[dbo].[Orders].[TotalAmount]\nFROM [SalesDB].[dbo].[Customers]\nINNER JOIN [SalesDB].[dbo].[Orders]\n    ON [SalesDB].[dbo].[Customers].[CustomerID] = [SalesDB].[dbo].[Orders].[CustomerID]\nWHERE [SalesDB].[dbo].[Orders].[OrderDate] &gt;= '2024-01-01'<\/code><\/pre>\n\n\n\n<p><strong>What GSP extracts:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ gsp-openmetadata-sidecar --sql-file om_issue_16424_square_brackets.sql --dry-run\n\nAnalyzing SQL (984 chars)...\nExtracted 2 table-level lineage relationships\n  SalesDB.dbo.Customers --&gt; ReportDB.dbo.vw_CustomerOrders (2 columns)\n  SalesDB.dbo.Orders --&gt; ReportDB.dbo.vw_CustomerOrders (3 columns)\n\n[DRY RUN] Would emit lineage:\n  mssql.salesdb.dbo.customers --&gt; mssql.reportdb.dbo.vw_customerorders\n    CustomerID \u2192 CustomerID\n    CustomerName \u2192 CustomerName\n  mssql.salesdb.dbo.orders --&gt; mssql.reportdb.dbo.vw_customerorders\n    OrderID \u2192 OrderID\n    OrderDate \u2192 OrderDate\n    TotalAmount \u2192 TotalAmount<\/code><\/pre>\n\n\n\n<p>GSP correctly handles the square brackets, resolves cross-database references (<code>SalesDB<\/code> \u2192 <code>ReportDB<\/code>), and extracts <strong>5 column-level lineage mappings<\/strong> across 2 source tables.<\/p>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A Real-World Example: Everything Combined<\/h2>\n\n\n\n<p>Let&#8217;s look at a more realistic stored procedure that combines all three patterns \u2014 the kind of ETL logic you&#8217;d find in any production MSSQL warehouse:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE PROCEDURE [dbo].[usp_UpdateCustomerOrders]\nAS\nBEGIN\n    SET NOCOUNT ON;\n\n    SELECT\n        c.customer_id, c.customer_name,\n        o.order_id, o.order_date, o.total_amount\n    INTO #staged_orders\n    FROM [dbo].[customers] c\n    INNER JOIN [dbo].[orders] o ON c.customer_id = o.customer_id\n    WHERE o.order_date &gt;= DATEADD(day, -30, GETDATE());\n\n    MERGE [dbo].[customer_order_summary] AS target\n    USING #staged_orders AS source\n    ON target.customer_id = source.customer_id\n    WHEN MATCHED THEN\n        UPDATE SET\n            target.last_order_date = source.order_date,\n            target.total_amount = source.total_amount,\n            target.customer_name = source.customer_name\n    WHEN NOT MATCHED THEN\n        INSERT (customer_id, customer_name, last_order_date, total_amount)\n        VALUES (source.customer_id, source.customer_name,\n                source.order_date, source.total_amount);\n\n    INSERT INTO [dbo].[audit_log] (action, record_count, run_date)\n    SELECT 'usp_UpdateCustomerOrders', COUNT(*), GETDATE()\n    FROM #staged_orders;\n\n    DROP TABLE #staged_orders;\nEND<\/code><\/pre>\n\n\n\n<p>This procedure has everything OpenMetadata can&#8217;t handle: <code>CREATE PROCEDURE<\/code>, <code>BEGIN\/END<\/code>, a temp table (<code>#staged_orders<\/code>), a <code>MERGE<\/code> statement, and square bracket identifiers.<\/p>\n\n\n\n<p><strong>What GSP extracts \u2014 4 lineage edges, 12 column-level mappings:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ gsp-openmetadata-sidecar --sql-file mssql_stored_procedure.sql --dry-run\n\nExtracted 4 table-level lineage relationships:\n  dbo.customers --&gt; #staged_orders (2 columns: customer_id, customer_name)\n  dbo.orders --&gt; #staged_orders (3 columns: order_id, order_date, total_amount)\n  #staged_orders --&gt; dbo.customer_order_summary (4 columns)\n  #staged_orders --&gt; dbo.audit_log (3 columns)<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-table\"><table style=\"border-collapse:collapse;width:100%\">\n<thead><tr><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">Source Table<\/th><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">Target Table<\/th><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">Column Mappings<\/th><\/tr><\/thead>\n<tbody>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd\"><code>dbo.customers<\/code><\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><code>#staged_orders<\/code><\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><code>customer_id<\/code> \u2192 <code>customer_id<\/code>, <code>customer_name<\/code> \u2192 <code>customer_name<\/code><\/td><\/tr>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><code>dbo.orders<\/code><\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><code>#staged_orders<\/code><\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><code>order_id<\/code> \u2192 <code>order_id<\/code>, <code>order_date<\/code> \u2192 <code>order_date<\/code>, <code>total_amount<\/code> \u2192 <code>total_amount<\/code><\/td><\/tr>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd\"><code>#staged_orders<\/code><\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><code>dbo.customer_order_summary<\/code><\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><code>customer_id<\/code>, <code>customer_name<\/code>, <code>order_date<\/code> \u2192 <code>last_order_date<\/code>, <code>total_amount<\/code><\/td><\/tr>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><code>#staged_orders<\/code><\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><code>dbo.audit_log<\/code><\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><code>COUNT(*)<\/code> \u2192 <code>record_count<\/code>, <code>GETDATE()<\/code> \u2192 <code>run_date<\/code>, literal \u2192 <code>action<\/code><\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p>The full data flow through the temp table staging layer is preserved, including the MERGE upsert pattern and the audit log insert.<\/p>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary: All Patterns Solved<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table style=\"border-collapse:collapse;width:100%\">\n<thead><tr><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">Issue<\/th><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">Pattern<\/th><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">OpenMetadata Today<\/th><th style=\"padding:8px 12px;border:1px solid #ddd;background:#f0f4f8;font-weight:bold\">With gsp-openmetadata-sidecar<\/th><\/tr><\/thead>\n<tbody>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd\"><a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/17586\">#17586<\/a><\/td><td style=\"padding:8px 12px;border:1px solid #ddd\">A \u2014 BEGIN\/END wrapper<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\">0 lineage (silently skipped)<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><strong>1 table-level, 1 column mapping<\/strong><\/td><\/tr>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/25299\">#25299<\/a><\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\">B \u2014 Temp table chain<\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\">0 lineage (3-hop fails)<\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><strong>2 table-level, 2 column mappings<\/strong><\/td><\/tr>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd\"><a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/16424\">#16424<\/a><\/td><td style=\"padding:8px 12px;border:1px solid #ddd\">C \u2014 Square brackets<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\">0 lineage (regex bug)<\/td><td style=\"padding:8px 12px;border:1px solid #ddd\"><strong>2 table-level, 5 column mappings<\/strong><\/td><\/tr>\n<tr><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\">Combined example<\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\">A + B + C<\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\">0 lineage<\/td><td style=\"padding:8px 12px;border:1px solid #ddd;background:#f9fafb\"><strong>4 table-level, 12 column mappings<\/strong><\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How It Works<\/h2>\n\n\n\n<p>The <a href=\"https:\/\/github.com\/gudusoftware\/gsp-openmetadata-sidecar\">gsp-openmetadata-sidecar<\/a> is a companion tool that runs alongside your OpenMetadata installation. It does not modify OpenMetadata \u2014 it uses the public REST API (<code>PUT \/api\/v1\/lineage<\/code>) to push the lineage that OpenMetadata&#8217;s own parser misses.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>OpenMetadata ingestion (unchanged)       gsp-openmetadata-sidecar\n  query logs \u2192 collate-sqllineage          |\n              |                            | 1. Parse SQL with Gudu SQLFlow\n              v                            | 2. Resolve table FQNs via OM API\n        silently skipped                   | 3. Push lineage via PUT \/api\/v1\/lineage\n        (lineage lost)                     v\n                                     OpenMetadata (lineage restored)<\/code><\/pre>\n\n\n\n<p><strong>What it does:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n\n<li><strong>Parses your SQL<\/strong> using <a href=\"https:\/\/sqlflow.gudusoft.com\">Gudu SQLFlow<\/a> \u2014 a specialized SQL parser that handles procedural T-SQL natively (including <code>BEGIN\/END<\/code>, <code>DECLARE<\/code>, temp tables, <code>MERGE<\/code>, dynamic SQL, cursors, and 20+ dialects)<\/li>\n\n<li><strong>Resolves table names<\/strong> to OpenMetadata entity UUIDs via <code>GET \/api\/v1\/tables\/name\/{fqn}<\/code><\/li>\n\n<li><strong>Pushes lineage edges<\/strong> to OpenMetadata via <code>PUT \/api\/v1\/lineage<\/code> with column-level detail<\/li>\n<\/ul>\n\n\n\n<p><strong>What it supports beyond MSSQL stored procedures:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>BigQuery procedural SQL (<code>DECLARE<\/code>, <code>IF\/THEN<\/code>, <code>CREATE TEMP TABLE<\/code>)<\/li>\n\n<li>Oracle PL\/SQL blocks<\/li>\n\n<li>Snowflake scripting<\/li>\n\n<li><code>MERGE INTO<\/code> column-level lineage (all dialects)<\/li>\n\n<li>Multi-statement SQL scripts<\/li>\n\n<li>Cross-database and cross-schema references<\/li>\n\n<li>20+ SQL dialects total<\/li>\n<\/ul>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Try It<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code># Install\npip install gsp-openmetadata-sidecar\n\n# Test with the exact SQL from issue #25299 (dry run \u2014 no OM connection needed):\ngsp-openmetadata-sidecar \\\n  --sql-file examples\/om_issue_25299_create_procedure_begin_end.sql \\\n  --dry-run\n\n# Test with your own stored procedures:\ngsp-openmetadata-sidecar \\\n  --sql-file your_procedure.sql \\\n  --db-vendor dbvmssql \\\n  --dry-run\n\n# Push lineage to your OpenMetadata instance:\ngsp-openmetadata-sidecar \\\n  --sql-file your_procedure.sql \\\n  --om-server http:\/\/localhost:8585\/api \\\n  --om-token \"eyJ...\" \\\n  --service-name mssql_prod \\\n  --database-name YourDB \\\n  --schema-name dbo<\/code><\/pre>\n\n\n\n<p>The tool supports four backend modes: anonymous (no signup, 50 calls\/day), authenticated (API key, 10k\/month), self-hosted Docker (unlimited, data stays in your network), and local JAR (offline, no network at all).<\/p>\n\n\n\n<p>Source code and documentation: <a href=\"https:\/\/github.com\/gudusoftware\/gsp-openmetadata-sidecar\">github.com\/gudusoftware\/gsp-openmetadata-sidecar<\/a><\/p>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Related OpenMetadata Issues<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/16737\">#16737<\/a> \u2014 Data Lineage Not Reflected for MSSQL Stored Procedure (open since June 2024)<\/li>\n\n<li><a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/25299\">#25299<\/a> \u2014 Stored Procedure lineage is not supported for MS SQL connector (release backlog)<\/li>\n\n<li><a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/17586\">#17586<\/a> \u2014 MS SQL Procedures Lineage Not Picked Up (partially fixed \u2014 filter removed, parser still fails)<\/li>\n\n<li><a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/issues\/16424\">#16424<\/a> \u2014 Square bracket syntax breaks lineage (fixed in sqlfluff, but proc wrapper still fails)<\/li>\n\n<li><a href=\"https:\/\/github.com\/open-metadata\/OpenMetadata\/discussions\/23717\">Discussion #23717<\/a> \u2014 Cross-database MSSQL lineage (unanswered)<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Also Affected by SQL Parsing Gaps?<\/h2>\n\n\n\n<p>If you&#8217;re using <strong>DataHub<\/strong> and hitting similar SQL parsing failures, see our <a href=\"https:\/\/pypi.org\/project\/gsp-datahub-sidecar\/\">gsp-datahub-sidecar<\/a> \u2014 same approach, same engine, built for DataHub&#8217;s ingestion pipeline. It addresses BigQuery procedural SQL (<a href=\"https:\/\/github.com\/datahub-project\/datahub\/issues\/11654\">#11654<\/a>), Power BI M-language lineage (<a href=\"https:\/\/github.com\/datahub-project\/datahub\/issues\/15327\">#15327<\/a>), and MSSQL stored procedures (<a href=\"https:\/\/github.com\/datahub-project\/datahub\/issues\/12606\">#12606<\/a>).<\/p>\n\n\n\n<p>&#8212;<\/p>\n\n\n\n<p><em>Disclosure: I work at <a href=\"https:\/\/www.gudusoft.com\">Gudu Software<\/a>, the company behind GSP SQL Parser, SQLFlow, and the gsp-openmetadata-sidecar. All lineage results shown were produced by running the SQL examples through GSP&#8217;s T-SQL parser and verified against the actual SQL from the referenced OpenMetadata GitHub issues. The example SQL files used in this post are available in the <a href=\"https:\/\/github.com\/gudusoftware\/gsp-openmetadata-sidecar\/tree\/main\/examples\">gsp-openmetadata-sidecar repository<\/a>.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>OpenMetadata issues #16737, #25299, and #17586 report zero lineage from MSSQL stored procedures. We analyze three failure patterns \u2014 BEGIN\/END blocks, temp table chains, and square bracket identifiers \u2014 with real SQL from the community, and show how gsp-openmetadata-sidecar recovers full column-level lineage.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[14,8,93],"tags":[119,135,144,146,141,140,143,142,145],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":5}},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v19.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It<\/title>\n<meta name=\"description\" content=\"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It\" \/>\n<meta property=\"og:description\" content=\"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/\" \/>\n<meta property=\"og:site_name\" content=\"SQL and Data Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-04-21T11:42:49+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-21T12:10:35+00:00\" \/>\n<meta name=\"author\" content=\"James\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"James\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#organization\",\"name\":\"SQL and Data Blog\",\"url\":\"https:\/\/www.dpriver.com\/blog\/\",\"sameAs\":[],\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png\",\"contentUrl\":\"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png\",\"width\":251,\"height\":72,\"caption\":\"SQL and Data Blog\"},\"image\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#website\",\"url\":\"https:\/\/www.dpriver.com\/blog\/\",\"name\":\"SQL and Data Blog\",\"description\":\"SQL related blog for database professional\",\"publisher\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.dpriver.com\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/\",\"url\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/\",\"name\":\"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It\",\"isPartOf\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#website\"},\"datePublished\":\"2026-04-21T11:42:49+00:00\",\"dateModified\":\"2026-04-21T12:10:35+00:00\",\"description\":\"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It\",\"breadcrumb\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.dpriver.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It\"}]},{\"@type\":\"Article\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/\"},\"author\":{\"name\":\"James\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04\"},\"headline\":\"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It\",\"datePublished\":\"2026-04-21T11:42:49+00:00\",\"dateModified\":\"2026-04-21T12:10:35+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/\"},\"wordCount\":1045,\"publisher\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#organization\"},\"keywords\":[\"column-lineage\",\"data-governance\",\"gsp-openmetadata-sidecar\",\"merge\",\"mssql\",\"openmetadata\",\"sql-server\",\"stored-procedure\",\"temp-tables\"],\"articleSection\":[\"gsp\",\"SQL language\",\"SQLFlow\"],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04\",\"name\":\"James\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g\",\"caption\":\"James\"},\"sameAs\":[\"http:\/\/www.dpriver.com\"],\"url\":\"https:\/\/www.dpriver.com\/blog\/author\/james\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It","description":"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/","og_locale":"en_US","og_type":"article","og_title":"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It","og_description":"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It","og_url":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/","og_site_name":"SQL and Data Blog","article_published_time":"2026-04-21T11:42:49+00:00","article_modified_time":"2026-04-21T12:10:35+00:00","author":"James","twitter_card":"summary_large_image","twitter_misc":{"Written by":"James","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Organization","@id":"https:\/\/www.dpriver.com\/blog\/#organization","name":"SQL and Data Blog","url":"https:\/\/www.dpriver.com\/blog\/","sameAs":[],"logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png","contentUrl":"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png","width":251,"height":72,"caption":"SQL and Data Blog"},"image":{"@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"WebSite","@id":"https:\/\/www.dpriver.com\/blog\/#website","url":"https:\/\/www.dpriver.com\/blog\/","name":"SQL and Data Blog","description":"SQL related blog for database professional","publisher":{"@id":"https:\/\/www.dpriver.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dpriver.com\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/","url":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/","name":"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It","isPartOf":{"@id":"https:\/\/www.dpriver.com\/blog\/#website"},"datePublished":"2026-04-21T11:42:49+00:00","dateModified":"2026-04-21T12:10:35+00:00","description":"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It","breadcrumb":{"@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.dpriver.com\/blog\/"},{"@type":"ListItem","position":2,"name":"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It"}]},{"@type":"Article","@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/#article","isPartOf":{"@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/"},"author":{"name":"James","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04"},"headline":"OpenMetadata + MSSQL Stored Procedures: Why Your Lineage Is Silently Empty \u2014 and How to Fix It","datePublished":"2026-04-21T11:42:49+00:00","dateModified":"2026-04-21T12:10:35+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dpriver.com\/blog\/2026\/04\/openmetadata-mssql-stored-procedures-why-your-lineage-is-silently-empty-and-how\/"},"wordCount":1045,"publisher":{"@id":"https:\/\/www.dpriver.com\/blog\/#organization"},"keywords":["column-lineage","data-governance","gsp-openmetadata-sidecar","merge","mssql","openmetadata","sql-server","stored-procedure","temp-tables"],"articleSection":["gsp","SQL language","SQLFlow"],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04","name":"James","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g","caption":"James"},"sameAs":["http:\/\/www.dpriver.com"],"url":"https:\/\/www.dpriver.com\/blog\/author\/james\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts\/3198"}],"collection":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/comments?post=3198"}],"version-history":[{"count":4,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts\/3198\/revisions"}],"predecessor-version":[{"id":3202,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts\/3198\/revisions\/3202"}],"wp:attachment":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/media?parent=3198"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/categories?post=3198"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/tags?post=3198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}