From 502917f0a3b9720507fb4806330c0a60d20a4ff1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 06:39:53 +0000 Subject: [PATCH 1/4] docs: overhaul README and CLI help with comprehensive feature documentation - Rewrite README with full CLI reference table, feature details for column stripping, join stripping, aggressive mode, and join hints (including safety matrix), re-inlining support, output format, and library usage examples with InlinerOptions API - Add Library NuGet installation section and programmatic API examples - Improve CLI --help output: descriptive option text, usage examples, join hint syntax, and a pointer to SqlInliner.Library NuGet package https://claude.ai/code/session_01RtJtCDtKnm8VE7NwoAy2Fe --- README.md | 292 ++++++++++++++++++++++++++++++++------ src/SqlInliner/Program.cs | 41 ++++-- 2 files changed, 276 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 52b6916..41d990a 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,26 @@ [![CI](https://github.com/stevehansen/sql-inliner/actions/workflows/ci.yml/badge.svg)](https://github.com/stevehansen/sql-inliner/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -Helper utility to inline SQL Server database views +A CLI tool and .NET library that optimizes SQL Server views by inlining nested views into a single flattened query. It can strip unused columns and joins to produce a leaner, faster view. -## What is the purpose of this tool -While SQL Server can technically handle the nesting of views, it’s important to understand that this practice can lead to significant performance problems. The core issue is not SQL Server’s inability to process nested views, but rather the loss of oversight by developers as the complexity increases. When multiple views are stacked on top of each other, it becomes easy to overlook the extra, often unnecessary data being retrieved—such as redundant joins or unneeded columns. These inefficiencies accumulate and can significantly impact query performance. -For instance, when a nested view pulls in more data than required, it leads to larger datasets being processed, even if they’re irrelevant to the final query result. This not only consumes more memory and processing power but also results in longer execution times. To ensure optimal performance, it’s critical for developers to regularly review and simplify the logic of views, avoiding unnecessary complexity and ensuring that only the required data is fetched. +## Why use sql-inliner? -> **Always verify the generated code manually before deploying to a production database** +While SQL Server can handle nested views, stacking views on top of each other leads to significant performance problems. As nesting grows, developers lose sight of the extra joins and columns being pulled in. These inefficiencies accumulate: larger intermediate datasets, wasted memory, and longer execution times. + +sql-inliner solves this by: + +- **Flattening** nested view references into a single query (recursively, no matter how deep) +- **Stripping unused columns** so only the data the outer view actually needs is selected +- **Stripping unused joins** so tables that contribute nothing to the result are removed entirely +- **Preserving the original SQL** inside the output so you can always restore or re-inline later + +> **Always verify the generated code manually before deploying to a production database.** ## Installation -Install the application as a [.NET tool](https://learn.microsoft.com/dotnet/core/tools/) using: +### CLI tool + +Install as a [.NET global tool](https://learn.microsoft.com/dotnet/core/tools/): ```bash dotnet tool install --global sqlinliner @@ -23,67 +32,258 @@ dotnet tool install --global sqlinliner This registers the `sqlinliner` command globally so it can be used from any directory. -## Example usage +### Library (NuGet) + +If you want to integrate view inlining into your own application or build pipeline, install the library package instead: + +```bash +dotnet add package SqlInliner.Library +``` + +The library targets `net472`, `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0`. See [Library usage](#library-usage) below for API examples. + +## CLI reference + +``` +sqlinliner [options] +``` + +| Option | Alias | Type | Default | Description | +|---|---|---|---|---| +| `--connection-string` | `-cs` | string | — | Connection string to the SQL Server database | +| `--view-name` | `-vn` | string | — | Fully qualified name of the view to inline (e.g. `dbo.MyView`) | +| `--view-path` | `-vp` | path | — | Path to a `.sql` file containing a `CREATE VIEW` statement | +| `--strip-unused-columns` | `-suc` | bool | `true` | Remove columns from nested views that the outer view does not reference | +| `--strip-unused-joins` | `-suj` | bool | `false` | Remove joins from nested views whose tables contribute no columns to the result | +| `--aggressive-join-stripping` | — | bool | `false` | Exclude join-condition column references from the usage count (can change results for INNER JOINs — see below) | +| `--generate-create-or-alter` | — | bool | `true` | Wrap the output in a `CREATE OR ALTER VIEW` statement | +| `--output-path` | `-op` | path | — | Write the resulting SQL to a file instead of the console | +| `--log-path` | `-lp` | path | — | Write warnings, errors, and timing info to a file | + +At least one of `--view-name` or `--view-path` is required. When both are supplied, `--view-path` provides the main view definition while `--view-name` (with `--connection-string`) is used to fetch any nested views referenced inside it from the database. + +## Examples + +### Inline a view from a database + +```bash +sqlinliner \ + -cs "Server=.;Database=Test;Integrated Security=true" \ + -vn "dbo.VHeavy" \ + --strip-unused-joins +``` + +Fetches the definition of `dbo.VHeavy`, recursively inlines every nested non-indexed view, strips unused columns (on by default) and unused joins. -Using integrated security on a local SQL Server instance: -``sqlinliner -cs "Server=.;Database=Test;Integrated Security=true" -vn "dbo.VHeavy" --strip-unused-joins`` +### SQL Server authentication -Will fetch the definition of the VHeavy view from the Test database and recursively inline each non-indexed view that it detects while stripping unused columns (defaults to true) and unused joins (both inner and outer) for performance reasons. +```bash +sqlinliner \ + -cs "Server=hostname.domain.net;Database=mydb;User=sa;Password='secret'" \ + -vn "dbo.SlowView" \ + --strip-unused-joins +``` + +### Inline a view from a local file + +```bash +sqlinliner -vp "./views/MyView.sql" --strip-unused-joins +``` + +Uses the exact contents of `MyView.sql`. If a connection string is also supplied, any views referenced *within* `MyView.sql` are fetched from the database. + +### Disable the CREATE OR ALTER wrapper + +```bash +sqlinliner -vp "./views/MyView.sql" --generate-create-or-alter false +``` + +Outputs only the inlined `SELECT` statement — useful when embedding the result inside a larger script or when comparing different versions. + +### Keep all joins (only strip columns) + +```bash +sqlinliner \ + -cs "Server=.;Database=Test;Integrated Security=true" \ + -vn "dbo.VHeavy" \ + --strip-unused-joins false +``` + +Expands nested views and removes unused columns but leaves every join in place. Useful when you want the view flattened but prefer to optimize joins yourself. + +### Write output and logs to files + +```bash +sqlinliner \ + -cs "Server=.;Database=Test;Integrated Security=true" \ + -vn "dbo.VHeavy" \ + --strip-unused-joins \ + -op "./output/VHeavy_inlined.sql" \ + -lp "./output/VHeavy.log" +``` -Using SQL Server authentication on a SQL Server in the network: -``sqlinliner -cs "Server=hostname.domain.net;Database=databasename;user=login;password='password example with space'" -vn "dbo.theSlowView" --strip-unused-joins`` +## Feature details -As with the first example, this command will fetch the definition of view theSlowView from the databasename database and recursively inline each non-indexed view that it detects while stripping unused columns (defaults to true) and unused joins (both inner and outer) for performance reasons. -A connection will be made using SQL Server login and password. +### Column stripping -The application will output the new create or alter view statement that can be used on the database to create the improved version of the original view. -The generated statement will include a starting comment containing the original statement (can be used to restore the original code) which is also used when the view is reused in other views to start working from the original statement. -Other included information will be the different views that were used, how many select columns and joins that were stripped. +Enabled by default (`--strip-unused-columns true`). When a nested view selects columns that the outer view never references, those columns are removed from the inlined subquery. This reduces the amount of data SQL Server has to process. -### Using a view definition from a local file -``sqlinliner -vp "./views/MyView.sql" --strip-unused-joins`` +For views that use `UNION`, `EXCEPT`, or `INTERSECT`, columns are removed by position across all branches to keep the query valid. -This scenario inlines the view defined in `MyView.sql`, with unused join stripping enabled (via `--strip-unused-joins`). -When a file path is specified for the main view, the tool uses the exact contents of that file. If a connection -string is also supplied, any views referenced *within* `MyView.sql` are fetched from the database. +### Join stripping -### Disabling the CREATE OR ALTER wrapper -``sqlinliner -vp "./views/MyView.sql" --generate-create-or-alter false`` +Disabled by default; enable with `--strip-unused-joins`. After column stripping, some tables in the nested view may no longer contribute any columns to the result. Join stripping removes those tables entirely, eliminating unnecessary I/O. -Pass `--generate-create-or-alter false` when you only need the inlined `SELECT` -statement. This can be useful when embedding the statement inside a larger -script or when comparing different versions of the SQL. +A join is considered safe to remove when: -### Keeping all joins in the final statement -``sqlinliner -cs "Server=.;Database=Test;Integrated Security=true" -vn "dbo.VHeavy" --strip-unused-joins false`` +- The table contributes zero columns to the outer query (or at most one column that is only used in its own join condition). +- For `LEFT JOIN`: the join is marked `@join:unique` (see [Join hints](#join-hints) below), guaranteeing at most one match per row and no row duplication. +- For `INNER JOIN`: the join is marked both `@join:unique` and `@join:required`, guaranteeing exactly one match per row (no filtering, no duplication). -This variant keeps every join from nested views but will still remove unused -columns. It can be handy when you want the view expanded but prefer to control -join optimization yourself. +Without these hints, the tool cannot be certain that removing a join won't change the result set, so it leaves the join in place. -### Additional options +### Aggressive join stripping -Two optional parameters can be used to control where the generated SQL and debug information are written: +When `--aggressive-join-stripping` is enabled, column references that appear *only* in a table's own `ON` clause are excluded from the usage count. This allows the tool to strip joins where the table is referenced solely in its join condition (e.g. `INNER JOIN b ON a.Id = b.Id AND b.Type = 'X'`). -* `--output-path` (`-op`) – write the resulting SQL to the specified file instead of the console. -* `--log-path` (`-lp`) – write warnings, errors and timing information to the given file. When not provided these details are written to the console. +**Use with care**: for `INNER JOIN`s, the `ON` clause can act as a filter. Removing such a join may change the result set if rows exist that don't match the condition. + +### Join hints + +Join hints are SQL comments placed on or near a `JOIN` clause that tell sql-inliner about the join's cardinality. They enable safe join removal that would otherwise be skipped. + +**Available hints:** + +| Hint | Meaning | +|---|---| +| `@join:unique` | The join produces at most one matching row per source row (join references a unique/primary key) | +| `@join:required` | Every source row has a matching row in the joined table (FK is `NOT NULL` and referential integrity is enforced) | + +**Syntax** — place hints as comments between the `JOIN` keyword and the `ON` clause: + +```sql +-- A LEFT JOIN that is safe to remove when unused (at most 1 match, all left rows preserved): +LEFT JOIN /* @join:unique */ dbo.Address a ON a.PersonId = p.Id + +-- An INNER JOIN that is safe to remove (exactly 1 match per row, no filtering): +INNER JOIN /* @join:unique @join:required */ dbo.Status s ON s.Id = p.StatusId + +-- Multiple separate comments work too: +LEFT JOIN /* @join:unique */ /* @join:required */ dbo.Lookup l ON l.Id = p.LookupId + +-- Single-line comment syntax is also supported: +LEFT JOIN -- @join:unique + dbo.Address a ON a.PersonId = p.Id +``` + +**Safety matrix:** + +| Join type | Hints | Safe to remove? | Reason | +|---|---|---|---| +| `LEFT JOIN` | `@join:unique` | Yes | At most 1 match; all left-side rows preserved | +| `LEFT JOIN` | `@join:unique @join:required` | Yes | Exactly 1 match; no row loss | +| `INNER JOIN` | `@join:unique @join:required` | Yes | Exactly 1 match per row; no filtering | +| `INNER JOIN` | `@join:unique` (no `@required`) | **No** | May filter out rows without a match | +| Any | `@join:required` (no `@unique`) | **No** | Could fan out (multiple matches per row) | +| `RIGHT JOIN` | Any | **No** | Not currently handled | +| `FULL OUTER JOIN` | Any | **No** | Not currently handled | + +### Re-inlining support + +The generated output embeds the original SQL between `-- BEGIN ORIGINAL SQL VIEW --` and `-- END ORIGINAL SQL VIEW --` markers inside a comment block. When a previously-inlined view is referenced by another view, sql-inliner automatically extracts and uses the original source — so re-inlining always starts from the un-inlined definition rather than compounding transformations. + +### Output format + +The generated SQL includes a metadata comment followed by the inlined view: + +```sql +/* +-- Generated on 1/15/2025 3:42 PM by SQL inliner in 00:00:00.1234567 +-- BEGIN ORIGINAL SQL VIEW -- + +-- END ORIGINAL SQL VIEW -- + +-- Referenced views (3): +[dbo].[VInner1] +[dbo].[VInner2] +[dbo].[VInner3] + +-- Removed: 12 select columns and 4 joins + +-- Warnings (0): + +-- Errors (0): + +*/ +CREATE OR ALTER VIEW [dbo].[VHeavy] AS +SELECT ... +``` ## Verifying the generated code -**Always** verify the SQL by comparing it with the old code. +**Always** compare the inlined view against the original to confirm they return identical results: ```sql -select * from dbo.VHeavy except select * from dbo.VHeavy_v2 -select * from dbo.VHeavy_v2 except select * from dbo.VHeavy +SELECT * FROM dbo.VHeavy EXCEPT SELECT * FROM dbo.VHeavy_v2; +SELECT * FROM dbo.VHeavy_v2 EXCEPT SELECT * FROM dbo.VHeavy; +``` + +Both queries should return zero rows. + +## Library usage + +The `SqlInliner.Library` NuGet package exposes the same inlining engine without the CLI. Use it to integrate view inlining into your own tooling, build pipelines, or automated workflows. + +```csharp +using SqlInliner; + +// Option 1: Use a live database connection +using var sqlConnection = new SqlConnection("Server=.;Database=Test;Integrated Security=true"); +var connection = new DatabaseConnection(sqlConnection); +var viewSql = connection.GetViewDefinition("dbo.VHeavy"); + +var inliner = new DatabaseViewInliner(connection, viewSql, InlinerOptions.Recommended()); + +if (inliner.Errors.Count == 0) +{ + // inliner.Result.Sql — full output with metadata comment + // inliner.Result.ConvertedSql — just the inlined SELECT statement + Console.WriteLine(inliner.Result.Sql); +} + +// Option 2: Use mock view definitions (no database required) +var mockConnection = new DatabaseConnection(); +mockConnection.AddViewDefinition( + DatabaseConnection.ToObjectName("dbo", "VInner"), + "CREATE VIEW dbo.VInner AS SELECT Id, Name FROM dbo.People" +); + +var outerSql = @"CREATE VIEW dbo.VOuter AS + SELECT v.Id FROM dbo.VInner v"; + +var inliner2 = new DatabaseViewInliner(mockConnection, outerSql, new InlinerOptions +{ + StripUnusedColumns = true, + StripUnusedJoins = true, +}); + +Console.WriteLine(inliner2.Sql); ``` -Should return 0 results for both queries. +### InlinerOptions + +| Property | Type | Default | Description | +|---|---|---|---| +| `StripUnusedColumns` | `bool` | `true` | Remove unused columns from nested views | +| `StripUnusedJoins` | `bool` | `false` | Remove unused joins from nested views | +| `AggressiveJoinStripping` | `bool` | `false` | Exclude join-condition references from usage count | + +Use `InlinerOptions.Recommended()` for the suggested defaults (`StripUnusedJoins = true`, everything else at default). ## Security considerations -`sql-inliner` retrieves view definitions by interpolating the provided view name -directly into a SQL statement. If untrusted input is used for the view name, this -query could be exploited for SQL injection. The tool is normally executed by a -trusted user who also specifies the connection string, so the risk is low, but -**only supply view names from trusted sources or sanitize them before running the -tool.** +`sql-inliner` retrieves view definitions by interpolating the provided view name directly into a SQL statement. If untrusted input is used for the view name, this query could be exploited for SQL injection. The tool is normally executed by a trusted user who also specifies the connection string, so the risk is low, but **only supply view names from trusted sources or sanitize them before running the tool.** + +## License + +[MIT](LICENSE) diff --git a/src/SqlInliner/Program.cs b/src/SqlInliner/Program.cs index 580a3f2..72ad1a6 100644 --- a/src/SqlInliner/Program.cs +++ b/src/SqlInliner/Program.cs @@ -11,16 +11,36 @@ internal static class Program { private static int Main(string[] args) { - var connectionStringOption = new Option("--connection-string", "-cs") { Description = "Contains the connection string to connect to the database" }; - var viewNameOption = new Option("--view-name", "-vn") { Description = "The name of the view to inline" }; - var viewPathOption = new Option("--view-path", "-vp") { Description = "The path of the view as a .sql file (including create statement)" }; - var stripUnusedColumnsOption = new Option("--strip-unused-columns", "-suc") { DefaultValueFactory = _ => true }; - var stripUnusedJoinsOption = new Option("--strip-unused-joins", "-suj"); - var aggressiveJoinStrippingOption = new Option("--aggressive-join-stripping") { Description = "Exclude join condition references from usage count when stripping joins (can change results for INNER JOINs)" }; - var generateCreateOrAlterOption = new Option("--generate-create-or-alter") { DefaultValueFactory = _ => true }; - var outputPathOption = new Option("--output-path", "-op") { Description = "Optional path of the file to write the resulting SQL to" }; - var logPathOption = new Option("--log-path", "-lp") { Description = "Optional path of the file to write debug information to" }; - var rootCommand = new RootCommand(ThisAssembly.AppName) + var connectionStringOption = new Option("--connection-string", "-cs") { Description = "Connection string to the SQL Server database" }; + var viewNameOption = new Option("--view-name", "-vn") { Description = "Fully qualified name of the view to inline (e.g. dbo.MyView)" }; + var viewPathOption = new Option("--view-path", "-vp") { Description = "Path to a .sql file containing a CREATE VIEW statement" }; + var stripUnusedColumnsOption = new Option("--strip-unused-columns", "-suc") { DefaultValueFactory = _ => true, Description = "Remove columns from nested views that the outer view does not reference" }; + var stripUnusedJoinsOption = new Option("--strip-unused-joins", "-suj") { Description = "Remove joins whose tables contribute no columns to the result. Use @join:unique / @join:required hints on JOINs to allow safe removal (see README)" }; + var aggressiveJoinStrippingOption = new Option("--aggressive-join-stripping") { Description = "Exclude join-condition column references from the usage count. Allows stripping joins where the table only appears in its own ON clause. Use with care: can change results for INNER JOINs where the ON clause filters rows" }; + var generateCreateOrAlterOption = new Option("--generate-create-or-alter") { DefaultValueFactory = _ => true, Description = "Wrap output in a CREATE OR ALTER VIEW statement" }; + var outputPathOption = new Option("--output-path", "-op") { Description = "Write the resulting SQL to a file instead of the console" }; + var logPathOption = new Option("--log-path", "-lp") { Description = "Write warnings, errors, and timing info to a file" }; + + var description = $""" + {ThisAssembly.AppName} — Optimizes SQL Server views by inlining nested views into a single flattened query. + + At least one of --view-name or --view-path is required. + When both are supplied, --view-path provides the main view while nested views are fetched from the database via --connection-string. + + Examples: + sqlinliner -cs "Server=.;Database=Test;Integrated Security=true" -vn "dbo.VHeavy" --strip-unused-joins + sqlinliner -vp "./views/MyView.sql" --strip-unused-joins + sqlinliner -vp "./views/MyView.sql" --generate-create-or-alter false + + Join hints — annotate JOINs in your SQL to enable safe join removal: + LEFT JOIN /* @join:unique */ dbo.Address a ON a.PersonId = p.Id + INNER JOIN /* @join:unique @join:required */ dbo.Status s ON s.Id = p.StatusId + + For programmatic usage, install the SqlInliner.Library NuGet package: + dotnet add package SqlInliner.Library + """; + + var rootCommand = new RootCommand(description) { connectionStringOption, viewNameOption, @@ -31,7 +51,6 @@ private static int Main(string[] args) generateCreateOrAlterOption, outputPathOption, logPathOption, - // TODO: DatabaseView.parser (hardcoded to TSql150Parser) }; rootCommand.SetAction(parseResult => From 94ba54ef076ff8da44748d226da2a59dce550227 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Feb 2026 06:53:25 +0000 Subject: [PATCH 2/4] docs: add missing Open() call in library usage example The DatabaseConnection constructor immediately queries sys.views, so the connection should be explicitly opened first. https://claude.ai/code/session_01RtJtCDtKnm8VE7NwoAy2Fe --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 41d990a..53ea008 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,7 @@ using SqlInliner; // Option 1: Use a live database connection using var sqlConnection = new SqlConnection("Server=.;Database=Test;Integrated Security=true"); +sqlConnection.Open(); var connection = new DatabaseConnection(sqlConnection); var viewSql = connection.GetViewDefinition("dbo.VHeavy"); From 6b1c2c0efedb9dff3191cb14b3ba99832dc56e2a Mon Sep 17 00:00:00 2001 From: Steve Hansen Date: Wed, 25 Feb 2026 08:54:33 +0100 Subject: [PATCH 3/4] fix: Bump LangVersion to latest to fix build with raw string literals Co-Authored-By: Claude Opus 4.6 --- src/SqlInliner/SqlInliner.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SqlInliner/SqlInliner.csproj b/src/SqlInliner/SqlInliner.csproj index de3ff26..dcff58f 100644 --- a/src/SqlInliner/SqlInliner.csproj +++ b/src/SqlInliner/SqlInliner.csproj @@ -3,7 +3,7 @@ Exe net8.0 - 10.0 + latest enable true sqlinliner From afc81ca84c95a92c5616c6ee4df803e5ab8501d7 Mon Sep 17 00:00:00 2001 From: Steve Hansen Date: Wed, 25 Feb 2026 09:00:27 +0100 Subject: [PATCH 4/4] docs: Improve README clarity for DBAs, developers, and AI agents - Add concrete before/after SQL example showing what inlining does - Add prerequisites section (requires .NET 8+ SDK) - Mention --help and --version in installation section - Document exit code behavior - Replace misleading "keep all joins" example with file+database combo - Fix SQL auth example to not use sa login - Clarify inliner.Sql vs Result.Sql vs Result.ConvertedSql in library docs Co-Authored-By: Claude Opus 4.6 --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 53ea008..eb039e1 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,52 @@ sql-inliner solves this by: - **Stripping unused joins** so tables that contribute nothing to the result are removed entirely - **Preserving the original SQL** inside the output so you can always restore or re-inline later +### Before and after + +Given two nested views where the outer view only uses a subset of columns: + +```sql +-- Inner view: selects many columns and joins several tables +CREATE VIEW dbo.VPerson AS +SELECT p.Id, p.Name, p.Email, a.City, a.Street, a.Zip +FROM dbo.Person p +LEFT JOIN dbo.Address a ON a.PersonId = p.Id + +-- Outer view: only uses Id and Name +CREATE VIEW dbo.VPersonNames AS +SELECT v.Id, v.Name +FROM dbo.VPerson v +``` + +After inlining with `--strip-unused-columns --strip-unused-joins`: + +```sql +CREATE OR ALTER VIEW [dbo].[VPersonNames] AS +SELECT [v].[Id], [v].[Name] +FROM ( + SELECT [p].[Id], [p].[Name] + FROM [dbo].[Person] [p] + -- Address join removed: contributed no columns to the result +) [v] +``` + +The nested view reference is replaced with a subquery, the unused `Email`/`City`/`Street`/`Zip` columns are stripped, and the `Address` join is removed entirely because none of its columns are needed. + > **Always verify the generated code manually before deploying to a production database.** +## Prerequisites + +The CLI tool is distributed as a [.NET global tool](https://learn.microsoft.com/dotnet/core/tools/global-tools) and requires the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (or later) to be installed. You can verify your installation by running `dotnet --version`. + ## Installation ### CLI tool -Install as a [.NET global tool](https://learn.microsoft.com/dotnet/core/tools/): - ```bash dotnet tool install --global sqlinliner ``` -This registers the `sqlinliner` command globally so it can be used from any directory. +This registers the `sqlinliner` command globally so it can be used from any directory. Run `sqlinliner --help` to see all available options, or `sqlinliner --version` to check the installed version. ### Library (NuGet) @@ -62,6 +95,8 @@ sqlinliner [options] At least one of `--view-name` or `--view-path` is required. When both are supplied, `--view-path` provides the main view definition while `--view-name` (with `--connection-string`) is used to fetch any nested views referenced inside it from the database. +The tool writes the inlined SQL to stdout (or to `--output-path`) and always exits with code `0`. Check the `-- Errors` section in the output metadata comment or the `--log-path` file to detect problems. + ## Examples ### Inline a view from a database @@ -79,7 +114,7 @@ Fetches the definition of `dbo.VHeavy`, recursively inlines every nested non-ind ```bash sqlinliner \ - -cs "Server=hostname.domain.net;Database=mydb;User=sa;Password='secret'" \ + -cs "Server=hostname.domain.net;Database=mydb;User=myuser;Password='secret'" \ -vn "dbo.SlowView" \ --strip-unused-joins ``` @@ -100,16 +135,16 @@ sqlinliner -vp "./views/MyView.sql" --generate-create-or-alter false Outputs only the inlined `SELECT` statement — useful when embedding the result inside a larger script or when comparing different versions. -### Keep all joins (only strip columns) +### Combine a local file with database lookups ```bash sqlinliner \ + -vp "./views/VHeavy.sql" \ -cs "Server=.;Database=Test;Integrated Security=true" \ - -vn "dbo.VHeavy" \ - --strip-unused-joins false + --strip-unused-joins ``` -Expands nested views and removes unused columns but leaves every join in place. Useful when you want the view flattened but prefer to optimize joins yourself. +The main view definition comes from `VHeavy.sql`, but any nested views it references (e.g. `dbo.VInner`) are fetched from the database via the connection string. This is useful when iterating on the outer view locally while the inner views live in the database. ### Write output and logs to files @@ -247,8 +282,6 @@ var inliner = new DatabaseViewInliner(connection, viewSql, InlinerOptions.Recomm if (inliner.Errors.Count == 0) { - // inliner.Result.Sql — full output with metadata comment - // inliner.Result.ConvertedSql — just the inlined SELECT statement Console.WriteLine(inliner.Result.Sql); } @@ -268,9 +301,17 @@ var inliner2 = new DatabaseViewInliner(mockConnection, outerSql, new InlinerOpti StripUnusedJoins = true, }); -Console.WriteLine(inliner2.Sql); +Console.WriteLine(inliner2.Result.Sql); ``` +The `DatabaseViewInliner` exposes two ways to get the SQL: + +| Property | Returns | +|---|---| +| `inliner.Sql` | The inlined SQL only (shorthand, same as `Result.Sql` on success or the original SQL on error) | +| `inliner.Result.Sql` | The full output including the metadata comment block with original SQL, referenced views, and strip statistics | +| `inliner.Result.ConvertedSql` | Just the inlined `CREATE VIEW` / `SELECT` statement without the metadata comment | + ### InlinerOptions | Property | Type | Default | Description |