From 9c790d750325910954f2a6c2405269a98700314c Mon Sep 17 00:00:00 2001 From: cangelosilima Date: Sat, 26 Sep 2026 11:34:35 -0300 Subject: [PATCH] Remove dynamic SQL investigation opt-out --- README.md | 3 +-- cli/src/SyncSql.Catalog/CatalogBuilder.cs | 2 +- cli/src/SyncSql.Cli/Commands/CatalogCommand.cs | 6 ------ cli/src/SyncSql.Core/Abstractions/Catalog.cs | 8 -------- cli/src/SyncSql.Core/Abstractions/Lineage.cs | 12 +----------- .../MsSqlLineageAnalyzer.cs | 2 +- .../TSqlLineageVisitor.cs | 8 ++------ .../OracleLineageAnalyzer.cs | 4 ++-- .../PlSqlLineageVisitor.cs | 4 ++-- .../CatalogBuilderTests.cs | 4 ++-- .../CatalogDynamicSqlOptionTests.cs | 18 ++++++++++++++++++ .../AdvancedSqlLineageTests.cs | 7 ++++--- .../MsSqlLineageAnalyzerTests.cs | 6 +++--- .../AdvancedSqlLineageTests.cs | 12 +++++------- .../DynamicSqlLineageTests.cs | 9 ++------- .../HeterogeneousOracleTests.cs | 2 -- .../ParserMemoryTests.cs | 2 +- .../VisitorBoundaryTests.cs | 4 ++-- 18 files changed, 47 insertions(+), 66 deletions(-) create mode 100644 cli/tests/SyncSql.Cli.Tests/CatalogDynamicSqlOptionTests.cs diff --git a/README.md b/README.md index 70362c8..c46fbee 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,6 @@ syncsql catalog build [--output-root] [--objects-root ] [--output ] [--repo-root] [--path-prefix] [--history-limit] [--max-versions-per-object] [--max-history-content-calls] [--max-co-change-commit-size] [--metrics-root] - [--no-dynamic-sql] syncsql metrics update [--output-root] [--snapshot-root ] [--history-root ] [--history-limit] syncsql lint [--output-root] [--path ...] [--config ] [--fail-on warning|error] @@ -1088,7 +1087,7 @@ Object references retain database links, and qualified column references keep their alias bindings within each dynamic statement. Variables, function-built strings and concatenations containing unknown values are not evaluated. -`syncsql catalog build --no-dynamic-sql` turns the whole thing off. +Dynamic SQL investigation always runs during catalog builds. ### System objects diff --git a/cli/src/SyncSql.Catalog/CatalogBuilder.cs b/cli/src/SyncSql.Catalog/CatalogBuilder.cs index f443ca7..6316dd9 100644 --- a/cli/src/SyncSql.Catalog/CatalogBuilder.cs +++ b/cli/src/SyncSql.Catalog/CatalogBuilder.cs @@ -83,7 +83,7 @@ public sealed class CatalogBuilder( Dictionary nodesById = nodes.ToDictionary(n => n.Id, StringComparer.OrdinalIgnoreCase); logger.LogInformation("Inferring lineage edges and column references"); - LineageInferenceResult lineage = InferLineage(nodes, nodesById, nodeIndex, new LineageAnalysisOptions { DynamicSql = request.DynamicSql }, request.Progress, cancellationToken); + LineageInferenceResult lineage = InferLineage(nodes, nodesById, nodeIndex, LineageAnalysisOptions.Default, request.Progress, cancellationToken); List edges = lineage.Edges; List orphanedReferences = lineage.OrphanedReferences; if (orphanedReferences.Count > 0) diff --git a/cli/src/SyncSql.Cli/Commands/CatalogCommand.cs b/cli/src/SyncSql.Cli/Commands/CatalogCommand.cs index a883756..a84f687 100644 --- a/cli/src/SyncSql.Cli/Commands/CatalogCommand.cs +++ b/cli/src/SyncSql.Cli/Commands/CatalogCommand.cs @@ -57,10 +57,6 @@ public static Command Build(IServiceProvider services) { Description = "Override the metrics history root for all inputs. Default: each input's metrics/ directory when present.", }; - Option noDynamicSqlOption = new("--no-dynamic-sql") - { - Description = "Skip recovering references from SQL built as a string at runtime (OPENQUERY, EXEC of a string, a variable assembled then executed). Those references are tagged `dynamic` in catalog.json rather than mixed in with the rest, so the default is to collect them.", - }; Option pruneOption = new("--prune") { Description = "Remove unreferenced hashed payloads after publishing the manifest. Use only in a dedicated catalog directory." }; @@ -76,7 +72,6 @@ public static Command Build(IServiceProvider services) maxHistoryCallsOption, maxCoChangeOption, metricsRootOption, - noDynamicSqlOption, pruneOption, }; @@ -112,7 +107,6 @@ public static Command Build(IServiceProvider services) MaxHistoryContentCalls = parseResult.GetValue(maxHistoryCallsOption), MaxCoChangeCommitSize = parseResult.GetValue(maxCoChangeOption), MetricsRoot = ToFullPathOrNull(parseResult.GetValue(metricsRootOption)), - DynamicSql = !parseResult.GetValue(noDynamicSqlOption), Progress = progress, }; diff --git a/cli/src/SyncSql.Core/Abstractions/Catalog.cs b/cli/src/SyncSql.Core/Abstractions/Catalog.cs index 61d3699..4560789 100644 --- a/cli/src/SyncSql.Core/Abstractions/Catalog.cs +++ b/cli/src/SyncSql.Core/Abstractions/Catalog.cs @@ -24,14 +24,6 @@ public sealed record CatalogBuildRequest /// Root of the accumulating metrics history tree. Omit to skip - node.metrics is simply left empty. public string? MetricsRoot { get; init; } - /// - /// Whether lineage inference also recovers references from SQL built as a string at runtime - /// (OPENQUERY, EXEC of a string, a variable assembled and then executed). On by default; - /// --no-dynamic-sql turns it off for a build that wants only what the parse tree states - /// outright. See . - /// - public bool DynamicSql { get; init; } = true; - /// Optional synchronous observer for build phases and object-level progress. public IProgress? Progress { get; init; } } diff --git a/cli/src/SyncSql.Core/Abstractions/Lineage.cs b/cli/src/SyncSql.Core/Abstractions/Lineage.cs index 26f9660..a3d3110 100644 --- a/cli/src/SyncSql.Core/Abstractions/Lineage.cs +++ b/cli/src/SyncSql.Core/Abstractions/Lineage.cs @@ -20,20 +20,10 @@ public interface ILineageAnalyzer } /// -/// Knobs for one analysis pass. Kept as a record rather than constructor-injected options so the same -/// analyzer instance can serve a build that wants dynamic-SQL scanning and one that doesn't, and so a test -/// can flip a switch without going through DI. +/// Context for one analysis pass, supplied per object rather than through dependency injection. /// public sealed record LineageAnalysisOptions { - /// - /// Whether to recover references from SQL built as a string at runtime (see - /// ). On by default: a large amount of real T-SQL reaches other - /// objects only through OPENQUERY/EXEC, and leaving that invisible is a bigger error than - /// the occasional over-eager match, which is tagged and never reported as an orphan anyway. - /// - public bool DynamicSql { get; init; } = true; - /// The current SQL database's Broker identity, when supplied by extraction metadata. public Guid? ServiceBrokerGuid { get; init; } diff --git a/cli/src/SyncSql.Lineage.MsSql/MsSqlLineageAnalyzer.cs b/cli/src/SyncSql.Lineage.MsSql/MsSqlLineageAnalyzer.cs index 3547ed9..c32c851 100644 --- a/cli/src/SyncSql.Lineage.MsSql/MsSqlLineageAnalyzer.cs +++ b/cli/src/SyncSql.Lineage.MsSql/MsSqlLineageAnalyzer.cs @@ -56,7 +56,7 @@ public LineageAnalysisResult Analyze(string ddl, LineageAnalysisOptions? options context.SourceObjectId, errors.Count, errors[0].Line, errors[0].Column, errors[0].Message); } - TSqlLineageVisitor visitor = new(context.DynamicSql, context.ServiceBrokerGuid); + TSqlLineageVisitor visitor = new(context.ServiceBrokerGuid); fragment.Accept(visitor); return new LineageAnalysisResult diff --git a/cli/src/SyncSql.Lineage.MsSql/TSqlLineageVisitor.cs b/cli/src/SyncSql.Lineage.MsSql/TSqlLineageVisitor.cs index 3833f0d..f28423a 100644 --- a/cli/src/SyncSql.Lineage.MsSql/TSqlLineageVisitor.cs +++ b/cli/src/SyncSql.Lineage.MsSql/TSqlLineageVisitor.cs @@ -16,9 +16,8 @@ namespace SyncSql.Lineage.MsSql; /// content is looked at, and only because they are the places T-SQL actually executes it: an ordinary /// literal in a SELECT list is still never treated as SQL. /// -/// Whether to scan dynamically-built SQL at all (see ). /// The current database's extracted Broker identity, when available. -internal sealed class TSqlLineageVisitor(bool dynamicSql = true, Guid? serviceBrokerGuid = null) : TSqlFragmentVisitor +internal sealed class TSqlLineageVisitor(Guid? serviceBrokerGuid = null) : TSqlFragmentVisitor { /// One budget per object, shared by every nested scan this walk starts. private readonly DynamicSqlScanner.Budget _budget = new(); @@ -377,10 +376,7 @@ private void ScanParameters(ExecutableEntity entity, string? linkedServer) private void ScanDynamic(string? sql, string? linkedServer) { - if (dynamicSql) - { - DynamicSqlScanner.Scan(sql, linkedServer, 0, _budget, ObjectRefs); - } + DynamicSqlScanner.Scan(sql, linkedServer, 0, _budget, ObjectRefs); } /// diff --git a/cli/src/SyncSql.Lineage.Oracle/OracleLineageAnalyzer.cs b/cli/src/SyncSql.Lineage.Oracle/OracleLineageAnalyzer.cs index 12103fa..1ff849d 100644 --- a/cli/src/SyncSql.Lineage.Oracle/OracleLineageAnalyzer.cs +++ b/cli/src/SyncSql.Lineage.Oracle/OracleLineageAnalyzer.cs @@ -100,10 +100,10 @@ private LineageAnalysisResult AnalyzeCore(string ddl, LineageAnalysisOptions opt options.SourceObjectId, errorListener.Errors.Count, errorListener.Errors[0]); } - PlSqlLineageVisitor visitor = new(options.DynamicSql ? sql => + PlSqlLineageVisitor visitor = new(sql => depth < 4 && sql.Length <= 65536 && budget.Remaining-- > 0 ? AnalyzeCore(sql.EndsWith(';') ? sql : sql + ";", options, depth + 1, budget, cache) - : LineageAnalysisResult.Empty : null); + : LineageAnalysisResult.Empty); visitor.Visit(tree); return new LineageAnalysisResult diff --git a/cli/src/SyncSql.Lineage.Oracle/PlSqlLineageVisitor.cs b/cli/src/SyncSql.Lineage.Oracle/PlSqlLineageVisitor.cs index 1bb6c5f..82a26c7 100644 --- a/cli/src/SyncSql.Lineage.Oracle/PlSqlLineageVisitor.cs +++ b/cli/src/SyncSql.Lineage.Oracle/PlSqlLineageVisitor.cs @@ -12,7 +12,7 @@ namespace SyncSql.Lineage.Oracle; /// VisitChildren(context) - so every override here that still wants its subtree walked must explicitly /// call VisitChildren(context) itself, or traversal silently stops there. /// -internal sealed class PlSqlLineageVisitor(Func? analyzeDynamic = null) : PlSqlParserBaseVisitor +internal sealed class PlSqlLineageVisitor(Func analyzeDynamic) : PlSqlParserBaseVisitor { public List ObjectRefs { get; } = []; public Dictionary Aliases { get; } = new(StringComparer.OrdinalIgnoreCase); @@ -247,7 +247,7 @@ private void BindTableAlias(ObjectRef? objRef, PlSqlParser.Table_aliasContext? t private void AnalyzeDynamicExpression(PlSqlParser.ExpressionContext? expression) { - if (analyzeDynamic is null || EvaluateLiteralExpression(expression) is not { } sql) + if (EvaluateLiteralExpression(expression) is not { } sql) { return; } diff --git a/cli/tests/SyncSql.Catalog.Tests/CatalogBuilderTests.cs b/cli/tests/SyncSql.Catalog.Tests/CatalogBuilderTests.cs index 0ca594e..c564575 100644 --- a/cli/tests/SyncSql.Catalog.Tests/CatalogBuilderTests.cs +++ b/cli/tests/SyncSql.Catalog.Tests/CatalogBuilderTests.cs @@ -269,11 +269,11 @@ public async Task BuildAsync_Passes_each_objects_Broker_context_to_its_analyzer( Directory.CreateDirectory(Path.GetDirectoryName(path)!); File.WriteAllText(path, ExtractedObjectFile.Write(obj)); } - Core.Domain.Catalog catalog = await CreateBuilder().BuildAsync(new CatalogBuildRequest { ObjectsRoot = _objectsRoot, DynamicSql = false }, CancellationToken.None); + Core.Domain.Catalog catalog = await CreateBuilder().BuildAsync(new CatalogBuildRequest { ObjectsRoot = _objectsRoot }, CancellationToken.None); foreach (CatalogNode node in catalog.Nodes) { Assert.Equal(node.Database == "App" ? brokerGuid : (Guid?)null, node.ServiceBrokerGuid); - _mssqlAnalyzer.Received(1).Analyze(node.Ddl, Arg.Is(o => o.ServiceBrokerGuid == node.ServiceBrokerGuid && !o.DynamicSql)); + _mssqlAnalyzer.Received(1).Analyze(node.Ddl, Arg.Is(o => o.ServiceBrokerGuid == node.ServiceBrokerGuid)); } } diff --git a/cli/tests/SyncSql.Cli.Tests/CatalogDynamicSqlOptionTests.cs b/cli/tests/SyncSql.Cli.Tests/CatalogDynamicSqlOptionTests.cs new file mode 100644 index 0000000..4bdb4fa --- /dev/null +++ b/cli/tests/SyncSql.Cli.Tests/CatalogDynamicSqlOptionTests.cs @@ -0,0 +1,18 @@ +using System.CommandLine; +using Microsoft.Extensions.DependencyInjection; +using SyncSql.Cli.Commands; + +namespace SyncSql.Cli.Tests; + +public sealed class CatalogDynamicSqlOptionTests +{ + [Fact] + public void CatalogRejectsRemovedDynamicSqlOptOut() + { + using ServiceProvider services = new ServiceCollection().BuildServiceProvider(); + var parsed = new RootCommand { CatalogCommand.Build(services) } + .Parse(["catalog", "build", "--no-dynamic-sql"]); + + Assert.NotEmpty(parsed.Errors); + } +} diff --git a/cli/tests/SyncSql.Lineage.MsSql.Tests/AdvancedSqlLineageTests.cs b/cli/tests/SyncSql.Lineage.MsSql.Tests/AdvancedSqlLineageTests.cs index e20dfb6..c38db0f 100644 --- a/cli/tests/SyncSql.Lineage.MsSql.Tests/AdvancedSqlLineageTests.cs +++ b/cli/tests/SyncSql.Lineage.MsSql.Tests/AdvancedSqlLineageTests.cs @@ -55,14 +55,15 @@ CREATE PROCEDURE dbo.RunReport @table sysname AS } [Fact] - public void Analyze_DynamicSqlDisabled_PreservesStaticHalfOfMixedBatch() + public void Analyze_MixedBatch_PreservesStaticAndDynamicReferences() { LineageAnalysisResult result = _analyzer.Analyze(""" SELECT o.OrderId FROM dbo.Orders o; EXEC (N'SELECT * FROM archive.Orders'); - """, new LineageAnalysisOptions { DynamicSql = false }); + """); - Assert.Equal(new ObjectRef("dbo", "Orders"), Assert.Single(result.ObjectRefs)); + Assert.Contains(new ObjectRef("dbo", "Orders"), result.ObjectRefs); + Assert.Contains(result.ObjectRefs, r => r is { Schema: "archive", Name: "Orders", Origin: ReferenceOrigin.Dynamic }); } [Fact] diff --git a/cli/tests/SyncSql.Lineage.MsSql.Tests/MsSqlLineageAnalyzerTests.cs b/cli/tests/SyncSql.Lineage.MsSql.Tests/MsSqlLineageAnalyzerTests.cs index 15614b5..a14cb1b 100644 --- a/cli/tests/SyncSql.Lineage.MsSql.Tests/MsSqlLineageAnalyzerTests.cs +++ b/cli/tests/SyncSql.Lineage.MsSql.Tests/MsSqlLineageAnalyzerTests.cs @@ -246,7 +246,7 @@ public void Analyze_StaticReference_IsNotMarkedDynamic() } [Fact] - public void Analyze_WithDynamicSqlDisabled_LeavesBuiltStringsAlone() + public void Analyze_WithExplicitOptions_RecoversBuiltSqlReferences() { LineageAnalysisResult result = _analyzer.Analyze( """ @@ -255,9 +255,9 @@ CREATE PROCEDURE [dbo].[Rebuild] AS EXEC ('SELECT * FROM dbo.Orders'); END """, - new LineageAnalysisOptions { DynamicSql = false }); + new LineageAnalysisOptions()); - Assert.Empty(result.ObjectRefs); + Assert.Contains(result.ObjectRefs, r => r is { Schema: "dbo", Name: "Orders", Origin: ReferenceOrigin.Dynamic }); } /// diff --git a/cli/tests/SyncSql.Lineage.Oracle.Tests/AdvancedSqlLineageTests.cs b/cli/tests/SyncSql.Lineage.Oracle.Tests/AdvancedSqlLineageTests.cs index 25c673f..6d538d3 100644 --- a/cli/tests/SyncSql.Lineage.Oracle.Tests/AdvancedSqlLineageTests.cs +++ b/cli/tests/SyncSql.Lineage.Oracle.Tests/AdvancedSqlLineageTests.cs @@ -171,10 +171,8 @@ SELECT JSON_VALUE(e.payload, '$.customer.id') AS customer_id Assert.DoesNotContain(result.ObjectRefs, r => r.Schema == "customer" || r.Name == "id"); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Analyze_ExecuteImmediate_RespectsDynamicSqlOption(bool dynamicSql) + [Fact] + public void Analyze_ExecuteImmediate_PreservesStaticAndDynamicReferences() { LineageAnalysisResult result = _analyzer.Analyze(""" CREATE OR REPLACE PROCEDURE app.refresh_report(p_id NUMBER) AS @@ -182,10 +180,10 @@ CREATE OR REPLACE PROCEDURE app.refresh_report(p_id NUMBER) AS INSERT INTO app.audit_log (id) VALUES (p_id); EXECUTE IMMEDIATE 'DELETE FROM archive.orders WHERE order_id = :id' USING p_id; END; - """, new LineageAnalysisOptions { DynamicSql = dynamicSql }); + """); Assert.Contains(new ObjectRef("app", "audit_log"), result.ObjectRefs); - Assert.Equal(dynamicSql, result.ObjectRefs.Any(r => - r is { Schema: "archive", Name: "orders", Origin: ReferenceOrigin.Dynamic })); + Assert.Contains(result.ObjectRefs, r => + r is { Schema: "archive", Name: "orders", Origin: ReferenceOrigin.Dynamic }); } } diff --git a/cli/tests/SyncSql.Lineage.Oracle.Tests/DynamicSqlLineageTests.cs b/cli/tests/SyncSql.Lineage.Oracle.Tests/DynamicSqlLineageTests.cs index 16207a8..ce847e4 100644 --- a/cli/tests/SyncSql.Lineage.Oracle.Tests/DynamicSqlLineageTests.cs +++ b/cli/tests/SyncSql.Lineage.Oracle.Tests/DynamicSqlLineageTests.cs @@ -61,10 +61,6 @@ public void KnownSql_PreservesObjectsAndColumnBindings(string statement) Assert.Contains(result.ColumnRefs, c => c.Column == "order_id" && result.Aliases.TryGetValue(c.AliasOrTable, out var target) && target is { Schema: "app", Name: "orders", Server: "remote", Origin: ReferenceOrigin.Dynamic }); - - var disabled = _analyzer.Analyze($"BEGIN {statement}; END;", new LineageAnalysisOptions { DynamicSql = false }); - Assert.DoesNotContain(disabled.ObjectRefs, r => r.Origin == ReferenceOrigin.Dynamic); - Assert.DoesNotContain(disabled.ColumnRefs, c => c.Column == "order_id"); } [Fact] @@ -102,10 +98,9 @@ public void UnknownExpressionsAndMessages_DoNotInventDependencies(string stateme } [Fact] - public void StaticOpenFor_RemainsStaticWhenDynamicSqlIsDisabled() + public void StaticOpenFor_RemainsStatic() { - var result = _analyzer.Analyze("BEGIN OPEN result_cursor FOR SELECT o.order_id FROM app.orders o; END;", - new LineageAnalysisOptions { DynamicSql = false }); + var result = _analyzer.Analyze("BEGIN OPEN result_cursor FOR SELECT o.order_id FROM app.orders o; END;"); Assert.Contains(new ObjectRef("app", "orders"), result.ObjectRefs); Assert.Contains(new ColumnRef("o", "order_id"), result.ColumnRefs); } diff --git a/cli/tests/SyncSql.Lineage.Oracle.Tests/HeterogeneousOracleTests.cs b/cli/tests/SyncSql.Lineage.Oracle.Tests/HeterogeneousOracleTests.cs index 6e2b80a..7584c66 100644 --- a/cli/tests/SyncSql.Lineage.Oracle.Tests/HeterogeneousOracleTests.cs +++ b/cli/tests/SyncSql.Lineage.Oracle.Tests/HeterogeneousOracleTests.cs @@ -16,8 +16,6 @@ public void Literal_dynamic_sql_retains_link_and_origin(string literal) string ddl = $"CREATE PROCEDURE PROCUREMENT.P AS N NUMBER; BEGIN EXECUTE IMMEDIATE {literal} INTO N; END;"; Assert.Contains(_analyzer.Analyze(ddl).ObjectRefs, r => r is { Schema: "INVENTORY", Name: "ITEMS", Server: "REMOTE", Origin: ReferenceOrigin.Dynamic }); - Assert.DoesNotContain(_analyzer.Analyze(ddl, new LineageAnalysisOptions { DynamicSql = false }).ObjectRefs, - r => r.Server == "REMOTE"); } [Theory] diff --git a/cli/tests/SyncSql.Lineage.Oracle.Tests/ParserMemoryTests.cs b/cli/tests/SyncSql.Lineage.Oracle.Tests/ParserMemoryTests.cs index 342a386..9e38ac7 100644 --- a/cli/tests/SyncSql.Lineage.Oracle.Tests/ParserMemoryTests.cs +++ b/cli/tests/SyncSql.Lineage.Oracle.Tests/ParserMemoryTests.cs @@ -85,7 +85,7 @@ public void Analyze_PredictionCachesAreCollectibleAfterBoundedReuseOrFailure(boo for (int i = 0; i < OracleLineageAnalyzer.MaxCachedScripts; i++) { - var result = analyzer.Analyze("SELECT o.id FROM app.orders o;", new LineageAnalysisOptions { DynamicSql = false }); + var result = analyzer.Analyze("SELECT o.id FROM app.orders o;"); if (!failParsing) { Assert.Contains(result.ObjectRefs, reference => reference.Name == "orders"); diff --git a/cli/tests/SyncSql.Lineage.Oracle.Tests/VisitorBoundaryTests.cs b/cli/tests/SyncSql.Lineage.Oracle.Tests/VisitorBoundaryTests.cs index 1eba710..7a328ee 100644 --- a/cli/tests/SyncSql.Lineage.Oracle.Tests/VisitorBoundaryTests.cs +++ b/cli/tests/SyncSql.Lineage.Oracle.Tests/VisitorBoundaryTests.cs @@ -18,7 +18,7 @@ private static PlSqlParser Parser(string sql) [Fact] public void Visitor_HandlesIncompleteTableRoutineAndExpressionNodes() { - var visitor = new PlSqlLineageVisitor(); + var visitor = new PlSqlLineageVisitor(sql => new OracleLineageAnalyzer(NullLogger.Instance).Analyze(sql)); visitor.VisitTableview_name(new PlSqlParser.Tableview_nameContext(null, 0)); visitor.VisitRoutine_name(new PlSqlParser.Routine_nameContext(null, 0)); visitor.VisitGeneral_element(new PlSqlParser.General_elementContext(null, 0)); @@ -75,7 +75,7 @@ public void DynamicSql_EnforcesLengthCountAndDepthBudgets() [Fact] public void GeneralElements_HandleFlatAndParenthesizedGrammarAlternatives() { - var visitor = new PlSqlLineageVisitor(); + var visitor = new PlSqlLineageVisitor(sql => new OracleLineageAnalyzer(NullLogger.Instance).Analyze(sql)); var flat = new PlSqlParser.General_elementContext(null, 0); foreach (string part in new[] { "app", "pkg", "run()" }) { flat.AddChild(Parser(part).general_element_part()); } visitor.VisitGeneral_element(flat);