From 3d31918fb266c99f3b3500b7810099ad37d1eb3c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 08:36:02 +0000 Subject: [PATCH 01/11] Add experimental SQL Server (mssql) driver POC Adds MssqlDriver (Microsoft.Data.SqlClient), the mssql engine mapping, authors examples (driver + Dapper), a SQL Server docker-compose service and end-to-end tests for annotations and macros. Requires a sqlc build with mssql macro support (sqlc.arg/narg/slice/embed). Co-Authored-By: Claude Sonnet 5.5 --- .env | 2 + CodeGenerator/CodeGenerator.cs | 1 + Drivers/DbDriver.cs | 8 +- Drivers/MssqlDriver.cs | 316 ++++++ PluginOptions/DriverName.cs | 6 +- PluginOptions/Options.cs | 3 +- docker-compose.yml | 15 + end2end/EndToEndCommon/EndToEndCommon.cs | 1 + end2end/EndToEndScaffold/Config.cs | 31 + end2end/EndToEndTests/EndToEndTests.csproj | 7 + end2end/EndToEndTests/GlobalSetup.cs | 32 +- end2end/EndToEndTests/MssqlDapperTester.cs | 18 + .../MssqlDapperTester.generated.cs | 373 +++++++ end2end/EndToEndTests/MssqlTester.cs | 18 + .../EndToEndTests/MssqlTester.generated.cs | 373 +++++++ end2end/scripts/generate_tests.sh | 10 + examples/MssqlDapperExample/Models.cs | 17 + .../MssqlDapperExample.csproj | 20 + examples/MssqlDapperExample/QuerySql.cs | 521 ++++++++++ examples/MssqlDapperExample/Utils.cs | 19 + examples/MssqlDapperExample/request.json | 911 ++++++++++++++++++ examples/MssqlDapperExample/request.message | Bin 0 -> 6377 bytes examples/MssqlExample/Models.cs | 6 + examples/MssqlExample/MssqlExample.csproj | 20 + examples/MssqlExample/QuerySql.cs | 642 ++++++++++++ examples/MssqlExample/Utils.cs | 13 + examples/MssqlExample/request.json | 911 ++++++++++++++++++ examples/MssqlExample/request.message | Bin 0 -> 6363 bytes examples/config/mssql/authors/query.sql | 59 ++ examples/config/mssql/authors/schema.sql | 16 + sqlc.ci.yaml | 23 + sqlc.local.generated.yaml | 23 + sqlc.request.generated.yaml | 25 + .../RepositoryTests/ArchitectureTests.cs | 2 +- 34 files changed, 4435 insertions(+), 7 deletions(-) create mode 100644 Drivers/MssqlDriver.cs create mode 100644 end2end/EndToEndTests/MssqlDapperTester.cs create mode 100644 end2end/EndToEndTests/MssqlDapperTester.generated.cs create mode 100644 end2end/EndToEndTests/MssqlTester.cs create mode 100644 end2end/EndToEndTests/MssqlTester.generated.cs create mode 100644 examples/MssqlDapperExample/Models.cs create mode 100644 examples/MssqlDapperExample/MssqlDapperExample.csproj create mode 100644 examples/MssqlDapperExample/QuerySql.cs create mode 100644 examples/MssqlDapperExample/Utils.cs create mode 100644 examples/MssqlDapperExample/request.json create mode 100644 examples/MssqlDapperExample/request.message create mode 100644 examples/MssqlExample/Models.cs create mode 100644 examples/MssqlExample/MssqlExample.csproj create mode 100644 examples/MssqlExample/QuerySql.cs create mode 100644 examples/MssqlExample/Utils.cs create mode 100644 examples/MssqlExample/request.json create mode 100644 examples/MssqlExample/request.message create mode 100644 examples/config/mssql/authors/query.sql create mode 100644 examples/config/mssql/authors/schema.sql diff --git a/.env b/.env index e8c9f7b96..131f98893 100644 --- a/.env +++ b/.env @@ -3,8 +3,10 @@ SOURCE_WASM_FILE="WasmRunner/obj/release/net10.0/wasi-wasm/wasm/for-publish/Wasm SQLITE_CONNECTION_STRING="Data Source=tests.db;Mode=ReadWrite" MYSQL_CONNECTION_STRING="server=localhost;database=tests;user=root;AllowLoadLocalInfile=true;ConvertZeroDateTime=True" +MSSQL_CONNECTION_STRING="Server=localhost,1433;Database=tests;User Id=sa;Password=Passw0rd!Test;TrustServerCertificate=True;Encrypt=False" POSTGRES_CONNECTION_STRING="host=localhost;database=tests;username=postgres;password=pass;IncludeErrorDetail=true" +MSSQL_SA_PASSWORD="Passw0rd!Test" POSTGRES_USER="postgres" POSTGRES_PASSWORD="pass" TESTS_DB="tests" \ No newline at end of file diff --git a/CodeGenerator/CodeGenerator.cs b/CodeGenerator/CodeGenerator.cs index b094a63a0..e2a08076a 100644 --- a/CodeGenerator/CodeGenerator.cs +++ b/CodeGenerator/CodeGenerator.cs @@ -98,6 +98,7 @@ private DbDriver InstantiateDriver() DriverName.MySqlConnector => new MySqlConnectorDriver(Options, Catalog, Queries), DriverName.Npgsql => new NpgsqlDriver(Options, Catalog, Queries), DriverName.Sqlite => new SqliteDriver(Options, Catalog, Queries), + DriverName.Mssql => new MssqlDriver(Options, Catalog, Queries), _ => throw new ArgumentException($"unknown driver: {Options.DriverName}") }; } diff --git a/Drivers/DbDriver.cs b/Drivers/DbDriver.cs index e16611de3..30668a4dd 100644 --- a/Drivers/DbDriver.cs +++ b/Drivers/DbDriver.cs @@ -147,11 +147,15 @@ protected DbDriver( private static Dictionary> ConstructTablesLookup(Catalog catalog) { + // The same schema can be listed more than once (e.g. SQL Server's default schema), so tables are merged per schema return catalog.Schemas .Where(s => !_excludedSchemas.Contains(s.Name)) + .GroupBy(s => s.Name == catalog.DefaultSchema ? string.Empty : s.Name) .ToDictionary( - s => s.Name == catalog.DefaultSchema ? string.Empty : s.Name, - s => s.Tables.ToDictionary(t => t.Rel.Name, t => t) + g => g.Key, + g => g.SelectMany(s => s.Tables) + .GroupBy(t => t.Rel.Name) + .ToDictionary(t => t.Key, t => t.First()) ); } diff --git a/Drivers/MssqlDriver.cs b/Drivers/MssqlDriver.cs new file mode 100644 index 000000000..9ee10ea32 --- /dev/null +++ b/Drivers/MssqlDriver.cs @@ -0,0 +1,316 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Plugin; +using SqlcGenCsharp.Drivers.Generators; +using System.Collections.Generic; +using System.Linq; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace SqlcGenCsharp.Drivers; + +public sealed class MssqlDriver : + DbDriver, IOne, IMany, IExec, IExecRows, IExecLastId, ICopyFrom +{ + private const string DefaultSqlClientVersion = "6.1.1"; + + public MssqlDriver( + Options options, + Catalog catalog, + IList queries) : base(options, catalog, queries) + { + NullableTypes.Add("DateTimeOffset"); + } + + protected override Dictionary ColumnMappings { get; } = + new() + { + /* Integer data types */ + ["bool"] = new( + new() + { + { "bit", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetBoolean({ordinal})", + convertFunc: x => $"Convert.ToBoolean({x})" + ), + ["byte"] = new( + new() + { + { "tinyint", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetByte({ordinal})", + convertFunc: x => $"Convert.ToByte({x})" + ), + ["short"] = new( + new() + { + { "smallint", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetInt16({ordinal})", + convertFunc: x => $"Convert.ToInt16({x})" + ), + ["int"] = new( + new() + { + { "int", new() }, + { "integer", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetInt32({ordinal})", + convertFunc: x => $"Convert.ToInt32({x})" + ), + ["long"] = new( + new() + { + { "bigint", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetInt64({ordinal})", + convertFunc: x => $"Convert.ToInt64({x})" + ), + + /* Floating point data types */ + ["float"] = new( + new() + { + { "real", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFloat({ordinal})" + ), + ["double"] = new( + new() + { + { "float", new() }, + { "double precision", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetDouble({ordinal})" + ), + ["decimal"] = new( + new() + { + { "decimal", new() }, + { "numeric", new() }, + { "money", new() }, + { "smallmoney", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetDecimal({ordinal})" + ), + + /* String data types */ + ["string"] = new( + new() + { + { "char", new() }, + { "nchar", new() }, + { "varchar", new() }, + { "nvarchar", new() }, + { "text", new() }, + { "ntext", new() }, + { "xml", new() }, + { "sysname", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetString({ordinal})" + ), + + /* Binary data types */ + ["byte[]"] = new( + new() + { + { "binary", new() }, + { "varbinary", new() }, + { "image", new() }, + { "rowversion", new() }, + { "timestamp", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFieldValue({ordinal})" + ), + + /* Date and time data types */ + ["DateTime"] = new( + new() + { + { "date", new() }, + { "datetime", new() }, + { "datetime2", new() }, + { "smalldatetime", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetDateTime({ordinal})" + ), + ["DateTimeOffset"] = new( + new() + { + { "datetimeoffset", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFieldValue({ordinal})" + ), + ["TimeSpan"] = new( + new() + { + { "time", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFieldValue({ordinal})" + ), + + /* Other data types */ + ["Guid"] = new( + new() + { + { "uniqueidentifier", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetGuid({ordinal})", + convertFunc: x => $"Guid.Parse({x}.ToString())" + ), + ["object"] = new( + new() + { + { "sql_variant", new() }, + { "any", new() } + }, + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetValue({ordinal})" + ) + }; + + public override string TransactionClassName => "SqlTransaction"; + + public override IDictionary GetPackageReferences() + { + return base + .GetPackageReferences() + .Merge(new Dictionary + { + { "Microsoft.Data.SqlClient", Options.OverrideDriverVersion != string.Empty ? Options.OverrideDriverVersion : DefaultSqlClientVersion } + }); + } + + public override ISet GetUsingDirectivesForQueries() + { + return base + .GetUsingDirectivesForQueries() + .AddRangeExcludeNulls(["Microsoft.Data.SqlClient"]); + } + + public override ISet GetUsingDirectivesForUtils() + { + return base + .GetUsingDirectivesForUtils() + .AddRangeExcludeNulls(["System"]); + } + + public override MemberDeclarationSyntax[] GetMemberDeclarationsForUtils() + { + return [.. base + .GetMemberDeclarationsForUtils() + .AddRangeIf([ + ParseMemberDeclaration(TransformQueryForSliceArgsImpl)! + ], SliceQueryExists())]; + } + + public override ConnectionGenCommands EstablishConnection(Query query) + { + return new( + GetConnectionOrDataSource: new( + $"var {Variable.Connection.AsVarName()} = new SqlConnection({Variable.ConnectionString.AsPropertyName()})", + true + ), + ConnectionOpen: $"await {Variable.Connection.AsVarName()}.OpenAsync({Cancellation.Argument()})" + ); + } + + public override CommandGenCommands CreateSqlCommand(string sqlTextConstant) + { + var commandVar = Variable.Command.AsVarName(); + return new CommandGenCommands( + CommandCreation: new( + $"var {commandVar} = new SqlCommand({sqlTextConstant}, {Variable.Connection.AsVarName()})", + true), + SetCommandText: string.Empty, + PrepareCommand: string.Empty + ); + } + + // sqlc already writes named parameters as @name for this engine + public override string TransformQueryText(Query query) + { + return query.Text; + } + + public MemberDeclarationSyntax OneDeclare(string queryTextConstant, string argInterface, + string returnInterface, Query query) + { + return new OneDeclareGen(this).Generate(queryTextConstant, argInterface, returnInterface, query); + } + + public MemberDeclarationSyntax ExecDeclare(string queryTextConstant, string argInterface, Query query) + { + return new ExecDeclareGen(this).Generate(queryTextConstant, argInterface, query); + } + + public MemberDeclarationSyntax ManyDeclare(string queryTextConstant, string argInterface, + string returnInterface, Query query) + { + return new ManyDeclareGen(this).Generate(queryTextConstant, argInterface, returnInterface, query); + } + + public MemberDeclarationSyntax ExecRowsDeclare(string queryTextConstant, string argInterface, Query query) + { + return new ExecRowsDeclareGen(this).Generate(queryTextConstant, argInterface, query); + } + + public MemberDeclarationSyntax ExecLastIdDeclare(string queryTextConstant, string argInterface, Query query) + { + return new ExecLastIdDeclareGen(this).Generate(queryTextConstant, argInterface, query); + } + + public MemberDeclarationSyntax CopyFromDeclare(string queryTextConstant, string argInterface, Query query) + { + return new CopyFromDeclareGen(this).Generate(queryTextConstant, argInterface, query); + } + + // :copyfrom is implemented with SqlBulkCopy over an in-memory DataTable + public string GetCopyFromImpl(Query query, string queryTextConstant) + { + var connectionCommands = EstablishConnection(query); + var connectionVar = Variable.Connection.AsVarName(); + var rowVar = Variable.Row.AsVarName(); + var tableVar = "dataTable"; + var bulkCopyVar = "bulkCopy"; + + var qualifiedTableName = !string.IsNullOrEmpty(query.InsertIntoTable.Schema) + ? $"[{query.InsertIntoTable.Schema}].[{query.InsertIntoTable.Name}]" + : $"[{query.InsertIntoTable.Name}]"; + + var addColumns = query.Params.Select(p => + { + var csharpType = GetCsharpTypeWithoutNullableSuffix(p.Column, query); + return $"""{tableVar}.Columns.Add("{p.Column.Name}", typeof({csharpType}));"""; + }).JoinByNewLine(); + + var mapColumns = query.Params.Select(p => + $"""{bulkCopyVar}.ColumnMappings.Add("{p.Column.Name}", "{p.Column.Name}");""").JoinByNewLine(); + + var rowValues = query.Params.Select(p => + { + var param = $"{rowVar}.{p.Column.Name.ToPascalCase()}"; + var writerFn = GetWriterFn(p.Column, query); + return writerFn is null ? param : writerFn( + param, + p.Column.Type.Name, + IsColumnNotNull(p.Column, query), + false, + Options.DotnetFramework.IsDotnetLegacy()); + }).JoinByComma(); + + return connectionCommands.GetConnectionOrDataSource.WrapBlock( + $$""" + {{connectionCommands.ConnectionOpen.AppendSemicolonUnlessEmpty()}} + var {{tableVar}} = new DataTable(); + {{addColumns}} + foreach (var {{rowVar}} in args) + {{tableVar}}.Rows.Add({{rowValues}}); + using (var {{bulkCopyVar}} = new SqlBulkCopy({{connectionVar}})) + { + {{bulkCopyVar}}.DestinationTableName = "{{qualifiedTableName}}"; + {{mapColumns}} + await {{bulkCopyVar}}.WriteToServerAsync({{tableVar}}{{Cancellation.TrailingArgument()}}); + } + """ + ); + } +} diff --git a/PluginOptions/DriverName.cs b/PluginOptions/DriverName.cs index a6917ebc2..2a7c1c48d 100644 --- a/PluginOptions/DriverName.cs +++ b/PluginOptions/DriverName.cs @@ -6,7 +6,8 @@ public enum DriverName { MySqlConnector, Npgsql, - Sqlite + Sqlite, + Mssql } @@ -16,7 +17,8 @@ public static class DriverNameExtensions { { DriverName.MySqlConnector, "MySqlConnector" }, { DriverName.Npgsql, "Npgsql" }, - { DriverName.Sqlite, "Microsoft.Data.Sqlite" } + { DriverName.Sqlite, "Microsoft.Data.Sqlite" }, + { DriverName.Mssql, "Microsoft.Data.SqlClient" } }; public static string ToName(this DriverName me) diff --git a/PluginOptions/Options.cs b/PluginOptions/Options.cs index d2a68741e..bfc56ff10 100644 --- a/PluginOptions/Options.cs +++ b/PluginOptions/Options.cs @@ -85,7 +85,8 @@ public Options(GenerateRequest generateRequest) { { "mysql", DriverName.MySqlConnector }, { "postgresql", DriverName.Npgsql }, - { "sqlite", DriverName.Sqlite } + { "sqlite", DriverName.Sqlite }, + { "mssql", DriverName.Mssql } }; private static bool NoOptionsProvided(string optionsText) diff --git a/docker-compose.yml b/docker-compose.yml index 31de993d0..d9a5ddea3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,3 +34,18 @@ services: test: "pg_isready -d tests" timeout: 20s retries: 10 + + mssqldb: + container_name: mssqldb + image: mcr.microsoft.com/mssql/server:2022-latest + restart: always + ports: + - "1433:1433" + environment: + ACCEPT_EULA: "Y" + MSSQL_SA_PASSWORD: ${MSSQL_SA_PASSWORD} + healthcheck: + test: /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P "$${MSSQL_SA_PASSWORD}" -Q "SELECT 1" || exit 1 + interval: 5s + timeout: 10s + retries: 20 diff --git a/end2end/EndToEndCommon/EndToEndCommon.cs b/end2end/EndToEndCommon/EndToEndCommon.cs index eb53dbddd..16f348866 100644 --- a/end2end/EndToEndCommon/EndToEndCommon.cs +++ b/end2end/EndToEndCommon/EndToEndCommon.cs @@ -11,6 +11,7 @@ public static class EndToEndCommon private const string EnvFile = ".env"; public const string PostgresConnectionStringEnv = "POSTGRES_CONNECTION_STRING"; public const string MySqlConnectionStringEnv = "MYSQL_CONNECTION_STRING"; + public const string MssqlConnectionStringEnv = "MSSQL_CONNECTION_STRING"; public const string SqliteConnectionStringEnv = "SQLITE_CONNECTION_STRING"; public static void SetupTestsSqliteDb() => SetupSqliteDb( diff --git a/end2end/EndToEndScaffold/Config.cs b/end2end/EndToEndScaffold/Config.cs index 25cd27272..5b255ff87 100644 --- a/end2end/EndToEndScaffold/Config.cs +++ b/end2end/EndToEndScaffold/Config.cs @@ -189,6 +189,21 @@ internal static class Config KnownTestType.PostgresNetworkCopyFrom ]; + private static readonly SortedSet _mssqlTestTypes = [ + KnownTestType.One, + KnownTestType.Many, + KnownTestType.Exec, + KnownTestType.ExecRows, + KnownTestType.ExecLastId, + KnownTestType.JoinEmbed, + KnownTestType.SelfJoinEmbed, + KnownTestType.PartialEmbed, + KnownTestType.Slice, + KnownTestType.MultipleSlices, + KnownTestType.NargNull, + KnownTestType.NargNotNull + ]; + private static readonly SortedSet _sqliteTestTypes = [ KnownTestType.One, KnownTestType.Many, @@ -261,5 +276,21 @@ internal static class Config TestTypes = _sqliteTestTypes } }, + { + "MssqlTester", new ClassGenConfig + { + TestNamespace = "MssqlExampleGen", + LegacyTestNamespace = "MssqlLegacyExampleGen", + TestTypes = _mssqlTestTypes + } + }, + { + "MssqlDapperTester", new ClassGenConfig + { + TestNamespace = "MssqlDapperExampleGen", + LegacyTestNamespace = "MssqlDapperLegacyExampleGen", + TestTypes = _mssqlTestTypes + } + }, }; } \ No newline at end of file diff --git a/end2end/EndToEndTests/EndToEndTests.csproj b/end2end/EndToEndTests/EndToEndTests.csproj index f23f2e1ab..d9b57bdf2 100644 --- a/end2end/EndToEndTests/EndToEndTests.csproj +++ b/end2end/EndToEndTests/EndToEndTests.csproj @@ -10,6 +10,10 @@ Always + + Always + authors.mssql.schema.sql + Always authors.sqlite.schema.sql @@ -22,6 +26,7 @@ + @@ -30,6 +35,8 @@ + + diff --git a/end2end/EndToEndTests/GlobalSetup.cs b/end2end/EndToEndTests/GlobalSetup.cs index a4f3ddbce..46a78dad3 100644 --- a/end2end/EndToEndTests/GlobalSetup.cs +++ b/end2end/EndToEndTests/GlobalSetup.cs @@ -1,4 +1,7 @@ +using Microsoft.Data.SqlClient; using NUnit.Framework; +using System; +using System.IO; namespace EndToEndTests; @@ -9,6 +12,7 @@ public class GlobalSetup public void SetUp() { EndToEndCommon.SetupTestsSqliteDb(); + SetupMssqlDb(); } [OneTimeTearDown] @@ -16,4 +20,30 @@ public void TearDown() { EndToEndCommon.RemoveExistingSqliteDb(); } -} \ No newline at end of file + + // SQL Server has no init hook in its docker image, so the database and its schema are created here + private static void SetupMssqlDb() + { + var connectionString = Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv); + if (string.IsNullOrWhiteSpace(connectionString)) + throw new InvalidOperationException($"{EndToEndCommon.MssqlConnectionStringEnv} environment variable is not set"); + + var builder = new SqlConnectionStringBuilder(connectionString); + var database = builder.InitialCatalog; + builder.InitialCatalog = "master"; + using (var connection = new SqlConnection(builder.ConnectionString)) + { + connection.Open(); + Execute(connection, $"DROP DATABASE IF EXISTS [{database}]"); + Execute(connection, $"CREATE DATABASE [{database}]"); + connection.ChangeDatabase(database); + Execute(connection, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "authors.mssql.schema.sql"))); + } + } + + private static void Execute(SqlConnection connection, string sql) + { + using (var command = new SqlCommand(sql, connection)) + command.ExecuteNonQuery(); + } +} diff --git a/end2end/EndToEndTests/MssqlDapperTester.cs b/end2end/EndToEndTests/MssqlDapperTester.cs new file mode 100644 index 000000000..388f7bef2 --- /dev/null +++ b/end2end/EndToEndTests/MssqlDapperTester.cs @@ -0,0 +1,18 @@ +using MssqlDapperExampleGen; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace EndToEndTests; + +public partial class MssqlDapperTester +{ + private QuerySql QuerySql { get; } = new( + Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)!); + + [TearDown] + public async Task EmptyTestsTables() + { + await QuerySql.DeleteAllAuthorsAsync(); + } +} diff --git a/end2end/EndToEndTests/MssqlDapperTester.generated.cs b/end2end/EndToEndTests/MssqlDapperTester.generated.cs new file mode 100644 index 000000000..46444cb9b --- /dev/null +++ b/end2end/EndToEndTests/MssqlDapperTester.generated.cs @@ -0,0 +1,373 @@ +using MssqlDapperExampleGen; +using NodaTime; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace EndToEndTests +{ + [TestFixture] + public partial class MssqlDapperTester + { + [Test] + public async Task TestOneAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new QuerySql.GetAuthorRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + var actual = await this.QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorRow x, QuerySql.GetAuthorRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestManyAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.ListAuthorsRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + new QuerySql.ListAuthorsRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + } + }; + var actual = await this.QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs { Limit = 2, Offset = 0 }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAuthorsRow x, QuerySql.ListAuthorsRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestExecAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + await this.QuerySql.DeleteAuthorAsync(new QuerySql.DeleteAuthorArgs { Name = "Bojack Horseman" }); + var actual = await this.QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNull(actual); + } + + [Test] + public async Task TestExecRowsAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var affectedRows = await this.QuerySql.UpdateAuthorsAsync(new QuerySql.UpdateAuthorsArgs { Bio = "Quote that everyone always attribute to Einstein" }); + ClassicAssert.AreEqual(2, affectedRows); + var expected = new List + { + new QuerySql.ListAuthorsRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Quote that everyone always attribute to Einstein" + }, + new QuerySql.ListAuthorsRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "Quote that everyone always attribute to Einstein" + } + }; + var actual = await this.QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs { Limit = 2, Offset = 0 }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAuthorsRow x, QuerySql.ListAuthorsRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestExecLastIdAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var expected = new QuerySql.GetAuthorByIdRow + { + Id = id1, + Name = "Albert Einstein", + Bio = "Quote that everyone always attribute to Einstein" + }; + var actual = await QuerySql.GetAuthorByIdAsync(new QuerySql.GetAuthorByIdArgs { Id = id1 }); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorByIdRow x, QuerySql.GetAuthorByIdRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestSelfJoinEmbedAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var id2 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Only 2 things are infinite, the universe and human stupidity" }); + var expected = new List() + { + new QuerySql.GetDuplicateAuthorsRow + { + Author = new Author + { + Id = id1, + Name = "Albert Einstein", + Bio = "Quote that everyone always attribute to Einstein" + }, + Author2 = new Author + { + Id = id2, + Name = "Albert Einstein", + Bio = "Only 2 things are infinite, the universe and human stupidity" + } + } + }; + var actual = await QuerySql.GetDuplicateAuthorsAsync(); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetDuplicateAuthorsRow x, QuerySql.GetDuplicateAuthorsRow y) + { + Assert.That(x.Author.Id, Is.EqualTo(y.Author.Id)); + Assert.That(x.Author.Name, Is.EqualTo(y.Author.Name)); + Assert.That(x.Author.Bio, Is.EqualTo(y.Author.Bio)); + Assert.That(x.Author2.Id, Is.EqualTo(y.Author2.Id)); + Assert.That(x.Author2.Name, Is.EqualTo(y.Author2.Name)); + Assert.That(x.Author2.Bio, Is.EqualTo(y.Author2.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestJoinEmbedAsync() + { + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var bojackBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { Name = "One Trick Pony", AuthorId = bojackId }); + var drSeussId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var drSeussBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { AuthorId = drSeussId, Name = "How the Grinch Stole Christmas!" }); + var expected = new List() + { + new QuerySql.ListAllAuthorsBooksRow + { + Author = new Author + { + Id = bojackId, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + Book = new Book + { + Id = bojackBookId, + AuthorId = bojackId, + Name = "One Trick Pony" + } + }, + new QuerySql.ListAllAuthorsBooksRow + { + Author = new Author + { + Id = drSeussId, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + }, + Book = new Book + { + Id = drSeussBookId, + AuthorId = drSeussId, + Name = "How the Grinch Stole Christmas!" + } + } + }; + var actual = await QuerySql.ListAllAuthorsBooksAsync(); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAllAuthorsBooksRow x, QuerySql.ListAllAuthorsBooksRow y) + { + Assert.That(x.Author.Id, Is.EqualTo(y.Author.Id)); + Assert.That(x.Author.Name, Is.EqualTo(y.Author.Name)); + Assert.That(x.Author.Bio, Is.EqualTo(y.Author.Bio)); + Assert.That(x.Book.Id, Is.EqualTo(y.Book.Id)); + Assert.That(x.Book.AuthorId, Is.EqualTo(y.Book.AuthorId)); + Assert.That(x.Book.Name, Is.EqualTo(y.Book.Name)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestPartialEmbedAsync() + { + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var bojackBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { Name = "One Trick Pony", AuthorId = bojackId }); + var drSeussId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var drSeussBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { AuthorId = drSeussId, Name = "How the Grinch Stole Christmas!" }); + var expected = new List + { + new QuerySql.GetAuthorsByBookNameRow + { + Id = bojackId, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show", + Book = new Book + { + Id = bojackBookId, + AuthorId = bojackId, + Name = "One Trick Pony" + } + } + }; + var actual = await QuerySql.GetAuthorsByBookNameAsync(new QuerySql.GetAuthorsByBookNameArgs { Name = "One Trick Pony" }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorsByBookNameRow x, QuerySql.GetAuthorsByBookNameRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + Assert.That(x.Book.Id, Is.EqualTo(y.Book.Id)); + Assert.That(x.Book.AuthorId, Is.EqualTo(y.Book.AuthorId)); + Assert.That(x.Book.Name, Is.EqualTo(y.Book.Name)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestSliceAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var actual = await QuerySql.GetAuthorsByIdsAsync(new QuerySql.GetAuthorsByIdsArgs { Ids = new[] { id1, bojackId } }); + ClassicAssert.AreEqual(2, actual.Count); + } + + [Test] + public async Task TestMultipleSlicesAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var actual = await QuerySql.GetAuthorsByIdsAndNamesAsync(new QuerySql.GetAuthorsByIdsAndNamesArgs { Ids = new[] { id1, bojackId }, Names = new[] { "Albert Einstein" } }); + ClassicAssert.AreEqual(1, actual.Count); + } + + [Test] + public async Task TestNargNullAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.GetAuthorByNamePatternRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + new QuerySql.GetAuthorByNamePatternRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + } + }; + var actual = await this.QuerySql.GetAuthorByNamePatternAsync(new QuerySql.GetAuthorByNamePatternArgs()); + AssertSequenceEquals(expected, actual); + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + + void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuthorByNamePatternRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestNargNotNullAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.GetAuthorByNamePatternRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + } + }; + var actual = await this.QuerySql.GetAuthorByNamePatternAsync(new QuerySql.GetAuthorByNamePatternArgs { NamePattern = "Bojack%" }); + AssertSequenceEquals(expected, actual); + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + + void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuthorByNamePatternRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + } +} diff --git a/end2end/EndToEndTests/MssqlTester.cs b/end2end/EndToEndTests/MssqlTester.cs new file mode 100644 index 000000000..0234036b2 --- /dev/null +++ b/end2end/EndToEndTests/MssqlTester.cs @@ -0,0 +1,18 @@ +using MssqlExampleGen; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace EndToEndTests; + +public partial class MssqlTester +{ + private QuerySql QuerySql { get; } = new( + Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)!); + + [TearDown] + public async Task EmptyTestsTables() + { + await QuerySql.DeleteAllAuthorsAsync(); + } +} diff --git a/end2end/EndToEndTests/MssqlTester.generated.cs b/end2end/EndToEndTests/MssqlTester.generated.cs new file mode 100644 index 000000000..027942cd4 --- /dev/null +++ b/end2end/EndToEndTests/MssqlTester.generated.cs @@ -0,0 +1,373 @@ +using MssqlExampleGen; +using NodaTime; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace EndToEndTests +{ + [TestFixture] + public partial class MssqlTester + { + [Test] + public async Task TestOneAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new QuerySql.GetAuthorRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + var actual = await this.QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + AssertSingularEquals(expected, actual.Value); + void AssertSingularEquals(QuerySql.GetAuthorRow x, QuerySql.GetAuthorRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestManyAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.ListAuthorsRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + new QuerySql.ListAuthorsRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + } + }; + var actual = await this.QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs { Limit = 2, Offset = 0 }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAuthorsRow x, QuerySql.ListAuthorsRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestExecAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + await this.QuerySql.DeleteAuthorAsync(new QuerySql.DeleteAuthorArgs { Name = "Bojack Horseman" }); + var actual = await this.QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNull(actual); + } + + [Test] + public async Task TestExecRowsAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var affectedRows = await this.QuerySql.UpdateAuthorsAsync(new QuerySql.UpdateAuthorsArgs { Bio = "Quote that everyone always attribute to Einstein" }); + ClassicAssert.AreEqual(2, affectedRows); + var expected = new List + { + new QuerySql.ListAuthorsRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Quote that everyone always attribute to Einstein" + }, + new QuerySql.ListAuthorsRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "Quote that everyone always attribute to Einstein" + } + }; + var actual = await this.QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs { Limit = 2, Offset = 0 }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAuthorsRow x, QuerySql.ListAuthorsRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestExecLastIdAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var expected = new QuerySql.GetAuthorByIdRow + { + Id = id1, + Name = "Albert Einstein", + Bio = "Quote that everyone always attribute to Einstein" + }; + var actual = await QuerySql.GetAuthorByIdAsync(new QuerySql.GetAuthorByIdArgs { Id = id1 }); + AssertSingularEquals(expected, actual.Value); + void AssertSingularEquals(QuerySql.GetAuthorByIdRow x, QuerySql.GetAuthorByIdRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestSelfJoinEmbedAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var id2 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Only 2 things are infinite, the universe and human stupidity" }); + var expected = new List() + { + new QuerySql.GetDuplicateAuthorsRow + { + Author = new Author + { + Id = id1, + Name = "Albert Einstein", + Bio = "Quote that everyone always attribute to Einstein" + }, + Author2 = new Author + { + Id = id2, + Name = "Albert Einstein", + Bio = "Only 2 things are infinite, the universe and human stupidity" + } + } + }; + var actual = await QuerySql.GetDuplicateAuthorsAsync(); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetDuplicateAuthorsRow x, QuerySql.GetDuplicateAuthorsRow y) + { + Assert.That(x.Author.Value.Id, Is.EqualTo(y.Author.Value.Id)); + Assert.That(x.Author.Value.Name, Is.EqualTo(y.Author.Value.Name)); + Assert.That(x.Author.Value.Bio, Is.EqualTo(y.Author.Value.Bio)); + Assert.That(x.Author2.Value.Id, Is.EqualTo(y.Author2.Value.Id)); + Assert.That(x.Author2.Value.Name, Is.EqualTo(y.Author2.Value.Name)); + Assert.That(x.Author2.Value.Bio, Is.EqualTo(y.Author2.Value.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestJoinEmbedAsync() + { + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var bojackBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { Name = "One Trick Pony", AuthorId = bojackId }); + var drSeussId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var drSeussBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { AuthorId = drSeussId, Name = "How the Grinch Stole Christmas!" }); + var expected = new List() + { + new QuerySql.ListAllAuthorsBooksRow + { + Author = new Author + { + Id = bojackId, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + Book = new Book + { + Id = bojackBookId, + AuthorId = bojackId, + Name = "One Trick Pony" + } + }, + new QuerySql.ListAllAuthorsBooksRow + { + Author = new Author + { + Id = drSeussId, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + }, + Book = new Book + { + Id = drSeussBookId, + AuthorId = drSeussId, + Name = "How the Grinch Stole Christmas!" + } + } + }; + var actual = await QuerySql.ListAllAuthorsBooksAsync(); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAllAuthorsBooksRow x, QuerySql.ListAllAuthorsBooksRow y) + { + Assert.That(x.Author.Value.Id, Is.EqualTo(y.Author.Value.Id)); + Assert.That(x.Author.Value.Name, Is.EqualTo(y.Author.Value.Name)); + Assert.That(x.Author.Value.Bio, Is.EqualTo(y.Author.Value.Bio)); + Assert.That(x.Book.Value.Id, Is.EqualTo(y.Book.Value.Id)); + Assert.That(x.Book.Value.AuthorId, Is.EqualTo(y.Book.Value.AuthorId)); + Assert.That(x.Book.Value.Name, Is.EqualTo(y.Book.Value.Name)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestPartialEmbedAsync() + { + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var bojackBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { Name = "One Trick Pony", AuthorId = bojackId }); + var drSeussId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var drSeussBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { AuthorId = drSeussId, Name = "How the Grinch Stole Christmas!" }); + var expected = new List + { + new QuerySql.GetAuthorsByBookNameRow + { + Id = bojackId, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show", + Book = new Book + { + Id = bojackBookId, + AuthorId = bojackId, + Name = "One Trick Pony" + } + } + }; + var actual = await QuerySql.GetAuthorsByBookNameAsync(new QuerySql.GetAuthorsByBookNameArgs { Name = "One Trick Pony" }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorsByBookNameRow x, QuerySql.GetAuthorsByBookNameRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + Assert.That(x.Book.Value.Id, Is.EqualTo(y.Book.Value.Id)); + Assert.That(x.Book.Value.AuthorId, Is.EqualTo(y.Book.Value.AuthorId)); + Assert.That(x.Book.Value.Name, Is.EqualTo(y.Book.Value.Name)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestSliceAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var actual = await QuerySql.GetAuthorsByIdsAsync(new QuerySql.GetAuthorsByIdsArgs { Ids = new[] { id1, bojackId } }); + ClassicAssert.AreEqual(2, actual.Count); + } + + [Test] + public async Task TestMultipleSlicesAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var actual = await QuerySql.GetAuthorsByIdsAndNamesAsync(new QuerySql.GetAuthorsByIdsAndNamesArgs { Ids = new[] { id1, bojackId }, Names = new[] { "Albert Einstein" } }); + ClassicAssert.AreEqual(1, actual.Count); + } + + [Test] + public async Task TestNargNullAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.GetAuthorByNamePatternRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + new QuerySql.GetAuthorByNamePatternRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + } + }; + var actual = await this.QuerySql.GetAuthorByNamePatternAsync(new QuerySql.GetAuthorByNamePatternArgs()); + AssertSequenceEquals(expected, actual); + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + + void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuthorByNamePatternRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestNargNotNullAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.GetAuthorByNamePatternRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + } + }; + var actual = await this.QuerySql.GetAuthorByNamePatternAsync(new QuerySql.GetAuthorByNamePatternArgs { NamePattern = "Bojack%" }); + AssertSequenceEquals(expected, actual); + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + + void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuthorByNamePatternRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + } +} diff --git a/end2end/scripts/generate_tests.sh b/end2end/scripts/generate_tests.sh index 862548d49..bb92911a9 100755 --- a/end2end/scripts/generate_tests.sh +++ b/end2end/scripts/generate_tests.sh @@ -10,6 +10,12 @@ TARGETS=( "SqliteTester" "SqliteDapperTester" ) + +# SQL Server has no legacy (netstandard) examples yet +NON_LEGACY_TARGETS=( + "MssqlTester" + "MssqlDapperTester" +) generate() { export IS_LEGACY=false @@ -30,4 +36,8 @@ generate_legacy() { for target in "${TARGETS[@]}"; do generate "$target" generate_legacy "$target" +done + +for target in "${NON_LEGACY_TARGETS[@]}"; do + generate "$target" done \ No newline at end of file diff --git a/examples/MssqlDapperExample/Models.cs b/examples/MssqlDapperExample/Models.cs new file mode 100644 index 000000000..28f50080d --- /dev/null +++ b/examples/MssqlDapperExample/Models.cs @@ -0,0 +1,17 @@ +// auto-generated by sqlc - do not edit +using System.Linq; + +namespace MssqlDapperExampleGen; +public class Author +{ + public required long Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } +}; +public class Book +{ + public required long Id { get; init; } + public required string Name { get; init; } + public required long AuthorId { get; init; } + public string? Description { get; init; } +}; \ No newline at end of file diff --git a/examples/MssqlDapperExample/MssqlDapperExample.csproj b/examples/MssqlDapperExample/MssqlDapperExample.csproj new file mode 100644 index 000000000..d866bcf17 --- /dev/null +++ b/examples/MssqlDapperExample/MssqlDapperExample.csproj @@ -0,0 +1,20 @@ + + + + + + net9.0 + MssqlDapperExampleGen + Library + enable + False + + + + + + + + \ No newline at end of file diff --git a/examples/MssqlDapperExample/QuerySql.cs b/examples/MssqlDapperExample/QuerySql.cs new file mode 100644 index 000000000..b2dfe1e16 --- /dev/null +++ b/examples/MssqlDapperExample/QuerySql.cs @@ -0,0 +1,521 @@ +// auto-generated by sqlc - do not edit +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable UseAwaitUsing +// ReSharper disable ConvertToUsingDeclaration +// ReSharper disable NotAccessedPositionalProperty.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +using Dapper; +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading; +using System.Threading.Tasks; + +namespace MssqlDapperExampleGen; +public class QuerySql +{ + public QuerySql() + { + Utils.ConfigureSqlMapper(); + DefaultTypeMap.MatchNamesWithUnderscores = true; + } + + public QuerySql(string connectionString) : this() + { + this.ConnectionString = connectionString; + } + + private QuerySql(SqlTransaction transaction) : this() + { + this.Transaction = transaction; + } + + public static QuerySql WithTransaction(SqlTransaction transaction) + { + return new QuerySql(transaction); + } + + private SqlTransaction? Transaction { get; } + private string? ConnectionString { get; } + + private const string GetAuthorSql = @"SELECT TOP 1 id, name, bio FROM authors + WHERE name = @name;"; + public class GetAuthorRow + { + public required long Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } + }; + public class GetAuthorArgs + { + public required string Name { get; init; } + }; + public async Task GetAuthorAsync(GetAuthorArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name", args.Name); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetAuthorSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorSql, queryParams, transaction: this.Transaction); + } + + private const string ListAuthorsSql = @"SELECT id, name, bio + FROM authors + ORDER BY name + OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;"; + public class ListAuthorsRow + { + public required long Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } + }; + public class ListAuthorsArgs + { + public required int Offset { get; init; } + public required int Limit { get; init; } + }; + public async Task> ListAuthorsAsync(ListAuthorsArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("offset", args.Offset); + queryParams.Add("limit", args.Limit); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync(ListAuthorsSql, queryParams); + return result.AsList(); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return (await this.Transaction.Connection.QueryAsync(ListAuthorsSql, queryParams, transaction: this.Transaction)).AsList(); + } + + private const string CreateAuthorSql = "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);"; + public class CreateAuthorArgs + { + public required long Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } + }; + public async Task CreateAuthorAsync(CreateAuthorArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("id", args.Id); + queryParams.Add("name", args.Name); + queryParams.Add("bio", args.Bio); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(CreateAuthorSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(CreateAuthorSql, queryParams, transaction: this.Transaction); + } + + private const string CreateAuthorReturnIdSql = "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);"; + public class CreateAuthorReturnIdRow + { + public required long Id { get; init; } + }; + public class CreateAuthorReturnIdArgs + { + public required string Name { get; init; } + public string? Bio { get; init; } + }; + public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name", args.Name); + queryParams.Add("bio", args.Bio); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.QuerySingleAsync(CreateAuthorReturnIdSql, queryParams); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QuerySingleAsync(CreateAuthorReturnIdSql, queryParams, transaction: this.Transaction); + } + + private const string GetAuthorByIdSql = @"SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment + WHERE id = @id;"; + public class GetAuthorByIdRow + { + public required long Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } + }; + public class GetAuthorByIdArgs + { + public required long Id { get; init; } + }; + public async Task GetAuthorByIdAsync(GetAuthorByIdArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("id", args.Id); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetAuthorByIdSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorByIdSql, queryParams, transaction: this.Transaction); + } + + private const string GetAuthorByNamePatternSql = @"SELECT id, name, bio FROM authors + WHERE name LIKE COALESCE(@name_pattern, '%');"; + public class GetAuthorByNamePatternRow + { + public required long Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } + }; + public class GetAuthorByNamePatternArgs + { + public object? NamePattern { get; init; } + }; + public async Task> GetAuthorByNamePatternAsync(GetAuthorByNamePatternArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name_pattern", args.NamePattern); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync(GetAuthorByNamePatternSql, queryParams); + return result.AsList(); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return (await this.Transaction.Connection.QueryAsync(GetAuthorByNamePatternSql, queryParams, transaction: this.Transaction)).AsList(); + } + + private const string DeleteAuthorSql = @"DELETE FROM authors + WHERE name = @name;"; + public class DeleteAuthorArgs + { + public required string Name { get; init; } + }; + public async Task DeleteAuthorAsync(DeleteAuthorArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name", args.Name); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(DeleteAuthorSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(DeleteAuthorSql, queryParams, transaction: this.Transaction); + } + + private const string DeleteAllAuthorsSql = "DELETE FROM authors;"; + public async Task DeleteAllAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(DeleteAllAuthorsSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(DeleteAllAuthorsSql, transaction: this.Transaction); + } + + private const string UpdateAuthorsSql = @"UPDATE authors + SET bio = @bio + WHERE bio IS NOT NULL;"; + public class UpdateAuthorsArgs + { + public string? Bio { get; init; } + }; + public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("bio", args.Bio); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.ExecuteAsync(UpdateAuthorsSql, queryParams); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.ExecuteAsync(UpdateAuthorsSql, queryParams, transaction: this.Transaction); + } + + private const string GetAuthorsByIdsSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);"; + public class GetAuthorsByIdsRow + { + public required long Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } + }; + public class GetAuthorsByIdsArgs + { + public required long[] Ids { get; init; } + }; + public async Task> GetAuthorsByIdsAsync(GetAuthorsByIdsArgs args) + { + var transformedSql = GetAuthorsByIdsSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + var queryParams = new Dictionary(); + for (int i = 0; i < args.Ids.Length; i++) + queryParams.Add($"@idsArg{i}", args.Ids[i]); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync(transformedSql, queryParams); + return result.AsList(); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return (await this.Transaction.Connection.QueryAsync(transformedSql, queryParams, transaction: this.Transaction)).AsList(); + } + + private const string GetAuthorsByIdsAndNamesSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);"; + public class GetAuthorsByIdsAndNamesRow + { + public required long Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } + }; + public class GetAuthorsByIdsAndNamesArgs + { + public required long[] Ids { get; init; } + public required string[] Names { get; init; } + }; + public async Task> GetAuthorsByIdsAndNamesAsync(GetAuthorsByIdsAndNamesArgs args) + { + var transformedSql = GetAuthorsByIdsAndNamesSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Names.Length, "names"); + var queryParams = new Dictionary(); + for (int i = 0; i < args.Ids.Length; i++) + queryParams.Add($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + queryParams.Add($"@namesArg{i}", args.Names[i]); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync(transformedSql, queryParams); + return result.AsList(); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return (await this.Transaction.Connection.QueryAsync(transformedSql, queryParams, transaction: this.Transaction)).AsList(); + } + + private const string CreateBookSql = "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);"; + public class CreateBookRow + { + public required long Id { get; init; } + }; + public class CreateBookArgs + { + public required string Name { get; init; } + public required long AuthorId { get; init; } + }; + public async Task CreateBookAsync(CreateBookArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name", args.Name); + queryParams.Add("author_id", args.AuthorId); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.QuerySingleAsync(CreateBookSql, queryParams); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QuerySingleAsync(CreateBookSql, queryParams, transaction: this.Transaction); + } + + private const string ListAllAuthorsBooksSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + ORDER BY authors.name;"; + public class ListAllAuthorsBooksRow + { + public required Author? Author { get; init; } + public required Book? Book { get; init; } + }; + public async Task> ListAllAuthorsBooksAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(ListAllAuthorsBooksSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAllAuthorsBooksSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + + private const string GetDuplicateAuthorsSql = @"SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio + FROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name + WHERE authors1.id < authors2.id;"; + public class GetDuplicateAuthorsRow + { + public required Author? Author { get; init; } + public required Author? Author2 { get; init; } + }; + public async Task> GetDuplicateAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetDuplicateAuthorsSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetDuplicateAuthorsSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + + private const string GetAuthorsByBookNameSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + WHERE books.name = @name;"; + public class GetAuthorsByBookNameRow + { + public required long Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } + public required Book? Book { get; init; } + }; + public class GetAuthorsByBookNameArgs + { + public required string Name { get; init; } + }; + public async Task> GetAuthorsByBookNameAsync(GetAuthorsByBookNameArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection)) + { + command.Parameters.AddWithValue("@name", args.Name); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorsByBookNameSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name", args.Name); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } +} \ No newline at end of file diff --git a/examples/MssqlDapperExample/Utils.cs b/examples/MssqlDapperExample/Utils.cs new file mode 100644 index 000000000..073f573c3 --- /dev/null +++ b/examples/MssqlDapperExample/Utils.cs @@ -0,0 +1,19 @@ +// auto-generated by sqlc - do not edit +using Dapper; +using System; +using System.Data; +using System.Linq; + +namespace MssqlDapperExampleGen; +public static class Utils +{ + public static void ConfigureSqlMapper() + { + } + + public static string TransformQueryForSliceArgs(string originalSql, int sliceSize, string paramName) + { + var paramArgs = Enumerable.Range(0, sliceSize).Select(i => $"@{paramName}Arg{i}").ToList(); + return originalSql.Replace($"/*SLICE:{paramName}*/@{paramName}", string.Join(",", paramArgs)); + } +} \ No newline at end of file diff --git a/examples/MssqlDapperExample/request.json b/examples/MssqlDapperExample/request.json new file mode 100644 index 000000000..c6b2a564f --- /dev/null +++ b/examples/MssqlDapperExample/request.json @@ -0,0 +1,911 @@ +{ + "settings": { + "version": "2", + "engine": "mssql", + "schema": [ + "examples/config/mssql/authors/schema.sql" + ], + "queries": [ + "examples/config/mssql/authors/query.sql" + ], + "codegen": { + "out": "examples/MssqlDapperExample", + "plugin": "csharp", + "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxEYXBwZXJFeGFtcGxlR2VuIiwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OS4wIiwidXNlRGFwcGVyIjp0cnVlfQ==", + "process": { + "cmd": "./dist/LocalRunner" + } + } + }, + "catalog": { + "defaultSchema": "public", + "schemas": [ + { + "name": "public" + }, + { + "name": "public", + "tables": [ + { + "rel": { + "schema": "public", + "name": "authors" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "books" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "author_id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "description", + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] + }, + { + "name": "sys" + }, + { + "name": "information_schema" + } + ] + }, + "queries": [ + { + "text": "SELECT TOP 1 id, name, bio FROM authors\nWHERE name = @name;", + "name": "GetAuthor", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio\nFROM authors\nORDER BY name\nOFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;", + "name": "ListAuthors", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "offset", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + }, + { + "number": 2, + "column": { + "name": "limit", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);", + "name": "CreateAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);", + "name": "CreateAuthorReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment\nWHERE id = @id;", + "name": "GetAuthorById", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(@name_pattern, '%');", + "name": "GetAuthorByNamePattern", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name_pattern", + "length": -1, + "isNamedParam": true, + "type": {} + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors\nWHERE name = @name;", + "name": "DeleteAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors;", + "name": "DeleteAllAuthors", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "UPDATE authors\nSET bio = @bio\nWHERE bio IS NOT NULL;", + "name": "UpdateAuthors", + "cmd": ":execrows", + "parameters": [ + { + "number": 1, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);", + "name": "GetAuthorsByIds", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);", + "name": "GetAuthorsByIdsAndNames", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "names", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "isSqlcSlice": true, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);", + "name": "CreateBook", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "author_id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "author_id" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "books" + } + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nORDER BY authors.name;", + "name": "ListAllAuthorsBooks", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio\nFROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name\nWHERE authors1.id \u003c authors2.id;", + "name": "GetDuplicateAuthors", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nWHERE books.name = @name;", + "name": "GetAuthorsByBookName", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + } + ], + "sqlc_version": "v1.31.1", + "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OS4wIiwibmFtZXNwYWNlTmFtZSI6Ik1zc3FsRGFwcGVyRXhhbXBsZUdlbiIsInVzZURhcHBlciI6dHJ1ZSwib3ZlcnJpZGVEYXBwZXJWZXJzaW9uIjoiIiwib3ZlcnJpZGVzIjpudWxsLCJkZWJ1Z1JlcXVlc3QiOmZhbHNlLCJ1c2VDZW50cmFsUGFja2FnZU1hbmFnZW1lbnQiOmZhbHNlLCJ3aXRoQXN5bmNTdWZmaXgiOnRydWUsIndpdGhDYW5jZWxsYXRpb25Ub2tlbiI6ZmFsc2UsInVzZUFzeW5jRW51bWVyYWJsZSI6ZmFsc2UsInVzZVByb3BlclNpbmd1bGFyaXphdGlvbiI6ZmFsc2V9" +} \ No newline at end of file diff --git a/examples/MssqlDapperExample/request.message b/examples/MssqlDapperExample/request.message new file mode 100644 index 0000000000000000000000000000000000000000..65e0d9f050b3545ada83b35a8b0ba8c4de49e0e0 GIT binary patch literal 6377 zcmeHM&2Jk;6z|$`noNsYwh>f|kVY$&B$jK}28q^gOJjSJrg81aA1&ol*7n$0vfg!f z)@_oi;?O&Xazf$^XOKV~xc3j>2!8-)fOqkgPfJe`K}Q|PRO=ub&0)d0o5bZ+6GF4*jJL5;MITlc}~w z?NrHZsd{x_z{%n_GvaK2(A2e-JQqCu8TB8fUj+1K@a3cjlz0V2wDx)7(hL7jsE$)L z=cL)D*3}IAnJ|aENAW6}VYg$OCvHO0obgb#S^(NJ;S37CNYV2z1W`fJre+>+ZivOX zA&awu6w5?=>W=hf1f2FO2HWfb{ztbqquBD^^!_NGV~i%E zxq(i#32j+g-_}fn$7O7PNRUaW$T2ZWcd5J}iki_et)9ww-glis`YVERHKn8!>bPFn z#A&Rx6PRf-fk7j9wOYB2J@Y_!Zz@&Ad5N##8;mF?&aV-B#ql#~CTki*zBZ2cem1lR z7RT9cOMIb$R zo)gQG9ny-g!9_kAbDzl;7le%w2a>>_z-S{!X|(aiTvRNLI@M&kc>kR4RuX%~L7JbJ z{3Im(5kWU6q8mh|Wn5|0HyaFnrUa#!)Y?O^T`~{n1s>-ru?Lp1-j+@|c*Jm)18FT&S#+lv+Vqa)QkLzG~aVG7@<4!Xm_)w>Vvt zA!%>Aua9XqNI?*N7oBENzz2e>pCBWgkU}qc^uH3&ViAg=x-u2-pzt|eBsv+5;IFY- z$fAV_qUFT1o*cS9%XbEQItQ>6|h^ALf~01m+(Rgbh?MC%iI9OsBfxF!H&z8##~R;h6>z-dxWaH80Z@!>=F8M+ zFgWY+PXO7CY3=+Z)Sv!ugYuJFk$wi=ZC_&*Qvc4dtzmGEMTB(6m zcz9AC$!}-SPa{b3@PyI%kF3v4`L200NLdZz8sGw-L74fK;k9pp$7?WkNH00sV8~gD zeDYyB`F=W?j{g>UESnFBWod0vv^03SLo5m-Mi^Vezt#83!l zP#+S)mygO>W1#Dak(qr*)hP_^fnx!hSxeP7)z-G!CAU=r{<8*G&U!SDG<$1>?i#Jy zpwrQI{K-CBE~rL}=(;mrt()60$@ez_V`q + + + + + net10.0 + MssqlExampleGen + Library + enable + False + + + + + + + + \ No newline at end of file diff --git a/examples/MssqlExample/QuerySql.cs b/examples/MssqlExample/QuerySql.cs new file mode 100644 index 000000000..deb9b5af1 --- /dev/null +++ b/examples/MssqlExample/QuerySql.cs @@ -0,0 +1,642 @@ +// auto-generated by sqlc - do not edit +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable UseAwaitUsing +// ReSharper disable ConvertToUsingDeclaration +// ReSharper disable NotAccessedPositionalProperty.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading; +using System.Threading.Tasks; + +namespace MssqlExampleGen; +public class QuerySql +{ + public QuerySql() + { + } + + public QuerySql(string connectionString) : this() + { + this.ConnectionString = connectionString; + } + + private QuerySql(SqlTransaction transaction) : this() + { + this.Transaction = transaction; + } + + public static QuerySql WithTransaction(SqlTransaction transaction) + { + return new QuerySql(transaction); + } + + private SqlTransaction? Transaction { get; } + private string? ConnectionString { get; } + + private const string GetAuthorSql = @"SELECT TOP 1 id, name, bio FROM authors + WHERE name = @name;"; + public readonly record struct GetAuthorRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorArgs(string Name); + public async Task GetAuthorAsync(GetAuthorArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorSql, connection)) + { + command.Parameters.AddWithValue("@name", args.Name); + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name", args.Name); + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string ListAuthorsSql = @"SELECT id, name, bio + FROM authors + ORDER BY name + OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;"; + public readonly record struct ListAuthorsRow(long Id, string Name, string? Bio); + public readonly record struct ListAuthorsArgs(int Offset, int Limit); + public async Task> ListAuthorsAsync(ListAuthorsArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(ListAuthorsSql, connection)) + { + command.Parameters.AddWithValue("@offset", args.Offset); + command.Parameters.AddWithValue("@limit", args.Limit); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAuthorsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAuthorsSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@offset", args.Offset); + command.Parameters.AddWithValue("@limit", args.Limit); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAuthorsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string CreateAuthorSql = "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);"; + public readonly record struct CreateAuthorArgs(long Id, string Name, string? Bio); + public async Task CreateAuthorAsync(CreateAuthorArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateAuthorSql, connection)) + { + command.Parameters.AddWithValue("@id", args.Id); + command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@id", args.Id); + command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + await command.ExecuteNonQueryAsync(); + } + } + + private const string CreateAuthorReturnIdSql = "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);"; + public readonly record struct CreateAuthorReturnIdRow(long Id); + public readonly record struct CreateAuthorReturnIdArgs(string Name, string? Bio); + public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateAuthorReturnIdSql, connection)) + { + command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateAuthorReturnIdSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + + private const string GetAuthorByIdSql = @"SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment + WHERE id = @id;"; + public readonly record struct GetAuthorByIdRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorByIdArgs(long Id); + public async Task GetAuthorByIdAsync(GetAuthorByIdArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorByIdSql, connection)) + { + command.Parameters.AddWithValue("@id", args.Id); + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByIdSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@id", args.Id); + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string GetAuthorByNamePatternSql = @"SELECT id, name, bio FROM authors + WHERE name LIKE COALESCE(@name_pattern, '%');"; + public readonly record struct GetAuthorByNamePatternRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorByNamePatternArgs(object? NamePattern); + public async Task> GetAuthorByNamePatternAsync(GetAuthorByNamePatternArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorByNamePatternSql, connection)) + { + command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorByNamePatternRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByNamePatternSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorByNamePatternRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string DeleteAuthorSql = @"DELETE FROM authors + WHERE name = @name;"; + public readonly record struct DeleteAuthorArgs(string Name); + public async Task DeleteAuthorAsync(DeleteAuthorArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(DeleteAuthorSql, connection)) + { + command.Parameters.AddWithValue("@name", args.Name); + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = DeleteAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name", args.Name); + await command.ExecuteNonQueryAsync(); + } + } + + private const string DeleteAllAuthorsSql = "DELETE FROM authors;"; + public async Task DeleteAllAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(DeleteAllAuthorsSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = DeleteAllAuthorsSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string UpdateAuthorsSql = @"UPDATE authors + SET bio = @bio + WHERE bio IS NOT NULL;"; + public readonly record struct UpdateAuthorsArgs(string? Bio); + public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(UpdateAuthorsSql, connection)) + { + command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + return await command.ExecuteNonQueryAsync(); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = UpdateAuthorsSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + return await command.ExecuteNonQueryAsync(); + } + } + + private const string GetAuthorsByIdsSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);"; + public readonly record struct GetAuthorsByIdsRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorsByIdsArgs(long[] Ids); + public async Task> GetAuthorsByIdsAsync(GetAuthorsByIdsArgs args) + { + var transformedSql = GetAuthorsByIdsSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(transformedSql, connection)) + { + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = transformedSql; + command.Transaction = this.Transaction; + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string GetAuthorsByIdsAndNamesSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);"; + public readonly record struct GetAuthorsByIdsAndNamesRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorsByIdsAndNamesArgs(long[] Ids, string[] Names); + public async Task> GetAuthorsByIdsAndNamesAsync(GetAuthorsByIdsAndNamesArgs args) + { + var transformedSql = GetAuthorsByIdsAndNamesSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Names.Length, "names"); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(transformedSql, connection)) + { + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsAndNamesRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = transformedSql; + command.Transaction = this.Transaction; + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsAndNamesRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string CreateBookSql = "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);"; + public readonly record struct CreateBookRow(long Id); + public readonly record struct CreateBookArgs(string Name, long AuthorId); + public async Task CreateBookAsync(CreateBookArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateBookSql, connection)) + { + command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.AddWithValue("@author_id", args.AuthorId); + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateBookSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.AddWithValue("@author_id", args.AuthorId); + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + + private const string ListAllAuthorsBooksSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + ORDER BY authors.name;"; + public readonly record struct ListAllAuthorsBooksRow(Author? Author, Book? Book); + public async Task> ListAllAuthorsBooksAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(ListAllAuthorsBooksSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAllAuthorsBooksSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + + private const string GetDuplicateAuthorsSql = @"SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio + FROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name + WHERE authors1.id < authors2.id;"; + public readonly record struct GetDuplicateAuthorsRow(Author? Author, Author? Author2); + public async Task> GetDuplicateAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetDuplicateAuthorsSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetDuplicateAuthorsSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + + private const string GetAuthorsByBookNameSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + WHERE books.name = @name;"; + public readonly record struct GetAuthorsByBookNameRow(long Id, string Name, string? Bio, Book? Book); + public readonly record struct GetAuthorsByBookNameArgs(string Name); + public async Task> GetAuthorsByBookNameAsync(GetAuthorsByBookNameArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection)) + { + command.Parameters.AddWithValue("@name", args.Name); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorsByBookNameSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name", args.Name); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } +} \ No newline at end of file diff --git a/examples/MssqlExample/Utils.cs b/examples/MssqlExample/Utils.cs new file mode 100644 index 000000000..f4de9ac7d --- /dev/null +++ b/examples/MssqlExample/Utils.cs @@ -0,0 +1,13 @@ +// auto-generated by sqlc - do not edit +using System; +using System.Linq; + +namespace MssqlExampleGen; +public static class Utils +{ + public static string TransformQueryForSliceArgs(string originalSql, int sliceSize, string paramName) + { + var paramArgs = Enumerable.Range(0, sliceSize).Select(i => $"@{paramName}Arg{i}").ToList(); + return originalSql.Replace($"/*SLICE:{paramName}*/@{paramName}", string.Join(",", paramArgs)); + } +} \ No newline at end of file diff --git a/examples/MssqlExample/request.json b/examples/MssqlExample/request.json new file mode 100644 index 000000000..22433935f --- /dev/null +++ b/examples/MssqlExample/request.json @@ -0,0 +1,911 @@ +{ + "settings": { + "version": "2", + "engine": "mssql", + "schema": [ + "examples/config/mssql/authors/schema.sql" + ], + "queries": [ + "examples/config/mssql/authors/query.sql" + ], + "codegen": { + "out": "examples/MssqlExample", + "plugin": "csharp", + "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxFeGFtcGxlR2VuIiwidGFyZ2V0RnJhbWV3b3JrIjoibmV0MTAuMCIsInVzZURhcHBlciI6ZmFsc2V9", + "process": { + "cmd": "./dist/LocalRunner" + } + } + }, + "catalog": { + "defaultSchema": "public", + "schemas": [ + { + "name": "public" + }, + { + "name": "public", + "tables": [ + { + "rel": { + "schema": "public", + "name": "authors" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "books" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "author_id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "description", + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] + }, + { + "name": "sys" + }, + { + "name": "information_schema" + } + ] + }, + "queries": [ + { + "text": "SELECT TOP 1 id, name, bio FROM authors\nWHERE name = @name;", + "name": "GetAuthor", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio\nFROM authors\nORDER BY name\nOFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;", + "name": "ListAuthors", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "offset", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + }, + { + "number": 2, + "column": { + "name": "limit", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);", + "name": "CreateAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);", + "name": "CreateAuthorReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment\nWHERE id = @id;", + "name": "GetAuthorById", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(@name_pattern, '%');", + "name": "GetAuthorByNamePattern", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name_pattern", + "length": -1, + "isNamedParam": true, + "type": {} + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors\nWHERE name = @name;", + "name": "DeleteAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors;", + "name": "DeleteAllAuthors", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "UPDATE authors\nSET bio = @bio\nWHERE bio IS NOT NULL;", + "name": "UpdateAuthors", + "cmd": ":execrows", + "parameters": [ + { + "number": 1, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);", + "name": "GetAuthorsByIds", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);", + "name": "GetAuthorsByIdsAndNames", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "names", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "isSqlcSlice": true, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);", + "name": "CreateBook", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "author_id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "author_id" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "books" + } + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nORDER BY authors.name;", + "name": "ListAllAuthorsBooks", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio\nFROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name\nWHERE authors1.id \u003c authors2.id;", + "name": "GetDuplicateAuthors", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nWHERE books.name = @name;", + "name": "GetAuthorsByBookName", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + } + ], + "sqlc_version": "v1.31.1", + "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0MTAuMCIsIm5hbWVzcGFjZU5hbWUiOiJNc3NxbEV4YW1wbGVHZW4iLCJ1c2VEYXBwZXIiOmZhbHNlLCJvdmVycmlkZURhcHBlclZlcnNpb24iOiIiLCJvdmVycmlkZXMiOm51bGwsImRlYnVnUmVxdWVzdCI6ZmFsc2UsInVzZUNlbnRyYWxQYWNrYWdlTWFuYWdlbWVudCI6ZmFsc2UsIndpdGhBc3luY1N1ZmZpeCI6dHJ1ZSwid2l0aENhbmNlbGxhdGlvblRva2VuIjpmYWxzZSwidXNlQXN5bmNFbnVtZXJhYmxlIjpmYWxzZSwidXNlUHJvcGVyU2luZ3VsYXJpemF0aW9uIjpmYWxzZX0=" +} \ No newline at end of file diff --git a/examples/MssqlExample/request.message b/examples/MssqlExample/request.message new file mode 100644 index 0000000000000000000000000000000000000000..b38667e7ff7ec02e573be964c7e2c8a0361ca9a3 GIT binary patch literal 6363 zcmeHMPj4GV6z|$`noNsYwgIY@kVY$&CYEc*0f}O_rLnz9)3|nIJ1yl>*7i7Avfg!f z)@_oCQ|}zYi7xMDL|0F?8L5j%1J3ISk=FRW@e?bQ^ zBrVB_E~Sr6WuENn-JVHkre#|lV=Kd_GkV|MwjG+Gt!>iP(?F?SJ)H7!pE!Gru=%}& z-U?~&u~Rhf40*amw{@qdJbR|LNwdFIBe06PYR+}~B&BWS)`v&_2*r%{Wohwc@F&$q zshTs&bko=}EcZZ~LH zxHQ<}qNF62;$3}L`6`AkgttmGZF`5xACBH>m}dNqa^j6fmPD8txz8;$>tXt67|)T7 z#-h27&b0|`IY!SlY)iytWPOB@aj2*XIZpSeIw#A9)v=wf&h~udnS%0X4CU)uSt~Yh zqq>2!*l4FPlVl2mMDR+jdJp^Rfgaq^Y8wBDZ{pjGC@;^h5_g$fmok;JEu!8W#rrTF z+6Rpj^hhm;sE=7FTz8*q+^9CGcmJU_?K9;&kj8#kJ~#I$qHl#XGp7eFF2Q(KY<( zYNh-zFP|%eYw!d_l|-(qTYINXL`xu|_t3NlNyK|ymzoJB&OKEEv8UowB^PI6@)eZe za2>J4BN7GoYYg35tJJkx1FuyY)xbXRd_;^3`2KQvQ>)|o+iafO3p_y87V`4>qC+4w z_?DB%kzLYCuEIq=9x{#B%-60Ahf^V6^k{8(~-| z>;d4XGBli&xe9im{1rphaWP?HATF=1Yq(flE^GCoHqV{Rqn_@%#IaKN>XobDYu*-Q zQGuYn;e9@((I5j(^j&nGd4T{3A%B91a7KxI6w&`$LRU+W3^lZgc!!D4`4Tb7um^vQ z*+LG@jbSY>U-bDf%|VoB(L}r#lrSG`Zj_ecUbAX`WWBsr)N)3fCNtoN zC}{tMQ2Qx!MW;0hS_gR)AjfIbp>*cZ3T|5(P9N9~qUt=KLY-HZ0$#3^c>WrZ5;IXI zC;kLUy)z`KWvk8H)&CN6=%7L!FR^3|vW8WZ;85}!{K)JKHkwdBDc&}s7W@`NH8CgS zr8S;s$EAFi0+sHu@-oi>G3vaGyzHz1g?cn6%a3HiwUNSiQU;tXb5j_!BLIq##bTN| z2?jo&{1lK~pH$9|+j_v)pa&pSxXNHwK)7&L@%k0YcQN#XEjttsu)-KtX~pMC0ZeXSBpup)TWWLAn(jR6HqoiBkKEcJWPx)UO=U%CGS z6pv3TBKhqU`e_JB5uPYI|B?22A>UJP1}V#7JO!K+BM4K!3VaSU@N_k%j%g()>kI`= zQO`cfrr*z|v&mm#&s6&faU7#fN{#`e`^2HpVuZ#ubgajB!4KHG0?)Ea>vs(3j zr(^5}9ep-k)U6gVP2O5<*gMe2_xkgEW4=bS`d#>UK+}Y#|KQ9G$A)Hp-LSU$rtTQ~ Je1o93|2Oc)hN=Jn literal 0 HcmV?d00001 diff --git a/examples/config/mssql/authors/query.sql b/examples/config/mssql/authors/query.sql new file mode 100644 index 000000000..13062b791 --- /dev/null +++ b/examples/config/mssql/authors/query.sql @@ -0,0 +1,59 @@ +-- name: GetAuthor :one +SELECT TOP 1 * FROM authors +WHERE name = @name; + +-- name: ListAuthors :many +SELECT * +FROM authors +ORDER BY name +OFFSET sqlc.arg('offset') ROWS FETCH NEXT sqlc.arg('limit') ROWS ONLY; + +-- name: CreateAuthor :exec +INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio); + +-- name: CreateAuthorReturnId :execlastid +INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio); + +-- name: GetAuthorById :one +SELECT TOP 1 * FROM authors -- test rest of line comment +WHERE id = @id; + +-- name: GetAuthorByNamePattern :many +SELECT * FROM authors +WHERE name LIKE COALESCE(sqlc.narg('name_pattern'), '%'); + +-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE name = @name; + +-- name: DeleteAllAuthors :exec +DELETE FROM authors; + +-- name: UpdateAuthors :execrows +UPDATE authors +SET bio = sqlc.arg('bio') +WHERE bio IS NOT NULL; + +-- name: GetAuthorsByIds :many +SELECT * FROM authors WHERE id IN (sqlc.slice('ids')); + +-- name: GetAuthorsByIdsAndNames :many +SELECT * FROM authors WHERE id IN (sqlc.slice('ids')) AND name IN (sqlc.slice('names')); + +-- name: CreateBook :execlastid +INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id); + +-- name: ListAllAuthorsBooks :many +SELECT sqlc.embed(authors), sqlc.embed(books) +FROM authors JOIN books ON authors.id = books.author_id +ORDER BY authors.name; + +-- name: GetDuplicateAuthors :many +SELECT sqlc.embed(authors1), sqlc.embed(authors2) +FROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name +WHERE authors1.id < authors2.id; + +-- name: GetAuthorsByBookName :many +SELECT authors.*, sqlc.embed(books) +FROM authors JOIN books ON authors.id = books.author_id +WHERE books.name = @name; diff --git a/examples/config/mssql/authors/schema.sql b/examples/config/mssql/authors/schema.sql new file mode 100644 index 000000000..fb03778a8 --- /dev/null +++ b/examples/config/mssql/authors/schema.sql @@ -0,0 +1,16 @@ +CREATE SEQUENCE authors_id_seq AS BIGINT START WITH 1 INCREMENT BY 1; +CREATE SEQUENCE books_id_seq AS BIGINT START WITH 1 INCREMENT BY 1; + +CREATE TABLE authors ( + id BIGINT NOT NULL PRIMARY KEY DEFAULT (NEXT VALUE FOR authors_id_seq), + name NVARCHAR(255) NOT NULL, + bio NVARCHAR(MAX) +); + +CREATE TABLE books ( + id BIGINT NOT NULL PRIMARY KEY DEFAULT (NEXT VALUE FOR books_id_seq), + name NVARCHAR(255) NOT NULL, + author_id BIGINT NOT NULL, + description NVARCHAR(MAX), + FOREIGN KEY (author_id) REFERENCES authors (id) ON DELETE CASCADE +); diff --git a/sqlc.ci.yaml b/sqlc.ci.yaml index 173a27490..e183179ef 100644 --- a/sqlc.ci.yaml +++ b/sqlc.ci.yaml @@ -530,3 +530,26 @@ sql: targetFramework: net10.0 generateCsproj: true namespaceName: NpgsqlDapperAsyncEnumerableExampleGen + # SQL Server + - schema: ["examples/config/mssql/authors/schema.sql"] + queries: ["examples/config/mssql/authors/query.sql"] + engine: "mssql" + codegen: + - plugin: csharp + out: examples/MssqlExample + options: + useDapper: false + targetFramework: net10.0 + generateCsproj: true + namespaceName: MssqlExampleGen + - schema: ["examples/config/mssql/authors/schema.sql"] + queries: ["examples/config/mssql/authors/query.sql"] + engine: "mssql" + codegen: + - plugin: csharp + out: examples/MssqlDapperExample + options: + useDapper: true + targetFramework: net9.0 + generateCsproj: true + namespaceName: MssqlDapperExampleGen diff --git a/sqlc.local.generated.yaml b/sqlc.local.generated.yaml index fad246902..687c86e8f 100644 --- a/sqlc.local.generated.yaml +++ b/sqlc.local.generated.yaml @@ -531,3 +531,26 @@ sql: targetFramework: net10.0 generateCsproj: true namespaceName: NpgsqlDapperAsyncEnumerableExampleGen + # SQL Server + - schema: ["examples/config/mssql/authors/schema.sql"] + queries: ["examples/config/mssql/authors/query.sql"] + engine: "mssql" + codegen: + - plugin: csharp + out: examples/MssqlExample + options: + useDapper: false + targetFramework: net10.0 + generateCsproj: true + namespaceName: MssqlExampleGen + - schema: ["examples/config/mssql/authors/schema.sql"] + queries: ["examples/config/mssql/authors/query.sql"] + engine: "mssql" + codegen: + - plugin: csharp + out: examples/MssqlDapperExample + options: + useDapper: true + targetFramework: net9.0 + generateCsproj: true + namespaceName: MssqlDapperExampleGen diff --git a/sqlc.request.generated.yaml b/sqlc.request.generated.yaml index f2359715a..a8699ea94 100644 --- a/sqlc.request.generated.yaml +++ b/sqlc.request.generated.yaml @@ -551,3 +551,28 @@ sql: generateCsproj: true namespaceName: NpgsqlDapperAsyncEnumerableExampleGen debugRequest: true + # SQL Server + - schema: ["examples/config/mssql/authors/schema.sql"] + queries: ["examples/config/mssql/authors/query.sql"] + engine: "mssql" + codegen: + - plugin: csharp + out: examples/MssqlExample + options: + useDapper: false + targetFramework: net10.0 + generateCsproj: true + namespaceName: MssqlExampleGen + debugRequest: true + - schema: ["examples/config/mssql/authors/schema.sql"] + queries: ["examples/config/mssql/authors/query.sql"] + engine: "mssql" + codegen: + - plugin: csharp + out: examples/MssqlDapperExample + options: + useDapper: true + targetFramework: net9.0 + generateCsproj: true + namespaceName: MssqlDapperExampleGen + debugRequest: true diff --git a/unit-tests/RepositoryTests/ArchitectureTests.cs b/unit-tests/RepositoryTests/ArchitectureTests.cs index d05fd3a9d..8b9370278 100644 --- a/unit-tests/RepositoryTests/ArchitectureTests.cs +++ b/unit-tests/RepositoryTests/ArchitectureTests.cs @@ -37,7 +37,7 @@ public void TestPluginProjectReferencesFollowLayering(string csprojPath) $"{csprojPath} references {string.Join(", ", actual)} - update AGENTS.md and this test if the layering should change"); } - private static readonly string[] ConcreteDrivers = ["NpgsqlDriver", "MySqlConnectorDriver", "SqliteDriver"]; + private static readonly string[] ConcreteDrivers = ["NpgsqlDriver", "MySqlConnectorDriver", "SqliteDriver", "MssqlDriver"]; // Only CodeGenerator.InstantiateDriver may know the concrete drivers; everything else goes through DbDriver. [Test] From 64d2b33490fe506c78bbceab321d017681ce36ad Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 08:44:21 +0000 Subject: [PATCH 02/11] mssql: types example, typed parameters, SqlBulkCopy :copyfrom, transaction tests Co-Authored-By: Claude Sonnet 5.5 --- Drivers/MssqlDriver.cs | 137 +- end2end/EndToEndScaffold/Config.cs | 24 +- end2end/EndToEndScaffold/Program.cs | 1 + .../EndToEndScaffold/Templates/MssqlTests.cs | 453 ++++ end2end/EndToEndTests/EndToEndTests.csproj | 4 + end2end/EndToEndTests/GlobalSetup.cs | 3 +- end2end/EndToEndTests/MssqlDapperTester.cs | 4 + .../MssqlDapperTester.generated.cs | 357 +++ end2end/EndToEndTests/MssqlTester.cs | 4 + .../EndToEndTests/MssqlTester.generated.cs | 357 +++ examples/MssqlDapperExample/Models.cs | 40 + examples/MssqlDapperExample/QuerySql.cs | 767 +++++- examples/MssqlDapperExample/Utils.cs | 20 + examples/MssqlDapperExample/request.json | 2099 ++++++++++++++++- examples/MssqlDapperExample/request.message | Bin 6377 -> 25003 bytes examples/MssqlExample/Models.cs | 7 +- examples/MssqlExample/QuerySql.cs | 1138 ++++++++- examples/MssqlExample/request.json | 2099 ++++++++++++++++- examples/MssqlExample/request.message | Bin 6363 -> 24989 bytes examples/config/mssql/types/query.sql | 222 ++ examples/config/mssql/types/schema.sql | 38 + sqlc.ci.yaml | 8 +- sqlc.local.generated.yaml | 8 +- sqlc.request.generated.yaml | 8 +- 24 files changed, 7715 insertions(+), 83 deletions(-) create mode 100644 end2end/EndToEndScaffold/Templates/MssqlTests.cs create mode 100644 examples/config/mssql/types/query.sql create mode 100644 examples/config/mssql/types/schema.sql diff --git a/Drivers/MssqlDriver.cs b/Drivers/MssqlDriver.cs index 9ee10ea32..b1fc5ba11 100644 --- a/Drivers/MssqlDriver.cs +++ b/Drivers/MssqlDriver.cs @@ -20,6 +20,25 @@ public MssqlDriver( NullableTypes.Add("DateTimeOffset"); } + // Dapper sends a null byte[] as nvarchar, which SQL Server refuses to convert to a binary type + private const string BinaryParameterImpl = """ + public class BinaryParameter : SqlMapper.ICustomQueryParameter + { + private readonly byte[]? _value; + + public BinaryParameter(byte[]? value) + { + _value = value; + } + + public void AddParameter(IDbCommand command, string name) + { + var parameter = new SqlParameter(name, SqlDbType.VarBinary) { Value = (object?)_value ?? DBNull.Value }; + command.Parameters.Add(parameter); + } + } + """; + protected override Dictionary ColumnMappings { get; } = new() { @@ -27,7 +46,7 @@ public MssqlDriver( ["bool"] = new( new() { - { "bit", new() } + { "bit", new(DbTypeOverride: "SqlDbType.Bit") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetBoolean({ordinal})", convertFunc: x => $"Convert.ToBoolean({x})" @@ -35,7 +54,7 @@ public MssqlDriver( ["byte"] = new( new() { - { "tinyint", new() } + { "tinyint", new(DbTypeOverride: "SqlDbType.TinyInt") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetByte({ordinal})", convertFunc: x => $"Convert.ToByte({x})" @@ -43,7 +62,7 @@ public MssqlDriver( ["short"] = new( new() { - { "smallint", new() } + { "smallint", new(DbTypeOverride: "SqlDbType.SmallInt") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetInt16({ordinal})", convertFunc: x => $"Convert.ToInt16({x})" @@ -51,8 +70,8 @@ public MssqlDriver( ["int"] = new( new() { - { "int", new() }, - { "integer", new() } + { "int", new(DbTypeOverride: "SqlDbType.Int") }, + { "integer", new(DbTypeOverride: "SqlDbType.Int") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetInt32({ordinal})", convertFunc: x => $"Convert.ToInt32({x})" @@ -60,7 +79,7 @@ public MssqlDriver( ["long"] = new( new() { - { "bigint", new() } + { "bigint", new(DbTypeOverride: "SqlDbType.BigInt") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetInt64({ordinal})", convertFunc: x => $"Convert.ToInt64({x})" @@ -70,18 +89,19 @@ public MssqlDriver( ["float"] = new( new() { - { "real", new() } + { "real", new(DbTypeOverride: "SqlDbType.Real") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFloat({ordinal})" ), ["double"] = new( new() { - { "float", new() }, - { "double precision", new() } + { "float", new(DbTypeOverride: "SqlDbType.Float") }, + { "double precision", new(DbTypeOverride: "SqlDbType.Float") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetDouble({ordinal})" ), + // decimal types are left untyped on purpose: the precision and scale are inferred from the value ["decimal"] = new( new() { @@ -97,14 +117,14 @@ public MssqlDriver( ["string"] = new( new() { - { "char", new() }, - { "nchar", new() }, - { "varchar", new() }, - { "nvarchar", new() }, - { "text", new() }, - { "ntext", new() }, - { "xml", new() }, - { "sysname", new() } + { "char", new(DbTypeOverride: "SqlDbType.Char") }, + { "nchar", new(DbTypeOverride: "SqlDbType.NChar") }, + { "varchar", new(DbTypeOverride: "SqlDbType.VarChar") }, + { "nvarchar", new(DbTypeOverride: "SqlDbType.NVarChar") }, + { "text", new(DbTypeOverride: "SqlDbType.Text") }, + { "ntext", new(DbTypeOverride: "SqlDbType.NText") }, + { "xml", new(DbTypeOverride: "SqlDbType.Xml") }, + { "sysname", new(DbTypeOverride: "SqlDbType.NVarChar") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetString({ordinal})" ), @@ -113,37 +133,45 @@ public MssqlDriver( ["byte[]"] = new( new() { - { "binary", new() }, - { "varbinary", new() }, - { "image", new() }, - { "rowversion", new() }, - { "timestamp", new() } + { "binary", new(DbTypeOverride: "SqlDbType.Binary") }, + { "varbinary", new(DbTypeOverride: "SqlDbType.VarBinary") }, + { "image", new(DbTypeOverride: "SqlDbType.Image") }, + { "rowversion", new(DbTypeOverride: "SqlDbType.Timestamp") }, + { "timestamp", new(DbTypeOverride: "SqlDbType.Timestamp") } }, - readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFieldValue({ordinal})" + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFieldValue({ordinal})", + writerFn: (el, _, notNull, isDapper, _) => + { + if (isDapper) + return $"new Utils.BinaryParameter({el})"; + return notNull ? el : $"{el} ?? (object)DBNull.Value"; + } ), /* Date and time data types */ ["DateTime"] = new( new() { - { "date", new() }, - { "datetime", new() }, - { "datetime2", new() }, - { "smalldatetime", new() } + { "date", new(DbTypeOverride: "SqlDbType.Date") }, + { "datetime", new(DbTypeOverride: "SqlDbType.DateTime") }, + { "datetime2", new(DbTypeOverride: "SqlDbType.DateTime2") }, + { "smalldatetime", new(DbTypeOverride: "SqlDbType.SmallDateTime") } }, - readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetDateTime({ordinal})" + readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetDateTime({ordinal})", + // Dapper sends DateTime as the less precise datetime unless told otherwise + sqlMapper: "SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2);" ), ["DateTimeOffset"] = new( new() { - { "datetimeoffset", new() } + { "datetimeoffset", new(DbTypeOverride: "SqlDbType.DateTimeOffset") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFieldValue({ordinal})" ), ["TimeSpan"] = new( new() { - { "time", new() } + { "time", new(DbTypeOverride: "SqlDbType.Time") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFieldValue({ordinal})" ), @@ -152,7 +180,7 @@ public MssqlDriver( ["Guid"] = new( new() { - { "uniqueidentifier", new() } + { "uniqueidentifier", new(DbTypeOverride: "SqlDbType.UniqueIdentifier") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetGuid({ordinal})", convertFunc: x => $"Guid.Parse({x}.ToString())" @@ -160,7 +188,7 @@ public MssqlDriver( ["object"] = new( new() { - { "sql_variant", new() }, + { "sql_variant", new(DbTypeOverride: "SqlDbType.Variant") }, { "any", new() } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetValue({ordinal})" @@ -179,6 +207,36 @@ public override IDictionary GetPackageReferences() }); } + // Parameters get an explicit SqlDbType, otherwise a null is sent as nvarchar and + // SQL Server refuses to convert it to binary types, and DateTime is always sent as the less precise datetime + public override string AddParametersToCommand(Query query) + { + return query.Params.Select(p => + { + var commandVar = Variable.Command.AsVarName(); + var param = $"{Variable.Args.AsVarName()}.{p.Column.Name.ToPascalCase()}"; + + if (p.Column.IsSqlcSlice) + return $$""" + for (int i = 0; i < {{param}}.Length; i++) + {{commandVar}}.Parameters.AddWithValue($"@{{p.Column.Name}}Arg{i}", {{param}}[i]); + """; + + var writerFn = GetWriterFn(p.Column, query); + var paramToWrite = writerFn is null ? param : writerFn( + param, + p.Column.Type.Name, + IsColumnNotNull(p.Column, query), + Options.UseDapper, + Options.DotnetFramework.IsDotnetLegacy()); + + var dbType = GetColumnDbTypeOverride(p.Column); + return dbType is null + ? $"""{commandVar}.Parameters.AddWithValue("@{p.Column.Name}", {paramToWrite});""" + : $"""{commandVar}.Parameters.Add("@{p.Column.Name}", {dbType}).Value = {paramToWrite};"""; + }).JoinByNewLine(); + } + public override ISet GetUsingDirectivesForQueries() { return base @@ -186,11 +244,19 @@ public override ISet GetUsingDirectivesForQueries() .AddRangeExcludeNulls(["Microsoft.Data.SqlClient"]); } + public override ISet GetUsingDirectivesForModels() + { + return base + .GetUsingDirectivesForModels() + .AddRangeExcludeNulls(["System"]); + } + public override ISet GetUsingDirectivesForUtils() { return base .GetUsingDirectivesForUtils() - .AddRangeExcludeNulls(["System"]); + .AddRangeExcludeNulls(["System"]) + .AddRangeIf(["Microsoft.Data.SqlClient"], Options.UseDapper); } public override MemberDeclarationSyntax[] GetMemberDeclarationsForUtils() @@ -199,7 +265,10 @@ public override MemberDeclarationSyntax[] GetMemberDeclarationsForUtils() .GetMemberDeclarationsForUtils() .AddRangeIf([ ParseMemberDeclaration(TransformQueryForSliceArgsImpl)! - ], SliceQueryExists())]; + ], SliceQueryExists()) + .AddRangeIf([ + ParseMemberDeclaration(BinaryParameterImpl)! + ], Options.UseDapper && TypeExistsInQueries("byte[]"))]; } public override ConnectionGenCommands EstablishConnection(Query query) diff --git a/end2end/EndToEndScaffold/Config.cs b/end2end/EndToEndScaffold/Config.cs index 5b255ff87..f8112ff66 100644 --- a/end2end/EndToEndScaffold/Config.cs +++ b/end2end/EndToEndScaffold/Config.cs @@ -26,6 +26,18 @@ public enum KnownTestType NargNull, NargNotNull, + // Mssql + MssqlIntegerDataTypes, + MssqlIntegerCopyFrom, + MssqlStringDataTypes, + MssqlStringCopyFrom, + MssqlDateTimeDataTypes, + MssqlDateTimeCopyFrom, + MssqlBinaryDataTypes, + MssqlBinaryCopyFrom, + MssqlTransaction, + MssqlTransactionRollback, + // Sqlite SqliteDataTypes, SqliteMultipleNamedParam, @@ -201,7 +213,17 @@ internal static class Config KnownTestType.Slice, KnownTestType.MultipleSlices, KnownTestType.NargNull, - KnownTestType.NargNotNull + KnownTestType.NargNotNull, + KnownTestType.MssqlIntegerDataTypes, + KnownTestType.MssqlIntegerCopyFrom, + KnownTestType.MssqlStringDataTypes, + KnownTestType.MssqlStringCopyFrom, + KnownTestType.MssqlDateTimeDataTypes, + KnownTestType.MssqlDateTimeCopyFrom, + KnownTestType.MssqlBinaryDataTypes, + KnownTestType.MssqlBinaryCopyFrom, + KnownTestType.MssqlTransaction, + KnownTestType.MssqlTransactionRollback ]; private static readonly SortedSet _sqliteTestTypes = [ diff --git a/end2end/EndToEndScaffold/Program.cs b/end2end/EndToEndScaffold/Program.cs index 68e857fdb..4575eee30 100644 --- a/end2end/EndToEndScaffold/Program.cs +++ b/end2end/EndToEndScaffold/Program.cs @@ -14,6 +14,7 @@ public static class Program { AnnotationTests.TestImplementations, MacroTests.TestImplementations, + MssqlTests.TestImplementations, MySqlTests.TestImplementations, PostgresTests.TestImplementations, SqliteTests.TestImplementations diff --git a/end2end/EndToEndScaffold/Templates/MssqlTests.cs b/end2end/EndToEndScaffold/Templates/MssqlTests.cs new file mode 100644 index 000000000..08ca06309 --- /dev/null +++ b/end2end/EndToEndScaffold/Templates/MssqlTests.cs @@ -0,0 +1,453 @@ +using System.Collections.Generic; + +namespace EndToEndScaffold.Templates; + +public static class MssqlTests +{ + public static Dictionary TestImplementations { get; } = new() + { + [KnownTestType.MssqlIntegerDataTypes] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlIntegerTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlNumericTypesAsync(); + await AssertRoundTrip(false); + + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlNumericTypesAsync(new QuerySql.InsertMssqlNumericTypesArgs + { + CBit = withValues ? (bool?)true : null, + CTinyint = withValues ? (byte?)(byte)13 : null, + CSmallint = withValues ? (short?)(short)2084 : null, + CInt = withValues ? (int?)-54355 : null, + CBigint = withValues ? (long?)9787668656L : null, + CReal = withValues ? (float?)3.4f : null, + CFloat = withValues ? (double?)21.214312452534 : null, + CDecimal = withValues ? (decimal?)11.0986m : null, + CNumeric = withValues ? (decimal?)-31.5556m : null, + CMoney = withValues ? (decimal?)922.5000m : null, + CSmallmoney = withValues ? (decimal?)12.3400m : null + }); + var expected = new QuerySql.GetMssqlNumericTypesRow + { + CBit = withValues ? (bool?)true : null, + CTinyint = withValues ? (byte?)(byte)13 : null, + CSmallint = withValues ? (short?)(short)2084 : null, + CInt = withValues ? (int?)-54355 : null, + CBigint = withValues ? (long?)9787668656L : null, + CReal = withValues ? (float?)3.4f : null, + CFloat = withValues ? (double?)21.214312452534 : null, + CDecimal = withValues ? (decimal?)11.0986m : null, + CNumeric = withValues ? (decimal?)-31.5556m : null, + CMoney = withValues ? (decimal?)922.5000m : null, + CSmallmoney = withValues ? (decimal?)12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesAsync(); + AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); + } + + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesRow x, QuerySql.GetMssqlNumericTypesRow y) + { + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + """ + }, + [KnownTestType.MssqlIntegerCopyFrom] = new TestImpl + { + Impl = $$""" + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlIntegerCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize) + .Select(_ => new QuerySql.InsertMssqlNumericTypesBatchArgs + { + CBit = withValues ? (bool?)true : null, + CTinyint = withValues ? (byte?)(byte)13 : null, + CSmallint = withValues ? (short?)(short)2084 : null, + CInt = withValues ? (int?)-54355 : null, + CBigint = withValues ? (long?)9787668656L : null, + CReal = withValues ? (float?)3.4f : null, + CFloat = withValues ? (double?)21.214312452534 : null, + CDecimal = withValues ? (decimal?)11.0986m : null, + CNumeric = withValues ? (decimal?)-31.5556m : null, + CMoney = withValues ? (decimal?)922.5000m : null, + CSmallmoney = withValues ? (decimal?)12.3400m : null + }) + .ToList(); + await QuerySql.InsertMssqlNumericTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlNumericTypesCntRow + { + Cnt = batchSize, + CBit = withValues ? (bool?)true : null, + CTinyint = withValues ? (byte?)(byte)13 : null, + CSmallint = withValues ? (short?)(short)2084 : null, + CInt = withValues ? (int?)-54355 : null, + CBigint = withValues ? (long?)9787668656L : null, + CReal = withValues ? (float?)3.4f : null, + CFloat = withValues ? (double?)21.214312452534 : null, + CDecimal = withValues ? (decimal?)11.0986m : null, + CNumeric = withValues ? (decimal?)-31.5556m : null, + CMoney = withValues ? (decimal?)922.5000m : null, + CSmallmoney = withValues ? (decimal?)12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesCntAsync(); + AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); + + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesCntRow x, QuerySql.GetMssqlNumericTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + """ + }, + [KnownTestType.MssqlStringDataTypes] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlStringTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlStringTypesAsync(); + await AssertRoundTrip(false); + + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlStringTypesAsync(new QuerySql.InsertMssqlStringTypesArgs + { + CChar = withValues ? (string?)"abcdefghij" : null, + CNchar = withValues ? (string?)"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string?)"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string?)"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string?)"London's Calling" : null, + CNvarcharMax = withValues ? (string?)"Police & Thieves \u1857" : null + }); + var expected = new QuerySql.GetMssqlStringTypesRow + { + CChar = withValues ? (string?)"abcdefghij" : null, + CNchar = withValues ? (string?)"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string?)"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string?)"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string?)"London's Calling" : null, + CNvarcharMax = withValues ? (string?)"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesAsync(); + AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); + } + + void AssertSingularEquals(QuerySql.GetMssqlStringTypesRow x, QuerySql.GetMssqlStringTypesRow y) + { + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + """ + }, + [KnownTestType.MssqlStringCopyFrom] = new TestImpl + { + Impl = $$""" + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlStringCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize) + .Select(_ => new QuerySql.InsertMssqlStringTypesBatchArgs + { + CChar = withValues ? (string?)"abcdefghij" : null, + CNchar = withValues ? (string?)"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string?)"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string?)"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string?)"London's Calling" : null, + CNvarcharMax = withValues ? (string?)"Police & Thieves \u1857" : null + }) + .ToList(); + await QuerySql.InsertMssqlStringTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlStringTypesCntRow + { + Cnt = batchSize, + CChar = withValues ? (string?)"abcdefghij" : null, + CNchar = withValues ? (string?)"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string?)"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string?)"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string?)"London's Calling" : null, + CNvarcharMax = withValues ? (string?)"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesCntAsync(); + AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); + + void AssertSingularEquals(QuerySql.GetMssqlStringTypesCntRow x, QuerySql.GetMssqlStringTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + """ + }, + [KnownTestType.MssqlDateTimeDataTypes] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlDateTimeTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlDatetimeTypesAsync(); + await AssertRoundTrip(false); + + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlDatetimeTypesAsync(new QuerySql.InsertMssqlDatetimeTypesArgs + { + CDate = withValues ? (DateTime?)new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset?)new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan?)new TimeSpan(7, 8, 9) : null + }); + var expected = new QuerySql.GetMssqlDatetimeTypesRow + { + CDate = withValues ? (DateTime?)new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset?)new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan?)new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesAsync(); + AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); + } + + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesRow x, QuerySql.GetMssqlDatetimeTypesRow y) + { + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + """ + }, + [KnownTestType.MssqlDateTimeCopyFrom] = new TestImpl + { + Impl = $$""" + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlDateTimeCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize) + .Select(_ => new QuerySql.InsertMssqlDatetimeTypesBatchArgs + { + CDate = withValues ? (DateTime?)new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset?)new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan?)new TimeSpan(7, 8, 9) : null + }) + .ToList(); + await QuerySql.InsertMssqlDatetimeTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlDatetimeTypesCntRow + { + Cnt = batchSize, + CDate = withValues ? (DateTime?)new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime?)new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset?)new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan?)new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesCntAsync(); + AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); + + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesCntRow x, QuerySql.GetMssqlDatetimeTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + """ + }, + [KnownTestType.MssqlBinaryDataTypes] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlBinaryTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlBinaryTypesAsync(); + await AssertRoundTrip(false); + + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlBinaryTypesAsync(new QuerySql.InsertMssqlBinaryTypesArgs + { + CBinary = withValues ? (byte[]?)new byte[] { 1, 2, 3 } : null, + CVarbinary = withValues ? (byte[]?)new byte[] { 4, 5, 6, 7 } : null, + CVarbinaryMax = withValues ? (byte[]?)new byte[] { 8, 9, 10, 11, 12 } : null, + CUniqueidentifier = withValues ? (Guid?)Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }); + var expected = new QuerySql.GetMssqlBinaryTypesRow + { + CBinary = withValues ? (byte[]?)new byte[] { 1, 2, 3 } : null, + CVarbinary = withValues ? (byte[]?)new byte[] { 4, 5, 6, 7 } : null, + CVarbinaryMax = withValues ? (byte[]?)new byte[] { 8, 9, 10, 11, 12 } : null, + CUniqueidentifier = withValues ? (Guid?)Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesAsync(); + AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); + } + + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesRow x, QuerySql.GetMssqlBinaryTypesRow y) + { + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + """ + }, + [KnownTestType.MssqlBinaryCopyFrom] = new TestImpl + { + Impl = $$""" + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlBinaryCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize) + .Select(_ => new QuerySql.InsertMssqlBinaryTypesBatchArgs + { + CBinary = withValues ? (byte[]?)new byte[] { 1, 2, 3 } : null, + CVarbinary = withValues ? (byte[]?)new byte[] { 4, 5, 6, 7 } : null, + CVarbinaryMax = withValues ? (byte[]?)new byte[] { 8, 9, 10, 11, 12 } : null, + CUniqueidentifier = withValues ? (Guid?)Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }) + .ToList(); + await QuerySql.InsertMssqlBinaryTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlBinaryTypesCntRow + { + Cnt = batchSize, + CBinary = withValues ? (byte[]?)new byte[] { 1, 2, 3 } : null, + CVarbinary = withValues ? (byte[]?)new byte[] { 4, 5, 6, 7 } : null, + CVarbinaryMax = withValues ? (byte[]?)new byte[] { 8, 9, 10, 11, 12 } : null, + CUniqueidentifier = withValues ? (Guid?)Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesCntAsync(); + AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); + + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesCntRow x, QuerySql.GetMssqlBinaryTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + """ + }, + [KnownTestType.MssqlTransaction] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlTransaction() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = {{Consts.BojackId}}, Name = {{Consts.BojackAuthor}}, Bio = {{Consts.BojackTheme}} }); + + // the uncommitted row is locked by the transaction, so it is read from within it + var actual = await querySqlWithTx.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = {{Consts.BojackAuthor}} }); + ClassicAssert.IsNotNull(actual); + + await transaction.CommitAsync(); + + var expected = new QuerySql.GetAuthorRow + { + Id = {{Consts.BojackId}}, + Name = {{Consts.BojackAuthor}}, + Bio = {{Consts.BojackTheme}} + }; + actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = {{Consts.BojackAuthor}} }); + AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); + + void AssertSingularEquals(QuerySql.GetAuthorRow x, QuerySql.GetAuthorRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + """ + }, + [KnownTestType.MssqlTransactionRollback] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlTransactionRollback() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = {{Consts.BojackId}}, Name = {{Consts.BojackAuthor}}, Bio = {{Consts.BojackTheme}} }); + + await transaction.RollbackAsync(); + + var actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = {{Consts.BojackAuthor}} }); + ClassicAssert.IsNull(actual); + } + """ + }, + }; +} diff --git a/end2end/EndToEndTests/EndToEndTests.csproj b/end2end/EndToEndTests/EndToEndTests.csproj index d9b57bdf2..db470dd15 100644 --- a/end2end/EndToEndTests/EndToEndTests.csproj +++ b/end2end/EndToEndTests/EndToEndTests.csproj @@ -14,6 +14,10 @@ Always authors.mssql.schema.sql + + Always + types.mssql.schema.sql + Always authors.sqlite.schema.sql diff --git a/end2end/EndToEndTests/GlobalSetup.cs b/end2end/EndToEndTests/GlobalSetup.cs index 46a78dad3..f45a31aec 100644 --- a/end2end/EndToEndTests/GlobalSetup.cs +++ b/end2end/EndToEndTests/GlobalSetup.cs @@ -37,7 +37,8 @@ private static void SetupMssqlDb() Execute(connection, $"DROP DATABASE IF EXISTS [{database}]"); Execute(connection, $"CREATE DATABASE [{database}]"); connection.ChangeDatabase(database); - Execute(connection, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "authors.mssql.schema.sql"))); + foreach (var schemaFile in new[] { "authors.mssql.schema.sql", "types.mssql.schema.sql" }) + Execute(connection, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, schemaFile))); } } diff --git a/end2end/EndToEndTests/MssqlDapperTester.cs b/end2end/EndToEndTests/MssqlDapperTester.cs index 388f7bef2..d36cf50cd 100644 --- a/end2end/EndToEndTests/MssqlDapperTester.cs +++ b/end2end/EndToEndTests/MssqlDapperTester.cs @@ -14,5 +14,9 @@ public partial class MssqlDapperTester public async Task EmptyTestsTables() { await QuerySql.DeleteAllAuthorsAsync(); + await QuerySql.TruncateMssqlNumericTypesAsync(); + await QuerySql.TruncateMssqlStringTypesAsync(); + await QuerySql.TruncateMssqlDatetimeTypesAsync(); + await QuerySql.TruncateMssqlBinaryTypesAsync(); } } diff --git a/end2end/EndToEndTests/MssqlDapperTester.generated.cs b/end2end/EndToEndTests/MssqlDapperTester.generated.cs index 46444cb9b..41b753304 100644 --- a/end2end/EndToEndTests/MssqlDapperTester.generated.cs +++ b/end2end/EndToEndTests/MssqlDapperTester.generated.cs @@ -369,5 +369,362 @@ void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuth Assert.That(x.Bio, Is.EqualTo(y.Bio)); } } + + [Test] + public async Task TestMssqlIntegerTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlNumericTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlNumericTypesAsync(new QuerySql.InsertMssqlNumericTypesArgs { CBit = withValues ? (bool? )true : null, CTinyint = withValues ? (byte? )(byte)13 : null, CSmallint = withValues ? (short? )(short)2084 : null, CInt = withValues ? (int? )-54355 : null, CBigint = withValues ? (long? )9787668656L : null, CReal = withValues ? (float? )3.4f : null, CFloat = withValues ? (double? )21.214312452534 : null, CDecimal = withValues ? (decimal? )11.0986m : null, CNumeric = withValues ? (decimal? )-31.5556m : null, CMoney = withValues ? (decimal? )922.5000m : null, CSmallmoney = withValues ? (decimal? )12.3400m : null }); + var expected = new QuerySql.GetMssqlNumericTypesRow + { + CBit = withValues ? (bool? )true : null, + CTinyint = withValues ? (byte? )(byte)13 : null, + CSmallint = withValues ? (short? )(short)2084 : null, + CInt = withValues ? (int? )-54355 : null, + CBigint = withValues ? (long? )9787668656L : null, + CReal = withValues ? (float? )3.4f : null, + CFloat = withValues ? (double? )21.214312452534 : null, + CDecimal = withValues ? (decimal? )11.0986m : null, + CNumeric = withValues ? (decimal? )-31.5556m : null, + CMoney = withValues ? (decimal? )922.5000m : null, + CSmallmoney = withValues ? (decimal? )12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesRow x, QuerySql.GetMssqlNumericTypesRow y) + { + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlIntegerCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlNumericTypesBatchArgs { CBit = withValues ? (bool? )true : null, CTinyint = withValues ? (byte? )(byte)13 : null, CSmallint = withValues ? (short? )(short)2084 : null, CInt = withValues ? (int? )-54355 : null, CBigint = withValues ? (long? )9787668656L : null, CReal = withValues ? (float? )3.4f : null, CFloat = withValues ? (double? )21.214312452534 : null, CDecimal = withValues ? (decimal? )11.0986m : null, CNumeric = withValues ? (decimal? )-31.5556m : null, CMoney = withValues ? (decimal? )922.5000m : null, CSmallmoney = withValues ? (decimal? )12.3400m : null }).ToList(); + await QuerySql.InsertMssqlNumericTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlNumericTypesCntRow + { + Cnt = batchSize, + CBit = withValues ? (bool? )true : null, + CTinyint = withValues ? (byte? )(byte)13 : null, + CSmallint = withValues ? (short? )(short)2084 : null, + CInt = withValues ? (int? )-54355 : null, + CBigint = withValues ? (long? )9787668656L : null, + CReal = withValues ? (float? )3.4f : null, + CFloat = withValues ? (double? )21.214312452534 : null, + CDecimal = withValues ? (decimal? )11.0986m : null, + CNumeric = withValues ? (decimal? )-31.5556m : null, + CMoney = withValues ? (decimal? )922.5000m : null, + CSmallmoney = withValues ? (decimal? )12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesCntRow x, QuerySql.GetMssqlNumericTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + + [Test] + public async Task TestMssqlStringTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlStringTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlStringTypesAsync(new QuerySql.InsertMssqlStringTypesArgs { CChar = withValues ? (string? )"abcdefghij" : null, CNchar = withValues ? (string? )"\u1857\u2649abcdefgh" : null, CVarchar = withValues ? (string? )"Holiday in Cambodia" : null, CNvarchar = withValues ? (string? )"Sheena \u2649 is a Punk Rocker" : null, CVarcharMax = withValues ? (string? )"London's Calling" : null, CNvarcharMax = withValues ? (string? )"Police & Thieves \u1857" : null }); + var expected = new QuerySql.GetMssqlStringTypesRow + { + CChar = withValues ? (string? )"abcdefghij" : null, + CNchar = withValues ? (string? )"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string? )"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string? )"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string? )"London's Calling" : null, + CNvarcharMax = withValues ? (string? )"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlStringTypesRow x, QuerySql.GetMssqlStringTypesRow y) + { + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlStringCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlStringTypesBatchArgs { CChar = withValues ? (string? )"abcdefghij" : null, CNchar = withValues ? (string? )"\u1857\u2649abcdefgh" : null, CVarchar = withValues ? (string? )"Holiday in Cambodia" : null, CNvarchar = withValues ? (string? )"Sheena \u2649 is a Punk Rocker" : null, CVarcharMax = withValues ? (string? )"London's Calling" : null, CNvarcharMax = withValues ? (string? )"Police & Thieves \u1857" : null }).ToList(); + await QuerySql.InsertMssqlStringTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlStringTypesCntRow + { + Cnt = batchSize, + CChar = withValues ? (string? )"abcdefghij" : null, + CNchar = withValues ? (string? )"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string? )"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string? )"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string? )"London's Calling" : null, + CNvarcharMax = withValues ? (string? )"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlStringTypesCntRow x, QuerySql.GetMssqlStringTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + + [Test] + public async Task TestMssqlDateTimeTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlDatetimeTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlDatetimeTypesAsync(new QuerySql.InsertMssqlDatetimeTypesArgs { CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null }); + var expected = new QuerySql.GetMssqlDatetimeTypesRow + { + CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesRow x, QuerySql.GetMssqlDatetimeTypesRow y) + { + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlDateTimeCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlDatetimeTypesBatchArgs { CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null }).ToList(); + await QuerySql.InsertMssqlDatetimeTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlDatetimeTypesCntRow + { + Cnt = batchSize, + CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesCntRow x, QuerySql.GetMssqlDatetimeTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + + [Test] + public async Task TestMssqlBinaryTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlBinaryTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlBinaryTypesAsync(new QuerySql.InsertMssqlBinaryTypesArgs { CBinary = withValues ? (byte[]? )new byte[] { 1, 2, 3 } : null, CVarbinary = withValues ? (byte[]? )new byte[] { 4, 5, 6, 7 } : null, CVarbinaryMax = withValues ? (byte[]? )new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }); + var expected = new QuerySql.GetMssqlBinaryTypesRow + { + CBinary = withValues ? (byte[]? )new byte[] + { + 1, + 2, + 3 + } + + : null, + CVarbinary = withValues ? (byte[]? )new byte[] + { + 4, + 5, + 6, + 7 + } + + : null, + CVarbinaryMax = withValues ? (byte[]? )new byte[] + { + 8, + 9, + 10, + 11, + 12 + } + + : null, + CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesRow x, QuerySql.GetMssqlBinaryTypesRow y) + { + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlBinaryCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlBinaryTypesBatchArgs { CBinary = withValues ? (byte[]? )new byte[] { 1, 2, 3 } : null, CVarbinary = withValues ? (byte[]? )new byte[] { 4, 5, 6, 7 } : null, CVarbinaryMax = withValues ? (byte[]? )new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }).ToList(); + await QuerySql.InsertMssqlBinaryTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlBinaryTypesCntRow + { + Cnt = batchSize, + CBinary = withValues ? (byte[]? )new byte[] + { + 1, + 2, + 3 + } + + : null, + CVarbinary = withValues ? (byte[]? )new byte[] + { + 4, + 5, + 6, + 7 + } + + : null, + CVarbinaryMax = withValues ? (byte[]? )new byte[] + { + 8, + 9, + 10, + 11, + 12 + } + + : null, + CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesCntRow x, QuerySql.GetMssqlBinaryTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + + [Test] + public async Task TestMssqlTransaction() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + // the uncommitted row is locked by the transaction, so it is read from within it + var actual = await querySqlWithTx.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNotNull(actual); + await transaction.CommitAsync(); + var expected = new QuerySql.GetAuthorRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorRow x, QuerySql.GetAuthorRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestMssqlTransactionRollback() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await transaction.RollbackAsync(); + var actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNull(actual); + } } } diff --git a/end2end/EndToEndTests/MssqlTester.cs b/end2end/EndToEndTests/MssqlTester.cs index 0234036b2..388746e15 100644 --- a/end2end/EndToEndTests/MssqlTester.cs +++ b/end2end/EndToEndTests/MssqlTester.cs @@ -14,5 +14,9 @@ public partial class MssqlTester public async Task EmptyTestsTables() { await QuerySql.DeleteAllAuthorsAsync(); + await QuerySql.TruncateMssqlNumericTypesAsync(); + await QuerySql.TruncateMssqlStringTypesAsync(); + await QuerySql.TruncateMssqlDatetimeTypesAsync(); + await QuerySql.TruncateMssqlBinaryTypesAsync(); } } diff --git a/end2end/EndToEndTests/MssqlTester.generated.cs b/end2end/EndToEndTests/MssqlTester.generated.cs index 027942cd4..6c3e9cf70 100644 --- a/end2end/EndToEndTests/MssqlTester.generated.cs +++ b/end2end/EndToEndTests/MssqlTester.generated.cs @@ -369,5 +369,362 @@ void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuth Assert.That(x.Bio, Is.EqualTo(y.Bio)); } } + + [Test] + public async Task TestMssqlIntegerTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlNumericTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlNumericTypesAsync(new QuerySql.InsertMssqlNumericTypesArgs { CBit = withValues ? (bool? )true : null, CTinyint = withValues ? (byte? )(byte)13 : null, CSmallint = withValues ? (short? )(short)2084 : null, CInt = withValues ? (int? )-54355 : null, CBigint = withValues ? (long? )9787668656L : null, CReal = withValues ? (float? )3.4f : null, CFloat = withValues ? (double? )21.214312452534 : null, CDecimal = withValues ? (decimal? )11.0986m : null, CNumeric = withValues ? (decimal? )-31.5556m : null, CMoney = withValues ? (decimal? )922.5000m : null, CSmallmoney = withValues ? (decimal? )12.3400m : null }); + var expected = new QuerySql.GetMssqlNumericTypesRow + { + CBit = withValues ? (bool? )true : null, + CTinyint = withValues ? (byte? )(byte)13 : null, + CSmallint = withValues ? (short? )(short)2084 : null, + CInt = withValues ? (int? )-54355 : null, + CBigint = withValues ? (long? )9787668656L : null, + CReal = withValues ? (float? )3.4f : null, + CFloat = withValues ? (double? )21.214312452534 : null, + CDecimal = withValues ? (decimal? )11.0986m : null, + CNumeric = withValues ? (decimal? )-31.5556m : null, + CMoney = withValues ? (decimal? )922.5000m : null, + CSmallmoney = withValues ? (decimal? )12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesAsync(); + AssertSingularEquals(expected, actual.Value); + } + + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesRow x, QuerySql.GetMssqlNumericTypesRow y) + { + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlIntegerCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlNumericTypesBatchArgs { CBit = withValues ? (bool? )true : null, CTinyint = withValues ? (byte? )(byte)13 : null, CSmallint = withValues ? (short? )(short)2084 : null, CInt = withValues ? (int? )-54355 : null, CBigint = withValues ? (long? )9787668656L : null, CReal = withValues ? (float? )3.4f : null, CFloat = withValues ? (double? )21.214312452534 : null, CDecimal = withValues ? (decimal? )11.0986m : null, CNumeric = withValues ? (decimal? )-31.5556m : null, CMoney = withValues ? (decimal? )922.5000m : null, CSmallmoney = withValues ? (decimal? )12.3400m : null }).ToList(); + await QuerySql.InsertMssqlNumericTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlNumericTypesCntRow + { + Cnt = batchSize, + CBit = withValues ? (bool? )true : null, + CTinyint = withValues ? (byte? )(byte)13 : null, + CSmallint = withValues ? (short? )(short)2084 : null, + CInt = withValues ? (int? )-54355 : null, + CBigint = withValues ? (long? )9787668656L : null, + CReal = withValues ? (float? )3.4f : null, + CFloat = withValues ? (double? )21.214312452534 : null, + CDecimal = withValues ? (decimal? )11.0986m : null, + CNumeric = withValues ? (decimal? )-31.5556m : null, + CMoney = withValues ? (decimal? )922.5000m : null, + CSmallmoney = withValues ? (decimal? )12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesCntAsync(); + AssertSingularEquals(expected, actual.Value); + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesCntRow x, QuerySql.GetMssqlNumericTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + + [Test] + public async Task TestMssqlStringTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlStringTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlStringTypesAsync(new QuerySql.InsertMssqlStringTypesArgs { CChar = withValues ? (string? )"abcdefghij" : null, CNchar = withValues ? (string? )"\u1857\u2649abcdefgh" : null, CVarchar = withValues ? (string? )"Holiday in Cambodia" : null, CNvarchar = withValues ? (string? )"Sheena \u2649 is a Punk Rocker" : null, CVarcharMax = withValues ? (string? )"London's Calling" : null, CNvarcharMax = withValues ? (string? )"Police & Thieves \u1857" : null }); + var expected = new QuerySql.GetMssqlStringTypesRow + { + CChar = withValues ? (string? )"abcdefghij" : null, + CNchar = withValues ? (string? )"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string? )"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string? )"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string? )"London's Calling" : null, + CNvarcharMax = withValues ? (string? )"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesAsync(); + AssertSingularEquals(expected, actual.Value); + } + + void AssertSingularEquals(QuerySql.GetMssqlStringTypesRow x, QuerySql.GetMssqlStringTypesRow y) + { + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlStringCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlStringTypesBatchArgs { CChar = withValues ? (string? )"abcdefghij" : null, CNchar = withValues ? (string? )"\u1857\u2649abcdefgh" : null, CVarchar = withValues ? (string? )"Holiday in Cambodia" : null, CNvarchar = withValues ? (string? )"Sheena \u2649 is a Punk Rocker" : null, CVarcharMax = withValues ? (string? )"London's Calling" : null, CNvarcharMax = withValues ? (string? )"Police & Thieves \u1857" : null }).ToList(); + await QuerySql.InsertMssqlStringTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlStringTypesCntRow + { + Cnt = batchSize, + CChar = withValues ? (string? )"abcdefghij" : null, + CNchar = withValues ? (string? )"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string? )"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string? )"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string? )"London's Calling" : null, + CNvarcharMax = withValues ? (string? )"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesCntAsync(); + AssertSingularEquals(expected, actual.Value); + void AssertSingularEquals(QuerySql.GetMssqlStringTypesCntRow x, QuerySql.GetMssqlStringTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + + [Test] + public async Task TestMssqlDateTimeTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlDatetimeTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlDatetimeTypesAsync(new QuerySql.InsertMssqlDatetimeTypesArgs { CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null }); + var expected = new QuerySql.GetMssqlDatetimeTypesRow + { + CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesAsync(); + AssertSingularEquals(expected, actual.Value); + } + + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesRow x, QuerySql.GetMssqlDatetimeTypesRow y) + { + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlDateTimeCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlDatetimeTypesBatchArgs { CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null }).ToList(); + await QuerySql.InsertMssqlDatetimeTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlDatetimeTypesCntRow + { + Cnt = batchSize, + CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesCntAsync(); + AssertSingularEquals(expected, actual.Value); + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesCntRow x, QuerySql.GetMssqlDatetimeTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + + [Test] + public async Task TestMssqlBinaryTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlBinaryTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlBinaryTypesAsync(new QuerySql.InsertMssqlBinaryTypesArgs { CBinary = withValues ? (byte[]? )new byte[] { 1, 2, 3 } : null, CVarbinary = withValues ? (byte[]? )new byte[] { 4, 5, 6, 7 } : null, CVarbinaryMax = withValues ? (byte[]? )new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }); + var expected = new QuerySql.GetMssqlBinaryTypesRow + { + CBinary = withValues ? (byte[]? )new byte[] + { + 1, + 2, + 3 + } + + : null, + CVarbinary = withValues ? (byte[]? )new byte[] + { + 4, + 5, + 6, + 7 + } + + : null, + CVarbinaryMax = withValues ? (byte[]? )new byte[] + { + 8, + 9, + 10, + 11, + 12 + } + + : null, + CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesAsync(); + AssertSingularEquals(expected, actual.Value); + } + + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesRow x, QuerySql.GetMssqlBinaryTypesRow y) + { + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlBinaryCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlBinaryTypesBatchArgs { CBinary = withValues ? (byte[]? )new byte[] { 1, 2, 3 } : null, CVarbinary = withValues ? (byte[]? )new byte[] { 4, 5, 6, 7 } : null, CVarbinaryMax = withValues ? (byte[]? )new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }).ToList(); + await QuerySql.InsertMssqlBinaryTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlBinaryTypesCntRow + { + Cnt = batchSize, + CBinary = withValues ? (byte[]? )new byte[] + { + 1, + 2, + 3 + } + + : null, + CVarbinary = withValues ? (byte[]? )new byte[] + { + 4, + 5, + 6, + 7 + } + + : null, + CVarbinaryMax = withValues ? (byte[]? )new byte[] + { + 8, + 9, + 10, + 11, + 12 + } + + : null, + CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesCntAsync(); + AssertSingularEquals(expected, actual.Value); + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesCntRow x, QuerySql.GetMssqlBinaryTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + + [Test] + public async Task TestMssqlTransaction() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + // the uncommitted row is locked by the transaction, so it is read from within it + var actual = await querySqlWithTx.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNotNull(actual); + await transaction.CommitAsync(); + var expected = new QuerySql.GetAuthorRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + AssertSingularEquals(expected, actual.Value); + void AssertSingularEquals(QuerySql.GetAuthorRow x, QuerySql.GetAuthorRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestMssqlTransactionRollback() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await transaction.RollbackAsync(); + var actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNull(actual); + } } } diff --git a/examples/MssqlDapperExample/Models.cs b/examples/MssqlDapperExample/Models.cs index 28f50080d..e2d8568a5 100644 --- a/examples/MssqlDapperExample/Models.cs +++ b/examples/MssqlDapperExample/Models.cs @@ -1,4 +1,5 @@ // auto-generated by sqlc - do not edit +using System; using System.Linq; namespace MssqlDapperExampleGen; @@ -14,4 +15,43 @@ public class Book public required string Name { get; init; } public required long AuthorId { get; init; } public string? Description { get; init; } +}; +public class MssqlNumericType +{ + public bool? CBit { get; init; } + public byte? CTinyint { get; init; } + public short? CSmallint { get; init; } + public int? CInt { get; init; } + public long? CBigint { get; init; } + public float? CReal { get; init; } + public double? CFloat { get; init; } + public decimal? CDecimal { get; init; } + public decimal? CNumeric { get; init; } + public decimal? CMoney { get; init; } + public decimal? CSmallmoney { get; init; } +}; +public class MssqlStringType +{ + public string? CChar { get; init; } + public string? CNchar { get; init; } + public string? CVarchar { get; init; } + public string? CNvarchar { get; init; } + public string? CVarcharMax { get; init; } + public string? CNvarcharMax { get; init; } +}; +public class MssqlDatetimeType +{ + public DateTime? CDate { get; init; } + public DateTime? CDatetime { get; init; } + public DateTime? CDatetime2 { get; init; } + public DateTime? CSmalldatetime { get; init; } + public DateTimeOffset? CDatetimeoffset { get; init; } + public TimeSpan? CTime { get; init; } +}; +public class MssqlBinaryType +{ + public byte[]? CBinary { get; init; } + public byte[]? CVarbinary { get; init; } + public byte[]? CVarbinaryMax { get; init; } + public Guid? CUniqueidentifier { get; init; } }; \ No newline at end of file diff --git a/examples/MssqlDapperExample/QuerySql.cs b/examples/MssqlDapperExample/QuerySql.cs index b2dfe1e16..a7eebec72 100644 --- a/examples/MssqlDapperExample/QuerySql.cs +++ b/examples/MssqlDapperExample/QuerySql.cs @@ -490,7 +490,7 @@ public async Task> GetAuthorsByBookNameAsync(GetAu await connection.OpenAsync(); using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection)) { - command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; using (var reader = await command.ExecuteReaderAsync()) { var result = new List(); @@ -508,7 +508,7 @@ public async Task> GetAuthorsByBookNameAsync(GetAu { command.CommandText = GetAuthorsByBookNameSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; using (var reader = await command.ExecuteReaderAsync()) { var result = new List(); @@ -518,4 +518,767 @@ public async Task> GetAuthorsByBookNameAsync(GetAu } } } + + private const string InsertMssqlNumericTypesSql = @" + INSERT INTO mssql_numeric_types + ( + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + ) + VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);"; + public class InsertMssqlNumericTypesArgs + { + public bool? CBit { get; init; } + public byte? CTinyint { get; init; } + public short? CSmallint { get; init; } + public int? CInt { get; init; } + public long? CBigint { get; init; } + public float? CReal { get; init; } + public double? CFloat { get; init; } + public decimal? CDecimal { get; init; } + public decimal? CNumeric { get; init; } + public decimal? CMoney { get; init; } + public decimal? CSmallmoney { get; init; } + }; + public async Task InsertMssqlNumericTypesAsync(InsertMssqlNumericTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_bit", args.CBit); + queryParams.Add("c_tinyint", args.CTinyint); + queryParams.Add("c_smallint", args.CSmallint); + queryParams.Add("c_int", args.CInt); + queryParams.Add("c_bigint", args.CBigint); + queryParams.Add("c_real", args.CReal); + queryParams.Add("c_float", args.CFloat); + queryParams.Add("c_decimal", args.CDecimal); + queryParams.Add("c_numeric", args.CNumeric); + queryParams.Add("c_money", args.CMoney); + queryParams.Add("c_smallmoney", args.CSmallmoney); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlNumericTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlNumericTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlNumericTypesBatchSql = @"INSERT INTO mssql_numeric_types + ( + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + ) + VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);"; + public class InsertMssqlNumericTypesBatchArgs + { + public bool? CBit { get; init; } + public byte? CTinyint { get; init; } + public short? CSmallint { get; init; } + public int? CInt { get; init; } + public long? CBigint { get; init; } + public float? CReal { get; init; } + public double? CFloat { get; init; } + public decimal? CDecimal { get; init; } + public decimal? CNumeric { get; init; } + public decimal? CMoney { get; init; } + public decimal? CSmallmoney { get; init; } + }; + public async Task InsertMssqlNumericTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_bit", typeof(bool)); + dataTable.Columns.Add("c_tinyint", typeof(byte)); + dataTable.Columns.Add("c_smallint", typeof(short)); + dataTable.Columns.Add("c_int", typeof(int)); + dataTable.Columns.Add("c_bigint", typeof(long)); + dataTable.Columns.Add("c_real", typeof(float)); + dataTable.Columns.Add("c_float", typeof(double)); + dataTable.Columns.Add("c_decimal", typeof(decimal)); + dataTable.Columns.Add("c_numeric", typeof(decimal)); + dataTable.Columns.Add("c_money", typeof(decimal)); + dataTable.Columns.Add("c_smallmoney", typeof(decimal)); + foreach (var row in args) + dataTable.Rows.Add(row.CBit, row.CTinyint, row.CSmallint, row.CInt, row.CBigint, row.CReal, row.CFloat, row.CDecimal, row.CNumeric, row.CMoney, row.CSmallmoney); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_numeric_types]"; + bulkCopy.ColumnMappings.Add("c_bit", "c_bit"); + bulkCopy.ColumnMappings.Add("c_tinyint", "c_tinyint"); + bulkCopy.ColumnMappings.Add("c_smallint", "c_smallint"); + bulkCopy.ColumnMappings.Add("c_int", "c_int"); + bulkCopy.ColumnMappings.Add("c_bigint", "c_bigint"); + bulkCopy.ColumnMappings.Add("c_real", "c_real"); + bulkCopy.ColumnMappings.Add("c_float", "c_float"); + bulkCopy.ColumnMappings.Add("c_decimal", "c_decimal"); + bulkCopy.ColumnMappings.Add("c_numeric", "c_numeric"); + bulkCopy.ColumnMappings.Add("c_money", "c_money"); + bulkCopy.ColumnMappings.Add("c_smallmoney", "c_smallmoney"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlNumericTypesSql = "SELECT TOP 1 c_bit, c_tinyint, c_smallint, c_int, c_bigint, c_real, c_float, c_decimal, c_numeric, c_money, c_smallmoney FROM mssql_numeric_types;"; + public class GetMssqlNumericTypesRow + { + public bool? CBit { get; init; } + public byte? CTinyint { get; init; } + public short? CSmallint { get; init; } + public int? CInt { get; init; } + public long? CBigint { get; init; } + public float? CReal { get; init; } + public double? CFloat { get; init; } + public decimal? CDecimal { get; init; } + public decimal? CNumeric { get; init; } + public decimal? CMoney { get; init; } + public decimal? CSmallmoney { get; init; } + }; + public async Task GetMssqlNumericTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlNumericTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + FROM mssql_numeric_types + GROUP BY + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney;"; + public class GetMssqlNumericTypesCntRow + { + public required int Cnt { get; init; } + public bool? CBit { get; init; } + public byte? CTinyint { get; init; } + public short? CSmallint { get; init; } + public int? CInt { get; init; } + public long? CBigint { get; init; } + public float? CReal { get; init; } + public double? CFloat { get; init; } + public decimal? CDecimal { get; init; } + public decimal? CNumeric { get; init; } + public decimal? CMoney { get; init; } + public decimal? CSmallmoney { get; init; } + }; + public async Task GetMssqlNumericTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlNumericTypesSql = "TRUNCATE TABLE mssql_numeric_types;"; + public async Task TruncateMssqlNumericTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlNumericTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlNumericTypesSql, transaction: this.Transaction); + } + + private const string InsertMssqlStringTypesSql = @" + INSERT INTO mssql_string_types + ( + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + ) + VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);"; + public class InsertMssqlStringTypesArgs + { + public string? CChar { get; init; } + public string? CNchar { get; init; } + public string? CVarchar { get; init; } + public string? CNvarchar { get; init; } + public string? CVarcharMax { get; init; } + public string? CNvarcharMax { get; init; } + }; + public async Task InsertMssqlStringTypesAsync(InsertMssqlStringTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_char", args.CChar); + queryParams.Add("c_nchar", args.CNchar); + queryParams.Add("c_varchar", args.CVarchar); + queryParams.Add("c_nvarchar", args.CNvarchar); + queryParams.Add("c_varchar_max", args.CVarcharMax); + queryParams.Add("c_nvarchar_max", args.CNvarcharMax); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlStringTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlStringTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlStringTypesBatchSql = @"INSERT INTO mssql_string_types + ( + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + ) + VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);"; + public class InsertMssqlStringTypesBatchArgs + { + public string? CChar { get; init; } + public string? CNchar { get; init; } + public string? CVarchar { get; init; } + public string? CNvarchar { get; init; } + public string? CVarcharMax { get; init; } + public string? CNvarcharMax { get; init; } + }; + public async Task InsertMssqlStringTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_char", typeof(string)); + dataTable.Columns.Add("c_nchar", typeof(string)); + dataTable.Columns.Add("c_varchar", typeof(string)); + dataTable.Columns.Add("c_nvarchar", typeof(string)); + dataTable.Columns.Add("c_varchar_max", typeof(string)); + dataTable.Columns.Add("c_nvarchar_max", typeof(string)); + foreach (var row in args) + dataTable.Rows.Add(row.CChar, row.CNchar, row.CVarchar, row.CNvarchar, row.CVarcharMax, row.CNvarcharMax); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_string_types]"; + bulkCopy.ColumnMappings.Add("c_char", "c_char"); + bulkCopy.ColumnMappings.Add("c_nchar", "c_nchar"); + bulkCopy.ColumnMappings.Add("c_varchar", "c_varchar"); + bulkCopy.ColumnMappings.Add("c_nvarchar", "c_nvarchar"); + bulkCopy.ColumnMappings.Add("c_varchar_max", "c_varchar_max"); + bulkCopy.ColumnMappings.Add("c_nvarchar_max", "c_nvarchar_max"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlStringTypesSql = "SELECT TOP 1 c_char, c_nchar, c_varchar, c_nvarchar, c_varchar_max, c_nvarchar_max FROM mssql_string_types;"; + public class GetMssqlStringTypesRow + { + public string? CChar { get; init; } + public string? CNchar { get; init; } + public string? CVarchar { get; init; } + public string? CNvarchar { get; init; } + public string? CVarcharMax { get; init; } + public string? CNvarcharMax { get; init; } + }; + public async Task GetMssqlStringTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlStringTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + FROM mssql_string_types + GROUP BY + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max;"; + public class GetMssqlStringTypesCntRow + { + public required int Cnt { get; init; } + public string? CChar { get; init; } + public string? CNchar { get; init; } + public string? CVarchar { get; init; } + public string? CNvarchar { get; init; } + public string? CVarcharMax { get; init; } + public string? CNvarcharMax { get; init; } + }; + public async Task GetMssqlStringTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlStringTypesSql = "TRUNCATE TABLE mssql_string_types;"; + public async Task TruncateMssqlStringTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlStringTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlStringTypesSql, transaction: this.Transaction); + } + + private const string InsertMssqlDatetimeTypesSql = @" + INSERT INTO mssql_datetime_types + ( + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + ) + VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);"; + public class InsertMssqlDatetimeTypesArgs + { + public DateTime? CDate { get; init; } + public DateTime? CDatetime { get; init; } + public DateTime? CDatetime2 { get; init; } + public DateTime? CSmalldatetime { get; init; } + public DateTimeOffset? CDatetimeoffset { get; init; } + public TimeSpan? CTime { get; init; } + }; + public async Task InsertMssqlDatetimeTypesAsync(InsertMssqlDatetimeTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_date", args.CDate); + queryParams.Add("c_datetime", args.CDatetime); + queryParams.Add("c_datetime2", args.CDatetime2); + queryParams.Add("c_smalldatetime", args.CSmalldatetime); + queryParams.Add("c_datetimeoffset", args.CDatetimeoffset); + queryParams.Add("c_time", args.CTime); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlDatetimeTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlDatetimeTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlDatetimeTypesBatchSql = @"INSERT INTO mssql_datetime_types + ( + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + ) + VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);"; + public class InsertMssqlDatetimeTypesBatchArgs + { + public DateTime? CDate { get; init; } + public DateTime? CDatetime { get; init; } + public DateTime? CDatetime2 { get; init; } + public DateTime? CSmalldatetime { get; init; } + public DateTimeOffset? CDatetimeoffset { get; init; } + public TimeSpan? CTime { get; init; } + }; + public async Task InsertMssqlDatetimeTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_date", typeof(DateTime)); + dataTable.Columns.Add("c_datetime", typeof(DateTime)); + dataTable.Columns.Add("c_datetime2", typeof(DateTime)); + dataTable.Columns.Add("c_smalldatetime", typeof(DateTime)); + dataTable.Columns.Add("c_datetimeoffset", typeof(DateTimeOffset)); + dataTable.Columns.Add("c_time", typeof(TimeSpan)); + foreach (var row in args) + dataTable.Rows.Add(row.CDate, row.CDatetime, row.CDatetime2, row.CSmalldatetime, row.CDatetimeoffset, row.CTime); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_datetime_types]"; + bulkCopy.ColumnMappings.Add("c_date", "c_date"); + bulkCopy.ColumnMappings.Add("c_datetime", "c_datetime"); + bulkCopy.ColumnMappings.Add("c_datetime2", "c_datetime2"); + bulkCopy.ColumnMappings.Add("c_smalldatetime", "c_smalldatetime"); + bulkCopy.ColumnMappings.Add("c_datetimeoffset", "c_datetimeoffset"); + bulkCopy.ColumnMappings.Add("c_time", "c_time"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlDatetimeTypesSql = "SELECT TOP 1 c_date, c_datetime, c_datetime2, c_smalldatetime, c_datetimeoffset, c_time FROM mssql_datetime_types;"; + public class GetMssqlDatetimeTypesRow + { + public DateTime? CDate { get; init; } + public DateTime? CDatetime { get; init; } + public DateTime? CDatetime2 { get; init; } + public DateTime? CSmalldatetime { get; init; } + public DateTimeOffset? CDatetimeoffset { get; init; } + public TimeSpan? CTime { get; init; } + }; + public async Task GetMssqlDatetimeTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlDatetimeTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + FROM mssql_datetime_types + GROUP BY + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time;"; + public class GetMssqlDatetimeTypesCntRow + { + public required int Cnt { get; init; } + public DateTime? CDate { get; init; } + public DateTime? CDatetime { get; init; } + public DateTime? CDatetime2 { get; init; } + public DateTime? CSmalldatetime { get; init; } + public DateTimeOffset? CDatetimeoffset { get; init; } + public TimeSpan? CTime { get; init; } + }; + public async Task GetMssqlDatetimeTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlDatetimeTypesSql = "TRUNCATE TABLE mssql_datetime_types;"; + public async Task TruncateMssqlDatetimeTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlDatetimeTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlDatetimeTypesSql, transaction: this.Transaction); + } + + private const string InsertMssqlBinaryTypesSql = @" + INSERT INTO mssql_binary_types + ( + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + ) + VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);"; + public class InsertMssqlBinaryTypesArgs + { + public byte[]? CBinary { get; init; } + public byte[]? CVarbinary { get; init; } + public byte[]? CVarbinaryMax { get; init; } + public Guid? CUniqueidentifier { get; init; } + }; + public async Task InsertMssqlBinaryTypesAsync(InsertMssqlBinaryTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_binary", new Utils.BinaryParameter(args.CBinary)); + queryParams.Add("c_varbinary", new Utils.BinaryParameter(args.CVarbinary)); + queryParams.Add("c_varbinary_max", new Utils.BinaryParameter(args.CVarbinaryMax)); + queryParams.Add("c_uniqueidentifier", args.CUniqueidentifier); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlBinaryTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlBinaryTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlBinaryTypesBatchSql = @"INSERT INTO mssql_binary_types + ( + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + ) + VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);"; + public class InsertMssqlBinaryTypesBatchArgs + { + public byte[]? CBinary { get; init; } + public byte[]? CVarbinary { get; init; } + public byte[]? CVarbinaryMax { get; init; } + public Guid? CUniqueidentifier { get; init; } + }; + public async Task InsertMssqlBinaryTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_binary", typeof(byte[])); + dataTable.Columns.Add("c_varbinary", typeof(byte[])); + dataTable.Columns.Add("c_varbinary_max", typeof(byte[])); + dataTable.Columns.Add("c_uniqueidentifier", typeof(Guid)); + foreach (var row in args) + dataTable.Rows.Add(row.CBinary ?? (object)DBNull.Value, row.CVarbinary ?? (object)DBNull.Value, row.CVarbinaryMax ?? (object)DBNull.Value, row.CUniqueidentifier); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_binary_types]"; + bulkCopy.ColumnMappings.Add("c_binary", "c_binary"); + bulkCopy.ColumnMappings.Add("c_varbinary", "c_varbinary"); + bulkCopy.ColumnMappings.Add("c_varbinary_max", "c_varbinary_max"); + bulkCopy.ColumnMappings.Add("c_uniqueidentifier", "c_uniqueidentifier"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlBinaryTypesSql = "SELECT TOP 1 c_binary, c_varbinary, c_varbinary_max, c_uniqueidentifier FROM mssql_binary_types;"; + public class GetMssqlBinaryTypesRow + { + public byte[]? CBinary { get; init; } + public byte[]? CVarbinary { get; init; } + public byte[]? CVarbinaryMax { get; init; } + public Guid? CUniqueidentifier { get; init; } + }; + public async Task GetMssqlBinaryTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlBinaryTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + FROM mssql_binary_types + GROUP BY + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier;"; + public class GetMssqlBinaryTypesCntRow + { + public required int Cnt { get; init; } + public byte[]? CBinary { get; init; } + public byte[]? CVarbinary { get; init; } + public byte[]? CVarbinaryMax { get; init; } + public Guid? CUniqueidentifier { get; init; } + }; + public async Task GetMssqlBinaryTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlBinaryTypesSql = "TRUNCATE TABLE mssql_binary_types;"; + public async Task TruncateMssqlBinaryTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlBinaryTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlBinaryTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlFunctionsSql = @" + SELECT + MAX(c_int) AS max_int, + MAX(c_varchar) AS max_varchar, + MAX(c_datetime2) AS max_datetime2 + FROM mssql_numeric_types + CROSS JOIN mssql_string_types + CROSS JOIN mssql_datetime_types;"; + public class GetMssqlFunctionsRow + { + public int? MaxInt { get; init; } + public string? MaxVarchar { get; init; } + public DateTime? MaxDatetime2 { get; init; } + }; + public async Task GetMssqlFunctionsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlFunctionsSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlFunctionsSql, transaction: this.Transaction); + } } \ No newline at end of file diff --git a/examples/MssqlDapperExample/Utils.cs b/examples/MssqlDapperExample/Utils.cs index 073f573c3..3d7bfaf3c 100644 --- a/examples/MssqlDapperExample/Utils.cs +++ b/examples/MssqlDapperExample/Utils.cs @@ -1,5 +1,6 @@ // auto-generated by sqlc - do not edit using Dapper; +using Microsoft.Data.SqlClient; using System; using System.Data; using System.Linq; @@ -9,6 +10,7 @@ public static class Utils { public static void ConfigureSqlMapper() { + SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2); } public static string TransformQueryForSliceArgs(string originalSql, int sliceSize, string paramName) @@ -16,4 +18,22 @@ public static string TransformQueryForSliceArgs(string originalSql, int sliceSiz var paramArgs = Enumerable.Range(0, sliceSize).Select(i => $"@{paramName}Arg{i}").ToList(); return originalSql.Replace($"/*SLICE:{paramName}*/@{paramName}", string.Join(",", paramArgs)); } + + public class BinaryParameter : SqlMapper.ICustomQueryParameter + { + private readonly byte[]? _value; + public BinaryParameter(byte[]? value) + { + _value = value; + } + + public void AddParameter(IDbCommand command, string name) + { + var parameter = new SqlParameter(name, SqlDbType.VarBinary) + { + Value = (object? )_value ?? DBNull.Value + }; + command.Parameters.Add(parameter); + } + } } \ No newline at end of file diff --git a/examples/MssqlDapperExample/request.json b/examples/MssqlDapperExample/request.json index c6b2a564f..4cb0ed3de 100644 --- a/examples/MssqlDapperExample/request.json +++ b/examples/MssqlDapperExample/request.json @@ -3,10 +3,12 @@ "version": "2", "engine": "mssql", "schema": [ - "examples/config/mssql/authors/schema.sql" + "examples/config/mssql/authors/schema.sql", + "examples/config/mssql/types/schema.sql" ], "queries": [ - "examples/config/mssql/authors/query.sql" + "examples/config/mssql/authors/query.sql", + "examples/config/mssql/types/query.sql" ], "codegen": { "out": "examples/MssqlDapperExample", @@ -123,6 +125,335 @@ } } ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "columns": [ + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + } + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + } + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + } + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + } + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + } + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + } + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + } + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + } + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + } + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_string_types" + }, + "columns": [ + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + } + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + } + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + } + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + } + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "columns": [ + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + } + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + } + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + } + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + } + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + } + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_binary_types" + }, + "columns": [ + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + } + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + } + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + } + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + } + } + ] } ] }, @@ -904,6 +1235,1770 @@ } ], "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", + "name": "InsertMssqlNumericTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_bit", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + } + }, + { + "number": 2, + "column": { + "name": "c_tinyint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + } + }, + { + "number": 3, + "column": { + "name": "c_smallint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + } + }, + { + "number": 4, + "column": { + "name": "c_int", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + } + }, + { + "number": 5, + "column": { + "name": "c_bigint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + } + }, + { + "number": 6, + "column": { + "name": "c_real", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + } + }, + { + "number": 7, + "column": { + "name": "c_float", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + } + }, + { + "number": 8, + "column": { + "name": "c_decimal", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + } + }, + { + "number": 9, + "column": { + "name": "c_numeric", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + } + }, + { + "number": 10, + "column": { + "name": "c_money", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + } + }, + { + "number": 11, + "column": { + "name": "c_smallmoney", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + } + ], + "comments": [ + " Numeric types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_numeric_types" + } + }, + { + "text": "INSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", + "name": "InsertMssqlNumericTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_bit", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + } + }, + { + "number": 2, + "column": { + "name": "c_tinyint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + } + }, + { + "number": 3, + "column": { + "name": "c_smallint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + } + }, + { + "number": 4, + "column": { + "name": "c_int", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + } + }, + { + "number": 5, + "column": { + "name": "c_bigint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + } + }, + { + "number": 6, + "column": { + "name": "c_real", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + } + }, + { + "number": 7, + "column": { + "name": "c_float", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + } + }, + { + "number": 8, + "column": { + "name": "c_decimal", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + } + }, + { + "number": 9, + "column": { + "name": "c_numeric", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + } + }, + { + "number": 10, + "column": { + "name": "c_money", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + } + }, + { + "number": 11, + "column": { + "name": "c_smallmoney", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_numeric_types" + } + }, + { + "text": "SELECT TOP 1 c_bit, c_tinyint, c_smallint, c_int, c_bigint, c_real, c_float, c_decimal, c_numeric, c_money, c_smallmoney FROM mssql_numeric_types;", + "name": "GetMssqlNumericTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\nFROM mssql_numeric_types\nGROUP BY\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney;", + "name": "GetMssqlNumericTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_numeric_types;", + "name": "TruncateMssqlNumericTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_string_types\n(\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\n)\nVALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);", + "name": "InsertMssqlStringTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_char", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + } + }, + { + "number": 2, + "column": { + "name": "c_nchar", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + } + }, + { + "number": 3, + "column": { + "name": "c_varchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + } + }, + { + "number": 4, + "column": { + "name": "c_nvarchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + } + }, + { + "number": 5, + "column": { + "name": "c_varchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + } + }, + { + "number": 6, + "column": { + "name": "c_nvarchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + } + ], + "comments": [ + " String types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_string_types" + } + }, + { + "text": "INSERT INTO mssql_string_types\n(\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\n)\nVALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);", + "name": "InsertMssqlStringTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_char", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + } + }, + { + "number": 2, + "column": { + "name": "c_nchar", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + } + }, + { + "number": 3, + "column": { + "name": "c_varchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + } + }, + { + "number": 4, + "column": { + "name": "c_nvarchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + } + }, + { + "number": 5, + "column": { + "name": "c_varchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + } + }, + { + "number": 6, + "column": { + "name": "c_nvarchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_string_types" + } + }, + { + "text": "SELECT TOP 1 c_char, c_nchar, c_varchar, c_nvarchar, c_varchar_max, c_nvarchar_max FROM mssql_string_types;", + "name": "GetMssqlStringTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\nFROM mssql_string_types\nGROUP BY\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max;", + "name": "GetMssqlStringTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_string_types;", + "name": "TruncateMssqlStringTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_datetime_types\n(\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\n)\nVALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);", + "name": "InsertMssqlDatetimeTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_date", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + } + }, + { + "number": 2, + "column": { + "name": "c_datetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + } + }, + { + "number": 3, + "column": { + "name": "c_datetime2", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + } + }, + { + "number": 4, + "column": { + "name": "c_smalldatetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + } + }, + { + "number": 5, + "column": { + "name": "c_datetimeoffset", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + } + }, + { + "number": 6, + "column": { + "name": "c_time", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + } + ], + "comments": [ + " Datetime types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_datetime_types" + } + }, + { + "text": "INSERT INTO mssql_datetime_types\n(\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\n)\nVALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);", + "name": "InsertMssqlDatetimeTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_date", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + } + }, + { + "number": 2, + "column": { + "name": "c_datetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + } + }, + { + "number": 3, + "column": { + "name": "c_datetime2", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + } + }, + { + "number": 4, + "column": { + "name": "c_smalldatetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + } + }, + { + "number": 5, + "column": { + "name": "c_datetimeoffset", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + } + }, + { + "number": 6, + "column": { + "name": "c_time", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_datetime_types" + } + }, + { + "text": "SELECT TOP 1 c_date, c_datetime, c_datetime2, c_smalldatetime, c_datetimeoffset, c_time FROM mssql_datetime_types;", + "name": "GetMssqlDatetimeTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\nFROM mssql_datetime_types\nGROUP BY\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time;", + "name": "GetMssqlDatetimeTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_datetime_types;", + "name": "TruncateMssqlDatetimeTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_binary_types\n(\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\n)\nVALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);", + "name": "InsertMssqlBinaryTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_binary", + "length": 3, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + } + }, + { + "number": 2, + "column": { + "name": "c_varbinary", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + } + }, + { + "number": 3, + "column": { + "name": "c_varbinary_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + } + }, + { + "number": 4, + "column": { + "name": "c_uniqueidentifier", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + } + ], + "comments": [ + " Binary types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_binary_types" + } + }, + { + "text": "INSERT INTO mssql_binary_types\n(\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\n)\nVALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);", + "name": "InsertMssqlBinaryTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_binary", + "length": 3, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + } + }, + { + "number": 2, + "column": { + "name": "c_varbinary", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + } + }, + { + "number": 3, + "column": { + "name": "c_varbinary_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + } + }, + { + "number": 4, + "column": { + "name": "c_uniqueidentifier", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_binary_types" + } + }, + { + "text": "SELECT TOP 1 c_binary, c_varbinary, c_varbinary_max, c_uniqueidentifier FROM mssql_binary_types;", + "name": "GetMssqlBinaryTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\nFROM mssql_binary_types\nGROUP BY\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier;", + "name": "GetMssqlBinaryTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_binary_types;", + "name": "TruncateMssqlBinaryTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nSELECT\n MAX(c_int) AS max_int,\n MAX(c_varchar) AS max_varchar,\n MAX(c_datetime2) AS max_datetime2\nFROM mssql_numeric_types\nCROSS JOIN mssql_string_types\nCROSS JOIN mssql_datetime_types;", + "name": "GetMssqlFunctions", + "cmd": ":one", + "columns": [ + { + "name": "max_int", + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "max_varchar", + "length": -1, + "type": { + "name": "varchar" + } + }, + { + "name": "max_datetime2", + "length": -1, + "type": { + "name": "datetime2" + } + } + ], + "comments": [ + " Functions " + ], + "filename": "query.sql" } ], "sqlc_version": "v1.31.1", diff --git a/examples/MssqlDapperExample/request.message b/examples/MssqlDapperExample/request.message index 65e0d9f050b3545ada83b35a8b0ba8c4de49e0e0..1b18f241b7938fba0647c9bb47443f959b6625af 100644 GIT binary patch literal 25003 zcmeGlS#KmqIX(7x?D8H?ud^(j1T_swyd!6A4@gPuTs~%Z&3b2?@i?0U;*4k7-p-C^ zyxrp@b`lVZgak-D7=gqC5-$i|kN}C}Av__JAAkfxzzYZ|5fX<02^695eRXtKch^kM z>;cI#DScSUat3SOtFGDF--X6OJ?lPoJpu9Rp6%`jmnPg z?N+LG0TR}%6^DDyZdj96tKx3kt+OuFda3DNftn4w_0XAV__XKQY#=M=4@p3$s4qga#BU}E!ml#DQ(Zbb1J z1Ej>}1u|N*y{hZ%wwz`|#!IYyXdp_gygfwj_OWt;a@s~?Z^w3>>INlyeSxH_8=Fo` z?x=H<&9nh41{9FNm1~V@dV!1pIH$1>gwX?>5hKGJ9crUQqygR&gj6@Yol3pl8$iH^ zXmzL^A$|rx(rNYv4W>y69i|C?0mcO~jHp8L?_rkGA``(f04z_2svE9dsrLYC$QZ;H z$a^#aV9t|Fbz`gEtn@OZjFF~I4z&@&QUK{9E@I8DI)L|Sy_7%$uy(xep>~3ZG_Ygp zln(z5GH{&KZU8ts&z)w&-tPflFi+Zq&XcBKo|niGPF%f$J7N$@UL58zB4uIbe;puq z`MjnxUh`V6)7a*$_82ZXO2KJT>R59V7Me-RBZMX>UI`E}EOw(S2rzY8@lZQTSc=0q zkIT%Z@pLU=9I(og;1+2LsRrx-Tq*>6BB+q88VgDA)Tx^O6glb-YXjuDyvm(%C67@j z(ioXy(4Qs8f?@UsIy7dbeErHG`H;Wn<7~}qp!-|Sj?Gv7EUr41$x}*bgam+1D5qlr z=(dFD7O;DP1+x&%s8kdgfVoOW1DLt#OetwxqsEA6o;%cTCqa%9MMkfX{ zHteJcwy1fwRJ>?%!7KSx_NgC6GNp^j^Q5kf!vm?H9 zW&Ch}JkL=dLOKZ{y;xexm&}EyDc(u(?Af(^*?gQau30KxS~Jh)%ZumDLjEZzuRA+V zixn3OD^JhqqbuMPu?6ys^z2Thv45*AQ4W^qUNXc`67t&~)X+kwrfZs-t_H7_ogPl< z_mDJ2S3(n3NnW^Lr^utrg|&RCY%UkdMZq8DL}(dLn@`TKtmoIviN~>=@(cU{Qae4T zA6W$RwPkb8Nzd9Z+tutjn8;%>_EA%uZ%aoQ$N+uF+*Lg@mSE?mkOZ>Pw4C3bzOblNPgmsi&j`?v`Cr87<~KsKw!@jNc$xn#HY+{SXv z7^V2GSG<-}yH0Mn(ETA+xbt zX|-&(F=^g+&wU_kPD*Q00HwXkKHs9+Un=0zJ0^z$dX;RqZ0fkS_`veTrspceT0-`B{;*H@S3VP0Iy60{=F zz=Qd~AI`s6u)Jm#iexVfKkie%hob0KHG(d{dMtpAQ}ZI`Ovk_u6q^C`N9&lUqh>eEQ;$y|H4XrAhJ^PjT(xp{))L?us5$>v1|#fhI6pJh4`Y; zheYcP0JZU-V80B($W-uFij-uVj0P3V39L8CMGEZkcV(9`0fe>Zbi%B&1qe}uZL+zT z6*<%wA|0$WVa}#uY?ABoEgJN+k?0z%C_4 zs`0%P`GLkcjtgrrZv~dUGcwM-Ga#|SQO6Jut;ABS3QPOw7S*JPO32YPKT!k`N*kHQ zg3uFaSwF%%@IevZClv$49_REE)YtSC(E<)jSQ)2@Fz_X9ym^}&ckwOo%jht|6{AZi zAjeZa!mL3G++1=$xsr9G<-R42oRof^J^2SKPi9JMxr57{Pc9c{D;VkdV7 zYFg08ffNXjgtQImQ})T>L58$&Y&y9wwo`~mqZ@`I){RJmf~-B9hJdLm?41lcFbVwj zhnf@uH~ix4v%^DrQ4tVN?u3WL4|p)02F#E~VK18(WuG6;=xeGlc`|enC_fNEb~$2h zh{CKhJgh$x3ZEx~28!a0gA~;7Y*E??Y$X7Q%wxb1w6mb3M=+FVWo*M+fceX5OhoCLc&+t{K=2;0I*klR?o z*1FRCO@fwW{8|?k=57+u_JYoOLS^74!Ru(=T2F;6+9X(E0n^c*Wso`X_i{EAE#;1) zi=iQEuOqqS<;Z4J~UxV@OCk}xvfA>{8Vz8OlT4&6goEUp*I6WP<|{F+&nw+sB8 zgZIcD13k#25L@J2skpuhhkD+tyCW0#wvFZM0sgKrp6?f1xAY9Z$iTVW(6>mQXYtUkXdzDLV_A6bmja?Y zwth;0qos021}uL>sIZ}XQu6B8=zWLKelDJwSVm(9JDMios>IIt%4iUGA8yPI2-nGeR)g)`oetM968o2@9l6b+KEZ56YUPfZj5HP7edC7WvX1fX4Eco? z-|dr0^?l2+cd9uywhh;3T`F+5Rh7XI)8;E~r`cJ85!7c9&o~1yuyyVG#)~ zbZMEC=1HnEaYIJR%iAw9*R!(U5!n7URd^r~=y6aMN)&)tmB|u74Y}Q&xmnDFc zWPPBl;+o956jogcP-O`&X(ol@vbNhRs)SCYp@&tFQcYP`I;fJ&oxCFDn;*ewaNpS2|QQslJut|_~jTknv zu@U-s_?MpAzyw%YyePX1$90}Z6@%s2cW*ra6NrX*hDM|#7ogEe5}%+)JhT(O5Dn%{ z(p=;#(FbyC)%|Egu8{q_1}@<%QSiE)293(2 z^{THzpE7wQ`;wwIlN)pT9U_*$#6^?rPZkvVHBlqA*RIxcZ8T~k%)Hcu2Ez(S%Kd;t(UBM_C!&x0HQ<*=@4087P(G@}?j_)-Yy+H7 zs?z+is{@#!KIBK1&>{5Os$N_|htRK({hS6q=+#bdFeT0~)O++XCwweP@hlU1gZ?0J zJ+fa#&-Bv?k2NKZMe$tIf#UtAHcvPiimDLDV|1YK-~8Z!yc)(o(&UEBS0dLsym$@4e9Iq1K$6`Wi~{Gi+7GkYi_qF_-$ zEPF3IRDfHz2QO6U;|R{iY#LW_6fu)#!LOrJTF%f#(d7N>KcQ4U{`*jE9OwVu;&7k=(pII)P&V0v6e-$OEd7E*AQAUCGvMooW^@|{d8^Ww~z zkIkH!$^Itwiq(A4c3pTsbjfuf^`z~3Fe!K`AKu7UKfd4LQGI+vp__O4BaiP-27{o# z4G+pL6L7%H`6MoBJ S4W6amt5;m-fCAtZUi%MI?+T^> delta 58 zcmV-A0LA~S!vX0r4+@+D3IQ?_1#NS4acmkWkzpQ@EeereDUq+h0;U9$aRCzo^aHbo Q0rUa0bPQG$vqNBL0V^~UKmY&$ diff --git a/examples/MssqlExample/Models.cs b/examples/MssqlExample/Models.cs index f9f1a87ff..90ea67aa9 100644 --- a/examples/MssqlExample/Models.cs +++ b/examples/MssqlExample/Models.cs @@ -1,6 +1,11 @@ // auto-generated by sqlc - do not edit +using System; using System.Linq; namespace MssqlExampleGen; public readonly record struct Author(long Id, string Name, string? Bio); -public readonly record struct Book(long Id, string Name, long AuthorId, string? Description); \ No newline at end of file +public readonly record struct Book(long Id, string Name, long AuthorId, string? Description); +public readonly record struct MssqlNumericType(bool? CBit, byte? CTinyint, short? CSmallint, int? CInt, long? CBigint, float? CReal, double? CFloat, decimal? CDecimal, decimal? CNumeric, decimal? CMoney, decimal? CSmallmoney); +public readonly record struct MssqlStringType(string? CChar, string? CNchar, string? CVarchar, string? CNvarchar, string? CVarcharMax, string? CNvarcharMax); +public readonly record struct MssqlDatetimeType(DateTime? CDate, DateTime? CDatetime, DateTime? CDatetime2, DateTime? CSmalldatetime, DateTimeOffset? CDatetimeoffset, TimeSpan? CTime); +public readonly record struct MssqlBinaryType(byte[]? CBinary, byte[]? CVarbinary, byte[]? CVarbinaryMax, Guid? CUniqueidentifier); \ No newline at end of file diff --git a/examples/MssqlExample/QuerySql.cs b/examples/MssqlExample/QuerySql.cs index deb9b5af1..e3113d470 100644 --- a/examples/MssqlExample/QuerySql.cs +++ b/examples/MssqlExample/QuerySql.cs @@ -49,7 +49,7 @@ public static QuerySql WithTransaction(SqlTransaction transaction) await connection.OpenAsync(); using (var command = new SqlCommand(GetAuthorSql, connection)) { - command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; using (var reader = await command.ExecuteReaderAsync()) { if (await reader.ReadAsync()) @@ -73,7 +73,7 @@ public static QuerySql WithTransaction(SqlTransaction transaction) { command.CommandText = GetAuthorSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; using (var reader = await command.ExecuteReaderAsync()) { if (await reader.ReadAsync()) @@ -106,8 +106,8 @@ public async Task> ListAuthorsAsync(ListAuthorsArgs args) await connection.OpenAsync(); using (var command = new SqlCommand(ListAuthorsSql, connection)) { - command.Parameters.AddWithValue("@offset", args.Offset); - command.Parameters.AddWithValue("@limit", args.Limit); + command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset; + command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit; using (var reader = await command.ExecuteReaderAsync()) { var result = new List(); @@ -125,8 +125,8 @@ public async Task> ListAuthorsAsync(ListAuthorsArgs args) { command.CommandText = ListAuthorsSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@offset", args.Offset); - command.Parameters.AddWithValue("@limit", args.Limit); + command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset; + command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit; using (var reader = await command.ExecuteReaderAsync()) { var result = new List(); @@ -148,9 +148,9 @@ public async Task CreateAuthorAsync(CreateAuthorArgs args) await connection.OpenAsync(); using (var command = new SqlCommand(CreateAuthorSql, connection)) { - command.Parameters.AddWithValue("@id", args.Id); - command.Parameters.AddWithValue("@name", args.Name); - command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; await command.ExecuteNonQueryAsync(); } @@ -164,9 +164,9 @@ public async Task CreateAuthorAsync(CreateAuthorArgs args) { command.CommandText = CreateAuthorSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@id", args.Id); - command.Parameters.AddWithValue("@name", args.Name); - command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; await command.ExecuteNonQueryAsync(); } } @@ -183,8 +183,8 @@ public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args) await connection.OpenAsync(); using (var command = new SqlCommand(CreateAuthorReturnIdSql, connection)) { - command.Parameters.AddWithValue("@name", args.Name); - command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; var result = await command.ExecuteScalarAsync(); return Convert.ToInt64(result); } @@ -197,8 +197,8 @@ public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args) { command.CommandText = CreateAuthorReturnIdSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@name", args.Name); - command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; var result = await command.ExecuteScalarAsync(); return Convert.ToInt64(result); } @@ -217,7 +217,7 @@ public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args) await connection.OpenAsync(); using (var command = new SqlCommand(GetAuthorByIdSql, connection)) { - command.Parameters.AddWithValue("@id", args.Id); + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; using (var reader = await command.ExecuteReaderAsync()) { if (await reader.ReadAsync()) @@ -241,7 +241,7 @@ public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args) { command.CommandText = GetAuthorByIdSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@id", args.Id); + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; using (var reader = await command.ExecuteReaderAsync()) { if (await reader.ReadAsync()) @@ -313,7 +313,7 @@ public async Task DeleteAuthorAsync(DeleteAuthorArgs args) await connection.OpenAsync(); using (var command = new SqlCommand(DeleteAuthorSql, connection)) { - command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; await command.ExecuteNonQueryAsync(); } @@ -327,7 +327,7 @@ public async Task DeleteAuthorAsync(DeleteAuthorArgs args) { command.CommandText = DeleteAuthorSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; await command.ExecuteNonQueryAsync(); } } @@ -372,7 +372,7 @@ public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args) await connection.OpenAsync(); using (var command = new SqlCommand(UpdateAuthorsSql, connection)) { - command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; return await command.ExecuteNonQueryAsync(); } } @@ -384,7 +384,7 @@ public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args) { command.CommandText = UpdateAuthorsSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@bio", args.Bio ?? (object)DBNull.Value); + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; return await command.ExecuteNonQueryAsync(); } } @@ -496,8 +496,8 @@ public async Task CreateBookAsync(CreateBookArgs args) await connection.OpenAsync(); using (var command = new SqlCommand(CreateBookSql, connection)) { - command.Parameters.AddWithValue("@name", args.Name); - command.Parameters.AddWithValue("@author_id", args.AuthorId); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId; var result = await command.ExecuteScalarAsync(); return Convert.ToInt64(result); } @@ -510,8 +510,8 @@ public async Task CreateBookAsync(CreateBookArgs args) { command.CommandText = CreateBookSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@name", args.Name); - command.Parameters.AddWithValue("@author_id", args.AuthorId); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId; var result = await command.ExecuteScalarAsync(); return Convert.ToInt64(result); } @@ -611,7 +611,7 @@ public async Task> GetAuthorsByBookNameAsync(GetAu await connection.OpenAsync(); using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection)) { - command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; using (var reader = await command.ExecuteReaderAsync()) { var result = new List(); @@ -629,7 +629,7 @@ public async Task> GetAuthorsByBookNameAsync(GetAu { command.CommandText = GetAuthorsByBookNameSql; command.Transaction = this.Transaction; - command.Parameters.AddWithValue("@name", args.Name); + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; using (var reader = await command.ExecuteReaderAsync()) { var result = new List(); @@ -639,4 +639,1086 @@ public async Task> GetAuthorsByBookNameAsync(GetAu } } } + + private const string InsertMssqlNumericTypesSql = @" + INSERT INTO mssql_numeric_types + ( + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + ) + VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);"; + public readonly record struct InsertMssqlNumericTypesArgs(bool? CBit, byte? CTinyint, short? CSmallint, int? CInt, long? CBigint, float? CReal, double? CFloat, decimal? CDecimal, decimal? CNumeric, decimal? CMoney, decimal? CSmallmoney); + public async Task InsertMssqlNumericTypesAsync(InsertMssqlNumericTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlNumericTypesSql, connection)) + { + command.Parameters.Add("@c_bit", SqlDbType.Bit).Value = args.CBit ?? (object)DBNull.Value; + command.Parameters.Add("@c_tinyint", SqlDbType.TinyInt).Value = args.CTinyint ?? (object)DBNull.Value; + command.Parameters.Add("@c_smallint", SqlDbType.SmallInt).Value = args.CSmallint ?? (object)DBNull.Value; + command.Parameters.Add("@c_int", SqlDbType.Int).Value = args.CInt ?? (object)DBNull.Value; + command.Parameters.Add("@c_bigint", SqlDbType.BigInt).Value = args.CBigint ?? (object)DBNull.Value; + command.Parameters.Add("@c_real", SqlDbType.Real).Value = args.CReal ?? (object)DBNull.Value; + command.Parameters.Add("@c_float", SqlDbType.Float).Value = args.CFloat ?? (object)DBNull.Value; + command.Parameters.AddWithValue("@c_decimal", args.CDecimal ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_numeric", args.CNumeric ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_money", args.CMoney ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_smallmoney", args.CSmallmoney ?? (object)DBNull.Value); + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlNumericTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_bit", SqlDbType.Bit).Value = args.CBit ?? (object)DBNull.Value; + command.Parameters.Add("@c_tinyint", SqlDbType.TinyInt).Value = args.CTinyint ?? (object)DBNull.Value; + command.Parameters.Add("@c_smallint", SqlDbType.SmallInt).Value = args.CSmallint ?? (object)DBNull.Value; + command.Parameters.Add("@c_int", SqlDbType.Int).Value = args.CInt ?? (object)DBNull.Value; + command.Parameters.Add("@c_bigint", SqlDbType.BigInt).Value = args.CBigint ?? (object)DBNull.Value; + command.Parameters.Add("@c_real", SqlDbType.Real).Value = args.CReal ?? (object)DBNull.Value; + command.Parameters.Add("@c_float", SqlDbType.Float).Value = args.CFloat ?? (object)DBNull.Value; + command.Parameters.AddWithValue("@c_decimal", args.CDecimal ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_numeric", args.CNumeric ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_money", args.CMoney ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_smallmoney", args.CSmallmoney ?? (object)DBNull.Value); + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlNumericTypesBatchSql = @"INSERT INTO mssql_numeric_types + ( + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + ) + VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);"; + public readonly record struct InsertMssqlNumericTypesBatchArgs(bool? CBit, byte? CTinyint, short? CSmallint, int? CInt, long? CBigint, float? CReal, double? CFloat, decimal? CDecimal, decimal? CNumeric, decimal? CMoney, decimal? CSmallmoney); + public async Task InsertMssqlNumericTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_bit", typeof(bool)); + dataTable.Columns.Add("c_tinyint", typeof(byte)); + dataTable.Columns.Add("c_smallint", typeof(short)); + dataTable.Columns.Add("c_int", typeof(int)); + dataTable.Columns.Add("c_bigint", typeof(long)); + dataTable.Columns.Add("c_real", typeof(float)); + dataTable.Columns.Add("c_float", typeof(double)); + dataTable.Columns.Add("c_decimal", typeof(decimal)); + dataTable.Columns.Add("c_numeric", typeof(decimal)); + dataTable.Columns.Add("c_money", typeof(decimal)); + dataTable.Columns.Add("c_smallmoney", typeof(decimal)); + foreach (var row in args) + dataTable.Rows.Add(row.CBit ?? (object)DBNull.Value, row.CTinyint ?? (object)DBNull.Value, row.CSmallint ?? (object)DBNull.Value, row.CInt ?? (object)DBNull.Value, row.CBigint ?? (object)DBNull.Value, row.CReal ?? (object)DBNull.Value, row.CFloat ?? (object)DBNull.Value, row.CDecimal ?? (object)DBNull.Value, row.CNumeric ?? (object)DBNull.Value, row.CMoney ?? (object)DBNull.Value, row.CSmallmoney ?? (object)DBNull.Value); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_numeric_types]"; + bulkCopy.ColumnMappings.Add("c_bit", "c_bit"); + bulkCopy.ColumnMappings.Add("c_tinyint", "c_tinyint"); + bulkCopy.ColumnMappings.Add("c_smallint", "c_smallint"); + bulkCopy.ColumnMappings.Add("c_int", "c_int"); + bulkCopy.ColumnMappings.Add("c_bigint", "c_bigint"); + bulkCopy.ColumnMappings.Add("c_real", "c_real"); + bulkCopy.ColumnMappings.Add("c_float", "c_float"); + bulkCopy.ColumnMappings.Add("c_decimal", "c_decimal"); + bulkCopy.ColumnMappings.Add("c_numeric", "c_numeric"); + bulkCopy.ColumnMappings.Add("c_money", "c_money"); + bulkCopy.ColumnMappings.Add("c_smallmoney", "c_smallmoney"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlNumericTypesSql = "SELECT TOP 1 c_bit, c_tinyint, c_smallint, c_int, c_bigint, c_real, c_float, c_decimal, c_numeric, c_money, c_smallmoney FROM mssql_numeric_types;"; + public readonly record struct GetMssqlNumericTypesRow(bool? CBit, byte? CTinyint, short? CSmallint, int? CInt, long? CBigint, float? CReal, double? CFloat, decimal? CDecimal, decimal? CNumeric, decimal? CMoney, decimal? CSmallmoney); + public async Task GetMssqlNumericTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlNumericTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNumericTypesRow + { + CBit = reader.IsDBNull(0) ? null : reader.GetBoolean(0), + CTinyint = reader.IsDBNull(1) ? null : reader.GetByte(1), + CSmallint = reader.IsDBNull(2) ? null : reader.GetInt16(2), + CInt = reader.IsDBNull(3) ? null : reader.GetInt32(3), + CBigint = reader.IsDBNull(4) ? null : reader.GetInt64(4), + CReal = reader.IsDBNull(5) ? null : reader.GetFloat(5), + CFloat = reader.IsDBNull(6) ? null : reader.GetDouble(6), + CDecimal = reader.IsDBNull(7) ? null : reader.GetDecimal(7), + CNumeric = reader.IsDBNull(8) ? null : reader.GetDecimal(8), + CMoney = reader.IsDBNull(9) ? null : reader.GetDecimal(9), + CSmallmoney = reader.IsDBNull(10) ? null : reader.GetDecimal(10) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlNumericTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNumericTypesRow + { + CBit = reader.IsDBNull(0) ? null : reader.GetBoolean(0), + CTinyint = reader.IsDBNull(1) ? null : reader.GetByte(1), + CSmallint = reader.IsDBNull(2) ? null : reader.GetInt16(2), + CInt = reader.IsDBNull(3) ? null : reader.GetInt32(3), + CBigint = reader.IsDBNull(4) ? null : reader.GetInt64(4), + CReal = reader.IsDBNull(5) ? null : reader.GetFloat(5), + CFloat = reader.IsDBNull(6) ? null : reader.GetDouble(6), + CDecimal = reader.IsDBNull(7) ? null : reader.GetDecimal(7), + CNumeric = reader.IsDBNull(8) ? null : reader.GetDecimal(8), + CMoney = reader.IsDBNull(9) ? null : reader.GetDecimal(9), + CSmallmoney = reader.IsDBNull(10) ? null : reader.GetDecimal(10) + }; + } + } + } + + return null; + } + + private const string GetMssqlNumericTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + FROM mssql_numeric_types + GROUP BY + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney;"; + public readonly record struct GetMssqlNumericTypesCntRow(int Cnt, bool? CBit, byte? CTinyint, short? CSmallint, int? CInt, long? CBigint, float? CReal, double? CFloat, decimal? CDecimal, decimal? CNumeric, decimal? CMoney, decimal? CSmallmoney); + public async Task GetMssqlNumericTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlNumericTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNumericTypesCntRow + { + Cnt = reader.GetInt32(0), + CBit = reader.IsDBNull(1) ? null : reader.GetBoolean(1), + CTinyint = reader.IsDBNull(2) ? null : reader.GetByte(2), + CSmallint = reader.IsDBNull(3) ? null : reader.GetInt16(3), + CInt = reader.IsDBNull(4) ? null : reader.GetInt32(4), + CBigint = reader.IsDBNull(5) ? null : reader.GetInt64(5), + CReal = reader.IsDBNull(6) ? null : reader.GetFloat(6), + CFloat = reader.IsDBNull(7) ? null : reader.GetDouble(7), + CDecimal = reader.IsDBNull(8) ? null : reader.GetDecimal(8), + CNumeric = reader.IsDBNull(9) ? null : reader.GetDecimal(9), + CMoney = reader.IsDBNull(10) ? null : reader.GetDecimal(10), + CSmallmoney = reader.IsDBNull(11) ? null : reader.GetDecimal(11) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlNumericTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNumericTypesCntRow + { + Cnt = reader.GetInt32(0), + CBit = reader.IsDBNull(1) ? null : reader.GetBoolean(1), + CTinyint = reader.IsDBNull(2) ? null : reader.GetByte(2), + CSmallint = reader.IsDBNull(3) ? null : reader.GetInt16(3), + CInt = reader.IsDBNull(4) ? null : reader.GetInt32(4), + CBigint = reader.IsDBNull(5) ? null : reader.GetInt64(5), + CReal = reader.IsDBNull(6) ? null : reader.GetFloat(6), + CFloat = reader.IsDBNull(7) ? null : reader.GetDouble(7), + CDecimal = reader.IsDBNull(8) ? null : reader.GetDecimal(8), + CNumeric = reader.IsDBNull(9) ? null : reader.GetDecimal(9), + CMoney = reader.IsDBNull(10) ? null : reader.GetDecimal(10), + CSmallmoney = reader.IsDBNull(11) ? null : reader.GetDecimal(11) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlNumericTypesSql = "TRUNCATE TABLE mssql_numeric_types;"; + public async Task TruncateMssqlNumericTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlNumericTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlNumericTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlStringTypesSql = @" + INSERT INTO mssql_string_types + ( + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + ) + VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);"; + public readonly record struct InsertMssqlStringTypesArgs(string? CChar, string? CNchar, string? CVarchar, string? CNvarchar, string? CVarcharMax, string? CNvarcharMax); + public async Task InsertMssqlStringTypesAsync(InsertMssqlStringTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlStringTypesSql, connection)) + { + command.Parameters.Add("@c_char", SqlDbType.Char).Value = args.CChar ?? (object)DBNull.Value; + command.Parameters.Add("@c_nchar", SqlDbType.NChar).Value = args.CNchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_varchar", SqlDbType.VarChar).Value = args.CVarchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_nvarchar", SqlDbType.NVarChar).Value = args.CNvarchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_varchar_max", SqlDbType.VarChar).Value = args.CVarcharMax ?? (object)DBNull.Value; + command.Parameters.Add("@c_nvarchar_max", SqlDbType.NVarChar).Value = args.CNvarcharMax ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlStringTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_char", SqlDbType.Char).Value = args.CChar ?? (object)DBNull.Value; + command.Parameters.Add("@c_nchar", SqlDbType.NChar).Value = args.CNchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_varchar", SqlDbType.VarChar).Value = args.CVarchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_nvarchar", SqlDbType.NVarChar).Value = args.CNvarchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_varchar_max", SqlDbType.VarChar).Value = args.CVarcharMax ?? (object)DBNull.Value; + command.Parameters.Add("@c_nvarchar_max", SqlDbType.NVarChar).Value = args.CNvarcharMax ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlStringTypesBatchSql = @"INSERT INTO mssql_string_types + ( + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + ) + VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);"; + public readonly record struct InsertMssqlStringTypesBatchArgs(string? CChar, string? CNchar, string? CVarchar, string? CNvarchar, string? CVarcharMax, string? CNvarcharMax); + public async Task InsertMssqlStringTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_char", typeof(string)); + dataTable.Columns.Add("c_nchar", typeof(string)); + dataTable.Columns.Add("c_varchar", typeof(string)); + dataTable.Columns.Add("c_nvarchar", typeof(string)); + dataTable.Columns.Add("c_varchar_max", typeof(string)); + dataTable.Columns.Add("c_nvarchar_max", typeof(string)); + foreach (var row in args) + dataTable.Rows.Add(row.CChar ?? (object)DBNull.Value, row.CNchar ?? (object)DBNull.Value, row.CVarchar ?? (object)DBNull.Value, row.CNvarchar ?? (object)DBNull.Value, row.CVarcharMax ?? (object)DBNull.Value, row.CNvarcharMax ?? (object)DBNull.Value); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_string_types]"; + bulkCopy.ColumnMappings.Add("c_char", "c_char"); + bulkCopy.ColumnMappings.Add("c_nchar", "c_nchar"); + bulkCopy.ColumnMappings.Add("c_varchar", "c_varchar"); + bulkCopy.ColumnMappings.Add("c_nvarchar", "c_nvarchar"); + bulkCopy.ColumnMappings.Add("c_varchar_max", "c_varchar_max"); + bulkCopy.ColumnMappings.Add("c_nvarchar_max", "c_nvarchar_max"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlStringTypesSql = "SELECT TOP 1 c_char, c_nchar, c_varchar, c_nvarchar, c_varchar_max, c_nvarchar_max FROM mssql_string_types;"; + public readonly record struct GetMssqlStringTypesRow(string? CChar, string? CNchar, string? CVarchar, string? CNvarchar, string? CVarcharMax, string? CNvarcharMax); + public async Task GetMssqlStringTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlStringTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlStringTypesRow + { + CChar = reader.IsDBNull(0) ? null : reader.GetString(0), + CNchar = reader.IsDBNull(1) ? null : reader.GetString(1), + CVarchar = reader.IsDBNull(2) ? null : reader.GetString(2), + CNvarchar = reader.IsDBNull(3) ? null : reader.GetString(3), + CVarcharMax = reader.IsDBNull(4) ? null : reader.GetString(4), + CNvarcharMax = reader.IsDBNull(5) ? null : reader.GetString(5) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlStringTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlStringTypesRow + { + CChar = reader.IsDBNull(0) ? null : reader.GetString(0), + CNchar = reader.IsDBNull(1) ? null : reader.GetString(1), + CVarchar = reader.IsDBNull(2) ? null : reader.GetString(2), + CNvarchar = reader.IsDBNull(3) ? null : reader.GetString(3), + CVarcharMax = reader.IsDBNull(4) ? null : reader.GetString(4), + CNvarcharMax = reader.IsDBNull(5) ? null : reader.GetString(5) + }; + } + } + } + + return null; + } + + private const string GetMssqlStringTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + FROM mssql_string_types + GROUP BY + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max;"; + public readonly record struct GetMssqlStringTypesCntRow(int Cnt, string? CChar, string? CNchar, string? CVarchar, string? CNvarchar, string? CVarcharMax, string? CNvarcharMax); + public async Task GetMssqlStringTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlStringTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlStringTypesCntRow + { + Cnt = reader.GetInt32(0), + CChar = reader.IsDBNull(1) ? null : reader.GetString(1), + CNchar = reader.IsDBNull(2) ? null : reader.GetString(2), + CVarchar = reader.IsDBNull(3) ? null : reader.GetString(3), + CNvarchar = reader.IsDBNull(4) ? null : reader.GetString(4), + CVarcharMax = reader.IsDBNull(5) ? null : reader.GetString(5), + CNvarcharMax = reader.IsDBNull(6) ? null : reader.GetString(6) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlStringTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlStringTypesCntRow + { + Cnt = reader.GetInt32(0), + CChar = reader.IsDBNull(1) ? null : reader.GetString(1), + CNchar = reader.IsDBNull(2) ? null : reader.GetString(2), + CVarchar = reader.IsDBNull(3) ? null : reader.GetString(3), + CNvarchar = reader.IsDBNull(4) ? null : reader.GetString(4), + CVarcharMax = reader.IsDBNull(5) ? null : reader.GetString(5), + CNvarcharMax = reader.IsDBNull(6) ? null : reader.GetString(6) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlStringTypesSql = "TRUNCATE TABLE mssql_string_types;"; + public async Task TruncateMssqlStringTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlStringTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlStringTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlDatetimeTypesSql = @" + INSERT INTO mssql_datetime_types + ( + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + ) + VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);"; + public readonly record struct InsertMssqlDatetimeTypesArgs(DateTime? CDate, DateTime? CDatetime, DateTime? CDatetime2, DateTime? CSmalldatetime, DateTimeOffset? CDatetimeoffset, TimeSpan? CTime); + public async Task InsertMssqlDatetimeTypesAsync(InsertMssqlDatetimeTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlDatetimeTypesSql, connection)) + { + command.Parameters.Add("@c_date", SqlDbType.Date).Value = args.CDate ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetime", SqlDbType.DateTime).Value = args.CDatetime ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetime2", SqlDbType.DateTime2).Value = args.CDatetime2 ?? (object)DBNull.Value; + command.Parameters.Add("@c_smalldatetime", SqlDbType.SmallDateTime).Value = args.CSmalldatetime ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetimeoffset", SqlDbType.DateTimeOffset).Value = args.CDatetimeoffset ?? (object)DBNull.Value; + command.Parameters.Add("@c_time", SqlDbType.Time).Value = args.CTime ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlDatetimeTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_date", SqlDbType.Date).Value = args.CDate ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetime", SqlDbType.DateTime).Value = args.CDatetime ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetime2", SqlDbType.DateTime2).Value = args.CDatetime2 ?? (object)DBNull.Value; + command.Parameters.Add("@c_smalldatetime", SqlDbType.SmallDateTime).Value = args.CSmalldatetime ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetimeoffset", SqlDbType.DateTimeOffset).Value = args.CDatetimeoffset ?? (object)DBNull.Value; + command.Parameters.Add("@c_time", SqlDbType.Time).Value = args.CTime ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlDatetimeTypesBatchSql = @"INSERT INTO mssql_datetime_types + ( + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + ) + VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);"; + public readonly record struct InsertMssqlDatetimeTypesBatchArgs(DateTime? CDate, DateTime? CDatetime, DateTime? CDatetime2, DateTime? CSmalldatetime, DateTimeOffset? CDatetimeoffset, TimeSpan? CTime); + public async Task InsertMssqlDatetimeTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_date", typeof(DateTime)); + dataTable.Columns.Add("c_datetime", typeof(DateTime)); + dataTable.Columns.Add("c_datetime2", typeof(DateTime)); + dataTable.Columns.Add("c_smalldatetime", typeof(DateTime)); + dataTable.Columns.Add("c_datetimeoffset", typeof(DateTimeOffset)); + dataTable.Columns.Add("c_time", typeof(TimeSpan)); + foreach (var row in args) + dataTable.Rows.Add(row.CDate ?? (object)DBNull.Value, row.CDatetime ?? (object)DBNull.Value, row.CDatetime2 ?? (object)DBNull.Value, row.CSmalldatetime ?? (object)DBNull.Value, row.CDatetimeoffset ?? (object)DBNull.Value, row.CTime ?? (object)DBNull.Value); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_datetime_types]"; + bulkCopy.ColumnMappings.Add("c_date", "c_date"); + bulkCopy.ColumnMappings.Add("c_datetime", "c_datetime"); + bulkCopy.ColumnMappings.Add("c_datetime2", "c_datetime2"); + bulkCopy.ColumnMappings.Add("c_smalldatetime", "c_smalldatetime"); + bulkCopy.ColumnMappings.Add("c_datetimeoffset", "c_datetimeoffset"); + bulkCopy.ColumnMappings.Add("c_time", "c_time"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlDatetimeTypesSql = "SELECT TOP 1 c_date, c_datetime, c_datetime2, c_smalldatetime, c_datetimeoffset, c_time FROM mssql_datetime_types;"; + public readonly record struct GetMssqlDatetimeTypesRow(DateTime? CDate, DateTime? CDatetime, DateTime? CDatetime2, DateTime? CSmalldatetime, DateTimeOffset? CDatetimeoffset, TimeSpan? CTime); + public async Task GetMssqlDatetimeTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlDatetimeTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlDatetimeTypesRow + { + CDate = reader.IsDBNull(0) ? null : reader.GetDateTime(0), + CDatetime = reader.IsDBNull(1) ? null : reader.GetDateTime(1), + CDatetime2 = reader.IsDBNull(2) ? null : reader.GetDateTime(2), + CSmalldatetime = reader.IsDBNull(3) ? null : reader.GetDateTime(3), + CDatetimeoffset = reader.IsDBNull(4) ? null : reader.GetFieldValue(4), + CTime = reader.IsDBNull(5) ? null : reader.GetFieldValue(5) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlDatetimeTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlDatetimeTypesRow + { + CDate = reader.IsDBNull(0) ? null : reader.GetDateTime(0), + CDatetime = reader.IsDBNull(1) ? null : reader.GetDateTime(1), + CDatetime2 = reader.IsDBNull(2) ? null : reader.GetDateTime(2), + CSmalldatetime = reader.IsDBNull(3) ? null : reader.GetDateTime(3), + CDatetimeoffset = reader.IsDBNull(4) ? null : reader.GetFieldValue(4), + CTime = reader.IsDBNull(5) ? null : reader.GetFieldValue(5) + }; + } + } + } + + return null; + } + + private const string GetMssqlDatetimeTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + FROM mssql_datetime_types + GROUP BY + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time;"; + public readonly record struct GetMssqlDatetimeTypesCntRow(int Cnt, DateTime? CDate, DateTime? CDatetime, DateTime? CDatetime2, DateTime? CSmalldatetime, DateTimeOffset? CDatetimeoffset, TimeSpan? CTime); + public async Task GetMssqlDatetimeTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlDatetimeTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlDatetimeTypesCntRow + { + Cnt = reader.GetInt32(0), + CDate = reader.IsDBNull(1) ? null : reader.GetDateTime(1), + CDatetime = reader.IsDBNull(2) ? null : reader.GetDateTime(2), + CDatetime2 = reader.IsDBNull(3) ? null : reader.GetDateTime(3), + CSmalldatetime = reader.IsDBNull(4) ? null : reader.GetDateTime(4), + CDatetimeoffset = reader.IsDBNull(5) ? null : reader.GetFieldValue(5), + CTime = reader.IsDBNull(6) ? null : reader.GetFieldValue(6) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlDatetimeTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlDatetimeTypesCntRow + { + Cnt = reader.GetInt32(0), + CDate = reader.IsDBNull(1) ? null : reader.GetDateTime(1), + CDatetime = reader.IsDBNull(2) ? null : reader.GetDateTime(2), + CDatetime2 = reader.IsDBNull(3) ? null : reader.GetDateTime(3), + CSmalldatetime = reader.IsDBNull(4) ? null : reader.GetDateTime(4), + CDatetimeoffset = reader.IsDBNull(5) ? null : reader.GetFieldValue(5), + CTime = reader.IsDBNull(6) ? null : reader.GetFieldValue(6) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlDatetimeTypesSql = "TRUNCATE TABLE mssql_datetime_types;"; + public async Task TruncateMssqlDatetimeTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlDatetimeTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlDatetimeTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlBinaryTypesSql = @" + INSERT INTO mssql_binary_types + ( + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + ) + VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);"; + public readonly record struct InsertMssqlBinaryTypesArgs(byte[]? CBinary, byte[]? CVarbinary, byte[]? CVarbinaryMax, Guid? CUniqueidentifier); + public async Task InsertMssqlBinaryTypesAsync(InsertMssqlBinaryTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlBinaryTypesSql, connection)) + { + command.Parameters.Add("@c_binary", SqlDbType.Binary).Value = args.CBinary ?? (object)DBNull.Value; + command.Parameters.Add("@c_varbinary", SqlDbType.VarBinary).Value = args.CVarbinary ?? (object)DBNull.Value; + command.Parameters.Add("@c_varbinary_max", SqlDbType.VarBinary).Value = args.CVarbinaryMax ?? (object)DBNull.Value; + command.Parameters.Add("@c_uniqueidentifier", SqlDbType.UniqueIdentifier).Value = args.CUniqueidentifier ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlBinaryTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_binary", SqlDbType.Binary).Value = args.CBinary ?? (object)DBNull.Value; + command.Parameters.Add("@c_varbinary", SqlDbType.VarBinary).Value = args.CVarbinary ?? (object)DBNull.Value; + command.Parameters.Add("@c_varbinary_max", SqlDbType.VarBinary).Value = args.CVarbinaryMax ?? (object)DBNull.Value; + command.Parameters.Add("@c_uniqueidentifier", SqlDbType.UniqueIdentifier).Value = args.CUniqueidentifier ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlBinaryTypesBatchSql = @"INSERT INTO mssql_binary_types + ( + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + ) + VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);"; + public readonly record struct InsertMssqlBinaryTypesBatchArgs(byte[]? CBinary, byte[]? CVarbinary, byte[]? CVarbinaryMax, Guid? CUniqueidentifier); + public async Task InsertMssqlBinaryTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_binary", typeof(byte[])); + dataTable.Columns.Add("c_varbinary", typeof(byte[])); + dataTable.Columns.Add("c_varbinary_max", typeof(byte[])); + dataTable.Columns.Add("c_uniqueidentifier", typeof(Guid)); + foreach (var row in args) + dataTable.Rows.Add(row.CBinary ?? (object)DBNull.Value, row.CVarbinary ?? (object)DBNull.Value, row.CVarbinaryMax ?? (object)DBNull.Value, row.CUniqueidentifier ?? (object)DBNull.Value); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_binary_types]"; + bulkCopy.ColumnMappings.Add("c_binary", "c_binary"); + bulkCopy.ColumnMappings.Add("c_varbinary", "c_varbinary"); + bulkCopy.ColumnMappings.Add("c_varbinary_max", "c_varbinary_max"); + bulkCopy.ColumnMappings.Add("c_uniqueidentifier", "c_uniqueidentifier"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlBinaryTypesSql = "SELECT TOP 1 c_binary, c_varbinary, c_varbinary_max, c_uniqueidentifier FROM mssql_binary_types;"; + public readonly record struct GetMssqlBinaryTypesRow(byte[]? CBinary, byte[]? CVarbinary, byte[]? CVarbinaryMax, Guid? CUniqueidentifier); + public async Task GetMssqlBinaryTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlBinaryTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlBinaryTypesRow + { + CBinary = reader.IsDBNull(0) ? null : reader.GetFieldValue(0), + CVarbinary = reader.IsDBNull(1) ? null : reader.GetFieldValue(1), + CVarbinaryMax = reader.IsDBNull(2) ? null : reader.GetFieldValue(2), + CUniqueidentifier = reader.IsDBNull(3) ? null : reader.GetGuid(3) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlBinaryTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlBinaryTypesRow + { + CBinary = reader.IsDBNull(0) ? null : reader.GetFieldValue(0), + CVarbinary = reader.IsDBNull(1) ? null : reader.GetFieldValue(1), + CVarbinaryMax = reader.IsDBNull(2) ? null : reader.GetFieldValue(2), + CUniqueidentifier = reader.IsDBNull(3) ? null : reader.GetGuid(3) + }; + } + } + } + + return null; + } + + private const string GetMssqlBinaryTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + FROM mssql_binary_types + GROUP BY + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier;"; + public readonly record struct GetMssqlBinaryTypesCntRow(int Cnt, byte[]? CBinary, byte[]? CVarbinary, byte[]? CVarbinaryMax, Guid? CUniqueidentifier); + public async Task GetMssqlBinaryTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlBinaryTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlBinaryTypesCntRow + { + Cnt = reader.GetInt32(0), + CBinary = reader.IsDBNull(1) ? null : reader.GetFieldValue(1), + CVarbinary = reader.IsDBNull(2) ? null : reader.GetFieldValue(2), + CVarbinaryMax = reader.IsDBNull(3) ? null : reader.GetFieldValue(3), + CUniqueidentifier = reader.IsDBNull(4) ? null : reader.GetGuid(4) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlBinaryTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlBinaryTypesCntRow + { + Cnt = reader.GetInt32(0), + CBinary = reader.IsDBNull(1) ? null : reader.GetFieldValue(1), + CVarbinary = reader.IsDBNull(2) ? null : reader.GetFieldValue(2), + CVarbinaryMax = reader.IsDBNull(3) ? null : reader.GetFieldValue(3), + CUniqueidentifier = reader.IsDBNull(4) ? null : reader.GetGuid(4) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlBinaryTypesSql = "TRUNCATE TABLE mssql_binary_types;"; + public async Task TruncateMssqlBinaryTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlBinaryTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlBinaryTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string GetMssqlFunctionsSql = @" + SELECT + MAX(c_int) AS max_int, + MAX(c_varchar) AS max_varchar, + MAX(c_datetime2) AS max_datetime2 + FROM mssql_numeric_types + CROSS JOIN mssql_string_types + CROSS JOIN mssql_datetime_types;"; + public readonly record struct GetMssqlFunctionsRow(int? MaxInt, string? MaxVarchar, DateTime? MaxDatetime2); + public async Task GetMssqlFunctionsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlFunctionsSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlFunctionsRow + { + MaxInt = reader.IsDBNull(0) ? null : reader.GetInt32(0), + MaxVarchar = reader.IsDBNull(1) ? null : reader.GetString(1), + MaxDatetime2 = reader.IsDBNull(2) ? null : reader.GetDateTime(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlFunctionsSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlFunctionsRow + { + MaxInt = reader.IsDBNull(0) ? null : reader.GetInt32(0), + MaxVarchar = reader.IsDBNull(1) ? null : reader.GetString(1), + MaxDatetime2 = reader.IsDBNull(2) ? null : reader.GetDateTime(2) + }; + } + } + } + + return null; + } } \ No newline at end of file diff --git a/examples/MssqlExample/request.json b/examples/MssqlExample/request.json index 22433935f..fb23df3af 100644 --- a/examples/MssqlExample/request.json +++ b/examples/MssqlExample/request.json @@ -3,10 +3,12 @@ "version": "2", "engine": "mssql", "schema": [ - "examples/config/mssql/authors/schema.sql" + "examples/config/mssql/authors/schema.sql", + "examples/config/mssql/types/schema.sql" ], "queries": [ - "examples/config/mssql/authors/query.sql" + "examples/config/mssql/authors/query.sql", + "examples/config/mssql/types/query.sql" ], "codegen": { "out": "examples/MssqlExample", @@ -123,6 +125,335 @@ } } ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "columns": [ + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + } + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + } + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + } + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + } + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + } + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + } + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + } + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + } + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + } + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_string_types" + }, + "columns": [ + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + } + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + } + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + } + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + } + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "columns": [ + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + } + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + } + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + } + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + } + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + } + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_binary_types" + }, + "columns": [ + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + } + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + } + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + } + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + } + } + ] } ] }, @@ -904,6 +1235,1770 @@ } ], "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", + "name": "InsertMssqlNumericTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_bit", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + } + }, + { + "number": 2, + "column": { + "name": "c_tinyint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + } + }, + { + "number": 3, + "column": { + "name": "c_smallint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + } + }, + { + "number": 4, + "column": { + "name": "c_int", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + } + }, + { + "number": 5, + "column": { + "name": "c_bigint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + } + }, + { + "number": 6, + "column": { + "name": "c_real", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + } + }, + { + "number": 7, + "column": { + "name": "c_float", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + } + }, + { + "number": 8, + "column": { + "name": "c_decimal", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + } + }, + { + "number": 9, + "column": { + "name": "c_numeric", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + } + }, + { + "number": 10, + "column": { + "name": "c_money", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + } + }, + { + "number": 11, + "column": { + "name": "c_smallmoney", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + } + ], + "comments": [ + " Numeric types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_numeric_types" + } + }, + { + "text": "INSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", + "name": "InsertMssqlNumericTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_bit", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + } + }, + { + "number": 2, + "column": { + "name": "c_tinyint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + } + }, + { + "number": 3, + "column": { + "name": "c_smallint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + } + }, + { + "number": 4, + "column": { + "name": "c_int", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + } + }, + { + "number": 5, + "column": { + "name": "c_bigint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + } + }, + { + "number": 6, + "column": { + "name": "c_real", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + } + }, + { + "number": 7, + "column": { + "name": "c_float", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + } + }, + { + "number": 8, + "column": { + "name": "c_decimal", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + } + }, + { + "number": 9, + "column": { + "name": "c_numeric", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + } + }, + { + "number": 10, + "column": { + "name": "c_money", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + } + }, + { + "number": 11, + "column": { + "name": "c_smallmoney", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_numeric_types" + } + }, + { + "text": "SELECT TOP 1 c_bit, c_tinyint, c_smallint, c_int, c_bigint, c_real, c_float, c_decimal, c_numeric, c_money, c_smallmoney FROM mssql_numeric_types;", + "name": "GetMssqlNumericTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\nFROM mssql_numeric_types\nGROUP BY\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney;", + "name": "GetMssqlNumericTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_numeric_types;", + "name": "TruncateMssqlNumericTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_string_types\n(\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\n)\nVALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);", + "name": "InsertMssqlStringTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_char", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + } + }, + { + "number": 2, + "column": { + "name": "c_nchar", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + } + }, + { + "number": 3, + "column": { + "name": "c_varchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + } + }, + { + "number": 4, + "column": { + "name": "c_nvarchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + } + }, + { + "number": 5, + "column": { + "name": "c_varchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + } + }, + { + "number": 6, + "column": { + "name": "c_nvarchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + } + ], + "comments": [ + " String types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_string_types" + } + }, + { + "text": "INSERT INTO mssql_string_types\n(\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\n)\nVALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);", + "name": "InsertMssqlStringTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_char", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + } + }, + { + "number": 2, + "column": { + "name": "c_nchar", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + } + }, + { + "number": 3, + "column": { + "name": "c_varchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + } + }, + { + "number": 4, + "column": { + "name": "c_nvarchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + } + }, + { + "number": 5, + "column": { + "name": "c_varchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + } + }, + { + "number": 6, + "column": { + "name": "c_nvarchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_string_types" + } + }, + { + "text": "SELECT TOP 1 c_char, c_nchar, c_varchar, c_nvarchar, c_varchar_max, c_nvarchar_max FROM mssql_string_types;", + "name": "GetMssqlStringTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\nFROM mssql_string_types\nGROUP BY\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max;", + "name": "GetMssqlStringTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_string_types;", + "name": "TruncateMssqlStringTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_datetime_types\n(\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\n)\nVALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);", + "name": "InsertMssqlDatetimeTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_date", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + } + }, + { + "number": 2, + "column": { + "name": "c_datetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + } + }, + { + "number": 3, + "column": { + "name": "c_datetime2", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + } + }, + { + "number": 4, + "column": { + "name": "c_smalldatetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + } + }, + { + "number": 5, + "column": { + "name": "c_datetimeoffset", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + } + }, + { + "number": 6, + "column": { + "name": "c_time", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + } + ], + "comments": [ + " Datetime types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_datetime_types" + } + }, + { + "text": "INSERT INTO mssql_datetime_types\n(\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\n)\nVALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);", + "name": "InsertMssqlDatetimeTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_date", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + } + }, + { + "number": 2, + "column": { + "name": "c_datetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + } + }, + { + "number": 3, + "column": { + "name": "c_datetime2", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + } + }, + { + "number": 4, + "column": { + "name": "c_smalldatetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + } + }, + { + "number": 5, + "column": { + "name": "c_datetimeoffset", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + } + }, + { + "number": 6, + "column": { + "name": "c_time", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_datetime_types" + } + }, + { + "text": "SELECT TOP 1 c_date, c_datetime, c_datetime2, c_smalldatetime, c_datetimeoffset, c_time FROM mssql_datetime_types;", + "name": "GetMssqlDatetimeTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\nFROM mssql_datetime_types\nGROUP BY\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time;", + "name": "GetMssqlDatetimeTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_datetime_types;", + "name": "TruncateMssqlDatetimeTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_binary_types\n(\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\n)\nVALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);", + "name": "InsertMssqlBinaryTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_binary", + "length": 3, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + } + }, + { + "number": 2, + "column": { + "name": "c_varbinary", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + } + }, + { + "number": 3, + "column": { + "name": "c_varbinary_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + } + }, + { + "number": 4, + "column": { + "name": "c_uniqueidentifier", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + } + ], + "comments": [ + " Binary types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_binary_types" + } + }, + { + "text": "INSERT INTO mssql_binary_types\n(\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\n)\nVALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);", + "name": "InsertMssqlBinaryTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_binary", + "length": 3, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + } + }, + { + "number": 2, + "column": { + "name": "c_varbinary", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + } + }, + { + "number": 3, + "column": { + "name": "c_varbinary_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + } + }, + { + "number": 4, + "column": { + "name": "c_uniqueidentifier", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_binary_types" + } + }, + { + "text": "SELECT TOP 1 c_binary, c_varbinary, c_varbinary_max, c_uniqueidentifier FROM mssql_binary_types;", + "name": "GetMssqlBinaryTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\nFROM mssql_binary_types\nGROUP BY\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier;", + "name": "GetMssqlBinaryTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_binary_types;", + "name": "TruncateMssqlBinaryTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nSELECT\n MAX(c_int) AS max_int,\n MAX(c_varchar) AS max_varchar,\n MAX(c_datetime2) AS max_datetime2\nFROM mssql_numeric_types\nCROSS JOIN mssql_string_types\nCROSS JOIN mssql_datetime_types;", + "name": "GetMssqlFunctions", + "cmd": ":one", + "columns": [ + { + "name": "max_int", + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "max_varchar", + "length": -1, + "type": { + "name": "varchar" + } + }, + { + "name": "max_datetime2", + "length": -1, + "type": { + "name": "datetime2" + } + } + ], + "comments": [ + " Functions " + ], + "filename": "query.sql" } ], "sqlc_version": "v1.31.1", diff --git a/examples/MssqlExample/request.message b/examples/MssqlExample/request.message index b38667e7ff7ec02e573be964c7e2c8a0361ca9a3..2882fa50799f1e6f650422600a91df5257a6c17c 100644 GIT binary patch literal 24989 zcmeG_+ioMtb#6)&rP5rqJ=)2n?KL``tXD(GBZ&!MTk_hg3ui_i4XLH5on3EYEk(98 z?V(6bH?_Oc+6D{*25bX

=w52#}W`c?pmNh?9rpDG2-l0dhf*ha?DKzvKctKn%m! zoKu(P>Fy$%oQu7laDZJ^Rac#=I(4pfs*3DiGR!EL*`pwM*tF){cWXz-O*dGmw_69^ z;Q}vSsGW4~xBXxtsNZ*wYB#Xv$MiKjr^nE^ThDn(Ur#}Nc;fn}2+(;^Ur_)ItL^`( z!JZFm-o-%|#gNU3dT_tyA6xIe=QP~?lf#M&t%8oT-0@G`dFRk=xqhwVt_R0{`-`fy zRXcKn<67Me$rNtYI@a-gUH)_YnuJ0@# z)S7|&<@_u&Z!9#tptG>muGgBClNR)9{@zt{;`n60>D8UdF!dkLtEBa}274~dGxMnQAul^a8WUc*4YhvF!^je*BW0J|F`Dr$eGsDK(M$DRQy?2!5fE3pAZ83JTNpd4y=(nuhFuP4mECW*9|Yznl5>jJ(a&g^&M46& zGML1CKEtL2PWR&YOaRhi^A4MCxIx|bjyqnvMe&ko9~p=iE8m}BFN9dR!ue~jb#mnT zUVV=fySc`)^}T(sLp$o>LWgO>Ux0CkO(Lq0{D+t&S!AYI27ndW zM19Y9Yt11*O<3dD0(p-o0L*QctM45&+qGeal(VwD$+Oa2G7b z$xFh##7Guq{@-KlBO$Mug4cqM@3jskt9=%i9HrnQtMsk84+|}%@ZUuW7STGCKj7vq80hrrtx(l22LKBTg*6eeJRv|>?`M*-VBc%i{8LF4MWIWuWN|FXg|x0PQLyH@aS1gDCV4+& zpm|{dfkj23POd~C!SaQb`SOK14v(2%z~u`IQ@N!N@LAUF3V=&yywA+~-bu>?W7%uC zt&Vr#xjv6eZjdp3 zGR>~cvq?aoj#frW52FE)q-@Lg~tlF;##xklVY3q#@b9xJ$BC$Y$m0donwN9V4B`UxYeT+>AltlctPa0^U zbJI1;O;=0S$}di4%okafqbsF}s6-d;pEB$-o28v%rD|`Ms%6C=_FQBcuiLM!ZtWI# z?73I4obwC(0aClZVqRDW^R?qj&dDyj@4EH;Ettq>6YQg>xX_l4F^~oP1Vo#8l0=)| zog6c_;!LF<7v<01Y=v%9o1kPW;wp!mFPz+vU#k?4*g|$;a_%o>*o)o(hKCphAVP&08=&6XeHt!Ve z_44Xgac8|a$A!$^ajnyF{notw(u*&Fthq|oq6A8NTYP>-wZQ@i(HGbSlmZG7#Hs>~ z@F6Smkz)UM4EE9n7(>-!Pke`l&xH-Q>Bc4aQ?wSA*{o)0E9Q(GN3+@0@~BPdykPV8 z*LJrzR$*RJ$}+Sf(7=QFz#qxKSg^Tcm&#SUw7azhY`lAnu7ZL*#pR~oekTAiWjtLm zWqoaot);SpEo@Q{JewtZZXv(3wYgqg_8LKc0pw5s)PFgo{s2YMvuXt0f%RAe8>bP( z%$dG>*Y}OM`G628>3nUR9X$3b*sG-tZofuW30V}^iT{O_`i0m^t+pB{u0|{72i?7S z{fgx)U^U!=?JdL?k3OVYX8@>&{{;JG2!>L@_cE+PZ89EIu;;Meyc8+0$3Ku=CIk@H zUNM=l&ekAA5w*!y5>{{}vE|>(0?u!7R_F#|02PVG)J*M#240^3JYe=tuRw+lba!0c z=m+s|t!^p7@EYt=a->>+l3{;lNRE@j8q8aT<=_Uzd2jmZ)5Z$U8 ziKs*zP5X0Y5TU%0T`nm-ftK~lvV#y53w=^GK$fx6&A`NVUmvr6Jju!QX|2%8V07t7%28!%XGAIp7CnY2)!qR6VoZXF1TcRNUMNnyP>I2p3uwkc0uHacW)E$jl zTip-&Hf$a3^$238cLr)&(Z?AHgvUbK7WtHaZE{>7EgG9n?@R0yBGUMVp^9}Q(x4#k zPG%usstbFkgYKFHVf%A~gutz^IRED4gjv=E#M3*GA@MUAOy>bJq*2(*mPPruCUfSF zE=-;dT?NX|RFIuU%q>-z_a-OJ&qu=N>7aq4B;z1O^^K(?WyuQA)s*Ne46P5_m29XF z+hbR<0^p4ayv5_>={>5zJK{}7%i*K}Pr<^d!u;vvw0YPw9G~7zBp`nl3F~91EE=AV zd@fwJrR=f!F&LW&`L*XQ-ANhizoyw=W=5j_F+~4IdWinDTBm;Bnp&>6k53Q$_7RA) zkq#K?0DcnDNEZVm9gvQrsJa>H060iCfXM&BBs*7*p*ScYt!830Q=6F^P4jlwVpaxo zC^SP&jbN}()HJxmn5RS-SwCj%hZ(sU%B2q9Ls&2G zma234>-Or7U8max;m*MaWRHO#KXo@@Bz^fg+Q{YrmA)1B%hk$mX&uiER9Dxw zind0Ut(d=9^-o$DtVqZUxsgd>{&9}|y`kAQrq=_)U1L1oueNUa8Ge<4bGeakvAoRU zp~X@&x(9}y~S=w2mW{SLqH5ZTYgGZUL=>|jTW3|y7O z8DEM9b@u^x^sog#S&a$UILcgOxT}Xb9-+KT?n9))1uf69iF;Dos}Z;_#Xsa5uzkCW z){mU|Vj~qgm_M>IGN4T2DP2{}hu^^cHTe;?^2W_KqIW1#yP*+Wa{J=kNZhEv?P0b2 z`7gmXuGjm9MQXQXcF}7Z=Z5CWg%z!n|EdAoyGMPlVWjp?ryaY^p+CWF#cCBU*@84R z+kLZ^=CY3UhdK6lMsl~0lIqVb$9`DPv59TC5$ke+J7V1!H<2?6PoH&(9$nM3>K|p< z1L`ipb_%KpzQdvtQt0wBl9oxXGjT(Pn=iMzite%0oO6e$ zwiHOIip|-H=&}@WMAmneRZ^2V_oAvR1uB)`L^Ej=mvh)#QKfW3h8|TxS~cZ7=%bQS zdZN>bF^66u5h}L6H^JVI>?-#2?>PZWkNHVjgTzax)E#%ypHlbYe0H3PhVryWtmf5q zmdie+&HwMLw#^)^Ge=2&#k>~r<)~B_XHqEh{%4N;o6eO}mrKR{&d@Z(V&#pgD)D?l z74TCZy=sY@UaBfyI*odCyeD+eLI5;V=$`J;ewWa(34#W5A8%RM1y&gHWzv&`i!>L-H*0th5R=RZ~;60ZcGo*fBeq$S^NUF?h>^XuY_aC znt|?W^#Ef?T&_M#T+!}%-P%f2&EErm@eWcJU-#Sq0|0`;x=2v?H+~&k78hph4M|NJ zKk%xhy})n%dyf69#uHR@^UN^(3%wXV;TMiL9}g_WJ~h-i(ZzxtCb`*WSYO5fE9y;V`D_#8Gvdlb+ZWBfs$ zs8=chpkLVq7s-jeSNI+W6bmh`@pq3R9_Su;ps&)0l)zI+1Q*2! zeN;)@OTbV07C50?rG;bH2QVXj&_|ZgAq?BRUwWi=s@GYg~0*38rJ`0 z*>^PF8?Ez!C4U;H6VvA8pYX>pA)n-7TI=my2;{H@;~58v79o?J%! zpxY8MdnBWxU~xfAy%*XCiG&gAlxOA2R4zXOsqDP@=uh3HN@1tnPv)kK{!u8+`BI8( zP3iOTBrBmF>u<8`9|sObiPe8h`+!*gl$zp^rzg=D2{RE29m>KV(eWwDg&vl}2ZxS( zMS3WS#Unk0CNzikzB$JJ+JGm6@R>*+fW5o=`W(OE!Ovg=Qt*nbU5LOCH7-0-~%3XTJ>y`4(j(B=OoJFMJE0*}GAd1#2=5xx=y$L54@fplO%IFta zPR@b^SHcs`>MpRe#HO*DoDHqo4Y>2W(6R;`+x-N)h)vW;3bYZ(%di}uC+_BEjKZ`X z4wBxWpTFh54ll_0wdQuM{-Ac~-mSIZ|M;q$ zeBFQEdB^MAUky%M^_`Q01Mgk+BLY~wUTf9eW|O~ZT5UgopAHZ|3BUpKHbqWBwf!di c&Oo=%ZNCk_AFu;YQlB(yzIVm}sGk$~KLLRRRsaA1 delta 72 zcmbPxnDMqDKi4ECE=D6E*4*Oa!W=1$iHUL(b-5 Date: Tue, 27 Oct 2026 08:47:41 +0000 Subject: [PATCH 03/11] mssql: unit tests and formatting Co-Authored-By: Claude Sonnet 5.5 --- Drivers/MssqlDriver.cs | 2 +- .../EndToEndScaffold/Templates/MssqlTests.cs | 2 +- end2end/EndToEndTests/GlobalSetup.cs | 2 +- end2end/EndToEndTests/MssqlDapperTester.cs | 2 +- end2end/EndToEndTests/MssqlTester.cs | 2 +- .../CodegenTests/CodegenCancellationTests.cs | 4 +- unit-tests/CodegenTests/CodegenTests.csproj | 8 +++ unit-tests/CodegenTests/ExampleTests.cs | 6 ++ unit-tests/CodegenTests/ExtensionsTests.cs | 1 + unit-tests/CodegenTests/MssqlDriverTests.cs | 58 +++++++++++++++++++ unit-tests/CodegenTests/OptionsTests.cs | 1 + 11 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 unit-tests/CodegenTests/MssqlDriverTests.cs diff --git a/Drivers/MssqlDriver.cs b/Drivers/MssqlDriver.cs index b1fc5ba11..8fb66cdc6 100644 --- a/Drivers/MssqlDriver.cs +++ b/Drivers/MssqlDriver.cs @@ -382,4 +382,4 @@ public string GetCopyFromImpl(Query query, string queryTextConstant) """ ); } -} +} \ No newline at end of file diff --git a/end2end/EndToEndScaffold/Templates/MssqlTests.cs b/end2end/EndToEndScaffold/Templates/MssqlTests.cs index 08ca06309..abfbfbae3 100644 --- a/end2end/EndToEndScaffold/Templates/MssqlTests.cs +++ b/end2end/EndToEndScaffold/Templates/MssqlTests.cs @@ -450,4 +450,4 @@ public async Task TestMssqlTransactionRollback() """ }, }; -} +} \ No newline at end of file diff --git a/end2end/EndToEndTests/GlobalSetup.cs b/end2end/EndToEndTests/GlobalSetup.cs index f45a31aec..378206874 100644 --- a/end2end/EndToEndTests/GlobalSetup.cs +++ b/end2end/EndToEndTests/GlobalSetup.cs @@ -47,4 +47,4 @@ private static void Execute(SqlConnection connection, string sql) using (var command = new SqlCommand(sql, connection)) command.ExecuteNonQuery(); } -} +} \ No newline at end of file diff --git a/end2end/EndToEndTests/MssqlDapperTester.cs b/end2end/EndToEndTests/MssqlDapperTester.cs index d36cf50cd..ed35f82ae 100644 --- a/end2end/EndToEndTests/MssqlDapperTester.cs +++ b/end2end/EndToEndTests/MssqlDapperTester.cs @@ -19,4 +19,4 @@ public async Task EmptyTestsTables() await QuerySql.TruncateMssqlDatetimeTypesAsync(); await QuerySql.TruncateMssqlBinaryTypesAsync(); } -} +} \ No newline at end of file diff --git a/end2end/EndToEndTests/MssqlTester.cs b/end2end/EndToEndTests/MssqlTester.cs index 388746e15..15bc290b6 100644 --- a/end2end/EndToEndTests/MssqlTester.cs +++ b/end2end/EndToEndTests/MssqlTester.cs @@ -19,4 +19,4 @@ public async Task EmptyTestsTables() await QuerySql.TruncateMssqlDatetimeTypesAsync(); await QuerySql.TruncateMssqlBinaryTypesAsync(); } -} +} \ No newline at end of file diff --git a/unit-tests/CodegenTests/CodegenCancellationTests.cs b/unit-tests/CodegenTests/CodegenCancellationTests.cs index 163d2fc49..3771c6bca 100644 --- a/unit-tests/CodegenTests/CodegenCancellationTests.cs +++ b/unit-tests/CodegenTests/CodegenCancellationTests.cs @@ -145,7 +145,9 @@ public void DisabledLeavesDapperCallsWithoutCommandDefinition() "MySqlConnectorExampleRequest.message", "MySqlConnectorDapperExampleRequest.message", "SqliteExampleRequest.message", - "SqliteDapperExampleRequest.message" + "SqliteDapperExampleRequest.message", + "MssqlExampleRequest.message", + "MssqlDapperExampleRequest.message" ]; private static GenerateRequest ExampleRequestWith(string requestFile, bool withCancellationToken) diff --git a/unit-tests/CodegenTests/CodegenTests.csproj b/unit-tests/CodegenTests/CodegenTests.csproj index 0be414fc5..9537d83d3 100644 --- a/unit-tests/CodegenTests/CodegenTests.csproj +++ b/unit-tests/CodegenTests/CodegenTests.csproj @@ -12,6 +12,14 @@ Always + + Always + MssqlExampleRequest.message + + + Always + MssqlDapperExampleRequest.message + Always NpgsqlExampleRequest.message diff --git a/unit-tests/CodegenTests/ExampleTests.cs b/unit-tests/CodegenTests/ExampleTests.cs index 836bc2a50..bf99e24ee 100644 --- a/unit-tests/CodegenTests/ExampleTests.cs +++ b/unit-tests/CodegenTests/ExampleTests.cs @@ -17,6 +17,12 @@ public class ExampleTests [Test] public static async Task TestMySqlConnectorDapperExample() => await AssertRequestSuccess("MySqlConnectorDapperExampleRequest.message"); + [Test] + public static async Task TestMssqlExample() => await AssertRequestSuccess("MssqlExampleRequest.message"); + + [Test] + public static async Task TestMssqlDapperExample() => await AssertRequestSuccess("MssqlDapperExampleRequest.message"); + [Test] public static async Task TestSqliteExample() => await AssertRequestSuccess("SqliteExampleRequest.message"); diff --git a/unit-tests/CodegenTests/ExtensionsTests.cs b/unit-tests/CodegenTests/ExtensionsTests.cs index 3dbf48dae..68a9f1d94 100644 --- a/unit-tests/CodegenTests/ExtensionsTests.cs +++ b/unit-tests/CodegenTests/ExtensionsTests.cs @@ -138,6 +138,7 @@ public class PluginOptionNamesTests [TestCase(DriverName.MySqlConnector, ExpectedResult = "MySqlConnector")] [TestCase(DriverName.Npgsql, ExpectedResult = "Npgsql")] [TestCase(DriverName.Sqlite, ExpectedResult = "Microsoft.Data.Sqlite")] + [TestCase(DriverName.Mssql, ExpectedResult = "Microsoft.Data.SqlClient")] public static string TestDriverNameToName(DriverName driverName) { return driverName.ToName(); diff --git a/unit-tests/CodegenTests/MssqlDriverTests.cs b/unit-tests/CodegenTests/MssqlDriverTests.cs new file mode 100644 index 000000000..700557a44 --- /dev/null +++ b/unit-tests/CodegenTests/MssqlDriverTests.cs @@ -0,0 +1,58 @@ +using NUnit.Framework; +using Plugin; +using SqlcGenCsharp; +using System.Text; + +namespace CodegenTests; + +public class MssqlDriverTests +{ + private static async Task> GenerateFilesFor(string requestFile) + { + var request = GenerateRequest.Parser.ParseFrom(System.IO.File.ReadAllBytes(requestFile)); + var response = await new CodeGenerator().Generate(request); + return response.Files.ToDictionary(f => f.Name, f => Encoding.UTF8.GetString(f.Contents.ToByteArray())); + } + + [Test] + public async Task TestParametersAreBoundWithAnExplicitSqlDbType() + { + var files = await GenerateFilesFor("MssqlExampleRequest.message"); + var queries = files["QuerySql.cs"]; + + // a null sent as nvarchar cannot be converted to a binary type, and DateTime must not fall back to datetime + Assert.That(queries, Does.Contain("""Parameters.Add("@c_varbinary", SqlDbType.VarBinary).Value = args.CVarbinary ?? (object)DBNull.Value;""")); + Assert.That(queries, Does.Contain("""Parameters.Add("@c_datetime2", SqlDbType.DateTime2)""")); + // decimals take their precision and scale from the value + Assert.That(queries, Does.Contain("""Parameters.AddWithValue("@c_decimal", args.CDecimal ?? (object)DBNull.Value);""")); + } + + [Test] + public async Task TestCopyFromUsesSqlBulkCopy() + { + var files = await GenerateFilesFor("MssqlExampleRequest.message"); + var queries = files["QuerySql.cs"]; + + Assert.That(queries, Does.Contain("new SqlBulkCopy(connection)")); + Assert.That(queries, Does.Contain("""DestinationTableName = "[mssql_numeric_types]";""")); + } + + [Test] + public async Task TestSliceQueriesGenerateTheSliceTransformUtil() + { + var files = await GenerateFilesFor("MssqlExampleRequest.message"); + + Assert.That(files["QuerySql.cs"], Does.Contain("""WHERE id IN (/*SLICE:ids*/@ids)""")); + Assert.That(files["Utils.cs"], Does.Contain("TransformQueryForSliceArgs")); + } + + [Test] + public async Task TestDapperConfiguresDateTimeAndBinaryParameters() + { + var files = await GenerateFilesFor("MssqlDapperExampleRequest.message"); + + Assert.That(files["Utils.cs"], Does.Contain("SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2);")); + Assert.That(files["Utils.cs"], Does.Contain("class BinaryParameter")); + Assert.That(files["QuerySql.cs"], Does.Contain("new Utils.BinaryParameter(args.CBinary)")); + } +} \ No newline at end of file diff --git a/unit-tests/CodegenTests/OptionsTests.cs b/unit-tests/CodegenTests/OptionsTests.cs index 3c0dc50f5..88a1d133a 100644 --- a/unit-tests/CodegenTests/OptionsTests.cs +++ b/unit-tests/CodegenTests/OptionsTests.cs @@ -47,6 +47,7 @@ public void TestDefaultsWhenNoOptionsProvided(string optionsText) [TestCase("mysql", ExpectedResult = DriverName.MySqlConnector)] [TestCase("postgresql", ExpectedResult = DriverName.Npgsql)] [TestCase("sqlite", ExpectedResult = DriverName.Sqlite)] + [TestCase("mssql", ExpectedResult = DriverName.Mssql)] public static DriverName TestEngineMapsToDriver(string engine) { return new Options(CreateRequest(engine, "{}")).DriverName; From ff3ced6fb30e64a3947ed740cffe5916503fa083 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 08:49:23 +0000 Subject: [PATCH 04/11] mssql: docs, SqlClient 7.1.1 Co-Authored-By: Claude Sonnet 5.5 --- AGENTS.md | 6 +- Drivers/MssqlDriver.cs | 2 +- docs/01_Intro.md | 2 +- docs/03_Usage.md | 30 +++++----- docs/06_Mssql.md | 60 +++++++++++++++++++ docs/09_Examples.md | 30 ++++++++++ end2end/EndToEndTests/EndToEndTests.csproj | 2 +- .../MssqlDapperExample.csproj | 2 +- examples/MssqlExample/MssqlExample.csproj | 2 +- 9 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 docs/06_Mssql.md diff --git a/AGENTS.md b/AGENTS.md index 78b44c8c7..cd3ccbc6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,8 @@ **sqlc-gen-csharp** is a [sqlc](https://github.com/sqlc-dev/sqlc) plugin (written in C#) that turns a sqlc `GenerateRequest` (protobuf: catalog + queries + options) into C# data access code for -PostgreSQL (Npgsql), MySQL (MySqlConnector) and SQLite (Microsoft.Data.Sqlite), optionally wrapped with Dapper. +PostgreSQL (Npgsql), MySQL (MySqlConnector), SQLite (Microsoft.Data.Sqlite) and, experimentally, SQL Server +(Microsoft.Data.SqlClient, `engine: mssql`), optionally wrapped with Dapper. More specific instructions live next to the code - read them before working in those areas: - `Drivers/AGENTS.md` - type mappings, driver hooks, annotation generators, enums, per-DB quirks @@ -221,6 +222,9 @@ CI fails if any of the generated artifacts above is stale (`sqlc diff`, `git dif | `sqlc.arg`, `sqlc.narg`, `sqlc.embed` | ✅ | ✅ | ✅ | | | `sqlc.slice` | 🚫 (use arrays) | ✅ | ✅ | runtime `Utils.TransformQueryForSliceArgs` | +SQL Server (experimental, `MssqlDriver`) supports every annotation and macro above. `:execlastid` relies on a user-written +`OUTPUT INSERTED.id`, `:copyfrom` uses `SqlBulkCopy`. It needs a sqlc build with the mssql macro fixes. + ### Target frameworks - The plugin projects (section 1 graph) and the tooling/test projects target `net10.0`; SDK pinned in `global.json`. Exceptions: `end2end/EndToEndCommon` (`netstandard2.0`) and `end2end/EndToEndTestsLegacy` (`net472`). diff --git a/Drivers/MssqlDriver.cs b/Drivers/MssqlDriver.cs index 8fb66cdc6..18e092fa0 100644 --- a/Drivers/MssqlDriver.cs +++ b/Drivers/MssqlDriver.cs @@ -10,7 +10,7 @@ namespace SqlcGenCsharp.Drivers; public sealed class MssqlDriver : DbDriver, IOne, IMany, IExec, IExecRows, IExecLastId, ICopyFrom { - private const string DefaultSqlClientVersion = "6.1.1"; + private const string DefaultSqlClientVersion = "7.1.1"; public MssqlDriver( Options options, diff --git a/docs/01_Intro.md b/docs/01_Intro.md index 156866834..eb8653f73 100644 --- a/docs/01_Intro.md +++ b/docs/01_Intro.md @@ -5,4 +5,4 @@ .Net Framework Tests (Legacy)

-sqlc-gen-csharp is a .Net plugin for [sqlc](https://github.com/sqlc-dev/sqlc).
It leverages the SQLC plugin system to generate type-safe C# code for SQL queries, supporting PostgresSQL, MySQL & SQLite via the corresponding driver or suitable Dapper abstraction. +sqlc-gen-csharp is a .Net plugin for [sqlc](https://github.com/sqlc-dev/sqlc).
It leverages the SQLC plugin system to generate type-safe C# code for SQL queries, supporting PostgresSQL, MySQL & SQLite (and, experimentally, SQL Server) via the corresponding driver or suitable Dapper abstraction. diff --git a/docs/03_Usage.md b/docs/03_Usage.md index 8f4d72a87..01448e48c 100644 --- a/docs/03_Usage.md +++ b/docs/03_Usage.md @@ -3,7 +3,7 @@ | Option | Possible values | Optional | Info | |-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| overrideDriverVersion | default:
`2.4.0` for MySqlConnector (mysql)
`8.0.6` for Npgsql (postgresql)
`10.0.13` for Microsoft.Data.Sqlite (sqlite)

values: The desired driver version | Yes | Allows you to override the version of DB driver to be used. | +| overrideDriverVersion | default:
`2.4.0` for MySqlConnector (mysql)
`8.0.6` for Npgsql (postgresql)
`10.0.13` for Microsoft.Data.Sqlite (sqlite)
`7.1.1` for Microsoft.Data.SqlClient (mssql)

values: The desired driver version | Yes | Allows you to override the version of DB driver to be used. | | targetFramework | default: `net8.0`
values: `netstandard2.0`, `netstandard2.1`, `net8.0`, `net9.0`, `net10.0` | Yes | Determines the target framework for your generated code, meaning the generated code will be compiled to the specified runtime.
For more information and help deciding on the right value, refer to the [Microsoft .NET Standard documentation](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-1-0). | | generateCsproj | default: `true`
values: `false`,`true` | Yes | Assists you with the integration of SQLC and csharp by generating a `.csproj` file. This converts the generated output to a .dll, a project that you can easily incorporate into your build process. | | namespaceName | default: the generated project name | Yes | Allows you to override the namespace name to be different than the project name | @@ -64,14 +64,14 @@ Advanced functionality - varies between databases: - `:copyfrom` - batch insert, implementation varies greatly
-| Annotation | PostgresSQL | MySQL | SQLite | -|-------------|-------------|-------|---------| -| :one | ✅ | ✅ | ✅ | -| :many | ✅ | ✅ | ✅ | -| :exec | ✅ | ✅ | ✅ | -| :execrows | ✅ | ✅ | ✅ | -| :execlastid | ✅ | ✅ | ✅ | -| :copyfrom | ✅ | ✅ | ✅ | +| Annotation | PostgresSQL | MySQL | SQLite | SQL Server (experimental) | +|-------------|-------------|-------|---------|---------------------------| +| :one | ✅ | ✅ | ✅ | ✅ | +| :many | ✅ | ✅ | ✅ | ✅ | +| :exec | ✅ | ✅ | ✅ | ✅ | +| :execrows | ✅ | ✅ | ✅ | ✅ | +| :execlastid | ✅ | ✅ | ✅ | ✅ | +| :copyfrom | ✅ | ✅ | ✅ | ✅ | More info can be found in [here](https://docs.sqlc.dev/en/stable/reference/query-annotations.html). @@ -82,12 +82,12 @@ More info can be found in [here](https://docs.sqlc.dev/en/stable/reference/query - `sqlc.embed` - Embedding allows you to reuse existing model structs in more queries
-| Annotation | PostgresSQL | MySQL | SQLite | -|-------------|-------------|-------|---------| -| sqlc.arg | ✅ | ✅ | ✅ | -| sqlc.narg | ✅ | ✅ | ✅ | -| sqlc.slice | 🚫 | ✅ | ✅ | -| sqlc.embed | ✅ | ✅ | ✅ | +| Annotation | PostgresSQL | MySQL | SQLite | SQL Server (experimental) | +|-------------|-------------|-------|---------|---------------------------| +| sqlc.arg | ✅ | ✅ | ✅ | ✅ | +| sqlc.narg | ✅ | ✅ | ✅ | ✅ | +| sqlc.slice | 🚫 | ✅ | ✅ | ✅ | +| sqlc.embed | ✅ | ✅ | ✅ | ✅ | More info can be found in [here](https://docs.sqlc.dev/en/stable/reference/macros.html#macros). diff --git a/docs/06_Mssql.md b/docs/06_Mssql.md new file mode 100644 index 000000000..e90a08c85 --- /dev/null +++ b/docs/06_Mssql.md @@ -0,0 +1,60 @@ +# SQL Server (experimental) +> **Status: proof of concept.** sqlc's SQL Server engine (`engine: mssql`) is new and lives on sqlc's `main` branch, +> so a sqlc build from `main` is required. The `sqlc.arg` / `sqlc.narg` / `sqlc.slice` / `sqlc.embed` macros, +> `TRUNCATE TABLE` and `:copyfrom` additionally need the fixes tracked in the sqlc repository (see [Contributing](#contributing)). + +```yaml +sql: + - engine: mssql + schema: schema.sql + queries: query.sql + codegen: + - plugin: csharp + out: Generated +``` + +Parameters are written as `@name` (or with the sqlc macros), and the generated code uses `Microsoft.Data.SqlClient`. + +
+:execlastid - Implementation + +## :execlastid - Implementation +Implemented via an `OUTPUT` clause, allowing the `INSERT` command to return the newly created id. +Use a `SEQUENCE` default (or `IDENTITY`) for the id column. + +```sql +INSERT INTO tab1 (field1, field2) OUTPUT INSERTED.id_field VALUES (@field1, @field2); +``` +
+ +
+:copyfrom - Implementation + +## :copyfrom - Implementation +Implemented via `SqlBulkCopy`: the batch is copied into a `DataTable` and written to the destination table. +
+ +
+Supported Data Types + +| DB Type | C# Type | Supported? | +|------------------------------------|------------------|------------| +| bit | bool | ✅ | +| tinyint | byte | ✅ | +| smallint | short | ✅ | +| int | int | ✅ | +| bigint | long | ✅ | +| real | float | ✅ | +| float | double | ✅ | +| decimal, numeric, money, smallmoney | decimal | ✅ | +| char, nchar, varchar, nvarchar, text, ntext, xml | string | ✅ | +| binary, varbinary, image, rowversion | byte[] | ✅ | +| date, datetime, datetime2, smalldatetime | DateTime | ✅ | +| datetimeoffset | DateTimeOffset | ✅ | +| time | TimeSpan | ✅ | +| uniqueidentifier | Guid | ✅ | +| sql_variant | object | ✅ | + +Not supported yet: `geography`, `geometry`, `hierarchyid`, user defined types, netstandard (legacy) targets. + +
diff --git a/docs/09_Examples.md b/docs/09_Examples.md index 1b6276c49..6d797fb71 100644 --- a/docs/09_Examples.md +++ b/docs/09_Examples.md @@ -589,3 +589,33 @@ namespaceName: NpgsqlDapperAsyncEnumerableExampleGen ``` + +
+Mssql + +## Engine `mssql`: [MssqlExample](examples/MssqlExample) +### [Schema](examples/config/mssql/authors/schema.sql) | [Queries](examples/config/mssql/authors/query.sql) | [End2End Test](end2end/EndToEndTests/MssqlTester.cs) +### Config +```yaml +useDapper: false +targetFramework: net10.0 +generateCsproj: true +namespaceName: MssqlExampleGen +``` + +
+ +
+MssqlDapper + +## Engine `mssql`: [MssqlDapperExample](examples/MssqlDapperExample) +### [Schema](examples/config/mssql/authors/schema.sql) | [Queries](examples/config/mssql/authors/query.sql) | [End2End Test](end2end/EndToEndTests/MssqlDapperTester.cs) +### Config +```yaml +useDapper: true +targetFramework: net9.0 +generateCsproj: true +namespaceName: MssqlDapperExampleGen +``` + +
diff --git a/end2end/EndToEndTests/EndToEndTests.csproj b/end2end/EndToEndTests/EndToEndTests.csproj index db470dd15..d3bd4c26b 100644 --- a/end2end/EndToEndTests/EndToEndTests.csproj +++ b/end2end/EndToEndTests/EndToEndTests.csproj @@ -30,7 +30,7 @@ - + diff --git a/examples/MssqlDapperExample/MssqlDapperExample.csproj b/examples/MssqlDapperExample/MssqlDapperExample.csproj index d866bcf17..aa9a4c70c 100644 --- a/examples/MssqlDapperExample/MssqlDapperExample.csproj +++ b/examples/MssqlDapperExample/MssqlDapperExample.csproj @@ -14,7 +14,7 @@ - + \ No newline at end of file diff --git a/examples/MssqlExample/MssqlExample.csproj b/examples/MssqlExample/MssqlExample.csproj index c44bc8632..c503210c6 100644 --- a/examples/MssqlExample/MssqlExample.csproj +++ b/examples/MssqlExample/MssqlExample.csproj @@ -14,7 +14,7 @@ - + \ No newline at end of file From 28928bad511ef6695a2fda950e329fda662888fc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 08:50:29 +0000 Subject: [PATCH 05/11] mssql: keep the sqlc patches the POC depends on Co-Authored-By: Claude Sonnet 5.5 --- ...ort-sqlc.arg-narg-slice-embed-macros.patch | 115 ++++++++++++++++++ ...T-.-VALUES-an-empty-FROM-clause-so-c.patch | 25 ++++ .../0003-mssql-convert-TRUNCATE-TABLE.patch | 39 ++++++ scripts/sqlc-patches/README.md | 17 +++ 4 files changed, 196 insertions(+) create mode 100644 scripts/sqlc-patches/0001-mssql-support-sqlc.arg-narg-slice-embed-macros.patch create mode 100644 scripts/sqlc-patches/0002-mssql-give-INSERT-.-VALUES-an-empty-FROM-clause-so-c.patch create mode 100644 scripts/sqlc-patches/0003-mssql-convert-TRUNCATE-TABLE.patch create mode 100644 scripts/sqlc-patches/README.md diff --git a/scripts/sqlc-patches/0001-mssql-support-sqlc.arg-narg-slice-embed-macros.patch b/scripts/sqlc-patches/0001-mssql-support-sqlc.arg-narg-slice-embed-macros.patch new file mode 100644 index 000000000..89a02f177 --- /dev/null +++ b/scripts/sqlc-patches/0001-mssql-support-sqlc.arg-narg-slice-embed-macros.patch @@ -0,0 +1,115 @@ +From 3a7c93cd7a0895f3190327a297bbfac15df66ee8 Mon Sep 17 00:00:00 2001 +From: Claude +Date: Tue, 27 Oct 2026 08:30:06 +0000 +Subject: [PATCH 1/3] mssql: support sqlc.arg/narg/slice/embed macros + +--- + internal/compiler/parse_core.go | 43 +++++++++++++++++++++++++++ + internal/sql/preprocess/dialect.go | 9 ++++++ + internal/sql/preprocess/preprocess.go | 5 ++++ + 3 files changed, 57 insertions(+) + +diff --git a/internal/compiler/parse_core.go b/internal/compiler/parse_core.go +index 2e1dfc704..091224415 100644 +--- a/internal/compiler/parse_core.go ++++ b/internal/compiler/parse_core.go +@@ -64,6 +64,7 @@ func (c *Compiler) parseQueryCore(raw *ast.RawStmt, src string, pre *preprocess. + for _, col := range res.Columns { + cols = append(cols, coreColumn(col)) + } ++ cols = collapseEmbeds(cols, res.Stars, pre.Embeds) + placeholders, err := placeholderNames(raw) + if err != nil { + return nil, err +@@ -211,3 +212,45 @@ func coreParamColumn(p core.Parameter, params *named.ParamSet) *Column { + } + return col + } ++ ++// collapseEmbeds replaces the columns a rewritten sqlc.embed() star expanded ++// to with a single column that references the embedded table. ++func collapseEmbeds(cols []*Column, stars []core.StarExpansion, embeds preprocess.EmbedSet) []*Column { ++ for _, star := range stars { ++ embed, ok := embeds.Find(star.Location) ++ if !ok { ++ continue ++ } ++ n := len(star.Columns) ++ if n == 0 { ++ continue ++ } ++ for i := 0; i+n <= len(cols); i++ { ++ if !matchesStar(cols[i:i+n], star) { ++ continue ++ } ++ // The embed names a table or an alias of it; the column ++ // references the table itself. ++ table := embed.Table ++ if src := cols[i].Table; src != nil { ++ table = src ++ } ++ rest := append([]*Column{{ ++ Name: table.Name, ++ EmbedTable: table, ++ }}, cols[i+n:]...) ++ cols = append(cols[:i], rest...) ++ break ++ } ++ } ++ return cols ++} ++ ++func matchesStar(cols []*Column, star core.StarExpansion) bool { ++ for i, sc := range star.Columns { ++ if cols[i].EmbedTable != nil || cols[i].Name != sc.Name || cols[i].TableAlias != sc.Relation { ++ return false ++ } ++ } ++ return true ++} +diff --git a/internal/sql/preprocess/dialect.go b/internal/sql/preprocess/dialect.go +index 4d8d4ae85..8c8e45456 100644 +--- a/internal/sql/preprocess/dialect.go ++++ b/internal/sql/preprocess/dialect.go +@@ -14,6 +14,9 @@ const ( + StyleQuestion + // StyleOrdinal numbers parameters as ?1, ?2, ... (SQLite) + StyleOrdinal ++ ++ // StyleAtName writes parameters as @name (SQL Server) ++ StyleAtName + ) + + // Dialect describes the lexical rules the preprocessor needs to walk a query +@@ -88,6 +91,12 @@ var dialects = map[config.Engine]Dialect{ + Backtick: true, + Backslash: true, + }, ++ // SQL Server binds by name with @name. Identifiers keep their case. ++ config.EngineMSSQL: { ++ Style: StyleAtName, ++ AtSign: true, ++ NestedBlockComment: true, ++ }, + // ClickHouse binds with an unnumbered ?, like MySQL, and its identifiers + // keep their case. + config.EngineClickHouse: { +diff --git a/internal/sql/preprocess/preprocess.go b/internal/sql/preprocess/preprocess.go +index ad25bc9b2..1af0f074a 100644 +--- a/internal/sql/preprocess/preprocess.go ++++ b/internal/sql/preprocess/preprocess.go +@@ -377,6 +377,11 @@ func (d Dialect) replacement(occ *occurrence) string { + return occ.ident + } + switch d.Style { ++ case StyleAtName: ++ if occ.kind == kindSlice { ++ return fmt.Sprintf("/*SLICE:%s*/@%s", occ.name, occ.name) ++ } ++ return "@" + occ.name + case StyleDollar: + // PostgreSQL passes a slice as a single array parameter, so it needs no + // expansion marker. +-- +2.43.0 + diff --git a/scripts/sqlc-patches/0002-mssql-give-INSERT-.-VALUES-an-empty-FROM-clause-so-c.patch b/scripts/sqlc-patches/0002-mssql-give-INSERT-.-VALUES-an-empty-FROM-clause-so-c.patch new file mode 100644 index 000000000..11b95001c --- /dev/null +++ b/scripts/sqlc-patches/0002-mssql-give-INSERT-.-VALUES-an-empty-FROM-clause-so-c.patch @@ -0,0 +1,25 @@ +From f7d038c13fe802489c146466c1ebbae82ca939d3 Mon Sep 17 00:00:00 2001 +From: Claude +Date: Tue, 27 Oct 2026 08:38:09 +0000 +Subject: [PATCH 2/3] mssql: give INSERT ... VALUES an empty FROM clause so + :copyfrom validates + +--- + internal/engine/mssql/convert.go | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/internal/engine/mssql/convert.go b/internal/engine/mssql/convert.go +index bdef335bc..1f387d8b9 100644 +--- a/internal/engine/mssql/convert.go ++++ b/internal/engine/mssql/convert.go +@@ -827,6 +827,7 @@ func (c *cc) convertInsertStatement(n *tsql.InsertStatement) ast.Node { + case *tsql.ValuesInsertSource: + if !src.IsDefaultValues { + sel := &ast.SelectStmt{ ++ FromClause: &ast.List{}, + ValuesLists: &ast.List{}, + } + for _, row := range src.RowValues { +-- +2.43.0 + diff --git a/scripts/sqlc-patches/0003-mssql-convert-TRUNCATE-TABLE.patch b/scripts/sqlc-patches/0003-mssql-convert-TRUNCATE-TABLE.patch new file mode 100644 index 000000000..b2256279e --- /dev/null +++ b/scripts/sqlc-patches/0003-mssql-convert-TRUNCATE-TABLE.patch @@ -0,0 +1,39 @@ +From 286f32e61d4a14e5821d29cbe847e2b5235a12d7 Mon Sep 17 00:00:00 2001 +From: Claude +Date: Tue, 27 Oct 2026 08:40:09 +0000 +Subject: [PATCH 3/3] mssql: convert TRUNCATE TABLE + +--- + internal/engine/mssql/convert.go | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/internal/engine/mssql/convert.go b/internal/engine/mssql/convert.go +index 1f387d8b9..03c1ca1bc 100644 +--- a/internal/engine/mssql/convert.go ++++ b/internal/engine/mssql/convert.go +@@ -65,6 +65,8 @@ func (c *cc) convert(node tsql.Node) ast.Node { + return c.convertAlterTableDropTableElementStatement(n) + case *tsql.CreateTypeUddtStatement: + return c.convertCreateTypeUddtStatement(n) ++ case *tsql.TruncateTableStatement: ++ return c.convertTruncateTableStatement(n) + default: + return todo(n) + } +@@ -1209,3 +1211,13 @@ func (c *cc) convertAlterTableDropTableElementStatement(n *tsql.AlterTableDropTa + } + return stmt + } ++ ++// convertTruncateTableStatement converts TRUNCATE TABLE name. ++func (c *cc) convertTruncateTableStatement(n *tsql.TruncateTableStatement) ast.Node { ++ if n.TableName == nil { ++ return todo(n) ++ } ++ return &ast.TruncateStmt{ ++ Relations: &ast.List{Items: []ast.Node{c.parseRangeVar(n.TableName)}}, ++ } ++} +-- +2.43.0 + diff --git a/scripts/sqlc-patches/README.md b/scripts/sqlc-patches/README.md new file mode 100644 index 000000000..ab99c9f7b --- /dev/null +++ b/scripts/sqlc-patches/README.md @@ -0,0 +1,17 @@ +# sqlc patches for SQL Server (mssql) support + +The mssql engine in sqlc `main` (v1.31.1+) is missing a few things this plugin relies on. These patches are +applied on top of sqlc commit `3ad8b20fb` and are meant to be upstreamed: + +1. `0001` - the mssql dialect for the query preprocessor (`sqlc.arg`, `sqlc.narg`, `sqlc.slice`, `sqlc.embed` + become `@name` / `/*SLICE:name*/@name` / `table.*`) and `sqlc.embed` support in the core analysis path. +2. `0002` - `INSERT ... VALUES` gets an empty FROM clause (without it `:copyfrom` panics with a nil dereference). +3. `0003` - `TRUNCATE TABLE` (statements the converter does not know are silently dropped from the output). + +Build a patched sqlc: + +```bash +git clone https://github.com/sqlc-dev/sqlc && cd sqlc && git checkout 3ad8b20fb +git am /path/to/sqlc-gen-csharp/scripts/sqlc-patches/*.patch +go build -o /usr/local/bin/sqlc ./cmd/sqlc +``` From 80e9eb68408127309611da38699a12159186c2d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 14:24:04 +0000 Subject: [PATCH 06/11] mssql: T-SQL syntax matrix with SQL Server as the oracle, and the plugin fixes it found - end2end/MssqlSyntaxMatrix: 187 T-SQL cases run through sqlc builds, the plugin, the C# compiler and sp_describe_* in SQL Server; writes REPORT.md / results.json - plugin: untyped columns fall back to object instead of failing - plugin: geography / geometry / hierarchyid (Microsoft.SqlServer.Types), NodaTime Instant override - plugin (all engines): escape quotes and backslashes in single-line SQL constants, prefix identifiers that start with a digit, suffix duplicate result column names (and read them by ordinal under Dapper) - sqlc patch 0004: parameter names are case-insensitive, as in SQL Server Co-Authored-By: Claude Opus 5.5 --- CodeGenerator/Generators/QueriesGen.cs | 5 +- Drivers/DbDriver.cs | 29 + Drivers/Generators/ManyDeclareGen.cs | 4 +- Drivers/Generators/OneDeclareGen.cs | 4 +- Drivers/MssqlDriver.cs | 103 +- Extensions/StringExtensions.cs | 3 + Makefile | 9 + end2end/MssqlSyntaxMatrix/CaseResult.cs | 66 + end2end/MssqlSyntaxMatrix/Comparison.cs | 138 + end2end/MssqlSyntaxMatrix/MarkdownReport.cs | 148 + end2end/MssqlSyntaxMatrix/MatrixOptions.cs | 63 + .../MssqlSyntaxMatrix.csproj | 16 + end2end/MssqlSyntaxMatrix/PluginCompiler.cs | 62 + end2end/MssqlSyntaxMatrix/Program.cs | 54 + end2end/MssqlSyntaxMatrix/README.md | 65 + end2end/MssqlSyntaxMatrix/REPORT.md | 1370 ++ end2end/MssqlSyntaxMatrix/SqlServerOracle.cs | 165 + end2end/MssqlSyntaxMatrix/SqlcRunner.cs | 151 + end2end/MssqlSyntaxMatrix/TestCase.cs | 82 + .../cases/aggregate/basic_aggregates.sql | 6 + .../cases/aggregate/group_by_having.sql | 4 + .../aggregate/rollup_cube_grouping_sets.sql | 10 + .../cases/aggregate/statistical.sql | 4 + .../cases/aggregate/string_agg.sql | 4 + .../annotations/copyfrom_identity_table.sql | 4 + .../annotations/copyfrom_schema_scoped.sql | 8 + .../execlastid_output_identity.sql | 4 + .../annotations/execlastid_scope_identity.sql | 5 + .../cases/annotations/execrows.sql | 7 + .../cases/annotations/one_many_exec.sql | 10 + end2end/MssqlSyntaxMatrix/cases/base.sql | 16 + .../cases/batch/declare_variables.sql | 5 + .../cases/batch/select_into_temp_table.sql | 5 + .../cases/batch/set_nocount_on.sql | 5 + .../cases/batch/transaction_in_query.sql | 7 + .../cases/batch/try_catch_throw.sql | 9 + .../cases/cte/cte_with_dml.sql | 9 + .../cases/cte/multiple_ctes.sql | 6 + .../cases/cte/recursive_cte.sql | 9 + .../cases/cte/simple_cte.sql | 5 + .../cases/ddl/alias_type.sql | 14 + .../ddl/alter_column_type_and_nullability.sql | 6 + .../cases/ddl/alter_table_add_column.sql | 6 + .../cases/ddl/alter_table_drop_column.sql | 6 + .../cases/ddl/bracketed_identifiers.sql | 10 + .../cases/ddl/case_insensitive_names.sql | 4 + .../ddl/check_and_unique_constraints.sql | 13 + .../cases/ddl/collate_and_sparse.sql | 10 + .../cases/ddl/computed_columns.sql | 12 + .../cases/ddl/drop_table_if_exists.sql | 8 + .../cases/ddl/foreign_key_on_delete.sql | 12 + .../cases/ddl/go_batch_separators.sql | 9 + .../cases/ddl/identity_seed_increment.sql | 9 + .../MssqlSyntaxMatrix/cases/ddl/indexes.sql | 8 + .../cases/ddl/named_default_constraints.sql | 10 + .../cases/ddl/rowversion.sql | 13 + .../cases/ddl/same_table_name_two_schemas.sql | 14 + .../cases/ddl/schema_scoped_table.sql | 15 + .../cases/ddl/sequence_default.sql | 13 + .../cases/ddl/sp_rename_column.sql | 6 + .../MssqlSyntaxMatrix/cases/ddl/synonym.sql | 6 + .../cases/ddl/table_level_primary_key.sql | 11 + .../cases/ddl/temporal_table.sql | 12 + end2end/MssqlSyntaxMatrix/cases/ddl/view.sql | 8 + .../cases/delete/from_join.sql | 4 + .../cases/delete/output_deleted.sql | 4 + .../MssqlSyntaxMatrix/cases/delete/simple.sql | 7 + .../MssqlSyntaxMatrix/cases/delete/top.sql | 4 + .../cases/delete/truncate.sql | 6 + .../cases/expressions/arithmetic.sql | 4 + .../cases/expressions/at_time_zone.sql | 6 + .../cases/expressions/bitwise.sql | 4 + .../cases/expressions/cast_convert.sql | 10 + .../expressions/coalesce_isnull_nullif.sql | 10 + .../cases/expressions/concat_format.sql | 6 + .../cases/expressions/datetime_functions.sql | 9 + .../cases/expressions/greatest_least.sql | 4 + .../cases/expressions/is_distinct_from.sql | 4 + .../cases/expressions/math_functions.sql | 6 + .../cases/expressions/sql2022_functions.sql | 7 + .../cases/expressions/string_functions.sql | 8 + .../cases/expressions/system_functions.sql | 5 + .../cases/hints/query_hints.sql | 7 + .../cases/hints/table_hints.sql | 10 + .../identifiers/at_sign_parameter_names.sql | 4 + .../identifiers/duplicate_result_names.sql | 4 + .../cases/identifiers/mixed_case_names.sql | 6 + .../non_identifier_column_names.sql | 6 + .../identifiers/reserved_word_columns.sql | 6 + .../identifiers/unnamed_expression_column.sql | 4 + .../cases/insert/default_values.sql | 9 + .../cases/insert/identity_insert.sql | 6 + .../cases/insert/insert_exec.sql | 9 + .../cases/insert/insert_select.sql | 4 + .../cases/insert/multi_row_values.sql | 4 + .../cases/insert/output_inserted.sql | 7 + .../insert/output_into_table_variable.sql | 6 + .../cases/insert/schema_scoped.sql | 8 + .../MssqlSyntaxMatrix/cases/insert/values.sql | 4 + .../cases/joins/cross_apply.sql | 9 + .../cases/joins/cross_join.sql | 7 + .../cases/joins/inner_left_right_full.sql | 13 + .../cases/joins/join_derived_table.sql | 5 + .../cases/joins/schema_qualified_join.sql | 4 + .../cases/joins/self_join.sql | 4 + .../MssqlSyntaxMatrix/cases/json/for_json.sql | 7 + .../cases/json/json_object_array.sql | 4 + .../cases/json/json_value_query.sql | 8 + .../MssqlSyntaxMatrix/cases/json/openjson.sql | 7 + .../cases/macros/arg_forms.sql | 4 + .../cases/macros/arg_in_cast.sql | 4 + .../cases/macros/args_in_top_offset.sql | 7 + .../MssqlSyntaxMatrix/cases/macros/embed.sql | 10 + .../cases/macros/embed_schema_scoped.sql | 8 + .../cases/macros/mixed_with_at_params.sql | 4 + .../MssqlSyntaxMatrix/cases/macros/narg.sql | 4 + .../MssqlSyntaxMatrix/cases/macros/slice.sql | 13 + .../cases/merge/merge_delete.sql | 9 + .../cases/merge/merge_upsert.sql | 9 + .../cases/merge/upsert_without_merge.sql | 6 + .../cases/programmability/exec_procedure.sql | 10 + .../programmability/inline_table_function.sql | 8 + .../multi_statement_table_function.sql | 12 + .../cases/programmability/scalar_function.sql | 10 + .../cases/programmability/string_split.sql | 4 + .../table_valued_parameter.sql | 6 + .../cases/select/alias_equals_syntax.sql | 4 + .../cases/select/between_params.sql | 4 + .../cases/select/case_expression.sql | 7 + .../cases/select/comments.sql | 6 + .../cases/select/derived_table.sql | 4 + .../cases/select/distinct.sql | 4 + .../MssqlSyntaxMatrix/cases/select/exists.sql | 7 + .../cases/select/function_on_parameter.sql | 4 + .../cases/select/iif_choose.sql | 4 + .../cases/select/in_list_params.sql | 4 + .../cases/select/in_subquery_param.sql | 4 + .../cases/select/like_escape.sql | 4 + .../cases/select/no_semicolon_terminator.sql | 7 + .../cases/select/offset_fetch.sql | 4 + .../cases/select/offset_without_fetch.sql | 4 + .../cases/select/optional_filter.sql | 4 + .../cases/select/order_by_parameter.sql | 5 + .../cases/select/parameter_in_select_list.sql | 4 + .../cases/select/quoted_aliases.sql | 4 + .../cases/select/repeated_parameter.sql | 4 + .../cases/select/scalar_subquery_column.sql | 4 + .../cases/select/select_star.sql | 7 + .../cases/select/table_value_constructor.sql | 4 + .../cases/select/top_literal_with_ties.sql | 7 + .../cases/select/top_parameter.sql | 4 + .../cases/select/unicode_literals.sql | 4 + .../cases/select/without_from.sql | 7 + .../cases/setops/pivot_unpivot.sql | 13 + .../cases/setops/union_except_intersect.sql | 13 + .../MssqlSyntaxMatrix/cases/types/bigint.sql | 12 + .../MssqlSyntaxMatrix/cases/types/binary.sql | 12 + end2end/MssqlSyntaxMatrix/cases/types/bit.sql | 12 + .../MssqlSyntaxMatrix/cases/types/char.sql | 12 + .../MssqlSyntaxMatrix/cases/types/date.sql | 12 + .../cases/types/datetime.sql | 12 + .../cases/types/datetime2.sql | 12 + .../cases/types/datetime2_0.sql | 12 + .../cases/types/datetimeoffset.sql | 12 + .../MssqlSyntaxMatrix/cases/types/decimal.sql | 12 + .../MssqlSyntaxMatrix/cases/types/float.sql | 12 + .../cases/types/float_24.sql | 12 + .../cases/types/geography.sql | 9 + .../cases/types/geometry.sql | 9 + .../cases/types/hierarchyid.sql | 12 + .../MssqlSyntaxMatrix/cases/types/image.sql | 9 + end2end/MssqlSyntaxMatrix/cases/types/int.sql | 12 + .../cases/types/max_lengths_and_precision.sql | 6 + .../MssqlSyntaxMatrix/cases/types/money.sql | 12 + .../MssqlSyntaxMatrix/cases/types/nchar.sql | 12 + .../MssqlSyntaxMatrix/cases/types/ntext.sql | 9 + .../MssqlSyntaxMatrix/cases/types/numeric.sql | 12 + .../cases/types/nvarchar.sql | 12 + .../cases/types/nvarchar_max.sql | 12 + .../MssqlSyntaxMatrix/cases/types/real.sql | 12 + .../cases/types/rowversion_timestamp.sql | 10 + .../cases/types/smalldatetime.sql | 12 + .../cases/types/smallint.sql | 12 + .../cases/types/smallmoney.sql | 12 + .../cases/types/sql_variant.sql | 12 + .../MssqlSyntaxMatrix/cases/types/sysname.sql | 12 + .../MssqlSyntaxMatrix/cases/types/text.sql | 9 + .../MssqlSyntaxMatrix/cases/types/time.sql | 12 + .../MssqlSyntaxMatrix/cases/types/tinyint.sql | 12 + .../cases/types/uniqueidentifier.sql | 12 + .../cases/types/varbinary.sql | 12 + .../cases/types/varbinary_max.sql | 12 + .../MssqlSyntaxMatrix/cases/types/varchar.sql | 12 + .../cases/types/varchar_max.sql | 12 + end2end/MssqlSyntaxMatrix/cases/types/xml.sql | 9 + .../cases/update/compound_assignment.sql | 4 + .../cases/update/from_join.sql | 4 + .../cases/update/output_deleted_inserted.sql | 4 + .../cases/update/set_default.sql | 4 + .../MssqlSyntaxMatrix/cases/update/simple.sql | 4 + .../MssqlSyntaxMatrix/cases/update/top.sql | 4 + .../cases/window/aggregate_over.sql | 6 + .../cases/window/lag_lead_first_value.sql | 9 + .../cases/window/ranking.sql | 9 + .../window/window_clause_and_percentiles.sql | 10 + .../MssqlSyntaxMatrix/cases/xml/for_xml.sql | 7 + .../cases/xml/xml_methods.sql | 10 + end2end/MssqlSyntaxMatrix/results.json | 11652 ++++++++++++++++ ...ort-sqlc.arg-narg-slice-embed-macros.patch | 2 +- ...T-.-VALUES-an-empty-FROM-clause-so-c.patch | 2 +- .../0003-mssql-convert-TRUNCATE-TABLE.patch | 2 +- ...names-are-case-insensitive-as-in-SQL.patch | 80 + sqlc-gen-csharp.sln | 17 + .../CodegenTests/CodegenQueryShapeTests.cs | 79 + unit-tests/CodegenTests/ExtensionsTests.cs | 2 + 215 files changed, 15830 insertions(+), 12 deletions(-) create mode 100644 end2end/MssqlSyntaxMatrix/CaseResult.cs create mode 100644 end2end/MssqlSyntaxMatrix/Comparison.cs create mode 100644 end2end/MssqlSyntaxMatrix/MarkdownReport.cs create mode 100644 end2end/MssqlSyntaxMatrix/MatrixOptions.cs create mode 100644 end2end/MssqlSyntaxMatrix/MssqlSyntaxMatrix.csproj create mode 100644 end2end/MssqlSyntaxMatrix/PluginCompiler.cs create mode 100644 end2end/MssqlSyntaxMatrix/Program.cs create mode 100644 end2end/MssqlSyntaxMatrix/README.md create mode 100644 end2end/MssqlSyntaxMatrix/REPORT.md create mode 100644 end2end/MssqlSyntaxMatrix/SqlServerOracle.cs create mode 100644 end2end/MssqlSyntaxMatrix/SqlcRunner.cs create mode 100644 end2end/MssqlSyntaxMatrix/TestCase.cs create mode 100644 end2end/MssqlSyntaxMatrix/cases/aggregate/basic_aggregates.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/aggregate/group_by_having.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/aggregate/rollup_cube_grouping_sets.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/aggregate/statistical.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/aggregate/string_agg.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_identity_table.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_schema_scoped.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_output_identity.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_scope_identity.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/annotations/execrows.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/annotations/one_many_exec.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/base.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/batch/declare_variables.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/batch/select_into_temp_table.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/batch/set_nocount_on.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/batch/transaction_in_query.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/batch/try_catch_throw.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/cte/cte_with_dml.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/cte/multiple_ctes.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/cte/recursive_cte.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/cte/simple_cte.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/alias_type.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/alter_column_type_and_nullability.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_add_column.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_drop_column.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/bracketed_identifiers.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/case_insensitive_names.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/check_and_unique_constraints.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/collate_and_sparse.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/computed_columns.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/drop_table_if_exists.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/foreign_key_on_delete.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/go_batch_separators.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/identity_seed_increment.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/indexes.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/named_default_constraints.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/rowversion.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/same_table_name_two_schemas.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/schema_scoped_table.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/sequence_default.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/sp_rename_column.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/synonym.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/table_level_primary_key.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/temporal_table.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/ddl/view.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/delete/from_join.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/delete/output_deleted.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/delete/simple.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/delete/top.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/delete/truncate.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/arithmetic.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/at_time_zone.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/bitwise.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/cast_convert.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/coalesce_isnull_nullif.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/concat_format.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/datetime_functions.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/greatest_least.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/is_distinct_from.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/math_functions.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/sql2022_functions.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/string_functions.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/expressions/system_functions.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/hints/query_hints.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/hints/table_hints.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/identifiers/at_sign_parameter_names.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/identifiers/duplicate_result_names.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/identifiers/mixed_case_names.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/identifiers/non_identifier_column_names.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/identifiers/reserved_word_columns.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/identifiers/unnamed_expression_column.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/insert/default_values.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/insert/identity_insert.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/insert/insert_exec.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/insert/insert_select.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/insert/multi_row_values.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/insert/output_inserted.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/insert/output_into_table_variable.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/insert/schema_scoped.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/insert/values.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/joins/cross_apply.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/joins/cross_join.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/joins/inner_left_right_full.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/joins/join_derived_table.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/joins/schema_qualified_join.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/joins/self_join.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/json/for_json.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/json/json_object_array.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/json/json_value_query.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/json/openjson.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/macros/arg_forms.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/macros/arg_in_cast.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/macros/args_in_top_offset.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/macros/embed.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/macros/embed_schema_scoped.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/macros/mixed_with_at_params.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/macros/narg.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/macros/slice.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/merge/merge_delete.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/merge/merge_upsert.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/merge/upsert_without_merge.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/programmability/exec_procedure.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/programmability/inline_table_function.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/programmability/multi_statement_table_function.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/programmability/scalar_function.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/programmability/string_split.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/programmability/table_valued_parameter.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/alias_equals_syntax.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/between_params.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/case_expression.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/comments.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/derived_table.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/distinct.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/exists.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/function_on_parameter.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/iif_choose.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/in_list_params.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/in_subquery_param.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/like_escape.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/no_semicolon_terminator.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/offset_fetch.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/offset_without_fetch.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/optional_filter.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/order_by_parameter.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/parameter_in_select_list.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/quoted_aliases.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/repeated_parameter.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/scalar_subquery_column.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/select_star.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/table_value_constructor.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/top_literal_with_ties.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/top_parameter.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/unicode_literals.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/select/without_from.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/setops/pivot_unpivot.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/setops/union_except_intersect.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/bigint.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/binary.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/bit.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/char.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/date.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/datetime.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/datetime2.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/datetime2_0.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/datetimeoffset.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/decimal.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/float.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/float_24.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/geography.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/geometry.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/hierarchyid.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/image.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/int.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/max_lengths_and_precision.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/money.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/nchar.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/ntext.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/numeric.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/nvarchar.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/nvarchar_max.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/real.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/rowversion_timestamp.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/smalldatetime.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/smallint.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/smallmoney.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/sql_variant.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/sysname.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/text.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/time.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/tinyint.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/uniqueidentifier.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/varbinary.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/varbinary_max.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/varchar.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/varchar_max.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/types/xml.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/update/compound_assignment.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/update/from_join.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/update/output_deleted_inserted.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/update/set_default.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/update/simple.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/update/top.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/window/aggregate_over.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/window/lag_lead_first_value.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/window/ranking.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/window/window_clause_and_percentiles.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/xml/for_xml.sql create mode 100644 end2end/MssqlSyntaxMatrix/cases/xml/xml_methods.sql create mode 100644 end2end/MssqlSyntaxMatrix/results.json create mode 100644 scripts/sqlc-patches/0004-mssql-parameter-names-are-case-insensitive-as-in-SQL.patch create mode 100644 unit-tests/CodegenTests/CodegenQueryShapeTests.cs diff --git a/CodeGenerator/Generators/QueriesGen.cs b/CodeGenerator/Generators/QueriesGen.cs index 99559061d..451d4d17c 100644 --- a/CodeGenerator/Generators/QueriesGen.cs +++ b/CodeGenerator/Generators/QueriesGen.cs @@ -167,7 +167,10 @@ private IEnumerable GetMembersForSingleQuery(Query quer $"{memberIndentPrefix}{declaration}{fullQueryString}\";")!; } - var singleLineQueryText = LongWhitespaceRegex().Replace(transformedQueryText, " "); + // a regular string literal: backslashes and double quotes (quoted identifiers) must be escaped + var singleLineQueryText = LongWhitespaceRegex().Replace(transformedQueryText, " ") + .Replace("\\", "\\\\") + .Replace("\"", "\\\""); return ParseMemberDeclaration( $"""private const string {memberName} = "{singleLineQueryText}";""")! .AppendNewLine(); diff --git a/Drivers/DbDriver.cs b/Drivers/DbDriver.cs index 30668a4dd..d8a232d0f 100644 --- a/Drivers/DbDriver.cs +++ b/Drivers/DbDriver.cs @@ -131,6 +131,8 @@ protected DbDriver( DefaultSchema = catalog.DefaultSchema; Tables = ConstructTablesLookup(catalog); Queries = queries; + foreach (var query in Queries) + DisambiguateColumnNames(query); if (!Options.DotnetFramework.IsDotnetCore()) return; @@ -139,6 +141,33 @@ protected DbDriver( NullableTypes.Add(t); } + private readonly HashSet _queriesWithRenamedColumns = []; + + // Two result columns with the same name (SELECT a.id, b.id) would become two properties with the same name, + // so the later ones get a numeric suffix: id, id_2, id_3 + private void DisambiguateColumnNames(Query query) + { + var seen = new Dictionary(); + foreach (var column in query.Columns.Where(c => c.EmbedTable is null)) + { + var key = column.Name.ToPascalCase(); + if (!seen.TryGetValue(key, out var count)) + { + seen[key] = 1; + continue; + } + seen[key] = ++count; + column.Name = $"{column.Name}_{count}"; + _queriesWithRenamedColumns.Add(query.Name); + } + } + + // Dapper maps result columns to properties by name, which cannot work for embedded tables or renamed duplicates + public bool MustReadByOrdinal(Query query) + { + return query.Columns.Any(c => c.EmbedTable is not null) || _queriesWithRenamedColumns.Contains(query.Name); + } + private static readonly HashSet _excludedSchemas = [ "pg_catalog", diff --git a/Drivers/Generators/ManyDeclareGen.cs b/Drivers/Generators/ManyDeclareGen.cs index 9e8dbadc0..b70bfdbd2 100644 --- a/Drivers/Generators/ManyDeclareGen.cs +++ b/Drivers/Generators/ManyDeclareGen.cs @@ -29,8 +29,8 @@ public MemberDeclarationSyntax Generate(string queryTextConstant, string argInte private string GetMethodBody(string queryTextConstant, string returnInterface, Query query) { var sqlTextTransform = CommonGen.GetSqlTransformations(query, queryTextConstant); - var anyEmbeddedTableExists = query.Columns.Any(c => c.EmbedTable is not null); - var useDapper = dbDriver.Options.UseDapper && !anyEmbeddedTableExists; + var mustReadByOrdinal = dbDriver.MustReadByOrdinal(query); + var useDapper = dbDriver.Options.UseDapper && !mustReadByOrdinal; var dapperParams = useDapper ? CommonGen.ConstructDapperParamsDict(query) : string.Empty; var sqlVar = sqlTextTransform != string.Empty ? Variable.TransformedSql.AsVarName() : queryTextConstant; diff --git a/Drivers/Generators/OneDeclareGen.cs b/Drivers/Generators/OneDeclareGen.cs index ce2d95997..a6fcdb7c4 100644 --- a/Drivers/Generators/OneDeclareGen.cs +++ b/Drivers/Generators/OneDeclareGen.cs @@ -25,8 +25,8 @@ public MemberDeclarationSyntax Generate(string queryTextConstant, string argInte private string GetMethodBody(string queryTextConstant, string returnInterface, Query query) { var sqlTextTransform = CommonGen.GetSqlTransformations(query, queryTextConstant); - var anyEmbeddedTableExists = query.Columns.Any(c => c.EmbedTable is not null); - var useDapper = dbDriver.Options.UseDapper && !anyEmbeddedTableExists; + var mustReadByOrdinal = dbDriver.MustReadByOrdinal(query); + var useDapper = dbDriver.Options.UseDapper && !mustReadByOrdinal; var dapperParams = useDapper ? CommonGen.ConstructDapperParamsDict(query) : string.Empty; var sqlVar = sqlTextTransform != string.Empty ? Variable.TransformedSql.AsVarName() : queryTextConstant; diff --git a/Drivers/MssqlDriver.cs b/Drivers/MssqlDriver.cs index 18e092fa0..3133c8f99 100644 --- a/Drivers/MssqlDriver.cs +++ b/Drivers/MssqlDriver.cs @@ -11,6 +11,10 @@ public sealed class MssqlDriver : DbDriver, IOne, IMany, IExec, IExecRows, IExecLastId, ICopyFrom { private const string DefaultSqlClientVersion = "7.1.1"; + private const string DefaultSqlServerTypesVersion = "160.1000.6"; + + // CLR types of the spatial and hierarchyid columns, from Microsoft.SqlServer.Types + private static readonly HashSet SqlServerTypes = ["SqlGeography", "SqlGeometry", "SqlHierarchyId"]; public MssqlDriver( Options options, @@ -18,6 +22,7 @@ public MssqlDriver( IList queries) : base(options, catalog, queries) { NullableTypes.Add("DateTimeOffset"); + NullableTypes.Add("SqlHierarchyId"); } // Dapper sends a null byte[] as nvarchar, which SQL Server refuses to convert to a binary type @@ -39,6 +44,26 @@ public void AddParameter(IDbCommand command, string name) } """; + private static readonly SqlMapperImplFunc NodaInstantTypeHandler = _ => """ + private class NodaInstantTypeHandler : SqlMapper.TypeHandler + { + public override Instant Parse(object value) + { + if (value is DateTime dt) + return DateTime.SpecifyKind(dt, DateTimeKind.Utc).ToInstant(); + if (value is DateTimeOffset dto) + return dto.ToInstant(); + throw new DataException($"Cannot convert {value?.GetType()} to Instant"); + } + + public override void SetValue(IDbDataParameter parameter, Instant value) + { + parameter.Value = value.ToDateTimeUtc(); + parameter.DbType = DbType.DateTime2; + } + } + """; + protected override Dictionary ColumnMappings { get; } = new() { @@ -185,11 +210,64 @@ public void AddParameter(IDbCommand command, string name) readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetGuid({ordinal})", convertFunc: x => $"Guid.Parse({x}.ToString())" ), + ["SqlGeography"] = new( + new() + { + { "geography", new(DbTypeOverride: "SqlDbType.Udt") } + }, + readerFn: (ordinal, _) => $"SqlGeography.Deserialize({Variable.Reader.AsVarName()}.GetSqlBytes({ordinal}))", + usingDirectives: ["Microsoft.SqlServer.Types"] + ), + ["SqlGeometry"] = new( + new() + { + { "geometry", new(DbTypeOverride: "SqlDbType.Udt") } + }, + readerFn: (ordinal, _) => $"SqlGeometry.Deserialize({Variable.Reader.AsVarName()}.GetSqlBytes({ordinal}))", + usingDirectives: ["Microsoft.SqlServer.Types"] + ), + ["SqlHierarchyId"] = new( + new() + { + { "hierarchyid", new(DbTypeOverride: "SqlDbType.Udt") } + }, + readerFn: (ordinal, _) => $$""" + (new Func(b => + { + var id = new SqlHierarchyId(); + id.Read(new System.IO.BinaryReader(b.Stream)); + return id; + }))({{Variable.Reader.AsVarName()}}.GetSqlBytes({{ordinal}})) + """, + usingDirectives: ["Microsoft.SqlServer.Types"] + ), + + /* Reachable only through overrides (datetime, datetime2, datetimeoffset columns) */ + ["Instant"] = new( + [], + readerFn: (ordinal, dbType) => dbType.ToLower() == "datetimeoffset" + ? $"{Variable.Reader.AsVarName()}.GetFieldValue({ordinal}).ToInstant()" + : $"DateTime.SpecifyKind({Variable.Reader.AsVarName()}.GetDateTime({ordinal}), DateTimeKind.Utc).ToInstant()", + writerFn: (el, dbType, notNull, isDapper, _) => + { + var convert = dbType.ToLower() == "datetimeoffset" ? "ToDateTimeOffset()" : "ToDateTimeUtc()"; + if (notNull) + return $"{el}.{convert}"; + var nullValue = isDapper ? "null" : "(object)DBNull.Value"; + return $"{el} is null ? {nullValue} : {el}.Value.{convert}"; + }, + usingDirectives: ["System", "NodaTime", "NodaTime.Extensions"], + sqlMapper: "SqlMapper.AddTypeHandler(typeof(Instant), new NodaInstantTypeHandler());", + sqlMapperImpl: NodaInstantTypeHandler + ), + ["object"] = new( new() { { "sql_variant", new(DbTypeOverride: "SqlDbType.Variant") }, - { "any", new() } + { "any", new() }, + // sqlc leaves the type of many expressions empty; they are read as object rather than failing + { "", new() } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetValue({ordinal})" ) @@ -204,7 +282,11 @@ public override IDictionary GetPackageReferences() .Merge(new Dictionary { { "Microsoft.Data.SqlClient", Options.OverrideDriverVersion != string.Empty ? Options.OverrideDriverVersion : DefaultSqlClientVersion } - }); + }) + .MergeIf(new Dictionary + { + { "Microsoft.SqlServer.Types", DefaultSqlServerTypesVersion } + }, SqlServerTypesInUse()); } // Parameters get an explicit SqlDbType, otherwise a null is sent as nvarchar and @@ -231,6 +313,8 @@ public override string AddParametersToCommand(Query query) Options.DotnetFramework.IsDotnetLegacy()); var dbType = GetColumnDbTypeOverride(p.Column); + if (dbType == "SqlDbType.Udt") + return $$"""{{commandVar}}.Parameters.Add(new SqlParameter("@{{p.Column.Name}}", SqlDbType.Udt) { UdtTypeName = "{{p.Column.Type.Name.ToLower()}}", Value = {{paramToWrite}} });"""; return dbType is null ? $"""{commandVar}.Parameters.AddWithValue("@{p.Column.Name}", {paramToWrite});""" : $"""{commandVar}.Parameters.Add("@{p.Column.Name}", {dbType}).Value = {paramToWrite};"""; @@ -241,14 +325,25 @@ public override ISet GetUsingDirectivesForQueries() { return base .GetUsingDirectivesForQueries() - .AddRangeExcludeNulls(["Microsoft.Data.SqlClient"]); + .AddRangeExcludeNulls(["Microsoft.Data.SqlClient"]) + .AddRangeIf(["Microsoft.SqlServer.Types"], SqlServerTypes.Any(TypeExistsInQueries)); } public override ISet GetUsingDirectivesForModels() { return base .GetUsingDirectivesForModels() - .AddRangeExcludeNulls(["System"]); + .AddRangeExcludeNulls(["System"]) + .AddRangeIf(["Microsoft.SqlServer.Types"], SqlServerTypesInUse()); + } + + // table models are generated for every table, so the spatial types count when any table column uses them + private bool SqlServerTypesInUse() + { + return SqlServerTypes.Any(TypeExistsInQueries) || Tables.Values + .SelectMany(t => t.Values) + .SelectMany(t => t.Columns) + .Any(c => SqlServerTypes.Contains(GetCsharpTypeWithoutNullableSuffix(c, null))); } public override ISet GetUsingDirectivesForUtils() diff --git a/Extensions/StringExtensions.cs b/Extensions/StringExtensions.cs index e23b60d8c..4bce51eb7 100644 --- a/Extensions/StringExtensions.cs +++ b/Extensions/StringExtensions.cs @@ -36,6 +36,9 @@ public static string ToPascalCase(this string value) sb.Append(word[1..]); } + // a C# identifier cannot start with a digit (e.g. a column named [1st place]) + if (sb.Length > 0 && char.IsDigit(sb[0])) + sb.Insert(0, '_'); return sb.ToString(); } diff --git a/Makefile b/Makefile index 467f692cd..f224b8b4b 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,15 @@ $(WASM_PLUGIN_OUTPUT): $(WASM_SOURCES) ./scripts/wasm/copy_plugin_to.sh dist ./scripts/wasm/update_sha.sh sqlc.ci.yaml +# SQL Server syntax matrix (needs SQL Server running and the process plugin published) +SQLC_UPSTREAM ?= sqlc +SQLC_PATCHED ?= sqlc +mssql-syntax-matrix: dotnet-publish-process + dotnet run --project end2end/MssqlSyntaxMatrix -- \ + --sqlc "upstream=$(SQLC_UPSTREAM)@sqlc main" \ + --sqlc "patched=$(SQLC_PATCHED)@sqlc main + scripts/sqlc-patches" \ + --compile + run-end2end-tests: ./end2end/scripts/run_tests.sh diff --git a/end2end/MssqlSyntaxMatrix/CaseResult.cs b/end2end/MssqlSyntaxMatrix/CaseResult.cs new file mode 100644 index 000000000..415fb17e8 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/CaseResult.cs @@ -0,0 +1,66 @@ +namespace MssqlSyntaxMatrix; + +public class CaseResult(TestCase testCase) +{ + public TestCase Case { get; } = testCase; + public Dictionary Sqlc { get; } = []; + public OracleResult Oracle { get; set; } = new(); + public RunStatus CompileStatus { get; set; } = RunStatus.Skipped; + public string CompileError { get; set; } = ""; + + // the verdict of every query for one sqlc build, in case order + public List<(CaseQuery Query, QueryVerdict Verdict)> Verdicts(string build) + { + var sqlc = Sqlc[build]; + return [.. Case.Queries.Select(q => + { + var verdict = new QueryVerdict(); + if (sqlc.Status == RunStatus.Panic) + verdict.Add(Level.Panic, $"sqlc panics: {FirstLine(sqlc.Error)}"); + else if (sqlc.Status == RunStatus.Error && sqlc.Error.Contains("no queries contained in paths")) + verdict.Add(Level.Dropped, "every statement is silently dropped (sqlc: no queries contained in paths)"); + else if (sqlc.Status == RunStatus.Error) + verdict.Add(Level.Error, sqlc.Error); + else if (!sqlc.Queries.TryGetValue(q.Name, out var query)) + verdict.Add(Level.Dropped, "the query is silently missing from sqlc's output"); + else + verdict = Comparison.Compare(query, Oracle.Queries.GetValueOrDefault(q.Name)); + return (q, verdict); + })]; + } + + public Level CaseLevel(string build) => + Verdicts(build).Select(v => v.Verdict.Level).DefaultIfEmpty(Level.Ok).Max(); + + public static string FirstLine(string text) => text.Split('\n').FirstOrDefault(l => l.Trim().Length > 0)?.Trim() ?? ""; + + public object ToJson(MatrixOptions options) => new + { + id = Case.Id, + description = Case.Description, + schemaError = Oracle.SchemaError, + builds = options.SqlcBuilds.ToDictionary(b => b.Name, b => new + { + status = Sqlc[b.Name].Status.ToString(), + level = CaseLevel(b.Name).ToString(), + error = Sqlc[b.Name].Error, + queries = Verdicts(b.Name).Select(v => new + { + name = v.Query.Name, + level = v.Verdict.Level.ToString(), + findings = v.Verdict.Findings.Select(f => $"{f.Level}: {f.Message}") + }) + }), + plugin = Sqlc[options.PluginBuild.Name].PluginStatus.ToString(), + pluginError = Sqlc[options.PluginBuild.Name].PluginError, + compile = CompileStatus.ToString(), + compileError = CompileError, + oracle = Oracle.Queries.ToDictionary(q => q.Key, q => new + { + paramsError = q.Value.ParamsError, + columnsError = q.Value.ColumnsError, + @params = q.Value.Params?.Select(p => $"{p.Name} {p.Type}"), + columns = q.Value.Columns?.Select(c => $"{c.Name} {c.Type}{(c.Nullable ? "" : " NOT NULL")}") + }) + }; +} \ No newline at end of file diff --git a/end2end/MssqlSyntaxMatrix/Comparison.cs b/end2end/MssqlSyntaxMatrix/Comparison.cs new file mode 100644 index 000000000..ffd278b5f --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/Comparison.cs @@ -0,0 +1,138 @@ +namespace MssqlSyntaxMatrix; + +// Ordered from best to worst; a case gets the worst level of its queries. +public enum Level { Ok, Minor, Mismatch, Dropped, Error, Panic } + +public record Finding(Level Level, string Message); + +public class QueryVerdict +{ + public Level Level => Findings.Count == 0 ? Level.Ok : Findings.Max(f => f.Level); + public List Findings { get; } = []; + public void Add(Level level, string message) => Findings.Add(new Finding(level, message)); +} + +// Compares what sqlc reports for a query with what SQL Server reports for it. +public static class Comparison +{ + private static readonly Dictionary Synonyms = new() + { + ["numeric"] = "decimal", + ["integer"] = "int", + ["double precision"] = "float", + ["rowversion"] = "timestamp", + ["sysname"] = "nvarchar", + ["character varying"] = "varchar", + ["national character varying"] = "nvarchar" + }; + + private static readonly string[][] Families = + [ + ["bit"], + ["tinyint", "smallint", "int", "bigint"], + ["real", "float"], + ["decimal", "money", "smallmoney"], + ["char", "nchar", "varchar", "nvarchar", "text", "ntext"], + ["binary", "varbinary", "image", "timestamp"], + ["date", "datetime", "datetime2", "smalldatetime"] + ]; + + public static string Normalize(string type) + { + var t = type.Trim().ToLowerInvariant(); + var paren = t.IndexOf('('); + if (paren >= 0) + t = t[..paren].Trim(); + if (t.StartsWith("sys.") || t.StartsWith("dbo.")) + t = t[4..]; + return Synonyms.GetValueOrDefault(t, t); + } + + private static bool IsUntyped(string type) => Normalize(type) is "" or "any" or "unknown"; + + private static bool SameFamily(string a, string b) => Families.Any(f => f.Contains(a) && f.Contains(b)); + + public static QueryVerdict Compare(SqlcQuery query, OracleQuery? oracle) + { + var verdict = new QueryVerdict(); + CompareParams(query, oracle, verdict); + CompareColumns(query, oracle, verdict); + return verdict; + } + + private static void CompareParams(SqlcQuery query, OracleQuery? oracle, QueryVerdict verdict) + { + foreach (var p in query.Params.Where(p => IsUntyped(p.Type))) + { + var oracleType = oracle?.Params?.FirstOrDefault(o => Same(o.Name, p.Name))?.Type; + verdict.Add(Level.Mismatch, oracleType is { Length: > 0 } + ? $"parameter @{p.Name} has no type (SQL Server: {oracleType})" + : $"parameter @{p.Name} has no type (SQL Server cannot deduce it either)"); + } + + if (oracle?.Params is null) + return; + + foreach (var o in oracle.Params) + { + var p = query.Params.FirstOrDefault(p => Same(o.Name, p.Name)); + if (p is null) + { + verdict.Add(Level.Mismatch, $"parameter {o.Name} ({o.Type}) is missing"); + continue; + } + if (IsUntyped(p.Type) || o.Type == "") + continue; + var (sqlcType, oracleType) = (Normalize(p.Type), Normalize(o.Type)); + if (sqlcType == oracleType) + continue; + verdict.Add(SameFamily(sqlcType, oracleType) ? Level.Minor : Level.Mismatch, + $"parameter @{p.Name}: sqlc {p.Type}, SQL Server {o.Type}"); + } + + foreach (var p in query.Params.Where(p => !oracle.Params.Any(o => Same(o.Name, p.Name)))) + verdict.Add(Level.Mismatch, $"parameter @{p.Name} is not a parameter for SQL Server"); + } + + private static void CompareColumns(SqlcQuery query, OracleQuery? oracle, QueryVerdict verdict) + { + foreach (var c in query.Columns.Where(c => c.EmbedTable is null && IsUntyped(c.Type))) + verdict.Add(Level.Mismatch, $"column {Display(c.Name)} has no type"); + + if (oracle?.Columns is null || query.Columns.Any(c => c.EmbedTable is not null)) + return; + + if (query.Columns.Count != oracle.Columns.Count) + { + verdict.Add(Level.Mismatch, $"{query.Columns.Count} result columns, SQL Server returns {oracle.Columns.Count}" + + $" ({string.Join(", ", oracle.Columns.Select(c => Display(c.Name)))})"); + return; + } + + for (var i = 0; i < query.Columns.Count; i++) + { + var (c, o) = (query.Columns[i], oracle.Columns[i]); + if (o.Name == "") + verdict.Add(Level.Minor, $"column {i + 1} has no name in SQL Server (sqlc calls it {Display(c.Name)})"); + else if (!Same(c.Name, o.Name)) + verdict.Add(Level.Mismatch, $"column {i + 1} is named {Display(c.Name)}, SQL Server names it {o.Name}"); + else if (c.Name != o.Name) + verdict.Add(Level.Minor, $"column {i + 1} is named {c.Name}, SQL Server keeps the case: {o.Name}"); + + if (!IsUntyped(c.Type) && Normalize(c.Type) != Normalize(o.Type)) + verdict.Add(Level.Mismatch, $"column {Display(c.Name)}: sqlc {c.Type}, SQL Server {o.Type}"); + + // SQL Server reports most expressions (COUNT(*), CASE, ...) as nullable, so only a table column is a sure miss + if (c.NotNull && o.Nullable) + verdict.Add(c.FromTable ? Level.Mismatch : Level.Minor, + $"column {Display(c.Name)}: sqlc NOT NULL, SQL Server nullable" + (c.FromTable ? "" : " (SQL Server is conservative for expressions)")); + else if (!c.NotNull && !o.Nullable) + verdict.Add(Level.Minor, $"column {Display(c.Name)}: sqlc nullable, SQL Server NOT NULL"); + } + } + + private static bool Same(string a, string b) => + string.Equals(a.TrimStart('@'), b.TrimStart('@'), StringComparison.OrdinalIgnoreCase); + + private static string Display(string name) => name == "" ? "(unnamed)" : name; +} \ No newline at end of file diff --git a/end2end/MssqlSyntaxMatrix/MarkdownReport.cs b/end2end/MssqlSyntaxMatrix/MarkdownReport.cs new file mode 100644 index 000000000..c47ec3f02 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/MarkdownReport.cs @@ -0,0 +1,148 @@ +using System.Text; + +namespace MssqlSyntaxMatrix; + +public static class MarkdownReport +{ + private static string Icon(Level level) => level switch + { + Level.Ok => "✅", + Level.Minor => "🟡", + Level.Mismatch => "🟠", + Level.Dropped => "🕳️", + Level.Error => "❌", + Level.Panic => "💥", + _ => "?" + }; + + private static string Icon(RunStatus status) => status switch + { + RunStatus.Ok => "✅", + RunStatus.Error => "❌", + RunStatus.Panic => "💥", + _ => "–" + }; + + public static string Render(List results, MatrixOptions options) + { + var builds = options.SqlcBuilds; + var sb = new StringBuilder(); + sb.AppendLine("# SQL Server (mssql) syntax matrix"); + sb.AppendLine(); + sb.AppendLine(""); + sb.AppendLine("Every case in [`cases/`](cases) is run through each sqlc build, the C# plugin, and SQL Server's own"); + sb.AppendLine("`sp_describe_undeclared_parameters` / `sp_describe_first_result_set`, which act as the oracle for what"); + sb.AppendLine("the parameter and result column types should be. See [README.md](README.md)."); + sb.AppendLine(); + foreach (var build in builds) + sb.AppendLine($"- **{build.Name}**: {(build.Label == "" ? build.Path : build.Label)}"); + sb.AppendLine(); + sb.AppendLine("| Legend | |"); + sb.AppendLine("|---|---|"); + sb.AppendLine("| ✅ | matches SQL Server |"); + sb.AppendLine("| 🟡 | minor difference (sqlc is more conservative, or a compatible type of the same family) |"); + sb.AppendLine("| 🟠 | wrong or missing type, nullability, name or column count |"); + sb.AppendLine("| 🕳️ | the query is silently dropped from sqlc's output |"); + sb.AppendLine("| ❌ | sqlc fails |"); + sb.AppendLine("| 💥 | sqlc panics |"); + sb.AppendLine(); + + sb.AppendLine("## Summary"); + sb.AppendLine(); + sb.AppendLine($"| | {string.Join(" | ", builds.Select(b => b.Name))} |"); + sb.AppendLine($"|---|{string.Concat(builds.Select(_ => "---|"))}"); + foreach (var level in Enum.GetValues()) + sb.AppendLine($"| {Icon(level)} {level} | {string.Join(" | ", builds.Select(b => results.Count(r => r.CaseLevel(b.Name) == level)))} |"); + sb.AppendLine($"| **cases** | {string.Join(" | ", builds.Select(_ => results.Count))} |"); + var pluginResults = results.Where(r => r.Sqlc[options.PluginBuild.Name].PluginStatus != RunStatus.Skipped).ToList(); + sb.AppendLine(); + sb.AppendLine($"C# plugin (on **{options.PluginBuild.Name}** output): {pluginResults.Count(r => r.Sqlc[options.PluginBuild.Name].PluginStatus == RunStatus.Ok)}/{pluginResults.Count} generate" + + (options.Compile ? $", {results.Count(r => r.CompileStatus == RunStatus.Ok)}/{pluginResults.Count} compile" : "") + "."); + var invalid = results.Where(r => r.Oracle.SchemaError is not null).ToList(); + if (invalid.Count > 0) + sb.AppendLine($"\n⚠️ {invalid.Count} case(s) have a schema SQL Server rejects: {string.Join(", ", invalid.Select(r => r.Case.Id))}"); + sb.AppendLine(); + + foreach (var category in results.GroupBy(r => r.Case.Category)) + { + sb.AppendLine($"## {category.Key}"); + sb.AppendLine(); + sb.AppendLine($"| Case | {string.Join(" | ", builds.Select(b => b.Name))} | plugin | compile | Notes |"); + sb.AppendLine($"|---|{string.Concat(builds.Select(_ => "---|"))}---|---|---|"); + foreach (var r in category) + { + var plugin = r.Sqlc[options.PluginBuild.Name]; + var note = Note(r, builds[^1].Name); + sb.AppendLine($"| [{r.Case.Id[(r.Case.Id.IndexOf('/') + 1)..]}](cases/{r.Case.Id}.sql) {Escape(r.Case.Description)} | " + + $"{string.Join(" | ", builds.Select(b => Icon(r.CaseLevel(b.Name))))} | {Icon(plugin.PluginStatus)} | {Icon(r.CompileStatus)} | {Escape(note)} |"); + } + sb.AppendLine(); + } + + sb.AppendLine("## Details"); + sb.AppendLine(); + foreach (var r in results) + { + var anyFinding = builds.Any(b => r.CaseLevel(b.Name) != Level.Ok) + || r.Sqlc[options.PluginBuild.Name].PluginStatus == RunStatus.Error + || r.CompileStatus == RunStatus.Error + || r.Oracle.SchemaError is not null; + if (!anyFinding) + continue; + sb.AppendLine($"### {r.Case.Id}"); + sb.AppendLine(); + sb.AppendLine(r.Case.Description); + sb.AppendLine(); + if (r.Oracle.SchemaError is not null) + sb.AppendLine($"- ⚠️ SQL Server rejects the schema: `{r.Oracle.SchemaError}`"); + foreach (var build in builds) + { + var sqlc = r.Sqlc[build.Name]; + if (sqlc.Status is RunStatus.Error or RunStatus.Panic) + { + sb.AppendLine($"- **{build.Name}** {Icon(r.CaseLevel(build.Name))}"); + sb.AppendLine(" ```"); + foreach (var line in sqlc.Error.Split('\n')) + sb.AppendLine($" {line}"); + sb.AppendLine(" ```"); + continue; + } + foreach (var (query, verdict) in r.Verdicts(build.Name).Where(v => v.Verdict.Findings.Count > 0)) + foreach (var f in verdict.Findings) + sb.AppendLine($"- **{build.Name}** `{query.Name}` {Icon(f.Level)} {Escape(f.Message)}"); + } + var plugin = r.Sqlc[options.PluginBuild.Name]; + if (plugin.PluginStatus == RunStatus.Error) + sb.AppendLine($"- **plugin** ❌ `{Escape(CaseResult.FirstLine(plugin.PluginError))}`"); + if (r.CompileStatus == RunStatus.Error) + foreach (var line in r.CompileError.Split('\n')) + sb.AppendLine($"- **compile** ❌ `{Escape(line)}`"); + foreach (var (name, oracle) in r.Oracle.Queries) + { + if (oracle.ParamsError is not null) + sb.AppendLine($"- SQL Server, `{name}` parameters: {Escape(oracle.ParamsError)}"); + if (oracle.ColumnsError is not null) + sb.AppendLine($"- SQL Server, `{name}` result set: {Escape(oracle.ColumnsError)}"); + } + sb.AppendLine(); + } + return sb.ToString(); + } + + private static string Note(CaseResult r, string build) + { + var sqlc = r.Sqlc[build]; + if (sqlc.Status is RunStatus.Error or RunStatus.Panic) + return r.Verdicts(build).First().Verdict.Findings.First().Message.Split('\n')[0]; + var finding = r.Verdicts(build).SelectMany(v => v.Verdict.Findings).OrderByDescending(f => f.Level).FirstOrDefault(); + if (finding is not null && finding.Level > Level.Minor) + return finding.Message; + if (sqlc.PluginStatus == RunStatus.Error) + return "plugin: " + CaseResult.FirstLine(sqlc.PluginError); + if (r.CompileStatus == RunStatus.Error) + return "compile: " + CaseResult.FirstLine(r.CompileError); + return finding?.Message ?? ""; + } + + private static string Escape(string text) => text.Replace("|", "\\|").Replace("\n", " ").Replace("\r", ""); +} \ No newline at end of file diff --git a/end2end/MssqlSyntaxMatrix/MatrixOptions.cs b/end2end/MssqlSyntaxMatrix/MatrixOptions.cs new file mode 100644 index 000000000..aa65f2e02 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/MatrixOptions.cs @@ -0,0 +1,63 @@ +namespace MssqlSyntaxMatrix; + +public record SqlcBuild(string Name, string Path, string Label); + +public class MatrixOptions +{ + public required string RepoRoot { get; init; } + public required string MatrixDir { get; init; } + public required List SqlcBuilds { get; init; } + public required string PluginPath { get; init; } + public required string ConnectionString { get; init; } + public bool Compile { get; init; } + public string? Filter { get; init; } + + // the build whose output is fed to the plugin and the compiler: the last one given + public SqlcBuild PluginBuild => SqlcBuilds[^1]; + + // --sqlc name=path[@label] (repeatable, in report column order), --plugin path, --compile, --filter text + public static MatrixOptions Parse(string[] args, string repoRoot) + { + var builds = new List(); + var pluginPath = Path.Combine(repoRoot, "dist", "LocalRunner"); + var compile = false; + string? filter = null; + for (var i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "--sqlc": + var spec = args[++i]; + var (nameAndPath, label) = spec.Contains('@') ? (spec[..spec.LastIndexOf('@')], spec[(spec.LastIndexOf('@') + 1)..]) : (spec, ""); + var parts = nameAndPath.Split('=', 2); + builds.Add(new SqlcBuild(parts[0], parts[1], label)); + break; + case "--plugin": + pluginPath = Path.GetFullPath(args[++i]); + break; + case "--compile": + compile = true; + break; + case "--filter": + filter = args[++i]; + break; + default: + throw new ArgumentException($"unknown argument {args[i]}"); + } + } + if (builds.Count == 0) + builds.Add(new SqlcBuild("sqlc", "sqlc", "")); + + return new MatrixOptions + { + RepoRoot = repoRoot, + MatrixDir = Path.Combine(repoRoot, "end2end", "MssqlSyntaxMatrix"), + SqlcBuilds = builds, + PluginPath = pluginPath, + ConnectionString = Environment.GetEnvironmentVariable("MSSQL_CONNECTION_STRING") + ?? throw new InvalidOperationException("MSSQL_CONNECTION_STRING is not set"), + Compile = compile, + Filter = filter + }; + } +} \ No newline at end of file diff --git a/end2end/MssqlSyntaxMatrix/MssqlSyntaxMatrix.csproj b/end2end/MssqlSyntaxMatrix/MssqlSyntaxMatrix.csproj new file mode 100644 index 000000000..e9e55f067 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/MssqlSyntaxMatrix.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0 + enable + enable + MssqlSyntaxMatrix + + + + + + + + diff --git a/end2end/MssqlSyntaxMatrix/PluginCompiler.cs b/end2end/MssqlSyntaxMatrix/PluginCompiler.cs new file mode 100644 index 000000000..148a38c4d --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/PluginCompiler.cs @@ -0,0 +1,62 @@ +using System.Text.RegularExpressions; + +namespace MssqlSyntaxMatrix; + +// Builds the plugin output of every case in one project, so a case whose generated C# does not compile is spotted +// without paying for one build per case. +public static partial class PluginCompiler +{ + public static async Task CompileAll(List results, MatrixOptions options, string dir) + { + Directory.CreateDirectory(dir); + var byNamespace = new Dictionary(); + foreach (var result in results) + { + var sqlc = result.Sqlc[options.PluginBuild.Name]; + if (sqlc.PluginOutputDir is null) + continue; + var ns = SqlcRunner.CaseNamespace(result.Case); + byNamespace[ns] = result; + result.CompileStatus = RunStatus.Ok; + foreach (var file in Directory.GetFiles(sqlc.PluginOutputDir, "*.cs", SearchOption.AllDirectories)) + { + var target = Path.Combine(dir, ns, Path.GetRelativePath(sqlc.PluginOutputDir, file)); + Directory.CreateDirectory(Path.GetDirectoryName(target)!); + File.Copy(file, target, true); + } + } + + await File.WriteAllTextAsync(Path.Combine(dir, "Compile.csproj"), """ + + + net10.0 + enable + Library + $(NoWarn);CS8618;CS8604;CS8600;CS8601;CS8602;CS8603;NU1902;NU1903 + + + + + + + + + """); + + var (_, output) = await SqlcRunner.Exec("dotnet", "build -nologo -clp:NoSummary", dir); + foreach (Match m in ErrorRegex().Matches(output)) + { + var relative = Path.GetRelativePath(dir, m.Groups["file"].Value); + var ns = relative.Split(Path.DirectorySeparatorChar)[0]; + if (!byNamespace.TryGetValue(ns, out var result)) + continue; + result.CompileStatus = RunStatus.Error; + var error = $"{string.Join('/', relative.Split(Path.DirectorySeparatorChar).Skip(1))}: {m.Groups["code"].Value} {m.Groups["message"].Value}"; + if (!result.CompileError.Contains(error) && result.CompileError.Split('\n').Length < 5) + result.CompileError = (result.CompileError + "\n" + error).Trim(); + } + } + + [GeneratedRegex(@"^(?[^\r\n]*?\.cs)\(\d+,\d+\): error (?CS\d+): (?[^\r\n]*?) \[", RegexOptions.Multiline)] + private static partial Regex ErrorRegex(); +} \ No newline at end of file diff --git a/end2end/MssqlSyntaxMatrix/Program.cs b/end2end/MssqlSyntaxMatrix/Program.cs new file mode 100644 index 000000000..a64d94c43 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/Program.cs @@ -0,0 +1,54 @@ +using dotenv.net; +using System.Text.Json; + +namespace MssqlSyntaxMatrix; + +// Runs every T-SQL case under cases/ through sqlc (one or more builds), the C# plugin and SQL Server's own +// sp_describe_* procedures, and writes a support matrix. See README.md. +public static class Program +{ + public static async Task Main(string[] args) + { + var root = FindRepoRoot(); + DotEnv.Load(options: new DotEnvOptions(envFilePaths: [Path.Combine(root, ".env")])); + + var options = MatrixOptions.Parse(args, root); + var cases = TestCase.LoadAll(Path.Combine(options.MatrixDir, "cases")); + if (options.Filter is not null) + cases = [.. cases.Where(c => c.Id.Contains(options.Filter, StringComparison.OrdinalIgnoreCase))]; + Console.Error.WriteLine($"{cases.Count} cases, sqlc builds: {string.Join(", ", options.SqlcBuilds.Select(b => b.Name))}"); + + var oracle = new SqlServerOracle(options.ConnectionString); + var workDir = Directory.CreateTempSubdirectory("mssql-matrix-"); + var results = new List(); + foreach (var testCase in cases) + { + Console.Error.WriteLine($"- {testCase.Id}"); + var caseDir = Path.Combine(workDir.FullName, testCase.Id.Replace('/', '_')); + Directory.CreateDirectory(caseDir); + var result = new CaseResult(testCase); + foreach (var build in options.SqlcBuilds) + result.Sqlc[build.Name] = await SqlcRunner.Run(build, testCase, Path.Combine(caseDir, build.Name), options); + result.Oracle = await oracle.Describe(testCase); + results.Add(result); + } + + if (options.Compile) + await PluginCompiler.CompileAll(results, options, Path.Combine(workDir.FullName, "compile")); + + var report = MarkdownReport.Render(results, options); + await File.WriteAllTextAsync(Path.Combine(options.MatrixDir, "REPORT.md"), report); + await File.WriteAllTextAsync(Path.Combine(options.MatrixDir, "results.json"), + JsonSerializer.Serialize(results.Select(r => r.ToJson(options)), new JsonSerializerOptions { WriteIndented = true }) + "\n"); + Console.Error.WriteLine($"wrote {Path.Combine(options.MatrixDir, "REPORT.md")}"); + return 0; + } + + private static string FindRepoRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "sqlc-gen-csharp.sln"))) + dir = dir.Parent; + return dir?.FullName ?? throw new InvalidOperationException("repository root not found"); + } +} \ No newline at end of file diff --git a/end2end/MssqlSyntaxMatrix/README.md b/end2end/MssqlSyntaxMatrix/README.md new file mode 100644 index 000000000..3a83ded65 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/README.md @@ -0,0 +1,65 @@ +# SQL Server syntax matrix + +What does sqlc need to support for a C# developer to use it with SQL Server? This project answers it with +evidence. Every file in [`cases/`](cases) is a small schema plus one or more sqlc queries, covering one T-SQL +feature, and every case is run through: + +1. **each sqlc build** given on the command line (typically sqlc `main`, and sqlc `main` with the patches in + [`scripts/sqlc-patches`](../../scripts/sqlc-patches)), with the `json` codegen, to get the parameters and + result columns sqlc infers; +2. **SQL Server itself**: the schema is created in a fresh database and the query is described with + `sp_describe_undeclared_parameters` and `sp_describe_first_result_set`. This is the oracle: what SQL Server + says the parameter and column types are; +3. **this plugin** (driver and Dapper flavours) on the output of the last sqlc build; +4. optionally, **the C# compiler** on all the generated code (`--compile`). + +The result is [REPORT.md](REPORT.md) (and [results.json](results.json) for tooling). + +| Level | Meaning | +|-------|---------| +| ✅ | sqlc agrees with SQL Server | +| 🟡 | minor: sqlc is more conservative, or picks a compatible type of the same family (`int` vs `bigint`) | +| 🟠 | wrong or missing type, nullability, parameter, name or column count | +| 🕳️ | the query is silently dropped from sqlc's output | +| ❌ | sqlc fails | +| 💥 | sqlc panics | + +## Running it + +Needs SQL Server (`docker compose up -d mssqldb`), the published process plugin (`make dotnet-publish-process`) +and one or more sqlc binaries: + +```bash +make mssql-syntax-matrix SQLC_UPSTREAM=/path/to/sqlc-main SQLC_PATCHED=/path/to/sqlc-patched +# or directly +dotnet run --project end2end/MssqlSyntaxMatrix -- \ + --sqlc "upstream=/path/to/sqlc@sqlc main (3ad8b20)" \ + --sqlc "patched=/path/to/sqlc@sqlc main + patches" \ + --compile [--filter select/] +``` + +`--sqlc name=path[@label]` is repeatable and sets the report columns; the plugin and compile columns use the last one. + +## Adding a case + +```sql +-- @description What the case covers (shown in the report) +-- @schema +CREATE TABLE things (id INT NOT NULL); -- optional; cases/base.sql (authors, books) is always created first +-- @queries +-- name: ListThings :many +SELECT id FROM things WHERE id > @min_id; +``` + +Keep a case to one feature, write it the way a SQL Server developer would, and make sure SQL Server accepts it +(the report flags a schema SQL Server rejects, and the details show when SQL Server cannot describe a query). + +## Known limits of the oracle + +- SQL Server reports most expressions (`COUNT(*)`, `CASE`, ...) as nullable, so a NOT NULL expression from sqlc is + only 🟡; for table columns (joins, `ROLLUP`) the difference is 🟠. +- `sp_describe_undeclared_parameters` refuses a parameter used twice; every use is then described separately and + the types are merged (`@p IS NULL` makes SQL Server guess `int`, so a concrete type wins over it). +- Some parameters cannot be typed by SQL Server either (`COALESCE(@p, '%')`, `STRING_SPLIT(@p, ',')`, + `OPENJSON(@p)`) and temp tables cannot be described; the details say so. +- SQL Server 2025 types (`json`, `vector`) are not covered because the CI image is SQL Server 2022. diff --git a/end2end/MssqlSyntaxMatrix/REPORT.md b/end2end/MssqlSyntaxMatrix/REPORT.md new file mode 100644 index 000000000..516d614d9 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/REPORT.md @@ -0,0 +1,1370 @@ +# SQL Server (mssql) syntax matrix + + +Every case in [`cases/`](cases) is run through each sqlc build, the C# plugin, and SQL Server's own +`sp_describe_undeclared_parameters` / `sp_describe_first_result_set`, which act as the oracle for what +the parameter and result column types should be. See [README.md](README.md). + +- **upstream**: sqlc main (3ad8b20) +- **patched**: sqlc main (3ad8b20) + scripts/sqlc-patches + +| Legend | | +|---|---| +| ✅ | matches SQL Server | +| 🟡 | minor difference (sqlc is more conservative, or a compatible type of the same family) | +| 🟠 | wrong or missing type, nullability, name or column count | +| 🕳️ | the query is silently dropped from sqlc's output | +| ❌ | sqlc fails | +| 💥 | sqlc panics | + +## Summary + +| | upstream | patched | +|---|---|---| +| ✅ Ok | 98 | 106 | +| 🟡 Minor | 17 | 18 | +| 🟠 Mismatch | 38 | 34 | +| 🕳️ Dropped | 9 | 8 | +| ❌ Error | 24 | 21 | +| 💥 Panic | 1 | 0 | +| **cases** | 187 | 187 | + +C# plugin (on **patched** output): 157/158 generate, 157/158 compile. + +## aggregate + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [basic_aggregates](cases/aggregate/basic_aggregates.sql) COUNT, COUNT_BIG, SUM, AVG, MIN, MAX and their result types | 🟡 | 🟡 | ✅ | ✅ | column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) | +| [group_by_having](cases/aggregate/group_by_having.sql) GROUP BY with HAVING on a parameter | 🟡 | 🟡 | ✅ | ✅ | column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) | +| [rollup_cube_grouping_sets](cases/aggregate/rollup_cube_grouping_sets.sql) ROLLUP, CUBE, GROUPING SETS and GROUPING() | 🟠 | 🟠 | ✅ | ✅ | column is_total has no type | +| [statistical](cases/aggregate/statistical.sql) STDEV, VAR and APPROX_COUNT_DISTINCT | 🟠 | 🟠 | ✅ | ✅ | column stdev has no type | +| [string_agg](cases/aggregate/string_agg.sql) STRING_AGG with WITHIN GROUP | 🟠 | 🟠 | ✅ | ✅ | column titles has no type | + +## annotations + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [copyfrom_identity_table](cases/annotations/copyfrom_identity_table.sql) `:copyfrom` into a table with an IDENTITY column | 💥 | ✅ | ✅ | ✅ | | +| [copyfrom_schema_scoped](cases/annotations/copyfrom_schema_scoped.sql) `:copyfrom` into a table in a user-defined schema | ❌ | ❌ | – | – | query.sql:1:1: expected ), got message | +| [execlastid_output_identity](cases/annotations/execlastid_output_identity.sql) `:execlastid` via OUTPUT INSERTED.id on an IDENTITY column | ✅ | ✅ | ✅ | ✅ | | +| [execlastid_scope_identity](cases/annotations/execlastid_scope_identity.sql) `:execlastid` via SELECT SCOPE_IDENTITY() after the INSERT | 🟠 | 🟠 | ✅ | ✅ | 0 result columns, SQL Server returns 1 ((unnamed)) | +| [execrows](cases/annotations/execrows.sql) `:execrows` on UPDATE and DELETE | ✅ | ✅ | ✅ | ✅ | | +| [one_many_exec](cases/annotations/one_many_exec.sql) `:one`, `:many`, `:exec` basics | ✅ | ✅ | ✅ | ✅ | | + +## batch + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [declare_variables](cases/batch/declare_variables.sql) DECLARE / SET local variables before the final SELECT | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) | +| [select_into_temp_table](cases/batch/select_into_temp_table.sql) SELECT ... INTO #temp then reading it, in one query | ✅ | ✅ | ✅ | ✅ | | +| [set_nocount_on](cases/batch/set_nocount_on.sql) SET NOCOUNT ON in front of a query (a very common T-SQL habit) | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) | +| [transaction_in_query](cases/batch/transaction_in_query.sql) Explicit BEGIN TRAN / COMMIT inside a query | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) | +| [try_catch_throw](cases/batch/try_catch_throw.sql) TRY ... CATCH with THROW | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) | + +## cte + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [cte_with_dml](cases/cte/cte_with_dml.sql) CTEs in front of UPDATE and DELETE | 🟡 | 🟡 | ✅ | ✅ | parameter @n: sqlc int, SQL Server bigint | +| [multiple_ctes](cases/cte/multiple_ctes.sql) Several CTEs referencing each other | 🟠 | 🟠 | ✅ | ✅ | column rnk has no type | +| [recursive_cte](cases/cte/recursive_cte.sql) Recursive CTE with UNION ALL | ❌ | ❌ | – | – | query.sql:1:1: with numbers: relation "public"."numbers": class "numbers": sql: no rows in result set | +| [simple_cte](cases/cte/simple_cte.sql) A single common table expression | ✅ | ✅ | ✅ | ✅ | | + +## ddl + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [alias_type](cases/ddl/alias_type.sql) User-defined alias type (CREATE TYPE ... FROM) | 🟠 | 🟠 | ❌ | – | column email: sqlc email_address, SQL Server nvarchar(320) | +| [alter_column_type_and_nullability](cases/ddl/alter_column_type_and_nullability.sql) ALTER TABLE ... ALTER COLUMN changes the type and nullability | 🟡 | 🟡 | ✅ | ✅ | column bio: sqlc nullable, SQL Server NOT NULL | +| [alter_table_add_column](cases/ddl/alter_table_add_column.sql) ALTER TABLE ... ADD column | ✅ | ✅ | ✅ | ✅ | | +| [alter_table_drop_column](cases/ddl/alter_table_drop_column.sql) ALTER TABLE ... DROP COLUMN | ✅ | ✅ | ✅ | ✅ | | +| [bracketed_identifiers](cases/ddl/bracketed_identifiers.sql) Bracket-quoted table and column names with spaces and reserved words | 🟡 | 🟡 | ✅ | ✅ | column 3 is named unit price, SQL Server keeps the case: Unit Price | +| [case_insensitive_names](cases/ddl/case_insensitive_names.sql) Object names are case-insensitive under the default collation | 🟡 | 🟡 | ✅ | ✅ | column 1 is named id, SQL Server keeps the case: ID | +| [check_and_unique_constraints](cases/ddl/check_and_unique_constraints.sql) CHECK and UNIQUE constraints, column and table level | ✅ | ✅ | ✅ | ✅ | | +| [collate_and_sparse](cases/ddl/collate_and_sparse.sql) COLLATE and SPARSE column options | ✅ | ✅ | ✅ | ✅ | | +| [computed_columns](cases/ddl/computed_columns.sql) Computed columns, plain and PERSISTED | ❌ | ❌ | – | – | schema.sql:1:1: column people.full_name: missing type name | +| [drop_table_if_exists](cases/ddl/drop_table_if_exists.sql) DROP TABLE IF EXISTS followed by a re-create with a different shape | ✅ | ✅ | ✅ | ✅ | | +| [foreign_key_on_delete](cases/ddl/foreign_key_on_delete.sql) Table-level FOREIGN KEY with ON DELETE CASCADE / SET NULL | ✅ | ✅ | ✅ | ✅ | | +| [go_batch_separators](cases/ddl/go_batch_separators.sql) Schema split into several batches with GO | ✅ | ✅ | ✅ | ✅ | | +| [identity_seed_increment](cases/ddl/identity_seed_increment.sql) IDENTITY(seed, increment) column on a non-default seed | ✅ | ✅ | ✅ | ✅ | | +| [indexes](cases/ddl/indexes.sql) CREATE INDEX statements (filtered, INCLUDE, unique nonclustered) are accepted and ignored | ✅ | ✅ | ✅ | ✅ | | +| [named_default_constraints](cases/ddl/named_default_constraints.sql) Named DEFAULT constraints with GETDATE() and NEWID() | ✅ | ✅ | ✅ | ✅ | | +| [rowversion](cases/ddl/rowversion.sql) ROWVERSION column used for optimistic concurrency | ✅ | ✅ | ✅ | ✅ | | +| [same_table_name_two_schemas](cases/ddl/same_table_name_two_schemas.sql) Two tables with the same name in different schemas | ✅ | ✅ | ✅ | ✅ | | +| [schema_scoped_table](cases/ddl/schema_scoped_table.sql) Table in a user-defined schema (CREATE SCHEMA in its own batch) | ✅ | ✅ | ✅ | ✅ | | +| [sequence_default](cases/ddl/sequence_default.sql) SEQUENCE used as a column default and with NEXT VALUE FOR in a query | 🟠 | 🟠 | ✅ | ✅ | column next_id has no type | +| [sp_rename_column](cases/ddl/sp_rename_column.sql) Column renamed with sp_rename (how T-SQL renames columns) | ❌ | ❌ | – | – | query.sql:1:1: unknown column "biography" | +| [synonym](cases/ddl/synonym.sql) CREATE SYNONYM for a table | ❌ | ❌ | – | – | query.sql:1:1: relation "public"."writers": class "writers": sql: no rows in result set | +| [table_level_primary_key](cases/ddl/table_level_primary_key.sql) Columns in a table-level (composite) PRIMARY KEY are implicitly NOT NULL | ✅ | ✅ | ✅ | ✅ | | +| [temporal_table](cases/ddl/temporal_table.sql) System-versioned temporal table and FOR SYSTEM_TIME AS OF | 🟠 | 🟠 | ✅ | ✅ | parameter @as_of (datetime2(7)) is missing | +| [view](cases/ddl/view.sql) Querying a VIEW | ❌ | ❌ | – | – | query.sql:1:1: relation "public"."author_books": class "author_books": sql: no rows in result set | + +## delete + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [from_join](cases/delete/from_join.sql) DELETE ... FROM with a JOIN | ✅ | ✅ | ✅ | ✅ | | +| [output_deleted](cases/delete/output_deleted.sql) DELETE ... OUTPUT DELETED.* | ✅ | ✅ | ✅ | ✅ | | +| [simple](cases/delete/simple.sql) DELETE with a parameter | ✅ | ✅ | ✅ | ✅ | | +| [top](cases/delete/top.sql) DELETE TOP (@n) | 🟠 | 🟠 | ✅ | ✅ | parameter @n (bigint) is missing | +| [truncate](cases/delete/truncate.sql) TRUNCATE TABLE | 🕳️ | ✅ | ✅ | ✅ | | + +## expressions + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [arithmetic](cases/expressions/arithmetic.sql) Arithmetic operators, integer division, modulo and type promotion | 🟠 | 🟠 | ✅ | ✅ | column doubled: sqlc bigint, SQL Server decimal(12,2) | +| [at_time_zone](cases/expressions/at_time_zone.sql) AT TIME ZONE and SWITCHOFFSET | 🟠 | 🟠 | ✅ | ✅ | parameter @time_zone (nvarchar(4000)) is missing | +| [bitwise](cases/expressions/bitwise.sql) Bitwise operators and bit columns | 🟡 | 🟡 | ✅ | ✅ | column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) | +| [cast_convert](cases/expressions/cast_convert.sql) CAST, CONVERT (with a style), TRY_CAST, TRY_CONVERT, PARSE and TRY_PARSE | 🟠 | 🟠 | ✅ | ✅ | parameter @text (nvarchar(4000)) is missing | +| [coalesce_isnull_nullif](cases/expressions/coalesce_isnull_nullif.sql) COALESCE, ISNULL and NULLIF: result types, nullability, and parameter inference | 🟠 | 🟠 | ✅ | ✅ | column bio_or_empty has no type | +| [concat_format](cases/expressions/concat_format.sql) CONCAT, CONCAT_WS, FORMAT, QUOTENAME, TRANSLATE | 🟠 | 🟠 | ✅ | ✅ | column label has no type | +| [datetime_functions](cases/expressions/datetime_functions.sql) GETDATE, SYSDATETIME, SYSDATETIMEOFFSET, DATEADD, DATEDIFF, DATEDIFF_BIG, DATEPART, DATENAME, EOMONTH, DATEFROMPARTS, DATETRUNC | ❌ | ❌ | – | – | query.sql:1:1: unknown column "day" | +| [greatest_least](cases/expressions/greatest_least.sql) GREATEST and LEAST (SQL Server 2022) | 🟠 | 🟠 | ✅ | ✅ | parameter @floor has no type (SQL Server: numeric(38,19)) | +| [is_distinct_from](cases/expressions/is_distinct_from.sql) IS [NOT] DISTINCT FROM (SQL Server 2022) | 🟠 | 🟠 | ✅ | ✅ | parameter @bio (nvarchar(max)) is missing | +| [math_functions](cases/expressions/math_functions.sql) ABS, ROUND, CEILING, FLOOR, POWER, SQRT, SIGN, PI, RAND | 🟠 | 🟠 | ✅ | ✅ | column abs_price has no type | +| [sql2022_functions](cases/expressions/sql2022_functions.sql) GENERATE_SERIES, DATE_BUCKET, TRIM ... FROM and STRING_ESCAPE (SQL Server 2022) | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO | +| [string_functions](cases/expressions/string_functions.sql) LEN, DATALENGTH, SUBSTRING, LEFT, RIGHT, UPPER, LOWER, TRIM, REPLACE, CHARINDEX, PATINDEX, REVERSE, REPLICATE, STUFF | 🟠 | 🟠 | ✅ | ✅ | column prefix has no type | +| [system_functions](cases/expressions/system_functions.sql) NEWID, @@ROWCOUNT, SCOPE_IDENTITY, IDENT_CURRENT, DB_NAME, SUSER_SNAME, HASHBYTES, CHECKSUM | 🟠 | 🟠 | ✅ | ✅ | parameter @payload has no type (SQL Server: nvarchar(4000)) | + +## hints + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [query_hints](cases/hints/query_hints.sql) OPTION (RECOMPILE), OPTION (MAXDOP 1), OPTION (OPTIMIZE FOR UNKNOWN) | ✅ | ✅ | ✅ | ✅ | | +| [table_hints](cases/hints/table_hints.sql) Table hints: NOLOCK, UPDLOCK, ROWLOCK, READPAST, FORCESEEK | ✅ | ✅ | ✅ | ✅ | | + +## identifiers + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [at_sign_parameter_names](cases/identifiers/at_sign_parameter_names.sql) Parameter names with digits, underscores and mixed case | ✅ | ✅ | ✅ | ✅ | | +| [duplicate_result_names](cases/identifiers/duplicate_result_names.sql) Two result columns with the same name | ✅ | ✅ | ✅ | ✅ | | +| [mixed_case_names](cases/identifiers/mixed_case_names.sql) Mixed-case table and column names map to PascalCase | 🟡 | 🟡 | ✅ | ✅ | column 1 is named customerid, SQL Server keeps the case: CustomerID | +| [non_identifier_column_names](cases/identifiers/non_identifier_column_names.sql) Column names that are not valid C# identifiers: leading digits, spaces, dashes, Unicode | 🟡 | 🟡 | ✅ | ✅ | column 4 is named total $, SQL Server keeps the case: Total $ | +| [reserved_word_columns](cases/identifiers/reserved_word_columns.sql) Columns named after T-SQL and C# keywords | ✅ | ✅ | ✅ | ✅ | | +| [unnamed_expression_column](cases/identifiers/unnamed_expression_column.sql) A result column without a name | 🟡 | 🟡 | ✅ | ✅ | column 1 has no name in SQL Server (sqlc calls it count) | + +## insert + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [default_values](cases/insert/default_values.sql) INSERT ... DEFAULT VALUES and explicit DEFAULT | ✅ | ✅ | ✅ | ✅ | | +| [identity_insert](cases/insert/identity_insert.sql) Explicit values for an IDENTITY column with SET IDENTITY_INSERT | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) | +| [insert_exec](cases/insert/insert_exec.sql) INSERT ... EXEC from a stored procedure | ✅ | ✅ | ✅ | ✅ | | +| [insert_select](cases/insert/insert_select.sql) INSERT ... SELECT with a parameter | 🟠 | 🟠 | ✅ | ✅ | parameter @target_author_id has no type (SQL Server: bigint) | +| [multi_row_values](cases/insert/multi_row_values.sql) INSERT with several VALUES rows | ✅ | ✅ | ✅ | ✅ | | +| [output_inserted](cases/insert/output_inserted.sql) INSERT ... OUTPUT INSERTED.* / INSERTED.col returning rows | ✅ | ✅ | ✅ | ✅ | | +| [output_into_table_variable](cases/insert/output_into_table_variable.sql) OUTPUT ... INTO a table variable inside a multi-statement query | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) | +| [schema_scoped](cases/insert/schema_scoped.sql) INSERT into a table in a user-defined schema | ❌ | ❌ | – | – | query.sql:1:1: expected ), got message | +| [values](cases/insert/values.sql) INSERT ... VALUES with parameters for every column | ✅ | ✅ | ✅ | ✅ | | + +## joins + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [cross_apply](cases/joins/cross_apply.sql) CROSS APPLY and OUTER APPLY with a correlated TOP subquery | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO | +| [cross_join](cases/joins/cross_join.sql) CROSS JOIN and the comma join | ✅ | ✅ | ✅ | ✅ | | +| [inner_left_right_full](cases/joins/inner_left_right_full.sql) INNER, LEFT, RIGHT and FULL joins and the nullability they introduce | 🟠 | 🟠 | ✅ | ✅ | column title: sqlc NOT NULL, SQL Server nullable | +| [join_derived_table](cases/joins/join_derived_table.sql) Join to an aggregated derived table | ✅ | ✅ | ✅ | ✅ | | +| [schema_qualified_join](cases/joins/schema_qualified_join.sql) Join across dbo-qualified and bracket-quoted names | ✅ | ✅ | ✅ | ✅ | | +| [self_join](cases/joins/self_join.sql) Self join with aliases | ✅ | ✅ | ✅ | ✅ | | + +## json + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [for_json](cases/json/for_json.sql) FOR JSON PATH / AUTO producing a single JSON column | 🟠 | 🟠 | ✅ | ✅ | 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B) | +| [json_object_array](cases/json/json_object_array.sql) JSON_OBJECT and JSON_ARRAY constructors (SQL Server 2022) | 🟠 | 🟠 | ✅ | ✅ | column obj has no type | +| [json_value_query](cases/json/json_value_query.sql) JSON_VALUE, JSON_QUERY, ISJSON and JSON_MODIFY on an NVARCHAR(MAX) column | 🟠 | 🟠 | ✅ | ✅ | parameter @new_name has no type (SQL Server: float) | +| [openjson](cases/json/openjson.sql) OPENJSON with an explicit WITH schema, fed by a parameter | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO | + +## macros + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [arg_forms](cases/macros/arg_forms.sql) sqlc.arg with a quoted name, a bare identifier and a double-quoted name | ❌ | ✅ | ✅ | ✅ | | +| [arg_in_cast](cases/macros/arg_in_cast.sql) sqlc.arg inside CAST, which fixes its type | 🟠 | ✅ | ✅ | ✅ | | +| [args_in_top_offset](cases/macros/args_in_top_offset.sql) sqlc.arg in TOP and in OFFSET / FETCH | 🟠 | 🟡 | ✅ | ✅ | parameter @take: sqlc int, SQL Server bigint | +| [embed](cases/macros/embed.sql) sqlc.embed of a table, of aliases (self join) and next to a t.* star | ❌ | ✅ | ✅ | ✅ | | +| [embed_schema_scoped](cases/macros/embed_schema_scoped.sql) sqlc.embed of a table in a user-defined schema | ❌ | ✅ | ✅ | ✅ | | +| [mixed_with_at_params](cases/macros/mixed_with_at_params.sql) sqlc.arg mixed with plain @params, sharing a name | ✅ | ✅ | ✅ | ✅ | | +| [narg](cases/macros/narg.sql) sqlc.narg makes a parameter nullable | 🟠 | ✅ | ✅ | ✅ | | +| [slice](cases/macros/slice.sql) sqlc.slice for integers, strings, NOT IN and several slices in one query | 🟠 | ✅ | ✅ | ✅ | | + +## merge + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [merge_delete](cases/merge/merge_delete.sql) MERGE with WHEN NOT MATCHED BY SOURCE THEN DELETE | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) | +| [merge_upsert](cases/merge/merge_upsert.sql) MERGE as an upsert, with OUTPUT $action | ❌ | ❌ | – | – | query.sql:1:1: expected identifier for schema object name | +| [upsert_without_merge](cases/merge/upsert_without_merge.sql) The UPDATE-then-INSERT upsert pattern with @@ROWCOUNT | ✅ | ✅ | ✅ | ✅ | | + +## programmability + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [exec_procedure](cases/programmability/exec_procedure.sql) Calling a stored procedure with EXEC and named arguments | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) | +| [inline_table_function](cases/programmability/inline_table_function.sql) An inline table-valued function in FROM | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO | +| [multi_statement_table_function](cases/programmability/multi_statement_table_function.sql) A multi-statement table-valued function in FROM | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO | +| [scalar_function](cases/programmability/scalar_function.sql) A user-defined scalar function in the select list and WHERE clause | 🟠 | 🟠 | ✅ | ✅ | parameter @initials has no type (SQL Server: nvarchar(10)) | +| [string_split](cases/programmability/string_split.sql) STRING_SPLIT as a table-valued function fed by a parameter | ❌ | ❌ | – | – | query.sql:1:1: where: scope: unsupported FROM item *ast.TODO | +| [table_valued_parameter](cases/programmability/table_valued_parameter.sql) A table-valued parameter (user-defined table type) used in a join | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO | + +## select + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [alias_equals_syntax](cases/select/alias_equals_syntax.sql) T-SQL column alias written as alias = expression | ✅ | ✅ | ✅ | ✅ | | +| [between_params](cases/select/between_params.sql) BETWEEN with two parameters | ✅ | ✅ | ✅ | ✅ | | +| [case_expression](cases/select/case_expression.sql) Searched and simple CASE expressions | 🟠 | 🟠 | ✅ | ✅ | column band: sqlc varchar, SQL Server nvarchar(9) | +| [comments](cases/select/comments.sql) Block comments, nested block comments and trailing line comments inside a query | ❌ | ❌ | – | – | query.sql:1:1: unexpected token: * | +| [derived_table](cases/select/derived_table.sql) Derived table (subquery in FROM) with column aliases | ✅ | ✅ | ✅ | ✅ | | +| [distinct](cases/select/distinct.sql) SELECT DISTINCT | ✅ | ✅ | ✅ | ✅ | | +| [exists](cases/select/exists.sql) EXISTS / NOT EXISTS with a parameter inside the subquery | ✅ | ✅ | ✅ | ✅ | | +| [function_on_parameter](cases/select/function_on_parameter.sql) Parameters wrapped in functions and compared to columns | 🟡 | 🟡 | ✅ | ✅ | parameter @name: sqlc varchar, SQL Server nvarchar(4000) | +| [iif_choose](cases/select/iif_choose.sql) IIF and CHOOSE | 🟠 | 🟠 | ✅ | ✅ | column second has no type | +| [in_list_params](cases/select/in_list_params.sql) IN with a list of scalar parameters | ✅ | ✅ | ✅ | ✅ | | +| [in_subquery_param](cases/select/in_subquery_param.sql) A parameter inside IN (subquery) | ✅ | ✅ | ✅ | ✅ | | +| [like_escape](cases/select/like_escape.sql) LIKE with a parameter and an ESCAPE clause | ✅ | ✅ | ✅ | ✅ | | +| [no_semicolon_terminator](cases/select/no_semicolon_terminator.sql) Statements without a terminating semicolon (optional in T-SQL) | ❌ | ❌ | – | – | query.sql:6:0: missing semicolon at end of file | +| [offset_fetch](cases/select/offset_fetch.sql) OFFSET ... ROWS FETCH NEXT ... ROWS ONLY with parameters | 🟡 | 🟡 | ✅ | ✅ | parameter @skip: sqlc int, SQL Server bigint | +| [offset_without_fetch](cases/select/offset_without_fetch.sql) OFFSET ... ROWS without FETCH | 🟡 | 🟡 | ✅ | ✅ | parameter @skip: sqlc int, SQL Server bigint | +| [optional_filter](cases/select/optional_filter.sql) The optional filter idiom: (@p IS NULL OR column = @p) | ✅ | ✅ | ✅ | ✅ | | +| [order_by_parameter](cases/select/order_by_parameter.sql) Dynamic ORDER BY driven by a parameter | ✅ | ✅ | ✅ | ✅ | | +| [parameter_in_select_list](cases/select/parameter_in_select_list.sql) A parameter echoed in the select list, with and without CAST | 🟠 | 🟠 | ✅ | ✅ | parameter @label has no type (SQL Server cannot deduce it either) | +| [quoted_aliases](cases/select/quoted_aliases.sql) Aliases quoted with brackets, double quotes and single quotes | 🟡 | 🟡 | ✅ | ✅ | column 1 is named author id, SQL Server keeps the case: Author Id | +| [repeated_parameter](cases/select/repeated_parameter.sql) The same parameter used several times, with different case | ✅ | ✅ | ✅ | ✅ | | +| [scalar_subquery_column](cases/select/scalar_subquery_column.sql) Correlated scalar subquery in the select list | ✅ | ✅ | ✅ | ✅ | | +| [select_star](cases/select/select_star.sql) SELECT * and qualified t.* expansion | ✅ | ✅ | ✅ | ✅ | | +| [table_value_constructor](cases/select/table_value_constructor.sql) VALUES as a derived table | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO | +| [top_literal_with_ties](cases/select/top_literal_with_ties.sql) TOP n WITH TIES and TOP n PERCENT | ✅ | ✅ | ✅ | ✅ | | +| [top_parameter](cases/select/top_parameter.sql) TOP with a parenthesized parameter | 🟡 | 🟡 | ✅ | ✅ | parameter @take: sqlc int, SQL Server bigint | +| [unicode_literals](cases/select/unicode_literals.sql) N'' Unicode literals and string concatenation with + | 🟠 | 🟠 | ✅ | ✅ | column greeting: sqlc varchar, SQL Server nvarchar(105) | +| [without_from](cases/select/without_from.sql) SELECT without FROM: parameters and functions only | 🟡 | 🟡 | ✅ | ✅ | column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) | + +## setops + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [pivot_unpivot](cases/setops/pivot_unpivot.sql) PIVOT and UNPIVOT | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO | +| [union_except_intersect](cases/setops/union_except_intersect.sql) UNION, UNION ALL, EXCEPT and INTERSECT | ✅ | ✅ | ✅ | ✅ | | + +## types + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [bigint](cases/types/bigint.sql) `BIGINT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [binary](cases/types/binary.sql) `BINARY(16)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [bit](cases/types/bit.sql) `BIT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [char](cases/types/char.sql) `CHAR(10)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [date](cases/types/date.sql) `DATE` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [datetime](cases/types/datetime.sql) `DATETIME` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [datetime2](cases/types/datetime2.sql) `DATETIME2(7)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [datetime2_0](cases/types/datetime2_0.sql) `DATETIME2(0)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [datetimeoffset](cases/types/datetimeoffset.sql) `DATETIMEOFFSET(7)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [decimal](cases/types/decimal.sql) `DECIMAL(18, 4)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [float](cases/types/float.sql) `FLOAT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [float_24](cases/types/float_24.sql) `FLOAT(24)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [geography](cases/types/geography.sql) `GEOGRAPHY` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | | +| [geometry](cases/types/geometry.sql) `GEOMETRY` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | | +| [hierarchyid](cases/types/hierarchyid.sql) `HIERARCHYID` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [image](cases/types/image.sql) `IMAGE` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | | +| [int](cases/types/int.sql) `INT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [max_lengths_and_precision](cases/types/max_lengths_and_precision.sql) The type expression keeps lengths, MAX, precision and scale | ✅ | ✅ | ✅ | ✅ | | +| [money](cases/types/money.sql) `MONEY` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [nchar](cases/types/nchar.sql) `NCHAR(10)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [ntext](cases/types/ntext.sql) `NTEXT` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | | +| [numeric](cases/types/numeric.sql) `NUMERIC(10, 0)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [nvarchar](cases/types/nvarchar.sql) `NVARCHAR(50)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [nvarchar_max](cases/types/nvarchar_max.sql) `NVARCHAR(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [real](cases/types/real.sql) `REAL` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [rowversion_timestamp](cases/types/rowversion_timestamp.sql) `ROWVERSION` and its legacy synonym `TIMESTAMP` | ✅ | ✅ | ✅ | ✅ | | +| [smalldatetime](cases/types/smalldatetime.sql) `SMALLDATETIME` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [smallint](cases/types/smallint.sql) `SMALLINT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [smallmoney](cases/types/smallmoney.sql) `SMALLMONEY` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [sql_variant](cases/types/sql_variant.sql) `SQL_VARIANT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [sysname](cases/types/sysname.sql) `SYSNAME` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [text](cases/types/text.sql) `TEXT` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | | +| [time](cases/types/time.sql) `TIME(3)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [tinyint](cases/types/tinyint.sql) `TINYINT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [uniqueidentifier](cases/types/uniqueidentifier.sql) `UNIQUEIDENTIFIER` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [varbinary](cases/types/varbinary.sql) `VARBINARY(50)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [varbinary_max](cases/types/varbinary_max.sql) `VARBINARY(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [varchar](cases/types/varchar.sql) `VARCHAR(50)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [varchar_max](cases/types/varchar_max.sql) `VARCHAR(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | | +| [xml](cases/types/xml.sql) `XML` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | | + +## update + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [compound_assignment](cases/update/compound_assignment.sql) Compound assignment operators += and *= | ✅ | ✅ | ✅ | ✅ | | +| [from_join](cases/update/from_join.sql) UPDATE ... FROM with a JOIN (T-SQL specific) | ✅ | ✅ | ✅ | ✅ | | +| [output_deleted_inserted](cases/update/output_deleted_inserted.sql) UPDATE ... OUTPUT DELETED.x, INSERTED.x | ✅ | ✅ | ✅ | ✅ | | +| [set_default](cases/update/set_default.sql) UPDATE ... SET column = DEFAULT and = NULL | ✅ | ✅ | ✅ | ✅ | | +| [simple](cases/update/simple.sql) UPDATE with SET parameters and WHERE | ✅ | ✅ | ✅ | ✅ | | +| [top](cases/update/top.sql) UPDATE TOP (@n) | 🟠 | 🟠 | ✅ | ✅ | parameter @n (bigint) is missing | + +## window + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [aggregate_over](cases/window/aggregate_over.sql) Aggregates over windows, including a running total | 🟡 | 🟡 | ✅ | ✅ | column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) | +| [lag_lead_first_value](cases/window/lag_lead_first_value.sql) LAG, LEAD, FIRST_VALUE and LAST_VALUE with a frame | 🟠 | 🟠 | ✅ | ✅ | column previous_price has no type | +| [ranking](cases/window/ranking.sql) ROW_NUMBER, RANK, DENSE_RANK and NTILE | 🟠 | 🟠 | ✅ | ✅ | column rn has no type | +| [window_clause_and_percentiles](cases/window/window_clause_and_percentiles.sql) Named WINDOW clause (SQL Server 2022), PERCENTILE_CONT and PERCENTILE_DISC | 🟠 | 🟠 | ✅ | ✅ | column median has no type | + +## xml + +| Case | upstream | patched | plugin | compile | Notes | +|---|---|---|---|---|---| +| [for_xml](cases/xml/for_xml.sql) FOR XML PATH, including the string-concatenation idiom | 🟠 | 🟠 | ✅ | ✅ | 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B) | +| [xml_methods](cases/xml/xml_methods.sql) XML column with the value(), query(), exist() and nodes() methods | ❌ | ❌ | – | – | query.sql:1:1: unexpected token: query | + +## Details + +### aggregate/basic_aggregates + +COUNT, COUNT_BIG, SUM, AVG, MIN, MAX and their result types + +- **upstream** `BookStats` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `BookStats` 🟡 column cnt_big: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `BookStats` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `BookStats` 🟡 column cnt_big: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### aggregate/group_by_having + +GROUP BY with HAVING on a parameter + +- **upstream** `ProlificAuthors` 🟡 column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `ProlificAuthors` 🟡 column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### aggregate/rollup_cube_grouping_sets + +ROLLUP, CUBE, GROUPING SETS and GROUPING() + +- **upstream** `Rollup` 🟠 column is_total has no type +- **upstream** `Rollup` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable +- **upstream** `Rollup` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable +- **upstream** `Rollup` 🟡 column is_total: sqlc nullable, SQL Server NOT NULL +- **upstream** `Cube` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable +- **upstream** `Cube` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable +- **upstream** `Cube` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `GroupingSets` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable +- **upstream** `GroupingSets` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable +- **upstream** `GroupingSets` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Rollup` 🟠 column is_total has no type +- **patched** `Rollup` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable +- **patched** `Rollup` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable +- **patched** `Rollup` 🟡 column is_total: sqlc nullable, SQL Server NOT NULL +- **patched** `Cube` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable +- **patched** `Cube` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable +- **patched** `Cube` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `GroupingSets` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable +- **patched** `GroupingSets` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable +- **patched** `GroupingSets` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### aggregate/statistical + +STDEV, VAR and APPROX_COUNT_DISTINCT + +- **upstream** `PriceStatistics` 🟠 column stdev has no type +- **upstream** `PriceStatistics` 🟠 column variance has no type +- **upstream** `PriceStatistics` 🟠 column approx_authors has no type +- **patched** `PriceStatistics` 🟠 column stdev has no type +- **patched** `PriceStatistics` 🟠 column variance has no type +- **patched** `PriceStatistics` 🟠 column approx_authors has no type + +### aggregate/string_agg + +STRING_AGG with WITHIN GROUP + +- **upstream** `TitlesPerAuthor` 🟠 column titles has no type +- **patched** `TitlesPerAuthor` 🟠 column titles has no type + +### annotations/copyfrom_identity_table + +`:copyfrom` into a table with an IDENTITY column + +- **upstream** 💥 + ``` + panic: runtime error: invalid memory address or nil pointer dereference + github.com/sqlc-dev/sqlc/internal/sql/validate.validateCopyfrom({0x23937e0?, 0x18a3e1537420?}) + github.com/sqlc-dev/sqlc/internal/sql/validate.Cmd({0x23937e0?, 0x18a3e1537420?}, {0x18a3e124c429?, 0x488e7d?}, {0x18a3e124c433?, 0x15000000488e7d?}) + github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQueryCore(0x18a3e152e108, 0x18a3e1130b80, {0x18a3e124c420?, 0x18a3e112a5a0?}, 0x18a3e112a728) + github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQuery(0x18a3e152e108, {0x2392a60, 0x18a3e1130b80}, 0x18a3e1102a80, {{0xe0?}}) + github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).analyzeStatements.func1(...) + ``` + +### annotations/copyfrom_schema_scoped + +`:copyfrom` into a table in a user-defined schema + +- **upstream** ❌ + ``` + query.sql:1:1: expected ), got message + ``` +- **patched** ❌ + ``` + query.sql:1:1: expected ), got message + ``` + +### annotations/execlastid_scope_identity + +`:execlastid` via SELECT SCOPE_IDENTITY() after the INSERT + +- **upstream** `CreateAuthorReturnId` 🟠 0 result columns, SQL Server returns 1 ((unnamed)) +- **patched** `CreateAuthorReturnId` 🟠 0 result columns, SQL Server returns 1 ((unnamed)) + +### batch/declare_variables + +DECLARE / SET local variables before the final SELECT + +- **upstream** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` +- **patched** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` + +### batch/set_nocount_on + +SET NOCOUNT ON in front of a query (a very common T-SQL habit) + +- **upstream** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` +- **patched** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` + +### batch/transaction_in_query + +Explicit BEGIN TRAN / COMMIT inside a query + +- **upstream** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` +- **patched** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` + +### batch/try_catch_throw + +TRY ... CATCH with THROW + +- **upstream** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` +- **patched** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` + +### cte/cte_with_dml + +CTEs in front of UPDATE and DELETE + +- **upstream** `DiscountTopBooks` 🟡 parameter @n: sqlc int, SQL Server bigint +- **patched** `DiscountTopBooks` 🟡 parameter @n: sqlc int, SQL Server bigint + +### cte/multiple_ctes + +Several CTEs referencing each other + +- **upstream** `AuthorTotals` 🟠 column rnk has no type +- **patched** `AuthorTotals` 🟠 column rnk has no type + +### cte/recursive_cte + +Recursive CTE with UNION ALL + +- **upstream** ❌ + ``` + query.sql:1:1: with numbers: relation "public"."numbers": class "numbers": sql: no rows in result set + ``` +- **patched** ❌ + ``` + query.sql:1:1: with numbers: relation "public"."numbers": class "numbers": sql: no rows in result set + ``` + +### ddl/alias_type + +User-defined alias type (CREATE TYPE ... FROM) + +- **upstream** `ListContacts` 🟠 column email: sqlc email_address, SQL Server nvarchar(320) +- **upstream** `GetContactByEmail` 🟠 parameter @email: sqlc email_address, SQL Server nvarchar(320) +- **patched** `ListContacts` 🟠 column email: sqlc email_address, SQL Server nvarchar(320) +- **patched** `GetContactByEmail` 🟠 parameter @email: sqlc email_address, SQL Server nvarchar(320) +- **plugin** ❌ `error generating code: process: error running command Unhandled exception. System.NotSupportedException: Column email has unsupported column type: email_address in MssqlDriver` + +### ddl/alter_column_type_and_nullability + +ALTER TABLE ... ALTER COLUMN changes the type and nullability + +- **upstream** `ListBios` 🟡 column bio: sqlc nullable, SQL Server NOT NULL +- **patched** `ListBios` 🟡 column bio: sqlc nullable, SQL Server NOT NULL + +### ddl/bracketed_identifiers + +Bracket-quoted table and column names with spaces and reserved words + +- **upstream** `ListOrderDetails` 🟡 column 3 is named unit price, SQL Server keeps the case: Unit Price +- **patched** `ListOrderDetails` 🟡 column 3 is named unit price, SQL Server keeps the case: Unit Price + +### ddl/case_insensitive_names + +Object names are case-insensitive under the default collation + +- **upstream** `ListAuthorsUpper` 🟡 column 1 is named id, SQL Server keeps the case: ID +- **upstream** `ListAuthorsUpper` 🟡 column 2 is named name, SQL Server keeps the case: NAME +- **patched** `ListAuthorsUpper` 🟡 column 1 is named id, SQL Server keeps the case: ID +- **patched** `ListAuthorsUpper` 🟡 column 2 is named name, SQL Server keeps the case: NAME + +### ddl/computed_columns + +Computed columns, plain and PERSISTED + +- **upstream** ❌ + ``` + schema.sql:1:1: column people.full_name: missing type name + ``` +- **patched** ❌ + ``` + schema.sql:1:1: column people.full_name: missing type name + ``` + +### ddl/sequence_default + +SEQUENCE used as a column default and with NEXT VALUE FOR in a query + +- **upstream** `NextTicketId` 🟠 column next_id has no type +- **patched** `NextTicketId` 🟠 column next_id has no type + +### ddl/sp_rename_column + +Column renamed with sp_rename (how T-SQL renames columns) + +- **upstream** ❌ + ``` + query.sql:1:1: unknown column "biography" + ``` +- **patched** ❌ + ``` + query.sql:1:1: unknown column "biography" + ``` + +### ddl/synonym + +CREATE SYNONYM for a table + +- **upstream** ❌ + ``` + query.sql:1:1: relation "public"."writers": class "writers": sql: no rows in result set + ``` +- **patched** ❌ + ``` + query.sql:1:1: relation "public"."writers": class "writers": sql: no rows in result set + ``` + +### ddl/temporal_table + +System-versioned temporal table and FOR SYSTEM_TIME AS OF + +- **upstream** `ListPricesAsOf` 🟠 parameter @as_of (datetime2(7)) is missing +- **patched** `ListPricesAsOf` 🟠 parameter @as_of (datetime2(7)) is missing + +### ddl/view + +Querying a VIEW + +- **upstream** ❌ + ``` + query.sql:1:1: relation "public"."author_books": class "author_books": sql: no rows in result set + ``` +- **patched** ❌ + ``` + query.sql:1:1: relation "public"."author_books": class "author_books": sql: no rows in result set + ``` + +### delete/top + +DELETE TOP (@n) + +- **upstream** `DeleteSomeBooks` 🟠 parameter @n (bigint) is missing +- **patched** `DeleteSomeBooks` 🟠 parameter @n (bigint) is missing + +### delete/truncate + +TRUNCATE TABLE + +- **upstream** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` + +### expressions/arithmetic + +Arithmetic operators, integer division, modulo and type promotion + +- **upstream** `Arithmetic` 🟠 column doubled: sqlc bigint, SQL Server decimal(12,2) +- **upstream** `Arithmetic` 🟡 column half_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `Arithmetic` 🟡 column remainder: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `Arithmetic` 🟡 column negated: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Arithmetic` 🟠 column doubled: sqlc bigint, SQL Server decimal(12,2) +- **patched** `Arithmetic` 🟡 column half_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Arithmetic` 🟡 column remainder: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Arithmetic` 🟡 column negated: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### expressions/at_time_zone + +AT TIME ZONE and SWITCHOFFSET + +- **upstream** `InTimeZone` 🟠 parameter @time_zone (nvarchar(4000)) is missing +- **upstream** `InTimeZone` 🟠 column local_time has no type +- **upstream** `InTimeZone` 🟠 column switched has no type +- **upstream** `InTimeZone` 🟡 column local_time: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `InTimeZone` 🟠 parameter @time_zone (nvarchar(4000)) is missing +- **patched** `InTimeZone` 🟠 column local_time has no type +- **patched** `InTimeZone` 🟠 column switched has no type +- **patched** `InTimeZone` 🟡 column local_time: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### expressions/bitwise + +Bitwise operators and bit columns + +- **upstream** `Bitwise` 🟡 column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Bitwise` 🟡 column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### expressions/cast_convert + +CAST, CONVERT (with a style), TRY_CAST, TRY_CONVERT, PARSE and TRY_PARSE + +- **upstream** `Conversions` 🟠 parameter @text (nvarchar(4000)) is missing +- **upstream** `Conversions` 🟠 column parsed has no type +- **upstream** `Conversions` 🟠 column maybe_date has no type +- **upstream** `Conversions` 🟡 column id_text: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `Conversions` 🟡 column title_number: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `Conversions` 🟡 column amount: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `Conversions` 🟡 column parsed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `Conversions` 🟡 column maybe_date: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Conversions` 🟠 parameter @text (nvarchar(4000)) is missing +- **patched** `Conversions` 🟠 column parsed has no type +- **patched** `Conversions` 🟠 column maybe_date has no type +- **patched** `Conversions` 🟡 column id_text: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Conversions` 🟡 column title_number: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Conversions` 🟡 column amount: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Conversions` 🟡 column parsed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Conversions` 🟡 column maybe_date: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### expressions/coalesce_isnull_nullif + +COALESCE, ISNULL and NULLIF: result types, nullability, and parameter inference + +- **upstream** `Fallbacks` 🟠 column bio_or_empty has no type +- **upstream** `Fallbacks` 🟠 column name_or_null has no type +- **upstream** `Fallbacks` 🟡 column bio_or_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `Fallbacks` 🟡 column bio_or_empty: sqlc nullable, SQL Server NOT NULL +- **upstream** `ByPattern` 🟠 parameter @pattern has no type (SQL Server cannot deduce it either) +- **upstream** `ByPatternIsNull` 🟠 parameter @pattern has no type (SQL Server: nvarchar(4000)) +- **patched** `Fallbacks` 🟠 column bio_or_empty has no type +- **patched** `Fallbacks` 🟠 column name_or_null has no type +- **patched** `Fallbacks` 🟡 column bio_or_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Fallbacks` 🟡 column bio_or_empty: sqlc nullable, SQL Server NOT NULL +- **patched** `ByPattern` 🟠 parameter @pattern has no type (SQL Server cannot deduce it either) +- **patched** `ByPatternIsNull` 🟠 parameter @pattern has no type (SQL Server: nvarchar(4000)) +- SQL Server, `ByPattern` parameters: The type for parameter '@pattern' cannot be deduced in this context. + +### expressions/concat_format + +CONCAT, CONCAT_WS, FORMAT, QUOTENAME, TRANSLATE + +- **upstream** `Formatting` 🟠 column label has no type +- **upstream** `Formatting` 🟠 column joined has no type +- **upstream** `Formatting` 🟠 column formatted has no type +- **upstream** `Formatting` 🟠 column quoted has no type +- **upstream** `Formatting` 🟠 column translated has no type +- **upstream** `Formatting` 🟡 column label: sqlc nullable, SQL Server NOT NULL +- **upstream** `Formatting` 🟡 column joined: sqlc nullable, SQL Server NOT NULL +- **patched** `Formatting` 🟠 column label has no type +- **patched** `Formatting` 🟠 column joined has no type +- **patched** `Formatting` 🟠 column formatted has no type +- **patched** `Formatting` 🟠 column quoted has no type +- **patched** `Formatting` 🟠 column translated has no type +- **patched** `Formatting` 🟡 column label: sqlc nullable, SQL Server NOT NULL +- **patched** `Formatting` 🟡 column joined: sqlc nullable, SQL Server NOT NULL + +### expressions/datetime_functions + +GETDATE, SYSDATETIME, SYSDATETIMEOFFSET, DATEADD, DATEDIFF, DATEDIFF_BIG, DATEPART, DATENAME, EOMONTH, DATEFROMPARTS, DATETRUNC + +- **upstream** ❌ + ``` + query.sql:1:1: unknown column "day" + ``` +- **patched** ❌ + ``` + query.sql:1:1: unknown column "day" + ``` + +### expressions/greatest_least + +GREATEST and LEAST (SQL Server 2022) + +- **upstream** `Clamp` 🟠 parameter @floor has no type (SQL Server: numeric(38,19)) +- **upstream** `Clamp` 🟠 parameter @ceiling has no type (SQL Server: numeric(38,19)) +- **upstream** `Clamp` 🟠 column at_least has no type +- **upstream** `Clamp` 🟠 column at_most has no type +- **patched** `Clamp` 🟠 parameter @floor has no type (SQL Server: numeric(38,19)) +- **patched** `Clamp` 🟠 parameter @ceiling has no type (SQL Server: numeric(38,19)) +- **patched** `Clamp` 🟠 column at_least has no type +- **patched** `Clamp` 🟠 column at_most has no type + +### expressions/is_distinct_from + +IS [NOT] DISTINCT FROM (SQL Server 2022) + +- **upstream** `ChangedBios` 🟠 parameter @bio (nvarchar(max)) is missing +- **patched** `ChangedBios` 🟠 parameter @bio (nvarchar(max)) is missing + +### expressions/math_functions + +ABS, ROUND, CEILING, FLOOR, POWER, SQRT, SIGN, PI, RAND + +- **upstream** `MathFunctions` 🟠 column abs_price has no type +- **upstream** `MathFunctions` 🟠 column rounded has no type +- **upstream** `MathFunctions` 🟠 column ceil has no type +- **upstream** `MathFunctions` 🟠 column floor_price has no type +- **upstream** `MathFunctions` 🟠 column squared has no type +- **upstream** `MathFunctions` 🟠 column root has no type +- **upstream** `MathFunctions` 🟠 column sign has no type +- **upstream** `MathFunctions` 🟠 column pi has no type +- **upstream** `MathFunctions` 🟠 column random has no type +- **upstream** `MathFunctions` 🟡 column pi: sqlc nullable, SQL Server NOT NULL +- **patched** `MathFunctions` 🟠 column abs_price has no type +- **patched** `MathFunctions` 🟠 column rounded has no type +- **patched** `MathFunctions` 🟠 column ceil has no type +- **patched** `MathFunctions` 🟠 column floor_price has no type +- **patched** `MathFunctions` 🟠 column squared has no type +- **patched** `MathFunctions` 🟠 column root has no type +- **patched** `MathFunctions` 🟠 column sign has no type +- **patched** `MathFunctions` 🟠 column pi has no type +- **patched** `MathFunctions` 🟠 column random has no type +- **patched** `MathFunctions` 🟡 column pi: sqlc nullable, SQL Server NOT NULL + +### expressions/sql2022_functions + +GENERATE_SERIES, DATE_BUCKET, TRIM ... FROM and STRING_ESCAPE (SQL Server 2022) + +- **upstream** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + query.sql:5:1: unknown column "week" + ``` +- **patched** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + query.sql:5:1: unknown column "week" + ``` +- SQL Server, `Series` parameters: Argument data type void type is invalid for argument 2 of generate_series function. Operand type clash: int is incompatible with void type The batch could not be analyzed because of compile errors. +- SQL Server, `Series` result set: Argument data type void type is invalid for argument 2 of generate_series function. Operand type clash: int is incompatible with void type The batch could not be analyzed because of compile errors. + +### expressions/string_functions + +LEN, DATALENGTH, SUBSTRING, LEFT, RIGHT, UPPER, LOWER, TRIM, REPLACE, CHARINDEX, PATINDEX, REVERSE, REPLICATE, STUFF + +- **upstream** `StringFunctions` 🟠 column prefix has no type +- **upstream** `StringFunctions` 🟠 column left2 has no type +- **upstream** `StringFunctions` 🟠 column right2 has no type +- **upstream** `StringFunctions` 🟠 column replaced has no type +- **upstream** `StringFunctions` 🟠 column pos has no type +- **upstream** `StringFunctions` 🟠 column digit_pos has no type +- **upstream** `StringFunctions` 🟠 column reversed has no type +- **upstream** `StringFunctions` 🟠 column stars has no type +- **upstream** `StringFunctions` 🟠 column stuffed has no type +- **upstream** `StringFunctions` 🟡 column len: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `StringFunctions` 🟡 column bytes: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `StringFunctions` 🟡 column left2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `StringFunctions` 🟡 column right2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `StringFunctions` 🟡 column upper_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `StringFunctions` 🟡 column lower_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `StringFunctions` 🟡 column trimmed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `StringFunctions` 🟡 column trimmed2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `StringFunctions` 🟠 column prefix has no type +- **patched** `StringFunctions` 🟠 column left2 has no type +- **patched** `StringFunctions` 🟠 column right2 has no type +- **patched** `StringFunctions` 🟠 column replaced has no type +- **patched** `StringFunctions` 🟠 column pos has no type +- **patched** `StringFunctions` 🟠 column digit_pos has no type +- **patched** `StringFunctions` 🟠 column reversed has no type +- **patched** `StringFunctions` 🟠 column stars has no type +- **patched** `StringFunctions` 🟠 column stuffed has no type +- **patched** `StringFunctions` 🟡 column len: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `StringFunctions` 🟡 column bytes: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `StringFunctions` 🟡 column left2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `StringFunctions` 🟡 column right2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `StringFunctions` 🟡 column upper_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `StringFunctions` 🟡 column lower_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `StringFunctions` 🟡 column trimmed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `StringFunctions` 🟡 column trimmed2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### expressions/system_functions + +NEWID, @@ROWCOUNT, SCOPE_IDENTITY, IDENT_CURRENT, DB_NAME, SUSER_SNAME, HASHBYTES, CHECKSUM + +- **upstream** `SystemValues` 🟠 parameter @payload has no type (SQL Server: nvarchar(4000)) +- **upstream** `SystemValues` 🟠 column row_count has no type +- **upstream** `SystemValues` 🟠 column current_id has no type +- **upstream** `SystemValues` 🟠 column db has no type +- **upstream** `SystemValues` 🟠 column login has no type +- **upstream** `SystemValues` 🟠 column hash has no type +- **upstream** `SystemValues` 🟠 column checksum has no type +- **upstream** `SystemValues` 🟡 column id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **upstream** `SystemValues` 🟡 column scope_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `SystemValues` 🟠 parameter @payload has no type (SQL Server: nvarchar(4000)) +- **patched** `SystemValues` 🟠 column row_count has no type +- **patched** `SystemValues` 🟠 column current_id has no type +- **patched** `SystemValues` 🟠 column db has no type +- **patched** `SystemValues` 🟠 column login has no type +- **patched** `SystemValues` 🟠 column hash has no type +- **patched** `SystemValues` 🟠 column checksum has no type +- **patched** `SystemValues` 🟡 column id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `SystemValues` 🟡 column scope_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### identifiers/mixed_case_names + +Mixed-case table and column names map to PascalCase + +- **upstream** `ListMixed` 🟡 column 1 is named customerid, SQL Server keeps the case: CustomerID +- **upstream** `ListMixed` 🟡 column 2 is named firstname, SQL Server keeps the case: firstName +- **upstream** `ListMixed` 🟡 column 3 is named last_name, SQL Server keeps the case: LAST_NAME +- **patched** `ListMixed` 🟡 column 1 is named customerid, SQL Server keeps the case: CustomerID +- **patched** `ListMixed` 🟡 column 2 is named firstname, SQL Server keeps the case: firstName +- **patched** `ListMixed` 🟡 column 3 is named last_name, SQL Server keeps the case: LAST_NAME + +### identifiers/non_identifier_column_names + +Column names that are not valid C# identifiers: leading digits, spaces, dashes, Unicode + +- **upstream** `ListOdd` 🟡 column 4 is named total $, SQL Server keeps the case: Total $ +- **patched** `ListOdd` 🟡 column 4 is named total $, SQL Server keeps the case: Total $ + +### identifiers/unnamed_expression_column + +A result column without a name + +- **upstream** `Unnamed` 🟡 column 1 has no name in SQL Server (sqlc calls it count) +- **upstream** `Unnamed` 🟡 column count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `Unnamed` 🟡 column 1 has no name in SQL Server (sqlc calls it count) +- **patched** `Unnamed` 🟡 column count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### insert/identity_insert + +Explicit values for an IDENTITY column with SET IDENTITY_INSERT + +- **upstream** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` +- **patched** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` + +### insert/insert_select + +INSERT ... SELECT with a parameter + +- **upstream** `CopyBooksToAuthor` 🟠 parameter @target_author_id has no type (SQL Server: bigint) +- **patched** `CopyBooksToAuthor` 🟠 parameter @target_author_id has no type (SQL Server: bigint) + +### insert/output_into_table_variable + +OUTPUT ... INTO a table variable inside a multi-statement query + +- **upstream** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` +- **patched** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` + +### insert/schema_scoped + +INSERT into a table in a user-defined schema + +- **upstream** ❌ + ``` + query.sql:1:1: expected ), got message + ``` +- **patched** ❌ + ``` + query.sql:1:1: expected ), got message + ``` + +### joins/cross_apply + +CROSS APPLY and OUTER APPLY with a correlated TOP subquery + +- **upstream** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + query.sql:6:1: scope: unsupported FROM item *ast.TODO + ``` +- **patched** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + query.sql:6:1: scope: unsupported FROM item *ast.TODO + ``` + +### joins/inner_left_right_full + +INNER, LEFT, RIGHT and FULL joins and the nullability they introduce + +- **upstream** `LeftJoin` 🟠 column title: sqlc NOT NULL, SQL Server nullable +- **upstream** `RightJoin` 🟠 column name: sqlc NOT NULL, SQL Server nullable +- **upstream** `FullJoin` 🟠 column name: sqlc NOT NULL, SQL Server nullable +- **upstream** `FullJoin` 🟠 column title: sqlc NOT NULL, SQL Server nullable +- **patched** `LeftJoin` 🟠 column title: sqlc NOT NULL, SQL Server nullable +- **patched** `RightJoin` 🟠 column name: sqlc NOT NULL, SQL Server nullable +- **patched** `FullJoin` 🟠 column name: sqlc NOT NULL, SQL Server nullable +- **patched** `FullJoin` 🟠 column title: sqlc NOT NULL, SQL Server nullable + +### json/for_json + +FOR JSON PATH / AUTO producing a single JSON column + +- **upstream** `AuthorsAsJson` 🟠 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B) +- **upstream** `AuthorAsJsonObject` 🟠 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B) +- **patched** `AuthorsAsJson` 🟠 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B) +- **patched** `AuthorAsJsonObject` 🟠 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B) + +### json/json_object_array + +JSON_OBJECT and JSON_ARRAY constructors (SQL Server 2022) + +- **upstream** `AuthorJson` 🟠 column obj has no type +- **upstream** `AuthorJson` 🟠 column arr has no type +- **patched** `AuthorJson` 🟠 column obj has no type +- **patched** `AuthorJson` 🟠 column arr has no type + +### json/json_value_query + +JSON_VALUE, JSON_QUERY, ISJSON and JSON_MODIFY on an NVARCHAR(MAX) column + +- **upstream** `JsonAccess` 🟠 parameter @new_name has no type (SQL Server: float) +- **upstream** `JsonAccess` 🟠 parameter @kind has no type (SQL Server: nvarchar(4000)) +- **upstream** `JsonAccess` 🟠 column name has no type +- **upstream** `JsonAccess` 🟠 column tags has no type +- **upstream** `JsonAccess` 🟠 column is_json has no type +- **upstream** `JsonAccess` 🟠 column renamed has no type +- **patched** `JsonAccess` 🟠 parameter @new_name has no type (SQL Server: float) +- **patched** `JsonAccess` 🟠 parameter @kind has no type (SQL Server: nvarchar(4000)) +- **patched** `JsonAccess` 🟠 column name has no type +- **patched** `JsonAccess` 🟠 column tags has no type +- **patched** `JsonAccess` 🟠 column is_json has no type +- **patched** `JsonAccess` 🟠 column renamed has no type + +### json/openjson + +OPENJSON with an explicit WITH schema, fed by a parameter + +- **upstream** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + query.sql:5:1: where: scope: unsupported FROM item *ast.TODO + ``` +- **patched** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + query.sql:5:1: where: scope: unsupported FROM item *ast.TODO + ``` +- SQL Server, `ParseItems` parameters: The parameter type for '@items' cannot be uniquely deduced; two possibilities are 'sql_variant' and 'xml'. +- SQL Server, `AuthorsByJsonIds` parameters: The parameter type for '@ids' cannot be uniquely deduced; two possibilities are 'sql_variant' and 'xml'. + +### macros/arg_forms + +sqlc.arg with a quoted name, a bare identifier and a double-quoted name + +- **upstream** ❌ + ``` + query.sql:1:1: where: unknown column "min_id" + ``` + +### macros/arg_in_cast + +sqlc.arg inside CAST, which fixes its type + +- **upstream** `CastArg` 🟠 parameter @min_price (decimal(10,2)) is missing + +### macros/args_in_top_offset + +sqlc.arg in TOP and in OFFSET / FETCH + +- **upstream** `TopArg` 🟠 parameter @take (bigint) is missing +- **upstream** `PageArgs` 🟠 parameter @skip (bigint) is missing +- **upstream** `PageArgs` 🟠 parameter @take (bigint) is missing +- **patched** `TopArg` 🟡 parameter @take: sqlc int, SQL Server bigint +- **patched** `PageArgs` 🟡 parameter @skip: sqlc int, SQL Server bigint +- **patched** `PageArgs` 🟡 parameter @take: sqlc int, SQL Server bigint + +### macros/embed + +sqlc.embed of a table, of aliases (self join) and next to a t.* star + +- **upstream** ❌ + ``` + query.sql:1:1: unknown column "authors" + query.sql:5:1: unknown column "a1" + query.sql:8:1: unknown column "authors" + ``` + +### macros/embed_schema_scoped + +sqlc.embed of a table in a user-defined schema + +- **upstream** ❌ + ``` + query.sql:1:1: unknown column "orders" + ``` + +### macros/narg + +sqlc.narg makes a parameter nullable + +- **upstream** `NargFilter` 🟠 parameter @name (nvarchar(100)) is missing + +### macros/slice + +sqlc.slice for integers, strings, NOT IN and several slices in one query + +- **upstream** `ByIds` 🟠 parameter @ids (bigint) is missing +- **upstream** `ByNames` 🟠 parameter @names (nvarchar(100)) is missing +- **upstream** `NotByIds` 🟠 parameter @excluded (bigint) is missing +- **upstream** `ByIdsAndTitles` 🟠 parameter @author_ids (bigint) is missing +- **upstream** `ByIdsAndTitles` 🟠 parameter @titles (nvarchar(200)) is missing + +### merge/merge_delete + +MERGE with WHEN NOT MATCHED BY SOURCE THEN DELETE + +- **upstream** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` +- **patched** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` +- SQL Server, `SyncTags` parameters: Argument data type void type is invalid for argument 1 of string_split function. The batch could not be analyzed because of compile errors. +- SQL Server, `SyncTags` result set: Argument data type void type is invalid for argument 1 of string_split function. The batch could not be analyzed because of compile errors. + +### merge/merge_upsert + +MERGE as an upsert, with OUTPUT $action + +- **upstream** ❌ + ``` + query.sql:1:1: expected identifier for schema object name + ``` +- **patched** ❌ + ``` + query.sql:1:1: expected identifier for schema object name + ``` + +### programmability/exec_procedure + +Calling a stored procedure with EXEC and named arguments + +- **upstream** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` +- **patched** 🕳️ + ``` + error parsing queries: no queries contained in paths query.sql + ``` + +### programmability/inline_table_function + +An inline table-valued function in FROM + +- **upstream** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + ``` +- **patched** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + ``` + +### programmability/multi_statement_table_function + +A multi-statement table-valued function in FROM + +- **upstream** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + ``` +- **patched** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + ``` + +### programmability/scalar_function + +A user-defined scalar function in the select list and WHERE clause + +- **upstream** `ListWithInitials` 🟠 parameter @initials has no type (SQL Server: nvarchar(10)) +- **upstream** `ListWithInitials` 🟠 column initials has no type +- **patched** `ListWithInitials` 🟠 parameter @initials has no type (SQL Server: nvarchar(10)) +- **patched** `ListWithInitials` 🟠 column initials has no type + +### programmability/string_split + +STRING_SPLIT as a table-valued function fed by a parameter + +- **upstream** ❌ + ``` + query.sql:1:1: where: scope: unsupported FROM item *ast.TODO + ``` +- **patched** ❌ + ``` + query.sql:1:1: where: scope: unsupported FROM item *ast.TODO + ``` +- SQL Server, `AuthorsByNameList` parameters: Argument data type void type is invalid for argument 1 of string_split function. The batch could not be analyzed because of compile errors. +- SQL Server, `AuthorsByNameList` result set: Argument data type void type is invalid for argument 1 of string_split function. The batch could not be analyzed because of compile errors. + +### programmability/table_valued_parameter + +A table-valued parameter (user-defined table type) used in a join + +- **upstream** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + ``` +- **patched** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + ``` +- SQL Server, `AuthorsByIdList` parameters: Must declare the table variable "@ids". The batch could not be analyzed because of compile errors. +- SQL Server, `AuthorsByIdList` result set: Must declare the table variable "@ids". The batch could not be analyzed because of compile errors. + +### select/case_expression + +Searched and simple CASE expressions + +- **upstream** `ListPriceBands` 🟠 column band: sqlc varchar, SQL Server nvarchar(9) +- **patched** `ListPriceBands` 🟠 column band: sqlc varchar, SQL Server nvarchar(9) + +### select/comments + +Block comments, nested block comments and trailing line comments inside a query + +- **upstream** ❌ + ``` + query.sql:1:1: unexpected token: * + ``` +- **patched** ❌ + ``` + query.sql:1:1: unexpected token: * + ``` + +### select/function_on_parameter + +Parameters wrapped in functions and compared to columns + +- **upstream** `CaseInsensitiveMatch` 🟡 parameter @name: sqlc varchar, SQL Server nvarchar(4000) +- **patched** `CaseInsensitiveMatch` 🟡 parameter @name: sqlc varchar, SQL Server nvarchar(4000) + +### select/iif_choose + +IIF and CHOOSE + +- **upstream** `ListFlags` 🟠 column second has no type +- **patched** `ListFlags` 🟠 column second has no type + +### select/no_semicolon_terminator + +Statements without a terminating semicolon (optional in T-SQL) + +- **upstream** ❌ + ``` + query.sql:6:0: missing semicolon at end of file + ``` +- **patched** ❌ + ``` + query.sql:6:0: missing semicolon at end of file + ``` + +### select/offset_fetch + +OFFSET ... ROWS FETCH NEXT ... ROWS ONLY with parameters + +- **upstream** `PageAuthors` 🟡 parameter @skip: sqlc int, SQL Server bigint +- **upstream** `PageAuthors` 🟡 parameter @take: sqlc int, SQL Server bigint +- **patched** `PageAuthors` 🟡 parameter @skip: sqlc int, SQL Server bigint +- **patched** `PageAuthors` 🟡 parameter @take: sqlc int, SQL Server bigint + +### select/offset_without_fetch + +OFFSET ... ROWS without FETCH + +- **upstream** `SkipAuthors` 🟡 parameter @skip: sqlc int, SQL Server bigint +- **patched** `SkipAuthors` 🟡 parameter @skip: sqlc int, SQL Server bigint + +### select/parameter_in_select_list + +A parameter echoed in the select list, with and without CAST + +- **upstream** `EchoParams` 🟠 parameter @label has no type (SQL Server cannot deduce it either) +- **upstream** `EchoParams` 🟠 column label has no type +- **patched** `EchoParams` 🟠 parameter @label has no type (SQL Server cannot deduce it either) +- **patched** `EchoParams` 🟠 column label has no type +- SQL Server, `EchoParams` parameters: The parameter type for '@label' cannot be uniquely deduced; two possibilities are 'sql_variant' and 'xml'. +- SQL Server, `EchoParams` result set: The metadata could not be determined because statement 'SELECT id, @label AS label, CAST(@factor AS DECIMAL(5, 2)) * price AS scaled FROM books' uses an undeclared parameter in a context that affects its metadata. + +### select/quoted_aliases + +Aliases quoted with brackets, double quotes and single quotes + +- **upstream** `ListQuotedAliases` 🟡 column 1 is named author id, SQL Server keeps the case: Author Id +- **upstream** `ListQuotedAliases` 🟡 column 2 is named author name, SQL Server keeps the case: Author Name +- **upstream** `ListQuotedAliases` 🟡 column 3 is named biography, SQL Server keeps the case: Biography +- **patched** `ListQuotedAliases` 🟡 column 1 is named author id, SQL Server keeps the case: Author Id +- **patched** `ListQuotedAliases` 🟡 column 2 is named author name, SQL Server keeps the case: Author Name +- **patched** `ListQuotedAliases` 🟡 column 3 is named biography, SQL Server keeps the case: Biography + +### select/table_value_constructor + +VALUES as a derived table + +- **upstream** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + ``` +- **patched** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + ``` + +### select/top_parameter + +TOP with a parenthesized parameter + +- **upstream** `TopAuthors` 🟡 parameter @take: sqlc int, SQL Server bigint +- **patched** `TopAuthors` 🟡 parameter @take: sqlc int, SQL Server bigint + +### select/unicode_literals + +N'' Unicode literals and string concatenation with + + +- **upstream** `Greetings` 🟠 column greeting: sqlc varchar, SQL Server nvarchar(105) +- **patched** `Greetings` 🟠 column greeting: sqlc varchar, SQL Server nvarchar(105) + +### select/without_from + +SELECT without FROM: parameters and functions only + +- **upstream** `AddOne` 🟡 column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `AddOne` 🟡 column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### setops/pivot_unpivot + +PIVOT and UNPIVOT + +- **upstream** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + query.sql:7:1: scope: unsupported FROM item *ast.TODO + ``` +- **patched** ❌ + ``` + query.sql:1:1: scope: unsupported FROM item *ast.TODO + query.sql:7:1: scope: unsupported FROM item *ast.TODO + ``` + +### update/top + +UPDATE TOP (@n) + +- **upstream** `MarkSomeUnavailable` 🟠 parameter @n (bigint) is missing +- **patched** `MarkSomeUnavailable` 🟠 parameter @n (bigint) is missing + +### window/aggregate_over + +Aggregates over windows, including a running total + +- **upstream** `RunningTotals` 🟡 column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) +- **patched** `RunningTotals` 🟡 column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) + +### window/lag_lead_first_value + +LAG, LEAD, FIRST_VALUE and LAST_VALUE with a frame + +- **upstream** `PriceNeighbours` 🟠 column previous_price has no type +- **upstream** `PriceNeighbours` 🟠 column next_price has no type +- **upstream** `PriceNeighbours` 🟠 column first_title has no type +- **upstream** `PriceNeighbours` 🟠 column last_title has no type +- **patched** `PriceNeighbours` 🟠 column previous_price has no type +- **patched** `PriceNeighbours` 🟠 column next_price has no type +- **patched** `PriceNeighbours` 🟠 column first_title has no type +- **patched** `PriceNeighbours` 🟠 column last_title has no type + +### window/ranking + +ROW_NUMBER, RANK, DENSE_RANK and NTILE + +- **upstream** `RankedBooks` 🟠 column rn has no type +- **upstream** `RankedBooks` 🟠 column rnk has no type +- **upstream** `RankedBooks` 🟠 column dense_rnk has no type +- **upstream** `RankedBooks` 🟠 column quartile has no type +- **patched** `RankedBooks` 🟠 column rn has no type +- **patched** `RankedBooks` 🟠 column rnk has no type +- **patched** `RankedBooks` 🟠 column dense_rnk has no type +- **patched** `RankedBooks` 🟠 column quartile has no type + +### window/window_clause_and_percentiles + +Named WINDOW clause (SQL Server 2022), PERCENTILE_CONT and PERCENTILE_DISC + +- **upstream** `Percentiles` 🟠 column median has no type +- **upstream** `Percentiles` 🟠 column median_disc has no type +- **patched** `Percentiles` 🟠 column median has no type +- **patched** `Percentiles` 🟠 column median_disc has no type + +### xml/for_xml + +FOR XML PATH, including the string-concatenation idiom + +- **upstream** `AuthorsAsXml` 🟠 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B) +- **upstream** `ConcatenatedTitles` 🟠 column titles has no type +- **patched** `AuthorsAsXml` 🟠 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B) +- **patched** `ConcatenatedTitles` 🟠 column titles has no type + +### xml/xml_methods + +XML column with the value(), query(), exist() and nodes() methods + +- **upstream** ❌ + ``` + query.sql:1:1: unexpected token: query + ``` +- **patched** ❌ + ``` + query.sql:1:1: unexpected token: query + ``` +- SQL Server, `XmlAccess` parameters: XQuery: Unable to resolve sql:variable('@status'). The variable must be declared as a scalar TSQL variable. +- SQL Server, `XmlAccess` result set: XQuery: Unable to resolve sql:variable('@status'). The variable must be declared as a scalar TSQL variable. + diff --git a/end2end/MssqlSyntaxMatrix/SqlServerOracle.cs b/end2end/MssqlSyntaxMatrix/SqlServerOracle.cs new file mode 100644 index 000000000..0ac5b653f --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/SqlServerOracle.cs @@ -0,0 +1,165 @@ +using Microsoft.Data.SqlClient; +using System.Text.RegularExpressions; + +namespace MssqlSyntaxMatrix; + +public record OracleColumn(string Name, string Type, bool Nullable); + +public record OracleParam(string Name, string Type); + +public class OracleQuery +{ + public List? Params { get; set; } + public string? ParamsError { get; set; } + public List? Columns { get; set; } + public string? ColumnsError { get; set; } +} + +public class OracleResult +{ + public string? SchemaError { get; set; } + public Dictionary Queries { get; } = []; +} + +// Asks SQL Server itself what a query takes and returns: sp_describe_undeclared_parameters for the parameters and +// sp_describe_first_result_set for the result columns. Every case gets a fresh database. +public partial class SqlServerOracle(string connectionString) +{ + private const string Database = "sqlc_syntax_matrix"; + + public async Task Describe(TestCase testCase) + { + var result = new OracleResult(); + var builder = new SqlConnectionStringBuilder(connectionString) { InitialCatalog = "master" }; + await using var connection = new SqlConnection(builder.ConnectionString); + await connection.OpenAsync(); + await Execute(connection, $""" + IF DB_ID('{Database}') IS NOT NULL + BEGIN + ALTER DATABASE [{Database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE [{Database}]; + END + CREATE DATABASE [{Database}]; + """); + await connection.ChangeDatabaseAsync(Database); + + try + { + foreach (var batch in GoRegex().Split(testCase.Schema).Where(b => b.Trim().Length > 0)) + await Execute(connection, batch); + } + catch (SqlException e) + { + result.SchemaError = e.Message; + return result; + } + + foreach (var query in testCase.Queries) + result.Queries[query.Name] = await DescribeQuery(connection, ToNativeSql(query.Text)); + return result; + } + + private static async Task DescribeQuery(SqlConnection connection, string sql) + { + var result = new OracleQuery(); + try + { + result.Params = await DescribeParams(connection, sql); + } + catch (SqlException e) when (e.Message.Contains("is used more than once")) + { + // SQL Server refuses a parameter used twice: describe every use under its own name and keep the first type + try + { + var occurrence = 0; + var unique = ParamRegex().Replace(sql, m => $"{m.Value}__{occurrence++}"); + result.Params = [.. (await DescribeParams(connection, unique)) + .GroupBy(p => p.Name[..p.Name.LastIndexOf("__", StringComparison.Ordinal)], StringComparer.OrdinalIgnoreCase) + .Select(g => new OracleParam(g.Key, MergeTypes(g.Select(p => p.Type))))]; + } + catch (SqlException inner) + { + result.Params = null; + result.ParamsError = inner.Message; + } + } + catch (SqlException e) + { + result.Params = null; + result.ParamsError = e.Message; + } + + try + { + result.Columns = []; + await using var command = new SqlCommand("sp_describe_first_result_set", connection) { CommandType = System.Data.CommandType.StoredProcedure }; + command.Parameters.AddWithValue("@tsql", sql); + var declarations = result.Params?.Where(p => p.Type != "").Select(p => $"{p.Name} {p.Type}").ToList() ?? []; + command.Parameters.AddWithValue("@params", declarations.Count > 0 ? string.Join(", ", declarations) : DBNull.Value); + await using var reader = await command.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + if (reader.GetBoolean(reader.GetOrdinal("is_hidden"))) + continue; + result.Columns.Add(new OracleColumn( + reader.IsDBNull(reader.GetOrdinal("name")) ? "" : reader.GetString(reader.GetOrdinal("name")), + reader.IsDBNull(reader.GetOrdinal("system_type_name")) ? "" : reader.GetString(reader.GetOrdinal("system_type_name")), + reader.GetBoolean(reader.GetOrdinal("is_nullable")))); + } + } + catch (SqlException e) + { + result.Columns = null; + result.ColumnsError = e.Message; + } + return result; + } + + // "@p IS NULL" makes SQL Server suggest int, its default, so a use that pins a real type wins over it + private static string MergeTypes(IEnumerable types) + { + var distinct = types.Where(t => t != "").Distinct().ToList(); + if (distinct.Count > 1) + distinct.Remove("int"); + return distinct.FirstOrDefault() ?? ""; + } + + private static async Task> DescribeParams(SqlConnection connection, string sql) + { + var result = new List(); + await using var command = new SqlCommand("sp_describe_undeclared_parameters", connection) { CommandType = System.Data.CommandType.StoredProcedure }; + command.Parameters.AddWithValue("@tsql", sql); + await using var reader = await command.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + result.Add(new OracleParam( + reader.GetString(reader.GetOrdinal("name")), + reader.IsDBNull(reader.GetOrdinal("suggested_system_type_name")) ? "" : reader.GetString(reader.GetOrdinal("suggested_system_type_name")))); + return result; + } + + // what sqlc turns its macros into, so SQL Server sees plain T-SQL + public static string ToNativeSql(string sql) + { + sql = EmbedRegex().Replace(sql, m => m.Groups[1].Value + ".*"); + return MacroRegex().Replace(sql, m => "@" + m.Groups[1].Value); + } + + private static async Task Execute(SqlConnection connection, string sql) + { + await using var command = new SqlCommand(sql, connection); + await command.ExecuteNonQueryAsync(); + } + + // @name, but not @@ROWCOUNT and not inside a string literal such as sql:variable("@status") + [GeneratedRegex(@"(? Columns, List Params); + +public enum RunStatus { Ok, Error, Panic, Skipped } + +public class SqlcResult +{ + public RunStatus Status { get; set; } + public string Error { get; set; } = ""; + public Dictionary Queries { get; } = []; + public RunStatus PluginStatus { get; set; } = RunStatus.Skipped; + public string PluginError { get; set; } = ""; + public string? PluginOutputDir { get; set; } +} + +public static class SqlcRunner +{ + public static async Task Run(SqlcBuild build, TestCase testCase, string dir, MatrixOptions options) + { + Directory.CreateDirectory(dir); + await File.WriteAllTextAsync(Path.Combine(dir, "schema.sql"), testCase.Schema); + await File.WriteAllTextAsync(Path.Combine(dir, "query.sql"), testCase.QueriesText); + await File.WriteAllTextAsync(Path.Combine(dir, "sqlc.json.yaml"), """ + version: "2" + sql: + - engine: mssql + schema: schema.sql + queries: query.sql + gen: + json: + out: json + """); + + var result = new SqlcResult(); + var (exitCode, output) = await Exec(build.Path, "-f sqlc.json.yaml generate", dir); + if (exitCode != 0) + { + result.Status = output.Contains("panic:") ? RunStatus.Panic : RunStatus.Error; + result.Error = Summarize(output.Replace(dir + Path.DirectorySeparatorChar, "")); + return result; + } + + result.Status = RunStatus.Ok; + using var request = JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(dir, "json", "codegen_request.json"))); + foreach (var q in request.RootElement.GetProperty("queries").EnumerateArray()) + { + var query = new SqlcQuery( + q.GetProperty("name").GetString()!, + q.GetProperty("cmd").GetString()!, + q.GetProperty("text").GetString()!, + [.. q.GetProperty("columns").EnumerateArray().Select(ReadColumn)], + [.. q.GetProperty("params").EnumerateArray().Select(p => + { + var column = ReadColumn(p.GetProperty("column")); + var name = p.TryGetProperty("name", out var n) && n.GetString() is { Length: > 0 } s ? s : column.Name; + return column with { Name = name }; + })]); + result.Queries[query.Name] = query; + } + + if (build == options.PluginBuild) + await RunPlugin(dir, testCase, options, result); + return result; + } + + private static SqlcColumn ReadColumn(JsonElement c) + { + var embed = c.TryGetProperty("embed_table", out var e) && e.ValueKind == JsonValueKind.Object + ? e.GetProperty("name").GetString() + : null; + return new SqlcColumn( + c.GetProperty("name").GetString() ?? "", + c.GetProperty("type").GetProperty("name").GetString() ?? "", + c.GetProperty("not_null").GetBoolean(), + embed, + c.GetProperty("is_array").GetBoolean(), + c.TryGetProperty("is_sqlc_slice", out var s) && s.GetBoolean(), + c.TryGetProperty("table", out var t) && t.ValueKind == JsonValueKind.Object && t.GetProperty("name").GetString() is { Length: > 0 }); + } + + // the plugin generates both the driver and the Dapper flavour, as namespaces the compile step can tell apart + private static async Task RunPlugin(string dir, TestCase testCase, MatrixOptions options, SqlcResult result) + { + var ns = CaseNamespace(testCase); + await File.WriteAllTextAsync(Path.Combine(dir, "sqlc.plugin.yaml"), $""" + version: "2" + plugins: + - name: csharp + process: + cmd: {options.PluginPath} + sql: + - engine: mssql + schema: schema.sql + queries: query.sql + codegen: + - plugin: csharp + out: csharp/driver + options: + namespaceName: {ns}.Driver + generateCsproj: false + targetFramework: net10.0 + - plugin: csharp + out: csharp/dapper + options: + namespaceName: {ns}.Dapper + generateCsproj: false + useDapper: true + targetFramework: net10.0 + """); + var (exitCode, output) = await Exec(options.PluginBuild.Path, "-f sqlc.plugin.yaml generate", dir); + result.PluginStatus = exitCode == 0 ? RunStatus.Ok : RunStatus.Error; + result.PluginError = exitCode == 0 ? "" : Summarize(output.Replace(dir + Path.DirectorySeparatorChar, "")); + result.PluginOutputDir = exitCode == 0 ? Path.Combine(dir, "csharp") : null; + } + + public static string CaseNamespace(TestCase testCase) => + "Case_" + string.Concat(testCase.Id.Select(ch => char.IsLetterOrDigit(ch) ? ch : '_')); + + // keeps the lines that explain a failure and drops stack traces + private static string Summarize(string output) + { + var lines = output.Split('\n') + .Select(l => l.TrimEnd()) + .Where(l => l.Length > 0 && !l.StartsWith('\t') && !l.StartsWith(" at ") && !l.StartsWith("goroutine ") + && !l.StartsWith("# package") && !l.StartsWith("[signal")) + .Take(6); + return string.Join("\n", lines); + } + + public static async Task<(int ExitCode, string Output)> Exec(string file, string arguments, string workingDir) + { + var psi = new ProcessStartInfo(file, arguments) + { + WorkingDirectory = workingDir, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + using var process = Process.Start(psi)!; + var stdout = process.StandardOutput.ReadToEndAsync(); + var stderr = process.StandardError.ReadToEndAsync(); + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + await process.WaitForExitAsync(cts.Token); + return (process.ExitCode, (await stdout) + (await stderr)); + } +} \ No newline at end of file diff --git a/end2end/MssqlSyntaxMatrix/TestCase.cs b/end2end/MssqlSyntaxMatrix/TestCase.cs new file mode 100644 index 000000000..3e866fdc0 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/TestCase.cs @@ -0,0 +1,82 @@ +using System.Text.RegularExpressions; + +namespace MssqlSyntaxMatrix; + +public record CaseQuery(string Name, string Cmd, string Text); + +// A case file holds a description, an optional schema and one or more sqlc queries: +// -- @description What the case covers +// -- @schema +// CREATE TABLE ... +// -- @queries +// -- name: Foo :many +// SELECT ...; +// cases/base.sql is prepended to the schema of every case. +public partial class TestCase +{ + public required string Id { get; init; } + public required string Category { get; init; } + public required string Description { get; init; } + public required string Schema { get; init; } + public required string QueriesText { get; init; } + public required List Queries { get; init; } + + public static List LoadAll(string casesDir) + { + var baseSchema = File.ReadAllText(Path.Combine(casesDir, "base.sql")); + return [.. Directory.GetFiles(casesDir, "*.sql", SearchOption.AllDirectories) + .Where(f => Path.GetFileName(f) != "base.sql") + .Order(StringComparer.Ordinal) + .Select(f => Load(f, casesDir, baseSchema))]; + } + + private static TestCase Load(string file, string casesDir, string baseSchema) + { + var relative = Path.GetRelativePath(casesDir, file).Replace('\\', '/'); + var id = relative[..^".sql".Length]; + var description = new List(); + var schema = new List(); + var queries = new List(); + List? section = null; + foreach (var line in File.ReadAllLines(file)) + { + var trimmed = line.Trim(); + if (trimmed.StartsWith("-- @description")) + description.Add(trimmed["-- @description".Length..].Trim()); + else if (trimmed == "-- @schema") + section = schema; + else if (trimmed == "-- @queries") + section = queries; + else + section?.Add(line); + } + + var queriesText = string.Join("\n", queries).Trim() + "\n"; + return new TestCase + { + Id = id, + Category = id.Contains('/') ? id[..id.IndexOf('/')] : "misc", + Description = string.Join(" ", description), + // the base schema is its own batch, so a case can start with CREATE SCHEMA / VIEW / PROCEDURE + Schema = (baseSchema.TrimEnd() + "\nGO\n\n" + string.Join("\n", schema)).Trim() + "\n", + QueriesText = queriesText, + Queries = SplitQueries(queriesText) + }; + } + + private static List SplitQueries(string text) + { + var matches = NameRegex().Matches(text); + var result = new List(); + for (var i = 0; i < matches.Count; i++) + { + var start = matches[i].Index + matches[i].Length; + var end = i + 1 < matches.Count ? matches[i + 1].Index : text.Length; + result.Add(new CaseQuery(matches[i].Groups[1].Value, matches[i].Groups[2].Value, text[start..end].Trim())); + } + return result; + } + + [GeneratedRegex(@"^--\s*name:\s*(\w+)\s+(:\w+).*$", RegexOptions.Multiline)] + private static partial Regex NameRegex(); +} \ No newline at end of file diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/basic_aggregates.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/basic_aggregates.sql new file mode 100644 index 000000000..1a759f984 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/basic_aggregates.sql @@ -0,0 +1,6 @@ +-- @description COUNT, COUNT_BIG, SUM, AVG, MIN, MAX and their result types +-- @queries +-- name: BookStats :one +SELECT COUNT(*) AS cnt, COUNT_BIG(*) AS cnt_big, SUM(price) AS total, AVG(price) AS average, + MIN(published_at) AS first_published, MAX(title) AS last_title, AVG(id) AS avg_id +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/group_by_having.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/group_by_having.sql new file mode 100644 index 000000000..620d6f534 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/group_by_having.sql @@ -0,0 +1,4 @@ +-- @description GROUP BY with HAVING on a parameter +-- @queries +-- name: ProlificAuthors :many +SELECT author_id, COUNT(*) AS book_count FROM books GROUP BY author_id HAVING COUNT(*) >= @min_books; diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/rollup_cube_grouping_sets.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/rollup_cube_grouping_sets.sql new file mode 100644 index 000000000..00e5aa347 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/rollup_cube_grouping_sets.sql @@ -0,0 +1,10 @@ +-- @description ROLLUP, CUBE, GROUPING SETS and GROUPING() +-- @queries +-- name: Rollup :many +SELECT author_id, is_available, SUM(price) AS total, GROUPING(author_id) AS is_total FROM books GROUP BY ROLLUP (author_id, is_available); + +-- name: Cube :many +SELECT author_id, is_available, COUNT(*) AS cnt FROM books GROUP BY CUBE (author_id, is_available); + +-- name: GroupingSets :many +SELECT author_id, is_available, COUNT(*) AS cnt FROM books GROUP BY GROUPING SETS ((author_id), (is_available), ()); diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/statistical.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/statistical.sql new file mode 100644 index 000000000..96bfb1af5 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/statistical.sql @@ -0,0 +1,4 @@ +-- @description STDEV, VAR and APPROX_COUNT_DISTINCT +-- @queries +-- name: PriceStatistics :one +SELECT STDEV(price) AS stdev, VAR(price) AS variance, APPROX_COUNT_DISTINCT(author_id) AS approx_authors FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/string_agg.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/string_agg.sql new file mode 100644 index 000000000..9bdc1ea6c --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/string_agg.sql @@ -0,0 +1,4 @@ +-- @description STRING_AGG with WITHIN GROUP +-- @queries +-- name: TitlesPerAuthor :many +SELECT author_id, STRING_AGG(title, N', ') WITHIN GROUP (ORDER BY title) AS titles FROM books GROUP BY author_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_identity_table.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_identity_table.sql new file mode 100644 index 000000000..66345408a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_identity_table.sql @@ -0,0 +1,4 @@ +-- @description `:copyfrom` into a table with an IDENTITY column +-- @queries +-- name: CopyBooks :copyfrom +INSERT INTO books (author_id, title, price, published_at, is_available) VALUES (@author_id, @title, @price, @published_at, @is_available); diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_schema_scoped.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_schema_scoped.sql new file mode 100644 index 000000000..f98814fad --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_schema_scoped.sql @@ -0,0 +1,8 @@ +-- @description `:copyfrom` into a table in a user-defined schema +-- @schema +CREATE SCHEMA logging; +GO +CREATE TABLE logging.entries (id INT IDENTITY PRIMARY KEY, message NVARCHAR(200) NOT NULL, level TINYINT NOT NULL); +-- @queries +-- name: CopyEntries :copyfrom +INSERT INTO logging.entries (message, level) VALUES (@message, @level); diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_output_identity.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_output_identity.sql new file mode 100644 index 000000000..f1ab4313b --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_output_identity.sql @@ -0,0 +1,4 @@ +-- @description `:execlastid` via OUTPUT INSERTED.id on an IDENTITY column +-- @queries +-- name: CreateBookReturnId :execlastid +INSERT INTO books (author_id, title) OUTPUT INSERTED.id VALUES (@author_id, @title); diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_scope_identity.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_scope_identity.sql new file mode 100644 index 000000000..74b934024 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_scope_identity.sql @@ -0,0 +1,5 @@ +-- @description `:execlastid` via SELECT SCOPE_IDENTITY() after the INSERT +-- @queries +-- name: CreateAuthorReturnId :execlastid +INSERT INTO authors (name) VALUES (@name); +SELECT CAST(SCOPE_IDENTITY() AS BIGINT); diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/execrows.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/execrows.sql new file mode 100644 index 000000000..556c5a70d --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/annotations/execrows.sql @@ -0,0 +1,7 @@ +-- @description `:execrows` on UPDATE and DELETE +-- @queries +-- name: DisableAll :execrows +UPDATE books SET is_available = 0; + +-- name: DeleteUnavailable :execrows +DELETE FROM books WHERE is_available = 0; diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/one_many_exec.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/one_many_exec.sql new file mode 100644 index 000000000..17cbdbfbe --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/annotations/one_many_exec.sql @@ -0,0 +1,10 @@ +-- @description `:one`, `:many`, `:exec` basics +-- @queries +-- name: GetAuthor :one +SELECT id, name, bio FROM authors WHERE id = @id; + +-- name: ListAuthors :many +SELECT id, name, bio FROM authors ORDER BY name; + +-- name: DeleteAllAuthors :exec +DELETE FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/base.sql b/end2end/MssqlSyntaxMatrix/cases/base.sql new file mode 100644 index 000000000..b5303a5a5 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/base.sql @@ -0,0 +1,16 @@ +-- Shared by every case; a case adds its own objects in its -- @schema section. +CREATE TABLE authors ( + id BIGINT IDENTITY(1, 1) NOT NULL PRIMARY KEY, + name NVARCHAR(100) NOT NULL, + bio NVARCHAR(MAX) NULL, + birth_date DATE NULL +); + +CREATE TABLE books ( + id INT IDENTITY(1, 1) NOT NULL PRIMARY KEY, + author_id BIGINT NOT NULL REFERENCES authors (id), + title NVARCHAR(200) NOT NULL, + price DECIMAL(10, 2) NULL, + published_at DATETIME2 NULL, + is_available BIT NOT NULL DEFAULT 1 +); diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/declare_variables.sql b/end2end/MssqlSyntaxMatrix/cases/batch/declare_variables.sql new file mode 100644 index 000000000..7d433bdda --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/batch/declare_variables.sql @@ -0,0 +1,5 @@ +-- @description DECLARE / SET local variables before the final SELECT +-- @queries +-- name: BooksSinceDaysAgo :many +DECLARE @since DATETIME2 = DATEADD(DAY, -@days, SYSUTCDATETIME()); +SELECT id, title FROM books WHERE published_at >= @since; diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/select_into_temp_table.sql b/end2end/MssqlSyntaxMatrix/cases/batch/select_into_temp_table.sql new file mode 100644 index 000000000..278481a9f --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/batch/select_into_temp_table.sql @@ -0,0 +1,5 @@ +-- @description SELECT ... INTO #temp then reading it, in one query +-- @queries +-- name: ExpensiveBooksViaTemp :many +SELECT id, title INTO #expensive FROM books WHERE price > @min_price; +SELECT id, title FROM #expensive; diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/set_nocount_on.sql b/end2end/MssqlSyntaxMatrix/cases/batch/set_nocount_on.sql new file mode 100644 index 000000000..70f024539 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/batch/set_nocount_on.sql @@ -0,0 +1,5 @@ +-- @description SET NOCOUNT ON in front of a query (a very common T-SQL habit) +-- @queries +-- name: ListAuthorsNoCount :many +SET NOCOUNT ON; +SELECT id, name FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/transaction_in_query.sql b/end2end/MssqlSyntaxMatrix/cases/batch/transaction_in_query.sql new file mode 100644 index 000000000..550b64592 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/batch/transaction_in_query.sql @@ -0,0 +1,7 @@ +-- @description Explicit BEGIN TRAN / COMMIT inside a query +-- @queries +-- name: MoveBooks :exec +BEGIN TRANSACTION; +UPDATE books SET author_id = @to_author_id WHERE author_id = @from_author_id; +DELETE FROM authors WHERE id = @from_author_id; +COMMIT TRANSACTION; diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/try_catch_throw.sql b/end2end/MssqlSyntaxMatrix/cases/batch/try_catch_throw.sql new file mode 100644 index 000000000..ae22741e5 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/batch/try_catch_throw.sql @@ -0,0 +1,9 @@ +-- @description TRY ... CATCH with THROW +-- @queries +-- name: SafeDelete :exec +BEGIN TRY + DELETE FROM authors WHERE id = @id; +END TRY +BEGIN CATCH + THROW; +END CATCH; diff --git a/end2end/MssqlSyntaxMatrix/cases/cte/cte_with_dml.sql b/end2end/MssqlSyntaxMatrix/cases/cte/cte_with_dml.sql new file mode 100644 index 000000000..2f1bb61fc --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/cte/cte_with_dml.sql @@ -0,0 +1,9 @@ +-- @description CTEs in front of UPDATE and DELETE +-- @queries +-- name: DiscountTopBooks :execrows +WITH top_books AS (SELECT TOP (@n) * FROM books ORDER BY price DESC) +UPDATE top_books SET price = price * 0.9; + +-- name: DeleteDuplicates :execrows +WITH dups AS (SELECT id, ROW_NUMBER() OVER (PARTITION BY title ORDER BY id) AS rn FROM books) +DELETE FROM dups WHERE rn > 1; diff --git a/end2end/MssqlSyntaxMatrix/cases/cte/multiple_ctes.sql b/end2end/MssqlSyntaxMatrix/cases/cte/multiple_ctes.sql new file mode 100644 index 000000000..bae1bdeee --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/cte/multiple_ctes.sql @@ -0,0 +1,6 @@ +-- @description Several CTEs referencing each other +-- @queries +-- name: AuthorTotals :many +WITH totals AS (SELECT author_id, SUM(price) AS total FROM books GROUP BY author_id), + ranked AS (SELECT author_id, total, RANK() OVER (ORDER BY total DESC) AS rnk FROM totals) +SELECT a.name, r.total, r.rnk FROM ranked r JOIN authors a ON a.id = r.author_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/cte/recursive_cte.sql b/end2end/MssqlSyntaxMatrix/cases/cte/recursive_cte.sql new file mode 100644 index 000000000..8b6e84d4a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/cte/recursive_cte.sql @@ -0,0 +1,9 @@ +-- @description Recursive CTE with UNION ALL +-- @queries +-- name: Numbers :many +WITH numbers (n) AS ( + SELECT 1 + UNION ALL + SELECT n + 1 FROM numbers WHERE n < @upto +) +SELECT n FROM numbers OPTION (MAXRECURSION 1000); diff --git a/end2end/MssqlSyntaxMatrix/cases/cte/simple_cte.sql b/end2end/MssqlSyntaxMatrix/cases/cte/simple_cte.sql new file mode 100644 index 000000000..944b94d76 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/cte/simple_cte.sql @@ -0,0 +1,5 @@ +-- @description A single common table expression +-- @queries +-- name: CheapBooks :many +WITH cheap AS (SELECT id, title FROM books WHERE price < @max_price) +SELECT id, title FROM cheap; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/alias_type.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/alias_type.sql new file mode 100644 index 000000000..9478a65c5 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/alias_type.sql @@ -0,0 +1,14 @@ +-- @description User-defined alias type (CREATE TYPE ... FROM) +-- @schema +CREATE TYPE dbo.email_address FROM NVARCHAR(320) NOT NULL; +GO +CREATE TABLE contacts ( + id INT IDENTITY PRIMARY KEY, + email dbo.email_address +); +-- @queries +-- name: ListContacts :many +SELECT id, email FROM contacts; + +-- name: GetContactByEmail :one +SELECT id FROM contacts WHERE email = @email; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/alter_column_type_and_nullability.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_column_type_and_nullability.sql new file mode 100644 index 000000000..1d4347c9b --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_column_type_and_nullability.sql @@ -0,0 +1,6 @@ +-- @description ALTER TABLE ... ALTER COLUMN changes the type and nullability +-- @schema +ALTER TABLE authors ALTER COLUMN bio NVARCHAR(500) NOT NULL; +-- @queries +-- name: ListBios :many +SELECT id, bio FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_add_column.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_add_column.sql new file mode 100644 index 000000000..d0b2390ca --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_add_column.sql @@ -0,0 +1,6 @@ +-- @description ALTER TABLE ... ADD column +-- @schema +ALTER TABLE authors ADD nickname NVARCHAR(50) NULL; +-- @queries +-- name: ListNicknames :many +SELECT id, nickname FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_drop_column.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_drop_column.sql new file mode 100644 index 000000000..8fa2f84d2 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_drop_column.sql @@ -0,0 +1,6 @@ +-- @description ALTER TABLE ... DROP COLUMN +-- @schema +ALTER TABLE authors DROP COLUMN birth_date; +-- @queries +-- name: ListAuthors :many +SELECT * FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/bracketed_identifiers.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/bracketed_identifiers.sql new file mode 100644 index 000000000..b8ca0ddb9 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/bracketed_identifiers.sql @@ -0,0 +1,10 @@ +-- @description Bracket-quoted table and column names with spaces and reserved words +-- @schema +CREATE TABLE [order details] ( + [order id] INT NOT NULL, + [select] NVARCHAR(10) NULL, + [Unit Price] MONEY NOT NULL +); +-- @queries +-- name: ListOrderDetails :many +SELECT [order id], [select], [Unit Price] FROM [order details]; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/case_insensitive_names.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/case_insensitive_names.sql new file mode 100644 index 000000000..e0ca94a6a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/case_insensitive_names.sql @@ -0,0 +1,4 @@ +-- @description Object names are case-insensitive under the default collation +-- @queries +-- name: ListAuthorsUpper :many +SELECT ID, NAME FROM AUTHORS WHERE NAME = @Name; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/check_and_unique_constraints.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/check_and_unique_constraints.sql new file mode 100644 index 000000000..6501a859f --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/check_and_unique_constraints.sql @@ -0,0 +1,13 @@ +-- @description CHECK and UNIQUE constraints, column and table level +-- @schema +CREATE TABLE products ( + id INT IDENTITY PRIMARY KEY, + sku VARCHAR(20) NOT NULL UNIQUE, + price DECIMAL(10, 2) NOT NULL CHECK (price >= 0), + discount DECIMAL(10, 2) NULL, + CONSTRAINT ck_products_discount CHECK (discount IS NULL OR discount <= price), + CONSTRAINT uq_products_sku_price UNIQUE (sku, price) +); +-- @queries +-- name: ListProducts :many +SELECT id, sku, price, discount FROM products; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/collate_and_sparse.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/collate_and_sparse.sql new file mode 100644 index 000000000..7372b3b52 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/collate_and_sparse.sql @@ -0,0 +1,10 @@ +-- @description COLLATE and SPARSE column options +-- @schema +CREATE TABLE tags ( + id INT IDENTITY PRIMARY KEY, + code VARCHAR(20) COLLATE Latin1_General_CS_AS NOT NULL, + note NVARCHAR(100) SPARSE NULL +); +-- @queries +-- name: ListTags :many +SELECT id, code, note FROM tags; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/computed_columns.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/computed_columns.sql new file mode 100644 index 000000000..e152febfa --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/computed_columns.sql @@ -0,0 +1,12 @@ +-- @description Computed columns, plain and PERSISTED +-- @schema +CREATE TABLE people ( + id INT IDENTITY PRIMARY KEY, + first_name NVARCHAR(50) NOT NULL, + last_name NVARCHAR(50) NOT NULL, + full_name AS (first_name + N' ' + last_name), + name_length AS (LEN(first_name) + LEN(last_name)) PERSISTED +); +-- @queries +-- name: ListPeople :many +SELECT * FROM people; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/drop_table_if_exists.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/drop_table_if_exists.sql new file mode 100644 index 000000000..43dfa3428 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/drop_table_if_exists.sql @@ -0,0 +1,8 @@ +-- @description DROP TABLE IF EXISTS followed by a re-create with a different shape +-- @schema +CREATE TABLE scratch (id INT NOT NULL); +DROP TABLE IF EXISTS scratch; +CREATE TABLE scratch (id BIGINT NOT NULL, label NVARCHAR(10) NOT NULL); +-- @queries +-- name: ListScratch :many +SELECT id, label FROM scratch; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/foreign_key_on_delete.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/foreign_key_on_delete.sql new file mode 100644 index 000000000..d3eb90b8d --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/foreign_key_on_delete.sql @@ -0,0 +1,12 @@ +-- @description Table-level FOREIGN KEY with ON DELETE CASCADE / SET NULL +-- @schema +CREATE TABLE reviews ( + id INT IDENTITY PRIMARY KEY, + book_id INT NOT NULL, + reviewer_id BIGINT NULL, + CONSTRAINT fk_reviews_book FOREIGN KEY (book_id) REFERENCES books (id) ON DELETE CASCADE, + CONSTRAINT fk_reviews_reviewer FOREIGN KEY (reviewer_id) REFERENCES authors (id) ON DELETE SET NULL +); +-- @queries +-- name: ListReviews :many +SELECT id, book_id, reviewer_id FROM reviews; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/go_batch_separators.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/go_batch_separators.sql new file mode 100644 index 000000000..5f3a6c770 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/go_batch_separators.sql @@ -0,0 +1,9 @@ +-- @description Schema split into several batches with GO +-- @schema +CREATE TABLE countries (id INT PRIMARY KEY, name NVARCHAR(50) NOT NULL); +GO +CREATE TABLE regions (id INT PRIMARY KEY, country_id INT NOT NULL REFERENCES countries (id), name NVARCHAR(50) NOT NULL); +GO +-- @queries +-- name: ListRegions :many +SELECT r.id, r.name, c.name AS country FROM regions r JOIN countries c ON c.id = r.country_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/identity_seed_increment.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/identity_seed_increment.sql new file mode 100644 index 000000000..9df344ccb --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/identity_seed_increment.sql @@ -0,0 +1,9 @@ +-- @description IDENTITY(seed, increment) column on a non-default seed +-- @schema +CREATE TABLE invoices ( + id INT IDENTITY(1000, 5) NOT NULL PRIMARY KEY, + number NVARCHAR(20) NOT NULL +); +-- @queries +-- name: ListInvoices :many +SELECT id, number FROM invoices; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/indexes.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/indexes.sql new file mode 100644 index 000000000..b38e22f77 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/indexes.sql @@ -0,0 +1,8 @@ +-- @description CREATE INDEX statements (filtered, INCLUDE, unique nonclustered) are accepted and ignored +-- @schema +CREATE NONCLUSTERED INDEX ix_books_title ON books (title) INCLUDE (price); +CREATE UNIQUE NONCLUSTERED INDEX ux_books_author_title ON books (author_id, title); +CREATE INDEX ix_books_available ON books (published_at) WHERE is_available = 1; +-- @queries +-- name: ListAvailableBooks :many +SELECT id, title FROM books WHERE is_available = 1; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/named_default_constraints.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/named_default_constraints.sql new file mode 100644 index 000000000..c7b76a361 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/named_default_constraints.sql @@ -0,0 +1,10 @@ +-- @description Named DEFAULT constraints with GETDATE() and NEWID() +-- @schema +CREATE TABLE events ( + id INT IDENTITY CONSTRAINT pk_events PRIMARY KEY, + created_at DATETIME2 NOT NULL CONSTRAINT df_events_created_at DEFAULT (SYSUTCDATETIME()), + correlation_id UNIQUEIDENTIFIER NOT NULL CONSTRAINT df_events_correlation DEFAULT (NEWID()) +); +-- @queries +-- name: ListEvents :many +SELECT id, created_at, correlation_id FROM events; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/rowversion.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/rowversion.sql new file mode 100644 index 000000000..56b6dc0a2 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/rowversion.sql @@ -0,0 +1,13 @@ +-- @description ROWVERSION column used for optimistic concurrency +-- @schema +CREATE TABLE documents ( + id INT IDENTITY PRIMARY KEY, + body NVARCHAR(MAX) NOT NULL, + version ROWVERSION NOT NULL +); +-- @queries +-- name: GetDocument :one +SELECT id, body, version FROM documents WHERE id = @id; + +-- name: UpdateDocument :execrows +UPDATE documents SET body = @body WHERE id = @id AND version = @version; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/same_table_name_two_schemas.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/same_table_name_two_schemas.sql new file mode 100644 index 000000000..8594fd639 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/same_table_name_two_schemas.sql @@ -0,0 +1,14 @@ +-- @description Two tables with the same name in different schemas +-- @schema +CREATE SCHEMA audit; +GO +CREATE TABLE audit.authors ( + id BIGINT NOT NULL, + changed_at DATETIME2 NOT NULL +); +-- @queries +-- name: ListAuditAuthors :many +SELECT id, changed_at FROM audit.authors; + +-- name: ListAuthors :many +SELECT id, name FROM dbo.authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/schema_scoped_table.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/schema_scoped_table.sql new file mode 100644 index 000000000..dd11a8a63 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/schema_scoped_table.sql @@ -0,0 +1,15 @@ +-- @description Table in a user-defined schema (CREATE SCHEMA in its own batch) +-- @schema +CREATE SCHEMA sales; +GO +CREATE TABLE sales.orders ( + id INT IDENTITY PRIMARY KEY, + author_id BIGINT NOT NULL, + total MONEY NOT NULL +); +-- @queries +-- name: ListOrders :many +SELECT id, author_id, total FROM sales.orders; + +-- name: ListOrdersWithAuthor :many +SELECT o.id, a.name FROM sales.orders o JOIN dbo.authors a ON a.id = o.author_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/sequence_default.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/sequence_default.sql new file mode 100644 index 000000000..656fc0462 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/sequence_default.sql @@ -0,0 +1,13 @@ +-- @description SEQUENCE used as a column default and with NEXT VALUE FOR in a query +-- @schema +CREATE SEQUENCE ticket_seq AS BIGINT START WITH 1 INCREMENT BY 1; +CREATE TABLE tickets ( + id BIGINT NOT NULL PRIMARY KEY DEFAULT (NEXT VALUE FOR ticket_seq), + title NVARCHAR(100) NOT NULL +); +-- @queries +-- name: ListTickets :many +SELECT id, title FROM tickets; + +-- name: NextTicketId :one +SELECT NEXT VALUE FOR ticket_seq AS next_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/sp_rename_column.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/sp_rename_column.sql new file mode 100644 index 000000000..a4d814652 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/sp_rename_column.sql @@ -0,0 +1,6 @@ +-- @description Column renamed with sp_rename (how T-SQL renames columns) +-- @schema +EXEC sp_rename 'authors.bio', 'biography', 'COLUMN'; +-- @queries +-- name: ListBiographies :many +SELECT id, biography FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/synonym.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/synonym.sql new file mode 100644 index 000000000..582c7d863 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/synonym.sql @@ -0,0 +1,6 @@ +-- @description CREATE SYNONYM for a table +-- @schema +CREATE SYNONYM writers FOR dbo.authors; +-- @queries +-- name: ListWriters :many +SELECT id, name FROM writers; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/table_level_primary_key.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/table_level_primary_key.sql new file mode 100644 index 000000000..eb8bc60c3 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/table_level_primary_key.sql @@ -0,0 +1,11 @@ +-- @description Columns in a table-level (composite) PRIMARY KEY are implicitly NOT NULL +-- @schema +CREATE TABLE memberships ( + group_id INT, + user_id INT, + role NVARCHAR(20), + CONSTRAINT pk_memberships PRIMARY KEY (group_id, user_id) +); +-- @queries +-- name: ListMemberships :many +SELECT group_id, user_id, role FROM memberships; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/temporal_table.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/temporal_table.sql new file mode 100644 index 000000000..aa16365a9 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/temporal_table.sql @@ -0,0 +1,12 @@ +-- @description System-versioned temporal table and FOR SYSTEM_TIME AS OF +-- @schema +CREATE TABLE prices ( + id INT NOT NULL PRIMARY KEY CLUSTERED, + amount DECIMAL(10, 2) NOT NULL, + valid_from DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL, + valid_to DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL, + PERIOD FOR SYSTEM_TIME (valid_from, valid_to) +) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.prices_history)); +-- @queries +-- name: ListPricesAsOf :many +SELECT id, amount FROM prices FOR SYSTEM_TIME AS OF @as_of; diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/view.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/view.sql new file mode 100644 index 000000000..5cb24333e --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/ddl/view.sql @@ -0,0 +1,8 @@ +-- @description Querying a VIEW +-- @schema +CREATE VIEW author_books AS +SELECT a.name AS author_name, b.title +FROM authors a JOIN books b ON b.author_id = a.id; +-- @queries +-- name: ListAuthorBooks :many +SELECT author_name, title FROM author_books WHERE author_name = @author_name; diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/from_join.sql b/end2end/MssqlSyntaxMatrix/cases/delete/from_join.sql new file mode 100644 index 000000000..a30f42b8b --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/delete/from_join.sql @@ -0,0 +1,4 @@ +-- @description DELETE ... FROM with a JOIN +-- @queries +-- name: DeleteBooksByAuthorName :execrows +DELETE b FROM books b JOIN authors a ON a.id = b.author_id WHERE a.name = @author_name; diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/output_deleted.sql b/end2end/MssqlSyntaxMatrix/cases/delete/output_deleted.sql new file mode 100644 index 000000000..620bf848d --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/delete/output_deleted.sql @@ -0,0 +1,4 @@ +-- @description DELETE ... OUTPUT DELETED.* +-- @queries +-- name: DeleteBooksOfAuthor :many +DELETE FROM books OUTPUT DELETED.* WHERE author_id = @author_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/simple.sql b/end2end/MssqlSyntaxMatrix/cases/delete/simple.sql new file mode 100644 index 000000000..876af0f5f --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/delete/simple.sql @@ -0,0 +1,7 @@ +-- @description DELETE with a parameter +-- @queries +-- name: DeleteAuthor :exec +DELETE FROM authors WHERE id = @id; + +-- name: DeleteAuthorNoFrom :exec +DELETE authors WHERE id = @id; diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/top.sql b/end2end/MssqlSyntaxMatrix/cases/delete/top.sql new file mode 100644 index 000000000..201cfeef3 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/delete/top.sql @@ -0,0 +1,4 @@ +-- @description DELETE TOP (@n) +-- @queries +-- name: DeleteSomeBooks :execrows +DELETE TOP (@n) FROM books WHERE is_available = 0; diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/truncate.sql b/end2end/MssqlSyntaxMatrix/cases/delete/truncate.sql new file mode 100644 index 000000000..b3bec23f1 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/delete/truncate.sql @@ -0,0 +1,6 @@ +-- @description TRUNCATE TABLE +-- @schema +CREATE TABLE books_archive (id INT NOT NULL); +-- @queries +-- name: TruncateBooksArchive :exec +TRUNCATE TABLE books_archive; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/arithmetic.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/arithmetic.sql new file mode 100644 index 000000000..9ba3e30b8 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/arithmetic.sql @@ -0,0 +1,4 @@ +-- @description Arithmetic operators, integer division, modulo and type promotion +-- @queries +-- name: Arithmetic :many +SELECT id, price * 2 AS doubled, id / 2 AS half_id, id % 3 AS remainder, price + @delta AS adjusted, -id AS negated FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/at_time_zone.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/at_time_zone.sql new file mode 100644 index 000000000..f750f9cb9 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/at_time_zone.sql @@ -0,0 +1,6 @@ +-- @description AT TIME ZONE and SWITCHOFFSET +-- @queries +-- name: InTimeZone :many +SELECT published_at AT TIME ZONE 'UTC' AT TIME ZONE @time_zone AS local_time, + SWITCHOFFSET(SYSDATETIMEOFFSET(), '+02:00') AS switched +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/bitwise.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/bitwise.sql new file mode 100644 index 000000000..06e9c3561 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/bitwise.sql @@ -0,0 +1,4 @@ +-- @description Bitwise operators and bit columns +-- @queries +-- name: Bitwise :many +SELECT id, id & 1 AS low_bit, id | @mask AS masked, id ^ 2 AS xored, ~is_available AS not_available FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/cast_convert.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/cast_convert.sql new file mode 100644 index 000000000..8917cc06a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/cast_convert.sql @@ -0,0 +1,10 @@ +-- @description CAST, CONVERT (with a style), TRY_CAST, TRY_CONVERT, PARSE and TRY_PARSE +-- @queries +-- name: Conversions :many +SELECT CAST(id AS NVARCHAR(20)) AS id_text, + CONVERT(VARCHAR(10), published_at, 23) AS iso_date, + TRY_CAST(title AS INT) AS title_number, + TRY_CONVERT(DECIMAL(10, 2), @amount) AS amount, + PARSE(N'2024-01-02' AS DATE USING 'en-US') AS parsed, + TRY_PARSE(@text AS DATETIME2) AS maybe_date +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/coalesce_isnull_nullif.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/coalesce_isnull_nullif.sql new file mode 100644 index 000000000..8076175e4 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/coalesce_isnull_nullif.sql @@ -0,0 +1,10 @@ +-- @description COALESCE, ISNULL and NULLIF: result types, nullability, and parameter inference +-- @queries +-- name: Fallbacks :many +SELECT id, COALESCE(bio, name) AS bio_or_name, ISNULL(bio, N'') AS bio_or_empty, NULLIF(name, N'') AS name_or_null FROM authors; + +-- name: ByPattern :many +SELECT id, name FROM authors WHERE name LIKE COALESCE(@pattern, N'%'); + +-- name: ByPatternIsNull :many +SELECT id, name FROM authors WHERE name LIKE ISNULL(@pattern, N'%'); diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/concat_format.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/concat_format.sql new file mode 100644 index 000000000..32149c7d7 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/concat_format.sql @@ -0,0 +1,6 @@ +-- @description CONCAT, CONCAT_WS, FORMAT, QUOTENAME, TRANSLATE +-- @queries +-- name: Formatting :many +SELECT CONCAT(name, N' (', id, N')') AS label, CONCAT_WS(N', ', name, bio) AS joined, + FORMAT(birth_date, 'yyyy-MM-dd') AS formatted, QUOTENAME(name) AS quoted, TRANSLATE(name, N'abc', N'xyz') AS translated +FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/datetime_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/datetime_functions.sql new file mode 100644 index 000000000..df18c1ff9 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/datetime_functions.sql @@ -0,0 +1,9 @@ +-- @description GETDATE, SYSDATETIME, SYSDATETIMEOFFSET, DATEADD, DATEDIFF, DATEDIFF_BIG, DATEPART, DATENAME, EOMONTH, DATEFROMPARTS, DATETRUNC +-- @queries +-- name: DateFunctions :many +SELECT GETDATE() AS now_local, GETUTCDATE() AS now_utc, SYSDATETIME() AS precise, SYSDATETIMEOFFSET() AS with_offset, + DATEADD(DAY, @days, published_at) AS shifted, DATEDIFF(DAY, published_at, @until) AS days_between, + DATEDIFF_BIG(SECOND, published_at, SYSUTCDATETIME()) AS seconds_since, DATEPART(YEAR, published_at) AS year_part, + DATENAME(MONTH, published_at) AS month_name, YEAR(published_at) AS y, EOMONTH(published_at) AS month_end, + DATEFROMPARTS(2024, 1, 31) AS fixed_date, DATETRUNC(MONTH, published_at) AS month_start +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/greatest_least.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/greatest_least.sql new file mode 100644 index 000000000..95a79e3c4 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/greatest_least.sql @@ -0,0 +1,4 @@ +-- @description GREATEST and LEAST (SQL Server 2022) +-- @queries +-- name: Clamp :many +SELECT id, GREATEST(price, @floor) AS at_least, LEAST(price, @ceiling) AS at_most FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/is_distinct_from.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/is_distinct_from.sql new file mode 100644 index 000000000..30f45934e --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/is_distinct_from.sql @@ -0,0 +1,4 @@ +-- @description IS [NOT] DISTINCT FROM (SQL Server 2022) +-- @queries +-- name: ChangedBios :many +SELECT id FROM authors WHERE bio IS DISTINCT FROM @bio; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/math_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/math_functions.sql new file mode 100644 index 000000000..2b7d19709 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/math_functions.sql @@ -0,0 +1,6 @@ +-- @description ABS, ROUND, CEILING, FLOOR, POWER, SQRT, SIGN, PI, RAND +-- @queries +-- name: MathFunctions :many +SELECT ABS(price) AS abs_price, ROUND(price, 1) AS rounded, CEILING(price) AS ceil, FLOOR(price) AS floor_price, + POWER(id, 2) AS squared, SQRT(id) AS root, SIGN(price) AS sign, PI() AS pi, RAND() AS random +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/sql2022_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/sql2022_functions.sql new file mode 100644 index 000000000..2f57bc15c --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/sql2022_functions.sql @@ -0,0 +1,7 @@ +-- @description GENERATE_SERIES, DATE_BUCKET, TRIM ... FROM and STRING_ESCAPE (SQL Server 2022) +-- @queries +-- name: Series :many +SELECT value FROM GENERATE_SERIES(1, @upto); + +-- name: NewFunctions :many +SELECT DATE_BUCKET(WEEK, 1, published_at) AS week_bucket, TRIM(N'x' FROM title) AS trimmed, STRING_ESCAPE(title, 'json') AS escaped FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/string_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/string_functions.sql new file mode 100644 index 000000000..f201a85ae --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/string_functions.sql @@ -0,0 +1,8 @@ +-- @description LEN, DATALENGTH, SUBSTRING, LEFT, RIGHT, UPPER, LOWER, TRIM, REPLACE, CHARINDEX, PATINDEX, REVERSE, REPLICATE, STUFF +-- @queries +-- name: StringFunctions :many +SELECT LEN(name) AS len, DATALENGTH(name) AS bytes, SUBSTRING(name, 1, 3) AS prefix, LEFT(name, 2) AS left2, RIGHT(name, 2) AS right2, + UPPER(name) AS upper_name, LOWER(name) AS lower_name, TRIM(name) AS trimmed, LTRIM(RTRIM(name)) AS trimmed2, + REPLACE(name, N'a', N'b') AS replaced, CHARINDEX(N'a', name) AS pos, PATINDEX(N'%[0-9]%', name) AS digit_pos, + REVERSE(name) AS reversed, REPLICATE(N'*', 3) AS stars, STUFF(name, 1, 1, N'X') AS stuffed +FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/system_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/system_functions.sql new file mode 100644 index 000000000..215c33a9e --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/expressions/system_functions.sql @@ -0,0 +1,5 @@ +-- @description NEWID, @@ROWCOUNT, SCOPE_IDENTITY, IDENT_CURRENT, DB_NAME, SUSER_SNAME, HASHBYTES, CHECKSUM +-- @queries +-- name: SystemValues :one +SELECT NEWID() AS id, @@ROWCOUNT AS row_count, SCOPE_IDENTITY() AS scope_id, IDENT_CURRENT('authors') AS current_id, + DB_NAME() AS db, SUSER_SNAME() AS login, HASHBYTES('SHA2_256', @payload) AS hash, CHECKSUM(@payload) AS checksum; diff --git a/end2end/MssqlSyntaxMatrix/cases/hints/query_hints.sql b/end2end/MssqlSyntaxMatrix/cases/hints/query_hints.sql new file mode 100644 index 000000000..1a4dfad1c --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/hints/query_hints.sql @@ -0,0 +1,7 @@ +-- @description OPTION (RECOMPILE), OPTION (MAXDOP 1), OPTION (OPTIMIZE FOR UNKNOWN) +-- @queries +-- name: Recompiled :many +SELECT id FROM books WHERE author_id = @author_id OPTION (RECOMPILE); + +-- name: SingleThreaded :many +SELECT id FROM books WHERE price > @min_price OPTION (MAXDOP 1, OPTIMIZE FOR UNKNOWN); diff --git a/end2end/MssqlSyntaxMatrix/cases/hints/table_hints.sql b/end2end/MssqlSyntaxMatrix/cases/hints/table_hints.sql new file mode 100644 index 000000000..d64ba706e --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/hints/table_hints.sql @@ -0,0 +1,10 @@ +-- @description Table hints: NOLOCK, UPDLOCK, ROWLOCK, READPAST, FORCESEEK +-- @queries +-- name: ReadUncommitted :many +SELECT id FROM authors WITH (NOLOCK); + +-- name: LockForUpdate :one +SELECT id, name FROM authors WITH (UPDLOCK, ROWLOCK) WHERE id = @id; + +-- name: DequeueLike :one +SELECT TOP 1 id FROM books WITH (READPAST, UPDLOCK) WHERE is_available = 1; diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/at_sign_parameter_names.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/at_sign_parameter_names.sql new file mode 100644 index 000000000..90c2136b2 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/at_sign_parameter_names.sql @@ -0,0 +1,4 @@ +-- @description Parameter names with digits, underscores and mixed case +-- @queries +-- name: OddParams :many +SELECT id FROM authors WHERE id = @Id1 OR name = @first_Name OR bio = @_bio; diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/duplicate_result_names.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/duplicate_result_names.sql new file mode 100644 index 000000000..804f99588 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/duplicate_result_names.sql @@ -0,0 +1,4 @@ +-- @description Two result columns with the same name +-- @queries +-- name: DuplicateIds :many +SELECT a.id, b.id FROM authors a JOIN books b ON b.author_id = a.id; diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/mixed_case_names.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/mixed_case_names.sql new file mode 100644 index 000000000..3bb8c99dc --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/mixed_case_names.sql @@ -0,0 +1,6 @@ +-- @description Mixed-case table and column names map to PascalCase +-- @schema +CREATE TABLE CustomerAccounts (CustomerID INT NOT NULL PRIMARY KEY, firstName NVARCHAR(50) NOT NULL, LAST_NAME NVARCHAR(50) NOT NULL); +-- @queries +-- name: ListMixed :many +SELECT CustomerID, firstName, LAST_NAME FROM CustomerAccounts WHERE CustomerID = @CustomerID; diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/non_identifier_column_names.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/non_identifier_column_names.sql new file mode 100644 index 000000000..28eeafb36 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/non_identifier_column_names.sql @@ -0,0 +1,6 @@ +-- @description Column names that are not valid C# identifiers: leading digits, spaces, dashes, Unicode +-- @schema +CREATE TABLE odd_names ([1st place] INT NULL, [e-mail] NVARCHAR(50) NULL, [naïve] BIT NULL, [Total $] MONEY NULL); +-- @queries +-- name: ListOdd :many +SELECT [1st place], [e-mail], [naïve], [Total $] FROM odd_names; diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/reserved_word_columns.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/reserved_word_columns.sql new file mode 100644 index 000000000..df2f31a70 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/reserved_word_columns.sql @@ -0,0 +1,6 @@ +-- @description Columns named after T-SQL and C# keywords +-- @schema +CREATE TABLE keywords ([user] NVARCHAR(20) NOT NULL, [order] INT NOT NULL, [key] INT NULL, [class] INT NULL, [string] NVARCHAR(10) NULL); +-- @queries +-- name: ListKeywords :many +SELECT [user], [order], [key], [class], [string] FROM keywords WHERE [user] = @user; diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/unnamed_expression_column.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/unnamed_expression_column.sql new file mode 100644 index 000000000..89f4a7948 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/unnamed_expression_column.sql @@ -0,0 +1,4 @@ +-- @description A result column without a name +-- @queries +-- name: Unnamed :one +SELECT COUNT(*) FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/default_values.sql b/end2end/MssqlSyntaxMatrix/cases/insert/default_values.sql new file mode 100644 index 000000000..167bc9d45 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/insert/default_values.sql @@ -0,0 +1,9 @@ +-- @description INSERT ... DEFAULT VALUES and explicit DEFAULT +-- @schema +CREATE TABLE events (id INT IDENTITY PRIMARY KEY, created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()); +-- @queries +-- name: CreateEmptyEvent :exec +INSERT INTO events DEFAULT VALUES; + +-- name: CreateEventWithDefault :exec +INSERT INTO events (created_at) VALUES (DEFAULT); diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/identity_insert.sql b/end2end/MssqlSyntaxMatrix/cases/insert/identity_insert.sql new file mode 100644 index 000000000..650441341 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/insert/identity_insert.sql @@ -0,0 +1,6 @@ +-- @description Explicit values for an IDENTITY column with SET IDENTITY_INSERT +-- @queries +-- name: CreateAuthorWithId :exec +SET IDENTITY_INSERT authors ON; +INSERT INTO authors (id, name) VALUES (@id, @name); +SET IDENTITY_INSERT authors OFF; diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/insert_exec.sql b/end2end/MssqlSyntaxMatrix/cases/insert/insert_exec.sql new file mode 100644 index 000000000..f61acb495 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/insert/insert_exec.sql @@ -0,0 +1,9 @@ +-- @description INSERT ... EXEC from a stored procedure +-- @schema +CREATE TABLE author_archive (id BIGINT NOT NULL, name NVARCHAR(100) NOT NULL); +GO +CREATE PROCEDURE dbo.list_author_names AS SELECT id, name FROM authors; +GO +-- @queries +-- name: ArchiveAuthors :execrows +INSERT INTO author_archive (id, name) EXEC dbo.list_author_names; diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/insert_select.sql b/end2end/MssqlSyntaxMatrix/cases/insert/insert_select.sql new file mode 100644 index 000000000..0d3cf7cf2 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/insert/insert_select.sql @@ -0,0 +1,4 @@ +-- @description INSERT ... SELECT with a parameter +-- @queries +-- name: CopyBooksToAuthor :execrows +INSERT INTO books (author_id, title, price) SELECT @target_author_id, title, price FROM books WHERE author_id = @source_author_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/multi_row_values.sql b/end2end/MssqlSyntaxMatrix/cases/insert/multi_row_values.sql new file mode 100644 index 000000000..d603a0e9a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/insert/multi_row_values.sql @@ -0,0 +1,4 @@ +-- @description INSERT with several VALUES rows +-- @queries +-- name: CreateTwoAuthors :exec +INSERT INTO authors (name, bio) VALUES (@first_name, @first_bio), (@second_name, @second_bio); diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/output_inserted.sql b/end2end/MssqlSyntaxMatrix/cases/insert/output_inserted.sql new file mode 100644 index 000000000..d16498968 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/insert/output_inserted.sql @@ -0,0 +1,7 @@ +-- @description INSERT ... OUTPUT INSERTED.* / INSERTED.col returning rows +-- @queries +-- name: CreateAuthorReturningAll :one +INSERT INTO authors (name, bio) OUTPUT INSERTED.* VALUES (@name, @bio); + +-- name: CreateAuthorReturningSome :one +INSERT INTO authors (name) OUTPUT INSERTED.id, INSERTED.name VALUES (@name); diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/output_into_table_variable.sql b/end2end/MssqlSyntaxMatrix/cases/insert/output_into_table_variable.sql new file mode 100644 index 000000000..3abff4c77 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/insert/output_into_table_variable.sql @@ -0,0 +1,6 @@ +-- @description OUTPUT ... INTO a table variable inside a multi-statement query +-- @queries +-- name: CreateAuthorOutputInto :one +DECLARE @ids TABLE (id BIGINT); +INSERT INTO authors (name) OUTPUT INSERTED.id INTO @ids VALUES (@name); +SELECT id FROM @ids; diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/schema_scoped.sql b/end2end/MssqlSyntaxMatrix/cases/insert/schema_scoped.sql new file mode 100644 index 000000000..d3750c8f0 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/insert/schema_scoped.sql @@ -0,0 +1,8 @@ +-- @description INSERT into a table in a user-defined schema +-- @schema +CREATE SCHEMA logging; +GO +CREATE TABLE logging.entries (id INT IDENTITY PRIMARY KEY, message NVARCHAR(200) NOT NULL); +-- @queries +-- name: CreateLog :exec +INSERT INTO logging.entries (message) VALUES (@message); diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/values.sql b/end2end/MssqlSyntaxMatrix/cases/insert/values.sql new file mode 100644 index 000000000..e752b0f73 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/insert/values.sql @@ -0,0 +1,4 @@ +-- @description INSERT ... VALUES with parameters for every column +-- @queries +-- name: CreateAuthor :exec +INSERT INTO authors (name, bio, birth_date) VALUES (@name, @bio, @birth_date); diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/cross_apply.sql b/end2end/MssqlSyntaxMatrix/cases/joins/cross_apply.sql new file mode 100644 index 000000000..3b6156f0c --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/joins/cross_apply.sql @@ -0,0 +1,9 @@ +-- @description CROSS APPLY and OUTER APPLY with a correlated TOP subquery +-- @queries +-- name: LatestBookPerAuthor :many +SELECT a.id, latest.title FROM authors a +CROSS APPLY (SELECT TOP 1 b.title FROM books b WHERE b.author_id = a.id ORDER BY b.published_at DESC) AS latest; + +-- name: LatestBookPerAuthorOuter :many +SELECT a.id, latest.title FROM authors a +OUTER APPLY (SELECT TOP 1 b.title FROM books b WHERE b.author_id = a.id ORDER BY b.published_at DESC) AS latest; diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/cross_join.sql b/end2end/MssqlSyntaxMatrix/cases/joins/cross_join.sql new file mode 100644 index 000000000..2c1658755 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/joins/cross_join.sql @@ -0,0 +1,7 @@ +-- @description CROSS JOIN and the comma join +-- @queries +-- name: CrossJoin :many +SELECT a.id AS author_id, b.id AS book_id FROM authors a CROSS JOIN books b; + +-- name: CommaJoin :many +SELECT a.id AS author_id, b.id AS book_id FROM authors a, books b WHERE a.id = b.author_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/inner_left_right_full.sql b/end2end/MssqlSyntaxMatrix/cases/joins/inner_left_right_full.sql new file mode 100644 index 000000000..486e7c93d --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/joins/inner_left_right_full.sql @@ -0,0 +1,13 @@ +-- @description INNER, LEFT, RIGHT and FULL joins and the nullability they introduce +-- @queries +-- name: InnerJoin :many +SELECT a.name, b.title FROM authors a INNER JOIN books b ON b.author_id = a.id; + +-- name: LeftJoin :many +SELECT a.name, b.title, b.price FROM authors a LEFT JOIN books b ON b.author_id = a.id; + +-- name: RightJoin :many +SELECT a.name, b.title FROM authors a RIGHT OUTER JOIN books b ON b.author_id = a.id; + +-- name: FullJoin :many +SELECT a.name, b.title FROM authors a FULL OUTER JOIN books b ON b.author_id = a.id; diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/join_derived_table.sql b/end2end/MssqlSyntaxMatrix/cases/joins/join_derived_table.sql new file mode 100644 index 000000000..33b6ee5d6 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/joins/join_derived_table.sql @@ -0,0 +1,5 @@ +-- @description Join to an aggregated derived table +-- @queries +-- name: AuthorsWithTotals :many +SELECT a.name, t.total FROM authors a +JOIN (SELECT author_id, SUM(price) AS total FROM books GROUP BY author_id) t ON t.author_id = a.id; diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/schema_qualified_join.sql b/end2end/MssqlSyntaxMatrix/cases/joins/schema_qualified_join.sql new file mode 100644 index 000000000..257feb9a7 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/joins/schema_qualified_join.sql @@ -0,0 +1,4 @@ +-- @description Join across dbo-qualified and bracket-quoted names +-- @queries +-- name: QualifiedJoin :many +SELECT [a].[name], [b].[title] FROM [dbo].[authors] AS [a] JOIN dbo.books AS b ON b.author_id = [a].[id]; diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/self_join.sql b/end2end/MssqlSyntaxMatrix/cases/joins/self_join.sql new file mode 100644 index 000000000..a1004d0ad --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/joins/self_join.sql @@ -0,0 +1,4 @@ +-- @description Self join with aliases +-- @queries +-- name: SameNameAuthors :many +SELECT a1.id AS first_id, a2.id AS second_id FROM authors a1 JOIN authors a2 ON a1.name = a2.name AND a1.id < a2.id; diff --git a/end2end/MssqlSyntaxMatrix/cases/json/for_json.sql b/end2end/MssqlSyntaxMatrix/cases/json/for_json.sql new file mode 100644 index 000000000..91f28b80a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/json/for_json.sql @@ -0,0 +1,7 @@ +-- @description FOR JSON PATH / AUTO producing a single JSON column +-- @queries +-- name: AuthorsAsJson :one +SELECT id, name FROM authors FOR JSON PATH; + +-- name: AuthorAsJsonObject :one +SELECT id, name FROM authors WHERE id = @id FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER; diff --git a/end2end/MssqlSyntaxMatrix/cases/json/json_object_array.sql b/end2end/MssqlSyntaxMatrix/cases/json/json_object_array.sql new file mode 100644 index 000000000..da0b7f2a8 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/json/json_object_array.sql @@ -0,0 +1,4 @@ +-- @description JSON_OBJECT and JSON_ARRAY constructors (SQL Server 2022) +-- @queries +-- name: AuthorJson :many +SELECT JSON_OBJECT('id': id, 'name': name) AS obj, JSON_ARRAY(id, name) AS arr FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/json/json_value_query.sql b/end2end/MssqlSyntaxMatrix/cases/json/json_value_query.sql new file mode 100644 index 000000000..f51610567 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/json/json_value_query.sql @@ -0,0 +1,8 @@ +-- @description JSON_VALUE, JSON_QUERY, ISJSON and JSON_MODIFY on an NVARCHAR(MAX) column +-- @schema +CREATE TABLE settings (id INT IDENTITY PRIMARY KEY, payload NVARCHAR(MAX) NOT NULL CHECK (ISJSON(payload) = 1)); +-- @queries +-- name: JsonAccess :many +SELECT id, JSON_VALUE(payload, '$.name') AS name, JSON_QUERY(payload, '$.tags') AS tags, ISJSON(payload) AS is_json, + JSON_MODIFY(payload, '$.name', @new_name) AS renamed +FROM settings WHERE JSON_VALUE(payload, '$.kind') = @kind; diff --git a/end2end/MssqlSyntaxMatrix/cases/json/openjson.sql b/end2end/MssqlSyntaxMatrix/cases/json/openjson.sql new file mode 100644 index 000000000..cbc2cbaf6 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/json/openjson.sql @@ -0,0 +1,7 @@ +-- @description OPENJSON with an explicit WITH schema, fed by a parameter +-- @queries +-- name: ParseItems :many +SELECT item_id, quantity FROM OPENJSON(@items) WITH (item_id INT '$.id', quantity INT '$.qty'); + +-- name: AuthorsByJsonIds :many +SELECT id, name FROM authors WHERE id IN (SELECT CAST(value AS BIGINT) FROM OPENJSON(@ids)); diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/arg_forms.sql b/end2end/MssqlSyntaxMatrix/cases/macros/arg_forms.sql new file mode 100644 index 000000000..d6b5aaf39 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/macros/arg_forms.sql @@ -0,0 +1,4 @@ +-- @description sqlc.arg with a quoted name, a bare identifier and a double-quoted name +-- @queries +-- name: ArgForms :many +SELECT id FROM authors WHERE name = sqlc.arg('author_name') AND id > sqlc.arg(min_id) AND bio <> sqlc.arg("bio_text"); diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/arg_in_cast.sql b/end2end/MssqlSyntaxMatrix/cases/macros/arg_in_cast.sql new file mode 100644 index 000000000..3fc85a6e9 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/macros/arg_in_cast.sql @@ -0,0 +1,4 @@ +-- @description sqlc.arg inside CAST, which fixes its type +-- @queries +-- name: CastArg :many +SELECT id FROM books WHERE price > CAST(sqlc.arg('min_price') AS DECIMAL(10, 2)); diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/args_in_top_offset.sql b/end2end/MssqlSyntaxMatrix/cases/macros/args_in_top_offset.sql new file mode 100644 index 000000000..69d429131 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/macros/args_in_top_offset.sql @@ -0,0 +1,7 @@ +-- @description sqlc.arg in TOP and in OFFSET / FETCH +-- @queries +-- name: TopArg :many +SELECT TOP (sqlc.arg('take')) id FROM authors ORDER BY id; + +-- name: PageArgs :many +SELECT id FROM authors ORDER BY id OFFSET sqlc.arg('skip') ROWS FETCH NEXT sqlc.arg('take') ROWS ONLY; diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/embed.sql b/end2end/MssqlSyntaxMatrix/cases/macros/embed.sql new file mode 100644 index 000000000..5cb172d80 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/macros/embed.sql @@ -0,0 +1,10 @@ +-- @description sqlc.embed of a table, of aliases (self join) and next to a t.* star +-- @queries +-- name: AuthorsWithBooks :many +SELECT sqlc.embed(authors), sqlc.embed(books) FROM authors JOIN books ON books.author_id = authors.id; + +-- name: DuplicateAuthors :many +SELECT sqlc.embed(a1), sqlc.embed(a2) FROM authors a1 JOIN authors a2 ON a1.name = a2.name AND a1.id < a2.id; + +-- name: BooksWithAuthor :many +SELECT books.*, sqlc.embed(authors) FROM books JOIN authors ON authors.id = books.author_id WHERE books.title = @title; diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/embed_schema_scoped.sql b/end2end/MssqlSyntaxMatrix/cases/macros/embed_schema_scoped.sql new file mode 100644 index 000000000..0f9604fd5 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/macros/embed_schema_scoped.sql @@ -0,0 +1,8 @@ +-- @description sqlc.embed of a table in a user-defined schema +-- @schema +CREATE SCHEMA sales; +GO +CREATE TABLE sales.orders (id INT IDENTITY PRIMARY KEY, author_id BIGINT NOT NULL, total MONEY NOT NULL); +-- @queries +-- name: OrdersWithAuthor :many +SELECT sqlc.embed(orders), sqlc.embed(authors) FROM sales.orders JOIN authors ON authors.id = orders.author_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/mixed_with_at_params.sql b/end2end/MssqlSyntaxMatrix/cases/macros/mixed_with_at_params.sql new file mode 100644 index 000000000..c98bb85bc --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/macros/mixed_with_at_params.sql @@ -0,0 +1,4 @@ +-- @description sqlc.arg mixed with plain @params, sharing a name +-- @queries +-- name: Mixed :many +SELECT id FROM authors WHERE name = sqlc.arg('name') AND (bio = @bio OR bio = @name); diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/narg.sql b/end2end/MssqlSyntaxMatrix/cases/macros/narg.sql new file mode 100644 index 000000000..ea3b8ab77 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/macros/narg.sql @@ -0,0 +1,4 @@ +-- @description sqlc.narg makes a parameter nullable +-- @queries +-- name: NargFilter :many +SELECT id FROM authors WHERE name = sqlc.narg('name') OR sqlc.narg('name') IS NULL; diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/slice.sql b/end2end/MssqlSyntaxMatrix/cases/macros/slice.sql new file mode 100644 index 000000000..afcac218d --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/macros/slice.sql @@ -0,0 +1,13 @@ +-- @description sqlc.slice for integers, strings, NOT IN and several slices in one query +-- @queries +-- name: ByIds :many +SELECT id, name FROM authors WHERE id IN (sqlc.slice('ids')); + +-- name: ByNames :many +SELECT id FROM authors WHERE name IN (sqlc.slice('names')); + +-- name: NotByIds :many +SELECT id FROM authors WHERE id NOT IN (sqlc.slice('excluded')); + +-- name: ByIdsAndTitles :many +SELECT id FROM books WHERE author_id IN (sqlc.slice('author_ids')) AND title IN (sqlc.slice('titles')); diff --git a/end2end/MssqlSyntaxMatrix/cases/merge/merge_delete.sql b/end2end/MssqlSyntaxMatrix/cases/merge/merge_delete.sql new file mode 100644 index 000000000..58b045a53 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/merge/merge_delete.sql @@ -0,0 +1,9 @@ +-- @description MERGE with WHEN NOT MATCHED BY SOURCE THEN DELETE +-- @schema +CREATE TABLE tags (name NVARCHAR(50) NOT NULL PRIMARY KEY); +-- @queries +-- name: SyncTags :execrows +MERGE INTO tags AS t +USING (SELECT value AS name FROM STRING_SPLIT(@names, ',')) AS s ON t.name = s.name +WHEN NOT MATCHED BY TARGET THEN INSERT (name) VALUES (s.name) +WHEN NOT MATCHED BY SOURCE THEN DELETE; diff --git a/end2end/MssqlSyntaxMatrix/cases/merge/merge_upsert.sql b/end2end/MssqlSyntaxMatrix/cases/merge/merge_upsert.sql new file mode 100644 index 000000000..a187fcf6e --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/merge/merge_upsert.sql @@ -0,0 +1,9 @@ +-- @description MERGE as an upsert, with OUTPUT $action +-- @queries +-- name: UpsertAuthor :one +MERGE INTO authors AS target +USING (VALUES (@name, @bio)) AS source (name, bio) +ON target.name = source.name +WHEN MATCHED THEN UPDATE SET bio = source.bio +WHEN NOT MATCHED THEN INSERT (name, bio) VALUES (source.name, source.bio) +OUTPUT $action AS action, INSERTED.id; diff --git a/end2end/MssqlSyntaxMatrix/cases/merge/upsert_without_merge.sql b/end2end/MssqlSyntaxMatrix/cases/merge/upsert_without_merge.sql new file mode 100644 index 000000000..3893fbc4c --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/merge/upsert_without_merge.sql @@ -0,0 +1,6 @@ +-- @description The UPDATE-then-INSERT upsert pattern with @@ROWCOUNT +-- @queries +-- name: UpsertBio :exec +UPDATE authors SET bio = @bio WHERE name = @name; +IF @@ROWCOUNT = 0 + INSERT INTO authors (name, bio) VALUES (@name, @bio); diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/exec_procedure.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/exec_procedure.sql new file mode 100644 index 000000000..d21e35e95 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/programmability/exec_procedure.sql @@ -0,0 +1,10 @@ +-- @description Calling a stored procedure with EXEC and named arguments +-- @schema +CREATE PROCEDURE dbo.get_author @id BIGINT AS +BEGIN + SELECT id, name FROM authors WHERE id = @id; +END +GO +-- @queries +-- name: GetAuthorViaProc :many +EXEC dbo.get_author @id = @id; diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/inline_table_function.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/inline_table_function.sql new file mode 100644 index 000000000..19cf35243 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/programmability/inline_table_function.sql @@ -0,0 +1,8 @@ +-- @description An inline table-valued function in FROM +-- @schema +CREATE FUNCTION dbo.books_of (@author_id BIGINT) RETURNS TABLE AS +RETURN (SELECT id, title FROM books WHERE author_id = @author_id); +GO +-- @queries +-- name: BooksOf :many +SELECT id, title FROM dbo.books_of(@author_id); diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/multi_statement_table_function.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/multi_statement_table_function.sql new file mode 100644 index 000000000..7e9fd687d --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/programmability/multi_statement_table_function.sql @@ -0,0 +1,12 @@ +-- @description A multi-statement table-valued function in FROM +-- @schema +CREATE FUNCTION dbo.range_of (@upto INT) RETURNS @result TABLE (n INT NOT NULL) AS +BEGIN + DECLARE @i INT = 1; + WHILE @i <= @upto BEGIN INSERT INTO @result VALUES (@i); SET @i += 1; END; + RETURN; +END +GO +-- @queries +-- name: Range :many +SELECT n FROM dbo.range_of(@upto); diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/scalar_function.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/scalar_function.sql new file mode 100644 index 000000000..05f9f216c --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/programmability/scalar_function.sql @@ -0,0 +1,10 @@ +-- @description A user-defined scalar function in the select list and WHERE clause +-- @schema +CREATE FUNCTION dbo.initials (@name NVARCHAR(100)) RETURNS NVARCHAR(10) AS +BEGIN + RETURN LEFT(@name, 1); +END +GO +-- @queries +-- name: ListWithInitials :many +SELECT id, dbo.initials(name) AS initials FROM authors WHERE dbo.initials(name) = @initials; diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/string_split.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/string_split.sql new file mode 100644 index 000000000..58fa65440 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/programmability/string_split.sql @@ -0,0 +1,4 @@ +-- @description STRING_SPLIT as a table-valued function fed by a parameter +-- @queries +-- name: AuthorsByNameList :many +SELECT id, name FROM authors WHERE name IN (SELECT value FROM STRING_SPLIT(@names, ',')); diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/table_valued_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/table_valued_parameter.sql new file mode 100644 index 000000000..321e878f2 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/programmability/table_valued_parameter.sql @@ -0,0 +1,6 @@ +-- @description A table-valued parameter (user-defined table type) used in a join +-- @schema +CREATE TYPE dbo.id_list AS TABLE (id BIGINT NOT NULL PRIMARY KEY); +-- @queries +-- name: AuthorsByIdList :many +SELECT a.id, a.name FROM authors a JOIN @ids i ON i.id = a.id; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/alias_equals_syntax.sql b/end2end/MssqlSyntaxMatrix/cases/select/alias_equals_syntax.sql new file mode 100644 index 000000000..5a2d5ee80 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/alias_equals_syntax.sql @@ -0,0 +1,4 @@ +-- @description T-SQL column alias written as alias = expression +-- @queries +-- name: ListAuthorNames :many +SELECT author_id = id, author_name = name FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/between_params.sql b/end2end/MssqlSyntaxMatrix/cases/select/between_params.sql new file mode 100644 index 000000000..bb0054a78 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/between_params.sql @@ -0,0 +1,4 @@ +-- @description BETWEEN with two parameters +-- @queries +-- name: ListBooksInPriceRange :many +SELECT id, price FROM books WHERE price BETWEEN @min_price AND @max_price; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/case_expression.sql b/end2end/MssqlSyntaxMatrix/cases/select/case_expression.sql new file mode 100644 index 000000000..d21fc85f8 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/case_expression.sql @@ -0,0 +1,7 @@ +-- @description Searched and simple CASE expressions +-- @queries +-- name: ListPriceBands :many +SELECT id, + CASE WHEN price IS NULL THEN N'unknown' WHEN price < 10 THEN N'cheap' ELSE N'expensive' END AS band, + CASE is_available WHEN 1 THEN 'yes' ELSE 'no' END AS available +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/comments.sql b/end2end/MssqlSyntaxMatrix/cases/select/comments.sql new file mode 100644 index 000000000..6e6735ebd --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/comments.sql @@ -0,0 +1,6 @@ +-- @description Block comments, nested block comments and trailing line comments inside a query +-- @queries +-- name: Commented :many +SELECT id, /* the name */ name -- trailing +FROM authors /* outer /* nested */ still a comment */ +WHERE id > @min_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/derived_table.sql b/end2end/MssqlSyntaxMatrix/cases/select/derived_table.sql new file mode 100644 index 000000000..ed90e0f86 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/derived_table.sql @@ -0,0 +1,4 @@ +-- @description Derived table (subquery in FROM) with column aliases +-- @queries +-- name: ListExpensive :many +SELECT t.book_id, t.price FROM (SELECT id AS book_id, price FROM books WHERE price > @min) AS t; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/distinct.sql b/end2end/MssqlSyntaxMatrix/cases/select/distinct.sql new file mode 100644 index 000000000..721a6b187 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/distinct.sql @@ -0,0 +1,4 @@ +-- @description SELECT DISTINCT +-- @queries +-- name: ListDistinctAuthorIds :many +SELECT DISTINCT author_id FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/exists.sql b/end2end/MssqlSyntaxMatrix/cases/select/exists.sql new file mode 100644 index 000000000..eca04be6d --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/exists.sql @@ -0,0 +1,7 @@ +-- @description EXISTS / NOT EXISTS with a parameter inside the subquery +-- @queries +-- name: ListAuthorsWithBooksAbove :many +SELECT id FROM authors a WHERE EXISTS (SELECT 1 FROM books b WHERE b.author_id = a.id AND b.price > @min_price); + +-- name: ListAuthorsWithoutBooks :many +SELECT id FROM authors a WHERE NOT EXISTS (SELECT 1 FROM books b WHERE b.author_id = a.id); diff --git a/end2end/MssqlSyntaxMatrix/cases/select/function_on_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/select/function_on_parameter.sql new file mode 100644 index 000000000..a9284bcb4 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/function_on_parameter.sql @@ -0,0 +1,4 @@ +-- @description Parameters wrapped in functions and compared to columns +-- @queries +-- name: CaseInsensitiveMatch :many +SELECT id FROM authors WHERE LOWER(name) = LOWER(@name) AND LEN(@name) > 0; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/iif_choose.sql b/end2end/MssqlSyntaxMatrix/cases/select/iif_choose.sql new file mode 100644 index 000000000..20143288b --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/iif_choose.sql @@ -0,0 +1,4 @@ +-- @description IIF and CHOOSE +-- @queries +-- name: ListFlags :many +SELECT id, IIF(price > @threshold, 1, 0) AS is_expensive, CHOOSE(2, 'a', 'b', 'c') AS second FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/in_list_params.sql b/end2end/MssqlSyntaxMatrix/cases/select/in_list_params.sql new file mode 100644 index 000000000..02ee91816 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/in_list_params.sql @@ -0,0 +1,4 @@ +-- @description IN with a list of scalar parameters +-- @queries +-- name: ByThreeIds :many +SELECT id, name FROM authors WHERE id IN (@first_id, @second_id, @third_id); diff --git a/end2end/MssqlSyntaxMatrix/cases/select/in_subquery_param.sql b/end2end/MssqlSyntaxMatrix/cases/select/in_subquery_param.sql new file mode 100644 index 000000000..5a22729b2 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/in_subquery_param.sql @@ -0,0 +1,4 @@ +-- @description A parameter inside IN (subquery) +-- @queries +-- name: ListAuthorsOfTitle :many +SELECT id, name FROM authors WHERE id IN (SELECT author_id FROM books WHERE title = @title); diff --git a/end2end/MssqlSyntaxMatrix/cases/select/like_escape.sql b/end2end/MssqlSyntaxMatrix/cases/select/like_escape.sql new file mode 100644 index 000000000..746de5c42 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/like_escape.sql @@ -0,0 +1,4 @@ +-- @description LIKE with a parameter and an ESCAPE clause +-- @queries +-- name: SearchTitles :many +SELECT id, title FROM books WHERE title LIKE @pattern ESCAPE '\'; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/no_semicolon_terminator.sql b/end2end/MssqlSyntaxMatrix/cases/select/no_semicolon_terminator.sql new file mode 100644 index 000000000..849c54dea --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/no_semicolon_terminator.sql @@ -0,0 +1,7 @@ +-- @description Statements without a terminating semicolon (optional in T-SQL) +-- @queries +-- name: First :many +SELECT id FROM authors + +-- name: Second :many +SELECT id FROM books diff --git a/end2end/MssqlSyntaxMatrix/cases/select/offset_fetch.sql b/end2end/MssqlSyntaxMatrix/cases/select/offset_fetch.sql new file mode 100644 index 000000000..088f9e846 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/offset_fetch.sql @@ -0,0 +1,4 @@ +-- @description OFFSET ... ROWS FETCH NEXT ... ROWS ONLY with parameters +-- @queries +-- name: PageAuthors :many +SELECT id, name FROM authors ORDER BY name OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/offset_without_fetch.sql b/end2end/MssqlSyntaxMatrix/cases/select/offset_without_fetch.sql new file mode 100644 index 000000000..82b6e6367 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/offset_without_fetch.sql @@ -0,0 +1,4 @@ +-- @description OFFSET ... ROWS without FETCH +-- @queries +-- name: SkipAuthors :many +SELECT id FROM authors ORDER BY id OFFSET @skip ROWS; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/optional_filter.sql b/end2end/MssqlSyntaxMatrix/cases/select/optional_filter.sql new file mode 100644 index 000000000..fd245e70c --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/optional_filter.sql @@ -0,0 +1,4 @@ +-- @description The optional filter idiom: (@p IS NULL OR column = @p) +-- @queries +-- name: FilterBooks :many +SELECT id FROM books WHERE (@author_id IS NULL OR author_id = @author_id) AND (@title IS NULL OR title = @title); diff --git a/end2end/MssqlSyntaxMatrix/cases/select/order_by_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/select/order_by_parameter.sql new file mode 100644 index 000000000..31140ee89 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/order_by_parameter.sql @@ -0,0 +1,5 @@ +-- @description Dynamic ORDER BY driven by a parameter +-- @queries +-- name: SortedAuthors :many +SELECT id, name FROM authors +ORDER BY CASE WHEN @sort = 'name' THEN name END, CASE WHEN @sort = 'id' THEN id END DESC; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/parameter_in_select_list.sql b/end2end/MssqlSyntaxMatrix/cases/select/parameter_in_select_list.sql new file mode 100644 index 000000000..206087a34 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/parameter_in_select_list.sql @@ -0,0 +1,4 @@ +-- @description A parameter echoed in the select list, with and without CAST +-- @queries +-- name: EchoParams :many +SELECT id, @label AS label, CAST(@factor AS DECIMAL(5, 2)) * price AS scaled FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/quoted_aliases.sql b/end2end/MssqlSyntaxMatrix/cases/select/quoted_aliases.sql new file mode 100644 index 000000000..ab7d5eb51 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/quoted_aliases.sql @@ -0,0 +1,4 @@ +-- @description Aliases quoted with brackets, double quotes and single quotes +-- @queries +-- name: ListQuotedAliases :many +SELECT id AS [Author Id], name AS "Author Name", bio 'Biography' FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/repeated_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/select/repeated_parameter.sql new file mode 100644 index 000000000..dce23bef6 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/repeated_parameter.sql @@ -0,0 +1,4 @@ +-- @description The same parameter used several times, with different case +-- @queries +-- name: ByNameTwice :many +SELECT id FROM authors WHERE name = @name OR bio = @Name OR @NAME IS NULL; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/scalar_subquery_column.sql b/end2end/MssqlSyntaxMatrix/cases/select/scalar_subquery_column.sql new file mode 100644 index 000000000..d783a9c86 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/scalar_subquery_column.sql @@ -0,0 +1,4 @@ +-- @description Correlated scalar subquery in the select list +-- @queries +-- name: ListAuthorsWithBookCount :many +SELECT a.id, (SELECT COUNT(*) FROM books b WHERE b.author_id = a.id) AS book_count FROM authors a; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/select_star.sql b/end2end/MssqlSyntaxMatrix/cases/select/select_star.sql new file mode 100644 index 000000000..4179bfcb1 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/select_star.sql @@ -0,0 +1,7 @@ +-- @description SELECT * and qualified t.* expansion +-- @queries +-- name: ListAuthors :many +SELECT * FROM authors; + +-- name: ListAuthorsWithBookTitle :many +SELECT a.*, b.title FROM authors a JOIN books b ON b.author_id = a.id; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/table_value_constructor.sql b/end2end/MssqlSyntaxMatrix/cases/select/table_value_constructor.sql new file mode 100644 index 000000000..ae6cf788f --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/table_value_constructor.sql @@ -0,0 +1,4 @@ +-- @description VALUES as a derived table +-- @queries +-- name: ListConstants :many +SELECT v.code, v.label FROM (VALUES (1, N'one'), (2, N'two')) AS v (code, label); diff --git a/end2end/MssqlSyntaxMatrix/cases/select/top_literal_with_ties.sql b/end2end/MssqlSyntaxMatrix/cases/select/top_literal_with_ties.sql new file mode 100644 index 000000000..99c55b3bc --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/top_literal_with_ties.sql @@ -0,0 +1,7 @@ +-- @description TOP n WITH TIES and TOP n PERCENT +-- @queries +-- name: TopPricedWithTies :many +SELECT TOP 3 WITH TIES id, price FROM books ORDER BY price DESC; + +-- name: TopTenPercent :many +SELECT TOP 10 PERCENT id FROM books ORDER BY id; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/top_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/select/top_parameter.sql new file mode 100644 index 000000000..c5351d089 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/top_parameter.sql @@ -0,0 +1,4 @@ +-- @description TOP with a parenthesized parameter +-- @queries +-- name: TopAuthors :many +SELECT TOP (@take) id, name FROM authors ORDER BY name; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/unicode_literals.sql b/end2end/MssqlSyntaxMatrix/cases/select/unicode_literals.sql new file mode 100644 index 000000000..b4d65802a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/unicode_literals.sql @@ -0,0 +1,4 @@ +-- @description N'' Unicode literals and string concatenation with + +-- @queries +-- name: Greetings :many +SELECT N'שלום ' + name AS greeting, 'plain' AS ascii FROM authors; diff --git a/end2end/MssqlSyntaxMatrix/cases/select/without_from.sql b/end2end/MssqlSyntaxMatrix/cases/select/without_from.sql new file mode 100644 index 000000000..2f002cde5 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/select/without_from.sql @@ -0,0 +1,7 @@ +-- @description SELECT without FROM: parameters and functions only +-- @queries +-- name: Now :one +SELECT SYSUTCDATETIME() AS now_utc; + +-- name: AddOne :one +SELECT @value + 1 AS result; diff --git a/end2end/MssqlSyntaxMatrix/cases/setops/pivot_unpivot.sql b/end2end/MssqlSyntaxMatrix/cases/setops/pivot_unpivot.sql new file mode 100644 index 000000000..9c8eee65a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/setops/pivot_unpivot.sql @@ -0,0 +1,13 @@ +-- @description PIVOT and UNPIVOT +-- @schema +CREATE TABLE quarterly (region NVARCHAR(10) NOT NULL, q1 INT NOT NULL, q2 INT NOT NULL); +-- @queries +-- name: AvailabilityPivot :many +SELECT author_id, [0] AS unavailable, [1] AS available +FROM (SELECT author_id, CAST(is_available AS INT) AS is_available, id FROM books) src +PIVOT (COUNT(id) FOR is_available IN ([0], [1])) AS p; + +-- name: QuarterUnpivot :many +SELECT region, quarter, amount +FROM (SELECT region, q1, q2 FROM quarterly) src +UNPIVOT (amount FOR quarter IN (q1, q2)) AS u; diff --git a/end2end/MssqlSyntaxMatrix/cases/setops/union_except_intersect.sql b/end2end/MssqlSyntaxMatrix/cases/setops/union_except_intersect.sql new file mode 100644 index 000000000..67f21a690 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/setops/union_except_intersect.sql @@ -0,0 +1,13 @@ +-- @description UNION, UNION ALL, EXCEPT and INTERSECT +-- @queries +-- name: UnionNames :many +SELECT name FROM authors UNION SELECT title FROM books; + +-- name: UnionAllIds :many +SELECT id FROM authors UNION ALL SELECT author_id FROM books; + +-- name: AuthorsWithoutBooks :many +SELECT id FROM authors EXCEPT SELECT author_id FROM books; + +-- name: AuthorsWithBooks :many +SELECT id FROM authors INTERSECT SELECT author_id FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/bigint.sql b/end2end/MssqlSyntaxMatrix/cases/types/bigint.sql new file mode 100644 index 000000000..6a0e48a22 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/bigint.sql @@ -0,0 +1,12 @@ +-- @description `BIGINT` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_bigint (v BIGINT NULL, nn BIGINT NOT NULL); +-- @queries +-- name: SelectBigint :many +SELECT v, nn FROM t_bigint; + +-- name: InsertBigint :exec +INSERT INTO t_bigint (v, nn) VALUES (@v, @nn); + +-- name: FilterBigint :many +SELECT v FROM t_bigint WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/binary.sql b/end2end/MssqlSyntaxMatrix/cases/types/binary.sql new file mode 100644 index 000000000..7db3e9775 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/binary.sql @@ -0,0 +1,12 @@ +-- @description `BINARY(16)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_binary (v BINARY(16) NULL, nn BINARY(16) NOT NULL); +-- @queries +-- name: SelectBinary :many +SELECT v, nn FROM t_binary; + +-- name: InsertBinary :exec +INSERT INTO t_binary (v, nn) VALUES (@v, @nn); + +-- name: FilterBinary :many +SELECT v FROM t_binary WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/bit.sql b/end2end/MssqlSyntaxMatrix/cases/types/bit.sql new file mode 100644 index 000000000..733f86c17 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/bit.sql @@ -0,0 +1,12 @@ +-- @description `BIT` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_bit (v BIT NULL, nn BIT NOT NULL); +-- @queries +-- name: SelectBit :many +SELECT v, nn FROM t_bit; + +-- name: InsertBit :exec +INSERT INTO t_bit (v, nn) VALUES (@v, @nn); + +-- name: FilterBit :many +SELECT v FROM t_bit WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/char.sql b/end2end/MssqlSyntaxMatrix/cases/types/char.sql new file mode 100644 index 000000000..d4efa4dac --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/char.sql @@ -0,0 +1,12 @@ +-- @description `CHAR(10)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_char (v CHAR(10) NULL, nn CHAR(10) NOT NULL); +-- @queries +-- name: SelectChar :many +SELECT v, nn FROM t_char; + +-- name: InsertChar :exec +INSERT INTO t_char (v, nn) VALUES (@v, @nn); + +-- name: FilterChar :many +SELECT v FROM t_char WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/date.sql b/end2end/MssqlSyntaxMatrix/cases/types/date.sql new file mode 100644 index 000000000..282beca87 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/date.sql @@ -0,0 +1,12 @@ +-- @description `DATE` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_date (v DATE NULL, nn DATE NOT NULL); +-- @queries +-- name: SelectDate :many +SELECT v, nn FROM t_date; + +-- name: InsertDate :exec +INSERT INTO t_date (v, nn) VALUES (@v, @nn); + +-- name: FilterDate :many +SELECT v FROM t_date WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/datetime.sql b/end2end/MssqlSyntaxMatrix/cases/types/datetime.sql new file mode 100644 index 000000000..b4c4d85b1 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/datetime.sql @@ -0,0 +1,12 @@ +-- @description `DATETIME` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_datetime (v DATETIME NULL, nn DATETIME NOT NULL); +-- @queries +-- name: SelectDatetime :many +SELECT v, nn FROM t_datetime; + +-- name: InsertDatetime :exec +INSERT INTO t_datetime (v, nn) VALUES (@v, @nn); + +-- name: FilterDatetime :many +SELECT v FROM t_datetime WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/datetime2.sql b/end2end/MssqlSyntaxMatrix/cases/types/datetime2.sql new file mode 100644 index 000000000..6df588115 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/datetime2.sql @@ -0,0 +1,12 @@ +-- @description `DATETIME2(7)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_datetime2 (v DATETIME2(7) NULL, nn DATETIME2(7) NOT NULL); +-- @queries +-- name: SelectDatetime2 :many +SELECT v, nn FROM t_datetime2; + +-- name: InsertDatetime2 :exec +INSERT INTO t_datetime2 (v, nn) VALUES (@v, @nn); + +-- name: FilterDatetime2 :many +SELECT v FROM t_datetime2 WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/datetime2_0.sql b/end2end/MssqlSyntaxMatrix/cases/types/datetime2_0.sql new file mode 100644 index 000000000..bdfb1382a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/datetime2_0.sql @@ -0,0 +1,12 @@ +-- @description `DATETIME2(0)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_datetime2_0 (v DATETIME2(0) NULL, nn DATETIME2(0) NOT NULL); +-- @queries +-- name: SelectDatetime20 :many +SELECT v, nn FROM t_datetime2_0; + +-- name: InsertDatetime20 :exec +INSERT INTO t_datetime2_0 (v, nn) VALUES (@v, @nn); + +-- name: FilterDatetime20 :many +SELECT v FROM t_datetime2_0 WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/datetimeoffset.sql b/end2end/MssqlSyntaxMatrix/cases/types/datetimeoffset.sql new file mode 100644 index 000000000..3a6dc63b1 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/datetimeoffset.sql @@ -0,0 +1,12 @@ +-- @description `DATETIMEOFFSET(7)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_datetimeoffset (v DATETIMEOFFSET(7) NULL, nn DATETIMEOFFSET(7) NOT NULL); +-- @queries +-- name: SelectDatetimeoffset :many +SELECT v, nn FROM t_datetimeoffset; + +-- name: InsertDatetimeoffset :exec +INSERT INTO t_datetimeoffset (v, nn) VALUES (@v, @nn); + +-- name: FilterDatetimeoffset :many +SELECT v FROM t_datetimeoffset WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/decimal.sql b/end2end/MssqlSyntaxMatrix/cases/types/decimal.sql new file mode 100644 index 000000000..55d9ff6ff --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/decimal.sql @@ -0,0 +1,12 @@ +-- @description `DECIMAL(18, 4)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_decimal (v DECIMAL(18, 4) NULL, nn DECIMAL(18, 4) NOT NULL); +-- @queries +-- name: SelectDecimal :many +SELECT v, nn FROM t_decimal; + +-- name: InsertDecimal :exec +INSERT INTO t_decimal (v, nn) VALUES (@v, @nn); + +-- name: FilterDecimal :many +SELECT v FROM t_decimal WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/float.sql b/end2end/MssqlSyntaxMatrix/cases/types/float.sql new file mode 100644 index 000000000..2244d90c9 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/float.sql @@ -0,0 +1,12 @@ +-- @description `FLOAT` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_float (v FLOAT NULL, nn FLOAT NOT NULL); +-- @queries +-- name: SelectFloat :many +SELECT v, nn FROM t_float; + +-- name: InsertFloat :exec +INSERT INTO t_float (v, nn) VALUES (@v, @nn); + +-- name: FilterFloat :many +SELECT v FROM t_float WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/float_24.sql b/end2end/MssqlSyntaxMatrix/cases/types/float_24.sql new file mode 100644 index 000000000..8288dfe17 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/float_24.sql @@ -0,0 +1,12 @@ +-- @description `FLOAT(24)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_float_24 (v FLOAT(24) NULL, nn FLOAT(24) NOT NULL); +-- @queries +-- name: SelectFloat24 :many +SELECT v, nn FROM t_float_24; + +-- name: InsertFloat24 :exec +INSERT INTO t_float_24 (v, nn) VALUES (@v, @nn); + +-- name: FilterFloat24 :many +SELECT v FROM t_float_24 WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/geography.sql b/end2end/MssqlSyntaxMatrix/cases/types/geography.sql new file mode 100644 index 000000000..e3b6fd47c --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/geography.sql @@ -0,0 +1,9 @@ +-- @description `GEOGRAPHY` column: nullable and NOT NULL, read, written +-- @schema +CREATE TABLE t_geography (v GEOGRAPHY NULL, nn GEOGRAPHY NOT NULL); +-- @queries +-- name: SelectGeography :many +SELECT v, nn FROM t_geography; + +-- name: InsertGeography :exec +INSERT INTO t_geography (v, nn) VALUES (@v, @nn); diff --git a/end2end/MssqlSyntaxMatrix/cases/types/geometry.sql b/end2end/MssqlSyntaxMatrix/cases/types/geometry.sql new file mode 100644 index 000000000..1a7573865 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/geometry.sql @@ -0,0 +1,9 @@ +-- @description `GEOMETRY` column: nullable and NOT NULL, read, written +-- @schema +CREATE TABLE t_geometry (v GEOMETRY NULL, nn GEOMETRY NOT NULL); +-- @queries +-- name: SelectGeometry :many +SELECT v, nn FROM t_geometry; + +-- name: InsertGeometry :exec +INSERT INTO t_geometry (v, nn) VALUES (@v, @nn); diff --git a/end2end/MssqlSyntaxMatrix/cases/types/hierarchyid.sql b/end2end/MssqlSyntaxMatrix/cases/types/hierarchyid.sql new file mode 100644 index 000000000..2218f405e --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/hierarchyid.sql @@ -0,0 +1,12 @@ +-- @description `HIERARCHYID` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_hierarchyid (v HIERARCHYID NULL, nn HIERARCHYID NOT NULL); +-- @queries +-- name: SelectHierarchyid :many +SELECT v, nn FROM t_hierarchyid; + +-- name: InsertHierarchyid :exec +INSERT INTO t_hierarchyid (v, nn) VALUES (@v, @nn); + +-- name: FilterHierarchyid :many +SELECT v FROM t_hierarchyid WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/image.sql b/end2end/MssqlSyntaxMatrix/cases/types/image.sql new file mode 100644 index 000000000..10406fa45 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/image.sql @@ -0,0 +1,9 @@ +-- @description `IMAGE` column: nullable and NOT NULL, read, written +-- @schema +CREATE TABLE t_image (v IMAGE NULL, nn IMAGE NOT NULL); +-- @queries +-- name: SelectImage :many +SELECT v, nn FROM t_image; + +-- name: InsertImage :exec +INSERT INTO t_image (v, nn) VALUES (@v, @nn); diff --git a/end2end/MssqlSyntaxMatrix/cases/types/int.sql b/end2end/MssqlSyntaxMatrix/cases/types/int.sql new file mode 100644 index 000000000..75facb41b --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/int.sql @@ -0,0 +1,12 @@ +-- @description `INT` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_int (v INT NULL, nn INT NOT NULL); +-- @queries +-- name: SelectInt :many +SELECT v, nn FROM t_int; + +-- name: InsertInt :exec +INSERT INTO t_int (v, nn) VALUES (@v, @nn); + +-- name: FilterInt :many +SELECT v FROM t_int WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/max_lengths_and_precision.sql b/end2end/MssqlSyntaxMatrix/cases/types/max_lengths_and_precision.sql new file mode 100644 index 000000000..79dc0bf4f --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/max_lengths_and_precision.sql @@ -0,0 +1,6 @@ +-- @description The type expression keeps lengths, MAX, precision and scale +-- @schema +CREATE TABLE t_precise (d DECIMAL(38, 10) NOT NULL, n NVARCHAR(4000) NOT NULL, t TIME(0) NOT NULL, dt DATETIME2(3) NOT NULL); +-- @queries +-- name: SelectPrecise :many +SELECT d, n, t, dt FROM t_precise WHERE d = @d AND n = @n; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/money.sql b/end2end/MssqlSyntaxMatrix/cases/types/money.sql new file mode 100644 index 000000000..5be0130bb --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/money.sql @@ -0,0 +1,12 @@ +-- @description `MONEY` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_money (v MONEY NULL, nn MONEY NOT NULL); +-- @queries +-- name: SelectMoney :many +SELECT v, nn FROM t_money; + +-- name: InsertMoney :exec +INSERT INTO t_money (v, nn) VALUES (@v, @nn); + +-- name: FilterMoney :many +SELECT v FROM t_money WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/nchar.sql b/end2end/MssqlSyntaxMatrix/cases/types/nchar.sql new file mode 100644 index 000000000..afd395221 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/nchar.sql @@ -0,0 +1,12 @@ +-- @description `NCHAR(10)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_nchar (v NCHAR(10) NULL, nn NCHAR(10) NOT NULL); +-- @queries +-- name: SelectNchar :many +SELECT v, nn FROM t_nchar; + +-- name: InsertNchar :exec +INSERT INTO t_nchar (v, nn) VALUES (@v, @nn); + +-- name: FilterNchar :many +SELECT v FROM t_nchar WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/ntext.sql b/end2end/MssqlSyntaxMatrix/cases/types/ntext.sql new file mode 100644 index 000000000..a2b71b839 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/ntext.sql @@ -0,0 +1,9 @@ +-- @description `NTEXT` column: nullable and NOT NULL, read, written +-- @schema +CREATE TABLE t_ntext (v NTEXT NULL, nn NTEXT NOT NULL); +-- @queries +-- name: SelectNtext :many +SELECT v, nn FROM t_ntext; + +-- name: InsertNtext :exec +INSERT INTO t_ntext (v, nn) VALUES (@v, @nn); diff --git a/end2end/MssqlSyntaxMatrix/cases/types/numeric.sql b/end2end/MssqlSyntaxMatrix/cases/types/numeric.sql new file mode 100644 index 000000000..124251acf --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/numeric.sql @@ -0,0 +1,12 @@ +-- @description `NUMERIC(10, 0)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_numeric (v NUMERIC(10, 0) NULL, nn NUMERIC(10, 0) NOT NULL); +-- @queries +-- name: SelectNumeric :many +SELECT v, nn FROM t_numeric; + +-- name: InsertNumeric :exec +INSERT INTO t_numeric (v, nn) VALUES (@v, @nn); + +-- name: FilterNumeric :many +SELECT v FROM t_numeric WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/nvarchar.sql b/end2end/MssqlSyntaxMatrix/cases/types/nvarchar.sql new file mode 100644 index 000000000..9da83b1a6 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/nvarchar.sql @@ -0,0 +1,12 @@ +-- @description `NVARCHAR(50)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_nvarchar (v NVARCHAR(50) NULL, nn NVARCHAR(50) NOT NULL); +-- @queries +-- name: SelectNvarchar :many +SELECT v, nn FROM t_nvarchar; + +-- name: InsertNvarchar :exec +INSERT INTO t_nvarchar (v, nn) VALUES (@v, @nn); + +-- name: FilterNvarchar :many +SELECT v FROM t_nvarchar WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/nvarchar_max.sql b/end2end/MssqlSyntaxMatrix/cases/types/nvarchar_max.sql new file mode 100644 index 000000000..8be2b367e --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/nvarchar_max.sql @@ -0,0 +1,12 @@ +-- @description `NVARCHAR(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_nvarchar_max (v NVARCHAR(MAX) NULL, nn NVARCHAR(MAX) NOT NULL); +-- @queries +-- name: SelectNvarcharMax :many +SELECT v, nn FROM t_nvarchar_max; + +-- name: InsertNvarcharMax :exec +INSERT INTO t_nvarchar_max (v, nn) VALUES (@v, @nn); + +-- name: FilterNvarcharMax :many +SELECT v FROM t_nvarchar_max WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/real.sql b/end2end/MssqlSyntaxMatrix/cases/types/real.sql new file mode 100644 index 000000000..25ebae346 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/real.sql @@ -0,0 +1,12 @@ +-- @description `REAL` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_real (v REAL NULL, nn REAL NOT NULL); +-- @queries +-- name: SelectReal :many +SELECT v, nn FROM t_real; + +-- name: InsertReal :exec +INSERT INTO t_real (v, nn) VALUES (@v, @nn); + +-- name: FilterReal :many +SELECT v FROM t_real WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/rowversion_timestamp.sql b/end2end/MssqlSyntaxMatrix/cases/types/rowversion_timestamp.sql new file mode 100644 index 000000000..06897009d --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/rowversion_timestamp.sql @@ -0,0 +1,10 @@ +-- @description `ROWVERSION` and its legacy synonym `TIMESTAMP` +-- @schema +CREATE TABLE t_rowversion (id INT NOT NULL, rv ROWVERSION); +CREATE TABLE t_timestamp (id INT NOT NULL, ts TIMESTAMP); +-- @queries +-- name: SelectVersions :many +SELECT id, rv FROM t_rowversion; + +-- name: SelectTimestamp :many +SELECT id, ts FROM t_timestamp; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/smalldatetime.sql b/end2end/MssqlSyntaxMatrix/cases/types/smalldatetime.sql new file mode 100644 index 000000000..1dd85b473 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/smalldatetime.sql @@ -0,0 +1,12 @@ +-- @description `SMALLDATETIME` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_smalldatetime (v SMALLDATETIME NULL, nn SMALLDATETIME NOT NULL); +-- @queries +-- name: SelectSmalldatetime :many +SELECT v, nn FROM t_smalldatetime; + +-- name: InsertSmalldatetime :exec +INSERT INTO t_smalldatetime (v, nn) VALUES (@v, @nn); + +-- name: FilterSmalldatetime :many +SELECT v FROM t_smalldatetime WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/smallint.sql b/end2end/MssqlSyntaxMatrix/cases/types/smallint.sql new file mode 100644 index 000000000..7872c4055 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/smallint.sql @@ -0,0 +1,12 @@ +-- @description `SMALLINT` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_smallint (v SMALLINT NULL, nn SMALLINT NOT NULL); +-- @queries +-- name: SelectSmallint :many +SELECT v, nn FROM t_smallint; + +-- name: InsertSmallint :exec +INSERT INTO t_smallint (v, nn) VALUES (@v, @nn); + +-- name: FilterSmallint :many +SELECT v FROM t_smallint WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/smallmoney.sql b/end2end/MssqlSyntaxMatrix/cases/types/smallmoney.sql new file mode 100644 index 000000000..c984b1fe6 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/smallmoney.sql @@ -0,0 +1,12 @@ +-- @description `SMALLMONEY` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_smallmoney (v SMALLMONEY NULL, nn SMALLMONEY NOT NULL); +-- @queries +-- name: SelectSmallmoney :many +SELECT v, nn FROM t_smallmoney; + +-- name: InsertSmallmoney :exec +INSERT INTO t_smallmoney (v, nn) VALUES (@v, @nn); + +-- name: FilterSmallmoney :many +SELECT v FROM t_smallmoney WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/sql_variant.sql b/end2end/MssqlSyntaxMatrix/cases/types/sql_variant.sql new file mode 100644 index 000000000..ea79049fe --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/sql_variant.sql @@ -0,0 +1,12 @@ +-- @description `SQL_VARIANT` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_sql_variant (v SQL_VARIANT NULL, nn SQL_VARIANT NOT NULL); +-- @queries +-- name: SelectSqlVariant :many +SELECT v, nn FROM t_sql_variant; + +-- name: InsertSqlVariant :exec +INSERT INTO t_sql_variant (v, nn) VALUES (@v, @nn); + +-- name: FilterSqlVariant :many +SELECT v FROM t_sql_variant WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/sysname.sql b/end2end/MssqlSyntaxMatrix/cases/types/sysname.sql new file mode 100644 index 000000000..d583857de --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/sysname.sql @@ -0,0 +1,12 @@ +-- @description `SYSNAME` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_sysname (v SYSNAME NULL, nn SYSNAME NOT NULL); +-- @queries +-- name: SelectSysname :many +SELECT v, nn FROM t_sysname; + +-- name: InsertSysname :exec +INSERT INTO t_sysname (v, nn) VALUES (@v, @nn); + +-- name: FilterSysname :many +SELECT v FROM t_sysname WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/text.sql b/end2end/MssqlSyntaxMatrix/cases/types/text.sql new file mode 100644 index 000000000..0de8bb34a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/text.sql @@ -0,0 +1,9 @@ +-- @description `TEXT` column: nullable and NOT NULL, read, written +-- @schema +CREATE TABLE t_text (v TEXT NULL, nn TEXT NOT NULL); +-- @queries +-- name: SelectText :many +SELECT v, nn FROM t_text; + +-- name: InsertText :exec +INSERT INTO t_text (v, nn) VALUES (@v, @nn); diff --git a/end2end/MssqlSyntaxMatrix/cases/types/time.sql b/end2end/MssqlSyntaxMatrix/cases/types/time.sql new file mode 100644 index 000000000..7983c99fe --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/time.sql @@ -0,0 +1,12 @@ +-- @description `TIME(3)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_time (v TIME(3) NULL, nn TIME(3) NOT NULL); +-- @queries +-- name: SelectTime :many +SELECT v, nn FROM t_time; + +-- name: InsertTime :exec +INSERT INTO t_time (v, nn) VALUES (@v, @nn); + +-- name: FilterTime :many +SELECT v FROM t_time WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/tinyint.sql b/end2end/MssqlSyntaxMatrix/cases/types/tinyint.sql new file mode 100644 index 000000000..aa0e3b400 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/tinyint.sql @@ -0,0 +1,12 @@ +-- @description `TINYINT` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_tinyint (v TINYINT NULL, nn TINYINT NOT NULL); +-- @queries +-- name: SelectTinyint :many +SELECT v, nn FROM t_tinyint; + +-- name: InsertTinyint :exec +INSERT INTO t_tinyint (v, nn) VALUES (@v, @nn); + +-- name: FilterTinyint :many +SELECT v FROM t_tinyint WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/uniqueidentifier.sql b/end2end/MssqlSyntaxMatrix/cases/types/uniqueidentifier.sql new file mode 100644 index 000000000..77c6f46bc --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/uniqueidentifier.sql @@ -0,0 +1,12 @@ +-- @description `UNIQUEIDENTIFIER` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_uniqueidentifier (v UNIQUEIDENTIFIER NULL, nn UNIQUEIDENTIFIER NOT NULL); +-- @queries +-- name: SelectUniqueidentifier :many +SELECT v, nn FROM t_uniqueidentifier; + +-- name: InsertUniqueidentifier :exec +INSERT INTO t_uniqueidentifier (v, nn) VALUES (@v, @nn); + +-- name: FilterUniqueidentifier :many +SELECT v FROM t_uniqueidentifier WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/varbinary.sql b/end2end/MssqlSyntaxMatrix/cases/types/varbinary.sql new file mode 100644 index 000000000..824a223f9 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/varbinary.sql @@ -0,0 +1,12 @@ +-- @description `VARBINARY(50)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_varbinary (v VARBINARY(50) NULL, nn VARBINARY(50) NOT NULL); +-- @queries +-- name: SelectVarbinary :many +SELECT v, nn FROM t_varbinary; + +-- name: InsertVarbinary :exec +INSERT INTO t_varbinary (v, nn) VALUES (@v, @nn); + +-- name: FilterVarbinary :many +SELECT v FROM t_varbinary WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/varbinary_max.sql b/end2end/MssqlSyntaxMatrix/cases/types/varbinary_max.sql new file mode 100644 index 000000000..2d0077378 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/varbinary_max.sql @@ -0,0 +1,12 @@ +-- @description `VARBINARY(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_varbinary_max (v VARBINARY(MAX) NULL, nn VARBINARY(MAX) NOT NULL); +-- @queries +-- name: SelectVarbinaryMax :many +SELECT v, nn FROM t_varbinary_max; + +-- name: InsertVarbinaryMax :exec +INSERT INTO t_varbinary_max (v, nn) VALUES (@v, @nn); + +-- name: FilterVarbinaryMax :many +SELECT v FROM t_varbinary_max WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/varchar.sql b/end2end/MssqlSyntaxMatrix/cases/types/varchar.sql new file mode 100644 index 000000000..173321715 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/varchar.sql @@ -0,0 +1,12 @@ +-- @description `VARCHAR(50)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_varchar (v VARCHAR(50) NULL, nn VARCHAR(50) NOT NULL); +-- @queries +-- name: SelectVarchar :many +SELECT v, nn FROM t_varchar; + +-- name: InsertVarchar :exec +INSERT INTO t_varchar (v, nn) VALUES (@v, @nn); + +-- name: FilterVarchar :many +SELECT v FROM t_varchar WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/varchar_max.sql b/end2end/MssqlSyntaxMatrix/cases/types/varchar_max.sql new file mode 100644 index 000000000..f512181e2 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/varchar_max.sql @@ -0,0 +1,12 @@ +-- @description `VARCHAR(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter +-- @schema +CREATE TABLE t_varchar_max (v VARCHAR(MAX) NULL, nn VARCHAR(MAX) NOT NULL); +-- @queries +-- name: SelectVarcharMax :many +SELECT v, nn FROM t_varchar_max; + +-- name: InsertVarcharMax :exec +INSERT INTO t_varchar_max (v, nn) VALUES (@v, @nn); + +-- name: FilterVarcharMax :many +SELECT v FROM t_varchar_max WHERE nn = @nn; diff --git a/end2end/MssqlSyntaxMatrix/cases/types/xml.sql b/end2end/MssqlSyntaxMatrix/cases/types/xml.sql new file mode 100644 index 000000000..89b362f99 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/types/xml.sql @@ -0,0 +1,9 @@ +-- @description `XML` column: nullable and NOT NULL, read, written +-- @schema +CREATE TABLE t_xml (v XML NULL, nn XML NOT NULL); +-- @queries +-- name: SelectXml :many +SELECT v, nn FROM t_xml; + +-- name: InsertXml :exec +INSERT INTO t_xml (v, nn) VALUES (@v, @nn); diff --git a/end2end/MssqlSyntaxMatrix/cases/update/compound_assignment.sql b/end2end/MssqlSyntaxMatrix/cases/update/compound_assignment.sql new file mode 100644 index 000000000..fd8f1fcfa --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/update/compound_assignment.sql @@ -0,0 +1,4 @@ +-- @description Compound assignment operators += and *= +-- @queries +-- name: AdjustPrice :exec +UPDATE books SET price += @delta, title += @suffix WHERE author_id = @author_id; diff --git a/end2end/MssqlSyntaxMatrix/cases/update/from_join.sql b/end2end/MssqlSyntaxMatrix/cases/update/from_join.sql new file mode 100644 index 000000000..3dbf81231 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/update/from_join.sql @@ -0,0 +1,4 @@ +-- @description UPDATE ... FROM with a JOIN (T-SQL specific) +-- @queries +-- name: MarkAuthorsBooksUnavailable :execrows +UPDATE b SET b.is_available = 0 FROM books b JOIN authors a ON a.id = b.author_id WHERE a.name = @author_name; diff --git a/end2end/MssqlSyntaxMatrix/cases/update/output_deleted_inserted.sql b/end2end/MssqlSyntaxMatrix/cases/update/output_deleted_inserted.sql new file mode 100644 index 000000000..9c03d580a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/update/output_deleted_inserted.sql @@ -0,0 +1,4 @@ +-- @description UPDATE ... OUTPUT DELETED.x, INSERTED.x +-- @queries +-- name: RenameAuthor :one +UPDATE authors SET name = @name OUTPUT DELETED.name AS old_name, INSERTED.name AS new_name WHERE id = @id; diff --git a/end2end/MssqlSyntaxMatrix/cases/update/set_default.sql b/end2end/MssqlSyntaxMatrix/cases/update/set_default.sql new file mode 100644 index 000000000..3be2e0816 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/update/set_default.sql @@ -0,0 +1,4 @@ +-- @description UPDATE ... SET column = DEFAULT and = NULL +-- @queries +-- name: ResetBook :exec +UPDATE books SET is_available = DEFAULT, price = NULL WHERE id = @id; diff --git a/end2end/MssqlSyntaxMatrix/cases/update/simple.sql b/end2end/MssqlSyntaxMatrix/cases/update/simple.sql new file mode 100644 index 000000000..61b637395 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/update/simple.sql @@ -0,0 +1,4 @@ +-- @description UPDATE with SET parameters and WHERE +-- @queries +-- name: UpdateBio :execrows +UPDATE authors SET bio = @bio, birth_date = @birth_date WHERE id = @id; diff --git a/end2end/MssqlSyntaxMatrix/cases/update/top.sql b/end2end/MssqlSyntaxMatrix/cases/update/top.sql new file mode 100644 index 000000000..896c9401e --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/update/top.sql @@ -0,0 +1,4 @@ +-- @description UPDATE TOP (@n) +-- @queries +-- name: MarkSomeUnavailable :execrows +UPDATE TOP (@n) books SET is_available = 0 WHERE is_available = 1; diff --git a/end2end/MssqlSyntaxMatrix/cases/window/aggregate_over.sql b/end2end/MssqlSyntaxMatrix/cases/window/aggregate_over.sql new file mode 100644 index 000000000..1e3dc3c30 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/window/aggregate_over.sql @@ -0,0 +1,6 @@ +-- @description Aggregates over windows, including a running total +-- @queries +-- name: RunningTotals :many +SELECT id, SUM(price) OVER (PARTITION BY author_id ORDER BY id ROWS UNBOUNDED PRECEDING) AS running_total, + COUNT(*) OVER () AS total_count +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/window/lag_lead_first_value.sql b/end2end/MssqlSyntaxMatrix/cases/window/lag_lead_first_value.sql new file mode 100644 index 000000000..76a8c3e02 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/window/lag_lead_first_value.sql @@ -0,0 +1,9 @@ +-- @description LAG, LEAD, FIRST_VALUE and LAST_VALUE with a frame +-- @queries +-- name: PriceNeighbours :many +SELECT id, + LAG(price) OVER (ORDER BY published_at) AS previous_price, + LEAD(price, 1, 0) OVER (ORDER BY published_at) AS next_price, + FIRST_VALUE(title) OVER (PARTITION BY author_id ORDER BY published_at) AS first_title, + LAST_VALUE(title) OVER (PARTITION BY author_id ORDER BY published_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_title +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/window/ranking.sql b/end2end/MssqlSyntaxMatrix/cases/window/ranking.sql new file mode 100644 index 000000000..0bf2e1fe3 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/window/ranking.sql @@ -0,0 +1,9 @@ +-- @description ROW_NUMBER, RANK, DENSE_RANK and NTILE +-- @queries +-- name: RankedBooks :many +SELECT id, + ROW_NUMBER() OVER (PARTITION BY author_id ORDER BY price DESC) AS rn, + RANK() OVER (ORDER BY price DESC) AS rnk, + DENSE_RANK() OVER (ORDER BY price DESC) AS dense_rnk, + NTILE(4) OVER (ORDER BY price) AS quartile +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/window/window_clause_and_percentiles.sql b/end2end/MssqlSyntaxMatrix/cases/window/window_clause_and_percentiles.sql new file mode 100644 index 000000000..09932971a --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/window/window_clause_and_percentiles.sql @@ -0,0 +1,10 @@ +-- @description Named WINDOW clause (SQL Server 2022), PERCENTILE_CONT and PERCENTILE_DISC +-- @queries +-- name: NamedWindow :many +SELECT id, SUM(price) OVER w AS total, AVG(price) OVER w AS average FROM books WINDOW w AS (PARTITION BY author_id); + +-- name: Percentiles :many +SELECT DISTINCT author_id, + PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) OVER (PARTITION BY author_id) AS median, + PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY price) OVER (PARTITION BY author_id) AS median_disc +FROM books; diff --git a/end2end/MssqlSyntaxMatrix/cases/xml/for_xml.sql b/end2end/MssqlSyntaxMatrix/cases/xml/for_xml.sql new file mode 100644 index 000000000..262ba37e0 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/xml/for_xml.sql @@ -0,0 +1,7 @@ +-- @description FOR XML PATH, including the string-concatenation idiom +-- @queries +-- name: AuthorsAsXml :one +SELECT id, name FROM authors FOR XML PATH('author'), ROOT('authors'); + +-- name: ConcatenatedTitles :many +SELECT a.id, STUFF((SELECT N',' + b.title FROM books b WHERE b.author_id = a.id FOR XML PATH('')), 1, 1, N'') AS titles FROM authors a; diff --git a/end2end/MssqlSyntaxMatrix/cases/xml/xml_methods.sql b/end2end/MssqlSyntaxMatrix/cases/xml/xml_methods.sql new file mode 100644 index 000000000..e17ca42e9 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/cases/xml/xml_methods.sql @@ -0,0 +1,10 @@ +-- @description XML column with the value(), query(), exist() and nodes() methods +-- @schema +CREATE TABLE orders_xml (id INT IDENTITY PRIMARY KEY, doc XML NOT NULL); +-- @queries +-- name: XmlAccess :many +SELECT id, doc.value('(/order/@id)[1]', 'INT') AS order_id, doc.query('/order/item') AS items +FROM orders_xml WHERE doc.exist('/order[@status=sql:variable("@status")]') = 1; + +-- name: XmlNodes :many +SELECT o.id, i.n.value('@sku', 'NVARCHAR(20)') AS sku FROM orders_xml o CROSS APPLY o.doc.nodes('/order/item') AS i(n); diff --git a/end2end/MssqlSyntaxMatrix/results.json b/end2end/MssqlSyntaxMatrix/results.json new file mode 100644 index 000000000..66c5e225c --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/results.json @@ -0,0 +1,11652 @@ +[ + { + "id": "aggregate/basic_aggregates", + "description": "COUNT, COUNT_BIG, SUM, AVG, MIN, MAX and their result types", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "BookStats", + "level": "Minor", + "findings": [ + "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column cnt_big: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "BookStats", + "level": "Minor", + "findings": [ + "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column cnt_big: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "BookStats": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "cnt int", + "cnt_big bigint", + "total decimal(38,2)", + "average decimal(38,6)", + "first_published datetime2(7)", + "last_title nvarchar(200)", + "avg_id int" + ] + } + } + }, + { + "id": "aggregate/group_by_having", + "description": "GROUP BY with HAVING on a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ProlificAuthors", + "level": "Minor", + "findings": [ + "Minor: column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ProlificAuthors", + "level": "Minor", + "findings": [ + "Minor: column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ProlificAuthors": { + "paramsError": null, + "columnsError": null, + "params": [ + "@min_books int" + ], + "columns": [ + "author_id bigint NOT NULL", + "book_count int" + ] + } + } + }, + { + "id": "aggregate/rollup_cube_grouping_sets", + "description": "ROLLUP, CUBE, GROUPING SETS and GROUPING()", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Rollup", + "level": "Mismatch", + "findings": [ + "Mismatch: column is_total has no type", + "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable", + "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable", + "Minor: column is_total: sqlc nullable, SQL Server NOT NULL" + ] + }, + { + "name": "Cube", + "level": "Mismatch", + "findings": [ + "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable", + "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable", + "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + }, + { + "name": "GroupingSets", + "level": "Mismatch", + "findings": [ + "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable", + "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable", + "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Rollup", + "level": "Mismatch", + "findings": [ + "Mismatch: column is_total has no type", + "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable", + "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable", + "Minor: column is_total: sqlc nullable, SQL Server NOT NULL" + ] + }, + { + "name": "Cube", + "level": "Mismatch", + "findings": [ + "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable", + "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable", + "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + }, + { + "name": "GroupingSets", + "level": "Mismatch", + "findings": [ + "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable", + "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable", + "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Rollup": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint", + "is_available bit", + "total decimal(38,2)", + "is_total tinyint NOT NULL" + ] + }, + "Cube": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint", + "is_available bit", + "cnt int" + ] + }, + "GroupingSets": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint", + "is_available bit", + "cnt int" + ] + } + } + }, + { + "id": "aggregate/statistical", + "description": "STDEV, VAR and APPROX_COUNT_DISTINCT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "PriceStatistics", + "level": "Mismatch", + "findings": [ + "Mismatch: column stdev has no type", + "Mismatch: column variance has no type", + "Mismatch: column approx_authors has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "PriceStatistics", + "level": "Mismatch", + "findings": [ + "Mismatch: column stdev has no type", + "Mismatch: column variance has no type", + "Mismatch: column approx_authors has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "PriceStatistics": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "stdev float", + "variance float", + "approx_authors bigint" + ] + } + } + }, + { + "id": "aggregate/string_agg", + "description": "STRING_AGG with WITHIN GROUP", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "TitlesPerAuthor", + "level": "Mismatch", + "findings": [ + "Mismatch: column titles has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "TitlesPerAuthor", + "level": "Mismatch", + "findings": [ + "Mismatch: column titles has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "TitlesPerAuthor": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint NOT NULL", + "titles nvarchar(4000)" + ] + } + } + }, + { + "id": "annotations/copyfrom_identity_table", + "description": "\u0060:copyfrom\u0060 into a table with an IDENTITY column", + "schemaError": null, + "builds": { + "upstream": { + "status": "Panic", + "level": "Panic", + "error": "panic: runtime error: invalid memory address or nil pointer dereference\ngithub.com/sqlc-dev/sqlc/internal/sql/validate.validateCopyfrom({0x23937e0?, 0x18a3e1537420?})\ngithub.com/sqlc-dev/sqlc/internal/sql/validate.Cmd({0x23937e0?, 0x18a3e1537420?}, {0x18a3e124c429?, 0x488e7d?}, {0x18a3e124c433?, 0x15000000488e7d?})\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQueryCore(0x18a3e152e108, 0x18a3e1130b80, {0x18a3e124c420?, 0x18a3e112a5a0?}, 0x18a3e112a728)\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQuery(0x18a3e152e108, {0x2392a60, 0x18a3e1130b80}, 0x18a3e1102a80, {{0xe0?}})\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).analyzeStatements.func1(...)", + "queries": [ + { + "name": "CopyBooks", + "level": "Panic", + "findings": [ + "Panic: sqlc panics: panic: runtime error: invalid memory address or nil pointer dereference" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CopyBooks", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CopyBooks": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_id bigint", + "@title nvarchar(200)", + "@price decimal(10,2)", + "@published_at datetime2(7)", + "@is_available bit" + ], + "columns": [] + } + } + }, + { + "id": "annotations/copyfrom_schema_scoped", + "description": "\u0060:copyfrom\u0060 into a table in a user-defined schema", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: expected ), got message", + "queries": [ + { + "name": "CopyEntries", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: expected ), got message" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: expected ), got message", + "queries": [ + { + "name": "CopyEntries", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: expected ), got message" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "CopyEntries": { + "paramsError": null, + "columnsError": null, + "params": [ + "@message nvarchar(200)", + "@level tinyint" + ], + "columns": [] + } + } + }, + { + "id": "annotations/execlastid_output_identity", + "description": "\u0060:execlastid\u0060 via OUTPUT INSERTED.id on an IDENTITY column", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateBookReturnId", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateBookReturnId", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CreateBookReturnId": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_id bigint", + "@title nvarchar(200)" + ], + "columns": [ + "id int NOT NULL" + ] + } + } + }, + { + "id": "annotations/execlastid_scope_identity", + "description": "\u0060:execlastid\u0060 via SELECT SCOPE_IDENTITY() after the INSERT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "CreateAuthorReturnId", + "level": "Mismatch", + "findings": [ + "Mismatch: 0 result columns, SQL Server returns 1 ((unnamed))" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "CreateAuthorReturnId", + "level": "Mismatch", + "findings": [ + "Mismatch: 0 result columns, SQL Server returns 1 ((unnamed))" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CreateAuthorReturnId": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)" + ], + "columns": [ + " bigint" + ] + } + } + }, + { + "id": "annotations/execrows", + "description": "\u0060:execrows\u0060 on UPDATE and DELETE", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DisableAll", + "level": "Ok", + "findings": [] + }, + { + "name": "DeleteUnavailable", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DisableAll", + "level": "Ok", + "findings": [] + }, + { + "name": "DeleteUnavailable", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "DisableAll": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [] + }, + "DeleteUnavailable": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [] + } + } + }, + { + "id": "annotations/one_many_exec", + "description": "\u0060:one\u0060, \u0060:many\u0060, \u0060:exec\u0060 basics", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "GetAuthor", + "level": "Ok", + "findings": [] + }, + { + "name": "ListAuthors", + "level": "Ok", + "findings": [] + }, + { + "name": "DeleteAllAuthors", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "GetAuthor", + "level": "Ok", + "findings": [] + }, + { + "name": "ListAuthors", + "level": "Ok", + "findings": [] + }, + { + "name": "DeleteAllAuthors", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "GetAuthor": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id bigint" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)" + ] + }, + "ListAuthors": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)" + ] + }, + "DeleteAllAuthors": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [] + } + } + }, + { + "id": "batch/declare_variables", + "description": "DECLARE / SET local variables before the final SELECT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "BooksSinceDaysAgo", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "BooksSinceDaysAgo", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "BooksSinceDaysAgo": { + "paramsError": null, + "columnsError": null, + "params": [ + "@days int" + ], + "columns": [ + "id int NOT NULL", + "title nvarchar(200) NOT NULL" + ] + } + } + }, + { + "id": "batch/select_into_temp_table", + "description": "SELECT ... INTO #temp then reading it, in one query", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ExpensiveBooksViaTemp", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ExpensiveBooksViaTemp", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ExpensiveBooksViaTemp": { + "paramsError": "Invalid object name \u0027#expensive\u0027.\nThe batch could not be analyzed because of compile errors.", + "columnsError": "The metadata could not be determined because statement \u0027SELECT id, title FROM #expensive\u0027 uses a temp table. Metadata discovery only supports temp tables when analyzing a single-statement batch.", + "params": null, + "columns": null + } + } + }, + { + "id": "batch/set_nocount_on", + "description": "SET NOCOUNT ON in front of a query (a very common T-SQL habit)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "ListAuthorsNoCount", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "ListAuthorsNoCount", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "ListAuthorsNoCount": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "batch/transaction_in_query", + "description": "Explicit BEGIN TRAN / COMMIT inside a query", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "MoveBooks", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "MoveBooks", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "MoveBooks": { + "paramsError": null, + "columnsError": null, + "params": [ + "@to_author_id bigint", + "@from_author_id bigint" + ], + "columns": [] + } + } + }, + { + "id": "batch/try_catch_throw", + "description": "TRY ... CATCH with THROW", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "SafeDelete", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "SafeDelete", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "SafeDelete": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id bigint" + ], + "columns": [] + } + } + }, + { + "id": "cte/cte_with_dml", + "description": "CTEs in front of UPDATE and DELETE", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "DiscountTopBooks", + "level": "Minor", + "findings": [ + "Minor: parameter @n: sqlc int, SQL Server bigint" + ] + }, + { + "name": "DeleteDuplicates", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "DiscountTopBooks", + "level": "Minor", + "findings": [ + "Minor: parameter @n: sqlc int, SQL Server bigint" + ] + }, + { + "name": "DeleteDuplicates", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "DiscountTopBooks": { + "paramsError": null, + "columnsError": null, + "params": [ + "@n bigint" + ], + "columns": [] + }, + "DeleteDuplicates": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [] + } + } + }, + { + "id": "cte/multiple_ctes", + "description": "Several CTEs referencing each other", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "AuthorTotals", + "level": "Mismatch", + "findings": [ + "Mismatch: column rnk has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "AuthorTotals", + "level": "Mismatch", + "findings": [ + "Mismatch: column rnk has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "AuthorTotals": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "name nvarchar(100) NOT NULL", + "total decimal(38,2)", + "rnk bigint" + ] + } + } + }, + { + "id": "cte/recursive_cte", + "description": "Recursive CTE with UNION ALL", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: with numbers: relation \u0022public\u0022.\u0022numbers\u0022: class \u0022numbers\u0022: sql: no rows in result set", + "queries": [ + { + "name": "Numbers", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: with numbers: relation \u0022public\u0022.\u0022numbers\u0022: class \u0022numbers\u0022: sql: no rows in result set" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: with numbers: relation \u0022public\u0022.\u0022numbers\u0022: class \u0022numbers\u0022: sql: no rows in result set", + "queries": [ + { + "name": "Numbers", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: with numbers: relation \u0022public\u0022.\u0022numbers\u0022: class \u0022numbers\u0022: sql: no rows in result set" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "Numbers": { + "paramsError": null, + "columnsError": null, + "params": [ + "@upto int" + ], + "columns": [ + "n int" + ] + } + } + }, + { + "id": "cte/simple_cte", + "description": "A single common table expression", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CheapBooks", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CheapBooks", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CheapBooks": { + "paramsError": null, + "columnsError": null, + "params": [ + "@max_price decimal(38,19)" + ], + "columns": [ + "id int NOT NULL", + "title nvarchar(200) NOT NULL" + ] + } + } + }, + { + "id": "ddl/alias_type", + "description": "User-defined alias type (CREATE TYPE ... FROM)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListContacts", + "level": "Mismatch", + "findings": [ + "Mismatch: column email: sqlc email_address, SQL Server nvarchar(320)" + ] + }, + { + "name": "GetContactByEmail", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @email: sqlc email_address, SQL Server nvarchar(320)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListContacts", + "level": "Mismatch", + "findings": [ + "Mismatch: column email: sqlc email_address, SQL Server nvarchar(320)" + ] + }, + { + "name": "GetContactByEmail", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @email: sqlc email_address, SQL Server nvarchar(320)" + ] + } + ] + } + }, + "plugin": "Error", + "pluginError": "error generating code: process: error running command Unhandled exception. System.NotSupportedException: Column email has unsupported column type: email_address in MssqlDriver\nerror generating code: process: error running command Unhandled exception. System.NotSupportedException: Column email has unsupported column type: email_address in MssqlDriver", + "compile": "Skipped", + "compileError": "", + "oracle": { + "ListContacts": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "email nvarchar(320) NOT NULL" + ] + }, + "GetContactByEmail": { + "paramsError": null, + "columnsError": null, + "params": [ + "@email nvarchar(320)" + ], + "columns": [ + "id int NOT NULL" + ] + } + } + }, + { + "id": "ddl/alter_column_type_and_nullability", + "description": "ALTER TABLE ... ALTER COLUMN changes the type and nullability", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListBios", + "level": "Minor", + "findings": [ + "Minor: column bio: sqlc nullable, SQL Server NOT NULL" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListBios", + "level": "Minor", + "findings": [ + "Minor: column bio: sqlc nullable, SQL Server NOT NULL" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListBios": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "bio nvarchar(500) NOT NULL" + ] + } + } + }, + { + "id": "ddl/alter_table_add_column", + "description": "ALTER TABLE ... ADD column", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListNicknames", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListNicknames", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListNicknames": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "nickname nvarchar(50)" + ] + } + } + }, + { + "id": "ddl/alter_table_drop_column", + "description": "ALTER TABLE ... DROP COLUMN", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthors", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthors", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListAuthors": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)" + ] + } + } + }, + { + "id": "ddl/bracketed_identifiers", + "description": "Bracket-quoted table and column names with spaces and reserved words", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListOrderDetails", + "level": "Minor", + "findings": [ + "Minor: column 3 is named unit price, SQL Server keeps the case: Unit Price" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListOrderDetails", + "level": "Minor", + "findings": [ + "Minor: column 3 is named unit price, SQL Server keeps the case: Unit Price" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListOrderDetails": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "order id int NOT NULL", + "select nvarchar(10)", + "Unit Price money NOT NULL" + ] + } + } + }, + { + "id": "ddl/case_insensitive_names", + "description": "Object names are case-insensitive under the default collation", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListAuthorsUpper", + "level": "Minor", + "findings": [ + "Minor: column 1 is named id, SQL Server keeps the case: ID", + "Minor: column 2 is named name, SQL Server keeps the case: NAME" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListAuthorsUpper", + "level": "Minor", + "findings": [ + "Minor: column 1 is named id, SQL Server keeps the case: ID", + "Minor: column 2 is named name, SQL Server keeps the case: NAME" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListAuthorsUpper": { + "paramsError": null, + "columnsError": null, + "params": [ + "@Name nvarchar(100)" + ], + "columns": [ + "ID bigint NOT NULL", + "NAME nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "ddl/check_and_unique_constraints", + "description": "CHECK and UNIQUE constraints, column and table level", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListProducts", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListProducts", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListProducts": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "sku varchar(20) NOT NULL", + "price decimal(10,2) NOT NULL", + "discount decimal(10,2)" + ] + } + } + }, + { + "id": "ddl/collate_and_sparse", + "description": "COLLATE and SPARSE column options", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListTags", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListTags", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListTags": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "code varchar(20) NOT NULL", + "note nvarchar(100)" + ] + } + } + }, + { + "id": "ddl/computed_columns", + "description": "Computed columns, plain and PERSISTED", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "schema.sql:1:1: column people.full_name: missing type name", + "queries": [ + { + "name": "ListPeople", + "level": "Error", + "findings": [ + "Error: schema.sql:1:1: column people.full_name: missing type name" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "schema.sql:1:1: column people.full_name: missing type name", + "queries": [ + { + "name": "ListPeople", + "level": "Error", + "findings": [ + "Error: schema.sql:1:1: column people.full_name: missing type name" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "ListPeople": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "first_name nvarchar(50) NOT NULL", + "last_name nvarchar(50) NOT NULL", + "full_name nvarchar(101) NOT NULL", + "name_length int" + ] + } + } + }, + { + "id": "ddl/drop_table_if_exists", + "description": "DROP TABLE IF EXISTS followed by a re-create with a different shape", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListScratch", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListScratch", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListScratch": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "label nvarchar(10) NOT NULL" + ] + } + } + }, + { + "id": "ddl/foreign_key_on_delete", + "description": "Table-level FOREIGN KEY with ON DELETE CASCADE / SET NULL", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListReviews", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListReviews", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListReviews": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "book_id int NOT NULL", + "reviewer_id bigint" + ] + } + } + }, + { + "id": "ddl/go_batch_separators", + "description": "Schema split into several batches with GO", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListRegions", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListRegions", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListRegions": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "name nvarchar(50) NOT NULL", + "country nvarchar(50) NOT NULL" + ] + } + } + }, + { + "id": "ddl/identity_seed_increment", + "description": "IDENTITY(seed, increment) column on a non-default seed", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListInvoices", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListInvoices", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListInvoices": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "number nvarchar(20) NOT NULL" + ] + } + } + }, + { + "id": "ddl/indexes", + "description": "CREATE INDEX statements (filtered, INCLUDE, unique nonclustered) are accepted and ignored", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAvailableBooks", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAvailableBooks", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListAvailableBooks": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "title nvarchar(200) NOT NULL" + ] + } + } + }, + { + "id": "ddl/named_default_constraints", + "description": "Named DEFAULT constraints with GETDATE() and NEWID()", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListEvents", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListEvents", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListEvents": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "created_at datetime2(7) NOT NULL", + "correlation_id uniqueidentifier NOT NULL" + ] + } + } + }, + { + "id": "ddl/rowversion", + "description": "ROWVERSION column used for optimistic concurrency", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "GetDocument", + "level": "Ok", + "findings": [] + }, + { + "name": "UpdateDocument", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "GetDocument", + "level": "Ok", + "findings": [] + }, + { + "name": "UpdateDocument", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "GetDocument": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id int" + ], + "columns": [ + "id int NOT NULL", + "body nvarchar(max) NOT NULL", + "version timestamp NOT NULL" + ] + }, + "UpdateDocument": { + "paramsError": null, + "columnsError": null, + "params": [ + "@body nvarchar(max)", + "@id int", + "@version timestamp" + ], + "columns": [] + } + } + }, + { + "id": "ddl/same_table_name_two_schemas", + "description": "Two tables with the same name in different schemas", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuditAuthors", + "level": "Ok", + "findings": [] + }, + { + "name": "ListAuthors", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuditAuthors", + "level": "Ok", + "findings": [] + }, + { + "name": "ListAuthors", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListAuditAuthors": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "changed_at datetime2(7) NOT NULL" + ] + }, + "ListAuthors": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "ddl/schema_scoped_table", + "description": "Table in a user-defined schema (CREATE SCHEMA in its own batch)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListOrders", + "level": "Ok", + "findings": [] + }, + { + "name": "ListOrdersWithAuthor", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListOrders", + "level": "Ok", + "findings": [] + }, + { + "name": "ListOrdersWithAuthor", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListOrders": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "author_id bigint NOT NULL", + "total money NOT NULL" + ] + }, + "ListOrdersWithAuthor": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "ddl/sequence_default", + "description": "SEQUENCE used as a column default and with NEXT VALUE FOR in a query", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListTickets", + "level": "Ok", + "findings": [] + }, + { + "name": "NextTicketId", + "level": "Mismatch", + "findings": [ + "Mismatch: column next_id has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListTickets", + "level": "Ok", + "findings": [] + }, + { + "name": "NextTicketId", + "level": "Mismatch", + "findings": [ + "Mismatch: column next_id has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListTickets": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "title nvarchar(100) NOT NULL" + ] + }, + "NextTicketId": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "next_id bigint NOT NULL" + ] + } + } + }, + { + "id": "ddl/sp_rename_column", + "description": "Column renamed with sp_rename (how T-SQL renames columns)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unknown column \u0022biography\u0022", + "queries": [ + { + "name": "ListBiographies", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unknown column \u0022biography\u0022" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unknown column \u0022biography\u0022", + "queries": [ + { + "name": "ListBiographies", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unknown column \u0022biography\u0022" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "ListBiographies": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "biography nvarchar(max)" + ] + } + } + }, + { + "id": "ddl/synonym", + "description": "CREATE SYNONYM for a table", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: relation \u0022public\u0022.\u0022writers\u0022: class \u0022writers\u0022: sql: no rows in result set", + "queries": [ + { + "name": "ListWriters", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: relation \u0022public\u0022.\u0022writers\u0022: class \u0022writers\u0022: sql: no rows in result set" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: relation \u0022public\u0022.\u0022writers\u0022: class \u0022writers\u0022: sql: no rows in result set", + "queries": [ + { + "name": "ListWriters", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: relation \u0022public\u0022.\u0022writers\u0022: class \u0022writers\u0022: sql: no rows in result set" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "ListWriters": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "ddl/table_level_primary_key", + "description": "Columns in a table-level (composite) PRIMARY KEY are implicitly NOT NULL", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListMemberships", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListMemberships", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListMemberships": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "group_id int NOT NULL", + "user_id int NOT NULL", + "role nvarchar(20)" + ] + } + } + }, + { + "id": "ddl/temporal_table", + "description": "System-versioned temporal table and FOR SYSTEM_TIME AS OF", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListPricesAsOf", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @as_of (datetime2(7)) is missing" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListPricesAsOf", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @as_of (datetime2(7)) is missing" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListPricesAsOf": { + "paramsError": null, + "columnsError": null, + "params": [ + "@as_of datetime2(7)" + ], + "columns": [ + "id int NOT NULL", + "amount decimal(10,2) NOT NULL" + ] + } + } + }, + { + "id": "ddl/view", + "description": "Querying a VIEW", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: relation \u0022public\u0022.\u0022author_books\u0022: class \u0022author_books\u0022: sql: no rows in result set", + "queries": [ + { + "name": "ListAuthorBooks", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: relation \u0022public\u0022.\u0022author_books\u0022: class \u0022author_books\u0022: sql: no rows in result set" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: relation \u0022public\u0022.\u0022author_books\u0022: class \u0022author_books\u0022: sql: no rows in result set", + "queries": [ + { + "name": "ListAuthorBooks", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: relation \u0022public\u0022.\u0022author_books\u0022: class \u0022author_books\u0022: sql: no rows in result set" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "ListAuthorBooks": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_name nvarchar(100)" + ], + "columns": [ + "author_name nvarchar(100) NOT NULL", + "title nvarchar(200) NOT NULL" + ] + } + } + }, + { + "id": "delete/from_join", + "description": "DELETE ... FROM with a JOIN", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DeleteBooksByAuthorName", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DeleteBooksByAuthorName", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "DeleteBooksByAuthorName": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_name nvarchar(100)" + ], + "columns": [] + } + } + }, + { + "id": "delete/output_deleted", + "description": "DELETE ... OUTPUT DELETED.*", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DeleteBooksOfAuthor", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DeleteBooksOfAuthor", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "DeleteBooksOfAuthor": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_id bigint" + ], + "columns": [ + "id int NOT NULL", + "author_id bigint NOT NULL", + "title nvarchar(200) NOT NULL", + "price decimal(10,2)", + "published_at datetime2(7)", + "is_available bit NOT NULL" + ] + } + } + }, + { + "id": "delete/simple", + "description": "DELETE with a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DeleteAuthor", + "level": "Ok", + "findings": [] + }, + { + "name": "DeleteAuthorNoFrom", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DeleteAuthor", + "level": "Ok", + "findings": [] + }, + { + "name": "DeleteAuthorNoFrom", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "DeleteAuthor": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id bigint" + ], + "columns": [] + }, + "DeleteAuthorNoFrom": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id bigint" + ], + "columns": [] + } + } + }, + { + "id": "delete/top", + "description": "DELETE TOP (@n)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "DeleteSomeBooks", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @n (bigint) is missing" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "DeleteSomeBooks", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @n (bigint) is missing" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "DeleteSomeBooks": { + "paramsError": null, + "columnsError": null, + "params": [ + "@n bigint" + ], + "columns": [] + } + } + }, + { + "id": "delete/truncate", + "description": "TRUNCATE TABLE", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "TruncateBooksArchive", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "TruncateBooksArchive", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "TruncateBooksArchive": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [] + } + } + }, + { + "id": "expressions/arithmetic", + "description": "Arithmetic operators, integer division, modulo and type promotion", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Arithmetic", + "level": "Mismatch", + "findings": [ + "Mismatch: column doubled: sqlc bigint, SQL Server decimal(12,2)", + "Minor: column half_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column remainder: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column negated: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Arithmetic", + "level": "Mismatch", + "findings": [ + "Mismatch: column doubled: sqlc bigint, SQL Server decimal(12,2)", + "Minor: column half_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column remainder: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column negated: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Arithmetic": { + "paramsError": null, + "columnsError": null, + "params": [ + "@delta numeric(38,19)" + ], + "columns": [ + "id int NOT NULL", + "doubled decimal(12,2)", + "half_id int", + "remainder int", + "adjusted numeric(38,19)", + "negated int" + ] + } + } + }, + { + "id": "expressions/at_time_zone", + "description": "AT TIME ZONE and SWITCHOFFSET", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "InTimeZone", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @time_zone (nvarchar(4000)) is missing", + "Mismatch: column local_time has no type", + "Mismatch: column switched has no type", + "Minor: column local_time: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "InTimeZone", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @time_zone (nvarchar(4000)) is missing", + "Mismatch: column local_time has no type", + "Mismatch: column switched has no type", + "Minor: column local_time: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "InTimeZone": { + "paramsError": null, + "columnsError": null, + "params": [ + "@time_zone nvarchar(4000)" + ], + "columns": [ + "local_time datetimeoffset(7)", + "switched datetimeoffset(7)" + ] + } + } + }, + { + "id": "expressions/bitwise", + "description": "Bitwise operators and bit columns", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "Bitwise", + "level": "Minor", + "findings": [ + "Minor: column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "Bitwise", + "level": "Minor", + "findings": [ + "Minor: column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Bitwise": { + "paramsError": null, + "columnsError": null, + "params": [ + "@mask int" + ], + "columns": [ + "id int NOT NULL", + "low_bit int NOT NULL", + "masked int", + "xored int NOT NULL", + "not_available bit NOT NULL" + ] + } + } + }, + { + "id": "expressions/cast_convert", + "description": "CAST, CONVERT (with a style), TRY_CAST, TRY_CONVERT, PARSE and TRY_PARSE", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Conversions", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @text (nvarchar(4000)) is missing", + "Mismatch: column parsed has no type", + "Mismatch: column maybe_date has no type", + "Minor: column id_text: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column title_number: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column amount: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column parsed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column maybe_date: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Conversions", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @text (nvarchar(4000)) is missing", + "Mismatch: column parsed has no type", + "Mismatch: column maybe_date has no type", + "Minor: column id_text: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column title_number: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column amount: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column parsed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column maybe_date: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Conversions": { + "paramsError": null, + "columnsError": null, + "params": [ + "@amount decimal(10,2)", + "@text nvarchar(4000)" + ], + "columns": [ + "id_text nvarchar(20)", + "iso_date varchar(10)", + "title_number int", + "amount decimal(10,2)", + "parsed date", + "maybe_date datetime2(7)" + ] + } + } + }, + { + "id": "expressions/coalesce_isnull_nullif", + "description": "COALESCE, ISNULL and NULLIF: result types, nullability, and parameter inference", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Fallbacks", + "level": "Mismatch", + "findings": [ + "Mismatch: column bio_or_empty has no type", + "Mismatch: column name_or_null has no type", + "Minor: column bio_or_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column bio_or_empty: sqlc nullable, SQL Server NOT NULL" + ] + }, + { + "name": "ByPattern", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @pattern has no type (SQL Server cannot deduce it either)" + ] + }, + { + "name": "ByPatternIsNull", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @pattern has no type (SQL Server: nvarchar(4000))" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Fallbacks", + "level": "Mismatch", + "findings": [ + "Mismatch: column bio_or_empty has no type", + "Mismatch: column name_or_null has no type", + "Minor: column bio_or_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column bio_or_empty: sqlc nullable, SQL Server NOT NULL" + ] + }, + { + "name": "ByPattern", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @pattern has no type (SQL Server cannot deduce it either)" + ] + }, + { + "name": "ByPatternIsNull", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @pattern has no type (SQL Server: nvarchar(4000))" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Fallbacks": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "bio_or_name nvarchar(max)", + "bio_or_empty nvarchar(max) NOT NULL", + "name_or_null nvarchar(100)" + ] + }, + "ByPattern": { + "paramsError": "The type for parameter \u0027@pattern\u0027 cannot be deduced in this context.", + "columnsError": null, + "params": null, + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + }, + "ByPatternIsNull": { + "paramsError": null, + "columnsError": null, + "params": [ + "@pattern nvarchar(4000)" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "expressions/concat_format", + "description": "CONCAT, CONCAT_WS, FORMAT, QUOTENAME, TRANSLATE", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Formatting", + "level": "Mismatch", + "findings": [ + "Mismatch: column label has no type", + "Mismatch: column joined has no type", + "Mismatch: column formatted has no type", + "Mismatch: column quoted has no type", + "Mismatch: column translated has no type", + "Minor: column label: sqlc nullable, SQL Server NOT NULL", + "Minor: column joined: sqlc nullable, SQL Server NOT NULL" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Formatting", + "level": "Mismatch", + "findings": [ + "Mismatch: column label has no type", + "Mismatch: column joined has no type", + "Mismatch: column formatted has no type", + "Mismatch: column quoted has no type", + "Mismatch: column translated has no type", + "Minor: column label: sqlc nullable, SQL Server NOT NULL", + "Minor: column joined: sqlc nullable, SQL Server NOT NULL" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Formatting": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "label nvarchar(127) NOT NULL", + "joined nvarchar(max) NOT NULL", + "formatted nvarchar(4000)", + "quoted nvarchar(258)", + "translated nvarchar(4000)" + ] + } + } + }, + { + "id": "expressions/datetime_functions", + "description": "GETDATE, SYSDATETIME, SYSDATETIMEOFFSET, DATEADD, DATEDIFF, DATEDIFF_BIG, DATEPART, DATENAME, EOMONTH, DATEFROMPARTS, DATETRUNC", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unknown column \u0022day\u0022", + "queries": [ + { + "name": "DateFunctions", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unknown column \u0022day\u0022" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unknown column \u0022day\u0022", + "queries": [ + { + "name": "DateFunctions", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unknown column \u0022day\u0022" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "DateFunctions": { + "paramsError": null, + "columnsError": null, + "params": [ + "@days int", + "@until datetimeoffset(7)" + ], + "columns": [ + "now_local datetime NOT NULL", + "now_utc datetime NOT NULL", + "precise datetime2(7) NOT NULL", + "with_offset datetimeoffset(7) NOT NULL", + "shifted datetime2(7)", + "days_between int", + "seconds_since bigint", + "year_part int", + "month_name nvarchar(30)", + "y int", + "month_end date", + "fixed_date date NOT NULL", + "month_start datetime2(7)" + ] + } + } + }, + { + "id": "expressions/greatest_least", + "description": "GREATEST and LEAST (SQL Server 2022)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Clamp", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @floor has no type (SQL Server: numeric(38,19))", + "Mismatch: parameter @ceiling has no type (SQL Server: numeric(38,19))", + "Mismatch: column at_least has no type", + "Mismatch: column at_most has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Clamp", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @floor has no type (SQL Server: numeric(38,19))", + "Mismatch: parameter @ceiling has no type (SQL Server: numeric(38,19))", + "Mismatch: column at_least has no type", + "Mismatch: column at_most has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Clamp": { + "paramsError": null, + "columnsError": null, + "params": [ + "@floor numeric(38,19)", + "@ceiling numeric(38,19)" + ], + "columns": [ + "id int NOT NULL", + "at_least decimal(38,19)", + "at_most decimal(38,19)" + ] + } + } + }, + { + "id": "expressions/is_distinct_from", + "description": "IS [NOT] DISTINCT FROM (SQL Server 2022)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ChangedBios", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @bio (nvarchar(max)) is missing" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ChangedBios", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @bio (nvarchar(max)) is missing" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ChangedBios": { + "paramsError": null, + "columnsError": null, + "params": [ + "@bio nvarchar(max)" + ], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "expressions/math_functions", + "description": "ABS, ROUND, CEILING, FLOOR, POWER, SQRT, SIGN, PI, RAND", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "MathFunctions", + "level": "Mismatch", + "findings": [ + "Mismatch: column abs_price has no type", + "Mismatch: column rounded has no type", + "Mismatch: column ceil has no type", + "Mismatch: column floor_price has no type", + "Mismatch: column squared has no type", + "Mismatch: column root has no type", + "Mismatch: column sign has no type", + "Mismatch: column pi has no type", + "Mismatch: column random has no type", + "Minor: column pi: sqlc nullable, SQL Server NOT NULL" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "MathFunctions", + "level": "Mismatch", + "findings": [ + "Mismatch: column abs_price has no type", + "Mismatch: column rounded has no type", + "Mismatch: column ceil has no type", + "Mismatch: column floor_price has no type", + "Mismatch: column squared has no type", + "Mismatch: column root has no type", + "Mismatch: column sign has no type", + "Mismatch: column pi has no type", + "Mismatch: column random has no type", + "Minor: column pi: sqlc nullable, SQL Server NOT NULL" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "MathFunctions": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "abs_price decimal(10,2)", + "rounded decimal(10,2)", + "ceil decimal(10,0)", + "floor_price decimal(10,0)", + "squared int", + "root float", + "sign decimal(10,2)", + "pi float NOT NULL", + "random float" + ] + } + } + }, + { + "id": "expressions/sql2022_functions", + "description": "GENERATE_SERIES, DATE_BUCKET, TRIM ... FROM and STRING_ESCAPE (SQL Server 2022)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022", + "queries": [ + { + "name": "Series", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022" + ] + }, + { + "name": "NewFunctions", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022", + "queries": [ + { + "name": "Series", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022" + ] + }, + { + "name": "NewFunctions", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "Series": { + "paramsError": "Argument data type void type is invalid for argument 2 of generate_series function.\nOperand type clash: int is incompatible with void type\nThe batch could not be analyzed because of compile errors.", + "columnsError": "Argument data type void type is invalid for argument 2 of generate_series function.\nOperand type clash: int is incompatible with void type\nThe batch could not be analyzed because of compile errors.", + "params": null, + "columns": null + }, + "NewFunctions": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "week_bucket datetime2(7)", + "trimmed nvarchar(200)", + "escaped nvarchar(max)" + ] + } + } + }, + { + "id": "expressions/string_functions", + "description": "LEN, DATALENGTH, SUBSTRING, LEFT, RIGHT, UPPER, LOWER, TRIM, REPLACE, CHARINDEX, PATINDEX, REVERSE, REPLICATE, STUFF", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "StringFunctions", + "level": "Mismatch", + "findings": [ + "Mismatch: column prefix has no type", + "Mismatch: column left2 has no type", + "Mismatch: column right2 has no type", + "Mismatch: column replaced has no type", + "Mismatch: column pos has no type", + "Mismatch: column digit_pos has no type", + "Mismatch: column reversed has no type", + "Mismatch: column stars has no type", + "Mismatch: column stuffed has no type", + "Minor: column len: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column bytes: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column left2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column right2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column upper_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column lower_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column trimmed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column trimmed2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "StringFunctions", + "level": "Mismatch", + "findings": [ + "Mismatch: column prefix has no type", + "Mismatch: column left2 has no type", + "Mismatch: column right2 has no type", + "Mismatch: column replaced has no type", + "Mismatch: column pos has no type", + "Mismatch: column digit_pos has no type", + "Mismatch: column reversed has no type", + "Mismatch: column stars has no type", + "Mismatch: column stuffed has no type", + "Minor: column len: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column bytes: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column left2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column right2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column upper_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column lower_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column trimmed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column trimmed2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "StringFunctions": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "len int", + "bytes int", + "prefix nvarchar(3)", + "left2 nvarchar(2)", + "right2 nvarchar(2)", + "upper_name nvarchar(100)", + "lower_name nvarchar(100)", + "trimmed nvarchar(100)", + "trimmed2 nvarchar(100)", + "replaced nvarchar(4000)", + "pos int", + "digit_pos int", + "reversed nvarchar(100)", + "stars nvarchar(3)", + "stuffed nvarchar(100)" + ] + } + } + }, + { + "id": "expressions/system_functions", + "description": "NEWID, @@ROWCOUNT, SCOPE_IDENTITY, IDENT_CURRENT, DB_NAME, SUSER_SNAME, HASHBYTES, CHECKSUM", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "SystemValues", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @payload has no type (SQL Server: nvarchar(4000))", + "Mismatch: column row_count has no type", + "Mismatch: column current_id has no type", + "Mismatch: column db has no type", + "Mismatch: column login has no type", + "Mismatch: column hash has no type", + "Mismatch: column checksum has no type", + "Minor: column id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column scope_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "SystemValues", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @payload has no type (SQL Server: nvarchar(4000))", + "Mismatch: column row_count has no type", + "Mismatch: column current_id has no type", + "Mismatch: column db has no type", + "Mismatch: column login has no type", + "Mismatch: column hash has no type", + "Mismatch: column checksum has no type", + "Minor: column id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)", + "Minor: column scope_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SystemValues": { + "paramsError": null, + "columnsError": null, + "params": [ + "@payload nvarchar(4000)" + ], + "columns": [ + "id uniqueidentifier", + "row_count int NOT NULL", + "scope_id numeric(38,0)", + "current_id numeric(38,0)", + "db nvarchar(128)", + "login nvarchar(128)", + "hash varbinary(8000)", + "checksum int" + ] + } + } + }, + { + "id": "hints/query_hints", + "description": "OPTION (RECOMPILE), OPTION (MAXDOP 1), OPTION (OPTIMIZE FOR UNKNOWN)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "Recompiled", + "level": "Ok", + "findings": [] + }, + { + "name": "SingleThreaded", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "Recompiled", + "level": "Ok", + "findings": [] + }, + { + "name": "SingleThreaded", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Recompiled": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_id bigint" + ], + "columns": [ + "id int NOT NULL" + ] + }, + "SingleThreaded": { + "paramsError": null, + "columnsError": null, + "params": [ + "@min_price decimal(38,19)" + ], + "columns": [ + "id int NOT NULL" + ] + } + } + }, + { + "id": "hints/table_hints", + "description": "Table hints: NOLOCK, UPDLOCK, ROWLOCK, READPAST, FORCESEEK", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ReadUncommitted", + "level": "Ok", + "findings": [] + }, + { + "name": "LockForUpdate", + "level": "Ok", + "findings": [] + }, + { + "name": "DequeueLike", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ReadUncommitted", + "level": "Ok", + "findings": [] + }, + { + "name": "LockForUpdate", + "level": "Ok", + "findings": [] + }, + { + "name": "DequeueLike", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ReadUncommitted": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL" + ] + }, + "LockForUpdate": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id bigint" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + }, + "DequeueLike": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL" + ] + } + } + }, + { + "id": "identifiers/at_sign_parameter_names", + "description": "Parameter names with digits, underscores and mixed case", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "OddParams", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "OddParams", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "OddParams": { + "paramsError": null, + "columnsError": null, + "params": [ + "@Id1 bigint", + "@first_Name nvarchar(100)", + "@_bio nvarchar(max)" + ], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "identifiers/duplicate_result_names", + "description": "Two result columns with the same name", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DuplicateIds", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "DuplicateIds", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "DuplicateIds": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "id int NOT NULL" + ] + } + } + }, + { + "id": "identifiers/mixed_case_names", + "description": "Mixed-case table and column names map to PascalCase", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListMixed", + "level": "Minor", + "findings": [ + "Minor: column 1 is named customerid, SQL Server keeps the case: CustomerID", + "Minor: column 2 is named firstname, SQL Server keeps the case: firstName", + "Minor: column 3 is named last_name, SQL Server keeps the case: LAST_NAME" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListMixed", + "level": "Minor", + "findings": [ + "Minor: column 1 is named customerid, SQL Server keeps the case: CustomerID", + "Minor: column 2 is named firstname, SQL Server keeps the case: firstName", + "Minor: column 3 is named last_name, SQL Server keeps the case: LAST_NAME" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListMixed": { + "paramsError": null, + "columnsError": null, + "params": [ + "@CustomerID int" + ], + "columns": [ + "CustomerID int NOT NULL", + "firstName nvarchar(50) NOT NULL", + "LAST_NAME nvarchar(50) NOT NULL" + ] + } + } + }, + { + "id": "identifiers/non_identifier_column_names", + "description": "Column names that are not valid C# identifiers: leading digits, spaces, dashes, Unicode", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListOdd", + "level": "Minor", + "findings": [ + "Minor: column 4 is named total $, SQL Server keeps the case: Total $" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListOdd", + "level": "Minor", + "findings": [ + "Minor: column 4 is named total $, SQL Server keeps the case: Total $" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListOdd": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "1st place int", + "e-mail nvarchar(50)", + "na\u00EFve bit", + "Total $ money" + ] + } + } + }, + { + "id": "identifiers/reserved_word_columns", + "description": "Columns named after T-SQL and C# keywords", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListKeywords", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListKeywords", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListKeywords": { + "paramsError": null, + "columnsError": null, + "params": [ + "@user nvarchar(20)" + ], + "columns": [ + "user nvarchar(20) NOT NULL", + "order int NOT NULL", + "key int", + "class int", + "string nvarchar(10)" + ] + } + } + }, + { + "id": "identifiers/unnamed_expression_column", + "description": "A result column without a name", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "Unnamed", + "level": "Minor", + "findings": [ + "Minor: column 1 has no name in SQL Server (sqlc calls it count)", + "Minor: column count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "Unnamed", + "level": "Minor", + "findings": [ + "Minor: column 1 has no name in SQL Server (sqlc calls it count)", + "Minor: column count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Unnamed": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + " int" + ] + } + } + }, + { + "id": "insert/default_values", + "description": "INSERT ... DEFAULT VALUES and explicit DEFAULT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateEmptyEvent", + "level": "Ok", + "findings": [] + }, + { + "name": "CreateEventWithDefault", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateEmptyEvent", + "level": "Ok", + "findings": [] + }, + { + "name": "CreateEventWithDefault", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CreateEmptyEvent": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [] + }, + "CreateEventWithDefault": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [] + } + } + }, + { + "id": "insert/identity_insert", + "description": "Explicit values for an IDENTITY column with SET IDENTITY_INSERT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "CreateAuthorWithId", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "CreateAuthorWithId", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "CreateAuthorWithId": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id bigint", + "@name nvarchar(100)" + ], + "columns": [] + } + } + }, + { + "id": "insert/insert_exec", + "description": "INSERT ... EXEC from a stored procedure", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ArchiveAuthors", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ArchiveAuthors", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ArchiveAuthors": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [] + } + } + }, + { + "id": "insert/insert_select", + "description": "INSERT ... SELECT with a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "CopyBooksToAuthor", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @target_author_id has no type (SQL Server: bigint)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "CopyBooksToAuthor", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @target_author_id has no type (SQL Server: bigint)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CopyBooksToAuthor": { + "paramsError": null, + "columnsError": null, + "params": [ + "@target_author_id bigint", + "@source_author_id bigint" + ], + "columns": [] + } + } + }, + { + "id": "insert/multi_row_values", + "description": "INSERT with several VALUES rows", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateTwoAuthors", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateTwoAuthors", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CreateTwoAuthors": { + "paramsError": null, + "columnsError": null, + "params": [ + "@first_name nvarchar(100)", + "@first_bio nvarchar(max)", + "@second_name nvarchar(100)", + "@second_bio nvarchar(max)" + ], + "columns": [] + } + } + }, + { + "id": "insert/output_inserted", + "description": "INSERT ... OUTPUT INSERTED.* / INSERTED.col returning rows", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateAuthorReturningAll", + "level": "Ok", + "findings": [] + }, + { + "name": "CreateAuthorReturningSome", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateAuthorReturningAll", + "level": "Ok", + "findings": [] + }, + { + "name": "CreateAuthorReturningSome", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CreateAuthorReturningAll": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)", + "@bio nvarchar(max)" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)", + "birth_date date" + ] + }, + "CreateAuthorReturningSome": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "insert/output_into_table_variable", + "description": "OUTPUT ... INTO a table variable inside a multi-statement query", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "CreateAuthorOutputInto", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "CreateAuthorOutputInto", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "CreateAuthorOutputInto": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)" + ], + "columns": [ + "id bigint" + ] + } + } + }, + { + "id": "insert/schema_scoped", + "description": "INSERT into a table in a user-defined schema", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: expected ), got message", + "queries": [ + { + "name": "CreateLog", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: expected ), got message" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: expected ), got message", + "queries": [ + { + "name": "CreateLog", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: expected ), got message" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "CreateLog": { + "paramsError": null, + "columnsError": null, + "params": [ + "@message nvarchar(200)" + ], + "columns": [] + } + } + }, + { + "id": "insert/values", + "description": "INSERT ... VALUES with parameters for every column", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateAuthor", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CreateAuthor", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CreateAuthor": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)", + "@bio nvarchar(max)", + "@birth_date date" + ], + "columns": [] + } + } + }, + { + "id": "joins/cross_apply", + "description": "CROSS APPLY and OUTER APPLY with a correlated TOP subquery", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "LatestBookPerAuthor", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO" + ] + }, + { + "name": "LatestBookPerAuthorOuter", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "LatestBookPerAuthor", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO" + ] + }, + { + "name": "LatestBookPerAuthorOuter", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "LatestBookPerAuthor": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "title nvarchar(200) NOT NULL" + ] + }, + "LatestBookPerAuthorOuter": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "title nvarchar(200)" + ] + } + } + }, + { + "id": "joins/cross_join", + "description": "CROSS JOIN and the comma join", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CrossJoin", + "level": "Ok", + "findings": [] + }, + { + "name": "CommaJoin", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CrossJoin", + "level": "Ok", + "findings": [] + }, + { + "name": "CommaJoin", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CrossJoin": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint NOT NULL", + "book_id int NOT NULL" + ] + }, + "CommaJoin": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint NOT NULL", + "book_id int NOT NULL" + ] + } + } + }, + { + "id": "joins/inner_left_right_full", + "description": "INNER, LEFT, RIGHT and FULL joins and the nullability they introduce", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "InnerJoin", + "level": "Ok", + "findings": [] + }, + { + "name": "LeftJoin", + "level": "Mismatch", + "findings": [ + "Mismatch: column title: sqlc NOT NULL, SQL Server nullable" + ] + }, + { + "name": "RightJoin", + "level": "Mismatch", + "findings": [ + "Mismatch: column name: sqlc NOT NULL, SQL Server nullable" + ] + }, + { + "name": "FullJoin", + "level": "Mismatch", + "findings": [ + "Mismatch: column name: sqlc NOT NULL, SQL Server nullable", + "Mismatch: column title: sqlc NOT NULL, SQL Server nullable" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "InnerJoin", + "level": "Ok", + "findings": [] + }, + { + "name": "LeftJoin", + "level": "Mismatch", + "findings": [ + "Mismatch: column title: sqlc NOT NULL, SQL Server nullable" + ] + }, + { + "name": "RightJoin", + "level": "Mismatch", + "findings": [ + "Mismatch: column name: sqlc NOT NULL, SQL Server nullable" + ] + }, + { + "name": "FullJoin", + "level": "Mismatch", + "findings": [ + "Mismatch: column name: sqlc NOT NULL, SQL Server nullable", + "Mismatch: column title: sqlc NOT NULL, SQL Server nullable" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "InnerJoin": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "name nvarchar(100) NOT NULL", + "title nvarchar(200) NOT NULL" + ] + }, + "LeftJoin": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "name nvarchar(100) NOT NULL", + "title nvarchar(200)", + "price decimal(10,2)" + ] + }, + "RightJoin": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "name nvarchar(100)", + "title nvarchar(200) NOT NULL" + ] + }, + "FullJoin": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "name nvarchar(100)", + "title nvarchar(200)" + ] + } + } + }, + { + "id": "joins/join_derived_table", + "description": "Join to an aggregated derived table", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "AuthorsWithTotals", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "AuthorsWithTotals", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "AuthorsWithTotals": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "name nvarchar(100) NOT NULL", + "total decimal(38,2)" + ] + } + } + }, + { + "id": "joins/schema_qualified_join", + "description": "Join across dbo-qualified and bracket-quoted names", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "QualifiedJoin", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "QualifiedJoin", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "QualifiedJoin": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "name nvarchar(100) NOT NULL", + "title nvarchar(200) NOT NULL" + ] + } + } + }, + { + "id": "joins/self_join", + "description": "Self join with aliases", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SameNameAuthors", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SameNameAuthors", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SameNameAuthors": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "first_id bigint NOT NULL", + "second_id bigint NOT NULL" + ] + } + } + }, + { + "id": "json/for_json", + "description": "FOR JSON PATH / AUTO producing a single JSON column", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "AuthorsAsJson", + "level": "Mismatch", + "findings": [ + "Mismatch: 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)" + ] + }, + { + "name": "AuthorAsJsonObject", + "level": "Mismatch", + "findings": [ + "Mismatch: 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "AuthorsAsJson", + "level": "Mismatch", + "findings": [ + "Mismatch: 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)" + ] + }, + { + "name": "AuthorAsJsonObject", + "level": "Mismatch", + "findings": [ + "Mismatch: 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "AuthorsAsJson": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "JSON_F52E2B61-18A1-11d1-B105-00805F49916B nvarchar(max)" + ] + }, + "AuthorAsJsonObject": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id bigint" + ], + "columns": [ + "JSON_F52E2B61-18A1-11d1-B105-00805F49916B nvarchar(max)" + ] + } + } + }, + { + "id": "json/json_object_array", + "description": "JSON_OBJECT and JSON_ARRAY constructors (SQL Server 2022)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "AuthorJson", + "level": "Mismatch", + "findings": [ + "Mismatch: column obj has no type", + "Mismatch: column arr has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "AuthorJson", + "level": "Mismatch", + "findings": [ + "Mismatch: column obj has no type", + "Mismatch: column arr has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "AuthorJson": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "obj nvarchar(max)", + "arr nvarchar(max)" + ] + } + } + }, + { + "id": "json/json_value_query", + "description": "JSON_VALUE, JSON_QUERY, ISJSON and JSON_MODIFY on an NVARCHAR(MAX) column", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "JsonAccess", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @new_name has no type (SQL Server: float)", + "Mismatch: parameter @kind has no type (SQL Server: nvarchar(4000))", + "Mismatch: column name has no type", + "Mismatch: column tags has no type", + "Mismatch: column is_json has no type", + "Mismatch: column renamed has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "JsonAccess", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @new_name has no type (SQL Server: float)", + "Mismatch: parameter @kind has no type (SQL Server: nvarchar(4000))", + "Mismatch: column name has no type", + "Mismatch: column tags has no type", + "Mismatch: column is_json has no type", + "Mismatch: column renamed has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "JsonAccess": { + "paramsError": null, + "columnsError": null, + "params": [ + "@new_name float", + "@kind nvarchar(4000)" + ], + "columns": [ + "id int NOT NULL", + "name nvarchar(4000)", + "tags nvarchar(max)", + "is_json int", + "renamed nvarchar(max)" + ] + } + } + }, + { + "id": "json/openjson", + "description": "OPENJSON with an explicit WITH schema, fed by a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "ParseItems", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO" + ] + }, + { + "name": "AuthorsByJsonIds", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "ParseItems", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO" + ] + }, + { + "name": "AuthorsByJsonIds", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "ParseItems": { + "paramsError": "The parameter type for \u0027@items\u0027 cannot be uniquely deduced; two possibilities are \u0027sql_variant\u0027 and \u0027xml\u0027.", + "columnsError": null, + "params": null, + "columns": [ + "item_id int", + "quantity int" + ] + }, + "AuthorsByJsonIds": { + "paramsError": "The parameter type for \u0027@ids\u0027 cannot be uniquely deduced; two possibilities are \u0027sql_variant\u0027 and \u0027xml\u0027.", + "columnsError": null, + "params": null, + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "macros/arg_forms", + "description": "sqlc.arg with a quoted name, a bare identifier and a double-quoted name", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: where: unknown column \u0022min_id\u0022", + "queries": [ + { + "name": "ArgForms", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: where: unknown column \u0022min_id\u0022" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ArgForms", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ArgForms": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_name nvarchar(100)", + "@min_id bigint", + "@bio_text nvarchar(max)" + ], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "macros/arg_in_cast", + "description": "sqlc.arg inside CAST, which fixes its type", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "CastArg", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @min_price (decimal(10,2)) is missing" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "CastArg", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CastArg": { + "paramsError": null, + "columnsError": null, + "params": [ + "@min_price decimal(10,2)" + ], + "columns": [ + "id int NOT NULL" + ] + } + } + }, + { + "id": "macros/args_in_top_offset", + "description": "sqlc.arg in TOP and in OFFSET / FETCH", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "TopArg", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @take (bigint) is missing" + ] + }, + { + "name": "PageArgs", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @skip (bigint) is missing", + "Mismatch: parameter @take (bigint) is missing" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "TopArg", + "level": "Minor", + "findings": [ + "Minor: parameter @take: sqlc int, SQL Server bigint" + ] + }, + { + "name": "PageArgs", + "level": "Minor", + "findings": [ + "Minor: parameter @skip: sqlc int, SQL Server bigint", + "Minor: parameter @take: sqlc int, SQL Server bigint" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "TopArg": { + "paramsError": null, + "columnsError": null, + "params": [ + "@take bigint" + ], + "columns": [ + "id bigint NOT NULL" + ] + }, + "PageArgs": { + "paramsError": null, + "columnsError": null, + "params": [ + "@skip bigint", + "@take bigint" + ], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "macros/embed", + "description": "sqlc.embed of a table, of aliases (self join) and next to a t.* star", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unknown column \u0022authors\u0022\nquery.sql:5:1: unknown column \u0022a1\u0022\nquery.sql:8:1: unknown column \u0022authors\u0022", + "queries": [ + { + "name": "AuthorsWithBooks", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unknown column \u0022authors\u0022\nquery.sql:5:1: unknown column \u0022a1\u0022\nquery.sql:8:1: unknown column \u0022authors\u0022" + ] + }, + { + "name": "DuplicateAuthors", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unknown column \u0022authors\u0022\nquery.sql:5:1: unknown column \u0022a1\u0022\nquery.sql:8:1: unknown column \u0022authors\u0022" + ] + }, + { + "name": "BooksWithAuthor", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unknown column \u0022authors\u0022\nquery.sql:5:1: unknown column \u0022a1\u0022\nquery.sql:8:1: unknown column \u0022authors\u0022" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "AuthorsWithBooks", + "level": "Ok", + "findings": [] + }, + { + "name": "DuplicateAuthors", + "level": "Ok", + "findings": [] + }, + { + "name": "BooksWithAuthor", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "AuthorsWithBooks": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)", + "birth_date date", + "id int NOT NULL", + "author_id bigint NOT NULL", + "title nvarchar(200) NOT NULL", + "price decimal(10,2)", + "published_at datetime2(7)", + "is_available bit NOT NULL" + ] + }, + "DuplicateAuthors": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)", + "birth_date date", + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)", + "birth_date date" + ] + }, + "BooksWithAuthor": { + "paramsError": null, + "columnsError": null, + "params": [ + "@title nvarchar(200)" + ], + "columns": [ + "id int NOT NULL", + "author_id bigint NOT NULL", + "title nvarchar(200) NOT NULL", + "price decimal(10,2)", + "published_at datetime2(7)", + "is_available bit NOT NULL", + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)", + "birth_date date" + ] + } + } + }, + { + "id": "macros/embed_schema_scoped", + "description": "sqlc.embed of a table in a user-defined schema", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unknown column \u0022orders\u0022", + "queries": [ + { + "name": "OrdersWithAuthor", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unknown column \u0022orders\u0022" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "OrdersWithAuthor", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "OrdersWithAuthor": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "author_id bigint NOT NULL", + "total money NOT NULL", + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)", + "birth_date date" + ] + } + } + }, + { + "id": "macros/mixed_with_at_params", + "description": "sqlc.arg mixed with plain @params, sharing a name", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "Mixed", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "Mixed", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Mixed": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)", + "@bio nvarchar(max)" + ], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "macros/narg", + "description": "sqlc.narg makes a parameter nullable", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "NargFilter", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @name (nvarchar(100)) is missing" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "NargFilter", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "NargFilter": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)" + ], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "macros/slice", + "description": "sqlc.slice for integers, strings, NOT IN and several slices in one query", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ByIds", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @ids (bigint) is missing" + ] + }, + { + "name": "ByNames", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @names (nvarchar(100)) is missing" + ] + }, + { + "name": "NotByIds", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @excluded (bigint) is missing" + ] + }, + { + "name": "ByIdsAndTitles", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @author_ids (bigint) is missing", + "Mismatch: parameter @titles (nvarchar(200)) is missing" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ByIds", + "level": "Ok", + "findings": [] + }, + { + "name": "ByNames", + "level": "Ok", + "findings": [] + }, + { + "name": "NotByIds", + "level": "Ok", + "findings": [] + }, + { + "name": "ByIdsAndTitles", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ByIds": { + "paramsError": null, + "columnsError": null, + "params": [ + "@ids bigint" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + }, + "ByNames": { + "paramsError": null, + "columnsError": null, + "params": [ + "@names nvarchar(100)" + ], + "columns": [ + "id bigint NOT NULL" + ] + }, + "NotByIds": { + "paramsError": null, + "columnsError": null, + "params": [ + "@excluded bigint" + ], + "columns": [ + "id bigint NOT NULL" + ] + }, + "ByIdsAndTitles": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_ids bigint", + "@titles nvarchar(200)" + ], + "columns": [ + "id int NOT NULL" + ] + } + } + }, + { + "id": "merge/merge_delete", + "description": "MERGE with WHEN NOT MATCHED BY SOURCE THEN DELETE", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "SyncTags", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "SyncTags", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "SyncTags": { + "paramsError": "Argument data type void type is invalid for argument 1 of string_split function.\nThe batch could not be analyzed because of compile errors.", + "columnsError": "Argument data type void type is invalid for argument 1 of string_split function.\nThe batch could not be analyzed because of compile errors.", + "params": null, + "columns": null + } + } + }, + { + "id": "merge/merge_upsert", + "description": "MERGE as an upsert, with OUTPUT $action", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: expected identifier for schema object name", + "queries": [ + { + "name": "UpsertAuthor", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: expected identifier for schema object name" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: expected identifier for schema object name", + "queries": [ + { + "name": "UpsertAuthor", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: expected identifier for schema object name" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "UpsertAuthor": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)", + "@bio nvarchar(max)" + ], + "columns": [ + "action nvarchar(10) NOT NULL", + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "merge/upsert_without_merge", + "description": "The UPDATE-then-INSERT upsert pattern with @@ROWCOUNT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "UpsertBio", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "UpsertBio", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "UpsertBio": { + "paramsError": null, + "columnsError": null, + "params": [ + "@bio nvarchar(max)", + "@name nvarchar(100)" + ], + "columns": [] + } + } + }, + { + "id": "programmability/exec_procedure", + "description": "Calling a stored procedure with EXEC and named arguments", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "GetAuthorViaProc", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Dropped", + "error": "error parsing queries: no queries contained in paths query.sql", + "queries": [ + { + "name": "GetAuthorViaProc", + "level": "Dropped", + "findings": [ + "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "GetAuthorViaProc": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id bigint" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "programmability/inline_table_function", + "description": "An inline table-valued function in FROM", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "BooksOf", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "BooksOf", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "BooksOf": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_id bigint" + ], + "columns": [ + "id int NOT NULL", + "title nvarchar(200) NOT NULL" + ] + } + } + }, + { + "id": "programmability/multi_statement_table_function", + "description": "A multi-statement table-valued function in FROM", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "Range", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "Range", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "Range": { + "paramsError": null, + "columnsError": null, + "params": [ + "@upto int" + ], + "columns": [ + "n int NOT NULL" + ] + } + } + }, + { + "id": "programmability/scalar_function", + "description": "A user-defined scalar function in the select list and WHERE clause", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListWithInitials", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @initials has no type (SQL Server: nvarchar(10))", + "Mismatch: column initials has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListWithInitials", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @initials has no type (SQL Server: nvarchar(10))", + "Mismatch: column initials has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListWithInitials": { + "paramsError": null, + "columnsError": null, + "params": [ + "@initials nvarchar(10)" + ], + "columns": [ + "id bigint NOT NULL", + "initials nvarchar(10)" + ] + } + } + }, + { + "id": "programmability/string_split", + "description": "STRING_SPLIT as a table-valued function fed by a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: where: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "AuthorsByNameList", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: where: scope: unsupported FROM item *ast.TODO" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: where: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "AuthorsByNameList", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: where: scope: unsupported FROM item *ast.TODO" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "AuthorsByNameList": { + "paramsError": "Argument data type void type is invalid for argument 1 of string_split function.\nThe batch could not be analyzed because of compile errors.", + "columnsError": "Argument data type void type is invalid for argument 1 of string_split function.\nThe batch could not be analyzed because of compile errors.", + "params": null, + "columns": null + } + } + }, + { + "id": "programmability/table_valued_parameter", + "description": "A table-valued parameter (user-defined table type) used in a join", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "AuthorsByIdList", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "AuthorsByIdList", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "AuthorsByIdList": { + "paramsError": "Must declare the table variable \u0022@ids\u0022.\nThe batch could not be analyzed because of compile errors.", + "columnsError": "Must declare the table variable \u0022@ids\u0022.\nThe batch could not be analyzed because of compile errors.", + "params": null, + "columns": null + } + } + }, + { + "id": "select/alias_equals_syntax", + "description": "T-SQL column alias written as alias = expression", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthorNames", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthorNames", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListAuthorNames": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint NOT NULL", + "author_name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "select/between_params", + "description": "BETWEEN with two parameters", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListBooksInPriceRange", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListBooksInPriceRange", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListBooksInPriceRange": { + "paramsError": null, + "columnsError": null, + "params": [ + "@min_price decimal(38,19)", + "@max_price decimal(38,19)" + ], + "columns": [ + "id int NOT NULL", + "price decimal(10,2)" + ] + } + } + }, + { + "id": "select/case_expression", + "description": "Searched and simple CASE expressions", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListPriceBands", + "level": "Mismatch", + "findings": [ + "Mismatch: column band: sqlc varchar, SQL Server nvarchar(9)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListPriceBands", + "level": "Mismatch", + "findings": [ + "Mismatch: column band: sqlc varchar, SQL Server nvarchar(9)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListPriceBands": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "band nvarchar(9) NOT NULL", + "available varchar(3) NOT NULL" + ] + } + } + }, + { + "id": "select/comments", + "description": "Block comments, nested block comments and trailing line comments inside a query", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unexpected token: *", + "queries": [ + { + "name": "Commented", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unexpected token: *" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unexpected token: *", + "queries": [ + { + "name": "Commented", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unexpected token: *" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "Commented": { + "paramsError": null, + "columnsError": null, + "params": [ + "@min_id bigint" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "select/derived_table", + "description": "Derived table (subquery in FROM) with column aliases", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListExpensive", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListExpensive", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListExpensive": { + "paramsError": null, + "columnsError": null, + "params": [ + "@min decimal(38,19)" + ], + "columns": [ + "book_id int NOT NULL", + "price decimal(10,2)" + ] + } + } + }, + { + "id": "select/distinct", + "description": "SELECT DISTINCT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListDistinctAuthorIds", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListDistinctAuthorIds", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListDistinctAuthorIds": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint NOT NULL" + ] + } + } + }, + { + "id": "select/exists", + "description": "EXISTS / NOT EXISTS with a parameter inside the subquery", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthorsWithBooksAbove", + "level": "Ok", + "findings": [] + }, + { + "name": "ListAuthorsWithoutBooks", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthorsWithBooksAbove", + "level": "Ok", + "findings": [] + }, + { + "name": "ListAuthorsWithoutBooks", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListAuthorsWithBooksAbove": { + "paramsError": null, + "columnsError": null, + "params": [ + "@min_price decimal(38,19)" + ], + "columns": [ + "id bigint NOT NULL" + ] + }, + "ListAuthorsWithoutBooks": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "select/function_on_parameter", + "description": "Parameters wrapped in functions and compared to columns", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "CaseInsensitiveMatch", + "level": "Minor", + "findings": [ + "Minor: parameter @name: sqlc varchar, SQL Server nvarchar(4000)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "CaseInsensitiveMatch", + "level": "Minor", + "findings": [ + "Minor: parameter @name: sqlc varchar, SQL Server nvarchar(4000)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "CaseInsensitiveMatch": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(4000)" + ], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "select/iif_choose", + "description": "IIF and CHOOSE", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListFlags", + "level": "Mismatch", + "findings": [ + "Mismatch: column second has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "ListFlags", + "level": "Mismatch", + "findings": [ + "Mismatch: column second has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListFlags": { + "paramsError": null, + "columnsError": null, + "params": [ + "@threshold decimal(38,19)" + ], + "columns": [ + "id int NOT NULL", + "is_expensive int NOT NULL", + "second varchar(1)" + ] + } + } + }, + { + "id": "select/in_list_params", + "description": "IN with a list of scalar parameters", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ByThreeIds", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ByThreeIds", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ByThreeIds": { + "paramsError": null, + "columnsError": null, + "params": [ + "@first_id bigint", + "@second_id bigint", + "@third_id bigint" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "select/in_subquery_param", + "description": "A parameter inside IN (subquery)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthorsOfTitle", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthorsOfTitle", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListAuthorsOfTitle": { + "paramsError": null, + "columnsError": null, + "params": [ + "@title nvarchar(200)" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "select/like_escape", + "description": "LIKE with a parameter and an ESCAPE clause", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SearchTitles", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SearchTitles", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SearchTitles": { + "paramsError": null, + "columnsError": null, + "params": [ + "@pattern nvarchar(4000)" + ], + "columns": [ + "id int NOT NULL", + "title nvarchar(200) NOT NULL" + ] + } + } + }, + { + "id": "select/no_semicolon_terminator", + "description": "Statements without a terminating semicolon (optional in T-SQL)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:6:0: missing semicolon at end of file", + "queries": [ + { + "name": "First", + "level": "Error", + "findings": [ + "Error: query.sql:6:0: missing semicolon at end of file" + ] + }, + { + "name": "Second", + "level": "Error", + "findings": [ + "Error: query.sql:6:0: missing semicolon at end of file" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:6:0: missing semicolon at end of file", + "queries": [ + { + "name": "First", + "level": "Error", + "findings": [ + "Error: query.sql:6:0: missing semicolon at end of file" + ] + }, + { + "name": "Second", + "level": "Error", + "findings": [ + "Error: query.sql:6:0: missing semicolon at end of file" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "First": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL" + ] + }, + "Second": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL" + ] + } + } + }, + { + "id": "select/offset_fetch", + "description": "OFFSET ... ROWS FETCH NEXT ... ROWS ONLY with parameters", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "PageAuthors", + "level": "Minor", + "findings": [ + "Minor: parameter @skip: sqlc int, SQL Server bigint", + "Minor: parameter @take: sqlc int, SQL Server bigint" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "PageAuthors", + "level": "Minor", + "findings": [ + "Minor: parameter @skip: sqlc int, SQL Server bigint", + "Minor: parameter @take: sqlc int, SQL Server bigint" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "PageAuthors": { + "paramsError": null, + "columnsError": null, + "params": [ + "@skip bigint", + "@take bigint" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "select/offset_without_fetch", + "description": "OFFSET ... ROWS without FETCH", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "SkipAuthors", + "level": "Minor", + "findings": [ + "Minor: parameter @skip: sqlc int, SQL Server bigint" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "SkipAuthors", + "level": "Minor", + "findings": [ + "Minor: parameter @skip: sqlc int, SQL Server bigint" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SkipAuthors": { + "paramsError": null, + "columnsError": null, + "params": [ + "@skip bigint" + ], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "select/optional_filter", + "description": "The optional filter idiom: (@p IS NULL OR column = @p)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "FilterBooks", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "FilterBooks", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "FilterBooks": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_id bigint", + "@title nvarchar(200)" + ], + "columns": [ + "id int NOT NULL" + ] + } + } + }, + { + "id": "select/order_by_parameter", + "description": "Dynamic ORDER BY driven by a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SortedAuthors", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SortedAuthors", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SortedAuthors": { + "paramsError": null, + "columnsError": null, + "params": [ + "@sort varchar(4)" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "select/parameter_in_select_list", + "description": "A parameter echoed in the select list, with and without CAST", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "EchoParams", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @label has no type (SQL Server cannot deduce it either)", + "Mismatch: column label has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "EchoParams", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @label has no type (SQL Server cannot deduce it either)", + "Mismatch: column label has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "EchoParams": { + "paramsError": "The parameter type for \u0027@label\u0027 cannot be uniquely deduced; two possibilities are \u0027sql_variant\u0027 and \u0027xml\u0027.", + "columnsError": "The metadata could not be determined because statement \u0027SELECT id, @label AS label, CAST(@factor AS DECIMAL(5, 2)) * price AS scaled FROM books\u0027 uses an undeclared parameter in a context that affects its metadata.", + "params": null, + "columns": null + } + } + }, + { + "id": "select/quoted_aliases", + "description": "Aliases quoted with brackets, double quotes and single quotes", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListQuotedAliases", + "level": "Minor", + "findings": [ + "Minor: column 1 is named author id, SQL Server keeps the case: Author Id", + "Minor: column 2 is named author name, SQL Server keeps the case: Author Name", + "Minor: column 3 is named biography, SQL Server keeps the case: Biography" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "ListQuotedAliases", + "level": "Minor", + "findings": [ + "Minor: column 1 is named author id, SQL Server keeps the case: Author Id", + "Minor: column 2 is named author name, SQL Server keeps the case: Author Name", + "Minor: column 3 is named biography, SQL Server keeps the case: Biography" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListQuotedAliases": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "Author Id bigint NOT NULL", + "Author Name nvarchar(100) NOT NULL", + "Biography nvarchar(max)" + ] + } + } + }, + { + "id": "select/repeated_parameter", + "description": "The same parameter used several times, with different case", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ByNameTwice", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ByNameTwice", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ByNameTwice": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)" + ], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "select/scalar_subquery_column", + "description": "Correlated scalar subquery in the select list", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthorsWithBookCount", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthorsWithBookCount", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListAuthorsWithBookCount": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "book_count int" + ] + } + } + }, + { + "id": "select/select_star", + "description": "SELECT * and qualified t.* expansion", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthors", + "level": "Ok", + "findings": [] + }, + { + "name": "ListAuthorsWithBookTitle", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ListAuthors", + "level": "Ok", + "findings": [] + }, + { + "name": "ListAuthorsWithBookTitle", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ListAuthors": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)", + "birth_date date" + ] + }, + "ListAuthorsWithBookTitle": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL", + "bio nvarchar(max)", + "birth_date date", + "title nvarchar(200) NOT NULL" + ] + } + } + }, + { + "id": "select/table_value_constructor", + "description": "VALUES as a derived table", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "ListConstants", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "ListConstants", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "ListConstants": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "code int NOT NULL", + "label nvarchar(3) NOT NULL" + ] + } + } + }, + { + "id": "select/top_literal_with_ties", + "description": "TOP n WITH TIES and TOP n PERCENT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "TopPricedWithTies", + "level": "Ok", + "findings": [] + }, + { + "name": "TopTenPercent", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "TopPricedWithTies", + "level": "Ok", + "findings": [] + }, + { + "name": "TopTenPercent", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "TopPricedWithTies": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "price decimal(10,2)" + ] + }, + "TopTenPercent": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL" + ] + } + } + }, + { + "id": "select/top_parameter", + "description": "TOP with a parenthesized parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "TopAuthors", + "level": "Minor", + "findings": [ + "Minor: parameter @take: sqlc int, SQL Server bigint" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "TopAuthors", + "level": "Minor", + "findings": [ + "Minor: parameter @take: sqlc int, SQL Server bigint" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "TopAuthors": { + "paramsError": null, + "columnsError": null, + "params": [ + "@take bigint" + ], + "columns": [ + "id bigint NOT NULL", + "name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "select/unicode_literals", + "description": "N\u0027\u0027 Unicode literals and string concatenation with \u002B", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Greetings", + "level": "Mismatch", + "findings": [ + "Mismatch: column greeting: sqlc varchar, SQL Server nvarchar(105)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "Greetings", + "level": "Mismatch", + "findings": [ + "Mismatch: column greeting: sqlc varchar, SQL Server nvarchar(105)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Greetings": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "greeting nvarchar(105) NOT NULL", + "ascii varchar(5) NOT NULL" + ] + } + } + }, + { + "id": "select/without_from", + "description": "SELECT without FROM: parameters and functions only", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "Now", + "level": "Ok", + "findings": [] + }, + { + "name": "AddOne", + "level": "Minor", + "findings": [ + "Minor: column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "Now", + "level": "Ok", + "findings": [] + }, + { + "name": "AddOne", + "level": "Minor", + "findings": [ + "Minor: column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "Now": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "now_utc datetime2(7) NOT NULL" + ] + }, + "AddOne": { + "paramsError": null, + "columnsError": null, + "params": [ + "@value int" + ], + "columns": [ + "result int" + ] + } + } + }, + { + "id": "setops/pivot_unpivot", + "description": "PIVOT and UNPIVOT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "AvailabilityPivot", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO" + ] + }, + { + "name": "QuarterUnpivot", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO", + "queries": [ + { + "name": "AvailabilityPivot", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO" + ] + }, + { + "name": "QuarterUnpivot", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "AvailabilityPivot": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint NOT NULL", + "unavailable int", + "available int" + ] + }, + "QuarterUnpivot": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "region nvarchar(10) NOT NULL", + "quarter nvarchar(128)", + "amount int NOT NULL" + ] + } + } + }, + { + "id": "setops/union_except_intersect", + "description": "UNION, UNION ALL, EXCEPT and INTERSECT", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "UnionNames", + "level": "Ok", + "findings": [] + }, + { + "name": "UnionAllIds", + "level": "Ok", + "findings": [] + }, + { + "name": "AuthorsWithoutBooks", + "level": "Ok", + "findings": [] + }, + { + "name": "AuthorsWithBooks", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "UnionNames", + "level": "Ok", + "findings": [] + }, + { + "name": "UnionAllIds", + "level": "Ok", + "findings": [] + }, + { + "name": "AuthorsWithoutBooks", + "level": "Ok", + "findings": [] + }, + { + "name": "AuthorsWithBooks", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "UnionNames": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "name nvarchar(200) NOT NULL" + ] + }, + "UnionAllIds": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL" + ] + }, + "AuthorsWithoutBooks": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL" + ] + }, + "AuthorsWithBooks": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL" + ] + } + } + }, + { + "id": "types/bigint", + "description": "\u0060BIGINT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectBigint", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertBigint", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterBigint", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectBigint", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertBigint", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterBigint", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectBigint": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v bigint", + "nn bigint NOT NULL" + ] + }, + "InsertBigint": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v bigint", + "@nn bigint" + ], + "columns": [] + }, + "FilterBigint": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn bigint" + ], + "columns": [ + "v bigint" + ] + } + } + }, + { + "id": "types/binary", + "description": "\u0060BINARY(16)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectBinary", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertBinary", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterBinary", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectBinary", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertBinary", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterBinary", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectBinary": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v binary(16)", + "nn binary(16) NOT NULL" + ] + }, + "InsertBinary": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v binary(16)", + "@nn binary(16)" + ], + "columns": [] + }, + "FilterBinary": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn binary(16)" + ], + "columns": [ + "v binary(16)" + ] + } + } + }, + { + "id": "types/bit", + "description": "\u0060BIT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectBit", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertBit", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterBit", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectBit", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertBit", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterBit", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectBit": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v bit", + "nn bit NOT NULL" + ] + }, + "InsertBit": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v bit", + "@nn bit" + ], + "columns": [] + }, + "FilterBit": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn bit" + ], + "columns": [ + "v bit" + ] + } + } + }, + { + "id": "types/char", + "description": "\u0060CHAR(10)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectChar", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertChar", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterChar", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectChar", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertChar", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterChar", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectChar": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v char(10)", + "nn char(10) NOT NULL" + ] + }, + "InsertChar": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v char(10)", + "@nn char(10)" + ], + "columns": [] + }, + "FilterChar": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn char(10)" + ], + "columns": [ + "v char(10)" + ] + } + } + }, + { + "id": "types/date", + "description": "\u0060DATE\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDate", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDate", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDate", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDate", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDate", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDate", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectDate": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v date", + "nn date NOT NULL" + ] + }, + "InsertDate": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v date", + "@nn date" + ], + "columns": [] + }, + "FilterDate": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn date" + ], + "columns": [ + "v date" + ] + } + } + }, + { + "id": "types/datetime", + "description": "\u0060DATETIME\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDatetime", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDatetime", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDatetime", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDatetime", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDatetime", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDatetime", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectDatetime": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v datetime", + "nn datetime NOT NULL" + ] + }, + "InsertDatetime": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v datetime", + "@nn datetime" + ], + "columns": [] + }, + "FilterDatetime": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn datetime" + ], + "columns": [ + "v datetime" + ] + } + } + }, + { + "id": "types/datetime2", + "description": "\u0060DATETIME2(7)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDatetime2", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDatetime2", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDatetime2", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDatetime2", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDatetime2", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDatetime2", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectDatetime2": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v datetime2(7)", + "nn datetime2(7) NOT NULL" + ] + }, + "InsertDatetime2": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v datetime2(7)", + "@nn datetime2(7)" + ], + "columns": [] + }, + "FilterDatetime2": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn datetime2(7)" + ], + "columns": [ + "v datetime2(7)" + ] + } + } + }, + { + "id": "types/datetime2_0", + "description": "\u0060DATETIME2(0)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDatetime20", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDatetime20", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDatetime20", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDatetime20", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDatetime20", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDatetime20", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectDatetime20": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v datetime2(0)", + "nn datetime2(0) NOT NULL" + ] + }, + "InsertDatetime20": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v datetime2(0)", + "@nn datetime2(0)" + ], + "columns": [] + }, + "FilterDatetime20": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn datetime2(0)" + ], + "columns": [ + "v datetime2(0)" + ] + } + } + }, + { + "id": "types/datetimeoffset", + "description": "\u0060DATETIMEOFFSET(7)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDatetimeoffset", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDatetimeoffset", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDatetimeoffset", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDatetimeoffset", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDatetimeoffset", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDatetimeoffset", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectDatetimeoffset": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v datetimeoffset(7)", + "nn datetimeoffset(7) NOT NULL" + ] + }, + "InsertDatetimeoffset": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v datetimeoffset(7)", + "@nn datetimeoffset(7)" + ], + "columns": [] + }, + "FilterDatetimeoffset": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn datetimeoffset(7)" + ], + "columns": [ + "v datetimeoffset(7)" + ] + } + } + }, + { + "id": "types/decimal", + "description": "\u0060DECIMAL(18, 4)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDecimal", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDecimal", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDecimal", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectDecimal", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertDecimal", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterDecimal", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectDecimal": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v decimal(18,4)", + "nn decimal(18,4) NOT NULL" + ] + }, + "InsertDecimal": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v decimal(18,4)", + "@nn decimal(18,4)" + ], + "columns": [] + }, + "FilterDecimal": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn decimal(18,4)" + ], + "columns": [ + "v decimal(18,4)" + ] + } + } + }, + { + "id": "types/float", + "description": "\u0060FLOAT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectFloat", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertFloat", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterFloat", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectFloat", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertFloat", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterFloat", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectFloat": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v float", + "nn float NOT NULL" + ] + }, + "InsertFloat": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v float", + "@nn float" + ], + "columns": [] + }, + "FilterFloat": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn float" + ], + "columns": [ + "v float" + ] + } + } + }, + { + "id": "types/float_24", + "description": "\u0060FLOAT(24)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectFloat24", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertFloat24", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterFloat24", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectFloat24", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertFloat24", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterFloat24", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectFloat24": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v real", + "nn real NOT NULL" + ] + }, + "InsertFloat24": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v real", + "@nn real" + ], + "columns": [] + }, + "FilterFloat24": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn real" + ], + "columns": [ + "v real" + ] + } + } + }, + { + "id": "types/geography", + "description": "\u0060GEOGRAPHY\u0060 column: nullable and NOT NULL, read, written", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectGeography", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertGeography", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectGeography", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertGeography", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectGeography": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v geography", + "nn geography NOT NULL" + ] + }, + "InsertGeography": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v geography", + "@nn geography" + ], + "columns": [] + } + } + }, + { + "id": "types/geometry", + "description": "\u0060GEOMETRY\u0060 column: nullable and NOT NULL, read, written", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectGeometry", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertGeometry", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectGeometry", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertGeometry", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectGeometry": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v geometry", + "nn geometry NOT NULL" + ] + }, + "InsertGeometry": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v geometry", + "@nn geometry" + ], + "columns": [] + } + } + }, + { + "id": "types/hierarchyid", + "description": "\u0060HIERARCHYID\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectHierarchyid", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertHierarchyid", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterHierarchyid", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectHierarchyid", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertHierarchyid", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterHierarchyid", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectHierarchyid": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v hierarchyid", + "nn hierarchyid NOT NULL" + ] + }, + "InsertHierarchyid": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v hierarchyid", + "@nn hierarchyid" + ], + "columns": [] + }, + "FilterHierarchyid": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn hierarchyid" + ], + "columns": [ + "v hierarchyid" + ] + } + } + }, + { + "id": "types/image", + "description": "\u0060IMAGE\u0060 column: nullable and NOT NULL, read, written", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectImage", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertImage", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectImage", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertImage", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectImage": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v image", + "nn image NOT NULL" + ] + }, + "InsertImage": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v image", + "@nn image" + ], + "columns": [] + } + } + }, + { + "id": "types/int", + "description": "\u0060INT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectInt", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertInt", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterInt", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectInt", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertInt", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterInt", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectInt": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v int", + "nn int NOT NULL" + ] + }, + "InsertInt": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v int", + "@nn int" + ], + "columns": [] + }, + "FilterInt": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn int" + ], + "columns": [ + "v int" + ] + } + } + }, + { + "id": "types/max_lengths_and_precision", + "description": "The type expression keeps lengths, MAX, precision and scale", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectPrecise", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectPrecise", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectPrecise": { + "paramsError": null, + "columnsError": null, + "params": [ + "@d decimal(38,10)", + "@n nvarchar(4000)" + ], + "columns": [ + "d decimal(38,10) NOT NULL", + "n nvarchar(4000) NOT NULL", + "t time(0) NOT NULL", + "dt datetime2(3) NOT NULL" + ] + } + } + }, + { + "id": "types/money", + "description": "\u0060MONEY\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectMoney", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertMoney", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterMoney", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectMoney", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertMoney", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterMoney", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectMoney": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v money", + "nn money NOT NULL" + ] + }, + "InsertMoney": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v money", + "@nn money" + ], + "columns": [] + }, + "FilterMoney": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn money" + ], + "columns": [ + "v money" + ] + } + } + }, + { + "id": "types/nchar", + "description": "\u0060NCHAR(10)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNchar", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNchar", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterNchar", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNchar", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNchar", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterNchar", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectNchar": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v nchar(10)", + "nn nchar(10) NOT NULL" + ] + }, + "InsertNchar": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v nchar(10)", + "@nn nchar(10)" + ], + "columns": [] + }, + "FilterNchar": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn nchar(10)" + ], + "columns": [ + "v nchar(10)" + ] + } + } + }, + { + "id": "types/ntext", + "description": "\u0060NTEXT\u0060 column: nullable and NOT NULL, read, written", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNtext", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNtext", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNtext", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNtext", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectNtext": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v ntext", + "nn ntext NOT NULL" + ] + }, + "InsertNtext": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v ntext", + "@nn ntext" + ], + "columns": [] + } + } + }, + { + "id": "types/numeric", + "description": "\u0060NUMERIC(10, 0)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNumeric", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNumeric", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterNumeric", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNumeric", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNumeric", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterNumeric", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectNumeric": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v numeric(10,0)", + "nn numeric(10,0) NOT NULL" + ] + }, + "InsertNumeric": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v numeric(10,0)", + "@nn numeric(10,0)" + ], + "columns": [] + }, + "FilterNumeric": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn numeric(10,0)" + ], + "columns": [ + "v numeric(10,0)" + ] + } + } + }, + { + "id": "types/nvarchar", + "description": "\u0060NVARCHAR(50)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNvarchar", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNvarchar", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterNvarchar", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNvarchar", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNvarchar", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterNvarchar", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectNvarchar": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v nvarchar(50)", + "nn nvarchar(50) NOT NULL" + ] + }, + "InsertNvarchar": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v nvarchar(50)", + "@nn nvarchar(50)" + ], + "columns": [] + }, + "FilterNvarchar": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn nvarchar(50)" + ], + "columns": [ + "v nvarchar(50)" + ] + } + } + }, + { + "id": "types/nvarchar_max", + "description": "\u0060NVARCHAR(MAX)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNvarcharMax", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNvarcharMax", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterNvarcharMax", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectNvarcharMax", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertNvarcharMax", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterNvarcharMax", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectNvarcharMax": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v nvarchar(max)", + "nn nvarchar(max) NOT NULL" + ] + }, + "InsertNvarcharMax": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v nvarchar(max)", + "@nn nvarchar(max)" + ], + "columns": [] + }, + "FilterNvarcharMax": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn nvarchar(max)" + ], + "columns": [ + "v nvarchar(max)" + ] + } + } + }, + { + "id": "types/real", + "description": "\u0060REAL\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectReal", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertReal", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterReal", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectReal", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertReal", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterReal", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectReal": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v real", + "nn real NOT NULL" + ] + }, + "InsertReal": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v real", + "@nn real" + ], + "columns": [] + }, + "FilterReal": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn real" + ], + "columns": [ + "v real" + ] + } + } + }, + { + "id": "types/rowversion_timestamp", + "description": "\u0060ROWVERSION\u0060 and its legacy synonym \u0060TIMESTAMP\u0060", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVersions", + "level": "Ok", + "findings": [] + }, + { + "name": "SelectTimestamp", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVersions", + "level": "Ok", + "findings": [] + }, + { + "name": "SelectTimestamp", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectVersions": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "rv timestamp NOT NULL" + ] + }, + "SelectTimestamp": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "ts timestamp NOT NULL" + ] + } + } + }, + { + "id": "types/smalldatetime", + "description": "\u0060SMALLDATETIME\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSmalldatetime", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSmalldatetime", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSmalldatetime", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSmalldatetime", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSmalldatetime", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSmalldatetime", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectSmalldatetime": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v smalldatetime", + "nn smalldatetime NOT NULL" + ] + }, + "InsertSmalldatetime": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v smalldatetime", + "@nn smalldatetime" + ], + "columns": [] + }, + "FilterSmalldatetime": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn smalldatetime" + ], + "columns": [ + "v smalldatetime" + ] + } + } + }, + { + "id": "types/smallint", + "description": "\u0060SMALLINT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSmallint", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSmallint", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSmallint", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSmallint", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSmallint", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSmallint", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectSmallint": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v smallint", + "nn smallint NOT NULL" + ] + }, + "InsertSmallint": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v smallint", + "@nn smallint" + ], + "columns": [] + }, + "FilterSmallint": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn smallint" + ], + "columns": [ + "v smallint" + ] + } + } + }, + { + "id": "types/smallmoney", + "description": "\u0060SMALLMONEY\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSmallmoney", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSmallmoney", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSmallmoney", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSmallmoney", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSmallmoney", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSmallmoney", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectSmallmoney": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v smallmoney", + "nn smallmoney NOT NULL" + ] + }, + "InsertSmallmoney": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v smallmoney", + "@nn smallmoney" + ], + "columns": [] + }, + "FilterSmallmoney": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn smallmoney" + ], + "columns": [ + "v smallmoney" + ] + } + } + }, + { + "id": "types/sql_variant", + "description": "\u0060SQL_VARIANT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSqlVariant", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSqlVariant", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSqlVariant", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSqlVariant", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSqlVariant", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSqlVariant", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectSqlVariant": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v sql_variant", + "nn sql_variant NOT NULL" + ] + }, + "InsertSqlVariant": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v sql_variant", + "@nn sql_variant" + ], + "columns": [] + }, + "FilterSqlVariant": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn sql_variant" + ], + "columns": [ + "v sql_variant" + ] + } + } + }, + { + "id": "types/sysname", + "description": "\u0060SYSNAME\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSysname", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSysname", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSysname", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectSysname", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertSysname", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterSysname", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectSysname": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v nvarchar(128)", + "nn nvarchar(128) NOT NULL" + ] + }, + "InsertSysname": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v nvarchar(128)", + "@nn nvarchar(128)" + ], + "columns": [] + }, + "FilterSysname": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn nvarchar(128)" + ], + "columns": [ + "v nvarchar(128)" + ] + } + } + }, + { + "id": "types/text", + "description": "\u0060TEXT\u0060 column: nullable and NOT NULL, read, written", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectText", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertText", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectText", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertText", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectText": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v text", + "nn text NOT NULL" + ] + }, + "InsertText": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v text", + "@nn text" + ], + "columns": [] + } + } + }, + { + "id": "types/time", + "description": "\u0060TIME(3)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectTime", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertTime", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterTime", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectTime", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertTime", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterTime", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectTime": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v time(3)", + "nn time(3) NOT NULL" + ] + }, + "InsertTime": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v time(3)", + "@nn time(3)" + ], + "columns": [] + }, + "FilterTime": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn time(3)" + ], + "columns": [ + "v time(3)" + ] + } + } + }, + { + "id": "types/tinyint", + "description": "\u0060TINYINT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectTinyint", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertTinyint", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterTinyint", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectTinyint", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertTinyint", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterTinyint", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectTinyint": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v tinyint", + "nn tinyint NOT NULL" + ] + }, + "InsertTinyint": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v tinyint", + "@nn tinyint" + ], + "columns": [] + }, + "FilterTinyint": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn tinyint" + ], + "columns": [ + "v tinyint" + ] + } + } + }, + { + "id": "types/uniqueidentifier", + "description": "\u0060UNIQUEIDENTIFIER\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectUniqueidentifier", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertUniqueidentifier", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterUniqueidentifier", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectUniqueidentifier", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertUniqueidentifier", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterUniqueidentifier", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectUniqueidentifier": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v uniqueidentifier", + "nn uniqueidentifier NOT NULL" + ] + }, + "InsertUniqueidentifier": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v uniqueidentifier", + "@nn uniqueidentifier" + ], + "columns": [] + }, + "FilterUniqueidentifier": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn uniqueidentifier" + ], + "columns": [ + "v uniqueidentifier" + ] + } + } + }, + { + "id": "types/varbinary", + "description": "\u0060VARBINARY(50)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVarbinary", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertVarbinary", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterVarbinary", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVarbinary", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertVarbinary", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterVarbinary", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectVarbinary": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v varbinary(50)", + "nn varbinary(50) NOT NULL" + ] + }, + "InsertVarbinary": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v varbinary(50)", + "@nn varbinary(50)" + ], + "columns": [] + }, + "FilterVarbinary": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn varbinary(50)" + ], + "columns": [ + "v varbinary(50)" + ] + } + } + }, + { + "id": "types/varbinary_max", + "description": "\u0060VARBINARY(MAX)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVarbinaryMax", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertVarbinaryMax", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterVarbinaryMax", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVarbinaryMax", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertVarbinaryMax", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterVarbinaryMax", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectVarbinaryMax": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v varbinary(max)", + "nn varbinary(max) NOT NULL" + ] + }, + "InsertVarbinaryMax": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v varbinary(max)", + "@nn varbinary(max)" + ], + "columns": [] + }, + "FilterVarbinaryMax": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn varbinary(max)" + ], + "columns": [ + "v varbinary(max)" + ] + } + } + }, + { + "id": "types/varchar", + "description": "\u0060VARCHAR(50)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVarchar", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertVarchar", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterVarchar", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVarchar", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertVarchar", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterVarchar", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectVarchar": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v varchar(50)", + "nn varchar(50) NOT NULL" + ] + }, + "InsertVarchar": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v varchar(50)", + "@nn varchar(50)" + ], + "columns": [] + }, + "FilterVarchar": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn varchar(50)" + ], + "columns": [ + "v varchar(50)" + ] + } + } + }, + { + "id": "types/varchar_max", + "description": "\u0060VARCHAR(MAX)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVarcharMax", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertVarcharMax", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterVarcharMax", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectVarcharMax", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertVarcharMax", + "level": "Ok", + "findings": [] + }, + { + "name": "FilterVarcharMax", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectVarcharMax": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v varchar(max)", + "nn varchar(max) NOT NULL" + ] + }, + "InsertVarcharMax": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v varchar(max)", + "@nn varchar(max)" + ], + "columns": [] + }, + "FilterVarcharMax": { + "paramsError": null, + "columnsError": null, + "params": [ + "@nn varchar(max)" + ], + "columns": [ + "v varchar(max)" + ] + } + } + }, + { + "id": "types/xml", + "description": "\u0060XML\u0060 column: nullable and NOT NULL, read, written", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectXml", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertXml", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "SelectXml", + "level": "Ok", + "findings": [] + }, + { + "name": "InsertXml", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "SelectXml": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "v xml", + "nn xml NOT NULL" + ] + }, + "InsertXml": { + "paramsError": null, + "columnsError": null, + "params": [ + "@v xml", + "@nn xml" + ], + "columns": [] + } + } + }, + { + "id": "update/compound_assignment", + "description": "Compound assignment operators \u002B= and *=", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "AdjustPrice", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "AdjustPrice", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "AdjustPrice": { + "paramsError": null, + "columnsError": null, + "params": [ + "@delta decimal(38,19)", + "@suffix nvarchar(4000)", + "@author_id bigint" + ], + "columns": [] + } + } + }, + { + "id": "update/from_join", + "description": "UPDATE ... FROM with a JOIN (T-SQL specific)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "MarkAuthorsBooksUnavailable", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "MarkAuthorsBooksUnavailable", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "MarkAuthorsBooksUnavailable": { + "paramsError": null, + "columnsError": null, + "params": [ + "@author_name nvarchar(100)" + ], + "columns": [] + } + } + }, + { + "id": "update/output_deleted_inserted", + "description": "UPDATE ... OUTPUT DELETED.x, INSERTED.x", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "RenameAuthor", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "RenameAuthor", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "RenameAuthor": { + "paramsError": null, + "columnsError": null, + "params": [ + "@name nvarchar(100)", + "@id bigint" + ], + "columns": [ + "old_name nvarchar(100) NOT NULL", + "new_name nvarchar(100) NOT NULL" + ] + } + } + }, + { + "id": "update/set_default", + "description": "UPDATE ... SET column = DEFAULT and = NULL", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ResetBook", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "ResetBook", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "ResetBook": { + "paramsError": null, + "columnsError": null, + "params": [ + "@id int" + ], + "columns": [] + } + } + }, + { + "id": "update/simple", + "description": "UPDATE with SET parameters and WHERE", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "UpdateBio", + "level": "Ok", + "findings": [] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Ok", + "error": "", + "queries": [ + { + "name": "UpdateBio", + "level": "Ok", + "findings": [] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "UpdateBio": { + "paramsError": null, + "columnsError": null, + "params": [ + "@bio nvarchar(max)", + "@birth_date date", + "@id bigint" + ], + "columns": [] + } + } + }, + { + "id": "update/top", + "description": "UPDATE TOP (@n)", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "MarkSomeUnavailable", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @n (bigint) is missing" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "MarkSomeUnavailable", + "level": "Mismatch", + "findings": [ + "Mismatch: parameter @n (bigint) is missing" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "MarkSomeUnavailable": { + "paramsError": null, + "columnsError": null, + "params": [ + "@n bigint" + ], + "columns": [] + } + } + }, + { + "id": "window/aggregate_over", + "description": "Aggregates over windows, including a running total", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "RunningTotals", + "level": "Minor", + "findings": [ + "Minor: column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Minor", + "error": "", + "queries": [ + { + "name": "RunningTotals", + "level": "Minor", + "findings": [ + "Minor: column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "RunningTotals": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "running_total decimal(38,2)", + "total_count int" + ] + } + } + }, + { + "id": "window/lag_lead_first_value", + "description": "LAG, LEAD, FIRST_VALUE and LAST_VALUE with a frame", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "PriceNeighbours", + "level": "Mismatch", + "findings": [ + "Mismatch: column previous_price has no type", + "Mismatch: column next_price has no type", + "Mismatch: column first_title has no type", + "Mismatch: column last_title has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "PriceNeighbours", + "level": "Mismatch", + "findings": [ + "Mismatch: column previous_price has no type", + "Mismatch: column next_price has no type", + "Mismatch: column first_title has no type", + "Mismatch: column last_title has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "PriceNeighbours": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "previous_price decimal(10,2)", + "next_price decimal(10,2)", + "first_title nvarchar(200)", + "last_title nvarchar(200)" + ] + } + } + }, + { + "id": "window/ranking", + "description": "ROW_NUMBER, RANK, DENSE_RANK and NTILE", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "RankedBooks", + "level": "Mismatch", + "findings": [ + "Mismatch: column rn has no type", + "Mismatch: column rnk has no type", + "Mismatch: column dense_rnk has no type", + "Mismatch: column quartile has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "RankedBooks", + "level": "Mismatch", + "findings": [ + "Mismatch: column rn has no type", + "Mismatch: column rnk has no type", + "Mismatch: column dense_rnk has no type", + "Mismatch: column quartile has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "RankedBooks": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "rn bigint", + "rnk bigint", + "dense_rnk bigint", + "quartile bigint" + ] + } + } + }, + { + "id": "window/window_clause_and_percentiles", + "description": "Named WINDOW clause (SQL Server 2022), PERCENTILE_CONT and PERCENTILE_DISC", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "NamedWindow", + "level": "Ok", + "findings": [] + }, + { + "name": "Percentiles", + "level": "Mismatch", + "findings": [ + "Mismatch: column median has no type", + "Mismatch: column median_disc has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "NamedWindow", + "level": "Ok", + "findings": [] + }, + { + "name": "Percentiles", + "level": "Mismatch", + "findings": [ + "Mismatch: column median has no type", + "Mismatch: column median_disc has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "NamedWindow": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "total decimal(38,2)", + "average decimal(38,6)" + ] + }, + "Percentiles": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "author_id bigint NOT NULL", + "median float", + "median_disc decimal(10,2)" + ] + } + } + }, + { + "id": "xml/for_xml", + "description": "FOR XML PATH, including the string-concatenation idiom", + "schemaError": null, + "builds": { + "upstream": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "AuthorsAsXml", + "level": "Mismatch", + "findings": [ + "Mismatch: 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B)" + ] + }, + { + "name": "ConcatenatedTitles", + "level": "Mismatch", + "findings": [ + "Mismatch: column titles has no type" + ] + } + ] + }, + "patched": { + "status": "Ok", + "level": "Mismatch", + "error": "", + "queries": [ + { + "name": "AuthorsAsXml", + "level": "Mismatch", + "findings": [ + "Mismatch: 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B)" + ] + }, + { + "name": "ConcatenatedTitles", + "level": "Mismatch", + "findings": [ + "Mismatch: column titles has no type" + ] + } + ] + } + }, + "plugin": "Ok", + "pluginError": "", + "compile": "Ok", + "compileError": "", + "oracle": { + "AuthorsAsXml": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "XML_F52E2B61-18A1-11d1-B105-00805F49916B ntext" + ] + }, + "ConcatenatedTitles": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id bigint NOT NULL", + "titles nvarchar(max)" + ] + } + } + }, + { + "id": "xml/xml_methods", + "description": "XML column with the value(), query(), exist() and nodes() methods", + "schemaError": null, + "builds": { + "upstream": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unexpected token: query", + "queries": [ + { + "name": "XmlAccess", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unexpected token: query" + ] + }, + { + "name": "XmlNodes", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unexpected token: query" + ] + } + ] + }, + "patched": { + "status": "Error", + "level": "Error", + "error": "query.sql:1:1: unexpected token: query", + "queries": [ + { + "name": "XmlAccess", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unexpected token: query" + ] + }, + { + "name": "XmlNodes", + "level": "Error", + "findings": [ + "Error: query.sql:1:1: unexpected token: query" + ] + } + ] + } + }, + "plugin": "Skipped", + "pluginError": "", + "compile": "Skipped", + "compileError": "", + "oracle": { + "XmlAccess": { + "paramsError": "XQuery: Unable to resolve sql:variable(\u0027@status\u0027). The variable must be declared as a scalar TSQL variable.", + "columnsError": "XQuery: Unable to resolve sql:variable(\u0027@status\u0027). The variable must be declared as a scalar TSQL variable.", + "params": null, + "columns": null + }, + "XmlNodes": { + "paramsError": null, + "columnsError": null, + "params": [], + "columns": [ + "id int NOT NULL", + "sku nvarchar(20)" + ] + } + } + } +] diff --git a/scripts/sqlc-patches/0001-mssql-support-sqlc.arg-narg-slice-embed-macros.patch b/scripts/sqlc-patches/0001-mssql-support-sqlc.arg-narg-slice-embed-macros.patch index 89a02f177..d07f92893 100644 --- a/scripts/sqlc-patches/0001-mssql-support-sqlc.arg-narg-slice-embed-macros.patch +++ b/scripts/sqlc-patches/0001-mssql-support-sqlc.arg-narg-slice-embed-macros.patch @@ -1,7 +1,7 @@ From 3a7c93cd7a0895f3190327a297bbfac15df66ee8 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 08:30:06 +0000 -Subject: [PATCH 1/3] mssql: support sqlc.arg/narg/slice/embed macros +Subject: [PATCH 1/4] mssql: support sqlc.arg/narg/slice/embed macros --- internal/compiler/parse_core.go | 43 +++++++++++++++++++++++++++ diff --git a/scripts/sqlc-patches/0002-mssql-give-INSERT-.-VALUES-an-empty-FROM-clause-so-c.patch b/scripts/sqlc-patches/0002-mssql-give-INSERT-.-VALUES-an-empty-FROM-clause-so-c.patch index 11b95001c..66fb4d837 100644 --- a/scripts/sqlc-patches/0002-mssql-give-INSERT-.-VALUES-an-empty-FROM-clause-so-c.patch +++ b/scripts/sqlc-patches/0002-mssql-give-INSERT-.-VALUES-an-empty-FROM-clause-so-c.patch @@ -1,7 +1,7 @@ From f7d038c13fe802489c146466c1ebbae82ca939d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 08:38:09 +0000 -Subject: [PATCH 2/3] mssql: give INSERT ... VALUES an empty FROM clause so +Subject: [PATCH 2/4] mssql: give INSERT ... VALUES an empty FROM clause so :copyfrom validates --- diff --git a/scripts/sqlc-patches/0003-mssql-convert-TRUNCATE-TABLE.patch b/scripts/sqlc-patches/0003-mssql-convert-TRUNCATE-TABLE.patch index b2256279e..32cc00ff7 100644 --- a/scripts/sqlc-patches/0003-mssql-convert-TRUNCATE-TABLE.patch +++ b/scripts/sqlc-patches/0003-mssql-convert-TRUNCATE-TABLE.patch @@ -1,7 +1,7 @@ From 286f32e61d4a14e5821d29cbe847e2b5235a12d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 08:40:09 +0000 -Subject: [PATCH 3/3] mssql: convert TRUNCATE TABLE +Subject: [PATCH 3/4] mssql: convert TRUNCATE TABLE --- internal/engine/mssql/convert.go | 12 ++++++++++++ diff --git a/scripts/sqlc-patches/0004-mssql-parameter-names-are-case-insensitive-as-in-SQL.patch b/scripts/sqlc-patches/0004-mssql-parameter-names-are-case-insensitive-as-in-SQL.patch new file mode 100644 index 000000000..f6908b182 --- /dev/null +++ b/scripts/sqlc-patches/0004-mssql-parameter-names-are-case-insensitive-as-in-SQL.patch @@ -0,0 +1,80 @@ +From 9dd9acff1977691733732dab80b1b22faaf89bd5 Mon Sep 17 00:00:00 2001 +From: Claude +Date: Tue, 27 Oct 2026 14:09:46 +0000 +Subject: [PATCH 4/4] mssql: parameter names are case-insensitive, as in SQL + Server + +--- + internal/sql/preprocess/dialect.go | 12 +++++++++--- + internal/sql/preprocess/preprocess.go | 21 +++++++++++++++++++++ + 2 files changed, 30 insertions(+), 3 deletions(-) + +diff --git a/internal/sql/preprocess/dialect.go b/internal/sql/preprocess/dialect.go +index 8c8e45456..79d1a8af2 100644 +--- a/internal/sql/preprocess/dialect.go ++++ b/internal/sql/preprocess/dialect.go +@@ -64,6 +64,11 @@ type Dialect struct { + // identifiers. It decides the case of a parameter named by a bare + // reference, e.g. sqlc.arg(FooBar). + FoldIdentifier bool ++ ++ // CaseInsensitiveParams reports whether parameter names that differ only ++ // in case are the same parameter, as @Name and @name are in SQL Server. ++ // Every use is written with the spelling of the first one. ++ CaseInsensitiveParams bool + } + + var dialects = map[config.Engine]Dialect{ +@@ -93,9 +98,10 @@ var dialects = map[config.Engine]Dialect{ + }, + // SQL Server binds by name with @name. Identifiers keep their case. + config.EngineMSSQL: { +- Style: StyleAtName, +- AtSign: true, +- NestedBlockComment: true, ++ Style: StyleAtName, ++ AtSign: true, ++ NestedBlockComment: true, ++ CaseInsensitiveParams: true, + }, + // ClickHouse binds with an unnumbered ?, like MySQL, and its identifiers + // keep their case. +diff --git a/internal/sql/preprocess/preprocess.go b/internal/sql/preprocess/preprocess.go +index 1af0f074a..591f157dd 100644 +--- a/internal/sql/preprocess/preprocess.go ++++ b/internal/sql/preprocess/preprocess.go +@@ -216,6 +216,9 @@ func Dialected(d Dialect, src string) *Result { + } + + stmt.Dollar, stmt.ParamErr = validatePlaceholders(occs) ++ if d.CaseInsensitiveParams { ++ canonicalizeNames(occs) ++ } + stmt.Params = number(d, occs) + stmt.Numbers = map[int]int{} + +@@ -401,3 +404,21 @@ func (d Dialect) replacement(occ *occurrence) string { + return "?" + } + } ++ ++// canonicalizeNames gives every parameter the spelling of its first use when ++// names that differ only in case refer to the same parameter. ++func canonicalizeNames(occs []occurrence) { ++ first := map[string]string{} ++ for i := range occs { ++ occ := &occs[i] ++ if occ.kind == kindEmbed || occ.kind == kindNative { ++ continue ++ } ++ key := strings.ToLower(occ.name) ++ if name, ok := first[key]; ok { ++ occ.name = name ++ continue ++ } ++ first[key] = occ.name ++ } ++} +-- +2.43.0 + diff --git a/sqlc-gen-csharp.sln b/sqlc-gen-csharp.sln index dafdce33b..1a97d54ba 100644 --- a/sqlc-gen-csharp.sln +++ b/sqlc-gen-csharp.sln @@ -81,6 +81,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqliteAsyncEnumerableExampl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NpgsqlDapperAsyncEnumerableExample", "examples\NpgsqlDapperAsyncEnumerableExample\NpgsqlDapperAsyncEnumerableExample.csproj", "{76289575-7D22-443C-9842-EC6E9D5FFE97}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "end2end", "end2end", "{6A58D200-3E82-7CA9-E450-4BE772E74186}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MssqlSyntaxMatrix", "end2end\MssqlSyntaxMatrix\MssqlSyntaxMatrix.csproj", "{FE15A125-3902-4F04-BF58-78674055C8AD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -522,6 +526,18 @@ Global {76289575-7D22-443C-9842-EC6E9D5FFE97}.Release|x64.Build.0 = Release|Any CPU {76289575-7D22-443C-9842-EC6E9D5FFE97}.Release|x86.ActiveCfg = Release|Any CPU {76289575-7D22-443C-9842-EC6E9D5FFE97}.Release|x86.Build.0 = Release|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Debug|x64.Build.0 = Debug|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Debug|x86.ActiveCfg = Debug|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Debug|x86.Build.0 = Debug|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Release|Any CPU.Build.0 = Release|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Release|x64.ActiveCfg = Release|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Release|x64.Build.0 = Release|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Release|x86.ActiveCfg = Release|Any CPU + {FE15A125-3902-4F04-BF58-78674055C8AD}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -536,5 +552,6 @@ Global {C024456A-CBDB-4688-A58F-4C4A36F17836} = {A796C405-F147-4A0B-A99C-D6419C1AC1EC} {F139C86C-55AE-4E0D-B369-A0D58C07C857} = {9404C766-3491-4959-A575-D6015FFB7AE6} {76289575-7D22-443C-9842-EC6E9D5FFE97} = {9404C766-3491-4959-A575-D6015FFB7AE6} + {FE15A125-3902-4F04-BF58-78674055C8AD} = {6A58D200-3E82-7CA9-E450-4BE772E74186} EndGlobalSection EndGlobal diff --git a/unit-tests/CodegenTests/CodegenQueryShapeTests.cs b/unit-tests/CodegenTests/CodegenQueryShapeTests.cs new file mode 100644 index 000000000..7e83d7f1b --- /dev/null +++ b/unit-tests/CodegenTests/CodegenQueryShapeTests.cs @@ -0,0 +1,79 @@ +using Google.Protobuf; +using NUnit.Framework; +using Plugin; +using SqlcGenCsharp; +using System.Text; + +namespace CodegenTests; + +// Query shapes every engine can produce, found by the SQL Server syntax matrix +public class CodegenQueryShapeTests +{ + private static GenerateRequest RequestFor(Query query, string options = "{}") => new() + { + Settings = new Settings { Engine = "postgresql", Codegen = new Codegen { Out = "DummyProject" } }, + Catalog = new Catalog { DefaultSchema = "public", Schemas = { new Schema { Name = "public" } } }, + Queries = { query }, + PluginOptions = ByteString.CopyFrom(options, Encoding.UTF8) + }; + + private static Column IntColumn(string name) => new() { Name = name, NotNull = true, Type = new Identifier { Name = "integer" } }; + + private static async Task GenerateQueries(GenerateRequest request) + { + var response = await new CodeGenerator().Generate(request); + return response.Files.Single(f => f.Name == "QuerySql.cs").Contents.ToStringUtf8(); + } + + [Test] + public async Task TestDuplicateColumnNamesGetASuffix() + { + var query = new Query + { + Name = "DuplicateIds", + Cmd = ":many", + Filename = "query.sql", + Text = "SELECT a.id, b.id FROM a JOIN b ON b.a_id = a.id", + Columns = { IntColumn("id"), IntColumn("id") } + }; + + var queries = await GenerateQueries(RequestFor(query)); + + Assert.That(queries, Does.Contain("DuplicateIdsRow(int Id, int Id2)")); + } + + [Test] + public async Task TestDapperReadsDuplicateColumnNamesByOrdinal() + { + var query = new Query + { + Name = "DuplicateIds", + Cmd = ":many", + Filename = "query.sql", + Text = "SELECT a.id, b.id FROM a JOIN b ON b.a_id = a.id", + Columns = { IntColumn("id"), IntColumn("id") } + }; + + var queries = await GenerateQueries(RequestFor(query, """{"useDapper": true}""")); + + // Dapper maps by name and could not fill Id2 + Assert.That(queries, Does.Contain("Id2 = reader.GetInt32(1)")); + } + + [Test] + public async Task TestSingleLineQueryWithQuotesAndBackslashesIsEscaped() + { + var query = new Query + { + Name = "Quoted", + Cmd = ":many", + Filename = "query.sql", + Text = """SELECT id AS "Author Id" FROM a WHERE name LIKE 'a\_%' ESCAPE '\'""", + Columns = { IntColumn("Author Id") } + }; + + var queries = await GenerateQueries(RequestFor(query)); + + Assert.That(queries, Does.Contain("""private const string QuotedSql = "SELECT id AS \"Author Id\" FROM a WHERE name LIKE 'a\\_%' ESCAPE '\\'";""")); + } +} diff --git a/unit-tests/CodegenTests/ExtensionsTests.cs b/unit-tests/CodegenTests/ExtensionsTests.cs index 68a9f1d94..10fbd8410 100644 --- a/unit-tests/CodegenTests/ExtensionsTests.cs +++ b/unit-tests/CodegenTests/ExtensionsTests.cs @@ -30,6 +30,8 @@ public class StringExtensionsTests [TestCase(" ", ExpectedResult = "")] [TestCase("__", ExpectedResult = "")] [TestCase("already Pascal", ExpectedResult = "AlreadyPascal")] + [TestCase("1st place", ExpectedResult = "_1stPlace")] + [TestCase("e-mail", ExpectedResult = "EMail")] public static string TestToPascalCase(string value) { return value.ToPascalCase(); From a8a1c4cb61cc5afd1a68589e6bfc73aa67f38072 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 14:57:15 +0000 Subject: [PATCH 07/11] mssql: complete plugin-side support, isolated CI, legacy and async examples - examples: QuickStartMssqlDalGen, MssqlLegacyExample, MssqlDapperLegacyExample (netstandard2.0), MssqlAsyncEnumerableExample (net8.0, useAsyncEnumerable + withCancellationToken), overrides - e2e: IDENTITY :execlastid, schema-scoped table, spatial/hierarchyid, NodaTime Instant overrides (incl. :copyfrom), async enumerable and cancellation; legacy testers (LocalDB on Windows) - the SQL Server database is created by EndToEndCommon for both test projects; .env no longer overrides variables already set - SQL Server examples move to sqlc.mssql.ci.yaml, checked by .github/workflows/mssql.yml with sqlc main + scripts/sqlc-patches (build_sqlc.sh, SQLC_REF); sqlc.ci.yaml is back to main's content - the syntax matrix is deterministic and CI fails when REPORT.md / results.json are stale - docs and AGENTS.md Co-Authored-By: Claude Opus 5.5 --- .github/scripts/start-mssql.ps1 | 20 + .github/workflows/legacy-tests.yml | 9 +- .github/workflows/mssql.yml | 140 + AGENTS.md | 22 +- Drivers/AGENTS.md | 12 + Drivers/MssqlDriver.cs | 49 +- Makefile | 17 +- docs/06_Mssql.md | 62 +- docs/08_Contributing.md | 10 + docs/09_Examples.md | 142 + docs/ExamplesDocGen/ExampleDocGen.csproj | 3 + docs/ExamplesDocGen/Program.cs | 21 +- end2end/AGENTS.md | 12 +- end2end/EndToEndCommon/EndToEndCommon.cs | 45 +- end2end/EndToEndCommon/EndToEndCommon.csproj | 1 + end2end/EndToEndScaffold/Config.cs | 15 +- end2end/EndToEndScaffold/Program.cs | 3 + .../EndToEndScaffold/Templates/MssqlTests.cs | 191 +- end2end/EndToEndTests/EndToEndTests.csproj | 1 + end2end/EndToEndTests/GlobalSetup.cs | 32 +- .../MssqlAsyncEnumerableTester.cs | 107 + end2end/EndToEndTests/MssqlDapperTester.cs | 4 + .../MssqlDapperTester.generated.cs | 77 +- end2end/EndToEndTests/MssqlTester.cs | 4 + .../EndToEndTests/MssqlTester.generated.cs | 77 +- .../EndToEndTestsLegacy.csproj | 10 + end2end/EndToEndTestsLegacy/GlobalSetup.cs | 1 + .../EndToEndTestsLegacy/MssqlDapperTester.cs | 27 + .../MssqlDapperTester.generated.cs | 786 ++++ end2end/EndToEndTestsLegacy/MssqlTester.cs | 27 + .../MssqlTester.generated.cs | 786 ++++ end2end/MssqlSyntaxMatrix/REPORT.md | 8 +- end2end/MssqlSyntaxMatrix/SqlcRunner.cs | 9 +- end2end/MssqlSyntaxMatrix/results.json | 2 +- end2end/scripts/generate_tests.sh | 8 - .../MssqlAsyncEnumerableExample/Models.cs | 9 + .../MssqlAsyncEnumerableExample.csproj | 20 + .../MssqlAsyncEnumerableExample/QuerySql.cs | 976 +++++ examples/MssqlAsyncEnumerableExample/Utils.cs | 13 + .../MssqlAsyncEnumerableExample/request.json | 1225 ++++++ .../request.message | Bin 0 -> 8315 bytes examples/MssqlDapperExample/Models.cs | 25 + .../MssqlDapperExample.csproj | 4 +- examples/MssqlDapperExample/QuerySql.cs | 387 +- examples/MssqlDapperExample/Utils.cs | 22 + examples/MssqlDapperExample/request.json | 668 ++- examples/MssqlDapperExample/request.message | Bin 25003 -> 31292 bytes examples/MssqlDapperLegacyExample/Models.cs | 84 + .../MssqlDapperLegacyExample.csproj | 21 + examples/MssqlDapperLegacyExample/QuerySql.cs | 1617 ++++++++ examples/MssqlDapperLegacyExample/Utils.cs | 63 + .../MssqlDapperLegacyExample/request.json | 3670 +++++++++++++++++ .../MssqlDapperLegacyExample/request.message | Bin 0 -> 31326 bytes examples/MssqlExample/Models.cs | 9 +- examples/MssqlExample/MssqlExample.csproj | 4 +- examples/MssqlExample/QuerySql.cs | 595 ++- examples/MssqlExample/request.json | 668 ++- examples/MssqlExample/request.message | Bin 24989 -> 31278 bytes examples/MssqlLegacyExample/Models.cs | 84 + .../MssqlLegacyExample.csproj | 21 + examples/MssqlLegacyExample/QuerySql.cs | 2555 ++++++++++++ examples/MssqlLegacyExample/Utils.cs | 15 + examples/MssqlLegacyExample/request.json | 3670 +++++++++++++++++ examples/MssqlLegacyExample/request.message | Bin 0 -> 31310 bytes examples/QuickStartMssqlDalGen/Models.cs | 9 + examples/QuickStartMssqlDalGen/QuerySql.cs | 867 ++++ .../QuickStartMssqlDalGen.csproj | 20 + examples/QuickStartMssqlDalGen/Utils.cs | 13 + examples/QuickStartMssqlDalGen/request.json | 1225 ++++++ .../QuickStartMssqlDalGen/request.message | Bin 0 -> 8108 bytes examples/config/mssql/authors/query.sql | 18 + examples/config/mssql/authors/schema.sql | 15 + examples/config/mssql/types/query.sql | 30 + examples/config/mssql/types/schema.sql | 11 + scripts/sqlc-patches/README.md | 18 +- scripts/sqlc-patches/SQLC_REF | 1 + scripts/sqlc-patches/build_sqlc.sh | 17 + scripts/sync_sqlc_options.sh | 45 +- sqlc.ci.yaml | 23 - sqlc.local.generated.yaml | 23 - sqlc.mssql.ci.yaml | 156 + sqlc.mssql.local.generated.yaml | 153 + sqlc.mssql.request.generated.yaml | 160 + sqlc.request.generated.yaml | 25 - .../CodegenTests/CodegenQueryShapeTests.cs | 2 +- 85 files changed, 21672 insertions(+), 324 deletions(-) create mode 100644 .github/scripts/start-mssql.ps1 create mode 100644 .github/workflows/mssql.yml create mode 100644 end2end/EndToEndTests/MssqlAsyncEnumerableTester.cs create mode 100644 end2end/EndToEndTestsLegacy/MssqlDapperTester.cs create mode 100644 end2end/EndToEndTestsLegacy/MssqlDapperTester.generated.cs create mode 100644 end2end/EndToEndTestsLegacy/MssqlTester.cs create mode 100644 end2end/EndToEndTestsLegacy/MssqlTester.generated.cs create mode 100644 examples/MssqlAsyncEnumerableExample/Models.cs create mode 100644 examples/MssqlAsyncEnumerableExample/MssqlAsyncEnumerableExample.csproj create mode 100644 examples/MssqlAsyncEnumerableExample/QuerySql.cs create mode 100644 examples/MssqlAsyncEnumerableExample/Utils.cs create mode 100644 examples/MssqlAsyncEnumerableExample/request.json create mode 100644 examples/MssqlAsyncEnumerableExample/request.message create mode 100644 examples/MssqlDapperLegacyExample/Models.cs create mode 100644 examples/MssqlDapperLegacyExample/MssqlDapperLegacyExample.csproj create mode 100644 examples/MssqlDapperLegacyExample/QuerySql.cs create mode 100644 examples/MssqlDapperLegacyExample/Utils.cs create mode 100644 examples/MssqlDapperLegacyExample/request.json create mode 100644 examples/MssqlDapperLegacyExample/request.message create mode 100644 examples/MssqlLegacyExample/Models.cs create mode 100644 examples/MssqlLegacyExample/MssqlLegacyExample.csproj create mode 100644 examples/MssqlLegacyExample/QuerySql.cs create mode 100644 examples/MssqlLegacyExample/Utils.cs create mode 100644 examples/MssqlLegacyExample/request.json create mode 100644 examples/MssqlLegacyExample/request.message create mode 100644 examples/QuickStartMssqlDalGen/Models.cs create mode 100644 examples/QuickStartMssqlDalGen/QuerySql.cs create mode 100644 examples/QuickStartMssqlDalGen/QuickStartMssqlDalGen.csproj create mode 100644 examples/QuickStartMssqlDalGen/Utils.cs create mode 100644 examples/QuickStartMssqlDalGen/request.json create mode 100644 examples/QuickStartMssqlDalGen/request.message create mode 100644 scripts/sqlc-patches/SQLC_REF create mode 100755 scripts/sqlc-patches/build_sqlc.sh create mode 100644 sqlc.mssql.ci.yaml create mode 100644 sqlc.mssql.local.generated.yaml create mode 100644 sqlc.mssql.request.generated.yaml diff --git a/.github/scripts/start-mssql.ps1 b/.github/scripts/start-mssql.ps1 new file mode 100644 index 000000000..cbc76d8e4 --- /dev/null +++ b/.github/scripts/start-mssql.ps1 @@ -0,0 +1,20 @@ +# Starts SQL Server Express LocalDB on the Windows runner and points the tests at it (GlobalSetup creates the database). +$ErrorActionPreference = 'Stop' + +if (-not (Get-Command sqllocaldb -ErrorAction SilentlyContinue)) { + choco install sqllocaldb -y --no-progress + if ($LASTEXITCODE -ne 0) { throw 'installing LocalDB failed' } + $env:Path = [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('Path', 'User') +} + +sqllocaldb create MSSQLLocalDB -s +if ($LASTEXITCODE -ne 0) { + sqllocaldb start MSSQLLocalDB + if ($LASTEXITCODE -ne 0) { throw 'starting LocalDB failed' } +} +sqllocaldb info MSSQLLocalDB + +# later steps read it; it wins over the .env value (EndToEndCommon does not overwrite existing variables) +"MSSQL_CONNECTION_STRING=Server=(localdb)\MSSQLLocalDB;Database=$env:TESTS_DB;Integrated Security=true;TrustServerCertificate=True" | + Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 +Write-Host 'mssql ready' diff --git a/.github/workflows/legacy-tests.yml b/.github/workflows/legacy-tests.yml index fbb774d43..b51d33177 100644 --- a/.github/workflows/legacy-tests.yml +++ b/.github/workflows/legacy-tests.yml @@ -54,12 +54,13 @@ jobs: # PostgreSQL 17 and MySQL 8.0 come preinstalled on the Windows runner image - using them is much faster than # downloading and installing them (setup-postgres ~3.5 min, choco mysql ~2 min). - # Both databases are set up in the background while the test projects are restored and built. This has to be + # SQL Server (experimental) runs as LocalDB, installed with choco when the image does not have it. + # The databases are set up in the background while the test projects are restored and built. This has to be # a single step: child processes do not outlive the step that started them (the database services do). - name: Set up databases and build test projects in parallel shell: pwsh run: | - $dbJobs = foreach ($db in 'postgres', 'mysql') { + $dbJobs = foreach ($db in 'postgres', 'mysql', 'mssql') { Start-Process -FilePath pwsh -PassThru -WindowStyle Hidden ` -ArgumentList '-NoProfile', '-File', ".github/scripts/start-$db.ps1" ` -RedirectStandardOutput "$env:RUNNER_TEMP\$db.log" -RedirectStandardError "$env:RUNNER_TEMP\$db.err.log" @@ -73,9 +74,9 @@ jobs: msbuild.exe ./end2end/EndToEndTestsLegacy/EndToEndTestsLegacy.csproj -p:Configuration=Release -p:FrameworkVersion=v4.7.2 -m $buildExit = $LASTEXITCODE - $dbJobs | Wait-Process -Timeout 180 + $dbJobs | Wait-Process -Timeout 300 $failed = $false - foreach ($db in 'postgres', 'mysql') { + foreach ($db in 'postgres', 'mysql', 'mssql') { if (-not (Select-String -Path "$env:RUNNER_TEMP\$db.log" -Pattern "$db ready" -Quiet)) { Get-ChildItem $env:RUNNER_TEMP -Filter "$db*.log" | ForEach-Object { Write-Host "--- $_"; Get-Content $_ } $failed = $true diff --git a/.github/workflows/mssql.yml b/.github/workflows/mssql.yml new file mode 100644 index 000000000..3bbc3add3 --- /dev/null +++ b/.github/workflows/mssql.yml @@ -0,0 +1,140 @@ +name: SQL Server (experimental) +defaults: + run: + shell: bash + +env: + WASI_SDK_VERSION: 25 + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "*" ] + paths: + - "**" + - "!**.md" + - "!benchmark/BenchmarkDotNet.Artifacts/**" + +# The mssql engine is only on sqlc main and needs the patches in scripts/sqlc-patches, so the SQL Server examples +# are generated by their own config (sqlc.mssql.ci.yaml) with a sqlc built here. Their end-to-end tests run with +# the other databases in tests.yml / legacy-tests.yml. +jobs: + build-sqlc: + name: Build sqlc (main + patches) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-go@v6 + with: + go-version: stable + + - name: Build sqlc + run: ./scripts/sqlc-patches/build_sqlc.sh bin + + - uses: actions/upload-artifact@v7 + with: + name: sqlc-mssql + path: bin/ + + codegen: + name: Codegen (SQL Server) + runs-on: ubuntu-latest + needs: [build-sqlc] + steps: + - uses: actions/checkout@v7 + + - name: Set up .NET + uses: actions/setup-dotnet@v6 + with: + global-json-file: global.json + + - uses: actions/download-artifact@v8 + with: + name: sqlc-mssql + path: bin + + - run: chmod +x bin/* + + - name: Verify the generated yamls are synced + run: | + ./scripts/sync_sqlc_options.sh + git diff --exit-code sqlc.mssql.local.generated.yaml sqlc.mssql.request.generated.yaml + + - name: Verify pushed generated code and requests are synced (process plugin) + run: | + dotnet publish LocalRunner -c release --output dist/ + bin/sqlc -f sqlc.mssql.local.generated.yaml diff + bin/sqlc -f sqlc.mssql.request.generated.yaml diff + + - name: Install the WASI workload and SDK + run: | + dotnet workload install wasi-experimental + folder="wasi-sdk-${{ env.WASI_SDK_VERSION }}.0-x86_64-linux" + wget -q "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${{ env.WASI_SDK_VERSION }}/$folder.tar.gz" + tar xf "$folder.tar.gz" + echo "WASI_SDK_PATH=$(pwd)/$folder" >> "$GITHUB_ENV" + + - name: Verify pushed generated code is synced (WASM plugin) + run: | + dotnet publish WasmRunner -c release + ./scripts/wasm/copy_plugin_to.sh dist + ./scripts/wasm/update_sha.sh sqlc.mssql.ci.yaml + bin/sqlc -f sqlc.mssql.ci.yaml diff + + - name: Build the SQL Server examples + run: | + for project in examples/*Mssql*/*.csproj; do dotnet build "$project"; done + + syntax-matrix: + name: T-SQL syntax matrix + runs-on: ubuntu-latest + needs: [build-sqlc] + services: + mssql: + image: mcr.microsoft.com/mssql/server:2022-latest + env: + ACCEPT_EULA: "Y" + MSSQL_SA_PASSWORD: "Passw0rd!Test" + ports: + - 1433:1433 + options: >- + --health-cmd "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'Passw0rd!Test' -Q 'SELECT 1'" + --health-interval 5s --health-timeout 10s --health-retries 20 + + steps: + - uses: actions/checkout@v7 + + - name: Set up .NET + uses: actions/setup-dotnet@v6 + with: + global-json-file: global.json + + - uses: actions/download-artifact@v8 + with: + name: sqlc-mssql + path: bin + + - run: chmod +x bin/* + + - name: Run the syntax matrix + run: make mssql-syntax-matrix + + - uses: actions/upload-artifact@v7 + if: always() + with: + name: mssql-syntax-matrix + path: | + end2end/MssqlSyntaxMatrix/REPORT.md + end2end/MssqlSyntaxMatrix/results.json + + - name: Verify the pushed report is up to date + run: git diff --exit-code end2end/MssqlSyntaxMatrix/REPORT.md end2end/MssqlSyntaxMatrix/results.json diff --git a/AGENTS.md b/AGENTS.md index cd3ccbc6b..75e5abe16 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,7 +67,7 @@ architecture test in the same change and explain why. PluginRunner.Run read GenerateRequest from stdin CodeGenerator.Generate InitGenerators Options(request); debugRequest -> dump request.json/request.message and stop - InstantiateDriver engine (postgresql|mysql|sqlite) -> NpgsqlDriver | MySqlConnectorDriver | SqliteDriver + InstantiateDriver engine (postgresql|mysql|sqlite|mssql) -> NpgsqlDriver | MySqlConnectorDriver | SqliteDriver | MssqlDriver group queries by query *file name* -> one QueriesGen file per group ModelsGen (Models.cs), UtilsGen (Utils.cs, only if it has members), CsprojGen (if generateCsproj) PluginRunner write GenerateResponse to stdout @@ -98,6 +98,9 @@ public class QuerySql : IDisposable // Npgsql/MySQL: IDisposa |-----------------------------------------------------------------------|----------------------------------------------------------|--------------------------------------------------| | `examples/**` (code, `.csproj`, `request.json`, `request.message`) | plugin code + `examples/config/**` + `sqlc.ci.yaml` | `make sqlc-generate` | | `sqlc.local.generated.yaml`, `sqlc.request.generated.yaml` | `sqlc.ci.yaml` (`.sql` section) | `make sync-sqlc-options` (needs `yq`; part of `make sqlc-generate`) | +| `sqlc.mssql.local.generated.yaml`, `sqlc.mssql.request.generated.yaml` | `sqlc.mssql.ci.yaml` | `make sync-sqlc-options` | +| `examples/*Mssql*/**` | plugin code + `examples/config/mssql/**` + `sqlc.mssql.ci.yaml` | `make sqlc-generate-mssql` (needs `bin/sqlc`, see section 3) | +| `end2end/MssqlSyntaxMatrix/REPORT.md`, `results.json` | `end2end/MssqlSyntaxMatrix/cases/**` + the sqlc builds | `make mssql-syntax-matrix` (SQL Server running; deterministic, CI diffs it) | | `end2end/EndToEndTests*/**/*.generated.cs` | `end2end/EndToEndScaffold/` templates + `Config.cs` | `make generate-end2end-tests` | | `unit-tests/CodegenTests/test-requests/*/request.*` | `unit-tests/sqlc.yaml` + sibling `schema.sql`/`query.sql` | `cd unit-tests && sqlc generate` (part of `make unit-tests`) | | `GeneratedProtobuf/Codegen.cs` | sqlc's `plugin/codegen.proto` | `make generate-protobuf` | @@ -149,6 +152,16 @@ CI fails if any of the generated artifacts above is stale (`sqlc diff`, `git dif - `WasmRunner.csproj` forces a `wasip1` core module because sqlc's wazero cannot load the wasip2 component the .NET 10 WASI SDK produces. The override relies on private SDK item names - re-verify after any SDK upgrade. +**SQL Server (experimental)** +- sqlc releases have no `mssql` engine; it is on sqlc `main` and this plugin needs it with the patches in + `scripts/sqlc-patches` (`SQLC_REF` pins the commit; `build_sqlc.sh bin` builds `bin/sqlc-upstream` and `bin/sqlc`). + That is why the SQL Server examples live in `sqlc.mssql.ci.yaml` and are checked by `.github/workflows/mssql.yml`, + while `sqlc.ci.yaml` stays on the pinned sqlc release. The mssql e2e tests still run in the normal e2e jobs. +- sqlc's mssql catalog calls the default schema `public` (and lists it twice); `dbo.x` and `x` are the same table. +- Anything sqlc cannot type comes through with an empty type name; `MssqlDriver` maps it to `object` instead of failing. +- `end2end/MssqlSyntaxMatrix` is the evidence for what sqlc must support: add a case there for any T-SQL construct + before reporting it upstream. + **Repository** - `.env` is shared by docker-compose, the end-to-end tests and CI; `RepositoryTests` asserts it is consistent with `benchmark/.env`. @@ -201,7 +214,7 @@ CI fails if any of the generated artifacts above is stale (`sqlc diff`, `git dif | Option | Default | Notes | |-------------------------------|----------------------------------------------------------------|-------| | `targetFramework` | `net8.0` | `netstandard2.0`, `netstandard2.1`, `net8.0`, `net9.0`, `net10.0` | -| `overrideDriverVersion` | `2.4.0` MySqlConnector, `8.0.6` Npgsql, `10.0.13` Microsoft.Data.Sqlite | constants in each driver | +| `overrideDriverVersion` | `2.4.0` MySqlConnector, `8.0.6` Npgsql, `10.0.13` Microsoft.Data.Sqlite, `7.1.1` Microsoft.Data.SqlClient | constants in each driver | | `generateCsproj` | `true` | | | `namespaceName` | output directory name | | | `useDapper` | `false` | | @@ -223,7 +236,7 @@ CI fails if any of the generated artifacts above is stale (`sqlc diff`, `git dif | `sqlc.slice` | 🚫 (use arrays) | ✅ | ✅ | runtime `Utils.TransformQueryForSliceArgs` | SQL Server (experimental, `MssqlDriver`) supports every annotation and macro above. `:execlastid` relies on a user-written -`OUTPUT INSERTED.id`, `:copyfrom` uses `SqlBulkCopy`. It needs a sqlc build with the mssql macro fixes. +`OUTPUT INSERTED.id`, `:copyfrom` uses `SqlBulkCopy`. It needs sqlc `main` with `scripts/sqlc-patches` (section 3). ### Target frameworks - The plugin projects (section 1 graph) and the tooling/test projects target `net10.0`; SDK pinned in `global.json`. @@ -239,6 +252,9 @@ make generate-end2end-tests make test-plugin # everything above + build + end-to-end tests (Docker) make setup-wasm-plugin # WASM plugin -> dist/plugin.wasm (needs wasi-experimental workload + WASI SDK 25 in WASI_SDK_PATH) sqlc -f sqlc.ci.yaml diff # WASM output must equal the committed examples +scripts/sqlc-patches/build_sqlc.sh bin # sqlc main (+ patches) for SQL Server +make sqlc-generate-mssql # regenerate the SQL Server examples with bin/sqlc +make mssql-syntax-matrix # T-SQL syntax matrix (SQL Server running) make dotnet-format ``` diff --git a/Drivers/AGENTS.md b/Drivers/AGENTS.md index 7138263fe..7dae5ca69 100644 --- a/Drivers/AGENTS.md +++ b/Drivers/AGENTS.md @@ -97,6 +97,18 @@ Outliers: Does not apply writer functions or overrides. - `:execlastid`: relies on a user-written `RETURNING`. +**SQL Server (`MssqlDriver`, experimental)** +- Params: sqlc already writes `@name`, so `TransformQueryText` returns the text as is. A new `SqlConnection` per call; no `IDisposable`. +- Every parameter gets an explicit `SqlDbType` (`DbTypeOverride` of the mapping, `AddParametersToCommand` override): + an untyped `NULL` is sent as nvarchar, which SQL Server refuses for binary columns, and `AddWithValue(DateTime)` is + `datetime` (3.33 ms). `decimal`s stay untyped so precision and scale come from the value. Spatial/hierarchyid use + `SqlDbType.Udt` + `UdtTypeName`. +- Dapper: `SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2)` (a type handler for a built-in type is ignored by + Dapper), and `Utils.BinaryParameter` (an `ICustomQueryParameter`) for `byte[]`. +- `:copyfrom`: `SqlBulkCopy` from a `DataTable` whose columns are `object` (writer functions may change the CLR type). +- `:execlastid`: relies on a user-written `OUTPUT INSERTED.id`. +- An empty sqlc type name (expressions sqlc cannot type yet) maps to `object`. + ## Adding a type mapping 1. Add/extend the `ColumnMappings` entry in the right driver (reader, array reader, writer, `ConvertFunc`, usings, Dapper handler as needed). 2. Value type? Add it to `NullableTypes` (for a DB-specific type, add it from the driver, not the base list). diff --git a/Drivers/MssqlDriver.cs b/Drivers/MssqlDriver.cs index 3133c8f99..396da14d3 100644 --- a/Drivers/MssqlDriver.cs +++ b/Drivers/MssqlDriver.cs @@ -26,23 +26,27 @@ public MssqlDriver( } // Dapper sends a null byte[] as nvarchar, which SQL Server refuses to convert to a binary type - private const string BinaryParameterImpl = """ - public class BinaryParameter : SqlMapper.ICustomQueryParameter - { - private readonly byte[]? _value; - - public BinaryParameter(byte[]? value) + private static string BinaryParameterImpl(bool isDotnetCore) + { + var nullable = isDotnetCore ? "?" : ""; + return $$""" + public class BinaryParameter : SqlMapper.ICustomQueryParameter { - _value = value; - } + private readonly byte[]{{nullable}} _value; - public void AddParameter(IDbCommand command, string name) - { - var parameter = new SqlParameter(name, SqlDbType.VarBinary) { Value = (object?)_value ?? DBNull.Value }; - command.Parameters.Add(parameter); + public BinaryParameter(byte[]{{nullable}} value) + { + _value = value; + } + + public void AddParameter(IDbCommand command, string name) + { + var parameter = new SqlParameter(name, SqlDbType.VarBinary) { Value = (object{{nullable}})_value ?? DBNull.Value }; + command.Parameters.Add(parameter); + } } - } - """; + """; + } private static readonly SqlMapperImplFunc NodaInstantTypeHandler = _ => """ private class NodaInstantTypeHandler : SqlMapper.TypeHandler @@ -250,11 +254,13 @@ public override void SetValue(IDbDataParameter parameter, Instant value) : $"DateTime.SpecifyKind({Variable.Reader.AsVarName()}.GetDateTime({ordinal}), DateTimeKind.Utc).ToInstant()", writerFn: (el, dbType, notNull, isDapper, _) => { - var convert = dbType.ToLower() == "datetimeoffset" ? "ToDateTimeOffset()" : "ToDateTimeUtc()"; + var (convert, type) = dbType.ToLower() == "datetimeoffset" + ? ("ToDateTimeOffset()", "DateTimeOffset") + : ("ToDateTimeUtc()", "DateTime"); if (notNull) return $"{el}.{convert}"; var nullValue = isDapper ? "null" : "(object)DBNull.Value"; - return $"{el} is null ? {nullValue} : {el}.Value.{convert}"; + return $"{el} is null ? {nullValue} : ({type}?){el}.Value.{convert}"; }, usingDirectives: ["System", "NodaTime", "NodaTime.Extensions"], sqlMapper: "SqlMapper.AddTypeHandler(typeof(Instant), new NodaInstantTypeHandler());", @@ -362,7 +368,7 @@ public override MemberDeclarationSyntax[] GetMemberDeclarationsForUtils() ParseMemberDeclaration(TransformQueryForSliceArgsImpl)! ], SliceQueryExists()) .AddRangeIf([ - ParseMemberDeclaration(BinaryParameterImpl)! + ParseMemberDeclaration(BinaryParameterImpl(Options.DotnetFramework.IsDotnetCore()))! ], Options.UseDapper && TypeExistsInQueries("byte[]"))]; } @@ -440,11 +446,10 @@ public string GetCopyFromImpl(Query query, string queryTextConstant) ? $"[{query.InsertIntoTable.Schema}].[{query.InsertIntoTable.Name}]" : $"[{query.InsertIntoTable.Name}]"; - var addColumns = query.Params.Select(p => - { - var csharpType = GetCsharpTypeWithoutNullableSuffix(p.Column, query); - return $"""{tableVar}.Columns.Add("{p.Column.Name}", typeof({csharpType}));"""; - }).JoinByNewLine(); + // the columns hold whatever a writer function produces (e.g. a DateTime for an Instant), SqlBulkCopy converts it + var addColumns = query.Params + .Select(p => $"""{tableVar}.Columns.Add("{p.Column.Name}", typeof(object));""") + .JoinByNewLine(); var mapColumns = query.Params.Select(p => $"""{bulkCopyVar}.ColumnMappings.Add("{p.Column.Name}", "{p.Column.Name}");""").JoinByNewLine(); diff --git a/Makefile b/Makefile index f224b8b4b..aba4f58bb 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,12 @@ sqlc-generate-requests: dotnet-publish-process sqlc-generate: sync-sqlc-options dotnet-publish-process sqlc-generate-requests SQLCCACHE=./; sqlc -f sqlc.local.generated.yaml generate +# SQL Server (experimental): needs sqlc main with scripts/sqlc-patches applied, see scripts/sqlc-patches/README.md +SQLC_MSSQL ?= bin/sqlc +sqlc-generate-mssql: sync-sqlc-options dotnet-publish-process + $(SQLC_MSSQL) -f sqlc.mssql.request.generated.yaml generate + $(SQLC_MSSQL) -f sqlc.mssql.local.generated.yaml generate + test-plugin: unit-tests sqlc-generate generate-end2end-tests dotnet-build run-end2end-tests # WASM type plugin @@ -47,13 +53,14 @@ $(WASM_PLUGIN_OUTPUT): $(WASM_SOURCES) ./scripts/wasm/copy_plugin_to.sh dist ./scripts/wasm/update_sha.sh sqlc.ci.yaml -# SQL Server syntax matrix (needs SQL Server running and the process plugin published) -SQLC_UPSTREAM ?= sqlc -SQLC_PATCHED ?= sqlc +# SQL Server syntax matrix (needs SQL Server running); builds come from scripts/sqlc-patches/build_sqlc.sh +SQLC_UPSTREAM ?= bin/sqlc-upstream +SQLC_PATCHED ?= bin/sqlc +SQLC_REF := $(shell cut -c1-7 scripts/sqlc-patches/SQLC_REF) mssql-syntax-matrix: dotnet-publish-process dotnet run --project end2end/MssqlSyntaxMatrix -- \ - --sqlc "upstream=$(SQLC_UPSTREAM)@sqlc main" \ - --sqlc "patched=$(SQLC_PATCHED)@sqlc main + scripts/sqlc-patches" \ + --sqlc "upstream=$(SQLC_UPSTREAM)@sqlc main ($(SQLC_REF))" \ + --sqlc "patched=$(SQLC_PATCHED)@sqlc main ($(SQLC_REF)) + scripts/sqlc-patches" \ --compile run-end2end-tests: diff --git a/docs/06_Mssql.md b/docs/06_Mssql.md index e90a08c85..a3c33173a 100644 --- a/docs/06_Mssql.md +++ b/docs/06_Mssql.md @@ -1,7 +1,8 @@ # SQL Server (experimental) -> **Status: proof of concept.** sqlc's SQL Server engine (`engine: mssql`) is new and lives on sqlc's `main` branch, -> so a sqlc build from `main` is required. The `sqlc.arg` / `sqlc.narg` / `sqlc.slice` / `sqlc.embed` macros, -> `TRUNCATE TABLE` and `:copyfrom` additionally need the fixes tracked in the sqlc repository (see [Contributing](#contributing)). +> **Status: experimental.** sqlc's SQL Server engine (`engine: mssql`) is only on sqlc `main`, and this plugin +> currently needs it with the patches in [`scripts/sqlc-patches`](scripts/sqlc-patches) (macros, `:copyfrom`, +> `TRUNCATE`, case-insensitive parameter names). What sqlc does and does not support yet is tracked, with SQL Server +> itself as the reference, in the [T-SQL syntax matrix](end2end/MssqlSyntaxMatrix/REPORT.md). ```yaml sql: @@ -13,17 +14,20 @@ sql: out: Generated ``` -Parameters are written as `@name` (or with the sqlc macros), and the generated code uses `Microsoft.Data.SqlClient`. +Parameters are written as `@name` (matched case-insensitively, like SQL Server does) or with the sqlc macros +(`sqlc.arg`, `sqlc.narg`, `sqlc.slice`, `sqlc.embed`). The generated code uses `Microsoft.Data.SqlClient`, binds every +parameter with an explicit `SqlDbType` (so a `NULL` reaches a binary column and a `DateTime` keeps `datetime2` +precision), and opens a `SqlConnection` per call unless a transaction is given (`QuerySql.WithTransaction`). +All target frameworks are supported, Dapper included.
:execlastid - Implementation ## :execlastid - Implementation -Implemented via an `OUTPUT` clause, allowing the `INSERT` command to return the newly created id. -Use a `SEQUENCE` default (or `IDENTITY`) for the id column. +Implemented via an `OUTPUT` clause on the `INSERT`, for an `IDENTITY` column or a `SEQUENCE` default alike: ```sql -INSERT INTO tab1 (field1, field2) OUTPUT INSERTED.id_field VALUES (@field1, @field2); +INSERT INTO tab1 (field1, field2) OUTPUT INSERTED.id VALUES (@field1, @field2); ```
@@ -37,24 +41,30 @@ Implemented via `SqlBulkCopy`: the batch is copied into a `DataTable` and writte
Supported Data Types -| DB Type | C# Type | Supported? | -|------------------------------------|------------------|------------| -| bit | bool | ✅ | -| tinyint | byte | ✅ | -| smallint | short | ✅ | -| int | int | ✅ | -| bigint | long | ✅ | -| real | float | ✅ | -| float | double | ✅ | -| decimal, numeric, money, smallmoney | decimal | ✅ | -| char, nchar, varchar, nvarchar, text, ntext, xml | string | ✅ | -| binary, varbinary, image, rowversion | byte[] | ✅ | -| date, datetime, datetime2, smalldatetime | DateTime | ✅ | -| datetimeoffset | DateTimeOffset | ✅ | -| time | TimeSpan | ✅ | -| uniqueidentifier | Guid | ✅ | -| sql_variant | object | ✅ | - -Not supported yet: `geography`, `geometry`, `hierarchyid`, user defined types, netstandard (legacy) targets. +| DB Type | C# Type | +|----------------------------------------------------|------------------| +| bit | bool | +| tinyint | byte | +| smallint | short | +| int | int | +| bigint | long | +| real | float | +| float | double | +| decimal, numeric, money, smallmoney | decimal | +| char, nchar, varchar, nvarchar, text, ntext, xml | string | +| binary, varbinary, image, rowversion | byte[] | +| date, datetime, datetime2, smalldatetime | DateTime | +| datetimeoffset | DateTimeOffset | +| time | TimeSpan | +| uniqueidentifier | Guid | +| geography, geometry, hierarchyid | SqlGeography, SqlGeometry, SqlHierarchyId (`Microsoft.SqlServer.Types`) | +| sql_variant, and expressions sqlc cannot type yet | object | +## Useful Overrides +| DB Type | C# Type Override | Description | +|----------------------------------------|------------------|----------------------------------| +| datetime, datetime2, datetimeoffset | Instant | NodaTime instant (UTC) | + +Not supported yet: SQL Server 2025 `json` / `vector`, user-defined alias types (sqlc reports the alias name, not +its base type), table-valued parameters, stored procedure results.
diff --git a/docs/08_Contributing.md b/docs/08_Contributing.md index 0aa0e1f86..fb6a692c9 100644 --- a/docs/08_Contributing.md +++ b/docs/08_Contributing.md @@ -47,6 +47,16 @@ make setup-wasm-plugin # builds the WASM plugin into dist/plugin.wasm sqlc -f sqlc.ci.yaml diff # verifies the WASM plugin generates exactly the committed examples ``` +### SQL Server (experimental) +The `mssql` engine is only on sqlc `main` and needs the patches in `scripts/sqlc-patches`, so its examples live in +`sqlc.mssql.ci.yaml` (not `sqlc.ci.yaml`, which CI runs with the pinned sqlc release): +``` +scripts/sqlc-patches/build_sqlc.sh bin # bin/sqlc-upstream (sqlc main) and bin/sqlc (main + patches) +make sqlc-generate-mssql # regenerates the SQL Server examples and requests with bin/sqlc +docker compose up -d mssqldb +make mssql-syntax-matrix # T-SQL support matrix, see end2end/MssqlSyntaxMatrix/README.md +``` + ### Testing generated code Testing the SQLC generated code via a predefined flow: ``` diff --git a/docs/09_Examples.md b/docs/09_Examples.md index 6d797fb71..70c8a62df 100644 --- a/docs/09_Examples.md +++ b/docs/09_Examples.md @@ -590,6 +590,17 @@ namespaceName: NpgsqlDapperAsyncEnumerableExampleGen +
+QuickStartMssqlDalGen + +## Engine `mssql`: [QuickStartMssqlDalGen](examples/QuickStartMssqlDalGen) +### [Schema](examples/config/mssql/authors/schema.sql) | [Queries](examples/config/mssql/authors/query.sql) | [End2End Test](end2end/EndToEndTests/QuickStartMssqlDalGen.cs) +### Config +```yaml +``` + +
+
Mssql @@ -601,6 +612,27 @@ useDapper: false targetFramework: net10.0 generateCsproj: true namespaceName: MssqlExampleGen +overrides: +- column: "GetMssqlFunctions:max_int" + csharp_type: + type: "int" + notNull: false +- column: "GetMssqlFunctions:max_varchar" + csharp_type: + type: "string" + notNull: false +- column: "GetMssqlFunctions:max_datetime2" + csharp_type: + type: "DateTime" + notNull: true +- column: "*:c_datetime2_instant" + csharp_type: + type: "Instant" + notNull: false +- column: "*:c_datetimeoffset_instant" + csharp_type: + type: "Instant" + notNull: false ```
@@ -616,6 +648,116 @@ useDapper: true targetFramework: net9.0 generateCsproj: true namespaceName: MssqlDapperExampleGen +overrides: +- column: "GetMssqlFunctions:max_int" + csharp_type: + type: "int" + notNull: false +- column: "GetMssqlFunctions:max_varchar" + csharp_type: + type: "string" + notNull: false +- column: "GetMssqlFunctions:max_datetime2" + csharp_type: + type: "DateTime" + notNull: true +- column: "*:c_datetime2_instant" + csharp_type: + type: "Instant" + notNull: false +- column: "*:c_datetimeoffset_instant" + csharp_type: + type: "Instant" + notNull: false +``` + + + +
+MssqlLegacy + +## Engine `mssql`: [MssqlLegacyExample](examples/MssqlLegacyExample) +### [Schema](examples/config/mssql/authors/schema.sql) | [Queries](examples/config/mssql/authors/query.sql) | [End2End Test](end2end/EndToEndTestsLegacy/MssqlTester.cs) +### Config +```yaml +useDapper: false +targetFramework: netstandard2.0 +generateCsproj: true +namespaceName: MssqlLegacyExampleGen +overrides: +- column: "GetMssqlFunctions:max_int" + csharp_type: + type: "int" + notNull: false +- column: "GetMssqlFunctions:max_varchar" + csharp_type: + type: "string" + notNull: false +- column: "GetMssqlFunctions:max_datetime2" + csharp_type: + type: "DateTime" + notNull: true +- column: "*:c_datetime2_instant" + csharp_type: + type: "Instant" + notNull: false +- column: "*:c_datetimeoffset_instant" + csharp_type: + type: "Instant" + notNull: false +``` + +
+ +
+MssqlDapperLegacy + +## Engine `mssql`: [MssqlDapperLegacyExample](examples/MssqlDapperLegacyExample) +### [Schema](examples/config/mssql/authors/schema.sql) | [Queries](examples/config/mssql/authors/query.sql) | [End2End Test](end2end/EndToEndTestsLegacy/MssqlDapperTester.cs) +### Config +```yaml +useDapper: true +targetFramework: netstandard2.0 +generateCsproj: true +namespaceName: MssqlDapperLegacyExampleGen +overrides: +- column: "GetMssqlFunctions:max_int" + csharp_type: + type: "int" + notNull: false +- column: "GetMssqlFunctions:max_varchar" + csharp_type: + type: "string" + notNull: false +- column: "GetMssqlFunctions:max_datetime2" + csharp_type: + type: "DateTime" + notNull: true +- column: "*:c_datetime2_instant" + csharp_type: + type: "Instant" + notNull: false +- column: "*:c_datetimeoffset_instant" + csharp_type: + type: "Instant" + notNull: false +``` + +
+ +
+MssqlAsyncEnumerable + +## Engine `mssql`: [MssqlAsyncEnumerableExample](examples/MssqlAsyncEnumerableExample) +### [Schema](examples/config/mssql/authors/schema.sql) | [Queries](examples/config/mssql/authors/query.sql) | [End2End Test](end2end/EndToEndTests/MssqlAsyncEnumerableTester.cs) +### Config +```yaml +useDapper: false +useAsyncEnumerable: true +withCancellationToken: true +targetFramework: net8.0 +generateCsproj: true +namespaceName: MssqlAsyncEnumerableExampleGen ```
diff --git a/docs/ExamplesDocGen/ExampleDocGen.csproj b/docs/ExamplesDocGen/ExampleDocGen.csproj index a47f11f7d..df0424009 100644 --- a/docs/ExamplesDocGen/ExampleDocGen.csproj +++ b/docs/ExamplesDocGen/ExampleDocGen.csproj @@ -12,6 +12,9 @@ Always + + Always +
diff --git a/docs/ExamplesDocGen/Program.cs b/docs/ExamplesDocGen/Program.cs index 71528fd8c..16745632b 100644 --- a/docs/ExamplesDocGen/Program.cs +++ b/docs/ExamplesDocGen/Program.cs @@ -7,14 +7,11 @@ public static class ExamplesDocGen { public static void Main() { - var yamlContent = File.ReadAllText("sqlc.ci.yaml"); - var yaml = new YamlStream(); - yaml.Load(new StringReader(yamlContent)); - - var root = (YamlMappingNode)yaml.Documents[0].RootNode; - var sqlArray = (YamlSequenceNode)root.Children[new YamlScalarNode("sql")]; - - var exampleNodes = sqlArray.Children.Select(ParseConfigNode).ToList(); + // sqlc.mssql.ci.yaml holds the (experimental) SQL Server examples + var exampleNodes = new[] { "sqlc.ci.yaml", "sqlc.mssql.ci.yaml" } + .SelectMany(ReadSqlNodes) + .Select(ParseConfigNode) + .ToList(); var contents = $""" # Examples {exampleNodes.JoinByNewLine()} @@ -23,6 +20,14 @@ public static void Main() Console.Write(contents); } + private static IEnumerable ReadSqlNodes(string file) + { + var yaml = new YamlStream(); + yaml.Load(new StringReader(File.ReadAllText(file))); + var root = (YamlMappingNode)yaml.Documents[0].RootNode; + return ((YamlSequenceNode)root.Children[new YamlScalarNode("sql")]).Children; + } + private static string ParseConfigNode(YamlNode node) { var item = (YamlMappingNode)node; diff --git a/end2end/AGENTS.md b/end2end/AGENTS.md index 32061b51a..737b9c85e 100644 --- a/end2end/AGENTS.md +++ b/end2end/AGENTS.md @@ -12,7 +12,7 @@ Read the root `AGENTS.md` first. | `EndToEndCommon` | `netstandard2.0`| Shared helpers: env loading, SQLite DB setup. Also used by `benchmark/`. | ## How a tester class is built -Each tester (`NpgsqlTester`, `NpgsqlDapperTester`, `MySqlConnectorTester`, ... - 6 in total) is a `partial` class in two files: +Each tester (`NpgsqlTester`, `NpgsqlDapperTester`, `MySqlConnectorTester`, ... - 8 in total) is a `partial` class in two files: - `.cs` - **hand-written**: the `QuerySql` instance and the `[TearDown]` that empties every table. - `.generated.cs` - **generated, do not edit**: produced by `EndToEndScaffold` via `end2end/scripts/generate_tests.sh` (`make generate-end2end-tests`). CI regenerates and fails on `git diff`. @@ -42,6 +42,14 @@ C# 7.3 / `net472` compatible (block-scoped namespaces, no target-typed `new`, no containers yourself after a schema change, use `docker-compose up --build`. - SQLite is a file DB created by `GlobalSetup` from the schemas copied into the test output (`authors.sqlite.schema.sql`, `types.sqlite.schema.sql`) and deleted afterwards. +- SQL Server (experimental) runs in docker-compose (`mssqldb`, plain image); its image has no init hook, so + `EndToEndCommon.SetupTestsMssqlDb` (called from both `GlobalSetup`s) drops and recreates the database from + `authors.mssql.schema.sql` / `types.mssql.schema.sql`, split on `GO`. The Windows legacy job uses LocalDB + (`.github/scripts/start-mssql.ps1` sets `MSSQL_CONNECTION_STRING`). - Connection strings come from the root `.env` (`POSTGRES_CONNECTION_STRING`, `MYSQL_CONNECTION_STRING`, - `SQLITE_CONNECTION_STRING`). The MySQL one needs `AllowLoadLocalInfile=true` for `:copyfrom`. + `SQLITE_CONNECTION_STRING`, `MSSQL_CONNECTION_STRING`); a variable already set in the environment wins over the + file. The MySQL one needs `AllowLoadLocalInfile=true` for `:copyfrom`. +- `Config.NotForLegacy` lists test types left out of the legacy testers (spatial types need a native library on + .NET Framework). +- `end2end/MssqlSyntaxMatrix` is not a test project: it is the T-SQL support matrix (see its README). - The legacy workflow (Windows) loads the PostgreSQL schemas itself with `psql`; keep it in mind when adding schema files. diff --git a/end2end/EndToEndCommon/EndToEndCommon.cs b/end2end/EndToEndCommon/EndToEndCommon.cs index 16f348866..2cff7bf39 100644 --- a/end2end/EndToEndCommon/EndToEndCommon.cs +++ b/end2end/EndToEndCommon/EndToEndCommon.cs @@ -1,4 +1,5 @@ using dotenv.net; +using Microsoft.Data.SqlClient; using Microsoft.Data.Sqlite; using System; using System.IO; @@ -14,6 +15,47 @@ public static class EndToEndCommon public const string MssqlConnectionStringEnv = "MSSQL_CONNECTION_STRING"; public const string SqliteConnectionStringEnv = "SQLITE_CONNECTION_STRING"; + public static void SetupTestsMssqlDb() => SetupMssqlDb( + new string[] { "authors.mssql.schema.sql", "types.mssql.schema.sql" } + ); + + // SQL Server has no init hook in its docker image, so the database is (re)created here. The schema files are + // split on GO, since CREATE SCHEMA / VIEW / PROCEDURE must start a batch. + public static void SetupMssqlDb(string[] schemaFiles) + { + LoadEnvFile(); + var connectionString = Environment.GetEnvironmentVariable(MssqlConnectionStringEnv); + if (string.IsNullOrWhiteSpace(connectionString)) + throw new InvalidOperationException($"{MssqlConnectionStringEnv} environment variable is not set"); + + var builder = new SqlConnectionStringBuilder(connectionString); + var database = builder.InitialCatalog; + builder.InitialCatalog = "master"; + using (var connection = new SqlConnection(builder.ConnectionString)) + { + connection.Open(); + ExecuteMssql(connection, $"IF DB_ID('{database}') IS NOT NULL BEGIN ALTER DATABASE [{database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [{database}]; END"); + ExecuteMssql(connection, $"CREATE DATABASE [{database}]"); + connection.ChangeDatabase(database); + foreach (var schemaFile in schemaFiles) + { + var schemaSql = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, schemaFile)); + foreach (var batch in MssqlBatchSeparatorRegex.Split(schemaSql)) + if (batch.Trim().Length > 0) + ExecuteMssql(connection, batch); + } + } + Console.WriteLine($"Initialized SQL Server database {database}"); + } + + private static readonly Regex MssqlBatchSeparatorRegex = new Regex(@"^\s*GO\s*$", RegexOptions.Multiline | RegexOptions.IgnoreCase); + + private static void ExecuteMssql(SqlConnection connection, string sql) + { + using (var command = new SqlCommand(sql, connection)) + command.ExecuteNonQuery(); + } + public static void SetupTestsSqliteDb() => SetupSqliteDb( new string[] { "authors.sqlite.schema.sql", "types.sqlite.schema.sql" } ); @@ -25,7 +67,8 @@ public static void LoadEnvFile() { if (!File.Exists(EnvFile)) throw new FileNotFoundException($"{EnvFile} not found"); - DotEnv.Load(options: new DotEnvOptions(envFilePaths: new[] { EnvFile })); + // variables already set (e.g. by CI, pointing at another server) win over the file + DotEnv.Load(options: new DotEnvOptions(envFilePaths: new[] { EnvFile }, overwriteExistingVars: false)); } public static void SetupSqliteDb(string[] schemaFiles) diff --git a/end2end/EndToEndCommon/EndToEndCommon.csproj b/end2end/EndToEndCommon/EndToEndCommon.csproj index 91a121266..f52ec29ca 100644 --- a/end2end/EndToEndCommon/EndToEndCommon.csproj +++ b/end2end/EndToEndCommon/EndToEndCommon.csproj @@ -7,6 +7,7 @@ + diff --git a/end2end/EndToEndScaffold/Config.cs b/end2end/EndToEndScaffold/Config.cs index f8112ff66..222dad73d 100644 --- a/end2end/EndToEndScaffold/Config.cs +++ b/end2end/EndToEndScaffold/Config.cs @@ -37,6 +37,11 @@ public enum KnownTestType MssqlBinaryCopyFrom, MssqlTransaction, MssqlTransactionRollback, + MssqlIdentityExecLastId, + MssqlSchemaScopedTable, + MssqlSpatialDataTypes, + MssqlNodaTimeOverride, + MssqlNodaTimeCopyFrom, // Sqlite SqliteDataTypes, @@ -109,6 +114,9 @@ public enum KnownTestType internal static class Config { + // tests that cannot run on .NET Framework: Microsoft.SqlServer.Types needs its native library there + public static readonly HashSet NotForLegacy = [KnownTestType.MssqlSpatialDataTypes]; + private static readonly SortedSet _mysqlTestTypes = [ KnownTestType.MySqlTransaction, KnownTestType.MySqlTransactionRollback, @@ -223,7 +231,12 @@ internal static class Config KnownTestType.MssqlBinaryDataTypes, KnownTestType.MssqlBinaryCopyFrom, KnownTestType.MssqlTransaction, - KnownTestType.MssqlTransactionRollback + KnownTestType.MssqlTransactionRollback, + KnownTestType.MssqlIdentityExecLastId, + KnownTestType.MssqlSchemaScopedTable, + KnownTestType.MssqlSpatialDataTypes, + KnownTestType.MssqlNodaTimeOverride, + KnownTestType.MssqlNodaTimeCopyFrom ]; private static readonly SortedSet _sqliteTestTypes = [ diff --git a/end2end/EndToEndScaffold/Program.cs b/end2end/EndToEndScaffold/Program.cs index 4575eee30..253542eb1 100644 --- a/end2end/EndToEndScaffold/Program.cs +++ b/end2end/EndToEndScaffold/Program.cs @@ -37,6 +37,7 @@ private static string GetFileContents(string testClassName, bool isLegacyDotnet) throw new ArgumentException($"No code to generate for input {testClassName}"); var testsImplementation = string.Join("\n", config.TestTypes + .Where(t => !isLegacyDotnet || !Config.NotForLegacy.Contains(t)) .Select(t => GetTestImplementation(testClassName, isLegacyDotnet, t)) .ToList()); @@ -44,6 +45,7 @@ private static string GetFileContents(string testClassName, bool isLegacyDotnet) var optionalUsingSystemNet = config.TestNamespace.Contains("Npgsql") ? "using System.Net;" : string.Empty; var optionalUsingSystemNetNetworkInformation = config.TestNamespace.Contains("Npgsql") ? "using System.Net.NetworkInformation;" : string.Empty; var optionalUsingSystemTextJson = config.TestNamespace.Contains("MySqlConnector") || config.TestNamespace.Contains("Npgsql") ? "using System.Text.Json;" : string.Empty; + var optionalUsingSqlServerTypes = config.TestNamespace.Contains("Mssql") && !isLegacyDotnet ? "using Microsoft.SqlServer.Types;" : string.Empty; var optionalUsingSystemXml = config.TestNamespace.Contains("Npgsql") ? "using System.Xml;" : string.Empty; var namespaceToTest = isLegacyDotnet ? config.LegacyTestNamespace : config.TestNamespace; @@ -55,6 +57,7 @@ private static string GetFileContents(string testClassName, bool isLegacyDotnet) {{optionalUsingSystemNetNetworkInformation}} {{optionalUsingSystemTextJson}} {{optionalUsingSystemXml}} + {{optionalUsingSqlServerTypes}} using NodaTime; using NUnit.Framework; using NUnit.Framework.Legacy; diff --git a/end2end/EndToEndScaffold/Templates/MssqlTests.cs b/end2end/EndToEndScaffold/Templates/MssqlTests.cs index abfbfbae3..a232959dd 100644 --- a/end2end/EndToEndScaffold/Templates/MssqlTests.cs +++ b/end2end/EndToEndScaffold/Templates/MssqlTests.cs @@ -142,21 +142,21 @@ async Task AssertRoundTrip(bool withValues) { await QuerySql.InsertMssqlStringTypesAsync(new QuerySql.InsertMssqlStringTypesArgs { - CChar = withValues ? (string?)"abcdefghij" : null, - CNchar = withValues ? (string?)"\u1857\u2649abcdefgh" : null, - CVarchar = withValues ? (string?)"Holiday in Cambodia" : null, - CNvarchar = withValues ? (string?)"Sheena \u2649 is a Punk Rocker" : null, - CVarcharMax = withValues ? (string?)"London's Calling" : null, - CNvarcharMax = withValues ? (string?)"Police & Thieves \u1857" : null + CChar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"abcdefghij" : null, + CNchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"London's Calling" : null, + CNvarcharMax = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Police & Thieves \u1857" : null }); var expected = new QuerySql.GetMssqlStringTypesRow { - CChar = withValues ? (string?)"abcdefghij" : null, - CNchar = withValues ? (string?)"\u1857\u2649abcdefgh" : null, - CVarchar = withValues ? (string?)"Holiday in Cambodia" : null, - CNvarchar = withValues ? (string?)"Sheena \u2649 is a Punk Rocker" : null, - CVarcharMax = withValues ? (string?)"London's Calling" : null, - CNvarcharMax = withValues ? (string?)"Police & Thieves \u1857" : null + CChar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"abcdefghij" : null, + CNchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"London's Calling" : null, + CNvarcharMax = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Police & Thieves \u1857" : null }; var actual = await QuerySql.GetMssqlStringTypesAsync(); AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); @@ -185,24 +185,24 @@ public async Task TestMssqlStringCopyFrom(int batchSize, bool withValues) var batchArgs = Enumerable.Range(0, batchSize) .Select(_ => new QuerySql.InsertMssqlStringTypesBatchArgs { - CChar = withValues ? (string?)"abcdefghij" : null, - CNchar = withValues ? (string?)"\u1857\u2649abcdefgh" : null, - CVarchar = withValues ? (string?)"Holiday in Cambodia" : null, - CNvarchar = withValues ? (string?)"Sheena \u2649 is a Punk Rocker" : null, - CVarcharMax = withValues ? (string?)"London's Calling" : null, - CNvarcharMax = withValues ? (string?)"Police & Thieves \u1857" : null + CChar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"abcdefghij" : null, + CNchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"London's Calling" : null, + CNvarcharMax = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Police & Thieves \u1857" : null }) .ToList(); await QuerySql.InsertMssqlStringTypesBatchAsync(batchArgs); var expected = new QuerySql.GetMssqlStringTypesCntRow { Cnt = batchSize, - CChar = withValues ? (string?)"abcdefghij" : null, - CNchar = withValues ? (string?)"\u1857\u2649abcdefgh" : null, - CVarchar = withValues ? (string?)"Holiday in Cambodia" : null, - CNvarchar = withValues ? (string?)"Sheena \u2649 is a Punk Rocker" : null, - CVarcharMax = withValues ? (string?)"London's Calling" : null, - CNvarcharMax = withValues ? (string?)"Police & Thieves \u1857" : null + CChar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"abcdefghij" : null, + CNchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"London's Calling" : null, + CNvarcharMax = withValues ? (string{{Consts.UnknownNullableIndicatorPlaceholder}})"Police & Thieves \u1857" : null }; var actual = await QuerySql.GetMssqlStringTypesCntAsync(); AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}}); @@ -326,16 +326,16 @@ async Task AssertRoundTrip(bool withValues) { await QuerySql.InsertMssqlBinaryTypesAsync(new QuerySql.InsertMssqlBinaryTypesArgs { - CBinary = withValues ? (byte[]?)new byte[] { 1, 2, 3 } : null, - CVarbinary = withValues ? (byte[]?)new byte[] { 4, 5, 6, 7 } : null, - CVarbinaryMax = withValues ? (byte[]?)new byte[] { 8, 9, 10, 11, 12 } : null, + CBinary = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 1, 2, 3 } : null, + CVarbinary = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 4, 5, 6, 7 } : null, + CVarbinaryMax = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid?)Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }); var expected = new QuerySql.GetMssqlBinaryTypesRow { - CBinary = withValues ? (byte[]?)new byte[] { 1, 2, 3 } : null, - CVarbinary = withValues ? (byte[]?)new byte[] { 4, 5, 6, 7 } : null, - CVarbinaryMax = withValues ? (byte[]?)new byte[] { 8, 9, 10, 11, 12 } : null, + CBinary = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 1, 2, 3 } : null, + CVarbinary = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 4, 5, 6, 7 } : null, + CVarbinaryMax = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid?)Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }; var actual = await QuerySql.GetMssqlBinaryTypesAsync(); @@ -363,9 +363,9 @@ public async Task TestMssqlBinaryCopyFrom(int batchSize, bool withValues) var batchArgs = Enumerable.Range(0, batchSize) .Select(_ => new QuerySql.InsertMssqlBinaryTypesBatchArgs { - CBinary = withValues ? (byte[]?)new byte[] { 1, 2, 3 } : null, - CVarbinary = withValues ? (byte[]?)new byte[] { 4, 5, 6, 7 } : null, - CVarbinaryMax = withValues ? (byte[]?)new byte[] { 8, 9, 10, 11, 12 } : null, + CBinary = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 1, 2, 3 } : null, + CVarbinary = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 4, 5, 6, 7 } : null, + CVarbinaryMax = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid?)Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }) .ToList(); @@ -373,9 +373,9 @@ public async Task TestMssqlBinaryCopyFrom(int batchSize, bool withValues) var expected = new QuerySql.GetMssqlBinaryTypesCntRow { Cnt = batchSize, - CBinary = withValues ? (byte[]?)new byte[] { 1, 2, 3 } : null, - CVarbinary = withValues ? (byte[]?)new byte[] { 4, 5, 6, 7 } : null, - CVarbinaryMax = withValues ? (byte[]?)new byte[] { 8, 9, 10, 11, 12 } : null, + CBinary = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 1, 2, 3 } : null, + CVarbinary = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 4, 5, 6, 7 } : null, + CVarbinaryMax = withValues ? (byte[]{{Consts.UnknownNullableIndicatorPlaceholder}})new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid?)Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }; var actual = await QuerySql.GetMssqlBinaryTypesCntAsync(); @@ -409,7 +409,7 @@ public async Task TestMssqlTransaction() var actual = await querySqlWithTx.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = {{Consts.BojackAuthor}} }); ClassicAssert.IsNotNull(actual); - await transaction.CommitAsync(); + transaction.Commit(); var expected = new QuerySql.GetAuthorRow { @@ -442,12 +442,127 @@ public async Task TestMssqlTransactionRollback() var querySqlWithTx = QuerySql.WithTransaction(transaction); await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = {{Consts.BojackId}}, Name = {{Consts.BojackAuthor}}, Bio = {{Consts.BojackTheme}} }); - await transaction.RollbackAsync(); + transaction.Rollback(); var actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = {{Consts.BojackAuthor}} }); ClassicAssert.IsNull(actual); } """ }, + [KnownTestType.MssqlIdentityExecLastId] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlIdentityExecLastId() + { + var firstId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "first" }); + var secondId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "second" }); + Assert.That(secondId, Is.EqualTo(firstId + 1)); + + var actual = await QuerySql.GetNoteAsync(new QuerySql.GetNoteArgs { Id = secondId }); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.Id, Is.EqualTo(secondId)); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.Body, Is.EqualTo("second")); + } + """ + }, + [KnownTestType.MssqlSchemaScopedTable] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlSchemaScopedTable() + { + await QuerySql.CreateExtendedBioAsync(new QuerySql.CreateExtendedBioArgs + { + AuthorName = {{Consts.BojackAuthor}}, + Name = {{Consts.BojackBookTitle}}, + BioType = "Memoir" + }); + var expected = new QuerySql.GetFirstExtendedBioByTypeRow + { + AuthorName = {{Consts.BojackAuthor}}, + Name = {{Consts.BojackBookTitle}}, + BioType = "Memoir" + }; + var actual = await QuerySql.GetFirstExtendedBioByTypeAsync(new QuerySql.GetFirstExtendedBioByTypeArgs { BioType = "Memoir" }); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.AuthorName, Is.EqualTo(expected.AuthorName)); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.Name, Is.EqualTo(expected.Name)); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.BioType, Is.EqualTo(expected.BioType)); + } + """ + }, + [KnownTestType.MssqlSpatialDataTypes] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlSpatialTypes() + { + await QuerySql.InsertMssqlSpatialTypesAsync(new QuerySql.InsertMssqlSpatialTypesArgs + { + CGeography = SqlGeography.Point(32.1, 34.8, 4326), + CGeometry = SqlGeometry.Point(1, 2, 0), + CHierarchyid = SqlHierarchyId.Parse("/1/2/") + }); + var actual = await QuerySql.GetMssqlSpatialTypesAsync(); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CGeography.ToString(), Is.EqualTo("POINT (34.8 32.1)")); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CGeometry.ToString(), Is.EqualTo("POINT (1 2)")); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CHierarchyid.ToString(), Is.EqualTo("/1/2/")); + + await QuerySql.TruncateMssqlSpatialTypesAsync(); + await QuerySql.InsertMssqlSpatialTypesAsync(new QuerySql.InsertMssqlSpatialTypesArgs()); + actual = await QuerySql.GetMssqlSpatialTypesAsync(); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CGeography, Is.Null); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CGeometry, Is.Null); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CHierarchyid, Is.Null); + } + """ + }, + [KnownTestType.MssqlNodaTimeOverride] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestMssqlNodaTimeOverride() + { + var instant = Instant.FromUtc(2020, 5, 6, 7, 8, 9).PlusTicks(1234567); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs + { + CDatetime2Instant = instant, + CDatetimeoffsetInstant = instant + }); + var actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CDatetime2Instant, Is.EqualTo(instant)); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CDatetimeoffsetInstant, Is.EqualTo(instant)); + + await QuerySql.TruncateMssqlNodaTypesAsync(); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs()); + actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CDatetime2Instant, Is.Null); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CDatetimeoffsetInstant, Is.Null); + } + """ + }, + [KnownTestType.MssqlNodaTimeCopyFrom] = new TestImpl + { + Impl = $$""" + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlNodaTimeCopyFrom(int batchSize, bool withValues) + { + var instant = Instant.FromUtc(2021, 1, 2, 3, 4, 5); + var batchArgs = Enumerable.Range(0, batchSize) + .Select(_ => new QuerySql.InsertMssqlNodaTypesBatchArgs + { + CDatetime2Instant = withValues ? (Instant?)instant : null, + CDatetimeoffsetInstant = withValues ? (Instant?)instant : null + }) + .ToList(); + await QuerySql.InsertMssqlNodaTypesBatchAsync(batchArgs); + var actual = await QuerySql.GetMssqlNodaTypesCntAsync(); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.Cnt, Is.EqualTo(batchSize)); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CDatetime2Instant, Is.EqualTo(withValues ? (Instant?)instant : null)); + Assert.That(actual{{Consts.UnknownRecordValuePlaceholder}}.CDatetimeoffsetInstant, Is.EqualTo(withValues ? (Instant?)instant : null)); + } + """ + }, }; } \ No newline at end of file diff --git a/end2end/EndToEndTests/EndToEndTests.csproj b/end2end/EndToEndTests/EndToEndTests.csproj index d3bd4c26b..c02db6d78 100644 --- a/end2end/EndToEndTests/EndToEndTests.csproj +++ b/end2end/EndToEndTests/EndToEndTests.csproj @@ -39,6 +39,7 @@ + diff --git a/end2end/EndToEndTests/GlobalSetup.cs b/end2end/EndToEndTests/GlobalSetup.cs index 378206874..c280a1650 100644 --- a/end2end/EndToEndTests/GlobalSetup.cs +++ b/end2end/EndToEndTests/GlobalSetup.cs @@ -1,7 +1,4 @@ -using Microsoft.Data.SqlClient; using NUnit.Framework; -using System; -using System.IO; namespace EndToEndTests; @@ -12,7 +9,7 @@ public class GlobalSetup public void SetUp() { EndToEndCommon.SetupTestsSqliteDb(); - SetupMssqlDb(); + EndToEndCommon.SetupTestsMssqlDb(); } [OneTimeTearDown] @@ -20,31 +17,4 @@ public void TearDown() { EndToEndCommon.RemoveExistingSqliteDb(); } - - // SQL Server has no init hook in its docker image, so the database and its schema are created here - private static void SetupMssqlDb() - { - var connectionString = Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv); - if (string.IsNullOrWhiteSpace(connectionString)) - throw new InvalidOperationException($"{EndToEndCommon.MssqlConnectionStringEnv} environment variable is not set"); - - var builder = new SqlConnectionStringBuilder(connectionString); - var database = builder.InitialCatalog; - builder.InitialCatalog = "master"; - using (var connection = new SqlConnection(builder.ConnectionString)) - { - connection.Open(); - Execute(connection, $"DROP DATABASE IF EXISTS [{database}]"); - Execute(connection, $"CREATE DATABASE [{database}]"); - connection.ChangeDatabase(database); - foreach (var schemaFile in new[] { "authors.mssql.schema.sql", "types.mssql.schema.sql" }) - Execute(connection, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, schemaFile))); - } - } - - private static void Execute(SqlConnection connection, string sql) - { - using (var command = new SqlCommand(sql, connection)) - command.ExecuteNonQuery(); - } } \ No newline at end of file diff --git a/end2end/EndToEndTests/MssqlAsyncEnumerableTester.cs b/end2end/EndToEndTests/MssqlAsyncEnumerableTester.cs new file mode 100644 index 000000000..5ab3f4433 --- /dev/null +++ b/end2end/EndToEndTests/MssqlAsyncEnumerableTester.cs @@ -0,0 +1,107 @@ +using Microsoft.Data.SqlClient; +using MssqlAsyncEnumerableExampleGen; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace EndToEndTests; + +[TestFixture] +public class MssqlAsyncEnumerableTester +{ + private QuerySql QuerySql { get; } = new( + Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)!); + + [TearDown] + public async Task EmptyTestsTable() + { + await QuerySql.DeleteAllAuthorsAsync(); + } + + private async Task SeedAuthors(QuerySql querySql) + { + await querySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs(1, "Bravo", "b")); + await querySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs(2, "Alpha", null)); + await querySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs(3, "Charlie", "c")); + } + + private static async Task> ToListAsync(IAsyncEnumerable source) + { + var result = new List(); + await foreach (var item in source) + result.Add(item); + return result; + } + + [Test] + public async Task TestListAuthorsStreamsRows() + { + await SeedAuthors(QuerySql); + + var actual = await ToListAsync(QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs(0, 10))); + + Assert.That(actual.ConvertAll(a => a.Name), Is.EqualTo(new[] { "Alpha", "Bravo", "Charlie" })); + Assert.That(actual[0].Bio, Is.Null); + Assert.That(actual[1].Bio, Is.EqualTo("b")); + } + + [Test] + public async Task TestListAuthorsEmpty() + { + var actual = await ToListAsync(QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs(0, 10))); + Assert.That(actual, Is.Empty); + } + + [Test] + public async Task TestListAuthorsCanStopEarly() + { + await SeedAuthors(QuerySql); + + await foreach (var author in QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs(0, 10))) + { + Assert.That(author.Name, Is.EqualTo("Alpha")); + break; + } + } + + [Test] + public async Task TestListAuthorsInTransaction() + { + using var connection = new SqlConnection( + Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)!); + await connection.OpenAsync(); + using var transaction = connection.BeginTransaction(); + var transactionalQuerySql = QuerySql.WithTransaction(transaction); + await SeedAuthors(transactionalQuerySql); + + var actual = await ToListAsync(transactionalQuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs(0, 10))); + + Assert.That(actual, Has.Count.EqualTo(3)); + transaction.Rollback(); + Assert.That(await ToListAsync(QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs(0, 10))), Is.Empty); + } + + [Test] + public void TestOneHonoursCancellation() + { + using var cts = new CancellationTokenSource(); + cts.Cancel(); + Assert.CatchAsync(async () => + await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs("Alpha"), cts.Token)); + } + + [Test] + public async Task TestListAuthorsHonoursCancellation() + { + await SeedAuthors(QuerySql); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + Assert.CatchAsync(async () => + { + await foreach (var _ in QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs(0, 10), cts.Token)) { } + }); + } +} \ No newline at end of file diff --git a/end2end/EndToEndTests/MssqlDapperTester.cs b/end2end/EndToEndTests/MssqlDapperTester.cs index ed35f82ae..7a3e4adf8 100644 --- a/end2end/EndToEndTests/MssqlDapperTester.cs +++ b/end2end/EndToEndTests/MssqlDapperTester.cs @@ -18,5 +18,9 @@ public async Task EmptyTestsTables() await QuerySql.TruncateMssqlStringTypesAsync(); await QuerySql.TruncateMssqlDatetimeTypesAsync(); await QuerySql.TruncateMssqlBinaryTypesAsync(); + await QuerySql.TruncateMssqlSpatialTypesAsync(); + await QuerySql.TruncateMssqlNodaTypesAsync(); + await QuerySql.TruncateNotesAsync(); + await QuerySql.TruncateExtendedBiosAsync(); } } \ No newline at end of file diff --git a/end2end/EndToEndTests/MssqlDapperTester.generated.cs b/end2end/EndToEndTests/MssqlDapperTester.generated.cs index 41b753304..f76ecf4a3 100644 --- a/end2end/EndToEndTests/MssqlDapperTester.generated.cs +++ b/end2end/EndToEndTests/MssqlDapperTester.generated.cs @@ -1,4 +1,5 @@ using MssqlDapperExampleGen; +using Microsoft.SqlServer.Types; using NodaTime; using NUnit.Framework; using NUnit.Framework.Legacy; @@ -697,7 +698,7 @@ public async Task TestMssqlTransaction() // the uncommitted row is locked by the transaction, so it is read from within it var actual = await querySqlWithTx.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); ClassicAssert.IsNotNull(actual); - await transaction.CommitAsync(); + transaction.Commit(); var expected = new QuerySql.GetAuthorRow { Id = 1111, @@ -722,9 +723,81 @@ public async Task TestMssqlTransactionRollback() var transaction = connection.BeginTransaction(); var querySqlWithTx = QuerySql.WithTransaction(transaction); await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); - await transaction.RollbackAsync(); + transaction.Rollback(); var actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); ClassicAssert.IsNull(actual); } + + [Test] + public async Task TestMssqlIdentityExecLastId() + { + var firstId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "first" }); + var secondId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "second" }); + Assert.That(secondId, Is.EqualTo(firstId + 1)); + var actual = await QuerySql.GetNoteAsync(new QuerySql.GetNoteArgs { Id = secondId }); + Assert.That(actual.Id, Is.EqualTo(secondId)); + Assert.That(actual.Body, Is.EqualTo("second")); + } + + [Test] + public async Task TestMssqlSchemaScopedTable() + { + await QuerySql.CreateExtendedBioAsync(new QuerySql.CreateExtendedBioArgs { AuthorName = "Bojack Horseman", Name = "One Trick Pony", BioType = "Memoir" }); + var expected = new QuerySql.GetFirstExtendedBioByTypeRow + { + AuthorName = "Bojack Horseman", + Name = "One Trick Pony", + BioType = "Memoir" + }; + var actual = await QuerySql.GetFirstExtendedBioByTypeAsync(new QuerySql.GetFirstExtendedBioByTypeArgs { BioType = "Memoir" }); + Assert.That(actual.AuthorName, Is.EqualTo(expected.AuthorName)); + Assert.That(actual.Name, Is.EqualTo(expected.Name)); + Assert.That(actual.BioType, Is.EqualTo(expected.BioType)); + } + + [Test] + public async Task TestMssqlSpatialTypes() + { + await QuerySql.InsertMssqlSpatialTypesAsync(new QuerySql.InsertMssqlSpatialTypesArgs { CGeography = SqlGeography.Point(32.1, 34.8, 4326), CGeometry = SqlGeometry.Point(1, 2, 0), CHierarchyid = SqlHierarchyId.Parse("/1/2/") }); + var actual = await QuerySql.GetMssqlSpatialTypesAsync(); + Assert.That(actual.CGeography.ToString(), Is.EqualTo("POINT (34.8 32.1)")); + Assert.That(actual.CGeometry.ToString(), Is.EqualTo("POINT (1 2)")); + Assert.That(actual.CHierarchyid.ToString(), Is.EqualTo("/1/2/")); + await QuerySql.TruncateMssqlSpatialTypesAsync(); + await QuerySql.InsertMssqlSpatialTypesAsync(new QuerySql.InsertMssqlSpatialTypesArgs()); + actual = await QuerySql.GetMssqlSpatialTypesAsync(); + Assert.That(actual.CGeography, Is.Null); + Assert.That(actual.CGeometry, Is.Null); + Assert.That(actual.CHierarchyid, Is.Null); + } + + [Test] + public async Task TestMssqlNodaTimeOverride() + { + var instant = Instant.FromUtc(2020, 5, 6, 7, 8, 9).PlusTicks(1234567); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs { CDatetime2Instant = instant, CDatetimeoffsetInstant = instant }); + var actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual.CDatetime2Instant, Is.EqualTo(instant)); + Assert.That(actual.CDatetimeoffsetInstant, Is.EqualTo(instant)); + await QuerySql.TruncateMssqlNodaTypesAsync(); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs()); + actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual.CDatetime2Instant, Is.Null); + Assert.That(actual.CDatetimeoffsetInstant, Is.Null); + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlNodaTimeCopyFrom(int batchSize, bool withValues) + { + var instant = Instant.FromUtc(2021, 1, 2, 3, 4, 5); + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlNodaTypesBatchArgs { CDatetime2Instant = withValues ? (Instant? )instant : null, CDatetimeoffsetInstant = withValues ? (Instant? )instant : null }).ToList(); + await QuerySql.InsertMssqlNodaTypesBatchAsync(batchArgs); + var actual = await QuerySql.GetMssqlNodaTypesCntAsync(); + Assert.That(actual.Cnt, Is.EqualTo(batchSize)); + Assert.That(actual.CDatetime2Instant, Is.EqualTo(withValues ? (Instant? )instant : null)); + Assert.That(actual.CDatetimeoffsetInstant, Is.EqualTo(withValues ? (Instant? )instant : null)); + } } } diff --git a/end2end/EndToEndTests/MssqlTester.cs b/end2end/EndToEndTests/MssqlTester.cs index 15bc290b6..10f6fea7d 100644 --- a/end2end/EndToEndTests/MssqlTester.cs +++ b/end2end/EndToEndTests/MssqlTester.cs @@ -18,5 +18,9 @@ public async Task EmptyTestsTables() await QuerySql.TruncateMssqlStringTypesAsync(); await QuerySql.TruncateMssqlDatetimeTypesAsync(); await QuerySql.TruncateMssqlBinaryTypesAsync(); + await QuerySql.TruncateMssqlSpatialTypesAsync(); + await QuerySql.TruncateMssqlNodaTypesAsync(); + await QuerySql.TruncateNotesAsync(); + await QuerySql.TruncateExtendedBiosAsync(); } } \ No newline at end of file diff --git a/end2end/EndToEndTests/MssqlTester.generated.cs b/end2end/EndToEndTests/MssqlTester.generated.cs index 6c3e9cf70..a75eb8144 100644 --- a/end2end/EndToEndTests/MssqlTester.generated.cs +++ b/end2end/EndToEndTests/MssqlTester.generated.cs @@ -1,4 +1,5 @@ using MssqlExampleGen; +using Microsoft.SqlServer.Types; using NodaTime; using NUnit.Framework; using NUnit.Framework.Legacy; @@ -697,7 +698,7 @@ public async Task TestMssqlTransaction() // the uncommitted row is locked by the transaction, so it is read from within it var actual = await querySqlWithTx.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); ClassicAssert.IsNotNull(actual); - await transaction.CommitAsync(); + transaction.Commit(); var expected = new QuerySql.GetAuthorRow { Id = 1111, @@ -722,9 +723,81 @@ public async Task TestMssqlTransactionRollback() var transaction = connection.BeginTransaction(); var querySqlWithTx = QuerySql.WithTransaction(transaction); await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); - await transaction.RollbackAsync(); + transaction.Rollback(); var actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); ClassicAssert.IsNull(actual); } + + [Test] + public async Task TestMssqlIdentityExecLastId() + { + var firstId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "first" }); + var secondId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "second" }); + Assert.That(secondId, Is.EqualTo(firstId + 1)); + var actual = await QuerySql.GetNoteAsync(new QuerySql.GetNoteArgs { Id = secondId }); + Assert.That(actual.Value.Id, Is.EqualTo(secondId)); + Assert.That(actual.Value.Body, Is.EqualTo("second")); + } + + [Test] + public async Task TestMssqlSchemaScopedTable() + { + await QuerySql.CreateExtendedBioAsync(new QuerySql.CreateExtendedBioArgs { AuthorName = "Bojack Horseman", Name = "One Trick Pony", BioType = "Memoir" }); + var expected = new QuerySql.GetFirstExtendedBioByTypeRow + { + AuthorName = "Bojack Horseman", + Name = "One Trick Pony", + BioType = "Memoir" + }; + var actual = await QuerySql.GetFirstExtendedBioByTypeAsync(new QuerySql.GetFirstExtendedBioByTypeArgs { BioType = "Memoir" }); + Assert.That(actual.Value.AuthorName, Is.EqualTo(expected.AuthorName)); + Assert.That(actual.Value.Name, Is.EqualTo(expected.Name)); + Assert.That(actual.Value.BioType, Is.EqualTo(expected.BioType)); + } + + [Test] + public async Task TestMssqlSpatialTypes() + { + await QuerySql.InsertMssqlSpatialTypesAsync(new QuerySql.InsertMssqlSpatialTypesArgs { CGeography = SqlGeography.Point(32.1, 34.8, 4326), CGeometry = SqlGeometry.Point(1, 2, 0), CHierarchyid = SqlHierarchyId.Parse("/1/2/") }); + var actual = await QuerySql.GetMssqlSpatialTypesAsync(); + Assert.That(actual.Value.CGeography.ToString(), Is.EqualTo("POINT (34.8 32.1)")); + Assert.That(actual.Value.CGeometry.ToString(), Is.EqualTo("POINT (1 2)")); + Assert.That(actual.Value.CHierarchyid.ToString(), Is.EqualTo("/1/2/")); + await QuerySql.TruncateMssqlSpatialTypesAsync(); + await QuerySql.InsertMssqlSpatialTypesAsync(new QuerySql.InsertMssqlSpatialTypesArgs()); + actual = await QuerySql.GetMssqlSpatialTypesAsync(); + Assert.That(actual.Value.CGeography, Is.Null); + Assert.That(actual.Value.CGeometry, Is.Null); + Assert.That(actual.Value.CHierarchyid, Is.Null); + } + + [Test] + public async Task TestMssqlNodaTimeOverride() + { + var instant = Instant.FromUtc(2020, 5, 6, 7, 8, 9).PlusTicks(1234567); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs { CDatetime2Instant = instant, CDatetimeoffsetInstant = instant }); + var actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual.Value.CDatetime2Instant, Is.EqualTo(instant)); + Assert.That(actual.Value.CDatetimeoffsetInstant, Is.EqualTo(instant)); + await QuerySql.TruncateMssqlNodaTypesAsync(); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs()); + actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual.Value.CDatetime2Instant, Is.Null); + Assert.That(actual.Value.CDatetimeoffsetInstant, Is.Null); + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlNodaTimeCopyFrom(int batchSize, bool withValues) + { + var instant = Instant.FromUtc(2021, 1, 2, 3, 4, 5); + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlNodaTypesBatchArgs { CDatetime2Instant = withValues ? (Instant? )instant : null, CDatetimeoffsetInstant = withValues ? (Instant? )instant : null }).ToList(); + await QuerySql.InsertMssqlNodaTypesBatchAsync(batchArgs); + var actual = await QuerySql.GetMssqlNodaTypesCntAsync(); + Assert.That(actual.Value.Cnt, Is.EqualTo(batchSize)); + Assert.That(actual.Value.CDatetime2Instant, Is.EqualTo(withValues ? (Instant? )instant : null)); + Assert.That(actual.Value.CDatetimeoffsetInstant, Is.EqualTo(withValues ? (Instant? )instant : null)); + } } } diff --git a/end2end/EndToEndTestsLegacy/EndToEndTestsLegacy.csproj b/end2end/EndToEndTestsLegacy/EndToEndTestsLegacy.csproj index 8bfdb44cc..15616d5d0 100644 --- a/end2end/EndToEndTestsLegacy/EndToEndTestsLegacy.csproj +++ b/end2end/EndToEndTestsLegacy/EndToEndTestsLegacy.csproj @@ -9,6 +9,14 @@ Always + + Always + authors.mssql.schema.sql + + + Always + types.mssql.schema.sql + Always authors.sqlite.schema.sql @@ -29,6 +37,8 @@ + + diff --git a/end2end/EndToEndTestsLegacy/GlobalSetup.cs b/end2end/EndToEndTestsLegacy/GlobalSetup.cs index 8dca6d914..6c98e14ef 100644 --- a/end2end/EndToEndTestsLegacy/GlobalSetup.cs +++ b/end2end/EndToEndTestsLegacy/GlobalSetup.cs @@ -9,6 +9,7 @@ public class GlobalSetup public void SetUp() { EndToEndCommon.SetupTestsSqliteDb(); + EndToEndCommon.SetupTestsMssqlDb(); } [OneTimeTearDown] diff --git a/end2end/EndToEndTestsLegacy/MssqlDapperTester.cs b/end2end/EndToEndTestsLegacy/MssqlDapperTester.cs new file mode 100644 index 000000000..46308dca5 --- /dev/null +++ b/end2end/EndToEndTestsLegacy/MssqlDapperTester.cs @@ -0,0 +1,27 @@ +using MssqlDapperLegacyExampleGen; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace EndToEndTests +{ + public partial class MssqlDapperTester + { + private QuerySql QuerySql { get; } = new QuerySql( + Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + + [TearDown] + public async Task EmptyTestsTables() + { + await QuerySql.DeleteAllAuthorsAsync(); + await QuerySql.TruncateNotesAsync(); + await QuerySql.TruncateExtendedBiosAsync(); + await QuerySql.TruncateMssqlNumericTypesAsync(); + await QuerySql.TruncateMssqlStringTypesAsync(); + await QuerySql.TruncateMssqlDatetimeTypesAsync(); + await QuerySql.TruncateMssqlBinaryTypesAsync(); + await QuerySql.TruncateMssqlSpatialTypesAsync(); + await QuerySql.TruncateMssqlNodaTypesAsync(); + } + } +} \ No newline at end of file diff --git a/end2end/EndToEndTestsLegacy/MssqlDapperTester.generated.cs b/end2end/EndToEndTestsLegacy/MssqlDapperTester.generated.cs new file mode 100644 index 000000000..895e23918 --- /dev/null +++ b/end2end/EndToEndTestsLegacy/MssqlDapperTester.generated.cs @@ -0,0 +1,786 @@ +using MssqlDapperLegacyExampleGen; +using NodaTime; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace EndToEndTests +{ + [TestFixture] + public partial class MssqlDapperTester + { + [Test] + public async Task TestOneAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new QuerySql.GetAuthorRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + var actual = await this.QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorRow x, QuerySql.GetAuthorRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestManyAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.ListAuthorsRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + new QuerySql.ListAuthorsRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + } + }; + var actual = await this.QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs { Limit = 2, Offset = 0 }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAuthorsRow x, QuerySql.ListAuthorsRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestExecAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + await this.QuerySql.DeleteAuthorAsync(new QuerySql.DeleteAuthorArgs { Name = "Bojack Horseman" }); + var actual = await this.QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNull(actual); + } + + [Test] + public async Task TestExecRowsAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var affectedRows = await this.QuerySql.UpdateAuthorsAsync(new QuerySql.UpdateAuthorsArgs { Bio = "Quote that everyone always attribute to Einstein" }); + ClassicAssert.AreEqual(2, affectedRows); + var expected = new List + { + new QuerySql.ListAuthorsRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Quote that everyone always attribute to Einstein" + }, + new QuerySql.ListAuthorsRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "Quote that everyone always attribute to Einstein" + } + }; + var actual = await this.QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs { Limit = 2, Offset = 0 }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAuthorsRow x, QuerySql.ListAuthorsRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestExecLastIdAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var expected = new QuerySql.GetAuthorByIdRow + { + Id = id1, + Name = "Albert Einstein", + Bio = "Quote that everyone always attribute to Einstein" + }; + var actual = await QuerySql.GetAuthorByIdAsync(new QuerySql.GetAuthorByIdArgs { Id = id1 }); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorByIdRow x, QuerySql.GetAuthorByIdRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestSelfJoinEmbedAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var id2 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Only 2 things are infinite, the universe and human stupidity" }); + var expected = new List() + { + new QuerySql.GetDuplicateAuthorsRow + { + Author = new Author + { + Id = id1, + Name = "Albert Einstein", + Bio = "Quote that everyone always attribute to Einstein" + }, + Author2 = new Author + { + Id = id2, + Name = "Albert Einstein", + Bio = "Only 2 things are infinite, the universe and human stupidity" + } + } + }; + var actual = await QuerySql.GetDuplicateAuthorsAsync(); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetDuplicateAuthorsRow x, QuerySql.GetDuplicateAuthorsRow y) + { + Assert.That(x.Author.Id, Is.EqualTo(y.Author.Id)); + Assert.That(x.Author.Name, Is.EqualTo(y.Author.Name)); + Assert.That(x.Author.Bio, Is.EqualTo(y.Author.Bio)); + Assert.That(x.Author2.Id, Is.EqualTo(y.Author2.Id)); + Assert.That(x.Author2.Name, Is.EqualTo(y.Author2.Name)); + Assert.That(x.Author2.Bio, Is.EqualTo(y.Author2.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestJoinEmbedAsync() + { + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var bojackBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { Name = "One Trick Pony", AuthorId = bojackId }); + var drSeussId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var drSeussBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { AuthorId = drSeussId, Name = "How the Grinch Stole Christmas!" }); + var expected = new List() + { + new QuerySql.ListAllAuthorsBooksRow + { + Author = new Author + { + Id = bojackId, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + Book = new Book + { + Id = bojackBookId, + AuthorId = bojackId, + Name = "One Trick Pony" + } + }, + new QuerySql.ListAllAuthorsBooksRow + { + Author = new Author + { + Id = drSeussId, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + }, + Book = new Book + { + Id = drSeussBookId, + AuthorId = drSeussId, + Name = "How the Grinch Stole Christmas!" + } + } + }; + var actual = await QuerySql.ListAllAuthorsBooksAsync(); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAllAuthorsBooksRow x, QuerySql.ListAllAuthorsBooksRow y) + { + Assert.That(x.Author.Id, Is.EqualTo(y.Author.Id)); + Assert.That(x.Author.Name, Is.EqualTo(y.Author.Name)); + Assert.That(x.Author.Bio, Is.EqualTo(y.Author.Bio)); + Assert.That(x.Book.Id, Is.EqualTo(y.Book.Id)); + Assert.That(x.Book.AuthorId, Is.EqualTo(y.Book.AuthorId)); + Assert.That(x.Book.Name, Is.EqualTo(y.Book.Name)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestPartialEmbedAsync() + { + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var bojackBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { Name = "One Trick Pony", AuthorId = bojackId }); + var drSeussId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var drSeussBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { AuthorId = drSeussId, Name = "How the Grinch Stole Christmas!" }); + var expected = new List + { + new QuerySql.GetAuthorsByBookNameRow + { + Id = bojackId, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show", + Book = new Book + { + Id = bojackBookId, + AuthorId = bojackId, + Name = "One Trick Pony" + } + } + }; + var actual = await QuerySql.GetAuthorsByBookNameAsync(new QuerySql.GetAuthorsByBookNameArgs { Name = "One Trick Pony" }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorsByBookNameRow x, QuerySql.GetAuthorsByBookNameRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + Assert.That(x.Book.Id, Is.EqualTo(y.Book.Id)); + Assert.That(x.Book.AuthorId, Is.EqualTo(y.Book.AuthorId)); + Assert.That(x.Book.Name, Is.EqualTo(y.Book.Name)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestSliceAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var actual = await QuerySql.GetAuthorsByIdsAsync(new QuerySql.GetAuthorsByIdsArgs { Ids = new[] { id1, bojackId } }); + ClassicAssert.AreEqual(2, actual.Count); + } + + [Test] + public async Task TestMultipleSlicesAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var actual = await QuerySql.GetAuthorsByIdsAndNamesAsync(new QuerySql.GetAuthorsByIdsAndNamesArgs { Ids = new[] { id1, bojackId }, Names = new[] { "Albert Einstein" } }); + ClassicAssert.AreEqual(1, actual.Count); + } + + [Test] + public async Task TestNargNullAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.GetAuthorByNamePatternRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + new QuerySql.GetAuthorByNamePatternRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + } + }; + var actual = await this.QuerySql.GetAuthorByNamePatternAsync(new QuerySql.GetAuthorByNamePatternArgs()); + AssertSequenceEquals(expected, actual); + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + + void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuthorByNamePatternRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestNargNotNullAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.GetAuthorByNamePatternRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + } + }; + var actual = await this.QuerySql.GetAuthorByNamePatternAsync(new QuerySql.GetAuthorByNamePatternArgs { NamePattern = "Bojack%" }); + AssertSequenceEquals(expected, actual); + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + + void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuthorByNamePatternRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestMssqlIntegerTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlNumericTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlNumericTypesAsync(new QuerySql.InsertMssqlNumericTypesArgs { CBit = withValues ? (bool? )true : null, CTinyint = withValues ? (byte? )(byte)13 : null, CSmallint = withValues ? (short? )(short)2084 : null, CInt = withValues ? (int? )-54355 : null, CBigint = withValues ? (long? )9787668656L : null, CReal = withValues ? (float? )3.4f : null, CFloat = withValues ? (double? )21.214312452534 : null, CDecimal = withValues ? (decimal? )11.0986m : null, CNumeric = withValues ? (decimal? )-31.5556m : null, CMoney = withValues ? (decimal? )922.5000m : null, CSmallmoney = withValues ? (decimal? )12.3400m : null }); + var expected = new QuerySql.GetMssqlNumericTypesRow + { + CBit = withValues ? (bool? )true : null, + CTinyint = withValues ? (byte? )(byte)13 : null, + CSmallint = withValues ? (short? )(short)2084 : null, + CInt = withValues ? (int? )-54355 : null, + CBigint = withValues ? (long? )9787668656L : null, + CReal = withValues ? (float? )3.4f : null, + CFloat = withValues ? (double? )21.214312452534 : null, + CDecimal = withValues ? (decimal? )11.0986m : null, + CNumeric = withValues ? (decimal? )-31.5556m : null, + CMoney = withValues ? (decimal? )922.5000m : null, + CSmallmoney = withValues ? (decimal? )12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesRow x, QuerySql.GetMssqlNumericTypesRow y) + { + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlIntegerCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlNumericTypesBatchArgs { CBit = withValues ? (bool? )true : null, CTinyint = withValues ? (byte? )(byte)13 : null, CSmallint = withValues ? (short? )(short)2084 : null, CInt = withValues ? (int? )-54355 : null, CBigint = withValues ? (long? )9787668656L : null, CReal = withValues ? (float? )3.4f : null, CFloat = withValues ? (double? )21.214312452534 : null, CDecimal = withValues ? (decimal? )11.0986m : null, CNumeric = withValues ? (decimal? )-31.5556m : null, CMoney = withValues ? (decimal? )922.5000m : null, CSmallmoney = withValues ? (decimal? )12.3400m : null }).ToList(); + await QuerySql.InsertMssqlNumericTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlNumericTypesCntRow + { + Cnt = batchSize, + CBit = withValues ? (bool? )true : null, + CTinyint = withValues ? (byte? )(byte)13 : null, + CSmallint = withValues ? (short? )(short)2084 : null, + CInt = withValues ? (int? )-54355 : null, + CBigint = withValues ? (long? )9787668656L : null, + CReal = withValues ? (float? )3.4f : null, + CFloat = withValues ? (double? )21.214312452534 : null, + CDecimal = withValues ? (decimal? )11.0986m : null, + CNumeric = withValues ? (decimal? )-31.5556m : null, + CMoney = withValues ? (decimal? )922.5000m : null, + CSmallmoney = withValues ? (decimal? )12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesCntRow x, QuerySql.GetMssqlNumericTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + + [Test] + public async Task TestMssqlStringTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlStringTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlStringTypesAsync(new QuerySql.InsertMssqlStringTypesArgs { CChar = withValues ? (string)"abcdefghij" : null, CNchar = withValues ? (string)"\u1857\u2649abcdefgh" : null, CVarchar = withValues ? (string)"Holiday in Cambodia" : null, CNvarchar = withValues ? (string)"Sheena \u2649 is a Punk Rocker" : null, CVarcharMax = withValues ? (string)"London's Calling" : null, CNvarcharMax = withValues ? (string)"Police & Thieves \u1857" : null }); + var expected = new QuerySql.GetMssqlStringTypesRow + { + CChar = withValues ? (string)"abcdefghij" : null, + CNchar = withValues ? (string)"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string)"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string)"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string)"London's Calling" : null, + CNvarcharMax = withValues ? (string)"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlStringTypesRow x, QuerySql.GetMssqlStringTypesRow y) + { + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlStringCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlStringTypesBatchArgs { CChar = withValues ? (string)"abcdefghij" : null, CNchar = withValues ? (string)"\u1857\u2649abcdefgh" : null, CVarchar = withValues ? (string)"Holiday in Cambodia" : null, CNvarchar = withValues ? (string)"Sheena \u2649 is a Punk Rocker" : null, CVarcharMax = withValues ? (string)"London's Calling" : null, CNvarcharMax = withValues ? (string)"Police & Thieves \u1857" : null }).ToList(); + await QuerySql.InsertMssqlStringTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlStringTypesCntRow + { + Cnt = batchSize, + CChar = withValues ? (string)"abcdefghij" : null, + CNchar = withValues ? (string)"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string)"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string)"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string)"London's Calling" : null, + CNvarcharMax = withValues ? (string)"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlStringTypesCntRow x, QuerySql.GetMssqlStringTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + + [Test] + public async Task TestMssqlDateTimeTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlDatetimeTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlDatetimeTypesAsync(new QuerySql.InsertMssqlDatetimeTypesArgs { CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null }); + var expected = new QuerySql.GetMssqlDatetimeTypesRow + { + CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesRow x, QuerySql.GetMssqlDatetimeTypesRow y) + { + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlDateTimeCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlDatetimeTypesBatchArgs { CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null }).ToList(); + await QuerySql.InsertMssqlDatetimeTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlDatetimeTypesCntRow + { + Cnt = batchSize, + CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesCntRow x, QuerySql.GetMssqlDatetimeTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + + [Test] + public async Task TestMssqlBinaryTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlBinaryTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlBinaryTypesAsync(new QuerySql.InsertMssqlBinaryTypesArgs { CBinary = withValues ? (byte[])new byte[] { 1, 2, 3 } : null, CVarbinary = withValues ? (byte[])new byte[] { 4, 5, 6, 7 } : null, CVarbinaryMax = withValues ? (byte[])new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }); + var expected = new QuerySql.GetMssqlBinaryTypesRow + { + CBinary = withValues ? (byte[])new byte[] + { + 1, + 2, + 3 + } + + : null, + CVarbinary = withValues ? (byte[])new byte[] + { + 4, + 5, + 6, + 7 + } + + : null, + CVarbinaryMax = withValues ? (byte[])new byte[] + { + 8, + 9, + 10, + 11, + 12 + } + + : null, + CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesRow x, QuerySql.GetMssqlBinaryTypesRow y) + { + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlBinaryCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlBinaryTypesBatchArgs { CBinary = withValues ? (byte[])new byte[] { 1, 2, 3 } : null, CVarbinary = withValues ? (byte[])new byte[] { 4, 5, 6, 7 } : null, CVarbinaryMax = withValues ? (byte[])new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }).ToList(); + await QuerySql.InsertMssqlBinaryTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlBinaryTypesCntRow + { + Cnt = batchSize, + CBinary = withValues ? (byte[])new byte[] + { + 1, + 2, + 3 + } + + : null, + CVarbinary = withValues ? (byte[])new byte[] + { + 4, + 5, + 6, + 7 + } + + : null, + CVarbinaryMax = withValues ? (byte[])new byte[] + { + 8, + 9, + 10, + 11, + 12 + } + + : null, + CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesCntRow x, QuerySql.GetMssqlBinaryTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + + [Test] + public async Task TestMssqlTransaction() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + // the uncommitted row is locked by the transaction, so it is read from within it + var actual = await querySqlWithTx.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNotNull(actual); + transaction.Commit(); + var expected = new QuerySql.GetAuthorRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorRow x, QuerySql.GetAuthorRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestMssqlTransactionRollback() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + transaction.Rollback(); + var actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNull(actual); + } + + [Test] + public async Task TestMssqlIdentityExecLastId() + { + var firstId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "first" }); + var secondId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "second" }); + Assert.That(secondId, Is.EqualTo(firstId + 1)); + var actual = await QuerySql.GetNoteAsync(new QuerySql.GetNoteArgs { Id = secondId }); + Assert.That(actual.Id, Is.EqualTo(secondId)); + Assert.That(actual.Body, Is.EqualTo("second")); + } + + [Test] + public async Task TestMssqlSchemaScopedTable() + { + await QuerySql.CreateExtendedBioAsync(new QuerySql.CreateExtendedBioArgs { AuthorName = "Bojack Horseman", Name = "One Trick Pony", BioType = "Memoir" }); + var expected = new QuerySql.GetFirstExtendedBioByTypeRow + { + AuthorName = "Bojack Horseman", + Name = "One Trick Pony", + BioType = "Memoir" + }; + var actual = await QuerySql.GetFirstExtendedBioByTypeAsync(new QuerySql.GetFirstExtendedBioByTypeArgs { BioType = "Memoir" }); + Assert.That(actual.AuthorName, Is.EqualTo(expected.AuthorName)); + Assert.That(actual.Name, Is.EqualTo(expected.Name)); + Assert.That(actual.BioType, Is.EqualTo(expected.BioType)); + } + + [Test] + public async Task TestMssqlNodaTimeOverride() + { + var instant = Instant.FromUtc(2020, 5, 6, 7, 8, 9).PlusTicks(1234567); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs { CDatetime2Instant = instant, CDatetimeoffsetInstant = instant }); + var actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual.CDatetime2Instant, Is.EqualTo(instant)); + Assert.That(actual.CDatetimeoffsetInstant, Is.EqualTo(instant)); + await QuerySql.TruncateMssqlNodaTypesAsync(); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs()); + actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual.CDatetime2Instant, Is.Null); + Assert.That(actual.CDatetimeoffsetInstant, Is.Null); + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlNodaTimeCopyFrom(int batchSize, bool withValues) + { + var instant = Instant.FromUtc(2021, 1, 2, 3, 4, 5); + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlNodaTypesBatchArgs { CDatetime2Instant = withValues ? (Instant? )instant : null, CDatetimeoffsetInstant = withValues ? (Instant? )instant : null }).ToList(); + await QuerySql.InsertMssqlNodaTypesBatchAsync(batchArgs); + var actual = await QuerySql.GetMssqlNodaTypesCntAsync(); + Assert.That(actual.Cnt, Is.EqualTo(batchSize)); + Assert.That(actual.CDatetime2Instant, Is.EqualTo(withValues ? (Instant? )instant : null)); + Assert.That(actual.CDatetimeoffsetInstant, Is.EqualTo(withValues ? (Instant? )instant : null)); + } + } +} diff --git a/end2end/EndToEndTestsLegacy/MssqlTester.cs b/end2end/EndToEndTestsLegacy/MssqlTester.cs new file mode 100644 index 000000000..e2d9c6639 --- /dev/null +++ b/end2end/EndToEndTestsLegacy/MssqlTester.cs @@ -0,0 +1,27 @@ +using MssqlLegacyExampleGen; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace EndToEndTests +{ + public partial class MssqlTester + { + private QuerySql QuerySql { get; } = new QuerySql( + Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + + [TearDown] + public async Task EmptyTestsTables() + { + await QuerySql.DeleteAllAuthorsAsync(); + await QuerySql.TruncateNotesAsync(); + await QuerySql.TruncateExtendedBiosAsync(); + await QuerySql.TruncateMssqlNumericTypesAsync(); + await QuerySql.TruncateMssqlStringTypesAsync(); + await QuerySql.TruncateMssqlDatetimeTypesAsync(); + await QuerySql.TruncateMssqlBinaryTypesAsync(); + await QuerySql.TruncateMssqlSpatialTypesAsync(); + await QuerySql.TruncateMssqlNodaTypesAsync(); + } + } +} \ No newline at end of file diff --git a/end2end/EndToEndTestsLegacy/MssqlTester.generated.cs b/end2end/EndToEndTestsLegacy/MssqlTester.generated.cs new file mode 100644 index 000000000..1c647078b --- /dev/null +++ b/end2end/EndToEndTestsLegacy/MssqlTester.generated.cs @@ -0,0 +1,786 @@ +using MssqlLegacyExampleGen; +using NodaTime; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace EndToEndTests +{ + [TestFixture] + public partial class MssqlTester + { + [Test] + public async Task TestOneAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new QuerySql.GetAuthorRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + var actual = await this.QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorRow x, QuerySql.GetAuthorRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestManyAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.ListAuthorsRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + new QuerySql.ListAuthorsRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + } + }; + var actual = await this.QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs { Limit = 2, Offset = 0 }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAuthorsRow x, QuerySql.ListAuthorsRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestExecAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + await this.QuerySql.DeleteAuthorAsync(new QuerySql.DeleteAuthorArgs { Name = "Bojack Horseman" }); + var actual = await this.QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNull(actual); + } + + [Test] + public async Task TestExecRowsAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var affectedRows = await this.QuerySql.UpdateAuthorsAsync(new QuerySql.UpdateAuthorsArgs { Bio = "Quote that everyone always attribute to Einstein" }); + ClassicAssert.AreEqual(2, affectedRows); + var expected = new List + { + new QuerySql.ListAuthorsRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Quote that everyone always attribute to Einstein" + }, + new QuerySql.ListAuthorsRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "Quote that everyone always attribute to Einstein" + } + }; + var actual = await this.QuerySql.ListAuthorsAsync(new QuerySql.ListAuthorsArgs { Limit = 2, Offset = 0 }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAuthorsRow x, QuerySql.ListAuthorsRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestExecLastIdAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var expected = new QuerySql.GetAuthorByIdRow + { + Id = id1, + Name = "Albert Einstein", + Bio = "Quote that everyone always attribute to Einstein" + }; + var actual = await QuerySql.GetAuthorByIdAsync(new QuerySql.GetAuthorByIdArgs { Id = id1 }); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorByIdRow x, QuerySql.GetAuthorByIdRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestSelfJoinEmbedAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var id2 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Only 2 things are infinite, the universe and human stupidity" }); + var expected = new List() + { + new QuerySql.GetDuplicateAuthorsRow + { + Author = new Author + { + Id = id1, + Name = "Albert Einstein", + Bio = "Quote that everyone always attribute to Einstein" + }, + Author2 = new Author + { + Id = id2, + Name = "Albert Einstein", + Bio = "Only 2 things are infinite, the universe and human stupidity" + } + } + }; + var actual = await QuerySql.GetDuplicateAuthorsAsync(); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetDuplicateAuthorsRow x, QuerySql.GetDuplicateAuthorsRow y) + { + Assert.That(x.Author.Id, Is.EqualTo(y.Author.Id)); + Assert.That(x.Author.Name, Is.EqualTo(y.Author.Name)); + Assert.That(x.Author.Bio, Is.EqualTo(y.Author.Bio)); + Assert.That(x.Author2.Id, Is.EqualTo(y.Author2.Id)); + Assert.That(x.Author2.Name, Is.EqualTo(y.Author2.Name)); + Assert.That(x.Author2.Bio, Is.EqualTo(y.Author2.Bio)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestJoinEmbedAsync() + { + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var bojackBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { Name = "One Trick Pony", AuthorId = bojackId }); + var drSeussId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var drSeussBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { AuthorId = drSeussId, Name = "How the Grinch Stole Christmas!" }); + var expected = new List() + { + new QuerySql.ListAllAuthorsBooksRow + { + Author = new Author + { + Id = bojackId, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + Book = new Book + { + Id = bojackBookId, + AuthorId = bojackId, + Name = "One Trick Pony" + } + }, + new QuerySql.ListAllAuthorsBooksRow + { + Author = new Author + { + Id = drSeussId, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + }, + Book = new Book + { + Id = drSeussBookId, + AuthorId = drSeussId, + Name = "How the Grinch Stole Christmas!" + } + } + }; + var actual = await QuerySql.ListAllAuthorsBooksAsync(); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.ListAllAuthorsBooksRow x, QuerySql.ListAllAuthorsBooksRow y) + { + Assert.That(x.Author.Id, Is.EqualTo(y.Author.Id)); + Assert.That(x.Author.Name, Is.EqualTo(y.Author.Name)); + Assert.That(x.Author.Bio, Is.EqualTo(y.Author.Bio)); + Assert.That(x.Book.Id, Is.EqualTo(y.Book.Id)); + Assert.That(x.Book.AuthorId, Is.EqualTo(y.Book.AuthorId)); + Assert.That(x.Book.Name, Is.EqualTo(y.Book.Name)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestPartialEmbedAsync() + { + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var bojackBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { Name = "One Trick Pony", AuthorId = bojackId }); + var drSeussId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var drSeussBookId = await QuerySql.CreateBookAsync(new QuerySql.CreateBookArgs { AuthorId = drSeussId, Name = "How the Grinch Stole Christmas!" }); + var expected = new List + { + new QuerySql.GetAuthorsByBookNameRow + { + Id = bojackId, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show", + Book = new Book + { + Id = bojackBookId, + AuthorId = bojackId, + Name = "One Trick Pony" + } + } + }; + var actual = await QuerySql.GetAuthorsByBookNameAsync(new QuerySql.GetAuthorsByBookNameArgs { Name = "One Trick Pony" }); + AssertSequenceEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorsByBookNameRow x, QuerySql.GetAuthorsByBookNameRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + Assert.That(x.Book.Id, Is.EqualTo(y.Book.Id)); + Assert.That(x.Book.AuthorId, Is.EqualTo(y.Book.AuthorId)); + Assert.That(x.Book.Name, Is.EqualTo(y.Book.Name)); + } + + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + } + + [Test] + public async Task TestSliceAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var actual = await QuerySql.GetAuthorsByIdsAsync(new QuerySql.GetAuthorsByIdsArgs { Ids = new[] { id1, bojackId } }); + ClassicAssert.AreEqual(2, actual.Count); + } + + [Test] + public async Task TestMultipleSlicesAsync() + { + var id1 = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Albert Einstein", Bio = "Quote that everyone always attribute to Einstein" }); + var bojackId = await this.QuerySql.CreateAuthorReturnIdAsync(new QuerySql.CreateAuthorReturnIdArgs { Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var actual = await QuerySql.GetAuthorsByIdsAndNamesAsync(new QuerySql.GetAuthorsByIdsAndNamesArgs { Ids = new[] { id1, bojackId }, Names = new[] { "Albert Einstein" } }); + ClassicAssert.AreEqual(1, actual.Count); + } + + [Test] + public async Task TestNargNullAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.GetAuthorByNamePatternRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }, + new QuerySql.GetAuthorByNamePatternRow + { + Id = 2222, + Name = "Dr. Seuss", + Bio = "You'll miss the best things if you keep your eyes shut" + } + }; + var actual = await this.QuerySql.GetAuthorByNamePatternAsync(new QuerySql.GetAuthorByNamePatternArgs()); + AssertSequenceEquals(expected, actual); + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + + void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuthorByNamePatternRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestNargNotNullAsync() + { + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + await this.QuerySql.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 2222, Name = "Dr. Seuss", Bio = "You'll miss the best things if you keep your eyes shut" }); + var expected = new List + { + new QuerySql.GetAuthorByNamePatternRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + } + }; + var actual = await this.QuerySql.GetAuthorByNamePatternAsync(new QuerySql.GetAuthorByNamePatternArgs { NamePattern = "Bojack%" }); + AssertSequenceEquals(expected, actual); + void AssertSequenceEquals(List x, List y) + { + Assert.That(x.Count, Is.EqualTo(y.Count)); + for (int i = 0; i < x.Count; i++) + AssertSingularEquals(x[i], y[i]); + } + + void AssertSingularEquals(QuerySql.GetAuthorByNamePatternRow x, QuerySql.GetAuthorByNamePatternRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestMssqlIntegerTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlNumericTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlNumericTypesAsync(new QuerySql.InsertMssqlNumericTypesArgs { CBit = withValues ? (bool? )true : null, CTinyint = withValues ? (byte? )(byte)13 : null, CSmallint = withValues ? (short? )(short)2084 : null, CInt = withValues ? (int? )-54355 : null, CBigint = withValues ? (long? )9787668656L : null, CReal = withValues ? (float? )3.4f : null, CFloat = withValues ? (double? )21.214312452534 : null, CDecimal = withValues ? (decimal? )11.0986m : null, CNumeric = withValues ? (decimal? )-31.5556m : null, CMoney = withValues ? (decimal? )922.5000m : null, CSmallmoney = withValues ? (decimal? )12.3400m : null }); + var expected = new QuerySql.GetMssqlNumericTypesRow + { + CBit = withValues ? (bool? )true : null, + CTinyint = withValues ? (byte? )(byte)13 : null, + CSmallint = withValues ? (short? )(short)2084 : null, + CInt = withValues ? (int? )-54355 : null, + CBigint = withValues ? (long? )9787668656L : null, + CReal = withValues ? (float? )3.4f : null, + CFloat = withValues ? (double? )21.214312452534 : null, + CDecimal = withValues ? (decimal? )11.0986m : null, + CNumeric = withValues ? (decimal? )-31.5556m : null, + CMoney = withValues ? (decimal? )922.5000m : null, + CSmallmoney = withValues ? (decimal? )12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesRow x, QuerySql.GetMssqlNumericTypesRow y) + { + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlIntegerCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlNumericTypesBatchArgs { CBit = withValues ? (bool? )true : null, CTinyint = withValues ? (byte? )(byte)13 : null, CSmallint = withValues ? (short? )(short)2084 : null, CInt = withValues ? (int? )-54355 : null, CBigint = withValues ? (long? )9787668656L : null, CReal = withValues ? (float? )3.4f : null, CFloat = withValues ? (double? )21.214312452534 : null, CDecimal = withValues ? (decimal? )11.0986m : null, CNumeric = withValues ? (decimal? )-31.5556m : null, CMoney = withValues ? (decimal? )922.5000m : null, CSmallmoney = withValues ? (decimal? )12.3400m : null }).ToList(); + await QuerySql.InsertMssqlNumericTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlNumericTypesCntRow + { + Cnt = batchSize, + CBit = withValues ? (bool? )true : null, + CTinyint = withValues ? (byte? )(byte)13 : null, + CSmallint = withValues ? (short? )(short)2084 : null, + CInt = withValues ? (int? )-54355 : null, + CBigint = withValues ? (long? )9787668656L : null, + CReal = withValues ? (float? )3.4f : null, + CFloat = withValues ? (double? )21.214312452534 : null, + CDecimal = withValues ? (decimal? )11.0986m : null, + CNumeric = withValues ? (decimal? )-31.5556m : null, + CMoney = withValues ? (decimal? )922.5000m : null, + CSmallmoney = withValues ? (decimal? )12.3400m : null + }; + var actual = await QuerySql.GetMssqlNumericTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlNumericTypesCntRow x, QuerySql.GetMssqlNumericTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBit, Is.EqualTo(y.CBit)); + Assert.That(x.CTinyint, Is.EqualTo(y.CTinyint)); + Assert.That(x.CSmallint, Is.EqualTo(y.CSmallint)); + Assert.That(x.CInt, Is.EqualTo(y.CInt)); + Assert.That(x.CBigint, Is.EqualTo(y.CBigint)); + Assert.That(x.CReal, Is.EqualTo(y.CReal)); + Assert.That(x.CFloat, Is.EqualTo(y.CFloat)); + Assert.That(x.CDecimal, Is.EqualTo(y.CDecimal)); + Assert.That(x.CNumeric, Is.EqualTo(y.CNumeric)); + Assert.That(x.CMoney, Is.EqualTo(y.CMoney)); + Assert.That(x.CSmallmoney, Is.EqualTo(y.CSmallmoney)); + } + } + + [Test] + public async Task TestMssqlStringTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlStringTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlStringTypesAsync(new QuerySql.InsertMssqlStringTypesArgs { CChar = withValues ? (string)"abcdefghij" : null, CNchar = withValues ? (string)"\u1857\u2649abcdefgh" : null, CVarchar = withValues ? (string)"Holiday in Cambodia" : null, CNvarchar = withValues ? (string)"Sheena \u2649 is a Punk Rocker" : null, CVarcharMax = withValues ? (string)"London's Calling" : null, CNvarcharMax = withValues ? (string)"Police & Thieves \u1857" : null }); + var expected = new QuerySql.GetMssqlStringTypesRow + { + CChar = withValues ? (string)"abcdefghij" : null, + CNchar = withValues ? (string)"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string)"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string)"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string)"London's Calling" : null, + CNvarcharMax = withValues ? (string)"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlStringTypesRow x, QuerySql.GetMssqlStringTypesRow y) + { + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlStringCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlStringTypesBatchArgs { CChar = withValues ? (string)"abcdefghij" : null, CNchar = withValues ? (string)"\u1857\u2649abcdefgh" : null, CVarchar = withValues ? (string)"Holiday in Cambodia" : null, CNvarchar = withValues ? (string)"Sheena \u2649 is a Punk Rocker" : null, CVarcharMax = withValues ? (string)"London's Calling" : null, CNvarcharMax = withValues ? (string)"Police & Thieves \u1857" : null }).ToList(); + await QuerySql.InsertMssqlStringTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlStringTypesCntRow + { + Cnt = batchSize, + CChar = withValues ? (string)"abcdefghij" : null, + CNchar = withValues ? (string)"\u1857\u2649abcdefgh" : null, + CVarchar = withValues ? (string)"Holiday in Cambodia" : null, + CNvarchar = withValues ? (string)"Sheena \u2649 is a Punk Rocker" : null, + CVarcharMax = withValues ? (string)"London's Calling" : null, + CNvarcharMax = withValues ? (string)"Police & Thieves \u1857" : null + }; + var actual = await QuerySql.GetMssqlStringTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlStringTypesCntRow x, QuerySql.GetMssqlStringTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CChar, Is.EqualTo(y.CChar)); + Assert.That(x.CNchar, Is.EqualTo(y.CNchar)); + Assert.That(x.CVarchar, Is.EqualTo(y.CVarchar)); + Assert.That(x.CNvarchar, Is.EqualTo(y.CNvarchar)); + Assert.That(x.CVarcharMax, Is.EqualTo(y.CVarcharMax)); + Assert.That(x.CNvarcharMax, Is.EqualTo(y.CNvarcharMax)); + } + } + + [Test] + public async Task TestMssqlDateTimeTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlDatetimeTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlDatetimeTypesAsync(new QuerySql.InsertMssqlDatetimeTypesArgs { CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null }); + var expected = new QuerySql.GetMssqlDatetimeTypesRow + { + CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesRow x, QuerySql.GetMssqlDatetimeTypesRow y) + { + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlDateTimeCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlDatetimeTypesBatchArgs { CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null }).ToList(); + await QuerySql.InsertMssqlDatetimeTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlDatetimeTypesCntRow + { + Cnt = batchSize, + CDate = withValues ? (DateTime? )new DateTime(2020, 1, 2) : null, + CDatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9) : null, + CDatetime2 = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 9, 123) : null, + CSmalldatetime = withValues ? (DateTime? )new DateTime(2020, 5, 6, 7, 8, 0) : null, + CDatetimeoffset = withValues ? (DateTimeOffset? )new DateTimeOffset(2020, 5, 6, 7, 8, 9, TimeSpan.FromHours(2)) : null, + CTime = withValues ? (TimeSpan? )new TimeSpan(7, 8, 9) : null + }; + var actual = await QuerySql.GetMssqlDatetimeTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlDatetimeTypesCntRow x, QuerySql.GetMssqlDatetimeTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CDate, Is.EqualTo(y.CDate)); + Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime)); + Assert.That(x.CDatetime2, Is.EqualTo(y.CDatetime2)); + Assert.That(x.CSmalldatetime, Is.EqualTo(y.CSmalldatetime)); + Assert.That(x.CDatetimeoffset, Is.EqualTo(y.CDatetimeoffset)); + Assert.That(x.CTime, Is.EqualTo(y.CTime)); + } + } + + [Test] + public async Task TestMssqlBinaryTypes() + { + await AssertRoundTrip(true); + await QuerySql.TruncateMssqlBinaryTypesAsync(); + await AssertRoundTrip(false); + async Task AssertRoundTrip(bool withValues) + { + await QuerySql.InsertMssqlBinaryTypesAsync(new QuerySql.InsertMssqlBinaryTypesArgs { CBinary = withValues ? (byte[])new byte[] { 1, 2, 3 } : null, CVarbinary = withValues ? (byte[])new byte[] { 4, 5, 6, 7 } : null, CVarbinaryMax = withValues ? (byte[])new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }); + var expected = new QuerySql.GetMssqlBinaryTypesRow + { + CBinary = withValues ? (byte[])new byte[] + { + 1, + 2, + 3 + } + + : null, + CVarbinary = withValues ? (byte[])new byte[] + { + 4, + 5, + 6, + 7 + } + + : null, + CVarbinaryMax = withValues ? (byte[])new byte[] + { + 8, + 9, + 10, + 11, + 12 + } + + : null, + CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesAsync(); + AssertSingularEquals(expected, actual); + } + + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesRow x, QuerySql.GetMssqlBinaryTypesRow y) + { + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlBinaryCopyFrom(int batchSize, bool withValues) + { + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlBinaryTypesBatchArgs { CBinary = withValues ? (byte[])new byte[] { 1, 2, 3 } : null, CVarbinary = withValues ? (byte[])new byte[] { 4, 5, 6, 7 } : null, CVarbinaryMax = withValues ? (byte[])new byte[] { 8, 9, 10, 11, 12 } : null, CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null }).ToList(); + await QuerySql.InsertMssqlBinaryTypesBatchAsync(batchArgs); + var expected = new QuerySql.GetMssqlBinaryTypesCntRow + { + Cnt = batchSize, + CBinary = withValues ? (byte[])new byte[] + { + 1, + 2, + 3 + } + + : null, + CVarbinary = withValues ? (byte[])new byte[] + { + 4, + 5, + 6, + 7 + } + + : null, + CVarbinaryMax = withValues ? (byte[])new byte[] + { + 8, + 9, + 10, + 11, + 12 + } + + : null, + CUniqueidentifier = withValues ? (Guid? )Guid.Parse("a3b1c5d7-1234-4abc-9def-0123456789ab") : null + }; + var actual = await QuerySql.GetMssqlBinaryTypesCntAsync(); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetMssqlBinaryTypesCntRow x, QuerySql.GetMssqlBinaryTypesCntRow y) + { + Assert.That(x.Cnt, Is.EqualTo(y.Cnt)); + Assert.That(x.CBinary, Is.EqualTo(y.CBinary)); + Assert.That(x.CVarbinary, Is.EqualTo(y.CVarbinary)); + Assert.That(x.CVarbinaryMax, Is.EqualTo(y.CVarbinaryMax)); + Assert.That(x.CUniqueidentifier, Is.EqualTo(y.CUniqueidentifier)); + } + } + + [Test] + public async Task TestMssqlTransaction() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + // the uncommitted row is locked by the transaction, so it is read from within it + var actual = await querySqlWithTx.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNotNull(actual); + transaction.Commit(); + var expected = new QuerySql.GetAuthorRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + AssertSingularEquals(expected, actual); + void AssertSingularEquals(QuerySql.GetAuthorRow x, QuerySql.GetAuthorRow y) + { + Assert.That(x.Id, Is.EqualTo(y.Id)); + Assert.That(x.Name, Is.EqualTo(y.Name)); + Assert.That(x.Bio, Is.EqualTo(y.Bio)); + } + } + + [Test] + public async Task TestMssqlTransactionRollback() + { + var connection = new Microsoft.Data.SqlClient.SqlConnection(Environment.GetEnvironmentVariable(EndToEndCommon.MssqlConnectionStringEnv)); + await connection.OpenAsync(); + var transaction = connection.BeginTransaction(); + var querySqlWithTx = QuerySql.WithTransaction(transaction); + await querySqlWithTx.CreateAuthorAsync(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + transaction.Rollback(); + var actual = await QuerySql.GetAuthorAsync(new QuerySql.GetAuthorArgs { Name = "Bojack Horseman" }); + ClassicAssert.IsNull(actual); + } + + [Test] + public async Task TestMssqlIdentityExecLastId() + { + var firstId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "first" }); + var secondId = await QuerySql.CreateNoteReturnIdAsync(new QuerySql.CreateNoteReturnIdArgs { Body = "second" }); + Assert.That(secondId, Is.EqualTo(firstId + 1)); + var actual = await QuerySql.GetNoteAsync(new QuerySql.GetNoteArgs { Id = secondId }); + Assert.That(actual.Id, Is.EqualTo(secondId)); + Assert.That(actual.Body, Is.EqualTo("second")); + } + + [Test] + public async Task TestMssqlSchemaScopedTable() + { + await QuerySql.CreateExtendedBioAsync(new QuerySql.CreateExtendedBioArgs { AuthorName = "Bojack Horseman", Name = "One Trick Pony", BioType = "Memoir" }); + var expected = new QuerySql.GetFirstExtendedBioByTypeRow + { + AuthorName = "Bojack Horseman", + Name = "One Trick Pony", + BioType = "Memoir" + }; + var actual = await QuerySql.GetFirstExtendedBioByTypeAsync(new QuerySql.GetFirstExtendedBioByTypeArgs { BioType = "Memoir" }); + Assert.That(actual.AuthorName, Is.EqualTo(expected.AuthorName)); + Assert.That(actual.Name, Is.EqualTo(expected.Name)); + Assert.That(actual.BioType, Is.EqualTo(expected.BioType)); + } + + [Test] + public async Task TestMssqlNodaTimeOverride() + { + var instant = Instant.FromUtc(2020, 5, 6, 7, 8, 9).PlusTicks(1234567); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs { CDatetime2Instant = instant, CDatetimeoffsetInstant = instant }); + var actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual.CDatetime2Instant, Is.EqualTo(instant)); + Assert.That(actual.CDatetimeoffsetInstant, Is.EqualTo(instant)); + await QuerySql.TruncateMssqlNodaTypesAsync(); + await QuerySql.InsertMssqlNodaTypesAsync(new QuerySql.InsertMssqlNodaTypesArgs()); + actual = await QuerySql.GetMssqlNodaTypesAsync(); + Assert.That(actual.CDatetime2Instant, Is.Null); + Assert.That(actual.CDatetimeoffsetInstant, Is.Null); + } + + [Test] + [TestCase(100, true)] + [TestCase(10, false)] + public async Task TestMssqlNodaTimeCopyFrom(int batchSize, bool withValues) + { + var instant = Instant.FromUtc(2021, 1, 2, 3, 4, 5); + var batchArgs = Enumerable.Range(0, batchSize).Select(_ => new QuerySql.InsertMssqlNodaTypesBatchArgs { CDatetime2Instant = withValues ? (Instant? )instant : null, CDatetimeoffsetInstant = withValues ? (Instant? )instant : null }).ToList(); + await QuerySql.InsertMssqlNodaTypesBatchAsync(batchArgs); + var actual = await QuerySql.GetMssqlNodaTypesCntAsync(); + Assert.That(actual.Cnt, Is.EqualTo(batchSize)); + Assert.That(actual.CDatetime2Instant, Is.EqualTo(withValues ? (Instant? )instant : null)); + Assert.That(actual.CDatetimeoffsetInstant, Is.EqualTo(withValues ? (Instant? )instant : null)); + } + } +} diff --git a/end2end/MssqlSyntaxMatrix/REPORT.md b/end2end/MssqlSyntaxMatrix/REPORT.md index 516d614d9..b322b5783 100644 --- a/end2end/MssqlSyntaxMatrix/REPORT.md +++ b/end2end/MssqlSyntaxMatrix/REPORT.md @@ -391,10 +391,10 @@ STRING_AGG with WITHIN GROUP - **upstream** 💥 ``` panic: runtime error: invalid memory address or nil pointer dereference - github.com/sqlc-dev/sqlc/internal/sql/validate.validateCopyfrom({0x23937e0?, 0x18a3e1537420?}) - github.com/sqlc-dev/sqlc/internal/sql/validate.Cmd({0x23937e0?, 0x18a3e1537420?}, {0x18a3e124c429?, 0x488e7d?}, {0x18a3e124c433?, 0x15000000488e7d?}) - github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQueryCore(0x18a3e152e108, 0x18a3e1130b80, {0x18a3e124c420?, 0x18a3e112a5a0?}, 0x18a3e112a728) - github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQuery(0x18a3e152e108, {0x2392a60, 0x18a3e1130b80}, 0x18a3e1102a80, {{0xe0?}}) + github.com/sqlc-dev/sqlc/internal/sql/validate.validateCopyfrom({0x?, 0x?}) + github.com/sqlc-dev/sqlc/internal/sql/validate.Cmd({0x?, 0x?}, {0x?, 0x?}, {0x?, 0x?}) + github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQueryCore(0x?, 0x?, {0x?, 0x?}, 0x?) + github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQuery(0x?, {0x?, 0x?}, 0x?, {{0x?}}) github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).analyzeStatements.func1(...) ``` diff --git a/end2end/MssqlSyntaxMatrix/SqlcRunner.cs b/end2end/MssqlSyntaxMatrix/SqlcRunner.cs index b5ad13d46..2426fe9a9 100644 --- a/end2end/MssqlSyntaxMatrix/SqlcRunner.cs +++ b/end2end/MssqlSyntaxMatrix/SqlcRunner.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Text.Json; +using System.Text.RegularExpressions; namespace MssqlSyntaxMatrix; @@ -19,7 +20,7 @@ public class SqlcResult public string? PluginOutputDir { get; set; } } -public static class SqlcRunner +public static partial class SqlcRunner { public static async Task Run(SqlcBuild build, TestCase testCase, string dir, MatrixOptions options) { @@ -130,9 +131,13 @@ private static string Summarize(string output) .Where(l => l.Length > 0 && !l.StartsWith('\t') && !l.StartsWith(" at ") && !l.StartsWith("goroutine ") && !l.StartsWith("# package") && !l.StartsWith("[signal")) .Take(6); - return string.Join("\n", lines); + // goroutine traces carry addresses that change on every run + return HexRegex().Replace(string.Join("\n", lines), "0x?"); } + [GeneratedRegex(@"0x[0-9a-fA-F]+\??")] + private static partial Regex HexRegex(); + public static async Task<(int ExitCode, string Output)> Exec(string file, string arguments, string workingDir) { var psi = new ProcessStartInfo(file, arguments) diff --git a/end2end/MssqlSyntaxMatrix/results.json b/end2end/MssqlSyntaxMatrix/results.json index 66c5e225c..ea7aa6807 100644 --- a/end2end/MssqlSyntaxMatrix/results.json +++ b/end2end/MssqlSyntaxMatrix/results.json @@ -335,7 +335,7 @@ "upstream": { "status": "Panic", "level": "Panic", - "error": "panic: runtime error: invalid memory address or nil pointer dereference\ngithub.com/sqlc-dev/sqlc/internal/sql/validate.validateCopyfrom({0x23937e0?, 0x18a3e1537420?})\ngithub.com/sqlc-dev/sqlc/internal/sql/validate.Cmd({0x23937e0?, 0x18a3e1537420?}, {0x18a3e124c429?, 0x488e7d?}, {0x18a3e124c433?, 0x15000000488e7d?})\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQueryCore(0x18a3e152e108, 0x18a3e1130b80, {0x18a3e124c420?, 0x18a3e112a5a0?}, 0x18a3e112a728)\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQuery(0x18a3e152e108, {0x2392a60, 0x18a3e1130b80}, 0x18a3e1102a80, {{0xe0?}})\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).analyzeStatements.func1(...)", + "error": "panic: runtime error: invalid memory address or nil pointer dereference\ngithub.com/sqlc-dev/sqlc/internal/sql/validate.validateCopyfrom({0x?, 0x?})\ngithub.com/sqlc-dev/sqlc/internal/sql/validate.Cmd({0x?, 0x?}, {0x?, 0x?}, {0x?, 0x?})\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQueryCore(0x?, 0x?, {0x?, 0x?}, 0x?)\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQuery(0x?, {0x?, 0x?}, 0x?, {{0x?}})\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).analyzeStatements.func1(...)", "queries": [ { "name": "CopyBooks", diff --git a/end2end/scripts/generate_tests.sh b/end2end/scripts/generate_tests.sh index bb92911a9..5716250bb 100755 --- a/end2end/scripts/generate_tests.sh +++ b/end2end/scripts/generate_tests.sh @@ -9,10 +9,6 @@ TARGETS=( "NpgsqlDapperTester" "SqliteTester" "SqliteDapperTester" -) - -# SQL Server has no legacy (netstandard) examples yet -NON_LEGACY_TARGETS=( "MssqlTester" "MssqlDapperTester" ) @@ -36,8 +32,4 @@ generate_legacy() { for target in "${TARGETS[@]}"; do generate "$target" generate_legacy "$target" -done - -for target in "${NON_LEGACY_TARGETS[@]}"; do - generate "$target" done \ No newline at end of file diff --git a/examples/MssqlAsyncEnumerableExample/Models.cs b/examples/MssqlAsyncEnumerableExample/Models.cs new file mode 100644 index 000000000..8c8880148 --- /dev/null +++ b/examples/MssqlAsyncEnumerableExample/Models.cs @@ -0,0 +1,9 @@ +// auto-generated by sqlc - do not edit +using System; +using System.Linq; + +namespace MssqlAsyncEnumerableExampleGen; +public readonly record struct Author(long Id, string Name, string? Bio); +public readonly record struct Book(long Id, string Name, long AuthorId, string? Description); +public readonly record struct Note(int Id, string Body); +public readonly record struct ExtendedBio(string AuthorName, string Name, string? BioType); \ No newline at end of file diff --git a/examples/MssqlAsyncEnumerableExample/MssqlAsyncEnumerableExample.csproj b/examples/MssqlAsyncEnumerableExample/MssqlAsyncEnumerableExample.csproj new file mode 100644 index 000000000..bf2f89711 --- /dev/null +++ b/examples/MssqlAsyncEnumerableExample/MssqlAsyncEnumerableExample.csproj @@ -0,0 +1,20 @@ + + + + + + net8.0 + MssqlAsyncEnumerableExampleGen + Library + enable + False + + + + + + + + \ No newline at end of file diff --git a/examples/MssqlAsyncEnumerableExample/QuerySql.cs b/examples/MssqlAsyncEnumerableExample/QuerySql.cs new file mode 100644 index 000000000..aa3ab0fe2 --- /dev/null +++ b/examples/MssqlAsyncEnumerableExample/QuerySql.cs @@ -0,0 +1,976 @@ +// auto-generated by sqlc - do not edit +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable UseAwaitUsing +// ReSharper disable ConvertToUsingDeclaration +// ReSharper disable NotAccessedPositionalProperty.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace MssqlAsyncEnumerableExampleGen; +public class QuerySql +{ + public QuerySql() + { + } + + public QuerySql(string connectionString) : this() + { + this.ConnectionString = connectionString; + } + + private QuerySql(SqlTransaction transaction) : this() + { + this.Transaction = transaction; + } + + public static QuerySql WithTransaction(SqlTransaction transaction) + { + return new QuerySql(transaction); + } + + private SqlTransaction? Transaction { get; } + private string? ConnectionString { get; } + + private const string GetAuthorSql = @"SELECT TOP 1 id, name, bio FROM authors + WHERE name = @name;"; + public readonly record struct GetAuthorRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorArgs(string Name); + public async Task GetAuthorAsync(GetAuthorArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(GetAuthorSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + if (await reader.ReadAsync(cancellationToken)) + { + return new GetAuthorRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + if (await reader.ReadAsync(cancellationToken)) + { + return new GetAuthorRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string ListAuthorsSql = @"SELECT id, name, bio + FROM authors + ORDER BY name + OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;"; + public readonly record struct ListAuthorsRow(long Id, string Name, string? Bio); + public readonly record struct ListAuthorsArgs(int Offset, int Limit); + public async IAsyncEnumerable ListAuthorsAsync(ListAuthorsArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(ListAuthorsSql, connection)) + { + command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset; + command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new ListAuthorsRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + yield break; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAuthorsSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset; + command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new ListAuthorsRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + private const string CreateAuthorSql = "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);"; + public readonly record struct CreateAuthorArgs(long Id, string Name, string? Bio); + public async Task CreateAuthorAsync(CreateAuthorArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(CreateAuthorSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(cancellationToken); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(cancellationToken); + } + } + + private const string CreateAuthorReturnIdSql = "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);"; + public readonly record struct CreateAuthorReturnIdRow(long Id); + public readonly record struct CreateAuthorReturnIdArgs(string Name, string? Bio); + public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(CreateAuthorReturnIdSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + var result = await command.ExecuteScalarAsync(cancellationToken); + return Convert.ToInt64(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateAuthorReturnIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + var result = await command.ExecuteScalarAsync(cancellationToken); + return Convert.ToInt64(result); + } + } + + private const string GetAuthorByIdSql = @"SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment + WHERE id = @id;"; + public readonly record struct GetAuthorByIdRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorByIdArgs(long Id); + public async Task GetAuthorByIdAsync(GetAuthorByIdArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(GetAuthorByIdSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + if (await reader.ReadAsync(cancellationToken)) + { + return new GetAuthorByIdRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + if (await reader.ReadAsync(cancellationToken)) + { + return new GetAuthorByIdRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string GetAuthorByNamePatternSql = @"SELECT id, name, bio FROM authors + WHERE name LIKE COALESCE(@name_pattern, '%');"; + public readonly record struct GetAuthorByNamePatternRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorByNamePatternArgs(object? NamePattern); + public async IAsyncEnumerable GetAuthorByNamePatternAsync(GetAuthorByNamePatternArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(GetAuthorByNamePatternSql, connection)) + { + command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetAuthorByNamePatternRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + yield break; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByNamePatternSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetAuthorByNamePatternRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + private const string DeleteAuthorSql = @"DELETE FROM authors + WHERE name = @name;"; + public readonly record struct DeleteAuthorArgs(string Name); + public async Task DeleteAuthorAsync(DeleteAuthorArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(DeleteAuthorSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + await command.ExecuteNonQueryAsync(cancellationToken); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = DeleteAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + await command.ExecuteNonQueryAsync(cancellationToken); + } + } + + private const string DeleteAllAuthorsSql = "DELETE FROM authors;"; + public async Task DeleteAllAuthorsAsync(CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(DeleteAllAuthorsSql, connection)) + { + await command.ExecuteNonQueryAsync(cancellationToken); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = DeleteAllAuthorsSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(cancellationToken); + } + } + + private const string UpdateAuthorsSql = @"UPDATE authors + SET bio = @bio + WHERE bio IS NOT NULL;"; + public readonly record struct UpdateAuthorsArgs(string? Bio); + public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(UpdateAuthorsSql, connection)) + { + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + return await command.ExecuteNonQueryAsync(cancellationToken); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = UpdateAuthorsSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + return await command.ExecuteNonQueryAsync(cancellationToken); + } + } + + private const string GetAuthorsByIdsSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);"; + public readonly record struct GetAuthorsByIdsRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorsByIdsArgs(long[] Ids); + public async IAsyncEnumerable GetAuthorsByIdsAsync(GetAuthorsByIdsArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var transformedSql = GetAuthorsByIdsSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(transformedSql, connection)) + { + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetAuthorsByIdsRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + yield break; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = transformedSql; + command.Transaction = this.Transaction; + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetAuthorsByIdsRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + private const string GetAuthorsByIdsAndNamesSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);"; + public readonly record struct GetAuthorsByIdsAndNamesRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorsByIdsAndNamesArgs(long[] Ids, string[] Names); + public async IAsyncEnumerable GetAuthorsByIdsAndNamesAsync(GetAuthorsByIdsAndNamesArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var transformedSql = GetAuthorsByIdsAndNamesSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Names.Length, "names"); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(transformedSql, connection)) + { + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]); + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetAuthorsByIdsAndNamesRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + yield break; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = transformedSql; + command.Transaction = this.Transaction; + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]); + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetAuthorsByIdsAndNamesRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + private const string CreateBookSql = "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);"; + public readonly record struct CreateBookRow(long Id); + public readonly record struct CreateBookArgs(string Name, long AuthorId); + public async Task CreateBookAsync(CreateBookArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(CreateBookSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId; + var result = await command.ExecuteScalarAsync(cancellationToken); + return Convert.ToInt64(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateBookSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId; + var result = await command.ExecuteScalarAsync(cancellationToken); + return Convert.ToInt64(result); + } + } + + private const string ListAllAuthorsBooksSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + ORDER BY authors.name;"; + public readonly record struct ListAllAuthorsBooksRow(Author? Author, Book? Book); + public async IAsyncEnumerable ListAllAuthorsBooksAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(ListAllAuthorsBooksSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new ListAllAuthorsBooksRow + { + Author = new Author + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }, + Book = new Book + { + Id = reader.GetInt64(3), + Name = reader.GetString(4), + AuthorId = reader.GetInt64(5), + Description = reader.IsDBNull(6) ? null : reader.GetString(6) + } + }; + } + } + } + + yield break; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAllAuthorsBooksSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new ListAllAuthorsBooksRow + { + Author = new Author + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }, + Book = new Book + { + Id = reader.GetInt64(3), + Name = reader.GetString(4), + AuthorId = reader.GetInt64(5), + Description = reader.IsDBNull(6) ? null : reader.GetString(6) + } + }; + } + } + } + + private const string GetDuplicateAuthorsSql = @"SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio + FROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name + WHERE authors1.id < authors2.id;"; + public readonly record struct GetDuplicateAuthorsRow(Author? Author, Author? Author2); + public async IAsyncEnumerable GetDuplicateAuthorsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(GetDuplicateAuthorsSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetDuplicateAuthorsRow + { + Author = new Author + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }, + Author2 = new Author + { + Id = reader.GetInt64(3), + Name = reader.GetString(4), + Bio = reader.IsDBNull(5) ? null : reader.GetString(5) + } + }; + } + } + } + + yield break; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetDuplicateAuthorsSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetDuplicateAuthorsRow + { + Author = new Author + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }, + Author2 = new Author + { + Id = reader.GetInt64(3), + Name = reader.GetString(4), + Bio = reader.IsDBNull(5) ? null : reader.GetString(5) + } + }; + } + } + } + + private const string GetAuthorsByBookNameSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + WHERE books.name = @name;"; + public readonly record struct GetAuthorsByBookNameRow(long Id, string Name, string? Bio, Book? Book); + public readonly record struct GetAuthorsByBookNameArgs(string Name); + public async IAsyncEnumerable GetAuthorsByBookNameAsync(GetAuthorsByBookNameArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetAuthorsByBookNameRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2), + Book = new Book + { + Id = reader.GetInt64(3), + Name = reader.GetString(4), + AuthorId = reader.GetInt64(5), + Description = reader.IsDBNull(6) ? null : reader.GetString(6) + } + }; + } + } + } + + yield break; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorsByBookNameSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + while (await reader.ReadAsync(cancellationToken)) + yield return new GetAuthorsByBookNameRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2), + Book = new Book + { + Id = reader.GetInt64(3), + Name = reader.GetString(4), + AuthorId = reader.GetInt64(5), + Description = reader.IsDBNull(6) ? null : reader.GetString(6) + } + }; + } + } + } + + private const string CreateNoteReturnIdSql = "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);"; + public readonly record struct CreateNoteReturnIdRow(int Id); + public readonly record struct CreateNoteReturnIdArgs(string Body); + public async Task CreateNoteReturnIdAsync(CreateNoteReturnIdArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(CreateNoteReturnIdSql, connection)) + { + command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body; + var result = await command.ExecuteScalarAsync(cancellationToken); + return Convert.ToInt32(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateNoteReturnIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body; + var result = await command.ExecuteScalarAsync(cancellationToken); + return Convert.ToInt32(result); + } + } + + private const string GetNoteSql = "SELECT id, body FROM notes WHERE id = @id;"; + public readonly record struct GetNoteRow(int Id, string Body); + public readonly record struct GetNoteArgs(int Id); + public async Task GetNoteAsync(GetNoteArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(GetNoteSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + if (await reader.ReadAsync(cancellationToken)) + { + return new GetNoteRow + { + Id = reader.GetInt32(0), + Body = reader.GetString(1) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetNoteSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + if (await reader.ReadAsync(cancellationToken)) + { + return new GetNoteRow + { + Id = reader.GetInt32(0), + Body = reader.GetString(1) + }; + } + } + } + + return null; + } + + private const string TruncateNotesSql = "TRUNCATE TABLE notes;"; + public async Task TruncateNotesAsync(CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(TruncateNotesSql, connection)) + { + await command.ExecuteNonQueryAsync(cancellationToken); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateNotesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(cancellationToken); + } + } + + private const string CreateExtendedBioSql = "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);"; + public readonly record struct CreateExtendedBioArgs(string AuthorName, string Name, string? BioType); + public async Task CreateExtendedBioAsync(CreateExtendedBioArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(CreateExtendedBioSql, connection)) + { + command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(cancellationToken); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateExtendedBioSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(cancellationToken); + } + } + + private const string GetFirstExtendedBioByTypeSql = "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;"; + public readonly record struct GetFirstExtendedBioByTypeRow(string AuthorName, string Name, string? BioType); + public readonly record struct GetFirstExtendedBioByTypeArgs(string? BioType); + public async Task GetFirstExtendedBioByTypeAsync(GetFirstExtendedBioByTypeArgs args, CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(GetFirstExtendedBioByTypeSql, connection)) + { + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + if (await reader.ReadAsync(cancellationToken)) + { + return new GetFirstExtendedBioByTypeRow + { + AuthorName = reader.GetString(0), + Name = reader.GetString(1), + BioType = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetFirstExtendedBioByTypeSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + using (var reader = await command.ExecuteReaderAsync(cancellationToken)) + { + if (await reader.ReadAsync(cancellationToken)) + { + return new GetFirstExtendedBioByTypeRow + { + AuthorName = reader.GetString(0), + Name = reader.GetString(1), + BioType = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string TruncateExtendedBiosSql = "TRUNCATE TABLE extended.bios;"; + public async Task TruncateExtendedBiosAsync(CancellationToken cancellationToken = default) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(cancellationToken); + using (var command = new SqlCommand(TruncateExtendedBiosSql, connection)) + { + await command.ExecuteNonQueryAsync(cancellationToken); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateExtendedBiosSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(cancellationToken); + } + } +} \ No newline at end of file diff --git a/examples/MssqlAsyncEnumerableExample/Utils.cs b/examples/MssqlAsyncEnumerableExample/Utils.cs new file mode 100644 index 000000000..f3ee62580 --- /dev/null +++ b/examples/MssqlAsyncEnumerableExample/Utils.cs @@ -0,0 +1,13 @@ +// auto-generated by sqlc - do not edit +using System; +using System.Linq; + +namespace MssqlAsyncEnumerableExampleGen; +public static class Utils +{ + public static string TransformQueryForSliceArgs(string originalSql, int sliceSize, string paramName) + { + var paramArgs = Enumerable.Range(0, sliceSize).Select(i => $"@{paramName}Arg{i}").ToList(); + return originalSql.Replace($"/*SLICE:{paramName}*/@{paramName}", string.Join(",", paramArgs)); + } +} \ No newline at end of file diff --git a/examples/MssqlAsyncEnumerableExample/request.json b/examples/MssqlAsyncEnumerableExample/request.json new file mode 100644 index 000000000..878219571 --- /dev/null +++ b/examples/MssqlAsyncEnumerableExample/request.json @@ -0,0 +1,1225 @@ +{ + "settings": { + "version": "2", + "engine": "mssql", + "schema": [ + "examples/config/mssql/authors/schema.sql" + ], + "queries": [ + "examples/config/mssql/authors/query.sql" + ], + "codegen": { + "out": "examples/MssqlAsyncEnumerableExample", + "plugin": "csharp", + "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxBc3luY0VudW1lcmFibGVFeGFtcGxlR2VuIiwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OC4wIiwidXNlQXN5bmNFbnVtZXJhYmxlIjp0cnVlLCJ1c2VEYXBwZXIiOmZhbHNlLCJ3aXRoQ2FuY2VsbGF0aW9uVG9rZW4iOnRydWV9", + "process": { + "cmd": "./dist/LocalRunner" + } + } + }, + "catalog": { + "defaultSchema": "public", + "schemas": [ + { + "name": "public" + }, + { + "name": "public", + "tables": [ + { + "rel": { + "schema": "public", + "name": "authors" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "books" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "author_id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "description", + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "notes" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + } + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] + }, + { + "name": "sys" + }, + { + "name": "information_schema" + }, + { + "name": "extended", + "tables": [ + { + "rel": { + "schema": "extended", + "name": "bios" + }, + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] + } + ] + }, + "queries": [ + { + "text": "SELECT TOP 1 id, name, bio FROM authors\nWHERE name = @name;", + "name": "GetAuthor", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio\nFROM authors\nORDER BY name\nOFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;", + "name": "ListAuthors", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "offset", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + }, + { + "number": 2, + "column": { + "name": "limit", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);", + "name": "CreateAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);", + "name": "CreateAuthorReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment\nWHERE id = @id;", + "name": "GetAuthorById", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(@name_pattern, '%');", + "name": "GetAuthorByNamePattern", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name_pattern", + "length": -1, + "isNamedParam": true, + "type": {} + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors\nWHERE name = @name;", + "name": "DeleteAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors;", + "name": "DeleteAllAuthors", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "UPDATE authors\nSET bio = @bio\nWHERE bio IS NOT NULL;", + "name": "UpdateAuthors", + "cmd": ":execrows", + "parameters": [ + { + "number": 1, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);", + "name": "GetAuthorsByIds", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);", + "name": "GetAuthorsByIdsAndNames", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "names", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "isSqlcSlice": true, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);", + "name": "CreateBook", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "author_id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "author_id" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "books" + } + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nORDER BY authors.name;", + "name": "ListAllAuthorsBooks", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio\nFROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name\nWHERE authors1.id \u003c authors2.id;", + "name": "GetDuplicateAuthors", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nWHERE books.name = @name;", + "name": "GetAuthorsByBookName", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);", + "name": "CreateNoteReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "body", + "notNull": true, + "length": 200, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "notes" + } + }, + { + "text": "SELECT id, body FROM notes WHERE id = @id;", + "name": "GetNote", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE notes;", + "name": "TruncateNotes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);", + "name": "CreateExtendedBio", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "author_name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "schema": "extended", + "name": "bios" + } + }, + { + "text": "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;", + "name": "GetFirstExtendedBioByType", + "cmd": ":one", + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE extended.bios;", + "name": "TruncateExtendedBios", + "cmd": ":exec", + "filename": "query.sql" + } + ], + "sqlc_version": "v1.31.1", + "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OC4wIiwibmFtZXNwYWNlTmFtZSI6Ik1zc3FsQXN5bmNFbnVtZXJhYmxlRXhhbXBsZUdlbiIsInVzZURhcHBlciI6ZmFsc2UsIm92ZXJyaWRlRGFwcGVyVmVyc2lvbiI6IiIsIm92ZXJyaWRlcyI6bnVsbCwiZGVidWdSZXF1ZXN0IjpmYWxzZSwidXNlQ2VudHJhbFBhY2thZ2VNYW5hZ2VtZW50IjpmYWxzZSwid2l0aEFzeW5jU3VmZml4Ijp0cnVlLCJ3aXRoQ2FuY2VsbGF0aW9uVG9rZW4iOnRydWUsInVzZUFzeW5jRW51bWVyYWJsZSI6dHJ1ZSwidXNlUHJvcGVyU2luZ3VsYXJpemF0aW9uIjpmYWxzZX0=" +} \ No newline at end of file diff --git a/examples/MssqlAsyncEnumerableExample/request.message b/examples/MssqlAsyncEnumerableExample/request.message new file mode 100644 index 0000000000000000000000000000000000000000..12f440d6bfc2cd342a5df6aedc7c0add6c79a674 GIT binary patch literal 8315 zcmeHM&2JmW70;5QMUCCsS*xu(K!C-fsbxYDO|?Z`GLFTMm8^&+1(J%IUX&$wC~Zyd zGP_G#QV<|O4!QN9o_g%5=N9P4F}L{COOLr0X!{qWKo9Agncd~=E=9_<-2y^&5Xs#) zGjD$HbAE&VmO|3HoEf_AhXcGqj`iVaK-_%Kw)*B_o;}X%WADgz+`QX6B13%*B-Puq zB|jV!=Y$G&KTDzSg|d(6DrNV?>S@+^NF2R8AR1p%zS47#bZ3M=mp)Pr(j6bR2+ZPo zYSDAXWK}&R77Xx6)g3wZ&x5CyJ|yl)?~w+4QH$z~6}E__uBx8y91?HS0r97{^B9I$ z#Jjh47ha8B64NbM1>V&3(TF%|v9AwY(B`S>9aVL!M+O7kGi|G5KZfa3wA-@aN1zT&?~==Jc&qK~=!6O=NIThiUHgFmU2 zljm9BYcTHcwIMgCX$ZL~n)p@XLmLO5$Z(VZT^ekNT<;SZd?^|d5xDBU&fIqOEI zxH*~PhJ-Wg>7jm%f0;tp!(C;%w*AjL_cM~qiY){VBu#{ zOHhLgN7RVo@k~-Pa@sv{)n!>Wt-kFHnHwMQ5LdsH|4gA$ zTdQl;j?!uFDh0(fRu#I9RRyM1He1a{iXVSy|Do2>*h}TUvO$GP^3oRZ$}A`1g`#Z{ z^?uTHvx4*#LRtt^wGtvRH&9O*X320>-+j(-J*CloCQb9sTq7&VH9@C)^U}w8T4APb zaVabZ_z!9Hfe(EIbQA%-*{W$RrSd+*9W^&M+geB2u={wMTf`eX*0zDKGQ0=6>zT$2 zM#IZa5P)^+zlZfN(x`L6?617_mI9^OQyluw?kfY+B1+F54vFRYr2)(Ug~2pR@;Ab= zP&om>FJ)Y~Odq-2M&2n99SG5(EWDZ8U=Ml$RRc^m|8*A&I_$uF)(I03kG}P!V3kk(VO+zmm}H8Z<*4?OeRW#^+j%3`jhK zzrt>zh?b|QR+4Y{dJG1WDo?A4xUb(y=!bi|wKD8W=jT^My9nCg!G7Swk6-#=yR9^u z9i_2XuR|E`jf}8Nxp;}?O~-!fLYk6ZZJNTLrcq^XC@8!qg~YSnP*(D}c74056-~p< zv>c6lRlyalS`qzk+ zXoylfu}`qncVd<*TLw*6|F@XqPAGus7R$AuYuG{=1|@X>X>NB06PdSZnqS7T9@$RewOh~jRD%i1U** zw6_7u?wW|Xy9OB>4(gOiVnX~I7vbMSxC1ePxQQGzm3NzvLfIgt*$CAB?%tG;fdm=$w=bMlQ zYvU1cqeA8NPwvlw;>ATpB!9Dj{uDz}geQv5|H%7z$>;7(A*DSGcfe(_f>8Hs!0W&R zFBdU&!6!j!j%14G9d zTQqUe9n<6=KZ^VsNKesi0%o1&5?5K_dGUaTvp_>Lc3a@nB2p~&IkCp&O7iR6w=|!% zub8&4N$vvxp_!oT0fd=^{i(?BpGh(H9*b{tJJ2tbL&DgYJGI|^#OLJA$375m+akd~ zce?O0;7m%A{ozZ&mX*ZL@gLJ@-*<3+ClJA#+Xl?V(B_^WwW6P(0||b_WYQ)1yATbV zrsH~oaVjSrxV`Y><59voFJc?@!Ne4E=9z8?bxVYQ&J-cOQ^3#dgX_IwTjDMBMl3EP zmMO{0L3|4EPYF|X@kwFr2Zgml?ysqjRQm~W9Md2*$AoY15yypFKe#Q1d*bOg(K9ZV zsrNkBC3W(?IaG&TLr2sJUb$+~8V?4mvDbPN7dA~SNS p&2GTk<9^>f4sO3^+;z{nG~ab>xHNB@*5P=dJLW07WAvB%_}}GK@}K|! literal 0 HcmV?d00001 diff --git a/examples/MssqlDapperExample/Models.cs b/examples/MssqlDapperExample/Models.cs index e2d8568a5..5a332b9c9 100644 --- a/examples/MssqlDapperExample/Models.cs +++ b/examples/MssqlDapperExample/Models.cs @@ -1,4 +1,7 @@ // auto-generated by sqlc - do not edit +using Microsoft.SqlServer.Types; +using NodaTime; +using NodaTime.Extensions; using System; using System.Linq; @@ -16,6 +19,11 @@ public class Book public required long AuthorId { get; init; } public string? Description { get; init; } }; +public class Note +{ + public required int Id { get; init; } + public required string Body { get; init; } +}; public class MssqlNumericType { public bool? CBit { get; init; } @@ -54,4 +62,21 @@ public class MssqlBinaryType public byte[]? CVarbinary { get; init; } public byte[]? CVarbinaryMax { get; init; } public Guid? CUniqueidentifier { get; init; } +}; +public class MssqlSpatialType +{ + public SqlGeography? CGeography { get; init; } + public SqlGeometry? CGeometry { get; init; } + public SqlHierarchyId? CHierarchyid { get; init; } +}; +public class MssqlNodaType +{ + public DateTime? CDatetime2Instant { get; init; } + public DateTimeOffset? CDatetimeoffsetInstant { get; init; } +}; +public class ExtendedBio +{ + public required string AuthorName { get; init; } + public required string Name { get; init; } + public string? BioType { get; init; } }; \ No newline at end of file diff --git a/examples/MssqlDapperExample/MssqlDapperExample.csproj b/examples/MssqlDapperExample/MssqlDapperExample.csproj index aa9a4c70c..0914e6f74 100644 --- a/examples/MssqlDapperExample/MssqlDapperExample.csproj +++ b/examples/MssqlDapperExample/MssqlDapperExample.csproj @@ -14,7 +14,9 @@ - + + + \ No newline at end of file diff --git a/examples/MssqlDapperExample/QuerySql.cs b/examples/MssqlDapperExample/QuerySql.cs index a7eebec72..ce5e0c917 100644 --- a/examples/MssqlDapperExample/QuerySql.cs +++ b/examples/MssqlDapperExample/QuerySql.cs @@ -6,6 +6,9 @@ // ReSharper disable UnusedAutoPropertyAccessor.Global using Dapper; using Microsoft.Data.SqlClient; +using Microsoft.SqlServer.Types; +using NodaTime; +using NodaTime.Extensions; using System; using System.Collections.Generic; using System.Data; @@ -519,6 +522,150 @@ public async Task> GetAuthorsByBookNameAsync(GetAu } } + private const string CreateNoteReturnIdSql = "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);"; + public class CreateNoteReturnIdRow + { + public required int Id { get; init; } + }; + public class CreateNoteReturnIdArgs + { + public required string Body { get; init; } + }; + public async Task CreateNoteReturnIdAsync(CreateNoteReturnIdArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("body", args.Body); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.QuerySingleAsync(CreateNoteReturnIdSql, queryParams); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QuerySingleAsync(CreateNoteReturnIdSql, queryParams, transaction: this.Transaction); + } + + private const string GetNoteSql = "SELECT id, body FROM notes WHERE id = @id;"; + public class GetNoteRow + { + public required int Id { get; init; } + public required string Body { get; init; } + }; + public class GetNoteArgs + { + public required int Id { get; init; } + }; + public async Task GetNoteAsync(GetNoteArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("id", args.Id); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetNoteSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetNoteSql, queryParams, transaction: this.Transaction); + } + + private const string TruncateNotesSql = "TRUNCATE TABLE notes;"; + public async Task TruncateNotesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateNotesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateNotesSql, transaction: this.Transaction); + } + + private const string CreateExtendedBioSql = "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);"; + public class CreateExtendedBioArgs + { + public required string AuthorName { get; init; } + public required string Name { get; init; } + public string? BioType { get; init; } + }; + public async Task CreateExtendedBioAsync(CreateExtendedBioArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("author_name", args.AuthorName); + queryParams.Add("name", args.Name); + queryParams.Add("bio_type", args.BioType); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(CreateExtendedBioSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(CreateExtendedBioSql, queryParams, transaction: this.Transaction); + } + + private const string GetFirstExtendedBioByTypeSql = "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;"; + public class GetFirstExtendedBioByTypeRow + { + public required string AuthorName { get; init; } + public required string Name { get; init; } + public string? BioType { get; init; } + }; + public class GetFirstExtendedBioByTypeArgs + { + public string? BioType { get; init; } + }; + public async Task GetFirstExtendedBioByTypeAsync(GetFirstExtendedBioByTypeArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("bio_type", args.BioType); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetFirstExtendedBioByTypeSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetFirstExtendedBioByTypeSql, queryParams, transaction: this.Transaction); + } + + private const string TruncateExtendedBiosSql = "TRUNCATE TABLE extended.bios;"; + public async Task TruncateExtendedBiosAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateExtendedBiosSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateExtendedBiosSql, transaction: this.Transaction); + } + private const string InsertMssqlNumericTypesSql = @" INSERT INTO mssql_numeric_types ( @@ -612,17 +759,17 @@ public async Task InsertMssqlNumericTypesBatchAsync(List GetMssqlFunctionsAsync() { @@ -1281,4 +1428,188 @@ public class GetMssqlFunctionsRow throw new InvalidOperationException("Transaction is provided, but its connection is null."); return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlFunctionsSql, transaction: this.Transaction); } + + private const string InsertMssqlSpatialTypesSql = @" + INSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid);"; + public class InsertMssqlSpatialTypesArgs + { + public SqlGeography? CGeography { get; init; } + public SqlGeometry? CGeometry { get; init; } + public SqlHierarchyId? CHierarchyid { get; init; } + }; + public async Task InsertMssqlSpatialTypesAsync(InsertMssqlSpatialTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_geography", args.CGeography); + queryParams.Add("c_geometry", args.CGeometry); + queryParams.Add("c_hierarchyid", args.CHierarchyid); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlSpatialTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlSpatialTypesSql, queryParams, transaction: this.Transaction); + } + + private const string GetMssqlSpatialTypesSql = "SELECT TOP 1 c_geography, c_geometry, c_hierarchyid FROM mssql_spatial_types;"; + public class GetMssqlSpatialTypesRow + { + public SqlGeography? CGeography { get; init; } + public SqlGeometry? CGeometry { get; init; } + public SqlHierarchyId? CHierarchyid { get; init; } + }; + public async Task GetMssqlSpatialTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlSpatialTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlSpatialTypesSql, transaction: this.Transaction); + } + + private const string TruncateMssqlSpatialTypesSql = "TRUNCATE TABLE mssql_spatial_types;"; + public async Task TruncateMssqlSpatialTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlSpatialTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlSpatialTypesSql, transaction: this.Transaction); + } + + private const string InsertMssqlNodaTypesSql = @" + INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);"; + public class InsertMssqlNodaTypesArgs + { + public Instant? CDatetime2Instant { get; init; } + public Instant? CDatetimeoffsetInstant { get; init; } + }; + public async Task InsertMssqlNodaTypesAsync(InsertMssqlNodaTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_datetime2_instant", args.CDatetime2Instant is null ? null : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc()); + queryParams.Add("c_datetimeoffset_instant", args.CDatetimeoffsetInstant is null ? null : (DateTimeOffset? )args.CDatetimeoffsetInstant.Value.ToDateTimeOffset()); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlNodaTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlNodaTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlNodaTypesBatchSql = "INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);"; + public class InsertMssqlNodaTypesBatchArgs + { + public Instant? CDatetime2Instant { get; init; } + public Instant? CDatetimeoffsetInstant { get; init; } + }; + public async Task InsertMssqlNodaTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_datetime2_instant", typeof(object)); + dataTable.Columns.Add("c_datetimeoffset_instant", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CDatetime2Instant is null ? (object)DBNull.Value : (DateTime? )row.CDatetime2Instant.Value.ToDateTimeUtc(), row.CDatetimeoffsetInstant is null ? (object)DBNull.Value : (DateTimeOffset? )row.CDatetimeoffsetInstant.Value.ToDateTimeOffset()); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_noda_types]"; + bulkCopy.ColumnMappings.Add("c_datetime2_instant", "c_datetime2_instant"); + bulkCopy.ColumnMappings.Add("c_datetimeoffset_instant", "c_datetimeoffset_instant"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlNodaTypesSql = "SELECT TOP 1 c_datetime2_instant, c_datetimeoffset_instant FROM mssql_noda_types;"; + public class GetMssqlNodaTypesRow + { + public Instant? CDatetime2Instant { get; init; } + public Instant? CDatetimeoffsetInstant { get; init; } + }; + public async Task GetMssqlNodaTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlNodaTypesCntSql = @"SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant + FROM mssql_noda_types + GROUP BY c_datetime2_instant, c_datetimeoffset_instant;"; + public class GetMssqlNodaTypesCntRow + { + public required int Cnt { get; init; } + public Instant? CDatetime2Instant { get; init; } + public Instant? CDatetimeoffsetInstant { get; init; } + }; + public async Task GetMssqlNodaTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlNodaTypesSql = "TRUNCATE TABLE mssql_noda_types;"; + public async Task TruncateMssqlNodaTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlNodaTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlNodaTypesSql, transaction: this.Transaction); + } } \ No newline at end of file diff --git a/examples/MssqlDapperExample/Utils.cs b/examples/MssqlDapperExample/Utils.cs index 3d7bfaf3c..a7749c0f5 100644 --- a/examples/MssqlDapperExample/Utils.cs +++ b/examples/MssqlDapperExample/Utils.cs @@ -1,6 +1,9 @@ // auto-generated by sqlc - do not edit using Dapper; using Microsoft.Data.SqlClient; +using Microsoft.SqlServer.Types; +using NodaTime; +using NodaTime.Extensions; using System; using System.Data; using System.Linq; @@ -8,9 +11,28 @@ namespace MssqlDapperExampleGen; public static class Utils { + private class NodaInstantTypeHandler : SqlMapper.TypeHandler + { + public override Instant Parse(object value) + { + if (value is DateTime dt) + return DateTime.SpecifyKind(dt, DateTimeKind.Utc).ToInstant(); + if (value is DateTimeOffset dto) + return dto.ToInstant(); + throw new DataException($"Cannot convert {value?.GetType()} to Instant"); + } + + public override void SetValue(IDbDataParameter parameter, Instant value) + { + parameter.Value = value.ToDateTimeUtc(); + parameter.DbType = DbType.DateTime2; + } + } + public static void ConfigureSqlMapper() { SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2); + SqlMapper.AddTypeHandler(typeof(Instant), new NodaInstantTypeHandler()); } public static string TransformQueryForSliceArgs(string originalSql, int sliceSize, string paramName) diff --git a/examples/MssqlDapperExample/request.json b/examples/MssqlDapperExample/request.json index 4cb0ed3de..7430c83b2 100644 --- a/examples/MssqlDapperExample/request.json +++ b/examples/MssqlDapperExample/request.json @@ -13,7 +13,7 @@ "codegen": { "out": "examples/MssqlDapperExample", "plugin": "csharp", - "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxEYXBwZXJFeGFtcGxlR2VuIiwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OS4wIiwidXNlRGFwcGVyIjp0cnVlfQ==", + "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxEYXBwZXJFeGFtcGxlR2VuIiwib3ZlcnJpZGVzIjpbeyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfaW50IiwiY3NoYXJwX3R5cGUiOnsibm90TnVsbCI6ZmFsc2UsInR5cGUiOiJpbnQifX0seyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfdmFyY2hhciIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOmZhbHNlLCJ0eXBlIjoic3RyaW5nIn19LHsiY29sdW1uIjoiR2V0TXNzcWxGdW5jdGlvbnM6bWF4X2RhdGV0aW1lMiIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOnRydWUsInR5cGUiOiJEYXRlVGltZSJ9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZTJfaW5zdGFudCIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOmZhbHNlLCJ0eXBlIjoiSW5zdGFudCJ9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZW9mZnNldF9pbnN0YW50IiwiY3NoYXJwX3R5cGUiOnsibm90TnVsbCI6ZmFsc2UsInR5cGUiOiJJbnN0YW50In19XSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OS4wIiwidXNlRGFwcGVyIjp0cnVlfQ==", "process": { "cmd": "./dist/LocalRunner" } @@ -126,6 +126,38 @@ } ] }, + { + "rel": { + "schema": "public", + "name": "notes" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + } + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, { "rel": { "schema": "public", @@ -454,6 +486,77 @@ } } ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "columns": [ + { + "name": "c_geography", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + } + }, + { + "name": "c_geometry", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + } + }, + { + "name": "c_hierarchyid", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_noda_types" + }, + "columns": [ + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + } + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + } + } + ] } ] }, @@ -462,6 +565,54 @@ }, { "name": "information_schema" + }, + { + "name": "extended", + "tables": [ + { + "rel": { + "schema": "extended", + "name": "bios" + }, + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] } ] }, @@ -1236,6 +1387,240 @@ ], "filename": "query.sql" }, + { + "text": "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);", + "name": "CreateNoteReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "body", + "notNull": true, + "length": 200, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "notes" + } + }, + { + "text": "SELECT id, body FROM notes WHERE id = @id;", + "name": "GetNote", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE notes;", + "name": "TruncateNotes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);", + "name": "CreateExtendedBio", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "author_name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "schema": "extended", + "name": "bios" + } + }, + { + "text": "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;", + "name": "GetFirstExtendedBioByType", + "cmd": ":one", + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE extended.bios;", + "name": "TruncateExtendedBios", + "cmd": ":exec", + "filename": "query.sql" + }, { "text": "\nINSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", "name": "InsertMssqlNumericTypes", @@ -2999,8 +3384,287 @@ " Functions " ], "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid);", + "name": "InsertMssqlSpatialTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_geography", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + }, + "originalName": "c_geography" + } + }, + { + "number": 2, + "column": { + "name": "c_geometry", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + }, + "originalName": "c_geometry" + } + }, + { + "number": 3, + "column": { + "name": "c_hierarchyid", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + }, + "originalName": "c_hierarchyid" + } + } + ], + "comments": [ + " Spatial and hierarchyid types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_spatial_types" + } + }, + { + "text": "SELECT TOP 1 c_geography, c_geometry, c_hierarchyid FROM mssql_spatial_types;", + "name": "GetMssqlSpatialTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_geography", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + }, + "originalName": "c_geography" + }, + { + "name": "c_geometry", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + }, + "originalName": "c_geometry" + }, + { + "name": "c_hierarchyid", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + }, + "originalName": "c_hierarchyid" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_spatial_types;", + "name": "TruncateMssqlSpatialTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);", + "name": "InsertMssqlNodaTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_datetime2_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + } + }, + { + "number": 2, + "column": { + "name": "c_datetimeoffset_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + } + ], + "comments": [ + " NodaTime Instant overrides " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_noda_types" + } + }, + { + "text": "INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);", + "name": "InsertMssqlNodaTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_datetime2_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + } + }, + { + "number": 2, + "column": { + "name": "c_datetimeoffset_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_noda_types" + } + }, + { + "text": "SELECT TOP 1 c_datetime2_instant, c_datetimeoffset_instant FROM mssql_noda_types;", + "name": "GetMssqlNodaTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant\nFROM mssql_noda_types\nGROUP BY c_datetime2_instant, c_datetimeoffset_instant;", + "name": "GetMssqlNodaTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_noda_types;", + "name": "TruncateMssqlNodaTypes", + "cmd": ":exec", + "filename": "query.sql" } ], "sqlc_version": "v1.31.1", - "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OS4wIiwibmFtZXNwYWNlTmFtZSI6Ik1zc3FsRGFwcGVyRXhhbXBsZUdlbiIsInVzZURhcHBlciI6dHJ1ZSwib3ZlcnJpZGVEYXBwZXJWZXJzaW9uIjoiIiwib3ZlcnJpZGVzIjpudWxsLCJkZWJ1Z1JlcXVlc3QiOmZhbHNlLCJ1c2VDZW50cmFsUGFja2FnZU1hbmFnZW1lbnQiOmZhbHNlLCJ3aXRoQXN5bmNTdWZmaXgiOnRydWUsIndpdGhDYW5jZWxsYXRpb25Ub2tlbiI6ZmFsc2UsInVzZUFzeW5jRW51bWVyYWJsZSI6ZmFsc2UsInVzZVByb3BlclNpbmd1bGFyaXphdGlvbiI6ZmFsc2V9" + "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OS4wIiwibmFtZXNwYWNlTmFtZSI6Ik1zc3FsRGFwcGVyRXhhbXBsZUdlbiIsInVzZURhcHBlciI6dHJ1ZSwib3ZlcnJpZGVEYXBwZXJWZXJzaW9uIjoiIiwib3ZlcnJpZGVzIjpbeyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfaW50IiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6ImludCIsIm5vdE51bGwiOmZhbHNlfX0seyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfdmFyY2hhciIsImNzaGFycF90eXBlIjp7InR5cGUiOiJzdHJpbmciLCJub3ROdWxsIjpmYWxzZX19LHsiY29sdW1uIjoiR2V0TXNzcWxGdW5jdGlvbnM6bWF4X2RhdGV0aW1lMiIsImNzaGFycF90eXBlIjp7InR5cGUiOiJEYXRlVGltZSIsIm5vdE51bGwiOnRydWV9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZTJfaW5zdGFudCIsImNzaGFycF90eXBlIjp7InR5cGUiOiJJbnN0YW50Iiwibm90TnVsbCI6ZmFsc2V9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZW9mZnNldF9pbnN0YW50IiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6Ikluc3RhbnQiLCJub3ROdWxsIjpmYWxzZX19XSwiZGVidWdSZXF1ZXN0IjpmYWxzZSwidXNlQ2VudHJhbFBhY2thZ2VNYW5hZ2VtZW50IjpmYWxzZSwid2l0aEFzeW5jU3VmZml4Ijp0cnVlLCJ3aXRoQ2FuY2VsbGF0aW9uVG9rZW4iOmZhbHNlLCJ1c2VBc3luY0VudW1lcmFibGUiOmZhbHNlLCJ1c2VQcm9wZXJTaW5ndWxhcml6YXRpb24iOmZhbHNlfQ==" } \ No newline at end of file diff --git a/examples/MssqlDapperExample/request.message b/examples/MssqlDapperExample/request.message index 1b18f241b7938fba0647c9bb47443f959b6625af..be340bc9f16264a36d90f3d96580d5688f27b54b 100644 GIT binary patch delta 5472 zcmds5Pi)gx7?+du-)lRHn?iUH@T`fID62@>7$jn}DWMeF2GW#jW01PB+gc=XYrCik zYTf-~2TrX|lZM!|-OvuKlXlr<;xKlZhBS#ylP2xBBh%2N9nhW~+j%dE+rSQ+c1rB` zzR&M{zu)hB-}~;|qrUos+5x_322?-Ylvk9+9PC}^_OHM6j_19m_0JD_zVWU9l-8tc zf-H-?ps>+P*V(L;Tgn&MC_5~uV}{M?r9xH}rGgU8bITdApt4~$tITuqLPjkv2yFB^ zTaeW8rCg4U&T=_L2(#t)tTA_GCA^JKZ8>v|le5TS!;TeI77KF?_+HNNoGPedUg&pq zuCY;7UaGA66tYMoiz@e_Xtv@Eg+WodrwGM}HJ=QJ>2t(dp)@wW^+l@N^E=$UK|Q8=)+n%pZeCcL$%$FmguoIMI--|6!HD^PrU`lxR2x(iFn;MM z@XbhkF+d%@Po=61^(R!Y`91}IYXgffdFrC*3jmnbc|ny;c7DK zwDIRqjHZWV0V!!5v0Go=xr=(mAh?DTyyOvio8+O(Vb}-9TQgk71$INmW3>C`LP#F<0#q$?K23eF})Zc!;HZ!6flb< zhnS0lqmzjVrgwmtHBg>5;LMBBJIf?USnQ9BlC_l3G}>*lhV}?`gZOi{T1IlC$DG8~5zz+KRbeGcSDt_$ zI)fMnaZ}@(l^D#e!Y{pG%Hn7`d7g0w?41}a8cGdxtU(<+zvC9W7znQ z9h;a)34;OL0zFhrj`ApL??#1@AeRfxiE_nY@WWngNJa6?lC{Hn=)gtt0LN>G5?*Ii z7ZMO33`t0@8KSa4t|v*?SY=VdYLKcb17bs=Rixyr)l~=bJWF~etBw*`rPL`x$O-)i z7%N;1SKygSRC)c@!F^{)EGRhSPcjA6eHL4RB-4Y9Xkm%(Q88lIjE1V5n~7cW8;Y`DlEok)E_+IU#SZDpep_XTpmG#;W-&L8RzT?MT?z^8 zDyUz;q9dqo39COi>-X0)(I1`dN!A+(-J+1#w5`fr*N?4&z@Cy@ovc4OxA&6UpxVEW zF?Wn^bBuKZW1QPAgCC7Ftf}kaV=uU&zjd+u-@$EYO}=V4nkD=idfWho!} zKLDt?0=OMSzk0xhs?25!0h5QDI_v~Dg#??ah!d5Oc6zJmrJUPD7o|MCPW&&kD_hCI z#p8BjfEpGu_`Mf=KoRj7N=}Zadqanq!3ic?Q1O)0K)`@`eW|_xgW*(i@;noN-N;|Z zt!_pG-mqiDh_in4&Hldset>u20Uia6e)s6Epp)2R^e*BYFgJGl%I*%f>Lw%Sy)?C7J8OS28>?$bCUU)!qHwyo#O8{D100Wyt7(*OVf delta 129 zcmdnm&^Mt-i>Ok9jcLae#P#f3Ri8WR)cCh9gb?O~j_NsY07;;oR$G3C;e1)0^w z7l;Y56_h6BWF{+dz*+A^H~TYxWn_NI#x + + + + + netstandard2.0 + MssqlDapperLegacyExampleGen + Library + False + + + + + + + + + + \ No newline at end of file diff --git a/examples/MssqlDapperLegacyExample/QuerySql.cs b/examples/MssqlDapperLegacyExample/QuerySql.cs new file mode 100644 index 000000000..b6d109ef6 --- /dev/null +++ b/examples/MssqlDapperLegacyExample/QuerySql.cs @@ -0,0 +1,1617 @@ +// auto-generated by sqlc - do not edit +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable UseAwaitUsing +// ReSharper disable ConvertToUsingDeclaration +// ReSharper disable NotAccessedPositionalProperty.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +namespace MssqlDapperLegacyExampleGen +{ + using Dapper; + using Microsoft.Data.SqlClient; + using Microsoft.SqlServer.Types; + using NodaTime; + using NodaTime.Extensions; + using System; + using System.Collections.Generic; + using System.Data; + using System.Threading; + using System.Threading.Tasks; + + public class QuerySql + { + public QuerySql() + { + Utils.ConfigureSqlMapper(); + DefaultTypeMap.MatchNamesWithUnderscores = true; + } + + public QuerySql(string connectionString) : this() + { + this.ConnectionString = connectionString; + } + + private QuerySql(SqlTransaction transaction) : this() + { + this.Transaction = transaction; + } + + public static QuerySql WithTransaction(SqlTransaction transaction) + { + return new QuerySql(transaction); + } + + private SqlTransaction Transaction { get; } + private string ConnectionString { get; } + + private const string GetAuthorSql = @"SELECT TOP 1 id, name, bio FROM authors + WHERE name = @name;"; + public class GetAuthorRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorArgs + { + public string Name { get; set; } + }; + public async Task GetAuthorAsync(GetAuthorArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name", args.Name); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetAuthorSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorSql, queryParams, transaction: this.Transaction); + } + + private const string ListAuthorsSql = @"SELECT id, name, bio + FROM authors + ORDER BY name + OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;"; + public class ListAuthorsRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class ListAuthorsArgs + { + public int Offset { get; set; } + public int Limit { get; set; } + }; + public async Task> ListAuthorsAsync(ListAuthorsArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("offset", args.Offset); + queryParams.Add("limit", args.Limit); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync(ListAuthorsSql, queryParams); + return result.AsList(); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return (await this.Transaction.Connection.QueryAsync(ListAuthorsSql, queryParams, transaction: this.Transaction)).AsList(); + } + + private const string CreateAuthorSql = "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);"; + public class CreateAuthorArgs + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public async Task CreateAuthorAsync(CreateAuthorArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("id", args.Id); + queryParams.Add("name", args.Name); + queryParams.Add("bio", args.Bio); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(CreateAuthorSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(CreateAuthorSql, queryParams, transaction: this.Transaction); + } + + private const string CreateAuthorReturnIdSql = "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);"; + public class CreateAuthorReturnIdRow + { + public long Id { get; set; } + }; + public class CreateAuthorReturnIdArgs + { + public string Name { get; set; } + public string Bio { get; set; } + }; + public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name", args.Name); + queryParams.Add("bio", args.Bio); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.QuerySingleAsync(CreateAuthorReturnIdSql, queryParams); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QuerySingleAsync(CreateAuthorReturnIdSql, queryParams, transaction: this.Transaction); + } + + private const string GetAuthorByIdSql = @"SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment + WHERE id = @id;"; + public class GetAuthorByIdRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorByIdArgs + { + public long Id { get; set; } + }; + public async Task GetAuthorByIdAsync(GetAuthorByIdArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("id", args.Id); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetAuthorByIdSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorByIdSql, queryParams, transaction: this.Transaction); + } + + private const string GetAuthorByNamePatternSql = @"SELECT id, name, bio FROM authors + WHERE name LIKE COALESCE(@name_pattern, '%');"; + public class GetAuthorByNamePatternRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorByNamePatternArgs + { + public object NamePattern { get; set; } + }; + public async Task> GetAuthorByNamePatternAsync(GetAuthorByNamePatternArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name_pattern", args.NamePattern); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync(GetAuthorByNamePatternSql, queryParams); + return result.AsList(); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return (await this.Transaction.Connection.QueryAsync(GetAuthorByNamePatternSql, queryParams, transaction: this.Transaction)).AsList(); + } + + private const string DeleteAuthorSql = @"DELETE FROM authors + WHERE name = @name;"; + public class DeleteAuthorArgs + { + public string Name { get; set; } + }; + public async Task DeleteAuthorAsync(DeleteAuthorArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name", args.Name); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(DeleteAuthorSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(DeleteAuthorSql, queryParams, transaction: this.Transaction); + } + + private const string DeleteAllAuthorsSql = "DELETE FROM authors;"; + public async Task DeleteAllAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(DeleteAllAuthorsSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(DeleteAllAuthorsSql, transaction: this.Transaction); + } + + private const string UpdateAuthorsSql = @"UPDATE authors + SET bio = @bio + WHERE bio IS NOT NULL;"; + public class UpdateAuthorsArgs + { + public string Bio { get; set; } + }; + public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("bio", args.Bio); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.ExecuteAsync(UpdateAuthorsSql, queryParams); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.ExecuteAsync(UpdateAuthorsSql, queryParams, transaction: this.Transaction); + } + + private const string GetAuthorsByIdsSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);"; + public class GetAuthorsByIdsRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorsByIdsArgs + { + public long[] Ids { get; set; } + }; + public async Task> GetAuthorsByIdsAsync(GetAuthorsByIdsArgs args) + { + var transformedSql = GetAuthorsByIdsSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + var queryParams = new Dictionary(); + for (int i = 0; i < args.Ids.Length; i++) + queryParams.Add($"@idsArg{i}", args.Ids[i]); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync(transformedSql, queryParams); + return result.AsList(); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return (await this.Transaction.Connection.QueryAsync(transformedSql, queryParams, transaction: this.Transaction)).AsList(); + } + + private const string GetAuthorsByIdsAndNamesSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);"; + public class GetAuthorsByIdsAndNamesRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorsByIdsAndNamesArgs + { + public long[] Ids { get; set; } + public string[] Names { get; set; } + }; + public async Task> GetAuthorsByIdsAndNamesAsync(GetAuthorsByIdsAndNamesArgs args) + { + var transformedSql = GetAuthorsByIdsAndNamesSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Names.Length, "names"); + var queryParams = new Dictionary(); + for (int i = 0; i < args.Ids.Length; i++) + queryParams.Add($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + queryParams.Add($"@namesArg{i}", args.Names[i]); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync(transformedSql, queryParams); + return result.AsList(); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return (await this.Transaction.Connection.QueryAsync(transformedSql, queryParams, transaction: this.Transaction)).AsList(); + } + + private const string CreateBookSql = "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);"; + public class CreateBookRow + { + public long Id { get; set; } + }; + public class CreateBookArgs + { + public string Name { get; set; } + public long AuthorId { get; set; } + }; + public async Task CreateBookAsync(CreateBookArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("name", args.Name); + queryParams.Add("author_id", args.AuthorId); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.QuerySingleAsync(CreateBookSql, queryParams); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QuerySingleAsync(CreateBookSql, queryParams, transaction: this.Transaction); + } + + private const string ListAllAuthorsBooksSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + ORDER BY authors.name;"; + public class ListAllAuthorsBooksRow + { + public Author Author { get; set; } + public Book Book { get; set; } + }; + public async Task> ListAllAuthorsBooksAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(ListAllAuthorsBooksSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAllAuthorsBooksSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + + private const string GetDuplicateAuthorsSql = @"SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio + FROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name + WHERE authors1.id < authors2.id;"; + public class GetDuplicateAuthorsRow + { + public Author Author { get; set; } + public Author Author2 { get; set; } + }; + public async Task> GetDuplicateAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetDuplicateAuthorsSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetDuplicateAuthorsSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + + private const string GetAuthorsByBookNameSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + WHERE books.name = @name;"; + public class GetAuthorsByBookNameRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + public Book Book { get; set; } + }; + public class GetAuthorsByBookNameArgs + { + public string Name { get; set; } + }; + public async Task> GetAuthorsByBookNameAsync(GetAuthorsByBookNameArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorsByBookNameSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + + private const string CreateNoteReturnIdSql = "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);"; + public class CreateNoteReturnIdRow + { + public int Id { get; set; } + }; + public class CreateNoteReturnIdArgs + { + public string Body { get; set; } + }; + public async Task CreateNoteReturnIdAsync(CreateNoteReturnIdArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("body", args.Body); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.QuerySingleAsync(CreateNoteReturnIdSql, queryParams); + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QuerySingleAsync(CreateNoteReturnIdSql, queryParams, transaction: this.Transaction); + } + + private const string GetNoteSql = "SELECT id, body FROM notes WHERE id = @id;"; + public class GetNoteRow + { + public int Id { get; set; } + public string Body { get; set; } + }; + public class GetNoteArgs + { + public int Id { get; set; } + }; + public async Task GetNoteAsync(GetNoteArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("id", args.Id); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetNoteSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetNoteSql, queryParams, transaction: this.Transaction); + } + + private const string TruncateNotesSql = "TRUNCATE TABLE notes;"; + public async Task TruncateNotesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateNotesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateNotesSql, transaction: this.Transaction); + } + + private const string CreateExtendedBioSql = "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);"; + public class CreateExtendedBioArgs + { + public string AuthorName { get; set; } + public string Name { get; set; } + public string BioType { get; set; } + }; + public async Task CreateExtendedBioAsync(CreateExtendedBioArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("author_name", args.AuthorName); + queryParams.Add("name", args.Name); + queryParams.Add("bio_type", args.BioType); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(CreateExtendedBioSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(CreateExtendedBioSql, queryParams, transaction: this.Transaction); + } + + private const string GetFirstExtendedBioByTypeSql = "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;"; + public class GetFirstExtendedBioByTypeRow + { + public string AuthorName { get; set; } + public string Name { get; set; } + public string BioType { get; set; } + }; + public class GetFirstExtendedBioByTypeArgs + { + public string BioType { get; set; } + }; + public async Task GetFirstExtendedBioByTypeAsync(GetFirstExtendedBioByTypeArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("bio_type", args.BioType); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetFirstExtendedBioByTypeSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetFirstExtendedBioByTypeSql, queryParams, transaction: this.Transaction); + } + + private const string TruncateExtendedBiosSql = "TRUNCATE TABLE extended.bios;"; + public async Task TruncateExtendedBiosAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateExtendedBiosSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateExtendedBiosSql, transaction: this.Transaction); + } + + private const string InsertMssqlNumericTypesSql = @" + INSERT INTO mssql_numeric_types + ( + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + ) + VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);"; + public class InsertMssqlNumericTypesArgs + { + public bool? CBit { get; set; } + public byte? CTinyint { get; set; } + public short? CSmallint { get; set; } + public int? CInt { get; set; } + public long? CBigint { get; set; } + public float? CReal { get; set; } + public double? CFloat { get; set; } + public decimal? CDecimal { get; set; } + public decimal? CNumeric { get; set; } + public decimal? CMoney { get; set; } + public decimal? CSmallmoney { get; set; } + }; + public async Task InsertMssqlNumericTypesAsync(InsertMssqlNumericTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_bit", args.CBit); + queryParams.Add("c_tinyint", args.CTinyint); + queryParams.Add("c_smallint", args.CSmallint); + queryParams.Add("c_int", args.CInt); + queryParams.Add("c_bigint", args.CBigint); + queryParams.Add("c_real", args.CReal); + queryParams.Add("c_float", args.CFloat); + queryParams.Add("c_decimal", args.CDecimal); + queryParams.Add("c_numeric", args.CNumeric); + queryParams.Add("c_money", args.CMoney); + queryParams.Add("c_smallmoney", args.CSmallmoney); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlNumericTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlNumericTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlNumericTypesBatchSql = @"INSERT INTO mssql_numeric_types + ( + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + ) + VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);"; + public class InsertMssqlNumericTypesBatchArgs + { + public bool? CBit { get; set; } + public byte? CTinyint { get; set; } + public short? CSmallint { get; set; } + public int? CInt { get; set; } + public long? CBigint { get; set; } + public float? CReal { get; set; } + public double? CFloat { get; set; } + public decimal? CDecimal { get; set; } + public decimal? CNumeric { get; set; } + public decimal? CMoney { get; set; } + public decimal? CSmallmoney { get; set; } + }; + public async Task InsertMssqlNumericTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_bit", typeof(object)); + dataTable.Columns.Add("c_tinyint", typeof(object)); + dataTable.Columns.Add("c_smallint", typeof(object)); + dataTable.Columns.Add("c_int", typeof(object)); + dataTable.Columns.Add("c_bigint", typeof(object)); + dataTable.Columns.Add("c_real", typeof(object)); + dataTable.Columns.Add("c_float", typeof(object)); + dataTable.Columns.Add("c_decimal", typeof(object)); + dataTable.Columns.Add("c_numeric", typeof(object)); + dataTable.Columns.Add("c_money", typeof(object)); + dataTable.Columns.Add("c_smallmoney", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CBit, row.CTinyint, row.CSmallint, row.CInt, row.CBigint, row.CReal, row.CFloat, row.CDecimal, row.CNumeric, row.CMoney, row.CSmallmoney); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_numeric_types]"; + bulkCopy.ColumnMappings.Add("c_bit", "c_bit"); + bulkCopy.ColumnMappings.Add("c_tinyint", "c_tinyint"); + bulkCopy.ColumnMappings.Add("c_smallint", "c_smallint"); + bulkCopy.ColumnMappings.Add("c_int", "c_int"); + bulkCopy.ColumnMappings.Add("c_bigint", "c_bigint"); + bulkCopy.ColumnMappings.Add("c_real", "c_real"); + bulkCopy.ColumnMappings.Add("c_float", "c_float"); + bulkCopy.ColumnMappings.Add("c_decimal", "c_decimal"); + bulkCopy.ColumnMappings.Add("c_numeric", "c_numeric"); + bulkCopy.ColumnMappings.Add("c_money", "c_money"); + bulkCopy.ColumnMappings.Add("c_smallmoney", "c_smallmoney"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlNumericTypesSql = "SELECT TOP 1 c_bit, c_tinyint, c_smallint, c_int, c_bigint, c_real, c_float, c_decimal, c_numeric, c_money, c_smallmoney FROM mssql_numeric_types;"; + public class GetMssqlNumericTypesRow + { + public bool? CBit { get; set; } + public byte? CTinyint { get; set; } + public short? CSmallint { get; set; } + public int? CInt { get; set; } + public long? CBigint { get; set; } + public float? CReal { get; set; } + public double? CFloat { get; set; } + public decimal? CDecimal { get; set; } + public decimal? CNumeric { get; set; } + public decimal? CMoney { get; set; } + public decimal? CSmallmoney { get; set; } + }; + public async Task GetMssqlNumericTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlNumericTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + FROM mssql_numeric_types + GROUP BY + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney;"; + public class GetMssqlNumericTypesCntRow + { + public int Cnt { get; set; } + public bool? CBit { get; set; } + public byte? CTinyint { get; set; } + public short? CSmallint { get; set; } + public int? CInt { get; set; } + public long? CBigint { get; set; } + public float? CReal { get; set; } + public double? CFloat { get; set; } + public decimal? CDecimal { get; set; } + public decimal? CNumeric { get; set; } + public decimal? CMoney { get; set; } + public decimal? CSmallmoney { get; set; } + }; + public async Task GetMssqlNumericTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlNumericTypesSql = "TRUNCATE TABLE mssql_numeric_types;"; + public async Task TruncateMssqlNumericTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlNumericTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlNumericTypesSql, transaction: this.Transaction); + } + + private const string InsertMssqlStringTypesSql = @" + INSERT INTO mssql_string_types + ( + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + ) + VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);"; + public class InsertMssqlStringTypesArgs + { + public string CChar { get; set; } + public string CNchar { get; set; } + public string CVarchar { get; set; } + public string CNvarchar { get; set; } + public string CVarcharMax { get; set; } + public string CNvarcharMax { get; set; } + }; + public async Task InsertMssqlStringTypesAsync(InsertMssqlStringTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_char", args.CChar); + queryParams.Add("c_nchar", args.CNchar); + queryParams.Add("c_varchar", args.CVarchar); + queryParams.Add("c_nvarchar", args.CNvarchar); + queryParams.Add("c_varchar_max", args.CVarcharMax); + queryParams.Add("c_nvarchar_max", args.CNvarcharMax); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlStringTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlStringTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlStringTypesBatchSql = @"INSERT INTO mssql_string_types + ( + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + ) + VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);"; + public class InsertMssqlStringTypesBatchArgs + { + public string CChar { get; set; } + public string CNchar { get; set; } + public string CVarchar { get; set; } + public string CNvarchar { get; set; } + public string CVarcharMax { get; set; } + public string CNvarcharMax { get; set; } + }; + public async Task InsertMssqlStringTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_char", typeof(object)); + dataTable.Columns.Add("c_nchar", typeof(object)); + dataTable.Columns.Add("c_varchar", typeof(object)); + dataTable.Columns.Add("c_nvarchar", typeof(object)); + dataTable.Columns.Add("c_varchar_max", typeof(object)); + dataTable.Columns.Add("c_nvarchar_max", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CChar, row.CNchar, row.CVarchar, row.CNvarchar, row.CVarcharMax, row.CNvarcharMax); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_string_types]"; + bulkCopy.ColumnMappings.Add("c_char", "c_char"); + bulkCopy.ColumnMappings.Add("c_nchar", "c_nchar"); + bulkCopy.ColumnMappings.Add("c_varchar", "c_varchar"); + bulkCopy.ColumnMappings.Add("c_nvarchar", "c_nvarchar"); + bulkCopy.ColumnMappings.Add("c_varchar_max", "c_varchar_max"); + bulkCopy.ColumnMappings.Add("c_nvarchar_max", "c_nvarchar_max"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlStringTypesSql = "SELECT TOP 1 c_char, c_nchar, c_varchar, c_nvarchar, c_varchar_max, c_nvarchar_max FROM mssql_string_types;"; + public class GetMssqlStringTypesRow + { + public string CChar { get; set; } + public string CNchar { get; set; } + public string CVarchar { get; set; } + public string CNvarchar { get; set; } + public string CVarcharMax { get; set; } + public string CNvarcharMax { get; set; } + }; + public async Task GetMssqlStringTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlStringTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + FROM mssql_string_types + GROUP BY + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max;"; + public class GetMssqlStringTypesCntRow + { + public int Cnt { get; set; } + public string CChar { get; set; } + public string CNchar { get; set; } + public string CVarchar { get; set; } + public string CNvarchar { get; set; } + public string CVarcharMax { get; set; } + public string CNvarcharMax { get; set; } + }; + public async Task GetMssqlStringTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlStringTypesSql = "TRUNCATE TABLE mssql_string_types;"; + public async Task TruncateMssqlStringTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlStringTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlStringTypesSql, transaction: this.Transaction); + } + + private const string InsertMssqlDatetimeTypesSql = @" + INSERT INTO mssql_datetime_types + ( + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + ) + VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);"; + public class InsertMssqlDatetimeTypesArgs + { + public DateTime? CDate { get; set; } + public DateTime? CDatetime { get; set; } + public DateTime? CDatetime2 { get; set; } + public DateTime? CSmalldatetime { get; set; } + public DateTimeOffset? CDatetimeoffset { get; set; } + public TimeSpan? CTime { get; set; } + }; + public async Task InsertMssqlDatetimeTypesAsync(InsertMssqlDatetimeTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_date", args.CDate); + queryParams.Add("c_datetime", args.CDatetime); + queryParams.Add("c_datetime2", args.CDatetime2); + queryParams.Add("c_smalldatetime", args.CSmalldatetime); + queryParams.Add("c_datetimeoffset", args.CDatetimeoffset); + queryParams.Add("c_time", args.CTime); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlDatetimeTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlDatetimeTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlDatetimeTypesBatchSql = @"INSERT INTO mssql_datetime_types + ( + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + ) + VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);"; + public class InsertMssqlDatetimeTypesBatchArgs + { + public DateTime? CDate { get; set; } + public DateTime? CDatetime { get; set; } + public DateTime? CDatetime2 { get; set; } + public DateTime? CSmalldatetime { get; set; } + public DateTimeOffset? CDatetimeoffset { get; set; } + public TimeSpan? CTime { get; set; } + }; + public async Task InsertMssqlDatetimeTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_date", typeof(object)); + dataTable.Columns.Add("c_datetime", typeof(object)); + dataTable.Columns.Add("c_datetime2", typeof(object)); + dataTable.Columns.Add("c_smalldatetime", typeof(object)); + dataTable.Columns.Add("c_datetimeoffset", typeof(object)); + dataTable.Columns.Add("c_time", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CDate, row.CDatetime, row.CDatetime2, row.CSmalldatetime, row.CDatetimeoffset, row.CTime); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_datetime_types]"; + bulkCopy.ColumnMappings.Add("c_date", "c_date"); + bulkCopy.ColumnMappings.Add("c_datetime", "c_datetime"); + bulkCopy.ColumnMappings.Add("c_datetime2", "c_datetime2"); + bulkCopy.ColumnMappings.Add("c_smalldatetime", "c_smalldatetime"); + bulkCopy.ColumnMappings.Add("c_datetimeoffset", "c_datetimeoffset"); + bulkCopy.ColumnMappings.Add("c_time", "c_time"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlDatetimeTypesSql = "SELECT TOP 1 c_date, c_datetime, c_datetime2, c_smalldatetime, c_datetimeoffset, c_time FROM mssql_datetime_types;"; + public class GetMssqlDatetimeTypesRow + { + public DateTime? CDate { get; set; } + public DateTime? CDatetime { get; set; } + public DateTime? CDatetime2 { get; set; } + public DateTime? CSmalldatetime { get; set; } + public DateTimeOffset? CDatetimeoffset { get; set; } + public TimeSpan? CTime { get; set; } + }; + public async Task GetMssqlDatetimeTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlDatetimeTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + FROM mssql_datetime_types + GROUP BY + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time;"; + public class GetMssqlDatetimeTypesCntRow + { + public int Cnt { get; set; } + public DateTime? CDate { get; set; } + public DateTime? CDatetime { get; set; } + public DateTime? CDatetime2 { get; set; } + public DateTime? CSmalldatetime { get; set; } + public DateTimeOffset? CDatetimeoffset { get; set; } + public TimeSpan? CTime { get; set; } + }; + public async Task GetMssqlDatetimeTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlDatetimeTypesSql = "TRUNCATE TABLE mssql_datetime_types;"; + public async Task TruncateMssqlDatetimeTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlDatetimeTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlDatetimeTypesSql, transaction: this.Transaction); + } + + private const string InsertMssqlBinaryTypesSql = @" + INSERT INTO mssql_binary_types + ( + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + ) + VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);"; + public class InsertMssqlBinaryTypesArgs + { + public byte[] CBinary { get; set; } + public byte[] CVarbinary { get; set; } + public byte[] CVarbinaryMax { get; set; } + public Guid? CUniqueidentifier { get; set; } + }; + public async Task InsertMssqlBinaryTypesAsync(InsertMssqlBinaryTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_binary", new Utils.BinaryParameter(args.CBinary)); + queryParams.Add("c_varbinary", new Utils.BinaryParameter(args.CVarbinary)); + queryParams.Add("c_varbinary_max", new Utils.BinaryParameter(args.CVarbinaryMax)); + queryParams.Add("c_uniqueidentifier", args.CUniqueidentifier); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlBinaryTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlBinaryTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlBinaryTypesBatchSql = @"INSERT INTO mssql_binary_types + ( + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + ) + VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);"; + public class InsertMssqlBinaryTypesBatchArgs + { + public byte[] CBinary { get; set; } + public byte[] CVarbinary { get; set; } + public byte[] CVarbinaryMax { get; set; } + public Guid? CUniqueidentifier { get; set; } + }; + public async Task InsertMssqlBinaryTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_binary", typeof(object)); + dataTable.Columns.Add("c_varbinary", typeof(object)); + dataTable.Columns.Add("c_varbinary_max", typeof(object)); + dataTable.Columns.Add("c_uniqueidentifier", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CBinary ?? (object)DBNull.Value, row.CVarbinary ?? (object)DBNull.Value, row.CVarbinaryMax ?? (object)DBNull.Value, row.CUniqueidentifier); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_binary_types]"; + bulkCopy.ColumnMappings.Add("c_binary", "c_binary"); + bulkCopy.ColumnMappings.Add("c_varbinary", "c_varbinary"); + bulkCopy.ColumnMappings.Add("c_varbinary_max", "c_varbinary_max"); + bulkCopy.ColumnMappings.Add("c_uniqueidentifier", "c_uniqueidentifier"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlBinaryTypesSql = "SELECT TOP 1 c_binary, c_varbinary, c_varbinary_max, c_uniqueidentifier FROM mssql_binary_types;"; + public class GetMssqlBinaryTypesRow + { + public byte[] CBinary { get; set; } + public byte[] CVarbinary { get; set; } + public byte[] CVarbinaryMax { get; set; } + public Guid? CUniqueidentifier { get; set; } + }; + public async Task GetMssqlBinaryTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlBinaryTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + FROM mssql_binary_types + GROUP BY + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier;"; + public class GetMssqlBinaryTypesCntRow + { + public int Cnt { get; set; } + public byte[] CBinary { get; set; } + public byte[] CVarbinary { get; set; } + public byte[] CVarbinaryMax { get; set; } + public Guid? CUniqueidentifier { get; set; } + }; + public async Task GetMssqlBinaryTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlBinaryTypesSql = "TRUNCATE TABLE mssql_binary_types;"; + public async Task TruncateMssqlBinaryTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlBinaryTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlBinaryTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlFunctionsSql = @" + SELECT + MAX(c_int) AS max_int, + MAX(c_varchar) AS max_varchar, + MAX(c_datetime2) AS max_datetime2 + FROM mssql_numeric_types + CROSS JOIN mssql_string_types + CROSS JOIN mssql_datetime_types;"; + public class GetMssqlFunctionsRow + { + public int? MaxInt { get; set; } + public string MaxVarchar { get; set; } + public DateTime MaxDatetime2 { get; set; } + }; + public async Task GetMssqlFunctionsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlFunctionsSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlFunctionsSql, transaction: this.Transaction); + } + + private const string InsertMssqlSpatialTypesSql = @" + INSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid);"; + public class InsertMssqlSpatialTypesArgs + { + public SqlGeography CGeography { get; set; } + public SqlGeometry CGeometry { get; set; } + public SqlHierarchyId? CHierarchyid { get; set; } + }; + public async Task InsertMssqlSpatialTypesAsync(InsertMssqlSpatialTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_geography", args.CGeography); + queryParams.Add("c_geometry", args.CGeometry); + queryParams.Add("c_hierarchyid", args.CHierarchyid); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlSpatialTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlSpatialTypesSql, queryParams, transaction: this.Transaction); + } + + private const string GetMssqlSpatialTypesSql = "SELECT TOP 1 c_geography, c_geometry, c_hierarchyid FROM mssql_spatial_types;"; + public class GetMssqlSpatialTypesRow + { + public SqlGeography CGeography { get; set; } + public SqlGeometry CGeometry { get; set; } + public SqlHierarchyId? CHierarchyid { get; set; } + }; + public async Task GetMssqlSpatialTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlSpatialTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlSpatialTypesSql, transaction: this.Transaction); + } + + private const string TruncateMssqlSpatialTypesSql = "TRUNCATE TABLE mssql_spatial_types;"; + public async Task TruncateMssqlSpatialTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlSpatialTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlSpatialTypesSql, transaction: this.Transaction); + } + + private const string InsertMssqlNodaTypesSql = @" + INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);"; + public class InsertMssqlNodaTypesArgs + { + public Instant? CDatetime2Instant { get; set; } + public Instant? CDatetimeoffsetInstant { get; set; } + }; + public async Task InsertMssqlNodaTypesAsync(InsertMssqlNodaTypesArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("c_datetime2_instant", args.CDatetime2Instant is null ? null : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc()); + queryParams.Add("c_datetimeoffset_instant", args.CDatetimeoffsetInstant is null ? null : (DateTimeOffset? )args.CDatetimeoffsetInstant.Value.ToDateTimeOffset()); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(InsertMssqlNodaTypesSql, queryParams); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(InsertMssqlNodaTypesSql, queryParams, transaction: this.Transaction); + } + + private const string InsertMssqlNodaTypesBatchSql = "INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);"; + public class InsertMssqlNodaTypesBatchArgs + { + public Instant? CDatetime2Instant { get; set; } + public Instant? CDatetimeoffsetInstant { get; set; } + }; + public async Task InsertMssqlNodaTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_datetime2_instant", typeof(object)); + dataTable.Columns.Add("c_datetimeoffset_instant", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CDatetime2Instant is null ? (object)DBNull.Value : (DateTime? )row.CDatetime2Instant.Value.ToDateTimeUtc(), row.CDatetimeoffsetInstant is null ? (object)DBNull.Value : (DateTimeOffset? )row.CDatetimeoffsetInstant.Value.ToDateTimeOffset()); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_noda_types]"; + bulkCopy.ColumnMappings.Add("c_datetime2_instant", "c_datetime2_instant"); + bulkCopy.ColumnMappings.Add("c_datetimeoffset_instant", "c_datetimeoffset_instant"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlNodaTypesSql = "SELECT TOP 1 c_datetime2_instant, c_datetimeoffset_instant FROM mssql_noda_types;"; + public class GetMssqlNodaTypesRow + { + public Instant? CDatetime2Instant { get; set; } + public Instant? CDatetimeoffsetInstant { get; set; } + }; + public async Task GetMssqlNodaTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesSql, transaction: this.Transaction); + } + + private const string GetMssqlNodaTypesCntSql = @"SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant + FROM mssql_noda_types + GROUP BY c_datetime2_instant, c_datetimeoffset_instant;"; + public class GetMssqlNodaTypesCntRow + { + public int Cnt { get; set; } + public Instant? CDatetime2Instant { get; set; } + public Instant? CDatetimeoffsetInstant { get; set; } + }; + public async Task GetMssqlNodaTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesCntSql); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesCntSql, transaction: this.Transaction); + } + + private const string TruncateMssqlNodaTypesSql = "TRUNCATE TABLE mssql_noda_types;"; + public async Task TruncateMssqlNodaTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(TruncateMssqlNodaTypesSql); + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + await this.Transaction.Connection.ExecuteAsync(TruncateMssqlNodaTypesSql, transaction: this.Transaction); + } + } +} \ No newline at end of file diff --git a/examples/MssqlDapperLegacyExample/Utils.cs b/examples/MssqlDapperLegacyExample/Utils.cs new file mode 100644 index 000000000..5e8c04c3a --- /dev/null +++ b/examples/MssqlDapperLegacyExample/Utils.cs @@ -0,0 +1,63 @@ +// auto-generated by sqlc - do not edit +namespace MssqlDapperLegacyExampleGen +{ + using Dapper; + using Microsoft.Data.SqlClient; + using Microsoft.SqlServer.Types; + using NodaTime; + using NodaTime.Extensions; + using System; + using System.Data; + using System.Linq; + + public static class Utils + { + private class NodaInstantTypeHandler : SqlMapper.TypeHandler + { + public override Instant Parse(object value) + { + if (value is DateTime dt) + return DateTime.SpecifyKind(dt, DateTimeKind.Utc).ToInstant(); + if (value is DateTimeOffset dto) + return dto.ToInstant(); + throw new DataException($"Cannot convert {value?.GetType()} to Instant"); + } + + public override void SetValue(IDbDataParameter parameter, Instant value) + { + parameter.Value = value.ToDateTimeUtc(); + parameter.DbType = DbType.DateTime2; + } + } + + public static void ConfigureSqlMapper() + { + SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2); + SqlMapper.AddTypeHandler(typeof(Instant), new NodaInstantTypeHandler()); + } + + public static string TransformQueryForSliceArgs(string originalSql, int sliceSize, string paramName) + { + var paramArgs = Enumerable.Range(0, sliceSize).Select(i => $"@{paramName}Arg{i}").ToList(); + return originalSql.Replace($"/*SLICE:{paramName}*/@{paramName}", string.Join(",", paramArgs)); + } + + public class BinaryParameter : SqlMapper.ICustomQueryParameter + { + private readonly byte[] _value; + public BinaryParameter(byte[] value) + { + _value = value; + } + + public void AddParameter(IDbCommand command, string name) + { + var parameter = new SqlParameter(name, SqlDbType.VarBinary) + { + Value = (object)_value ?? DBNull.Value + }; + command.Parameters.Add(parameter); + } + } + } +} \ No newline at end of file diff --git a/examples/MssqlDapperLegacyExample/request.json b/examples/MssqlDapperLegacyExample/request.json new file mode 100644 index 000000000..f11215452 --- /dev/null +++ b/examples/MssqlDapperLegacyExample/request.json @@ -0,0 +1,3670 @@ +{ + "settings": { + "version": "2", + "engine": "mssql", + "schema": [ + "examples/config/mssql/authors/schema.sql", + "examples/config/mssql/types/schema.sql" + ], + "queries": [ + "examples/config/mssql/authors/query.sql", + "examples/config/mssql/types/query.sql" + ], + "codegen": { + "out": "examples/MssqlDapperLegacyExample", + "plugin": "csharp", + "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxEYXBwZXJMZWdhY3lFeGFtcGxlR2VuIiwib3ZlcnJpZGVzIjpbeyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfaW50IiwiY3NoYXJwX3R5cGUiOnsibm90TnVsbCI6ZmFsc2UsInR5cGUiOiJpbnQifX0seyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfdmFyY2hhciIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOmZhbHNlLCJ0eXBlIjoic3RyaW5nIn19LHsiY29sdW1uIjoiR2V0TXNzcWxGdW5jdGlvbnM6bWF4X2RhdGV0aW1lMiIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOnRydWUsInR5cGUiOiJEYXRlVGltZSJ9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZTJfaW5zdGFudCIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOmZhbHNlLCJ0eXBlIjoiSW5zdGFudCJ9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZW9mZnNldF9pbnN0YW50IiwiY3NoYXJwX3R5cGUiOnsibm90TnVsbCI6ZmFsc2UsInR5cGUiOiJJbnN0YW50In19XSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0c3RhbmRhcmQyLjAiLCJ1c2VEYXBwZXIiOnRydWV9", + "process": { + "cmd": "./dist/LocalRunner" + } + } + }, + "catalog": { + "defaultSchema": "public", + "schemas": [ + { + "name": "public" + }, + { + "name": "public", + "tables": [ + { + "rel": { + "schema": "public", + "name": "authors" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "books" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "author_id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "description", + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "notes" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + } + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "columns": [ + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + } + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + } + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + } + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + } + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + } + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + } + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + } + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + } + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + } + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_string_types" + }, + "columns": [ + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + } + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + } + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + } + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + } + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "columns": [ + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + } + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + } + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + } + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + } + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + } + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_binary_types" + }, + "columns": [ + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + } + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + } + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + } + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "columns": [ + { + "name": "c_geography", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + } + }, + { + "name": "c_geometry", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + } + }, + { + "name": "c_hierarchyid", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_noda_types" + }, + "columns": [ + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + } + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + } + } + ] + } + ] + }, + { + "name": "sys" + }, + { + "name": "information_schema" + }, + { + "name": "extended", + "tables": [ + { + "rel": { + "schema": "extended", + "name": "bios" + }, + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] + } + ] + }, + "queries": [ + { + "text": "SELECT TOP 1 id, name, bio FROM authors\nWHERE name = @name;", + "name": "GetAuthor", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio\nFROM authors\nORDER BY name\nOFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;", + "name": "ListAuthors", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "offset", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + }, + { + "number": 2, + "column": { + "name": "limit", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);", + "name": "CreateAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);", + "name": "CreateAuthorReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment\nWHERE id = @id;", + "name": "GetAuthorById", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(@name_pattern, '%');", + "name": "GetAuthorByNamePattern", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name_pattern", + "length": -1, + "isNamedParam": true, + "type": {} + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors\nWHERE name = @name;", + "name": "DeleteAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors;", + "name": "DeleteAllAuthors", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "UPDATE authors\nSET bio = @bio\nWHERE bio IS NOT NULL;", + "name": "UpdateAuthors", + "cmd": ":execrows", + "parameters": [ + { + "number": 1, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);", + "name": "GetAuthorsByIds", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);", + "name": "GetAuthorsByIdsAndNames", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "names", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "isSqlcSlice": true, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);", + "name": "CreateBook", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "author_id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "author_id" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "books" + } + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nORDER BY authors.name;", + "name": "ListAllAuthorsBooks", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio\nFROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name\nWHERE authors1.id \u003c authors2.id;", + "name": "GetDuplicateAuthors", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nWHERE books.name = @name;", + "name": "GetAuthorsByBookName", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);", + "name": "CreateNoteReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "body", + "notNull": true, + "length": 200, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "notes" + } + }, + { + "text": "SELECT id, body FROM notes WHERE id = @id;", + "name": "GetNote", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE notes;", + "name": "TruncateNotes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);", + "name": "CreateExtendedBio", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "author_name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "schema": "extended", + "name": "bios" + } + }, + { + "text": "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;", + "name": "GetFirstExtendedBioByType", + "cmd": ":one", + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE extended.bios;", + "name": "TruncateExtendedBios", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", + "name": "InsertMssqlNumericTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_bit", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + } + }, + { + "number": 2, + "column": { + "name": "c_tinyint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + } + }, + { + "number": 3, + "column": { + "name": "c_smallint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + } + }, + { + "number": 4, + "column": { + "name": "c_int", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + } + }, + { + "number": 5, + "column": { + "name": "c_bigint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + } + }, + { + "number": 6, + "column": { + "name": "c_real", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + } + }, + { + "number": 7, + "column": { + "name": "c_float", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + } + }, + { + "number": 8, + "column": { + "name": "c_decimal", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + } + }, + { + "number": 9, + "column": { + "name": "c_numeric", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + } + }, + { + "number": 10, + "column": { + "name": "c_money", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + } + }, + { + "number": 11, + "column": { + "name": "c_smallmoney", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + } + ], + "comments": [ + " Numeric types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_numeric_types" + } + }, + { + "text": "INSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", + "name": "InsertMssqlNumericTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_bit", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + } + }, + { + "number": 2, + "column": { + "name": "c_tinyint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + } + }, + { + "number": 3, + "column": { + "name": "c_smallint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + } + }, + { + "number": 4, + "column": { + "name": "c_int", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + } + }, + { + "number": 5, + "column": { + "name": "c_bigint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + } + }, + { + "number": 6, + "column": { + "name": "c_real", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + } + }, + { + "number": 7, + "column": { + "name": "c_float", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + } + }, + { + "number": 8, + "column": { + "name": "c_decimal", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + } + }, + { + "number": 9, + "column": { + "name": "c_numeric", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + } + }, + { + "number": 10, + "column": { + "name": "c_money", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + } + }, + { + "number": 11, + "column": { + "name": "c_smallmoney", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_numeric_types" + } + }, + { + "text": "SELECT TOP 1 c_bit, c_tinyint, c_smallint, c_int, c_bigint, c_real, c_float, c_decimal, c_numeric, c_money, c_smallmoney FROM mssql_numeric_types;", + "name": "GetMssqlNumericTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\nFROM mssql_numeric_types\nGROUP BY\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney;", + "name": "GetMssqlNumericTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_numeric_types;", + "name": "TruncateMssqlNumericTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_string_types\n(\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\n)\nVALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);", + "name": "InsertMssqlStringTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_char", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + } + }, + { + "number": 2, + "column": { + "name": "c_nchar", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + } + }, + { + "number": 3, + "column": { + "name": "c_varchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + } + }, + { + "number": 4, + "column": { + "name": "c_nvarchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + } + }, + { + "number": 5, + "column": { + "name": "c_varchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + } + }, + { + "number": 6, + "column": { + "name": "c_nvarchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + } + ], + "comments": [ + " String types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_string_types" + } + }, + { + "text": "INSERT INTO mssql_string_types\n(\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\n)\nVALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);", + "name": "InsertMssqlStringTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_char", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + } + }, + { + "number": 2, + "column": { + "name": "c_nchar", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + } + }, + { + "number": 3, + "column": { + "name": "c_varchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + } + }, + { + "number": 4, + "column": { + "name": "c_nvarchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + } + }, + { + "number": 5, + "column": { + "name": "c_varchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + } + }, + { + "number": 6, + "column": { + "name": "c_nvarchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_string_types" + } + }, + { + "text": "SELECT TOP 1 c_char, c_nchar, c_varchar, c_nvarchar, c_varchar_max, c_nvarchar_max FROM mssql_string_types;", + "name": "GetMssqlStringTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\nFROM mssql_string_types\nGROUP BY\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max;", + "name": "GetMssqlStringTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_string_types;", + "name": "TruncateMssqlStringTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_datetime_types\n(\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\n)\nVALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);", + "name": "InsertMssqlDatetimeTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_date", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + } + }, + { + "number": 2, + "column": { + "name": "c_datetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + } + }, + { + "number": 3, + "column": { + "name": "c_datetime2", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + } + }, + { + "number": 4, + "column": { + "name": "c_smalldatetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + } + }, + { + "number": 5, + "column": { + "name": "c_datetimeoffset", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + } + }, + { + "number": 6, + "column": { + "name": "c_time", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + } + ], + "comments": [ + " Datetime types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_datetime_types" + } + }, + { + "text": "INSERT INTO mssql_datetime_types\n(\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\n)\nVALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);", + "name": "InsertMssqlDatetimeTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_date", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + } + }, + { + "number": 2, + "column": { + "name": "c_datetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + } + }, + { + "number": 3, + "column": { + "name": "c_datetime2", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + } + }, + { + "number": 4, + "column": { + "name": "c_smalldatetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + } + }, + { + "number": 5, + "column": { + "name": "c_datetimeoffset", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + } + }, + { + "number": 6, + "column": { + "name": "c_time", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_datetime_types" + } + }, + { + "text": "SELECT TOP 1 c_date, c_datetime, c_datetime2, c_smalldatetime, c_datetimeoffset, c_time FROM mssql_datetime_types;", + "name": "GetMssqlDatetimeTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\nFROM mssql_datetime_types\nGROUP BY\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time;", + "name": "GetMssqlDatetimeTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_datetime_types;", + "name": "TruncateMssqlDatetimeTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_binary_types\n(\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\n)\nVALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);", + "name": "InsertMssqlBinaryTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_binary", + "length": 3, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + } + }, + { + "number": 2, + "column": { + "name": "c_varbinary", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + } + }, + { + "number": 3, + "column": { + "name": "c_varbinary_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + } + }, + { + "number": 4, + "column": { + "name": "c_uniqueidentifier", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + } + ], + "comments": [ + " Binary types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_binary_types" + } + }, + { + "text": "INSERT INTO mssql_binary_types\n(\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\n)\nVALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);", + "name": "InsertMssqlBinaryTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_binary", + "length": 3, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + } + }, + { + "number": 2, + "column": { + "name": "c_varbinary", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + } + }, + { + "number": 3, + "column": { + "name": "c_varbinary_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + } + }, + { + "number": 4, + "column": { + "name": "c_uniqueidentifier", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_binary_types" + } + }, + { + "text": "SELECT TOP 1 c_binary, c_varbinary, c_varbinary_max, c_uniqueidentifier FROM mssql_binary_types;", + "name": "GetMssqlBinaryTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\nFROM mssql_binary_types\nGROUP BY\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier;", + "name": "GetMssqlBinaryTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_binary_types;", + "name": "TruncateMssqlBinaryTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nSELECT\n MAX(c_int) AS max_int,\n MAX(c_varchar) AS max_varchar,\n MAX(c_datetime2) AS max_datetime2\nFROM mssql_numeric_types\nCROSS JOIN mssql_string_types\nCROSS JOIN mssql_datetime_types;", + "name": "GetMssqlFunctions", + "cmd": ":one", + "columns": [ + { + "name": "max_int", + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "max_varchar", + "length": -1, + "type": { + "name": "varchar" + } + }, + { + "name": "max_datetime2", + "length": -1, + "type": { + "name": "datetime2" + } + } + ], + "comments": [ + " Functions " + ], + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid);", + "name": "InsertMssqlSpatialTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_geography", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + }, + "originalName": "c_geography" + } + }, + { + "number": 2, + "column": { + "name": "c_geometry", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + }, + "originalName": "c_geometry" + } + }, + { + "number": 3, + "column": { + "name": "c_hierarchyid", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + }, + "originalName": "c_hierarchyid" + } + } + ], + "comments": [ + " Spatial and hierarchyid types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_spatial_types" + } + }, + { + "text": "SELECT TOP 1 c_geography, c_geometry, c_hierarchyid FROM mssql_spatial_types;", + "name": "GetMssqlSpatialTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_geography", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + }, + "originalName": "c_geography" + }, + { + "name": "c_geometry", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + }, + "originalName": "c_geometry" + }, + { + "name": "c_hierarchyid", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + }, + "originalName": "c_hierarchyid" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_spatial_types;", + "name": "TruncateMssqlSpatialTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);", + "name": "InsertMssqlNodaTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_datetime2_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + } + }, + { + "number": 2, + "column": { + "name": "c_datetimeoffset_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + } + ], + "comments": [ + " NodaTime Instant overrides " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_noda_types" + } + }, + { + "text": "INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);", + "name": "InsertMssqlNodaTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_datetime2_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + } + }, + { + "number": 2, + "column": { + "name": "c_datetimeoffset_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_noda_types" + } + }, + { + "text": "SELECT TOP 1 c_datetime2_instant, c_datetimeoffset_instant FROM mssql_noda_types;", + "name": "GetMssqlNodaTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant\nFROM mssql_noda_types\nGROUP BY c_datetime2_instant, c_datetimeoffset_instant;", + "name": "GetMssqlNodaTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_noda_types;", + "name": "TruncateMssqlNodaTypes", + "cmd": ":exec", + "filename": "query.sql" + } + ], + "sqlc_version": "v1.31.1", + "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0c3RhbmRhcmQyLjAiLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxEYXBwZXJMZWdhY3lFeGFtcGxlR2VuIiwidXNlRGFwcGVyIjp0cnVlLCJvdmVycmlkZURhcHBlclZlcnNpb24iOiIiLCJvdmVycmlkZXMiOlt7ImNvbHVtbiI6IkdldE1zc3FsRnVuY3Rpb25zOm1heF9pbnQiLCJjc2hhcnBfdHlwZSI6eyJ0eXBlIjoiaW50Iiwibm90TnVsbCI6ZmFsc2V9fSx7ImNvbHVtbiI6IkdldE1zc3FsRnVuY3Rpb25zOm1heF92YXJjaGFyIiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6InN0cmluZyIsIm5vdE51bGwiOmZhbHNlfX0seyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfZGF0ZXRpbWUyIiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6IkRhdGVUaW1lIiwibm90TnVsbCI6dHJ1ZX19LHsiY29sdW1uIjoiKjpjX2RhdGV0aW1lMl9pbnN0YW50IiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6Ikluc3RhbnQiLCJub3ROdWxsIjpmYWxzZX19LHsiY29sdW1uIjoiKjpjX2RhdGV0aW1lb2Zmc2V0X2luc3RhbnQiLCJjc2hhcnBfdHlwZSI6eyJ0eXBlIjoiSW5zdGFudCIsIm5vdE51bGwiOmZhbHNlfX1dLCJkZWJ1Z1JlcXVlc3QiOmZhbHNlLCJ1c2VDZW50cmFsUGFja2FnZU1hbmFnZW1lbnQiOmZhbHNlLCJ3aXRoQXN5bmNTdWZmaXgiOnRydWUsIndpdGhDYW5jZWxsYXRpb25Ub2tlbiI6ZmFsc2UsInVzZUFzeW5jRW51bWVyYWJsZSI6ZmFsc2UsInVzZVByb3BlclNpbmd1bGFyaXphdGlvbiI6ZmFsc2V9" +} \ No newline at end of file diff --git a/examples/MssqlDapperLegacyExample/request.message b/examples/MssqlDapperLegacyExample/request.message new file mode 100644 index 0000000000000000000000000000000000000000..f0dd98aa59a4f344072c3187b669b83985c2984e GIT binary patch literal 31326 zcmeG_U2I(0S-s=m9OuV-;|=L$7j7?X*BPaWGl06--ERCdN!&PN@7Uh#HniUH%(XMk zc*dDK-d%f}QYb2dN<0{Wv=4o#0Kp3qAkp#wPY88+K}ZM?s(1i|)IRZB0jkjQedq6f z=l;x{Nt_a;j)dU%gUdnnuD?^cgf%~!T(h&k3ys5i-iyig6vX>`etRDQdM_kb6aaO#&G&}b zM|90wIOv7S?ylcn^0zD1{h}DYJzNd$R@%GHUk|?P)%?x9?XnLbf{r)cY477>N|d+3vhu+Ipzguum%kx(DT#P4GjyB5OgYiAYxJ0 zH<3YMwzjqczw-!yKLc2=w731vwKlBz+pYFJSi+{?!GYE)?ONf|6rf-)@CB6#N_mi< zVD_cUwR+IGyws{z8s)tv(3Ac1&)dVhdz+1V)f?5Ne|J>T`74V(qaQ{j3)nx+a`oDI zYw82_&nm}jZH_v_oAqrVjYDgc$)f!UHi$HfjT0L&XS6A)~H8BPgo6jfN34zm% zC_ZC=60v!cjR7}T+x1=KTZ)%hdt)GpSoxwwu>zd#2lkb&SmAXxhnxYv$U_YO+JKME zR&5_v_h<(cIhd+x03JL4iHThN>Hz^$wkD_a-<# zXmDtaAn|m@ExW`{0JwT{ABNipoDA2hWLFimI(FmAF@L=|#iAG0KM#uV2Aup%3-ZnXVMqYqHS&LFlx_U8ovbAydk zH?|tBNwxzuyPQD-uy(TUp>>w2GDw8nc^m#46yPMO z9RWB#&z)A&-|quoFi+lu&y$y6p10XaNnHJcJLxb^UJ~YMMjm12|2Dv$(?ZY|ye9Om zWVKgt$x&TTv2xd%o3PMW4~du;I9?eLF)VhoCkQZgUh&X6#Y9QMxPi+o6~a_4V;r!` zM0cn%hg1W004`O!B@>h(r^XE>k~?3sUuUQEur@%Y&{gh^D>_D-kueJ0WZz+@!(sLZ z+8A@9i2t=g_9J@DC&Zd7V@Iy~HC%OkN2iq02nhh2a8Abp&}|vftzh>93ud93ajB>t z0OkrC3t<+fM#@>^8govl=7mG+85TZqq8PC&?2P0tRj;3+o^eib4pVKh3-b*OdV`IJ zgBI3b4DvQXSSYN?ITZ^6g!4fzE(B@Zh^b?22?9bxz>6e+@ z(gpYg3%df~k{RzZySlO0tb->8Zd0>U->Umv+HO~N?{-%!M#D|OqHc!rHGBf<ws}mUnm~a| zRV&V=>H$E08fD{MKomNA`}JBMP)|8yq#-&0^cm3fw-yVXlW}PvwrUlPqq8Be6_k?& ze9@&5;zzMsvXH2<^C48zf6D;fI~RLH_CT;7coVi=Z*H~PJFxVv=7tDMc;B+@QU9He z->msH=PxWaP96urBMR)RY)pC*(!HvcW0f`y1#vo5YG47?z^PN7S>H`xW}`3&z6Mhh zS^Y+p>4fv&IX1IeTq@45xogWS?iIIQyXfLHF1i5Ly;fenuZxx7#;yK`@FytCzN*H(*b z?p47FZh86ks(Y=tHh;q{6<>$vLX($BWvZSMsxNHY>1;Pqlu_Q7w%g*_Q}Q4YO%cLE|%7o6@R#s#xh=T zUz=T8FRr?iSMfdP7x)9Dc45XoIS<}h$CsQlH0{6RSM%3lBCo{QCz;~3s~BM*2lxqy zHg}Xnn_nLtu$Q7tl{_vY=DM&IMg>{~rE_4JODW@gC&z9iVpmJ03-0pz+R8d&9~Ysx zaH(Dkku9o8JWo(Om;KIOySZ3%#yGwkm7r6vJt8+8wSWLz=e~cq{*?i?)}!=yKlU*f zqEa2VjsI(Hxe##i-D+!R2W&H`8ekbfG1O}__9?9v=Jo;MPo-)&Dt0A&;CwH~mXmZs zsv=%myjgVTmuHuXtMkQ4E@U>qbnW=<=0*3#7hVKebB?S<36%DV`1vW-2A4sIKF>~~ z6i|pDMlfiE4>`t<6#MU4?8OBzhSrLy_|}Hc$pydRMM8eQ!>~T4cMkCbn zs7>g+{=i~CxxTV63-gjvmZ24a1|G}@{z(4C7mKTIX?e{ptuHMB8?W!8d9EOz;BvFw zdOH9yWj$Uo<$P&?&1JHJD{N8_Jc}iF@^XH4X>qW3mLHQTJA zxH?|3b`b7kI&;_)icHMlh5LzMW%bYLoG8J9iT6U6djP zcF}sW%Y*>J+A}s2*4Z3v0-842Ow0Yr@XK6T^?x&VPgz`r2a!Kh4w5(s29cWNY`=n}s*yD_SmiwAo z5zXPSl$CLTDFdHqS^&|!(sSx%BR3-2f19uYB54!6CUSr0#h3qduszb70{!?9b08M`o3P7Yls0r&d2vbDI5+M$F z!bCKP22Tr(4WMn|1-w^B*!SDCM1NhN#?>oo)dOa7G;)}dQ>@9>KXE(R8eF{sV7WUQe~cS zq#k7Ikb0D9Lh6y{*Qkdugf-Fme*n8ZmPVjh_o=+>($zE~efGF4g?cCZ_p1|2oG0 zICm`iA3^j#mm>P-DxK8_DK!7PuaaW$!a7)^Qf>-sGakkzm@wS&&mKVQ)Rr z{LO-vW&C=#63pE!py`6ndo{_x&4Sm}y!Bo;EZQtsrhw^c&kD$r`1?87f|hbu(IwEp zd5P?jWDUKmwd(n)25J`Ee#}!MjGXT=_MbT#XyP%1`Q`P}+GPHMJG<&uX}CZ~4xW%0 z15eXT5L@hed3k*WQZ}E|=!n>AkIB{fW=F>0KEeh7^F|^;B#}}Go`*RL7>ipROJ9gX z982GR&k|-v6ON@XKLlh12dMNdvmcAZiW6iR1ngDBI3_O=c6WYnguyA1czY;{S2<8SE$1x$ZLs1VRSN4)w~ zemQ~>&&7qbUZiO6-6VMT0uHi4i?)!HTcw^y8tK-ICdvXG{;Rg!7_~SttK<7KC?qx||`+ z?4M3MauhZBe2NpPRXFVm(o6{VO*jcBjh){aVSi&ir5yXi$s8LC!yU6O7r4i)8{sD6 zTRnc(CAxVonN@#xh~1;^5=2o@MeqoVDoCNr-;uH`a-E3*8S+fSFH}K^Of;_bskRfr zm+PUT%TO5L%j4Km)SP#dsJ0A9nTpL@HFQ}9I3nvqWfj+C-fdHLWk978oM@&5#pP|M zE2@l6$k0s{l&Ge>dtFpgMo)A)5#~r%NQ8==Zx6E%jHqHa|DF@D^q7y*8YEsirS7Pc z{+PNKr*EQ6)R(7y%xd*&lI22ARO8l>l4(DXthU0uL}!kY{EU6x@a3pfk0TYKe2asw!TTVS04DCv?v=0Qyh}vwISMm(Z~Z zf(EzlJTVd%U>5k=aO{2t9J>|NcL>ll39{}$0p*8{Q2F6sp0$Amu(af>{4S*Hyoo9X z-(%nX^#B4|HAE&*Wu8N$9-ue!6ZD8lJLYXJVBTcSgH9i*+J61Fg=VRcBjNa?0%WlyJDPJRY(aOhHNIF1itPg zJq!Q{3hN?4;a?s!zydejN06xSsW9#?NNP&(1J5~$w+}i0Ji`7l!4p(e^wco?^Jxqp z^9zqTA9IF`OInUO-xn(6$>e-ue$S=x`QHz*cEpWD9!Hl^@j5MoMwPquNxv$6Dg+Pp zB~@)9H)iZ-)n5LB6isw9zm%X~Gc{7WcJ)4+qERzp_NGr^Tc0Yp9xngtOM80lZn`pa z4iBX%z57WzF|)5n(TK2#sC8dvN8L3ap?l}6BkU_ZZ!05}DqB-gZ1nk|?N;J@D%ets zMz)9_=P1C!P2{}3oX}(b6c=0zJL{M~?>Drr5EPBuSzRns%AafO#}j>3lSO81$M9Ac z46PCOSO0g2eIv6J;D-+qZ-f-*67Z*d6P!@4()8GqH!zJp zK&9#GXcp>+r0XKlA=DM}U$(#pz2EHu$;=rVy+?1N#G4+&!2#3C%g3Yj^1qIr=@&9? zn#>$a!d;UG371!<-9MR7754E29VGZ~dUJrThV$Ja_Vonsjn;W$$)E1iiD`54kNIQA ze1N&Ia7+!ER&x5&%?Ctpgqvb?{?2a=>RS}U%w@z6x-DAb7!PU}EGmep_d@$1J7I)6 zWv<*ZmCHvUmAyL;e@}0w6!u!(WNyajABNJr&u7Tij6NStvJ&cX{%nZ-UC*0QV)Y-< zJ|NaVrKaf4(^2$AwwVZp_GRH8)A2sag%r!-$)V%4ogPYJ(VZSb6A4LszdXSH)Pf7) z@DgCY0efrq^+|rhgXdraQt-^IU(d=gZ5h ztKwoEk&8&TuUPC?1yQtyYaf)Kt3ReBW%LVdM9zY2uIP+r_0bOR3LC>}ayGPTxZ%#v zLdzPEw)=5*3Y(~r6lf!mm-;=w`&&PaTZJ)K50akAbpCvhJxEHRq}P$VusZryanx+; z0&pyoJG{w_8Ab!}LBbaBK%1E7)>lQhhBMLuXQY~TA3B)Iu|sU;<>i1CJ$BSr`XPEf zI(iKHM>uJo6DQ5p$O)#iJB6s8YKOaIs?2xE)PvY1Q&0IXfQND~{WkUNKF#~o56pYn zG0!A&y}DVU@T0Coz6+n0a>)`Ucm(<&uYqdkHwM_PNT3&{C+@KCLN?S4G60hESaNJM zgj-3Yi6f&3ahOW25Fvo99(r4Tk7te&O!v`4pNmZmle9eS2nU7(^gQf6=*FpC`#TCgC3tuKa)7;OxRr1j zw|Rb%$kZONZWzZ}T5F%NKVU>*>&5EJDg1$4(k135?oUuAIA`olXynGdD{thjE0=!a z%B3s$uMNMeKLWeZuEXbGU-R1md}szfAqpQ2P5#Dg4__O_``Ex%=E~rNd#A#bx|@RJAM;IzXjxZwtnlFf067Sj*8l(j literal 0 HcmV?d00001 diff --git a/examples/MssqlExample/Models.cs b/examples/MssqlExample/Models.cs index 90ea67aa9..814d56e6e 100644 --- a/examples/MssqlExample/Models.cs +++ b/examples/MssqlExample/Models.cs @@ -1,11 +1,18 @@ // auto-generated by sqlc - do not edit +using Microsoft.SqlServer.Types; +using NodaTime; +using NodaTime.Extensions; using System; using System.Linq; namespace MssqlExampleGen; public readonly record struct Author(long Id, string Name, string? Bio); public readonly record struct Book(long Id, string Name, long AuthorId, string? Description); +public readonly record struct Note(int Id, string Body); public readonly record struct MssqlNumericType(bool? CBit, byte? CTinyint, short? CSmallint, int? CInt, long? CBigint, float? CReal, double? CFloat, decimal? CDecimal, decimal? CNumeric, decimal? CMoney, decimal? CSmallmoney); public readonly record struct MssqlStringType(string? CChar, string? CNchar, string? CVarchar, string? CNvarchar, string? CVarcharMax, string? CNvarcharMax); public readonly record struct MssqlDatetimeType(DateTime? CDate, DateTime? CDatetime, DateTime? CDatetime2, DateTime? CSmalldatetime, DateTimeOffset? CDatetimeoffset, TimeSpan? CTime); -public readonly record struct MssqlBinaryType(byte[]? CBinary, byte[]? CVarbinary, byte[]? CVarbinaryMax, Guid? CUniqueidentifier); \ No newline at end of file +public readonly record struct MssqlBinaryType(byte[]? CBinary, byte[]? CVarbinary, byte[]? CVarbinaryMax, Guid? CUniqueidentifier); +public readonly record struct MssqlSpatialType(SqlGeography? CGeography, SqlGeometry? CGeometry, SqlHierarchyId? CHierarchyid); +public readonly record struct MssqlNodaType(DateTime? CDatetime2Instant, DateTimeOffset? CDatetimeoffsetInstant); +public readonly record struct ExtendedBio(string AuthorName, string Name, string? BioType); \ No newline at end of file diff --git a/examples/MssqlExample/MssqlExample.csproj b/examples/MssqlExample/MssqlExample.csproj index c503210c6..6ffb21ee5 100644 --- a/examples/MssqlExample/MssqlExample.csproj +++ b/examples/MssqlExample/MssqlExample.csproj @@ -14,7 +14,9 @@ - + + + \ No newline at end of file diff --git a/examples/MssqlExample/QuerySql.cs b/examples/MssqlExample/QuerySql.cs index e3113d470..5861a3027 100644 --- a/examples/MssqlExample/QuerySql.cs +++ b/examples/MssqlExample/QuerySql.cs @@ -5,6 +5,9 @@ // ReSharper disable NotAccessedPositionalProperty.Global // ReSharper disable UnusedAutoPropertyAccessor.Global using Microsoft.Data.SqlClient; +using Microsoft.SqlServer.Types; +using NodaTime; +using NodaTime.Extensions; using System; using System.Collections.Generic; using System.Data; @@ -640,6 +643,231 @@ public async Task> GetAuthorsByBookNameAsync(GetAu } } + private const string CreateNoteReturnIdSql = "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);"; + public readonly record struct CreateNoteReturnIdRow(int Id); + public readonly record struct CreateNoteReturnIdArgs(string Body); + public async Task CreateNoteReturnIdAsync(CreateNoteReturnIdArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateNoteReturnIdSql, connection)) + { + command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt32(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateNoteReturnIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt32(result); + } + } + + private const string GetNoteSql = "SELECT id, body FROM notes WHERE id = @id;"; + public readonly record struct GetNoteRow(int Id, string Body); + public readonly record struct GetNoteArgs(int Id); + public async Task GetNoteAsync(GetNoteArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetNoteSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetNoteRow + { + Id = reader.GetInt32(0), + Body = reader.GetString(1) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetNoteSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetNoteRow + { + Id = reader.GetInt32(0), + Body = reader.GetString(1) + }; + } + } + } + + return null; + } + + private const string TruncateNotesSql = "TRUNCATE TABLE notes;"; + public async Task TruncateNotesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateNotesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateNotesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string CreateExtendedBioSql = "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);"; + public readonly record struct CreateExtendedBioArgs(string AuthorName, string Name, string? BioType); + public async Task CreateExtendedBioAsync(CreateExtendedBioArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateExtendedBioSql, connection)) + { + command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateExtendedBioSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string GetFirstExtendedBioByTypeSql = "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;"; + public readonly record struct GetFirstExtendedBioByTypeRow(string AuthorName, string Name, string? BioType); + public readonly record struct GetFirstExtendedBioByTypeArgs(string? BioType); + public async Task GetFirstExtendedBioByTypeAsync(GetFirstExtendedBioByTypeArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetFirstExtendedBioByTypeSql, connection)) + { + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetFirstExtendedBioByTypeRow + { + AuthorName = reader.GetString(0), + Name = reader.GetString(1), + BioType = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetFirstExtendedBioByTypeSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetFirstExtendedBioByTypeRow + { + AuthorName = reader.GetString(0), + Name = reader.GetString(1), + BioType = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string TruncateExtendedBiosSql = "TRUNCATE TABLE extended.bios;"; + public async Task TruncateExtendedBiosAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateExtendedBiosSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateExtendedBiosSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + private const string InsertMssqlNumericTypesSql = @" INSERT INTO mssql_numeric_types ( @@ -727,17 +955,17 @@ public async Task InsertMssqlNumericTypesBatchAsync(List GetMssqlFunctionsAsync() { if (this.Transaction == null) @@ -1690,7 +1918,7 @@ CROSS JOIN mssql_string_types { MaxInt = reader.IsDBNull(0) ? null : reader.GetInt32(0), MaxVarchar = reader.IsDBNull(1) ? null : reader.GetString(1), - MaxDatetime2 = reader.IsDBNull(2) ? null : reader.GetDateTime(2) + MaxDatetime2 = reader.GetDateTime(2) }; } } @@ -1713,7 +1941,287 @@ CROSS JOIN mssql_string_types { MaxInt = reader.IsDBNull(0) ? null : reader.GetInt32(0), MaxVarchar = reader.IsDBNull(1) ? null : reader.GetString(1), - MaxDatetime2 = reader.IsDBNull(2) ? null : reader.GetDateTime(2) + MaxDatetime2 = reader.GetDateTime(2) + }; + } + } + } + + return null; + } + + private const string InsertMssqlSpatialTypesSql = @" + INSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid);"; + public readonly record struct InsertMssqlSpatialTypesArgs(SqlGeography? CGeography, SqlGeometry? CGeometry, SqlHierarchyId? CHierarchyid); + public async Task InsertMssqlSpatialTypesAsync(InsertMssqlSpatialTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlSpatialTypesSql, connection)) + { + command.Parameters.Add(new SqlParameter("@c_geography", SqlDbType.Udt) { UdtTypeName = "geography", Value = args.CGeography ?? (object)DBNull.Value }); + command.Parameters.Add(new SqlParameter("@c_geometry", SqlDbType.Udt) { UdtTypeName = "geometry", Value = args.CGeometry ?? (object)DBNull.Value }); + command.Parameters.Add(new SqlParameter("@c_hierarchyid", SqlDbType.Udt) { UdtTypeName = "hierarchyid", Value = args.CHierarchyid ?? (object)DBNull.Value }); + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlSpatialTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add(new SqlParameter("@c_geography", SqlDbType.Udt) { UdtTypeName = "geography", Value = args.CGeography ?? (object)DBNull.Value }); + command.Parameters.Add(new SqlParameter("@c_geometry", SqlDbType.Udt) { UdtTypeName = "geometry", Value = args.CGeometry ?? (object)DBNull.Value }); + command.Parameters.Add(new SqlParameter("@c_hierarchyid", SqlDbType.Udt) { UdtTypeName = "hierarchyid", Value = args.CHierarchyid ?? (object)DBNull.Value }); + await command.ExecuteNonQueryAsync(); + } + } + + private const string GetMssqlSpatialTypesSql = "SELECT TOP 1 c_geography, c_geometry, c_hierarchyid FROM mssql_spatial_types;"; + public readonly record struct GetMssqlSpatialTypesRow(SqlGeography? CGeography, SqlGeometry? CGeometry, SqlHierarchyId? CHierarchyid); + public async Task GetMssqlSpatialTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlSpatialTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlSpatialTypesRow + { + CGeography = reader.IsDBNull(0) ? null : SqlGeography.Deserialize(reader.GetSqlBytes(0)), + CGeometry = reader.IsDBNull(1) ? null : SqlGeometry.Deserialize(reader.GetSqlBytes(1)), + CHierarchyid = reader.IsDBNull(2) ? null : (new Func(b => + { + var id = new SqlHierarchyId(); + id.Read(new System.IO.BinaryReader(b.Stream)); + return id; + }))(reader.GetSqlBytes(2)) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlSpatialTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlSpatialTypesRow + { + CGeography = reader.IsDBNull(0) ? null : SqlGeography.Deserialize(reader.GetSqlBytes(0)), + CGeometry = reader.IsDBNull(1) ? null : SqlGeometry.Deserialize(reader.GetSqlBytes(1)), + CHierarchyid = reader.IsDBNull(2) ? null : (new Func(b => + { + var id = new SqlHierarchyId(); + id.Read(new System.IO.BinaryReader(b.Stream)); + return id; + }))(reader.GetSqlBytes(2)) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlSpatialTypesSql = "TRUNCATE TABLE mssql_spatial_types;"; + public async Task TruncateMssqlSpatialTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlSpatialTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlSpatialTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlNodaTypesSql = @" + INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);"; + public readonly record struct InsertMssqlNodaTypesArgs(Instant? CDatetime2Instant, Instant? CDatetimeoffsetInstant); + public async Task InsertMssqlNodaTypesAsync(InsertMssqlNodaTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlNodaTypesSql, connection)) + { + command.Parameters.Add("@c_datetime2_instant", SqlDbType.DateTime2).Value = args.CDatetime2Instant is null ? (object)DBNull.Value : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc(); + command.Parameters.Add("@c_datetimeoffset_instant", SqlDbType.DateTimeOffset).Value = args.CDatetimeoffsetInstant is null ? (object)DBNull.Value : (DateTimeOffset? )args.CDatetimeoffsetInstant.Value.ToDateTimeOffset(); + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlNodaTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_datetime2_instant", SqlDbType.DateTime2).Value = args.CDatetime2Instant is null ? (object)DBNull.Value : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc(); + command.Parameters.Add("@c_datetimeoffset_instant", SqlDbType.DateTimeOffset).Value = args.CDatetimeoffsetInstant is null ? (object)DBNull.Value : (DateTimeOffset? )args.CDatetimeoffsetInstant.Value.ToDateTimeOffset(); + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlNodaTypesBatchSql = "INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);"; + public readonly record struct InsertMssqlNodaTypesBatchArgs(Instant? CDatetime2Instant, Instant? CDatetimeoffsetInstant); + public async Task InsertMssqlNodaTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_datetime2_instant", typeof(object)); + dataTable.Columns.Add("c_datetimeoffset_instant", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CDatetime2Instant is null ? (object)DBNull.Value : (DateTime? )row.CDatetime2Instant.Value.ToDateTimeUtc(), row.CDatetimeoffsetInstant is null ? (object)DBNull.Value : (DateTimeOffset? )row.CDatetimeoffsetInstant.Value.ToDateTimeOffset()); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_noda_types]"; + bulkCopy.ColumnMappings.Add("c_datetime2_instant", "c_datetime2_instant"); + bulkCopy.ColumnMappings.Add("c_datetimeoffset_instant", "c_datetimeoffset_instant"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlNodaTypesSql = "SELECT TOP 1 c_datetime2_instant, c_datetimeoffset_instant FROM mssql_noda_types;"; + public readonly record struct GetMssqlNodaTypesRow(Instant? CDatetime2Instant, Instant? CDatetimeoffsetInstant); + public async Task GetMssqlNodaTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlNodaTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNodaTypesRow + { + CDatetime2Instant = reader.IsDBNull(0) ? null : DateTime.SpecifyKind(reader.GetDateTime(0), DateTimeKind.Utc).ToInstant(), + CDatetimeoffsetInstant = reader.IsDBNull(1) ? null : reader.GetFieldValue(1).ToInstant() + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlNodaTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNodaTypesRow + { + CDatetime2Instant = reader.IsDBNull(0) ? null : DateTime.SpecifyKind(reader.GetDateTime(0), DateTimeKind.Utc).ToInstant(), + CDatetimeoffsetInstant = reader.IsDBNull(1) ? null : reader.GetFieldValue(1).ToInstant() + }; + } + } + } + + return null; + } + + private const string GetMssqlNodaTypesCntSql = @"SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant + FROM mssql_noda_types + GROUP BY c_datetime2_instant, c_datetimeoffset_instant;"; + public readonly record struct GetMssqlNodaTypesCntRow(int Cnt, Instant? CDatetime2Instant, Instant? CDatetimeoffsetInstant); + public async Task GetMssqlNodaTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlNodaTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNodaTypesCntRow + { + Cnt = reader.GetInt32(0), + CDatetime2Instant = reader.IsDBNull(1) ? null : DateTime.SpecifyKind(reader.GetDateTime(1), DateTimeKind.Utc).ToInstant(), + CDatetimeoffsetInstant = reader.IsDBNull(2) ? null : reader.GetFieldValue(2).ToInstant() + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlNodaTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNodaTypesCntRow + { + Cnt = reader.GetInt32(0), + CDatetime2Instant = reader.IsDBNull(1) ? null : DateTime.SpecifyKind(reader.GetDateTime(1), DateTimeKind.Utc).ToInstant(), + CDatetimeoffsetInstant = reader.IsDBNull(2) ? null : reader.GetFieldValue(2).ToInstant() }; } } @@ -1721,4 +2229,31 @@ CROSS JOIN mssql_string_types return null; } + + private const string TruncateMssqlNodaTypesSql = "TRUNCATE TABLE mssql_noda_types;"; + public async Task TruncateMssqlNodaTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlNodaTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlNodaTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } } \ No newline at end of file diff --git a/examples/MssqlExample/request.json b/examples/MssqlExample/request.json index fb23df3af..07d45114d 100644 --- a/examples/MssqlExample/request.json +++ b/examples/MssqlExample/request.json @@ -13,7 +13,7 @@ "codegen": { "out": "examples/MssqlExample", "plugin": "csharp", - "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxFeGFtcGxlR2VuIiwidGFyZ2V0RnJhbWV3b3JrIjoibmV0MTAuMCIsInVzZURhcHBlciI6ZmFsc2V9", + "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxFeGFtcGxlR2VuIiwib3ZlcnJpZGVzIjpbeyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfaW50IiwiY3NoYXJwX3R5cGUiOnsibm90TnVsbCI6ZmFsc2UsInR5cGUiOiJpbnQifX0seyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfdmFyY2hhciIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOmZhbHNlLCJ0eXBlIjoic3RyaW5nIn19LHsiY29sdW1uIjoiR2V0TXNzcWxGdW5jdGlvbnM6bWF4X2RhdGV0aW1lMiIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOnRydWUsInR5cGUiOiJEYXRlVGltZSJ9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZTJfaW5zdGFudCIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOmZhbHNlLCJ0eXBlIjoiSW5zdGFudCJ9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZW9mZnNldF9pbnN0YW50IiwiY3NoYXJwX3R5cGUiOnsibm90TnVsbCI6ZmFsc2UsInR5cGUiOiJJbnN0YW50In19XSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0MTAuMCIsInVzZURhcHBlciI6ZmFsc2V9", "process": { "cmd": "./dist/LocalRunner" } @@ -126,6 +126,38 @@ } ] }, + { + "rel": { + "schema": "public", + "name": "notes" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + } + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, { "rel": { "schema": "public", @@ -454,6 +486,77 @@ } } ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "columns": [ + { + "name": "c_geography", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + } + }, + { + "name": "c_geometry", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + } + }, + { + "name": "c_hierarchyid", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_noda_types" + }, + "columns": [ + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + } + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + } + } + ] } ] }, @@ -462,6 +565,54 @@ }, { "name": "information_schema" + }, + { + "name": "extended", + "tables": [ + { + "rel": { + "schema": "extended", + "name": "bios" + }, + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] } ] }, @@ -1236,6 +1387,240 @@ ], "filename": "query.sql" }, + { + "text": "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);", + "name": "CreateNoteReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "body", + "notNull": true, + "length": 200, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "notes" + } + }, + { + "text": "SELECT id, body FROM notes WHERE id = @id;", + "name": "GetNote", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE notes;", + "name": "TruncateNotes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);", + "name": "CreateExtendedBio", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "author_name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "schema": "extended", + "name": "bios" + } + }, + { + "text": "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;", + "name": "GetFirstExtendedBioByType", + "cmd": ":one", + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE extended.bios;", + "name": "TruncateExtendedBios", + "cmd": ":exec", + "filename": "query.sql" + }, { "text": "\nINSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", "name": "InsertMssqlNumericTypes", @@ -2999,8 +3384,287 @@ " Functions " ], "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid);", + "name": "InsertMssqlSpatialTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_geography", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + }, + "originalName": "c_geography" + } + }, + { + "number": 2, + "column": { + "name": "c_geometry", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + }, + "originalName": "c_geometry" + } + }, + { + "number": 3, + "column": { + "name": "c_hierarchyid", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + }, + "originalName": "c_hierarchyid" + } + } + ], + "comments": [ + " Spatial and hierarchyid types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_spatial_types" + } + }, + { + "text": "SELECT TOP 1 c_geography, c_geometry, c_hierarchyid FROM mssql_spatial_types;", + "name": "GetMssqlSpatialTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_geography", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + }, + "originalName": "c_geography" + }, + { + "name": "c_geometry", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + }, + "originalName": "c_geometry" + }, + { + "name": "c_hierarchyid", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + }, + "originalName": "c_hierarchyid" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_spatial_types;", + "name": "TruncateMssqlSpatialTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);", + "name": "InsertMssqlNodaTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_datetime2_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + } + }, + { + "number": 2, + "column": { + "name": "c_datetimeoffset_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + } + ], + "comments": [ + " NodaTime Instant overrides " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_noda_types" + } + }, + { + "text": "INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);", + "name": "InsertMssqlNodaTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_datetime2_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + } + }, + { + "number": 2, + "column": { + "name": "c_datetimeoffset_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_noda_types" + } + }, + { + "text": "SELECT TOP 1 c_datetime2_instant, c_datetimeoffset_instant FROM mssql_noda_types;", + "name": "GetMssqlNodaTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant\nFROM mssql_noda_types\nGROUP BY c_datetime2_instant, c_datetimeoffset_instant;", + "name": "GetMssqlNodaTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_noda_types;", + "name": "TruncateMssqlNodaTypes", + "cmd": ":exec", + "filename": "query.sql" } ], "sqlc_version": "v1.31.1", - "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0MTAuMCIsIm5hbWVzcGFjZU5hbWUiOiJNc3NxbEV4YW1wbGVHZW4iLCJ1c2VEYXBwZXIiOmZhbHNlLCJvdmVycmlkZURhcHBlclZlcnNpb24iOiIiLCJvdmVycmlkZXMiOm51bGwsImRlYnVnUmVxdWVzdCI6ZmFsc2UsInVzZUNlbnRyYWxQYWNrYWdlTWFuYWdlbWVudCI6ZmFsc2UsIndpdGhBc3luY1N1ZmZpeCI6dHJ1ZSwid2l0aENhbmNlbGxhdGlvblRva2VuIjpmYWxzZSwidXNlQXN5bmNFbnVtZXJhYmxlIjpmYWxzZSwidXNlUHJvcGVyU2luZ3VsYXJpemF0aW9uIjpmYWxzZX0=" + "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0MTAuMCIsIm5hbWVzcGFjZU5hbWUiOiJNc3NxbEV4YW1wbGVHZW4iLCJ1c2VEYXBwZXIiOmZhbHNlLCJvdmVycmlkZURhcHBlclZlcnNpb24iOiIiLCJvdmVycmlkZXMiOlt7ImNvbHVtbiI6IkdldE1zc3FsRnVuY3Rpb25zOm1heF9pbnQiLCJjc2hhcnBfdHlwZSI6eyJ0eXBlIjoiaW50Iiwibm90TnVsbCI6ZmFsc2V9fSx7ImNvbHVtbiI6IkdldE1zc3FsRnVuY3Rpb25zOm1heF92YXJjaGFyIiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6InN0cmluZyIsIm5vdE51bGwiOmZhbHNlfX0seyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfZGF0ZXRpbWUyIiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6IkRhdGVUaW1lIiwibm90TnVsbCI6dHJ1ZX19LHsiY29sdW1uIjoiKjpjX2RhdGV0aW1lMl9pbnN0YW50IiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6Ikluc3RhbnQiLCJub3ROdWxsIjpmYWxzZX19LHsiY29sdW1uIjoiKjpjX2RhdGV0aW1lb2Zmc2V0X2luc3RhbnQiLCJjc2hhcnBfdHlwZSI6eyJ0eXBlIjoiSW5zdGFudCIsIm5vdE51bGwiOmZhbHNlfX1dLCJkZWJ1Z1JlcXVlc3QiOmZhbHNlLCJ1c2VDZW50cmFsUGFja2FnZU1hbmFnZW1lbnQiOmZhbHNlLCJ3aXRoQXN5bmNTdWZmaXgiOnRydWUsIndpdGhDYW5jZWxsYXRpb25Ub2tlbiI6ZmFsc2UsInVzZUFzeW5jRW51bWVyYWJsZSI6ZmFsc2UsInVzZVByb3BlclNpbmd1bGFyaXphdGlvbiI6ZmFsc2V9" } \ No newline at end of file diff --git a/examples/MssqlExample/request.message b/examples/MssqlExample/request.message index 2882fa50799f1e6f650422600a91df5257a6c17c..cb36f035e07fab0dbcd282a615d9d19e4f481da6 100644 GIT binary patch delta 5681 zcmcIo&2Jk;6xZ5L;!cv**(TJTHqEdqRbrWtq-tqw+9qzD5Ro{k<52NYIUDabS=C?h zuB*f)Yzv4=a6rvUNE~{o-~vcoa;OmE)JwS_5C?>~lz*Y3LV^pk-!qQaF-1MuyEE_Y zoA>+u-n^N+514QMWDbC@+5t1hwiRS~Jr4)(@W<}F`2O*K0-#Ua;0xuvD8DqP4<*ef zwmm1W@s%=s5V-S8N-bR%D-}r)WiIyC4K7#8R|`ch#!ZWgVKh}O<`k(^lw$>cBP$gZ zF3OqqS!J^i$>D%nb}TCuskqvL z?~Sa$E21J5#4%6jDi>2KRX6I3*dl{1>fA?SIoBCZgRJoXAr&*$d^#P5&lxMi($%Z7 zsQeGlSF{0U>d<@rY|_VWku9bB4E>Zs0zJ81@nXhIK(yDu(em zZH!~Y#>VJ2(*>LdZ2BhX&Slpmv7(c2Qxfz~tF(1D>?9`WizJ^z4E&V=(8*`WTwbvx z@Xp!}HXzMNN7~Cs`+naGCvza|TG6cVGSz;b4r|@>P`lQ3RONnW*r2$fh($pZ;ExOl z5s!ftsU))#pp&mEYo$uI$QMM6uaMp~iN<$7fv+ztu;U=$VR{jNpt3A3f?#@KxEivAVFH*^Mhp)R-3WIIzv@7NDwrNbgtoqS*#gCuTRb{B^S})1T|~I zoHpUii?cl@E0UHR5>m+uC9;fHJHJZ@7U->U06g(a38-A3Ny^ z#qv$M)^3IR)OsI=vLcs*es+dbN&|CBXf;Pw+f2xnD=t&Ma4&QQ2@Dd3=vAvy7)yos z{ou02(aih;8bwrwX&g-QQotdT#5MvtmF@p=_An;&lvI%w0*=I{n!#;^#qqgafHbZT z_Qum34eq>~d~ZM6N1?8I47h1B^WL_4+tM`e7)GWA7*%33ZR)Qfj&K_E*Qt;KCC>KR zY9umGigM>$bo0Cle+YmrK#H=#j*SM1{W^-=?N|R#Y?p*cDT4{p0zbGWrvzNa58+lx zteCCmqS-0v{IG+KC^(-vvS-kqKY^C$eVc3o}j<41C8YA35(_W)S_a+Tm4|>Ab0J4&W_@aQ^oC-*9#$`mRzR&2y)b%{w ziowtQV2+YLg&PSQiJD+mfd)*$w6hNp6q4q4wx1_aG@7^MQ^LqyAy8}NsWsR*sU9ZW z62|G6=$3E~n6yt%pEyq0;~HoyaZW#xS$WYOoerxX2f%Hnf%@ErQkR}{ps`O1y6o(i zbhk(;(4I2p+F|k8Z?U0DoIbD)dkGz;a7#;x@B;>`@ET~`-Qs=9tG~re)cbk+t7A37 zL&1q`o9O}W9Ap3LfW%B^V&pD^C95{;m%W0)9rNL^SD&qQ%|jJ>jVfbDk&zL6NJQ4b z4mpQtN@*LW=~;?S|YbPL868hM6?PY;1&bf>&~D z+IKTu>a)PviEM9}6OwssewH|&^TwI1nT?SA!v`*D_ht{OEdoUrN*8Y2quN!{)tOl$ zB@czw=}WKVu0KzKHBCIX)l5ZoAEdt*(texnX|5$Alkg`$_>7^pnG5qvsmx$x2u&`c zTu~tlniDhmlCE;VI6>8)y}@*PerW+E-q5S#xOK(Sf;VE%($Uavy*;M32m_rJuQKPv z(1`vGQ5djP(B$ynyp$av(?1K4h8FvdxrB+c&ry4lvF=Bw4|Q=J*GGp>jt-ATK55sI z%U`Nbc|E9iAoxPjygJ0^SK_Xaz7Ml?KwmjJw}Wc^y`$yWx(mj-Mt-h`Ok9jcLae#P#f3Ri8WR)cCh9gTZ(`&UO|3}GEyzhN*7pTTxk7kC zY{|tLiA4oc)f2DyO%5uTnaswlA-+INh^?SBDJL^oi384hC%V~{`70yyOE#{_itKWm y@3G$Cn7my;cJn4dE0N8ov@7g+l-SD*^^6Vm47F}BO_ncT$&y!^lQX%way0-r3^h;y diff --git a/examples/MssqlLegacyExample/Models.cs b/examples/MssqlLegacyExample/Models.cs new file mode 100644 index 000000000..b148df103 --- /dev/null +++ b/examples/MssqlLegacyExample/Models.cs @@ -0,0 +1,84 @@ +// auto-generated by sqlc - do not edit +namespace MssqlLegacyExampleGen +{ + using Microsoft.SqlServer.Types; + using NodaTime; + using NodaTime.Extensions; + using System; + using System.Linq; + + public class Author + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class Book + { + public long Id { get; set; } + public string Name { get; set; } + public long AuthorId { get; set; } + public string Description { get; set; } + }; + public class Note + { + public int Id { get; set; } + public string Body { get; set; } + }; + public class MssqlNumericType + { + public bool? CBit { get; set; } + public byte? CTinyint { get; set; } + public short? CSmallint { get; set; } + public int? CInt { get; set; } + public long? CBigint { get; set; } + public float? CReal { get; set; } + public double? CFloat { get; set; } + public decimal? CDecimal { get; set; } + public decimal? CNumeric { get; set; } + public decimal? CMoney { get; set; } + public decimal? CSmallmoney { get; set; } + }; + public class MssqlStringType + { + public string CChar { get; set; } + public string CNchar { get; set; } + public string CVarchar { get; set; } + public string CNvarchar { get; set; } + public string CVarcharMax { get; set; } + public string CNvarcharMax { get; set; } + }; + public class MssqlDatetimeType + { + public DateTime? CDate { get; set; } + public DateTime? CDatetime { get; set; } + public DateTime? CDatetime2 { get; set; } + public DateTime? CSmalldatetime { get; set; } + public DateTimeOffset? CDatetimeoffset { get; set; } + public TimeSpan? CTime { get; set; } + }; + public class MssqlBinaryType + { + public byte[] CBinary { get; set; } + public byte[] CVarbinary { get; set; } + public byte[] CVarbinaryMax { get; set; } + public Guid? CUniqueidentifier { get; set; } + }; + public class MssqlSpatialType + { + public SqlGeography CGeography { get; set; } + public SqlGeometry CGeometry { get; set; } + public SqlHierarchyId? CHierarchyid { get; set; } + }; + public class MssqlNodaType + { + public DateTime? CDatetime2Instant { get; set; } + public DateTimeOffset? CDatetimeoffsetInstant { get; set; } + }; + public class ExtendedBio + { + public string AuthorName { get; set; } + public string Name { get; set; } + public string BioType { get; set; } + }; +} \ No newline at end of file diff --git a/examples/MssqlLegacyExample/MssqlLegacyExample.csproj b/examples/MssqlLegacyExample/MssqlLegacyExample.csproj new file mode 100644 index 000000000..91c19f2a3 --- /dev/null +++ b/examples/MssqlLegacyExample/MssqlLegacyExample.csproj @@ -0,0 +1,21 @@ + + + + + + netstandard2.0 + MssqlLegacyExampleGen + Library + False + + + + + + + + + + \ No newline at end of file diff --git a/examples/MssqlLegacyExample/QuerySql.cs b/examples/MssqlLegacyExample/QuerySql.cs new file mode 100644 index 000000000..8f2f984fc --- /dev/null +++ b/examples/MssqlLegacyExample/QuerySql.cs @@ -0,0 +1,2555 @@ +// auto-generated by sqlc - do not edit +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable UseAwaitUsing +// ReSharper disable ConvertToUsingDeclaration +// ReSharper disable NotAccessedPositionalProperty.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +namespace MssqlLegacyExampleGen +{ + using Microsoft.Data.SqlClient; + using Microsoft.SqlServer.Types; + using NodaTime; + using NodaTime.Extensions; + using System; + using System.Collections.Generic; + using System.Data; + using System.Threading; + using System.Threading.Tasks; + + public class QuerySql + { + public QuerySql() + { + } + + public QuerySql(string connectionString) : this() + { + this.ConnectionString = connectionString; + } + + private QuerySql(SqlTransaction transaction) : this() + { + this.Transaction = transaction; + } + + public static QuerySql WithTransaction(SqlTransaction transaction) + { + return new QuerySql(transaction); + } + + private SqlTransaction Transaction { get; } + private string ConnectionString { get; } + + private const string GetAuthorSql = @"SELECT TOP 1 id, name, bio FROM authors + WHERE name = @name;"; + public class GetAuthorRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorArgs + { + public string Name { get; set; } + }; + public async Task GetAuthorAsync(GetAuthorArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string ListAuthorsSql = @"SELECT id, name, bio + FROM authors + ORDER BY name + OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;"; + public class ListAuthorsRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class ListAuthorsArgs + { + public int Offset { get; set; } + public int Limit { get; set; } + }; + public async Task> ListAuthorsAsync(ListAuthorsArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(ListAuthorsSql, connection)) + { + command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset; + command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAuthorsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAuthorsSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset; + command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAuthorsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string CreateAuthorSql = "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);"; + public class CreateAuthorArgs + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public async Task CreateAuthorAsync(CreateAuthorArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateAuthorSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string CreateAuthorReturnIdSql = "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);"; + public class CreateAuthorReturnIdRow + { + public long Id { get; set; } + }; + public class CreateAuthorReturnIdArgs + { + public string Name { get; set; } + public string Bio { get; set; } + }; + public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateAuthorReturnIdSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateAuthorReturnIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + + private const string GetAuthorByIdSql = @"SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment + WHERE id = @id;"; + public class GetAuthorByIdRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorByIdArgs + { + public long Id { get; set; } + }; + public async Task GetAuthorByIdAsync(GetAuthorByIdArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorByIdSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string GetAuthorByNamePatternSql = @"SELECT id, name, bio FROM authors + WHERE name LIKE COALESCE(@name_pattern, '%');"; + public class GetAuthorByNamePatternRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorByNamePatternArgs + { + public object NamePattern { get; set; } + }; + public async Task> GetAuthorByNamePatternAsync(GetAuthorByNamePatternArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorByNamePatternSql, connection)) + { + command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorByNamePatternRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByNamePatternSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorByNamePatternRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string DeleteAuthorSql = @"DELETE FROM authors + WHERE name = @name;"; + public class DeleteAuthorArgs + { + public string Name { get; set; } + }; + public async Task DeleteAuthorAsync(DeleteAuthorArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(DeleteAuthorSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = DeleteAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + await command.ExecuteNonQueryAsync(); + } + } + + private const string DeleteAllAuthorsSql = "DELETE FROM authors;"; + public async Task DeleteAllAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(DeleteAllAuthorsSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = DeleteAllAuthorsSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string UpdateAuthorsSql = @"UPDATE authors + SET bio = @bio + WHERE bio IS NOT NULL;"; + public class UpdateAuthorsArgs + { + public string Bio { get; set; } + }; + public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(UpdateAuthorsSql, connection)) + { + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + return await command.ExecuteNonQueryAsync(); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = UpdateAuthorsSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + return await command.ExecuteNonQueryAsync(); + } + } + + private const string GetAuthorsByIdsSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);"; + public class GetAuthorsByIdsRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorsByIdsArgs + { + public long[] Ids { get; set; } + }; + public async Task> GetAuthorsByIdsAsync(GetAuthorsByIdsArgs args) + { + var transformedSql = GetAuthorsByIdsSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(transformedSql, connection)) + { + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = transformedSql; + command.Transaction = this.Transaction; + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string GetAuthorsByIdsAndNamesSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);"; + public class GetAuthorsByIdsAndNamesRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorsByIdsAndNamesArgs + { + public long[] Ids { get; set; } + public string[] Names { get; set; } + }; + public async Task> GetAuthorsByIdsAndNamesAsync(GetAuthorsByIdsAndNamesArgs args) + { + var transformedSql = GetAuthorsByIdsAndNamesSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Names.Length, "names"); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(transformedSql, connection)) + { + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsAndNamesRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = transformedSql; + command.Transaction = this.Transaction; + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsAndNamesRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string CreateBookSql = "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);"; + public class CreateBookRow + { + public long Id { get; set; } + }; + public class CreateBookArgs + { + public string Name { get; set; } + public long AuthorId { get; set; } + }; + public async Task CreateBookAsync(CreateBookArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateBookSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateBookSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + + private const string ListAllAuthorsBooksSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + ORDER BY authors.name;"; + public class ListAllAuthorsBooksRow + { + public Author Author { get; set; } + public Book Book { get; set; } + }; + public async Task> ListAllAuthorsBooksAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(ListAllAuthorsBooksSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAllAuthorsBooksSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + + private const string GetDuplicateAuthorsSql = @"SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio + FROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name + WHERE authors1.id < authors2.id;"; + public class GetDuplicateAuthorsRow + { + public Author Author { get; set; } + public Author Author2 { get; set; } + }; + public async Task> GetDuplicateAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetDuplicateAuthorsSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetDuplicateAuthorsSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + + private const string GetAuthorsByBookNameSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + WHERE books.name = @name;"; + public class GetAuthorsByBookNameRow + { + public long Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + public Book Book { get; set; } + }; + public class GetAuthorsByBookNameArgs + { + public string Name { get; set; } + }; + public async Task> GetAuthorsByBookNameAsync(GetAuthorsByBookNameArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorsByBookNameSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + + private const string CreateNoteReturnIdSql = "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);"; + public class CreateNoteReturnIdRow + { + public int Id { get; set; } + }; + public class CreateNoteReturnIdArgs + { + public string Body { get; set; } + }; + public async Task CreateNoteReturnIdAsync(CreateNoteReturnIdArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateNoteReturnIdSql, connection)) + { + command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt32(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateNoteReturnIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt32(result); + } + } + + private const string GetNoteSql = "SELECT id, body FROM notes WHERE id = @id;"; + public class GetNoteRow + { + public int Id { get; set; } + public string Body { get; set; } + }; + public class GetNoteArgs + { + public int Id { get; set; } + }; + public async Task GetNoteAsync(GetNoteArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetNoteSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetNoteRow + { + Id = reader.GetInt32(0), + Body = reader.GetString(1) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetNoteSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetNoteRow + { + Id = reader.GetInt32(0), + Body = reader.GetString(1) + }; + } + } + } + + return null; + } + + private const string TruncateNotesSql = "TRUNCATE TABLE notes;"; + public async Task TruncateNotesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateNotesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateNotesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string CreateExtendedBioSql = "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);"; + public class CreateExtendedBioArgs + { + public string AuthorName { get; set; } + public string Name { get; set; } + public string BioType { get; set; } + }; + public async Task CreateExtendedBioAsync(CreateExtendedBioArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateExtendedBioSql, connection)) + { + command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateExtendedBioSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string GetFirstExtendedBioByTypeSql = "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;"; + public class GetFirstExtendedBioByTypeRow + { + public string AuthorName { get; set; } + public string Name { get; set; } + public string BioType { get; set; } + }; + public class GetFirstExtendedBioByTypeArgs + { + public string BioType { get; set; } + }; + public async Task GetFirstExtendedBioByTypeAsync(GetFirstExtendedBioByTypeArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetFirstExtendedBioByTypeSql, connection)) + { + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetFirstExtendedBioByTypeRow + { + AuthorName = reader.GetString(0), + Name = reader.GetString(1), + BioType = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetFirstExtendedBioByTypeSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetFirstExtendedBioByTypeRow + { + AuthorName = reader.GetString(0), + Name = reader.GetString(1), + BioType = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string TruncateExtendedBiosSql = "TRUNCATE TABLE extended.bios;"; + public async Task TruncateExtendedBiosAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateExtendedBiosSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateExtendedBiosSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlNumericTypesSql = @" + INSERT INTO mssql_numeric_types + ( + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + ) + VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);"; + public class InsertMssqlNumericTypesArgs + { + public bool? CBit { get; set; } + public byte? CTinyint { get; set; } + public short? CSmallint { get; set; } + public int? CInt { get; set; } + public long? CBigint { get; set; } + public float? CReal { get; set; } + public double? CFloat { get; set; } + public decimal? CDecimal { get; set; } + public decimal? CNumeric { get; set; } + public decimal? CMoney { get; set; } + public decimal? CSmallmoney { get; set; } + }; + public async Task InsertMssqlNumericTypesAsync(InsertMssqlNumericTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlNumericTypesSql, connection)) + { + command.Parameters.Add("@c_bit", SqlDbType.Bit).Value = args.CBit ?? (object)DBNull.Value; + command.Parameters.Add("@c_tinyint", SqlDbType.TinyInt).Value = args.CTinyint ?? (object)DBNull.Value; + command.Parameters.Add("@c_smallint", SqlDbType.SmallInt).Value = args.CSmallint ?? (object)DBNull.Value; + command.Parameters.Add("@c_int", SqlDbType.Int).Value = args.CInt ?? (object)DBNull.Value; + command.Parameters.Add("@c_bigint", SqlDbType.BigInt).Value = args.CBigint ?? (object)DBNull.Value; + command.Parameters.Add("@c_real", SqlDbType.Real).Value = args.CReal ?? (object)DBNull.Value; + command.Parameters.Add("@c_float", SqlDbType.Float).Value = args.CFloat ?? (object)DBNull.Value; + command.Parameters.AddWithValue("@c_decimal", args.CDecimal ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_numeric", args.CNumeric ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_money", args.CMoney ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_smallmoney", args.CSmallmoney ?? (object)DBNull.Value); + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlNumericTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_bit", SqlDbType.Bit).Value = args.CBit ?? (object)DBNull.Value; + command.Parameters.Add("@c_tinyint", SqlDbType.TinyInt).Value = args.CTinyint ?? (object)DBNull.Value; + command.Parameters.Add("@c_smallint", SqlDbType.SmallInt).Value = args.CSmallint ?? (object)DBNull.Value; + command.Parameters.Add("@c_int", SqlDbType.Int).Value = args.CInt ?? (object)DBNull.Value; + command.Parameters.Add("@c_bigint", SqlDbType.BigInt).Value = args.CBigint ?? (object)DBNull.Value; + command.Parameters.Add("@c_real", SqlDbType.Real).Value = args.CReal ?? (object)DBNull.Value; + command.Parameters.Add("@c_float", SqlDbType.Float).Value = args.CFloat ?? (object)DBNull.Value; + command.Parameters.AddWithValue("@c_decimal", args.CDecimal ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_numeric", args.CNumeric ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_money", args.CMoney ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@c_smallmoney", args.CSmallmoney ?? (object)DBNull.Value); + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlNumericTypesBatchSql = @"INSERT INTO mssql_numeric_types + ( + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + ) + VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);"; + public class InsertMssqlNumericTypesBatchArgs + { + public bool? CBit { get; set; } + public byte? CTinyint { get; set; } + public short? CSmallint { get; set; } + public int? CInt { get; set; } + public long? CBigint { get; set; } + public float? CReal { get; set; } + public double? CFloat { get; set; } + public decimal? CDecimal { get; set; } + public decimal? CNumeric { get; set; } + public decimal? CMoney { get; set; } + public decimal? CSmallmoney { get; set; } + }; + public async Task InsertMssqlNumericTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_bit", typeof(object)); + dataTable.Columns.Add("c_tinyint", typeof(object)); + dataTable.Columns.Add("c_smallint", typeof(object)); + dataTable.Columns.Add("c_int", typeof(object)); + dataTable.Columns.Add("c_bigint", typeof(object)); + dataTable.Columns.Add("c_real", typeof(object)); + dataTable.Columns.Add("c_float", typeof(object)); + dataTable.Columns.Add("c_decimal", typeof(object)); + dataTable.Columns.Add("c_numeric", typeof(object)); + dataTable.Columns.Add("c_money", typeof(object)); + dataTable.Columns.Add("c_smallmoney", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CBit ?? (object)DBNull.Value, row.CTinyint ?? (object)DBNull.Value, row.CSmallint ?? (object)DBNull.Value, row.CInt ?? (object)DBNull.Value, row.CBigint ?? (object)DBNull.Value, row.CReal ?? (object)DBNull.Value, row.CFloat ?? (object)DBNull.Value, row.CDecimal ?? (object)DBNull.Value, row.CNumeric ?? (object)DBNull.Value, row.CMoney ?? (object)DBNull.Value, row.CSmallmoney ?? (object)DBNull.Value); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_numeric_types]"; + bulkCopy.ColumnMappings.Add("c_bit", "c_bit"); + bulkCopy.ColumnMappings.Add("c_tinyint", "c_tinyint"); + bulkCopy.ColumnMappings.Add("c_smallint", "c_smallint"); + bulkCopy.ColumnMappings.Add("c_int", "c_int"); + bulkCopy.ColumnMappings.Add("c_bigint", "c_bigint"); + bulkCopy.ColumnMappings.Add("c_real", "c_real"); + bulkCopy.ColumnMappings.Add("c_float", "c_float"); + bulkCopy.ColumnMappings.Add("c_decimal", "c_decimal"); + bulkCopy.ColumnMappings.Add("c_numeric", "c_numeric"); + bulkCopy.ColumnMappings.Add("c_money", "c_money"); + bulkCopy.ColumnMappings.Add("c_smallmoney", "c_smallmoney"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlNumericTypesSql = "SELECT TOP 1 c_bit, c_tinyint, c_smallint, c_int, c_bigint, c_real, c_float, c_decimal, c_numeric, c_money, c_smallmoney FROM mssql_numeric_types;"; + public class GetMssqlNumericTypesRow + { + public bool? CBit { get; set; } + public byte? CTinyint { get; set; } + public short? CSmallint { get; set; } + public int? CInt { get; set; } + public long? CBigint { get; set; } + public float? CReal { get; set; } + public double? CFloat { get; set; } + public decimal? CDecimal { get; set; } + public decimal? CNumeric { get; set; } + public decimal? CMoney { get; set; } + public decimal? CSmallmoney { get; set; } + }; + public async Task GetMssqlNumericTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlNumericTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNumericTypesRow + { + CBit = reader.IsDBNull(0) ? (bool? )null : reader.GetBoolean(0), + CTinyint = reader.IsDBNull(1) ? (byte? )null : reader.GetByte(1), + CSmallint = reader.IsDBNull(2) ? (short? )null : reader.GetInt16(2), + CInt = reader.IsDBNull(3) ? (int? )null : reader.GetInt32(3), + CBigint = reader.IsDBNull(4) ? (long? )null : reader.GetInt64(4), + CReal = reader.IsDBNull(5) ? (float? )null : reader.GetFloat(5), + CFloat = reader.IsDBNull(6) ? (double? )null : reader.GetDouble(6), + CDecimal = reader.IsDBNull(7) ? (decimal? )null : reader.GetDecimal(7), + CNumeric = reader.IsDBNull(8) ? (decimal? )null : reader.GetDecimal(8), + CMoney = reader.IsDBNull(9) ? (decimal? )null : reader.GetDecimal(9), + CSmallmoney = reader.IsDBNull(10) ? (decimal? )null : reader.GetDecimal(10) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlNumericTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNumericTypesRow + { + CBit = reader.IsDBNull(0) ? (bool? )null : reader.GetBoolean(0), + CTinyint = reader.IsDBNull(1) ? (byte? )null : reader.GetByte(1), + CSmallint = reader.IsDBNull(2) ? (short? )null : reader.GetInt16(2), + CInt = reader.IsDBNull(3) ? (int? )null : reader.GetInt32(3), + CBigint = reader.IsDBNull(4) ? (long? )null : reader.GetInt64(4), + CReal = reader.IsDBNull(5) ? (float? )null : reader.GetFloat(5), + CFloat = reader.IsDBNull(6) ? (double? )null : reader.GetDouble(6), + CDecimal = reader.IsDBNull(7) ? (decimal? )null : reader.GetDecimal(7), + CNumeric = reader.IsDBNull(8) ? (decimal? )null : reader.GetDecimal(8), + CMoney = reader.IsDBNull(9) ? (decimal? )null : reader.GetDecimal(9), + CSmallmoney = reader.IsDBNull(10) ? (decimal? )null : reader.GetDecimal(10) + }; + } + } + } + + return null; + } + + private const string GetMssqlNumericTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney + FROM mssql_numeric_types + GROUP BY + c_bit, + c_tinyint, + c_smallint, + c_int, + c_bigint, + c_real, + c_float, + c_decimal, + c_numeric, + c_money, + c_smallmoney;"; + public class GetMssqlNumericTypesCntRow + { + public int Cnt { get; set; } + public bool? CBit { get; set; } + public byte? CTinyint { get; set; } + public short? CSmallint { get; set; } + public int? CInt { get; set; } + public long? CBigint { get; set; } + public float? CReal { get; set; } + public double? CFloat { get; set; } + public decimal? CDecimal { get; set; } + public decimal? CNumeric { get; set; } + public decimal? CMoney { get; set; } + public decimal? CSmallmoney { get; set; } + }; + public async Task GetMssqlNumericTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlNumericTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNumericTypesCntRow + { + Cnt = reader.GetInt32(0), + CBit = reader.IsDBNull(1) ? (bool? )null : reader.GetBoolean(1), + CTinyint = reader.IsDBNull(2) ? (byte? )null : reader.GetByte(2), + CSmallint = reader.IsDBNull(3) ? (short? )null : reader.GetInt16(3), + CInt = reader.IsDBNull(4) ? (int? )null : reader.GetInt32(4), + CBigint = reader.IsDBNull(5) ? (long? )null : reader.GetInt64(5), + CReal = reader.IsDBNull(6) ? (float? )null : reader.GetFloat(6), + CFloat = reader.IsDBNull(7) ? (double? )null : reader.GetDouble(7), + CDecimal = reader.IsDBNull(8) ? (decimal? )null : reader.GetDecimal(8), + CNumeric = reader.IsDBNull(9) ? (decimal? )null : reader.GetDecimal(9), + CMoney = reader.IsDBNull(10) ? (decimal? )null : reader.GetDecimal(10), + CSmallmoney = reader.IsDBNull(11) ? (decimal? )null : reader.GetDecimal(11) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlNumericTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNumericTypesCntRow + { + Cnt = reader.GetInt32(0), + CBit = reader.IsDBNull(1) ? (bool? )null : reader.GetBoolean(1), + CTinyint = reader.IsDBNull(2) ? (byte? )null : reader.GetByte(2), + CSmallint = reader.IsDBNull(3) ? (short? )null : reader.GetInt16(3), + CInt = reader.IsDBNull(4) ? (int? )null : reader.GetInt32(4), + CBigint = reader.IsDBNull(5) ? (long? )null : reader.GetInt64(5), + CReal = reader.IsDBNull(6) ? (float? )null : reader.GetFloat(6), + CFloat = reader.IsDBNull(7) ? (double? )null : reader.GetDouble(7), + CDecimal = reader.IsDBNull(8) ? (decimal? )null : reader.GetDecimal(8), + CNumeric = reader.IsDBNull(9) ? (decimal? )null : reader.GetDecimal(9), + CMoney = reader.IsDBNull(10) ? (decimal? )null : reader.GetDecimal(10), + CSmallmoney = reader.IsDBNull(11) ? (decimal? )null : reader.GetDecimal(11) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlNumericTypesSql = "TRUNCATE TABLE mssql_numeric_types;"; + public async Task TruncateMssqlNumericTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlNumericTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlNumericTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlStringTypesSql = @" + INSERT INTO mssql_string_types + ( + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + ) + VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);"; + public class InsertMssqlStringTypesArgs + { + public string CChar { get; set; } + public string CNchar { get; set; } + public string CVarchar { get; set; } + public string CNvarchar { get; set; } + public string CVarcharMax { get; set; } + public string CNvarcharMax { get; set; } + }; + public async Task InsertMssqlStringTypesAsync(InsertMssqlStringTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlStringTypesSql, connection)) + { + command.Parameters.Add("@c_char", SqlDbType.Char).Value = args.CChar ?? (object)DBNull.Value; + command.Parameters.Add("@c_nchar", SqlDbType.NChar).Value = args.CNchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_varchar", SqlDbType.VarChar).Value = args.CVarchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_nvarchar", SqlDbType.NVarChar).Value = args.CNvarchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_varchar_max", SqlDbType.VarChar).Value = args.CVarcharMax ?? (object)DBNull.Value; + command.Parameters.Add("@c_nvarchar_max", SqlDbType.NVarChar).Value = args.CNvarcharMax ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlStringTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_char", SqlDbType.Char).Value = args.CChar ?? (object)DBNull.Value; + command.Parameters.Add("@c_nchar", SqlDbType.NChar).Value = args.CNchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_varchar", SqlDbType.VarChar).Value = args.CVarchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_nvarchar", SqlDbType.NVarChar).Value = args.CNvarchar ?? (object)DBNull.Value; + command.Parameters.Add("@c_varchar_max", SqlDbType.VarChar).Value = args.CVarcharMax ?? (object)DBNull.Value; + command.Parameters.Add("@c_nvarchar_max", SqlDbType.NVarChar).Value = args.CNvarcharMax ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlStringTypesBatchSql = @"INSERT INTO mssql_string_types + ( + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + ) + VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);"; + public class InsertMssqlStringTypesBatchArgs + { + public string CChar { get; set; } + public string CNchar { get; set; } + public string CVarchar { get; set; } + public string CNvarchar { get; set; } + public string CVarcharMax { get; set; } + public string CNvarcharMax { get; set; } + }; + public async Task InsertMssqlStringTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_char", typeof(object)); + dataTable.Columns.Add("c_nchar", typeof(object)); + dataTable.Columns.Add("c_varchar", typeof(object)); + dataTable.Columns.Add("c_nvarchar", typeof(object)); + dataTable.Columns.Add("c_varchar_max", typeof(object)); + dataTable.Columns.Add("c_nvarchar_max", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CChar ?? (object)DBNull.Value, row.CNchar ?? (object)DBNull.Value, row.CVarchar ?? (object)DBNull.Value, row.CNvarchar ?? (object)DBNull.Value, row.CVarcharMax ?? (object)DBNull.Value, row.CNvarcharMax ?? (object)DBNull.Value); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_string_types]"; + bulkCopy.ColumnMappings.Add("c_char", "c_char"); + bulkCopy.ColumnMappings.Add("c_nchar", "c_nchar"); + bulkCopy.ColumnMappings.Add("c_varchar", "c_varchar"); + bulkCopy.ColumnMappings.Add("c_nvarchar", "c_nvarchar"); + bulkCopy.ColumnMappings.Add("c_varchar_max", "c_varchar_max"); + bulkCopy.ColumnMappings.Add("c_nvarchar_max", "c_nvarchar_max"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlStringTypesSql = "SELECT TOP 1 c_char, c_nchar, c_varchar, c_nvarchar, c_varchar_max, c_nvarchar_max FROM mssql_string_types;"; + public class GetMssqlStringTypesRow + { + public string CChar { get; set; } + public string CNchar { get; set; } + public string CVarchar { get; set; } + public string CNvarchar { get; set; } + public string CVarcharMax { get; set; } + public string CNvarcharMax { get; set; } + }; + public async Task GetMssqlStringTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlStringTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlStringTypesRow + { + CChar = reader.IsDBNull(0) ? null : reader.GetString(0), + CNchar = reader.IsDBNull(1) ? null : reader.GetString(1), + CVarchar = reader.IsDBNull(2) ? null : reader.GetString(2), + CNvarchar = reader.IsDBNull(3) ? null : reader.GetString(3), + CVarcharMax = reader.IsDBNull(4) ? null : reader.GetString(4), + CNvarcharMax = reader.IsDBNull(5) ? null : reader.GetString(5) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlStringTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlStringTypesRow + { + CChar = reader.IsDBNull(0) ? null : reader.GetString(0), + CNchar = reader.IsDBNull(1) ? null : reader.GetString(1), + CVarchar = reader.IsDBNull(2) ? null : reader.GetString(2), + CNvarchar = reader.IsDBNull(3) ? null : reader.GetString(3), + CVarcharMax = reader.IsDBNull(4) ? null : reader.GetString(4), + CNvarcharMax = reader.IsDBNull(5) ? null : reader.GetString(5) + }; + } + } + } + + return null; + } + + private const string GetMssqlStringTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max + FROM mssql_string_types + GROUP BY + c_char, + c_nchar, + c_varchar, + c_nvarchar, + c_varchar_max, + c_nvarchar_max;"; + public class GetMssqlStringTypesCntRow + { + public int Cnt { get; set; } + public string CChar { get; set; } + public string CNchar { get; set; } + public string CVarchar { get; set; } + public string CNvarchar { get; set; } + public string CVarcharMax { get; set; } + public string CNvarcharMax { get; set; } + }; + public async Task GetMssqlStringTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlStringTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlStringTypesCntRow + { + Cnt = reader.GetInt32(0), + CChar = reader.IsDBNull(1) ? null : reader.GetString(1), + CNchar = reader.IsDBNull(2) ? null : reader.GetString(2), + CVarchar = reader.IsDBNull(3) ? null : reader.GetString(3), + CNvarchar = reader.IsDBNull(4) ? null : reader.GetString(4), + CVarcharMax = reader.IsDBNull(5) ? null : reader.GetString(5), + CNvarcharMax = reader.IsDBNull(6) ? null : reader.GetString(6) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlStringTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlStringTypesCntRow + { + Cnt = reader.GetInt32(0), + CChar = reader.IsDBNull(1) ? null : reader.GetString(1), + CNchar = reader.IsDBNull(2) ? null : reader.GetString(2), + CVarchar = reader.IsDBNull(3) ? null : reader.GetString(3), + CNvarchar = reader.IsDBNull(4) ? null : reader.GetString(4), + CVarcharMax = reader.IsDBNull(5) ? null : reader.GetString(5), + CNvarcharMax = reader.IsDBNull(6) ? null : reader.GetString(6) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlStringTypesSql = "TRUNCATE TABLE mssql_string_types;"; + public async Task TruncateMssqlStringTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlStringTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlStringTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlDatetimeTypesSql = @" + INSERT INTO mssql_datetime_types + ( + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + ) + VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);"; + public class InsertMssqlDatetimeTypesArgs + { + public DateTime? CDate { get; set; } + public DateTime? CDatetime { get; set; } + public DateTime? CDatetime2 { get; set; } + public DateTime? CSmalldatetime { get; set; } + public DateTimeOffset? CDatetimeoffset { get; set; } + public TimeSpan? CTime { get; set; } + }; + public async Task InsertMssqlDatetimeTypesAsync(InsertMssqlDatetimeTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlDatetimeTypesSql, connection)) + { + command.Parameters.Add("@c_date", SqlDbType.Date).Value = args.CDate ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetime", SqlDbType.DateTime).Value = args.CDatetime ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetime2", SqlDbType.DateTime2).Value = args.CDatetime2 ?? (object)DBNull.Value; + command.Parameters.Add("@c_smalldatetime", SqlDbType.SmallDateTime).Value = args.CSmalldatetime ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetimeoffset", SqlDbType.DateTimeOffset).Value = args.CDatetimeoffset ?? (object)DBNull.Value; + command.Parameters.Add("@c_time", SqlDbType.Time).Value = args.CTime ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlDatetimeTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_date", SqlDbType.Date).Value = args.CDate ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetime", SqlDbType.DateTime).Value = args.CDatetime ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetime2", SqlDbType.DateTime2).Value = args.CDatetime2 ?? (object)DBNull.Value; + command.Parameters.Add("@c_smalldatetime", SqlDbType.SmallDateTime).Value = args.CSmalldatetime ?? (object)DBNull.Value; + command.Parameters.Add("@c_datetimeoffset", SqlDbType.DateTimeOffset).Value = args.CDatetimeoffset ?? (object)DBNull.Value; + command.Parameters.Add("@c_time", SqlDbType.Time).Value = args.CTime ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlDatetimeTypesBatchSql = @"INSERT INTO mssql_datetime_types + ( + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + ) + VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);"; + public class InsertMssqlDatetimeTypesBatchArgs + { + public DateTime? CDate { get; set; } + public DateTime? CDatetime { get; set; } + public DateTime? CDatetime2 { get; set; } + public DateTime? CSmalldatetime { get; set; } + public DateTimeOffset? CDatetimeoffset { get; set; } + public TimeSpan? CTime { get; set; } + }; + public async Task InsertMssqlDatetimeTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_date", typeof(object)); + dataTable.Columns.Add("c_datetime", typeof(object)); + dataTable.Columns.Add("c_datetime2", typeof(object)); + dataTable.Columns.Add("c_smalldatetime", typeof(object)); + dataTable.Columns.Add("c_datetimeoffset", typeof(object)); + dataTable.Columns.Add("c_time", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CDate ?? (object)DBNull.Value, row.CDatetime ?? (object)DBNull.Value, row.CDatetime2 ?? (object)DBNull.Value, row.CSmalldatetime ?? (object)DBNull.Value, row.CDatetimeoffset ?? (object)DBNull.Value, row.CTime ?? (object)DBNull.Value); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_datetime_types]"; + bulkCopy.ColumnMappings.Add("c_date", "c_date"); + bulkCopy.ColumnMappings.Add("c_datetime", "c_datetime"); + bulkCopy.ColumnMappings.Add("c_datetime2", "c_datetime2"); + bulkCopy.ColumnMappings.Add("c_smalldatetime", "c_smalldatetime"); + bulkCopy.ColumnMappings.Add("c_datetimeoffset", "c_datetimeoffset"); + bulkCopy.ColumnMappings.Add("c_time", "c_time"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlDatetimeTypesSql = "SELECT TOP 1 c_date, c_datetime, c_datetime2, c_smalldatetime, c_datetimeoffset, c_time FROM mssql_datetime_types;"; + public class GetMssqlDatetimeTypesRow + { + public DateTime? CDate { get; set; } + public DateTime? CDatetime { get; set; } + public DateTime? CDatetime2 { get; set; } + public DateTime? CSmalldatetime { get; set; } + public DateTimeOffset? CDatetimeoffset { get; set; } + public TimeSpan? CTime { get; set; } + }; + public async Task GetMssqlDatetimeTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlDatetimeTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlDatetimeTypesRow + { + CDate = reader.IsDBNull(0) ? (DateTime? )null : reader.GetDateTime(0), + CDatetime = reader.IsDBNull(1) ? (DateTime? )null : reader.GetDateTime(1), + CDatetime2 = reader.IsDBNull(2) ? (DateTime? )null : reader.GetDateTime(2), + CSmalldatetime = reader.IsDBNull(3) ? (DateTime? )null : reader.GetDateTime(3), + CDatetimeoffset = reader.IsDBNull(4) ? (DateTimeOffset? )null : reader.GetFieldValue(4), + CTime = reader.IsDBNull(5) ? (TimeSpan? )null : reader.GetFieldValue(5) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlDatetimeTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlDatetimeTypesRow + { + CDate = reader.IsDBNull(0) ? (DateTime? )null : reader.GetDateTime(0), + CDatetime = reader.IsDBNull(1) ? (DateTime? )null : reader.GetDateTime(1), + CDatetime2 = reader.IsDBNull(2) ? (DateTime? )null : reader.GetDateTime(2), + CSmalldatetime = reader.IsDBNull(3) ? (DateTime? )null : reader.GetDateTime(3), + CDatetimeoffset = reader.IsDBNull(4) ? (DateTimeOffset? )null : reader.GetFieldValue(4), + CTime = reader.IsDBNull(5) ? (TimeSpan? )null : reader.GetFieldValue(5) + }; + } + } + } + + return null; + } + + private const string GetMssqlDatetimeTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time + FROM mssql_datetime_types + GROUP BY + c_date, + c_datetime, + c_datetime2, + c_smalldatetime, + c_datetimeoffset, + c_time;"; + public class GetMssqlDatetimeTypesCntRow + { + public int Cnt { get; set; } + public DateTime? CDate { get; set; } + public DateTime? CDatetime { get; set; } + public DateTime? CDatetime2 { get; set; } + public DateTime? CSmalldatetime { get; set; } + public DateTimeOffset? CDatetimeoffset { get; set; } + public TimeSpan? CTime { get; set; } + }; + public async Task GetMssqlDatetimeTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlDatetimeTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlDatetimeTypesCntRow + { + Cnt = reader.GetInt32(0), + CDate = reader.IsDBNull(1) ? (DateTime? )null : reader.GetDateTime(1), + CDatetime = reader.IsDBNull(2) ? (DateTime? )null : reader.GetDateTime(2), + CDatetime2 = reader.IsDBNull(3) ? (DateTime? )null : reader.GetDateTime(3), + CSmalldatetime = reader.IsDBNull(4) ? (DateTime? )null : reader.GetDateTime(4), + CDatetimeoffset = reader.IsDBNull(5) ? (DateTimeOffset? )null : reader.GetFieldValue(5), + CTime = reader.IsDBNull(6) ? (TimeSpan? )null : reader.GetFieldValue(6) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlDatetimeTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlDatetimeTypesCntRow + { + Cnt = reader.GetInt32(0), + CDate = reader.IsDBNull(1) ? (DateTime? )null : reader.GetDateTime(1), + CDatetime = reader.IsDBNull(2) ? (DateTime? )null : reader.GetDateTime(2), + CDatetime2 = reader.IsDBNull(3) ? (DateTime? )null : reader.GetDateTime(3), + CSmalldatetime = reader.IsDBNull(4) ? (DateTime? )null : reader.GetDateTime(4), + CDatetimeoffset = reader.IsDBNull(5) ? (DateTimeOffset? )null : reader.GetFieldValue(5), + CTime = reader.IsDBNull(6) ? (TimeSpan? )null : reader.GetFieldValue(6) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlDatetimeTypesSql = "TRUNCATE TABLE mssql_datetime_types;"; + public async Task TruncateMssqlDatetimeTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlDatetimeTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlDatetimeTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlBinaryTypesSql = @" + INSERT INTO mssql_binary_types + ( + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + ) + VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);"; + public class InsertMssqlBinaryTypesArgs + { + public byte[] CBinary { get; set; } + public byte[] CVarbinary { get; set; } + public byte[] CVarbinaryMax { get; set; } + public Guid? CUniqueidentifier { get; set; } + }; + public async Task InsertMssqlBinaryTypesAsync(InsertMssqlBinaryTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlBinaryTypesSql, connection)) + { + command.Parameters.Add("@c_binary", SqlDbType.Binary).Value = args.CBinary ?? (object)DBNull.Value; + command.Parameters.Add("@c_varbinary", SqlDbType.VarBinary).Value = args.CVarbinary ?? (object)DBNull.Value; + command.Parameters.Add("@c_varbinary_max", SqlDbType.VarBinary).Value = args.CVarbinaryMax ?? (object)DBNull.Value; + command.Parameters.Add("@c_uniqueidentifier", SqlDbType.UniqueIdentifier).Value = args.CUniqueidentifier ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlBinaryTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_binary", SqlDbType.Binary).Value = args.CBinary ?? (object)DBNull.Value; + command.Parameters.Add("@c_varbinary", SqlDbType.VarBinary).Value = args.CVarbinary ?? (object)DBNull.Value; + command.Parameters.Add("@c_varbinary_max", SqlDbType.VarBinary).Value = args.CVarbinaryMax ?? (object)DBNull.Value; + command.Parameters.Add("@c_uniqueidentifier", SqlDbType.UniqueIdentifier).Value = args.CUniqueidentifier ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlBinaryTypesBatchSql = @"INSERT INTO mssql_binary_types + ( + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + ) + VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);"; + public class InsertMssqlBinaryTypesBatchArgs + { + public byte[] CBinary { get; set; } + public byte[] CVarbinary { get; set; } + public byte[] CVarbinaryMax { get; set; } + public Guid? CUniqueidentifier { get; set; } + }; + public async Task InsertMssqlBinaryTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_binary", typeof(object)); + dataTable.Columns.Add("c_varbinary", typeof(object)); + dataTable.Columns.Add("c_varbinary_max", typeof(object)); + dataTable.Columns.Add("c_uniqueidentifier", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CBinary ?? (object)DBNull.Value, row.CVarbinary ?? (object)DBNull.Value, row.CVarbinaryMax ?? (object)DBNull.Value, row.CUniqueidentifier ?? (object)DBNull.Value); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_binary_types]"; + bulkCopy.ColumnMappings.Add("c_binary", "c_binary"); + bulkCopy.ColumnMappings.Add("c_varbinary", "c_varbinary"); + bulkCopy.ColumnMappings.Add("c_varbinary_max", "c_varbinary_max"); + bulkCopy.ColumnMappings.Add("c_uniqueidentifier", "c_uniqueidentifier"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlBinaryTypesSql = "SELECT TOP 1 c_binary, c_varbinary, c_varbinary_max, c_uniqueidentifier FROM mssql_binary_types;"; + public class GetMssqlBinaryTypesRow + { + public byte[] CBinary { get; set; } + public byte[] CVarbinary { get; set; } + public byte[] CVarbinaryMax { get; set; } + public Guid? CUniqueidentifier { get; set; } + }; + public async Task GetMssqlBinaryTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlBinaryTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlBinaryTypesRow + { + CBinary = reader.IsDBNull(0) ? null : reader.GetFieldValue(0), + CVarbinary = reader.IsDBNull(1) ? null : reader.GetFieldValue(1), + CVarbinaryMax = reader.IsDBNull(2) ? null : reader.GetFieldValue(2), + CUniqueidentifier = reader.IsDBNull(3) ? (Guid? )null : reader.GetGuid(3) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlBinaryTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlBinaryTypesRow + { + CBinary = reader.IsDBNull(0) ? null : reader.GetFieldValue(0), + CVarbinary = reader.IsDBNull(1) ? null : reader.GetFieldValue(1), + CVarbinaryMax = reader.IsDBNull(2) ? null : reader.GetFieldValue(2), + CUniqueidentifier = reader.IsDBNull(3) ? (Guid? )null : reader.GetGuid(3) + }; + } + } + } + + return null; + } + + private const string GetMssqlBinaryTypesCntSql = @"SELECT TOP 1 + COUNT(*) AS cnt, + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier + FROM mssql_binary_types + GROUP BY + c_binary, + c_varbinary, + c_varbinary_max, + c_uniqueidentifier;"; + public class GetMssqlBinaryTypesCntRow + { + public int Cnt { get; set; } + public byte[] CBinary { get; set; } + public byte[] CVarbinary { get; set; } + public byte[] CVarbinaryMax { get; set; } + public Guid? CUniqueidentifier { get; set; } + }; + public async Task GetMssqlBinaryTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlBinaryTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlBinaryTypesCntRow + { + Cnt = reader.GetInt32(0), + CBinary = reader.IsDBNull(1) ? null : reader.GetFieldValue(1), + CVarbinary = reader.IsDBNull(2) ? null : reader.GetFieldValue(2), + CVarbinaryMax = reader.IsDBNull(3) ? null : reader.GetFieldValue(3), + CUniqueidentifier = reader.IsDBNull(4) ? (Guid? )null : reader.GetGuid(4) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlBinaryTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlBinaryTypesCntRow + { + Cnt = reader.GetInt32(0), + CBinary = reader.IsDBNull(1) ? null : reader.GetFieldValue(1), + CVarbinary = reader.IsDBNull(2) ? null : reader.GetFieldValue(2), + CVarbinaryMax = reader.IsDBNull(3) ? null : reader.GetFieldValue(3), + CUniqueidentifier = reader.IsDBNull(4) ? (Guid? )null : reader.GetGuid(4) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlBinaryTypesSql = "TRUNCATE TABLE mssql_binary_types;"; + public async Task TruncateMssqlBinaryTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlBinaryTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlBinaryTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string GetMssqlFunctionsSql = @" + SELECT + MAX(c_int) AS max_int, + MAX(c_varchar) AS max_varchar, + MAX(c_datetime2) AS max_datetime2 + FROM mssql_numeric_types + CROSS JOIN mssql_string_types + CROSS JOIN mssql_datetime_types;"; + public class GetMssqlFunctionsRow + { + public int? MaxInt { get; set; } + public string MaxVarchar { get; set; } + public DateTime MaxDatetime2 { get; set; } + }; + public async Task GetMssqlFunctionsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlFunctionsSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlFunctionsRow + { + MaxInt = reader.IsDBNull(0) ? (int? )null : reader.GetInt32(0), + MaxVarchar = reader.IsDBNull(1) ? null : reader.GetString(1), + MaxDatetime2 = reader.GetDateTime(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlFunctionsSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlFunctionsRow + { + MaxInt = reader.IsDBNull(0) ? (int? )null : reader.GetInt32(0), + MaxVarchar = reader.IsDBNull(1) ? null : reader.GetString(1), + MaxDatetime2 = reader.GetDateTime(2) + }; + } + } + } + + return null; + } + + private const string InsertMssqlSpatialTypesSql = @" + INSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid);"; + public class InsertMssqlSpatialTypesArgs + { + public SqlGeography CGeography { get; set; } + public SqlGeometry CGeometry { get; set; } + public SqlHierarchyId? CHierarchyid { get; set; } + }; + public async Task InsertMssqlSpatialTypesAsync(InsertMssqlSpatialTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlSpatialTypesSql, connection)) + { + command.Parameters.Add(new SqlParameter("@c_geography", SqlDbType.Udt) { UdtTypeName = "geography", Value = args.CGeography ?? (object)DBNull.Value }); + command.Parameters.Add(new SqlParameter("@c_geometry", SqlDbType.Udt) { UdtTypeName = "geometry", Value = args.CGeometry ?? (object)DBNull.Value }); + command.Parameters.Add(new SqlParameter("@c_hierarchyid", SqlDbType.Udt) { UdtTypeName = "hierarchyid", Value = args.CHierarchyid ?? (object)DBNull.Value }); + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlSpatialTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add(new SqlParameter("@c_geography", SqlDbType.Udt) { UdtTypeName = "geography", Value = args.CGeography ?? (object)DBNull.Value }); + command.Parameters.Add(new SqlParameter("@c_geometry", SqlDbType.Udt) { UdtTypeName = "geometry", Value = args.CGeometry ?? (object)DBNull.Value }); + command.Parameters.Add(new SqlParameter("@c_hierarchyid", SqlDbType.Udt) { UdtTypeName = "hierarchyid", Value = args.CHierarchyid ?? (object)DBNull.Value }); + await command.ExecuteNonQueryAsync(); + } + } + + private const string GetMssqlSpatialTypesSql = "SELECT TOP 1 c_geography, c_geometry, c_hierarchyid FROM mssql_spatial_types;"; + public class GetMssqlSpatialTypesRow + { + public SqlGeography CGeography { get; set; } + public SqlGeometry CGeometry { get; set; } + public SqlHierarchyId? CHierarchyid { get; set; } + }; + public async Task GetMssqlSpatialTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlSpatialTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlSpatialTypesRow + { + CGeography = reader.IsDBNull(0) ? null : SqlGeography.Deserialize(reader.GetSqlBytes(0)), + CGeometry = reader.IsDBNull(1) ? null : SqlGeometry.Deserialize(reader.GetSqlBytes(1)), + CHierarchyid = reader.IsDBNull(2) ? (SqlHierarchyId? )null : (new Func(b => + { + var id = new SqlHierarchyId(); + id.Read(new System.IO.BinaryReader(b.Stream)); + return id; + }))(reader.GetSqlBytes(2)) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlSpatialTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlSpatialTypesRow + { + CGeography = reader.IsDBNull(0) ? null : SqlGeography.Deserialize(reader.GetSqlBytes(0)), + CGeometry = reader.IsDBNull(1) ? null : SqlGeometry.Deserialize(reader.GetSqlBytes(1)), + CHierarchyid = reader.IsDBNull(2) ? (SqlHierarchyId? )null : (new Func(b => + { + var id = new SqlHierarchyId(); + id.Read(new System.IO.BinaryReader(b.Stream)); + return id; + }))(reader.GetSqlBytes(2)) + }; + } + } + } + + return null; + } + + private const string TruncateMssqlSpatialTypesSql = "TRUNCATE TABLE mssql_spatial_types;"; + public async Task TruncateMssqlSpatialTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlSpatialTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlSpatialTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlNodaTypesSql = @" + INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);"; + public class InsertMssqlNodaTypesArgs + { + public Instant? CDatetime2Instant { get; set; } + public Instant? CDatetimeoffsetInstant { get; set; } + }; + public async Task InsertMssqlNodaTypesAsync(InsertMssqlNodaTypesArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(InsertMssqlNodaTypesSql, connection)) + { + command.Parameters.Add("@c_datetime2_instant", SqlDbType.DateTime2).Value = args.CDatetime2Instant is null ? (object)DBNull.Value : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc(); + command.Parameters.Add("@c_datetimeoffset_instant", SqlDbType.DateTimeOffset).Value = args.CDatetimeoffsetInstant is null ? (object)DBNull.Value : (DateTimeOffset? )args.CDatetimeoffsetInstant.Value.ToDateTimeOffset(); + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = InsertMssqlNodaTypesSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@c_datetime2_instant", SqlDbType.DateTime2).Value = args.CDatetime2Instant is null ? (object)DBNull.Value : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc(); + command.Parameters.Add("@c_datetimeoffset_instant", SqlDbType.DateTimeOffset).Value = args.CDatetimeoffsetInstant is null ? (object)DBNull.Value : (DateTimeOffset? )args.CDatetimeoffsetInstant.Value.ToDateTimeOffset(); + await command.ExecuteNonQueryAsync(); + } + } + + private const string InsertMssqlNodaTypesBatchSql = "INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);"; + public class InsertMssqlNodaTypesBatchArgs + { + public Instant? CDatetime2Instant { get; set; } + public Instant? CDatetimeoffsetInstant { get; set; } + }; + public async Task InsertMssqlNodaTypesBatchAsync(List args) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + var dataTable = new DataTable(); + dataTable.Columns.Add("c_datetime2_instant", typeof(object)); + dataTable.Columns.Add("c_datetimeoffset_instant", typeof(object)); + foreach (var row in args) + dataTable.Rows.Add(row.CDatetime2Instant is null ? (object)DBNull.Value : (DateTime? )row.CDatetime2Instant.Value.ToDateTimeUtc(), row.CDatetimeoffsetInstant is null ? (object)DBNull.Value : (DateTimeOffset? )row.CDatetimeoffsetInstant.Value.ToDateTimeOffset()); + using (var bulkCopy = new SqlBulkCopy(connection)) + { + bulkCopy.DestinationTableName = "[mssql_noda_types]"; + bulkCopy.ColumnMappings.Add("c_datetime2_instant", "c_datetime2_instant"); + bulkCopy.ColumnMappings.Add("c_datetimeoffset_instant", "c_datetimeoffset_instant"); + await bulkCopy.WriteToServerAsync(dataTable); + } + } + } + + private const string GetMssqlNodaTypesSql = "SELECT TOP 1 c_datetime2_instant, c_datetimeoffset_instant FROM mssql_noda_types;"; + public class GetMssqlNodaTypesRow + { + public Instant? CDatetime2Instant { get; set; } + public Instant? CDatetimeoffsetInstant { get; set; } + }; + public async Task GetMssqlNodaTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlNodaTypesSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNodaTypesRow + { + CDatetime2Instant = reader.IsDBNull(0) ? (Instant? )null : DateTime.SpecifyKind(reader.GetDateTime(0), DateTimeKind.Utc).ToInstant(), + CDatetimeoffsetInstant = reader.IsDBNull(1) ? (Instant? )null : reader.GetFieldValue(1).ToInstant() + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlNodaTypesSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNodaTypesRow + { + CDatetime2Instant = reader.IsDBNull(0) ? (Instant? )null : DateTime.SpecifyKind(reader.GetDateTime(0), DateTimeKind.Utc).ToInstant(), + CDatetimeoffsetInstant = reader.IsDBNull(1) ? (Instant? )null : reader.GetFieldValue(1).ToInstant() + }; + } + } + } + + return null; + } + + private const string GetMssqlNodaTypesCntSql = @"SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant + FROM mssql_noda_types + GROUP BY c_datetime2_instant, c_datetimeoffset_instant;"; + public class GetMssqlNodaTypesCntRow + { + public int Cnt { get; set; } + public Instant? CDatetime2Instant { get; set; } + public Instant? CDatetimeoffsetInstant { get; set; } + }; + public async Task GetMssqlNodaTypesCntAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetMssqlNodaTypesCntSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNodaTypesCntRow + { + Cnt = reader.GetInt32(0), + CDatetime2Instant = reader.IsDBNull(1) ? (Instant? )null : DateTime.SpecifyKind(reader.GetDateTime(1), DateTimeKind.Utc).ToInstant(), + CDatetimeoffsetInstant = reader.IsDBNull(2) ? (Instant? )null : reader.GetFieldValue(2).ToInstant() + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetMssqlNodaTypesCntSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetMssqlNodaTypesCntRow + { + Cnt = reader.GetInt32(0), + CDatetime2Instant = reader.IsDBNull(1) ? (Instant? )null : DateTime.SpecifyKind(reader.GetDateTime(1), DateTimeKind.Utc).ToInstant(), + CDatetimeoffsetInstant = reader.IsDBNull(2) ? (Instant? )null : reader.GetFieldValue(2).ToInstant() + }; + } + } + } + + return null; + } + + private const string TruncateMssqlNodaTypesSql = "TRUNCATE TABLE mssql_noda_types;"; + public async Task TruncateMssqlNodaTypesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateMssqlNodaTypesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateMssqlNodaTypesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + } +} \ No newline at end of file diff --git a/examples/MssqlLegacyExample/Utils.cs b/examples/MssqlLegacyExample/Utils.cs new file mode 100644 index 000000000..2c7356eae --- /dev/null +++ b/examples/MssqlLegacyExample/Utils.cs @@ -0,0 +1,15 @@ +// auto-generated by sqlc - do not edit +namespace MssqlLegacyExampleGen +{ + using System; + using System.Linq; + + public static class Utils + { + public static string TransformQueryForSliceArgs(string originalSql, int sliceSize, string paramName) + { + var paramArgs = Enumerable.Range(0, sliceSize).Select(i => $"@{paramName}Arg{i}").ToList(); + return originalSql.Replace($"/*SLICE:{paramName}*/@{paramName}", string.Join(",", paramArgs)); + } + } +} \ No newline at end of file diff --git a/examples/MssqlLegacyExample/request.json b/examples/MssqlLegacyExample/request.json new file mode 100644 index 000000000..754526517 --- /dev/null +++ b/examples/MssqlLegacyExample/request.json @@ -0,0 +1,3670 @@ +{ + "settings": { + "version": "2", + "engine": "mssql", + "schema": [ + "examples/config/mssql/authors/schema.sql", + "examples/config/mssql/types/schema.sql" + ], + "queries": [ + "examples/config/mssql/authors/query.sql", + "examples/config/mssql/types/query.sql" + ], + "codegen": { + "out": "examples/MssqlLegacyExample", + "plugin": "csharp", + "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxMZWdhY3lFeGFtcGxlR2VuIiwib3ZlcnJpZGVzIjpbeyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfaW50IiwiY3NoYXJwX3R5cGUiOnsibm90TnVsbCI6ZmFsc2UsInR5cGUiOiJpbnQifX0seyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfdmFyY2hhciIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOmZhbHNlLCJ0eXBlIjoic3RyaW5nIn19LHsiY29sdW1uIjoiR2V0TXNzcWxGdW5jdGlvbnM6bWF4X2RhdGV0aW1lMiIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOnRydWUsInR5cGUiOiJEYXRlVGltZSJ9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZTJfaW5zdGFudCIsImNzaGFycF90eXBlIjp7Im5vdE51bGwiOmZhbHNlLCJ0eXBlIjoiSW5zdGFudCJ9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZW9mZnNldF9pbnN0YW50IiwiY3NoYXJwX3R5cGUiOnsibm90TnVsbCI6ZmFsc2UsInR5cGUiOiJJbnN0YW50In19XSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0c3RhbmRhcmQyLjAiLCJ1c2VEYXBwZXIiOmZhbHNlfQ==", + "process": { + "cmd": "./dist/LocalRunner" + } + } + }, + "catalog": { + "defaultSchema": "public", + "schemas": [ + { + "name": "public" + }, + { + "name": "public", + "tables": [ + { + "rel": { + "schema": "public", + "name": "authors" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "books" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "author_id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "description", + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "notes" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + } + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "columns": [ + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + } + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + } + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + } + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + } + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + } + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + } + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + } + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + } + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + } + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_string_types" + }, + "columns": [ + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + } + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + } + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + } + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + } + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "columns": [ + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + } + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + } + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + } + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + } + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + } + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_binary_types" + }, + "columns": [ + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + } + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + } + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + } + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "columns": [ + { + "name": "c_geography", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + } + }, + { + "name": "c_geometry", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + } + }, + { + "name": "c_hierarchyid", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "mssql_noda_types" + }, + "columns": [ + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + } + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + } + } + ] + } + ] + }, + { + "name": "sys" + }, + { + "name": "information_schema" + }, + { + "name": "extended", + "tables": [ + { + "rel": { + "schema": "extended", + "name": "bios" + }, + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] + } + ] + }, + "queries": [ + { + "text": "SELECT TOP 1 id, name, bio FROM authors\nWHERE name = @name;", + "name": "GetAuthor", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio\nFROM authors\nORDER BY name\nOFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;", + "name": "ListAuthors", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "offset", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + }, + { + "number": 2, + "column": { + "name": "limit", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);", + "name": "CreateAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);", + "name": "CreateAuthorReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment\nWHERE id = @id;", + "name": "GetAuthorById", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(@name_pattern, '%');", + "name": "GetAuthorByNamePattern", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name_pattern", + "length": -1, + "isNamedParam": true, + "type": {} + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors\nWHERE name = @name;", + "name": "DeleteAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors;", + "name": "DeleteAllAuthors", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "UPDATE authors\nSET bio = @bio\nWHERE bio IS NOT NULL;", + "name": "UpdateAuthors", + "cmd": ":execrows", + "parameters": [ + { + "number": 1, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);", + "name": "GetAuthorsByIds", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);", + "name": "GetAuthorsByIdsAndNames", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "names", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "isSqlcSlice": true, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);", + "name": "CreateBook", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "author_id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "author_id" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "books" + } + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nORDER BY authors.name;", + "name": "ListAllAuthorsBooks", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio\nFROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name\nWHERE authors1.id \u003c authors2.id;", + "name": "GetDuplicateAuthors", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nWHERE books.name = @name;", + "name": "GetAuthorsByBookName", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);", + "name": "CreateNoteReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "body", + "notNull": true, + "length": 200, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "notes" + } + }, + { + "text": "SELECT id, body FROM notes WHERE id = @id;", + "name": "GetNote", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE notes;", + "name": "TruncateNotes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);", + "name": "CreateExtendedBio", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "author_name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "schema": "extended", + "name": "bios" + } + }, + { + "text": "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;", + "name": "GetFirstExtendedBioByType", + "cmd": ":one", + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE extended.bios;", + "name": "TruncateExtendedBios", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", + "name": "InsertMssqlNumericTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_bit", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + } + }, + { + "number": 2, + "column": { + "name": "c_tinyint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + } + }, + { + "number": 3, + "column": { + "name": "c_smallint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + } + }, + { + "number": 4, + "column": { + "name": "c_int", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + } + }, + { + "number": 5, + "column": { + "name": "c_bigint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + } + }, + { + "number": 6, + "column": { + "name": "c_real", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + } + }, + { + "number": 7, + "column": { + "name": "c_float", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + } + }, + { + "number": 8, + "column": { + "name": "c_decimal", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + } + }, + { + "number": 9, + "column": { + "name": "c_numeric", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + } + }, + { + "number": 10, + "column": { + "name": "c_money", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + } + }, + { + "number": 11, + "column": { + "name": "c_smallmoney", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + } + ], + "comments": [ + " Numeric types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_numeric_types" + } + }, + { + "text": "INSERT INTO mssql_numeric_types\n(\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\n)\nVALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);", + "name": "InsertMssqlNumericTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_bit", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + } + }, + { + "number": 2, + "column": { + "name": "c_tinyint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + } + }, + { + "number": 3, + "column": { + "name": "c_smallint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + } + }, + { + "number": 4, + "column": { + "name": "c_int", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + } + }, + { + "number": 5, + "column": { + "name": "c_bigint", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + } + }, + { + "number": 6, + "column": { + "name": "c_real", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + } + }, + { + "number": 7, + "column": { + "name": "c_float", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + } + }, + { + "number": 8, + "column": { + "name": "c_decimal", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + } + }, + { + "number": 9, + "column": { + "name": "c_numeric", + "length": 18, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + } + }, + { + "number": 10, + "column": { + "name": "c_money", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + } + }, + { + "number": 11, + "column": { + "name": "c_smallmoney", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_numeric_types" + } + }, + { + "text": "SELECT TOP 1 c_bit, c_tinyint, c_smallint, c_int, c_bigint, c_real, c_float, c_decimal, c_numeric, c_money, c_smallmoney FROM mssql_numeric_types;", + "name": "GetMssqlNumericTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney\nFROM mssql_numeric_types\nGROUP BY\n c_bit,\n c_tinyint,\n c_smallint,\n c_int,\n c_bigint,\n c_real,\n c_float,\n c_decimal,\n c_numeric,\n c_money,\n c_smallmoney;", + "name": "GetMssqlNumericTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_bit", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bit" + }, + "originalName": "c_bit" + }, + { + "name": "c_tinyint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "tinyint" + }, + "originalName": "c_tinyint" + }, + { + "name": "c_smallint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallint" + }, + "originalName": "c_smallint" + }, + { + "name": "c_int", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "int" + }, + "originalName": "c_int" + }, + { + "name": "c_bigint", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "bigint" + }, + "originalName": "c_bigint" + }, + { + "name": "c_real", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "real" + }, + "originalName": "c_real" + }, + { + "name": "c_float", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "float" + }, + "originalName": "c_float" + }, + { + "name": "c_decimal", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_decimal" + }, + { + "name": "c_numeric", + "length": 18, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "decimal" + }, + "originalName": "c_numeric" + }, + { + "name": "c_money", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "money" + }, + "originalName": "c_money" + }, + { + "name": "c_smallmoney", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_numeric_types" + }, + "type": { + "name": "smallmoney" + }, + "originalName": "c_smallmoney" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_numeric_types;", + "name": "TruncateMssqlNumericTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_string_types\n(\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\n)\nVALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);", + "name": "InsertMssqlStringTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_char", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + } + }, + { + "number": 2, + "column": { + "name": "c_nchar", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + } + }, + { + "number": 3, + "column": { + "name": "c_varchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + } + }, + { + "number": 4, + "column": { + "name": "c_nvarchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + } + }, + { + "number": 5, + "column": { + "name": "c_varchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + } + }, + { + "number": 6, + "column": { + "name": "c_nvarchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + } + ], + "comments": [ + " String types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_string_types" + } + }, + { + "text": "INSERT INTO mssql_string_types\n(\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\n)\nVALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);", + "name": "InsertMssqlStringTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_char", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + } + }, + { + "number": 2, + "column": { + "name": "c_nchar", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + } + }, + { + "number": 3, + "column": { + "name": "c_varchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + } + }, + { + "number": 4, + "column": { + "name": "c_nvarchar", + "length": 100, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + } + }, + { + "number": 5, + "column": { + "name": "c_varchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + } + }, + { + "number": 6, + "column": { + "name": "c_nvarchar_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_string_types" + } + }, + { + "text": "SELECT TOP 1 c_char, c_nchar, c_varchar, c_nvarchar, c_varchar_max, c_nvarchar_max FROM mssql_string_types;", + "name": "GetMssqlStringTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max\nFROM mssql_string_types\nGROUP BY\n c_char,\n c_nchar,\n c_varchar,\n c_nvarchar,\n c_varchar_max,\n c_nvarchar_max;", + "name": "GetMssqlStringTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_char", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "char" + }, + "originalName": "c_char" + }, + { + "name": "c_nchar", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nchar" + }, + "originalName": "c_nchar" + }, + { + "name": "c_varchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar" + }, + { + "name": "c_nvarchar", + "length": 100, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar" + }, + { + "name": "c_varchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "varchar" + }, + "originalName": "c_varchar_max" + }, + { + "name": "c_nvarchar_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_string_types" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "c_nvarchar_max" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_string_types;", + "name": "TruncateMssqlStringTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_datetime_types\n(\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\n)\nVALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);", + "name": "InsertMssqlDatetimeTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_date", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + } + }, + { + "number": 2, + "column": { + "name": "c_datetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + } + }, + { + "number": 3, + "column": { + "name": "c_datetime2", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + } + }, + { + "number": 4, + "column": { + "name": "c_smalldatetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + } + }, + { + "number": 5, + "column": { + "name": "c_datetimeoffset", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + } + }, + { + "number": 6, + "column": { + "name": "c_time", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + } + ], + "comments": [ + " Datetime types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_datetime_types" + } + }, + { + "text": "INSERT INTO mssql_datetime_types\n(\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\n)\nVALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);", + "name": "InsertMssqlDatetimeTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_date", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + } + }, + { + "number": 2, + "column": { + "name": "c_datetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + } + }, + { + "number": 3, + "column": { + "name": "c_datetime2", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + } + }, + { + "number": 4, + "column": { + "name": "c_smalldatetime", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + } + }, + { + "number": 5, + "column": { + "name": "c_datetimeoffset", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + } + }, + { + "number": 6, + "column": { + "name": "c_time", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_datetime_types" + } + }, + { + "text": "SELECT TOP 1 c_date, c_datetime, c_datetime2, c_smalldatetime, c_datetimeoffset, c_time FROM mssql_datetime_types;", + "name": "GetMssqlDatetimeTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time\nFROM mssql_datetime_types\nGROUP BY\n c_date,\n c_datetime,\n c_datetime2,\n c_smalldatetime,\n c_datetimeoffset,\n c_time;", + "name": "GetMssqlDatetimeTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_date", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "date" + }, + "originalName": "c_date" + }, + { + "name": "c_datetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime" + }, + "originalName": "c_datetime" + }, + { + "name": "c_datetime2", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2" + }, + { + "name": "c_smalldatetime", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "smalldatetime" + }, + "originalName": "c_smalldatetime" + }, + { + "name": "c_datetimeoffset", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset" + }, + { + "name": "c_time", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_datetime_types" + }, + "type": { + "name": "time" + }, + "originalName": "c_time" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_datetime_types;", + "name": "TruncateMssqlDatetimeTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_binary_types\n(\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\n)\nVALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);", + "name": "InsertMssqlBinaryTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_binary", + "length": 3, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + } + }, + { + "number": 2, + "column": { + "name": "c_varbinary", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + } + }, + { + "number": 3, + "column": { + "name": "c_varbinary_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + } + }, + { + "number": 4, + "column": { + "name": "c_uniqueidentifier", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + } + ], + "comments": [ + " Binary types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_binary_types" + } + }, + { + "text": "INSERT INTO mssql_binary_types\n(\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\n)\nVALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);", + "name": "InsertMssqlBinaryTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_binary", + "length": 3, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + } + }, + { + "number": 2, + "column": { + "name": "c_varbinary", + "length": 10, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + } + }, + { + "number": 3, + "column": { + "name": "c_varbinary_max", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + } + }, + { + "number": 4, + "column": { + "name": "c_uniqueidentifier", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_binary_types" + } + }, + { + "text": "SELECT TOP 1 c_binary, c_varbinary, c_varbinary_max, c_uniqueidentifier FROM mssql_binary_types;", + "name": "GetMssqlBinaryTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1\n COUNT(*) AS cnt,\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier\nFROM mssql_binary_types\nGROUP BY\n c_binary,\n c_varbinary,\n c_varbinary_max,\n c_uniqueidentifier;", + "name": "GetMssqlBinaryTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_binary", + "length": 3, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "binary" + }, + "originalName": "c_binary" + }, + { + "name": "c_varbinary", + "length": 10, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary" + }, + { + "name": "c_varbinary_max", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "varbinary" + }, + "originalName": "c_varbinary_max" + }, + { + "name": "c_uniqueidentifier", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_binary_types" + }, + "type": { + "name": "uniqueidentifier" + }, + "originalName": "c_uniqueidentifier" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_binary_types;", + "name": "TruncateMssqlBinaryTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nSELECT\n MAX(c_int) AS max_int,\n MAX(c_varchar) AS max_varchar,\n MAX(c_datetime2) AS max_datetime2\nFROM mssql_numeric_types\nCROSS JOIN mssql_string_types\nCROSS JOIN mssql_datetime_types;", + "name": "GetMssqlFunctions", + "cmd": ":one", + "columns": [ + { + "name": "max_int", + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "max_varchar", + "length": -1, + "type": { + "name": "varchar" + } + }, + { + "name": "max_datetime2", + "length": -1, + "type": { + "name": "datetime2" + } + } + ], + "comments": [ + " Functions " + ], + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid);", + "name": "InsertMssqlSpatialTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_geography", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + }, + "originalName": "c_geography" + } + }, + { + "number": 2, + "column": { + "name": "c_geometry", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + }, + "originalName": "c_geometry" + } + }, + { + "number": 3, + "column": { + "name": "c_hierarchyid", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + }, + "originalName": "c_hierarchyid" + } + } + ], + "comments": [ + " Spatial and hierarchyid types " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_spatial_types" + } + }, + { + "text": "SELECT TOP 1 c_geography, c_geometry, c_hierarchyid FROM mssql_spatial_types;", + "name": "GetMssqlSpatialTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_geography", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geography" + }, + "originalName": "c_geography" + }, + { + "name": "c_geometry", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "geometry" + }, + "originalName": "c_geometry" + }, + { + "name": "c_hierarchyid", + "length": -1, + "table": { + "schema": "public", + "name": "mssql_spatial_types" + }, + "type": { + "name": "hierarchyid" + }, + "originalName": "c_hierarchyid" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_spatial_types;", + "name": "TruncateMssqlSpatialTypes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "\nINSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);", + "name": "InsertMssqlNodaTypes", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_datetime2_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + } + }, + { + "number": 2, + "column": { + "name": "c_datetimeoffset_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + } + ], + "comments": [ + " NodaTime Instant overrides " + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_noda_types" + } + }, + { + "text": "INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);", + "name": "InsertMssqlNodaTypesBatch", + "cmd": ":copyfrom", + "parameters": [ + { + "number": 1, + "column": { + "name": "c_datetime2_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + } + }, + { + "number": 2, + "column": { + "name": "c_datetimeoffset_instant", + "length": 7, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "mssql_noda_types" + } + }, + { + "text": "SELECT TOP 1 c_datetime2_instant, c_datetimeoffset_instant FROM mssql_noda_types;", + "name": "GetMssqlNodaTypes", + "cmd": ":one", + "columns": [ + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant\nFROM mssql_noda_types\nGROUP BY c_datetime2_instant, c_datetimeoffset_instant;", + "name": "GetMssqlNodaTypesCnt", + "cmd": ":one", + "columns": [ + { + "name": "cnt", + "notNull": true, + "length": -1, + "type": { + "name": "int" + } + }, + { + "name": "c_datetime2_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetime2" + }, + "originalName": "c_datetime2_instant" + }, + { + "name": "c_datetimeoffset_instant", + "length": 7, + "table": { + "schema": "public", + "name": "mssql_noda_types" + }, + "type": { + "name": "datetimeoffset" + }, + "originalName": "c_datetimeoffset_instant" + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE mssql_noda_types;", + "name": "TruncateMssqlNodaTypes", + "cmd": ":exec", + "filename": "query.sql" + } + ], + "sqlc_version": "v1.31.1", + "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0c3RhbmRhcmQyLjAiLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxMZWdhY3lFeGFtcGxlR2VuIiwidXNlRGFwcGVyIjpmYWxzZSwib3ZlcnJpZGVEYXBwZXJWZXJzaW9uIjoiIiwib3ZlcnJpZGVzIjpbeyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfaW50IiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6ImludCIsIm5vdE51bGwiOmZhbHNlfX0seyJjb2x1bW4iOiJHZXRNc3NxbEZ1bmN0aW9uczptYXhfdmFyY2hhciIsImNzaGFycF90eXBlIjp7InR5cGUiOiJzdHJpbmciLCJub3ROdWxsIjpmYWxzZX19LHsiY29sdW1uIjoiR2V0TXNzcWxGdW5jdGlvbnM6bWF4X2RhdGV0aW1lMiIsImNzaGFycF90eXBlIjp7InR5cGUiOiJEYXRlVGltZSIsIm5vdE51bGwiOnRydWV9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZTJfaW5zdGFudCIsImNzaGFycF90eXBlIjp7InR5cGUiOiJJbnN0YW50Iiwibm90TnVsbCI6ZmFsc2V9fSx7ImNvbHVtbiI6Iio6Y19kYXRldGltZW9mZnNldF9pbnN0YW50IiwiY3NoYXJwX3R5cGUiOnsidHlwZSI6Ikluc3RhbnQiLCJub3ROdWxsIjpmYWxzZX19XSwiZGVidWdSZXF1ZXN0IjpmYWxzZSwidXNlQ2VudHJhbFBhY2thZ2VNYW5hZ2VtZW50IjpmYWxzZSwid2l0aEFzeW5jU3VmZml4Ijp0cnVlLCJ3aXRoQ2FuY2VsbGF0aW9uVG9rZW4iOmZhbHNlLCJ1c2VBc3luY0VudW1lcmFibGUiOmZhbHNlLCJ1c2VQcm9wZXJTaW5ndWxhcml6YXRpb24iOmZhbHNlfQ==" +} \ No newline at end of file diff --git a/examples/MssqlLegacyExample/request.message b/examples/MssqlLegacyExample/request.message new file mode 100644 index 0000000000000000000000000000000000000000..405c94aa4d9a870859d992fd16defe0055b13e38 GIT binary patch literal 31310 zcmeG_U2G)DS?TfbmiNck>pM1`J7^j%=N)Z!y#pwRoy)C%X7`-+j`zm)?Ol$9RquwomCIQ2gZ7f0gFUDmmkXY;ms1d5-w)dd2rzisUQhtk#kRgQ z!akr&-oj3=ggce$K~Z$v9j!)pEA2h++rw`MwQy^HryN4%s1wX~+WX<9U?*&b?Mf$H zjP}~CPpPlX%5E6#RjOeL9)j5*4fsab3@!z&H^O$iUJIjO_SLt8YOAro+l1mb!Va%- zeZN`l)LYGHcDM4@X1&>gDxyD|IF4ZU?V#D}l=d5qV0OFGh@d~>dw^x%dFN6l%r`3S zDvYmhxKXEFZ|?L3x&~P3)OW)|58wiavd>FUVI3+Ypyy|+n;I4%A?j2HK*X{vZz6-j zY;A8xVdnt=e*&;xY43!c>us3rH(Tv{Fon&qgB`6^+O@*v89>2)6fRZv_QJNDs(12J z%)NZ2R*yPYR$A3cqrBe)QgVO!DR*>lf2&ci2IKna-#zu{{k6j$(=TI^?%khfxq9t_ zGxM(c=akc>w#L2Dt@;ko#*s75#N;(QEZ((y(LLqOZ!mvii#9@WTJt937OKb#&@vW$I1vvfqMDn@5gN8=|n$4HF;Va=h=2GEI=)c`m*;oBO+AyI$ooC~_CrNOg0o-l1ddT?P9G z6^@)SB%aQsjGJs6QH2~hz$giwF~zk2tjI>Io9(dD7y#6$H;grq{rLlcd4Y{p zH@6$D${<}D^G0};Bj*$oPXOr_PGT*r)&cJ`ZaISnVD4nuBj-F*&ma+U7hL#nSb&qH zb`0S7ICopk@L&LZ!8mypK2H7wDb&SNZo<3us$R@gboU8>w5T|MWW;T)#wViV>U==3I= zj5{sNzdm|C>77+jMV%w(36}UI$1smGhPH1XU}2)LB2a+m^ zLYLr|nV~cn(z3!z!jebMS=1o-$lDnL%?lFrEyrA30Mj^${BUom|@T z0iUi@uQcQotRwDr!q!f^vUj()S}`ha0Ty*Nl&|p{P$#!t;OE`215>N)d9ck>3e^M( zT&ikuE>$l8^5ZBQ?*gLG+dHV&27r3Tn;;d@0if@IroVMq?3_$W1F==BXdIo7d99$l zEZ~bCl@LFQ)$Br|$}YrEO$ROmbpKo&jJQM5K@?26ZoRqPYVX3-x0;(G77={MamT~A zI$^UG*1W%R*d%!!29GFmudxZ~Nl5pqR!$Y#G8DwwSgC;pR0F3@d1eDweU6PoC-@x9 zOl7s3RHjqjf9KfTT5+YgxbCm7-uAEh_1YyLhjGaVu>SS(>MdWop6t#G#d4AV=s)LQ z!$NcJDTrXq^B|8mJPS5g@Lbw!>E)juf;UTi)L=nlBGX4bzw^i&Vgu|?Zd zd%Ri-5+OYu*IMccT(@>a_>thT|3xfa}~357%EBV(WcMfB!=t z@*yhK@!R;n*0v7;m(Z`ac6Y%xld1ux0Te^MHs_wvYGL635dKK2hT~#a!Ux_Da%|P6 z6S^wmmF1g7e{pqwrMR|OoaRDi6HM1m*lu3(pLzNjkTp+`xhR3szAb)!M76;c5TZ}9 z(_?*! zYkAZrbX2Z=Ykp~U-7jsdtNpvVt#cQV=}LC4c%#er;uWu{c|=Mfocrha#ZmbHi-=foH*=FD-HV)tDt@QJg3K7iQ|m5;Ha5 ztf9C%nXz^huYTEQEMEqz;RZx+5no6EPNsDRfV%ik5DrE#lnTC^V`XZS@nt)I8p~ah zA_Z2_`m)P}0K(F9E)&+-0xSZWHrZUt3Z|=By!99*2@=k)a8^(bHAe+m3KNy7nc58v z{Coc6fZ3bfmI?vODbTTH9n0%{vM!|~1`h!paA_YPXSf@dlB07}_ zamW)Ul0igBhv47IfKfv8+TLe#EKf`#Y)QBa{O8yrm9Fp@5M>EQDV^Z^8&+_w-^`9~ z1Ij-SYB8N?V?&R~W}sO0AgV-g;XTV9UoUTz7SW?vpI=yks4%9ifWxrfhDD^#YCHT^q znK^e#@h3sQjZOb&f_)>$D5F{u!q_zP;UCWlyCffFmX>-|IacZ&@}bnD$c|EPn5?9p zdBTx;k*P!KRi+84SDs&^UgC@!c9H4eyXc;OO{blKtcX*__zU(5KgurQBkvDBh|b`X z>IpupZs4=%13twaKz9}rQp2`L?=6WuIwW(2?fgqX4>!{pb->}Nu)E$ak zyg0Sa6Cd)Q0s-`@JH`3aCVERbd<)@8RPbsFFZYI3B(UtH$nAM)Y&2xNQNa`v)8#}1#af8>z-Jx_nm zzdAk&>L;Dem)(fW=R1^j(V>)ieV+UTx^2u|v*q|@LkHW2)A^yw5TxmTp8A-tkB>u! z0nPo(1`QM?83!qG)dcOv>9K=eP^Mf5LJI@P=0so834?_j&#+C9+$m=>c%{fQ39Q2Zx4AX`+L>gGfT zKo)!-BL6?e*^vtS371b=&BSP?HZwPxF8Q&=tPEx?G_@wjQgRybkt;-eh;3!zLR$O_ za96-vyoFo!q!JSbw8$n{PTS7+X*mb4*v;iEaD5od!6&*{$62uZlZydIf@uq9LGEG+ z2OC!NHw#*p@f+N-n7dg(y9>JDMVo<}1+S-h8@y~-v{|rB0n^i-6_6$I53;cZE#;n~ zOQ3=C5?Lk5>UvLWH3+*Js9A6aF;9sw@_xYBf97bQiN_EYS2s%Q)A@`3{F+~-;Q}2w zc#p&wc$%g~Y_S{V)s5SbviV+(j)7fS_dV0>h{tzVZB%TVyk5 zCS1_+9Ge)EvhM4|xD@{(UxV=N23kLIZ|}fbtQSPt|AM{TjyC z9(zbL+o!`$Y(=&2Pw^6^3TJ&mnwD_ilxNv#?EU^2`y1yG<=79{IW`rBJ7HZea8Fn_ z!A-=sdibnMbn}9pRsUdw-J|XjL{U&h@Cb`~kV2P#BhT`Y>r4#Dkarq>p$bZ5qH(QH zwVepQ+=vxjhQa_}9{bis%>_4!YRiC>sn~)wLziWMBeFhLR%uNZ+%Z*G22?7+iDt4W zu3)FTqRME5blp@zRy7se>!Fe|TB5^AFo#_s5i0h+JIdZQqKduzdrrX8V?Iu6ka+2o zx|2@&Luy{^zDY9CK%VvqtJSME%Z2W!#-$@A(|#mb?KTUn&KxEAIroC$%TcMGPSpFC zG4@Y3S59r$7s)8vnZwV@8&gG+?*;XN$NsyO68m^nQ9LNa^yqj?=$>f+^raAI_gH_I z(6I`F2Dk14F%p+x6!_V2?0x|pyW6Pm5TI!iWSxNm$`31{^25J8YXcv^(vqL@dyua4 z8mbulj%^Rt0tjeT5t%?e^BfxW0_De3`Ci#dxWrkPsmbC8KH*tUAM*ZrjQyj<6I4|6$T0j< z-55UQ7oKoF<_sB!w488$C{)OMlkv zA$X`SsY(mEG3P$6*7BF6XrhbxC5wK|)JWa6Yw*b~8Z{GUfBF=b^{ImEo$Lt9tx=q7leFVa&W!Ba>r(}^C)+2Cnec=+QX-+W*_5mM|+z@PF>a6-9C(|xxu zU>a?JO4G&BEYvTy<08=^)CKaNcfbdIz1IPfnIkk>Po6}HCq2fU1E!USk0;CJe*-v)0=EdHBb9H6t|eSd^~&Emb$GVfXPr|WcL*qr=R z{@4j0U@k0dQ$wbeoc?I@0nr=rq8J^&_q)UT6va4m8S#T|i3CWLutXMGh}NFvR}c zfdk?25MaIldu#sXX}-gQ=U@X;aL=q@2JsV!Ra`ICKg)MoL>co1T}XdngfV26yYz|| z%d2Z^;$R(-i%6HRSn5{=QPhTGAC#Y~Z&Q*o`e`;MM?n@>bVjqfv?I96Ca{GzZ_=o*b*q|apXSCjy_c! zHJdsB9G}S<-sHp#qXPIKaSeE(Rm^?sYob}h8R>vCQtfsgI+)6-O>E}j<$x94chpz< zC3!qLdJOtU*lC^)6}!uG|y8% zG|y$nJd?!n>Slq$Pxun~KHM$klPR)z1bQQ{foku!huEz|pcjTG&am%6Hq;C<0FwP! zvTrnmTj@p<$3_uiGnJYlLI7DU^f&zh&m1L~9-xH*Cz~23-SV&#s5kSl4g9BrAzvJ3 zziJDHH1%=)C_}V7Qit^;QK*k1u&;hGM2RY*5BcY7?&@ozA`lH(Z!19u3~BN!4v3R` z%zkwMFj>Es2OQcUSXZWP1@m8ZK)(E9ue$~^`jU==cfGfu`wDT_DupL~jyYJ{^#PlB zFP^V&6r}oN0SsoLI^(JKzCXl1ZwU>=C4Pv+@O~RHq#XSds=%8H<)VI9svuItI`99G zV=pG;zhY9ty4_fws)3kPh?T`DlAmZJazg7}xcK;aIC!TQr}k{`IQW#{-TU?s`- z{+X+nujYSq^lg0$>{7c9_rbmtwj;P`2JR4rTSM*F%l2{ID4xd#wnF9&w8?8p<2z}^ zZKXzUv~s!c>uNh78DM1KIl|!JbU<=|D?G#d0D98Dv%A8SYMX-O7x{LFX&O@Z0T`~UH7_!jHwObHEW)J??MmZz zrFyTj6W*#c;s1D%gT7t(&3fnVe00#PuI+Dc*WXgt9pmT4O0ya^8vK%^_0~PO-dJ5p m41jr^B6sU6TMeLqxXs(`7F>$E2Db|DH!AJ=AqSxD$o)UnNsu!D literal 0 HcmV?d00001 diff --git a/examples/QuickStartMssqlDalGen/Models.cs b/examples/QuickStartMssqlDalGen/Models.cs new file mode 100644 index 000000000..7599d046f --- /dev/null +++ b/examples/QuickStartMssqlDalGen/Models.cs @@ -0,0 +1,9 @@ +// auto-generated by sqlc - do not edit +using System; +using System.Linq; + +namespace QuickStartMssqlDalGen; +public readonly record struct Author(long Id, string Name, string? Bio); +public readonly record struct Book(long Id, string Name, long AuthorId, string? Description); +public readonly record struct Note(int Id, string Body); +public readonly record struct ExtendedBio(string AuthorName, string Name, string? BioType); \ No newline at end of file diff --git a/examples/QuickStartMssqlDalGen/QuerySql.cs b/examples/QuickStartMssqlDalGen/QuerySql.cs new file mode 100644 index 000000000..df6a2d330 --- /dev/null +++ b/examples/QuickStartMssqlDalGen/QuerySql.cs @@ -0,0 +1,867 @@ +// auto-generated by sqlc - do not edit +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable UseAwaitUsing +// ReSharper disable ConvertToUsingDeclaration +// ReSharper disable NotAccessedPositionalProperty.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading; +using System.Threading.Tasks; + +namespace QuickStartMssqlDalGen; +public class QuerySql +{ + public QuerySql() + { + } + + public QuerySql(string connectionString) : this() + { + this.ConnectionString = connectionString; + } + + private QuerySql(SqlTransaction transaction) : this() + { + this.Transaction = transaction; + } + + public static QuerySql WithTransaction(SqlTransaction transaction) + { + return new QuerySql(transaction); + } + + private SqlTransaction? Transaction { get; } + private string? ConnectionString { get; } + + private const string GetAuthorSql = @"SELECT TOP 1 id, name, bio FROM authors + WHERE name = @name;"; + public readonly record struct GetAuthorRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorArgs(string Name); + public async Task GetAuthorAsync(GetAuthorArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string ListAuthorsSql = @"SELECT id, name, bio + FROM authors + ORDER BY name + OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;"; + public readonly record struct ListAuthorsRow(long Id, string Name, string? Bio); + public readonly record struct ListAuthorsArgs(int Offset, int Limit); + public async Task> ListAuthorsAsync(ListAuthorsArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(ListAuthorsSql, connection)) + { + command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset; + command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAuthorsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAuthorsSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset; + command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAuthorsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string CreateAuthorSql = "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);"; + public readonly record struct CreateAuthorArgs(long Id, string Name, string? Bio); + public async Task CreateAuthorAsync(CreateAuthorArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateAuthorSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string CreateAuthorReturnIdSql = "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);"; + public readonly record struct CreateAuthorReturnIdRow(long Id); + public readonly record struct CreateAuthorReturnIdArgs(string Name, string? Bio); + public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateAuthorReturnIdSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateAuthorReturnIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + + private const string GetAuthorByIdSql = @"SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment + WHERE id = @id;"; + public readonly record struct GetAuthorByIdRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorByIdArgs(long Id); + public async Task GetAuthorByIdAsync(GetAuthorByIdArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorByIdSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdRow + { + Id = reader.GetInt64(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string GetAuthorByNamePatternSql = @"SELECT id, name, bio FROM authors + WHERE name LIKE COALESCE(@name_pattern, '%');"; + public readonly record struct GetAuthorByNamePatternRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorByNamePatternArgs(object? NamePattern); + public async Task> GetAuthorByNamePatternAsync(GetAuthorByNamePatternArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorByNamePatternSql, connection)) + { + command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorByNamePatternRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByNamePatternSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorByNamePatternRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string DeleteAuthorSql = @"DELETE FROM authors + WHERE name = @name;"; + public readonly record struct DeleteAuthorArgs(string Name); + public async Task DeleteAuthorAsync(DeleteAuthorArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(DeleteAuthorSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = DeleteAuthorSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + await command.ExecuteNonQueryAsync(); + } + } + + private const string DeleteAllAuthorsSql = "DELETE FROM authors;"; + public async Task DeleteAllAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(DeleteAllAuthorsSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = DeleteAllAuthorsSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string UpdateAuthorsSql = @"UPDATE authors + SET bio = @bio + WHERE bio IS NOT NULL;"; + public readonly record struct UpdateAuthorsArgs(string? Bio); + public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(UpdateAuthorsSql, connection)) + { + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + return await command.ExecuteNonQueryAsync(); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = UpdateAuthorsSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value; + return await command.ExecuteNonQueryAsync(); + } + } + + private const string GetAuthorsByIdsSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);"; + public readonly record struct GetAuthorsByIdsRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorsByIdsArgs(long[] Ids); + public async Task> GetAuthorsByIdsAsync(GetAuthorsByIdsArgs args) + { + var transformedSql = GetAuthorsByIdsSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(transformedSql, connection)) + { + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = transformedSql; + command.Transaction = this.Transaction; + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string GetAuthorsByIdsAndNamesSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);"; + public readonly record struct GetAuthorsByIdsAndNamesRow(long Id, string Name, string? Bio); + public readonly record struct GetAuthorsByIdsAndNamesArgs(long[] Ids, string[] Names); + public async Task> GetAuthorsByIdsAndNamesAsync(GetAuthorsByIdsAndNamesArgs args) + { + var transformedSql = GetAuthorsByIdsAndNamesSql; + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids"); + transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Names.Length, "names"); + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(transformedSql, connection)) + { + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsAndNamesRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = transformedSql; + command.Transaction = this.Transaction; + for (int i = 0; i < args.Ids.Length; i++) + command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]); + for (int i = 0; i < args.Names.Length; i++) + command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]); + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByIdsAndNamesRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }); + return result; + } + } + } + + private const string CreateBookSql = "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);"; + public readonly record struct CreateBookRow(long Id); + public readonly record struct CreateBookArgs(string Name, long AuthorId); + public async Task CreateBookAsync(CreateBookArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateBookSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateBookSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt64(result); + } + } + + private const string ListAllAuthorsBooksSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + ORDER BY authors.name;"; + public readonly record struct ListAllAuthorsBooksRow(Author? Author, Book? Book); + public async Task> ListAllAuthorsBooksAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(ListAllAuthorsBooksSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = ListAllAuthorsBooksSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + + private const string GetDuplicateAuthorsSql = @"SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio + FROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name + WHERE authors1.id < authors2.id;"; + public readonly record struct GetDuplicateAuthorsRow(Author? Author, Author? Author2); + public async Task> GetDuplicateAuthorsAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetDuplicateAuthorsSql, connection)) + { + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetDuplicateAuthorsSql; + command.Transaction = this.Transaction; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } }); + return result; + } + } + } + + private const string GetAuthorsByBookNameSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description + FROM authors JOIN books ON authors.id = books.author_id + WHERE books.name = @name;"; + public readonly record struct GetAuthorsByBookNameRow(long Id, string Name, string? Bio, Book? Book); + public readonly record struct GetAuthorsByBookNameArgs(string Name); + public async Task> GetAuthorsByBookNameAsync(GetAuthorsByBookNameArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection)) + { + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorsByBookNameSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + using (var reader = await command.ExecuteReaderAsync()) + { + var result = new List(); + while (await reader.ReadAsync()) + result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } }); + return result; + } + } + } + + private const string CreateNoteReturnIdSql = "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);"; + public readonly record struct CreateNoteReturnIdRow(int Id); + public readonly record struct CreateNoteReturnIdArgs(string Body); + public async Task CreateNoteReturnIdAsync(CreateNoteReturnIdArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateNoteReturnIdSql, connection)) + { + command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt32(result); + } + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateNoteReturnIdSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body; + var result = await command.ExecuteScalarAsync(); + return Convert.ToInt32(result); + } + } + + private const string GetNoteSql = "SELECT id, body FROM notes WHERE id = @id;"; + public readonly record struct GetNoteRow(int Id, string Body); + public readonly record struct GetNoteArgs(int Id); + public async Task GetNoteAsync(GetNoteArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetNoteSql, connection)) + { + command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetNoteRow + { + Id = reader.GetInt32(0), + Body = reader.GetString(1) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetNoteSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetNoteRow + { + Id = reader.GetInt32(0), + Body = reader.GetString(1) + }; + } + } + } + + return null; + } + + private const string TruncateNotesSql = "TRUNCATE TABLE notes;"; + public async Task TruncateNotesAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateNotesSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateNotesSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } + + private const string CreateExtendedBioSql = "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);"; + public readonly record struct CreateExtendedBioArgs(string AuthorName, string Name, string? BioType); + public async Task CreateExtendedBioAsync(CreateExtendedBioArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(CreateExtendedBioSql, connection)) + { + command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = CreateExtendedBioSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName; + command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + await command.ExecuteNonQueryAsync(); + } + } + + private const string GetFirstExtendedBioByTypeSql = "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;"; + public readonly record struct GetFirstExtendedBioByTypeRow(string AuthorName, string Name, string? BioType); + public readonly record struct GetFirstExtendedBioByTypeArgs(string? BioType); + public async Task GetFirstExtendedBioByTypeAsync(GetFirstExtendedBioByTypeArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(GetFirstExtendedBioByTypeSql, connection)) + { + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetFirstExtendedBioByTypeRow + { + AuthorName = reader.GetString(0), + Name = reader.GetString(1), + BioType = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + }; + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetFirstExtendedBioByTypeSql; + command.Transaction = this.Transaction; + command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value; + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetFirstExtendedBioByTypeRow + { + AuthorName = reader.GetString(0), + Name = reader.GetString(1), + BioType = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + + return null; + } + + private const string TruncateExtendedBiosSql = "TRUNCATE TABLE extended.bios;"; + public async Task TruncateExtendedBiosAsync() + { + if (this.Transaction == null) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqlCommand(TruncateExtendedBiosSql, connection)) + { + await command.ExecuteNonQueryAsync(); + } + + return; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open) + throw new InvalidOperationException("Transaction is provided, but its connection is null."); + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = TruncateExtendedBiosSql; + command.Transaction = this.Transaction; + await command.ExecuteNonQueryAsync(); + } + } +} \ No newline at end of file diff --git a/examples/QuickStartMssqlDalGen/QuickStartMssqlDalGen.csproj b/examples/QuickStartMssqlDalGen/QuickStartMssqlDalGen.csproj new file mode 100644 index 000000000..49322980c --- /dev/null +++ b/examples/QuickStartMssqlDalGen/QuickStartMssqlDalGen.csproj @@ -0,0 +1,20 @@ + + + + + + net8.0 + QuickStartMssqlDalGen + Library + enable + False + + + + + + + + \ No newline at end of file diff --git a/examples/QuickStartMssqlDalGen/Utils.cs b/examples/QuickStartMssqlDalGen/Utils.cs new file mode 100644 index 000000000..ba2bb36d9 --- /dev/null +++ b/examples/QuickStartMssqlDalGen/Utils.cs @@ -0,0 +1,13 @@ +// auto-generated by sqlc - do not edit +using System; +using System.Linq; + +namespace QuickStartMssqlDalGen; +public static class Utils +{ + public static string TransformQueryForSliceArgs(string originalSql, int sliceSize, string paramName) + { + var paramArgs = Enumerable.Range(0, sliceSize).Select(i => $"@{paramName}Arg{i}").ToList(); + return originalSql.Replace($"/*SLICE:{paramName}*/@{paramName}", string.Join(",", paramArgs)); + } +} \ No newline at end of file diff --git a/examples/QuickStartMssqlDalGen/request.json b/examples/QuickStartMssqlDalGen/request.json new file mode 100644 index 000000000..4e5e8834a --- /dev/null +++ b/examples/QuickStartMssqlDalGen/request.json @@ -0,0 +1,1225 @@ +{ + "settings": { + "version": "2", + "engine": "mssql", + "schema": [ + "examples/config/mssql/authors/schema.sql" + ], + "queries": [ + "examples/config/mssql/authors/query.sql" + ], + "codegen": { + "out": "examples/QuickStartMssqlDalGen", + "plugin": "csharp", + "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWV9", + "process": { + "cmd": "./dist/LocalRunner" + } + } + }, + "catalog": { + "defaultSchema": "public", + "schemas": [ + { + "name": "public" + }, + { + "name": "public", + "tables": [ + { + "rel": { + "schema": "public", + "name": "authors" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "books" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "author_id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + } + }, + { + "name": "description", + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + } + } + ] + }, + { + "rel": { + "schema": "public", + "name": "notes" + }, + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + } + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] + }, + { + "name": "sys" + }, + { + "name": "information_schema" + }, + { + "name": "extended", + "tables": [ + { + "rel": { + "schema": "extended", + "name": "bios" + }, + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + } + } + ] + } + ] + } + ] + }, + "queries": [ + { + "text": "SELECT TOP 1 id, name, bio FROM authors\nWHERE name = @name;", + "name": "GetAuthor", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio\nFROM authors\nORDER BY name\nOFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;", + "name": "ListAuthors", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "offset", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + }, + { + "number": 2, + "column": { + "name": "limit", + "notNull": true, + "length": -1, + "isNamedParam": true, + "type": { + "name": "int" + } + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);", + "name": "CreateAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);", + "name": "CreateAuthorReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "authors" + } + }, + { + "text": "SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment\nWHERE id = @id;", + "name": "GetAuthorById", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(@name_pattern, '%');", + "name": "GetAuthorByNamePattern", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name_pattern", + "length": -1, + "isNamedParam": true, + "type": {} + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors\nWHERE name = @name;", + "name": "DeleteAuthor", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "DELETE FROM authors;", + "name": "DeleteAllAuthors", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "UPDATE authors\nSET bio = @bio\nWHERE bio IS NOT NULL;", + "name": "UpdateAuthors", + "cmd": ":execrows", + "parameters": [ + { + "number": 1, + "column": { + "name": "bio", + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);", + "name": "GetAuthorsByIds", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);", + "name": "GetAuthorsByIdsAndNames", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "ids", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "isSqlcSlice": true, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "names", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "isSqlcSlice": true, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);", + "name": "CreateBook", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 2, + "column": { + "name": "author_id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "bigint" + }, + "originalName": "author_id" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "books" + } + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nORDER BY authors.name;", + "name": "ListAllAuthorsBooks", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio\nFROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name\nWHERE authors1.id \u003c authors2.id;", + "name": "GetDuplicateAuthors", + "cmd": ":many", + "columns": [ + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + }, + { + "name": "authors", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "authors" + } + } + ], + "filename": "query.sql" + }, + { + "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nWHERE books.name = @name;", + "name": "GetAuthorsByBookName", + "cmd": ":many", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "bigint" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": 255, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "schema": "public", + "name": "authors" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio" + }, + { + "name": "books", + "length": -1, + "type": {}, + "embedTable": { + "schema": "public", + "name": "books" + } + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "name", + "notNull": true, + "length": 255, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "books" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + } + ], + "filename": "query.sql" + }, + { + "text": "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);", + "name": "CreateNoteReturnId", + "cmd": ":execlastid", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "body", + "notNull": true, + "length": 200, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "name": "notes" + } + }, + { + "text": "SELECT id, body FROM notes WHERE id = @id;", + "name": "GetNote", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + }, + { + "name": "body", + "notNull": true, + "length": 200, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "body" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "schema": "public", + "name": "notes" + }, + "type": { + "name": "int" + }, + "originalName": "id" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE notes;", + "name": "TruncateNotes", + "cmd": ":exec", + "filename": "query.sql" + }, + { + "text": "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);", + "name": "CreateExtendedBio", + "cmd": ":exec", + "parameters": [ + { + "number": 1, + "column": { + "name": "author_name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + } + }, + { + "number": 2, + "column": { + "name": "name", + "notNull": true, + "length": 100, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + } + }, + { + "number": 3, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql", + "insert_into_table": { + "schema": "extended", + "name": "bios" + } + }, + { + "text": "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;", + "name": "GetFirstExtendedBioByType", + "cmd": ":one", + "columns": [ + { + "name": "author_name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "author_name" + }, + { + "name": "name", + "notNull": true, + "length": 100, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "name" + }, + { + "name": "bio_type", + "length": 20, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "bio_type", + "length": 20, + "isNamedParam": true, + "table": { + "schema": "extended", + "name": "bios" + }, + "type": { + "name": "nvarchar" + }, + "originalName": "bio_type" + } + } + ], + "filename": "query.sql" + }, + { + "text": "TRUNCATE TABLE extended.bios;", + "name": "TruncateExtendedBios", + "cmd": ":exec", + "filename": "query.sql" + } + ], + "sqlc_version": "v1.31.1", + "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OC4wIiwibmFtZXNwYWNlTmFtZSI6IiIsInVzZURhcHBlciI6ZmFsc2UsIm92ZXJyaWRlRGFwcGVyVmVyc2lvbiI6IiIsIm92ZXJyaWRlcyI6bnVsbCwiZGVidWdSZXF1ZXN0IjpmYWxzZSwidXNlQ2VudHJhbFBhY2thZ2VNYW5hZ2VtZW50IjpmYWxzZSwid2l0aEFzeW5jU3VmZml4Ijp0cnVlLCJ3aXRoQ2FuY2VsbGF0aW9uVG9rZW4iOmZhbHNlLCJ1c2VBc3luY0VudW1lcmFibGUiOmZhbHNlLCJ1c2VQcm9wZXJTaW5ndWxhcml6YXRpb24iOmZhbHNlfQ==" +} \ No newline at end of file diff --git a/examples/QuickStartMssqlDalGen/request.message b/examples/QuickStartMssqlDalGen/request.message new file mode 100644 index 0000000000000000000000000000000000000000..a625eb4b09e5edc951a3475e8e98dcbc6b7ae1bb GIT binary patch literal 8108 zcmeHMO>^7E84gI%qSkKhz)I`xOgb3Mq?S2~WUB4dCF58k3CW6RQX#1*=|usFrGyFu z7yz^-WipeQ9D3_PJ@wdA&+YW_W6<@ zjfi_p1-m=w$C2o}BeQqd_I1~PM3<}S)&{ZVD?RT(cZc}3k5z+oNBb=Tqdi|O`R<5( zQkX~b(z0QC{&L;v=~ioG+r*WBGb>*ij=GlFQ}fZ)S2N)y{+EPajc#TG1>|3%jA`7G z?tT;gNv*V0H;=D$&3)7MPo+ExF6Fn-jI9p{9XxRm!g>3#?)E^HQ|UU2ZgBS2Hp2sq?aI+I`0z=v1ve9xv(_lAI?;KCumA;D1W! zhHyIrZsEzR=xP9G;A2KBCDe`W;QM3uz?oYHcy{QX&ph-#%EKo1{No|HJ3npR8{>7s zEi<>~@z)ttZfkX|+EF^qZKbG~#-c*iSX5wIWxdsWqy+JYb{}djjlERvE2~tfEYEEa ze~l#=JX3OPqTWxN?z|v9fshsgRjouwObyf%hFLmX)pwpTTu*3ppGuRwv(U&(a!uIN zy=m#=tX*NY+tOT=Tk+qr=)(Z|80aVldb3s2T1w>uhC6DmueY_1vg-8v9`Th{bGNOm zYn|#trJ=nK&n8)`$Pze^ zD%{Oy@-j|T( z&Iv0_BTj&{GKa$|kH5^IhZE6_veF%;xzpL+q3Ba1XtgEN7=z8l0-WbXoLj^nx%Q@k zuQI$X-SbW3Iiul4CkVhg^W9&A zQR8#1Ml6!d;4iUTD53cYTPw>qf_+%lxXRONBI@;L3H@wmyS4^;>HIK=Xcs{nJkSSz zg7~EmHrq<0*-;uh^*V&{&d`X;l!xb7-gKQO9;7Mh<)$h8Sr%2MhJwO-Qb;_T4P{}u z(5`P*wUTLgg=NS?9(e!t$ooB-iY{vtv<3F4z)8sP679^nN8|%3**?%2MA!Lj2KArY z3d&le#`@Qom1u}kJF#D|)DIJuTC)wBuKsT^*Bes+lPy+gLD#T>atuo90MgX%3?@3` z{Dk>5q8I!-gIZ!w#<&Y*fiAZgq!geMF4ULt1Vp9FGUDA?1(=X{PgYKK!Qzpgtv-e% z;lhPF3x)7N&7ctY06ti3Q_q5dJumzUkli|qF9DQOQ;$3IG07sFaPj!5fN*6Nb3zFJ zDTDqh1rZk{YiMsnl)WVpb8iVUHXPI`lf;Dh6&K;x5!|7eK-|O*n#wy(NTFzm5jp_j4E=xg1Zo|6#+WFD4-XuLs1GUZ8!_X z_pPx6ZJlpI8mx_mz>Nx(H#oUJ1Bw?H6_Nbo4Ejd`NfDknI{zc@<0YTFH-(h;Fx&y> z#R@{*uK}+^54>E&)CHsD#U?}1Q@)Z=Su6>RXH^y`2YY@UU=n3n=DEKCeDCCQ`OnX- z0U`&?3jpVVLxm$QIG&~~L#P`f{8Ofg@SOmDVHaHQ zCEF5jpw|;|8M91To)6_QYjaoj@?wTXXk=`IYQ-s27R9-*a~&oS~y zO&<=4tCsq@<&nklg6wf@!Ei60TCzu$wV3$m983UHR)Lmub!%Ji9qRk!k#56(N>xUS zJTd))HSgH&wMYHFc@*xQKCbF^k60G_4A^lF2`D?Bn2piw(Et?hTA;)D)wb(^8g0|w PA6dFOCu literal 0 HcmV?d00001 diff --git a/examples/config/mssql/authors/query.sql b/examples/config/mssql/authors/query.sql index 13062b791..82509712b 100644 --- a/examples/config/mssql/authors/query.sql +++ b/examples/config/mssql/authors/query.sql @@ -57,3 +57,21 @@ WHERE authors1.id < authors2.id; SELECT authors.*, sqlc.embed(books) FROM authors JOIN books ON authors.id = books.author_id WHERE books.name = @name; + +-- name: CreateNoteReturnId :execlastid +INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body); + +-- name: GetNote :one +SELECT id, body FROM notes WHERE id = @id; + +-- name: TruncateNotes :exec +TRUNCATE TABLE notes; + +-- name: CreateExtendedBio :exec +INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type); + +-- name: GetFirstExtendedBioByType :one +SELECT TOP 1 * FROM extended.bios WHERE bio_type = @bio_type; + +-- name: TruncateExtendedBios :exec +TRUNCATE TABLE extended.bios; diff --git a/examples/config/mssql/authors/schema.sql b/examples/config/mssql/authors/schema.sql index fb03778a8..d2fccb5a6 100644 --- a/examples/config/mssql/authors/schema.sql +++ b/examples/config/mssql/authors/schema.sql @@ -14,3 +14,18 @@ CREATE TABLE books ( description NVARCHAR(MAX), FOREIGN KEY (author_id) REFERENCES authors (id) ON DELETE CASCADE ); + +CREATE TABLE notes ( + id INT IDENTITY(1, 1) NOT NULL PRIMARY KEY, + body NVARCHAR(200) NOT NULL +); +GO + +CREATE SCHEMA extended; +GO + +CREATE TABLE extended.bios ( + author_name NVARCHAR(100) NOT NULL, + name NVARCHAR(100) NOT NULL, + bio_type NVARCHAR(20) NULL +); diff --git a/examples/config/mssql/types/query.sql b/examples/config/mssql/types/query.sql index ef382a94c..f11da9558 100644 --- a/examples/config/mssql/types/query.sql +++ b/examples/config/mssql/types/query.sql @@ -220,3 +220,33 @@ SELECT FROM mssql_numeric_types CROSS JOIN mssql_string_types CROSS JOIN mssql_datetime_types; + +/* Spatial and hierarchyid types */ + +-- name: InsertMssqlSpatialTypes :exec +INSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid); + +-- name: GetMssqlSpatialTypes :one +SELECT TOP 1 * FROM mssql_spatial_types; + +-- name: TruncateMssqlSpatialTypes :exec +TRUNCATE TABLE mssql_spatial_types; + +/* NodaTime Instant overrides */ + +-- name: InsertMssqlNodaTypes :exec +INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant); + +-- name: InsertMssqlNodaTypesBatch :copyfrom +INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant); + +-- name: GetMssqlNodaTypes :one +SELECT TOP 1 * FROM mssql_noda_types; + +-- name: GetMssqlNodaTypesCnt :one +SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant +FROM mssql_noda_types +GROUP BY c_datetime2_instant, c_datetimeoffset_instant; + +-- name: TruncateMssqlNodaTypes :exec +TRUNCATE TABLE mssql_noda_types; diff --git a/examples/config/mssql/types/schema.sql b/examples/config/mssql/types/schema.sql index 6db50cb4b..90dc1e66d 100644 --- a/examples/config/mssql/types/schema.sql +++ b/examples/config/mssql/types/schema.sql @@ -36,3 +36,14 @@ CREATE TABLE mssql_binary_types ( c_varbinary_max VARBINARY(MAX), c_uniqueidentifier UNIQUEIDENTIFIER ); + +CREATE TABLE mssql_spatial_types ( + c_geography GEOGRAPHY NULL, + c_geometry GEOMETRY NULL, + c_hierarchyid HIERARCHYID NULL +); + +CREATE TABLE mssql_noda_types ( + c_datetime2_instant DATETIME2 NULL, + c_datetimeoffset_instant DATETIMEOFFSET NULL +); diff --git a/scripts/sqlc-patches/README.md b/scripts/sqlc-patches/README.md index ab99c9f7b..09fd9117d 100644 --- a/scripts/sqlc-patches/README.md +++ b/scripts/sqlc-patches/README.md @@ -1,17 +1,17 @@ # sqlc patches for SQL Server (mssql) support -The mssql engine in sqlc `main` (v1.31.1+) is missing a few things this plugin relies on. These patches are -applied on top of sqlc commit `3ad8b20fb` and are meant to be upstreamed: +The mssql engine on sqlc `main` is missing a few things this plugin relies on. These patches apply on top of the +sqlc commit in [`SQLC_REF`](SQLC_REF) and are meant to be upstreamed: 1. `0001` - the mssql dialect for the query preprocessor (`sqlc.arg`, `sqlc.narg`, `sqlc.slice`, `sqlc.embed` - become `@name` / `/*SLICE:name*/@name` / `table.*`) and `sqlc.embed` support in the core analysis path. + become `@name` / `/*SLICE:name*/@name` / `table.*`) and `sqlc.embed` support in the core analysis path + (which every engine on that path lacked). 2. `0002` - `INSERT ... VALUES` gets an empty FROM clause (without it `:copyfrom` panics with a nil dereference). 3. `0003` - `TRUNCATE TABLE` (statements the converter does not know are silently dropped from the output). +4. `0004` - parameter names are case-insensitive (`@Name` and `@name` are one parameter), as in SQL Server. -Build a patched sqlc: +`build_sqlc.sh [dir]` builds `dir/sqlc-upstream` (the pinned commit as is) and `dir/sqlc` (with the patches); CI +(`.github/workflows/mssql.yml`) and `make sqlc-generate-mssql` / `make mssql-syntax-matrix` use them. -```bash -git clone https://github.com/sqlc-dev/sqlc && cd sqlc && git checkout 3ad8b20fb -git am /path/to/sqlc-gen-csharp/scripts/sqlc-patches/*.patch -go build -o /usr/local/bin/sqlc ./cmd/sqlc -``` +Everything else sqlc misses for SQL Server is listed, case by case, in the +[T-SQL syntax matrix](../../end2end/MssqlSyntaxMatrix/REPORT.md). diff --git a/scripts/sqlc-patches/SQLC_REF b/scripts/sqlc-patches/SQLC_REF new file mode 100644 index 000000000..4edb6ae75 --- /dev/null +++ b/scripts/sqlc-patches/SQLC_REF @@ -0,0 +1 @@ +3ad8b20fb02b5bea81591b673da0758b8685d951 diff --git a/scripts/sqlc-patches/build_sqlc.sh b/scripts/sqlc-patches/build_sqlc.sh new file mode 100755 index 000000000..515b336b9 --- /dev/null +++ b/scripts/sqlc-patches/build_sqlc.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Builds sqlc at the pinned commit twice: as is (bin/sqlc-upstream) and with the patches in this directory (bin/sqlc). +# Usage: scripts/sqlc-patches/build_sqlc.sh [output dir, default ./bin] +set -euo pipefail + +PATCHES_DIR="$(cd "$(dirname "$0")" && pwd)" +OUT_DIR="$(mkdir -p "${1:-bin}" && cd "${1:-bin}" && pwd)" +REF="$(cat "$PATCHES_DIR/SQLC_REF")" +SRC="$(mktemp -d)" + +git clone --quiet https://github.com/sqlc-dev/sqlc "$SRC" +git -C "$SRC" checkout --quiet "$REF" +(cd "$SRC" && go build -o "$OUT_DIR/sqlc-upstream" ./cmd/sqlc) +git -C "$SRC" apply "$PATCHES_DIR"/*.patch +(cd "$SRC" && go build -o "$OUT_DIR/sqlc" ./cmd/sqlc) +rm -rf "$SRC" +echo "built $OUT_DIR/sqlc-upstream and $OUT_DIR/sqlc (sqlc $REF + patches)" diff --git a/scripts/sync_sqlc_options.sh b/scripts/sync_sqlc_options.sh index 41daf5e95..ccb11c247 100755 --- a/scripts/sync_sqlc_options.sh +++ b/scripts/sync_sqlc_options.sh @@ -2,24 +2,33 @@ set -e -CI_YAML="sqlc.ci.yaml" -REQUESTS_YAML="sqlc.request.generated.yaml" -LOCAL_YAML="sqlc.local.generated.yaml" -TMP_REQUESTS_YML="${REQUESTS_YAML}.tmp" -TMP_LOCAL_YML="${LOCAL_YAML}.tmp" +# sync_pair : copies every .sql entry of the ci yaml (which uses the WASM +# plugin) into the generated yamls (which use the process plugin), with debugRequest: true in the requests yaml. +sync_pair() { + local CI_YAML="$1" + local REQUESTS_YAML="$2" + local LOCAL_YAML="$3" + local TMP_REQUESTS_YML="${REQUESTS_YAML}.tmp" + local TMP_LOCAL_YML="${LOCAL_YAML}.tmp" -cp "$REQUESTS_YAML" "$TMP_REQUESTS_YML" -cp "$LOCAL_YAML" "$TMP_LOCAL_YML" + cp "$REQUESTS_YAML" "$TMP_REQUESTS_YML" + cp "$LOCAL_YAML" "$TMP_LOCAL_YML" + local sql_count + sql_count=$(yq '.sql | length' "$CI_YAML") + for ((i=0; i Date: Tue, 27 Oct 2026 14:59:13 +0000 Subject: [PATCH 08/11] mssql: upstream issue drafts from the syntax matrix Co-Authored-By: Claude Opus 5.5 --- end2end/MssqlSyntaxMatrix/ISSUES.md | 137 ++++++++++++++++++++++++++++ end2end/MssqlSyntaxMatrix/README.md | 3 +- 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 end2end/MssqlSyntaxMatrix/ISSUES.md diff --git a/end2end/MssqlSyntaxMatrix/ISSUES.md b/end2end/MssqlSyntaxMatrix/ISSUES.md new file mode 100644 index 000000000..019f40ffe --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/ISSUES.md @@ -0,0 +1,137 @@ +# SQL Server support: what is missing upstream + +Issue drafts derived from the [syntax matrix](REPORT.md) (sqlc `main` at `3ad8b20` and teesql `v1.1.0`, SQL Server 2022 +as the reference). Each one names the component, links the matrix cases that reproduce it and says what SQL Server +answers. Parser attribution was checked by feeding the statements straight to `teesql/parser`: everything not listed +under teesql parses fine there and is lost in sqlc. + +| # | Component | Issue | Cases | +|---|-----------|-------|-------| +| P1 | sqlc (preprocess, compiler) | mssql macros, `sqlc.embed` on the core path, `:copyfrom` panic, `TRUNCATE`, case-insensitive `@name` | patches in [scripts/sqlc-patches](../../scripts/sqlc-patches) | +| S1 | sqlc (engine/mssql) | statements the converter does not know are silently dropped; multi-statement queries | 9 | +| S2 | sqlc (engine/mssql, analyzer) | FROM items: APPLY, table-valued functions, `VALUES`, `PIVOT`, TVPs | 9 | +| S3 | sqlc (engine/mssql) | date parts (`DAY`, `WEEK`, ...) are read as column references | 2 | +| S4 | sqlc (engine/mssql, catalog) | DDL: views, synonyms, computed columns, alias types, `sp_rename`, `dbo` schema | 5 | +| S5 | sqlc (core analyzer, mssql dialect) | functions and expressions without a result type; `N''` and arithmetic types | 22 | +| S6 | sqlc (core analyzer) | outer joins and `ROLLUP`/`CUBE` columns reported NOT NULL | 2 | +| S7 | sqlc (core analyzer) | parameters missing or untyped in some positions | 9 | +| S8 | sqlc (core analyzer) | result shape of `FOR JSON` / `FOR XML` and multi-statement `:execlastid` | 3 | +| S9 | sqlc (engine/mssql) | statements must end with `;` (optional in T-SQL) | 1 | +| S10 | sqlc (engine/mssql) | identifiers are lowercased (`[Author Id]` -> `author id`) | 1 | +| S11 | sqlc (engine/mssql) | recursive CTEs | 1 | +| T1 | teesql | `MERGE ... USING (VALUES ...) AS s (cols)` | 1 | +| T2 | teesql | `MESSAGE` is treated as a reserved word | 2 | +| T3 | teesql | nested block comments `/* /* */ */` | 1 | +| T4 | teesql | the xml `.query()` method | 1 | + +--- + +## P1. Upstream the patches this plugin depends on (sqlc) +The four commits in [scripts/sqlc-patches](../../scripts/sqlc-patches), each a candidate PR with sqlc endtoend tests: +1. mssql dialect for `internal/sql/preprocess` (`sqlc.arg/narg/slice/embed` -> `@name`, `/*SLICE:x*/@x`, `t.*`), + and `sqlc.embed` in `parseQueryCore` (affects every engine on the core analysis path). Cases: `macros/*`. +2. `INSERT ... VALUES` without a FROM clause makes `validate.Cmd` panic for `:copyfrom`. Case: `annotations/copyfrom_identity_table`. +3. `TRUNCATE TABLE` is dropped. Case: `delete/truncate`. +4. `@Name` / `@name` are one parameter in SQL Server. Case: `select/repeated_parameter`. + +## S1. Unknown statements are silently dropped; multi-statement queries (sqlc) +`internal/engine/mssql/parse.go` skips any statement that converts to `ast.TODO`. With one statement the query file +fails with `no queries contained in paths`; with more, the query silently loses statements. Common T-SQL that +hits it: `SET NOCOUNT ON;` before a query, `DECLARE`, `SET IDENTITY_INSERT`, `EXEC proc`, `MERGE`, +`BEGIN TRAN ... COMMIT`, `TRY ... CATCH`, `IF @@ROWCOUNT = 0 INSERT ...`. +- Expected: an error for an annotated query sqlc cannot handle, and support for `MERGE` (with `OUTPUT`), `EXEC` + (result set unknown, so `:exec`/`:execrows` at least) and multi-statement queries whose last statement produces + the result (`SET NOCOUNT ON; SELECT ...`, `INSERT ...; SELECT SCOPE_IDENTITY()`). +- Cases: [batch/set_nocount_on](cases/batch/set_nocount_on.sql), [batch/declare_variables](cases/batch/declare_variables.sql), + [batch/transaction_in_query](cases/batch/transaction_in_query.sql), [batch/try_catch_throw](cases/batch/try_catch_throw.sql), + [insert/identity_insert](cases/insert/identity_insert.sql), [insert/output_into_table_variable](cases/insert/output_into_table_variable.sql), + [merge/merge_delete](cases/merge/merge_delete.sql), [merge/upsert_without_merge](cases/merge/upsert_without_merge.sql), + [programmability/exec_procedure](cases/programmability/exec_procedure.sql) + +## S2. FROM items the converter does not support (sqlc) +`scope: unsupported FROM item *ast.TODO` for `CROSS APPLY` / `OUTER APPLY`, table-valued functions (user-defined, +`OPENJSON ... WITH (...)`, `STRING_SPLIT`, `GENERATE_SERIES`), `VALUES` as a derived table, `PIVOT` / `UNPIVOT`, +and table-valued parameters (`JOIN @ids`, needs `CREATE TYPE ... AS TABLE` in the catalog and a parameter type the +plugin can bind as `SqlDbType.Structured`). +- Cases: [joins/cross_apply](cases/joins/cross_apply.sql), [json/openjson](cases/json/openjson.sql), + [programmability/string_split](cases/programmability/string_split.sql), [programmability/inline_table_function](cases/programmability/inline_table_function.sql), + [programmability/multi_statement_table_function](cases/programmability/multi_statement_table_function.sql), + [programmability/table_valued_parameter](cases/programmability/table_valued_parameter.sql), + [select/table_value_constructor](cases/select/table_value_constructor.sql), [setops/pivot_unpivot](cases/setops/pivot_unpivot.sql), + [expressions/sql2022_functions](cases/expressions/sql2022_functions.sql) (`GENERATE_SERIES`) + +## S3. Date parts are read as column references (sqlc) +`DATEADD(DAY, @days, published_at)` fails with `unknown column "day"`; the same for `DATEDIFF`, `DATEDIFF_BIG`, +`DATEPART`, `DATENAME`, `DATETRUNC`, `DATE_BUCKET`. The first argument is a keyword, not an expression. +- Cases: [expressions/datetime_functions](cases/expressions/datetime_functions.sql), [expressions/sql2022_functions](cases/expressions/sql2022_functions.sql) + +## S4. DDL the catalog does not understand (sqlc) +- `CREATE VIEW` and `CREATE SYNONYM`: querying them fails (`relation "public"."author_books" ... no rows`). + Cases: [ddl/view](cases/ddl/view.sql), [ddl/synonym](cases/ddl/synonym.sql) +- Computed columns `full_name AS (...)` [PERSISTED]: `missing type name`; SQL Server derives the type from the + expression. Case: [ddl/computed_columns](cases/ddl/computed_columns.sql) +- Alias types `CREATE TYPE dbo.email FROM NVARCHAR(320) NOT NULL`: columns and parameters come through as + `email_address` instead of `nvarchar(320)` (and NOT NULL). Case: [ddl/alias_type](cases/ddl/alias_type.sql) +- `EXEC sp_rename 'authors.bio', 'biography', 'COLUMN'` (how T-SQL renames a column) is ignored. Case: [ddl/sp_rename_column](cases/ddl/sp_rename_column.sql) +- The default schema is reported as `public` (and listed twice in the catalog); SQL Server calls it `dbo`. + +## S5. Expressions without a result type (sqlc) +These come through with an empty type, so a typed client can only use `object`. SQL Server's types are in the report details. +- String: `SUBSTRING`, `LEFT`, `RIGHT`, `REPLACE`, `CHARINDEX`, `PATINDEX`, `REVERSE`, `REPLICATE`, `STUFF`, `CONCAT`, + `CONCAT_WS`, `FORMAT`, `QUOTENAME`, `TRANSLATE`, `STRING_AGG`, `STRING_ESCAPE` +- Math: `ABS`, `ROUND`, `CEILING`, `FLOOR`, `POWER`, `SQRT`, `SIGN`, `PI`, `RAND`; aggregates `STDEV`, `VAR`, `APPROX_COUNT_DISTINCT`, `GROUPING` +- Window: `ROW_NUMBER`, `RANK`, `DENSE_RANK`, `NTILE`, `LAG`, `LEAD`, `FIRST_VALUE`, `LAST_VALUE`, `PERCENTILE_CONT`, `PERCENTILE_DISC` +- Conditional / conversion: `ISNULL`, `NULLIF`, `IIF`, `CHOOSE`, `PARSE`, `TRY_PARSE`, `GREATEST`, `LEAST`, `AT TIME ZONE`, `SWITCHOFFSET` +- JSON: `JSON_VALUE`, `JSON_QUERY`, `ISJSON`, `JSON_MODIFY`, `JSON_OBJECT`, `JSON_ARRAY` +- System: `@@ROWCOUNT`, `IDENT_CURRENT`, `DB_NAME`, `SUSER_SNAME`, `HASHBYTES`, `CHECKSUM`, `NEXT VALUE FOR`, scalar user-defined functions +- Wrong types: `N'...'` literals are typed `varchar` (SQL Server: `nvarchar`), `decimal * int` is typed `bigint` (SQL Server: `decimal`). +- Cases: see every 🟠 "has no type" in the report, e.g. [expressions/string_functions](cases/expressions/string_functions.sql), + [window/ranking](cases/window/ranking.sql), [expressions/math_functions](cases/expressions/math_functions.sql), + [json/json_value_query](cases/json/json_value_query.sql), [select/unicode_literals](cases/select/unicode_literals.sql), + [expressions/arithmetic](cases/expressions/arithmetic.sql) + +## S6. Nullability of outer joins and grouping extensions (sqlc) +Columns from the nullable side of `LEFT` / `RIGHT` / `FULL OUTER JOIN`, and the grouping columns of `ROLLUP` / +`CUBE` / `GROUPING SETS`, are reported NOT NULL; a generated reader then fails at runtime on a NULL. +- Cases: [joins/inner_left_right_full](cases/joins/inner_left_right_full.sql), [aggregate/rollup_cube_grouping_sets](cases/aggregate/rollup_cube_grouping_sets.sql) + +## S7. Parameters missing or untyped (sqlc) +Missing (the generated code would not bind them): `UPDATE TOP (@n)`, `DELETE TOP (@n)`, `FOR SYSTEM_TIME AS OF @at`, +`AT TIME ZONE @tz`, `TRY_PARSE(@text AS ...)`, `IS DISTINCT FROM @bio`. +Untyped although SQL Server knows the type: a parameter in an `INSERT ... SELECT` list, `ISNULL(@p, '%')`, +`GREATEST(price, @floor)`, `HASHBYTES('SHA2_256', @payload)`, a scalar UDF argument, `JSON_VALUE(...) = @kind`. +- Cases: [update/top](cases/update/top.sql), [delete/top](cases/delete/top.sql), [ddl/temporal_table](cases/ddl/temporal_table.sql), + [expressions/at_time_zone](cases/expressions/at_time_zone.sql), [expressions/cast_convert](cases/expressions/cast_convert.sql), + [expressions/is_distinct_from](cases/expressions/is_distinct_from.sql), [insert/insert_select](cases/insert/insert_select.sql), + [expressions/coalesce_isnull_nullif](cases/expressions/coalesce_isnull_nullif.sql), [expressions/greatest_least](cases/expressions/greatest_least.sql) + +## S8. Result shape (sqlc) +- `FOR JSON` / `FOR XML` return one `nvarchar(max)` / `xml` column, not the selected columns. + Cases: [json/for_json](cases/json/for_json.sql), [xml/for_xml](cases/xml/for_xml.sql) +- `INSERT ...; SELECT SCOPE_IDENTITY()` reports no result column. Case: [annotations/execlastid_scope_identity](cases/annotations/execlastid_scope_identity.sql) + +## S9. A terminating semicolon is required (sqlc) +`missing semicolon at end of file`; statement splitting in `engine/mssql/parse.go` relies on `;`, but teesql already +returns statement boundaries. Case: [select/no_semicolon_terminator](cases/select/no_semicolon_terminator.sql) + +## S10. Identifiers are lowercased (sqlc) +`SELECT id AS [Author Id]` comes back as `author id`; SQL Server keeps `Author Id` (Minor in the report, but names +matter to clients that map by name). Case: [select/quoted_aliases](cases/select/quoted_aliases.sql) + +## S11. Recursive CTEs (sqlc) +`WITH numbers (n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM numbers ...)` fails: the CTE is looked up as a table. +Case: [cte/recursive_cte](cases/cte/recursive_cte.sql) + +## T1. `MERGE ... USING (VALUES ...) AS source (cols)` (teesql) +`expected identifier for schema object name`; `USING table AS source` parses. +Case: [merge/merge_upsert](cases/merge/merge_upsert.sql) + +## T2. `MESSAGE` is not a reserved word (teesql) +`INSERT INTO t (message) ...` -> `expected ), got message`, `SELECT message FROM t` -> `unexpected token in expression`. +Cases: [insert/schema_scoped](cases/insert/schema_scoped.sql), [annotations/copyfrom_schema_scoped](cases/annotations/copyfrom_schema_scoped.sql) + +## T3. Nested block comments (teesql) +T-SQL nests block comments: `SELECT 1 /* a /* b */ c */;` -> `unexpected token: *`. Case: [select/comments](cases/select/comments.sql) + +## T4. The xml `.query()` method (teesql) +`doc.query('/order/item')` -> `unexpected token: query` (`doc.value(...)` parses). Case: [xml/xml_methods](cases/xml/xml_methods.sql) diff --git a/end2end/MssqlSyntaxMatrix/README.md b/end2end/MssqlSyntaxMatrix/README.md index 3a83ded65..15b3d16a3 100644 --- a/end2end/MssqlSyntaxMatrix/README.md +++ b/end2end/MssqlSyntaxMatrix/README.md @@ -13,7 +13,8 @@ feature, and every case is run through: 3. **this plugin** (driver and Dapper flavours) on the output of the last sqlc build; 4. optionally, **the C# compiler** on all the generated code (`--compile`). -The result is [REPORT.md](REPORT.md) (and [results.json](results.json) for tooling). +The result is [REPORT.md](REPORT.md) (and [results.json](results.json) for tooling). [ISSUES.md](ISSUES.md) groups +what is missing into upstream issue drafts (sqlc vs. the teesql parser). | Level | Meaning | |-------|---------| From 8d05d376390cab23506ce8ab98b1311c9e72901e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 15:06:36 +0000 Subject: [PATCH 09/11] mssql: no process-global Dapper registration for DateTime SqlMapper.AddTypeMap(DateTime, DateTime2) is global to the process and broke Npgsql's DateTime parameters in the same test run (and would for any app using two databases). DateTime (and Instant on datetime2) values are now wrapped per parameter, like byte[]. Co-Authored-By: Claude Opus 5.5 --- Drivers/MssqlDriver.cs | 40 +++++++++++-------- examples/MssqlDapperExample/QuerySql.cs | 12 +++--- examples/MssqlDapperExample/Utils.cs | 22 ++++++++-- examples/MssqlDapperLegacyExample/QuerySql.cs | 12 +++--- examples/MssqlDapperLegacyExample/Utils.cs | 22 ++++++++-- unit-tests/CodegenTests/MssqlDriverTests.cs | 5 ++- 6 files changed, 78 insertions(+), 35 deletions(-) diff --git a/Drivers/MssqlDriver.cs b/Drivers/MssqlDriver.cs index 396da14d3..5aaccfa54 100644 --- a/Drivers/MssqlDriver.cs +++ b/Drivers/MssqlDriver.cs @@ -25,23 +25,25 @@ public MssqlDriver( NullableTypes.Add("SqlHierarchyId"); } - // Dapper sends a null byte[] as nvarchar, which SQL Server refuses to convert to a binary type - private static string BinaryParameterImpl(bool isDotnetCore) + // Dapper picks the parameter type from the value: a null byte[] goes as nvarchar (SQL Server refuses to convert + // it to a binary type) and a DateTime as the less precise datetime. Dapper's type maps and handlers are global + // to the process (they would leak into other databases), so these values are wrapped per parameter instead. + private static string TypedParameterImpl(string className, string valueType, string sqlDbType, bool isDotnetCore) { var nullable = isDotnetCore ? "?" : ""; return $$""" - public class BinaryParameter : SqlMapper.ICustomQueryParameter + public class {{className}} : SqlMapper.ICustomQueryParameter { - private readonly byte[]{{nullable}} _value; + private readonly {{valueType}} _value; - public BinaryParameter(byte[]{{nullable}} value) + public {{className}}({{valueType}} value) { _value = value; } public void AddParameter(IDbCommand command, string name) { - var parameter = new SqlParameter(name, SqlDbType.VarBinary) { Value = (object{{nullable}})_value ?? DBNull.Value }; + var parameter = new SqlParameter(name, SqlDbType.{{sqlDbType}}) { Value = (object{{nullable}})_value ?? DBNull.Value }; command.Parameters.Add(parameter); } } @@ -62,8 +64,7 @@ public override Instant Parse(object value) public override void SetValue(IDbDataParameter parameter, Instant value) { - parameter.Value = value.ToDateTimeUtc(); - parameter.DbType = DbType.DateTime2; + parameter.Value = value; } } """; @@ -187,8 +188,12 @@ public override void SetValue(IDbDataParameter parameter, Instant value) { "smalldatetime", new(DbTypeOverride: "SqlDbType.SmallDateTime") } }, readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetDateTime({ordinal})", - // Dapper sends DateTime as the less precise datetime unless told otherwise - sqlMapper: "SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2);" + writerFn: (el, _, notNull, isDapper, _) => + { + if (isDapper) + return $"new Utils.DateTime2Parameter({el})"; + return notNull ? el : $"{el} ?? (object)DBNull.Value"; + } ), ["DateTimeOffset"] = new( new() @@ -257,10 +262,10 @@ public override void SetValue(IDbDataParameter parameter, Instant value) var (convert, type) = dbType.ToLower() == "datetimeoffset" ? ("ToDateTimeOffset()", "DateTimeOffset") : ("ToDateTimeUtc()", "DateTime"); - if (notNull) - return $"{el}.{convert}"; - var nullValue = isDapper ? "null" : "(object)DBNull.Value"; - return $"{el} is null ? {nullValue} : ({type}?){el}.Value.{convert}"; + var value = notNull ? $"{el}.{convert}" : $"{el} is null ? null : ({type}?){el}.Value.{convert}"; + if (isDapper) + return type == "DateTime" ? $"new Utils.DateTime2Parameter({value})" : value; + return notNull ? value : $"{el} is null ? (object)DBNull.Value : ({type}?){el}.Value.{convert}"; }, usingDirectives: ["System", "NodaTime", "NodaTime.Extensions"], sqlMapper: "SqlMapper.AddTypeHandler(typeof(Instant), new NodaInstantTypeHandler());", @@ -368,8 +373,11 @@ public override MemberDeclarationSyntax[] GetMemberDeclarationsForUtils() ParseMemberDeclaration(TransformQueryForSliceArgsImpl)! ], SliceQueryExists()) .AddRangeIf([ - ParseMemberDeclaration(BinaryParameterImpl(Options.DotnetFramework.IsDotnetCore()))! - ], Options.UseDapper && TypeExistsInQueries("byte[]"))]; + ParseMemberDeclaration(TypedParameterImpl("BinaryParameter", "byte[]" + (Options.DotnetFramework.IsDotnetCore() ? "?" : ""), "VarBinary", Options.DotnetFramework.IsDotnetCore()))! + ], Options.UseDapper && TypeExistsInQueries("byte[]")) + .AddRangeIf([ + ParseMemberDeclaration(TypedParameterImpl("DateTime2Parameter", "DateTime?", "DateTime2", Options.DotnetFramework.IsDotnetCore()))! + ], Options.UseDapper && (TypeExistsInQueries("DateTime") || TypeExistsInQueries("Instant")))]; } public override ConnectionGenCommands EstablishConnection(Query query) diff --git a/examples/MssqlDapperExample/QuerySql.cs b/examples/MssqlDapperExample/QuerySql.cs index ce5e0c917..d35adae5e 100644 --- a/examples/MssqlDapperExample/QuerySql.cs +++ b/examples/MssqlDapperExample/QuerySql.cs @@ -1094,10 +1094,10 @@ public class InsertMssqlDatetimeTypesArgs public async Task InsertMssqlDatetimeTypesAsync(InsertMssqlDatetimeTypesArgs args) { var queryParams = new Dictionary(); - queryParams.Add("c_date", args.CDate); - queryParams.Add("c_datetime", args.CDatetime); - queryParams.Add("c_datetime2", args.CDatetime2); - queryParams.Add("c_smalldatetime", args.CSmalldatetime); + queryParams.Add("c_date", new Utils.DateTime2Parameter(args.CDate)); + queryParams.Add("c_datetime", new Utils.DateTime2Parameter(args.CDatetime)); + queryParams.Add("c_datetime2", new Utils.DateTime2Parameter(args.CDatetime2)); + queryParams.Add("c_smalldatetime", new Utils.DateTime2Parameter(args.CSmalldatetime)); queryParams.Add("c_datetimeoffset", args.CDatetimeoffset); queryParams.Add("c_time", args.CTime); if (this.Transaction == null) @@ -1146,7 +1146,7 @@ public async Task InsertMssqlDatetimeTypesBatchAsync(List(); - queryParams.Add("c_datetime2_instant", args.CDatetime2Instant is null ? null : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc()); + queryParams.Add("c_datetime2_instant", new Utils.DateTime2Parameter(args.CDatetime2Instant is null ? null : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc())); queryParams.Add("c_datetimeoffset_instant", args.CDatetimeoffsetInstant is null ? null : (DateTimeOffset? )args.CDatetimeoffsetInstant.Value.ToDateTimeOffset()); if (this.Transaction == null) { diff --git a/examples/MssqlDapperExample/Utils.cs b/examples/MssqlDapperExample/Utils.cs index a7749c0f5..c223e2e7c 100644 --- a/examples/MssqlDapperExample/Utils.cs +++ b/examples/MssqlDapperExample/Utils.cs @@ -24,14 +24,12 @@ public override Instant Parse(object value) public override void SetValue(IDbDataParameter parameter, Instant value) { - parameter.Value = value.ToDateTimeUtc(); - parameter.DbType = DbType.DateTime2; + parameter.Value = value; } } public static void ConfigureSqlMapper() { - SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2); SqlMapper.AddTypeHandler(typeof(Instant), new NodaInstantTypeHandler()); } @@ -58,4 +56,22 @@ public void AddParameter(IDbCommand command, string name) command.Parameters.Add(parameter); } } + + public class DateTime2Parameter : SqlMapper.ICustomQueryParameter + { + private readonly DateTime? _value; + public DateTime2Parameter(DateTime? value) + { + _value = value; + } + + public void AddParameter(IDbCommand command, string name) + { + var parameter = new SqlParameter(name, SqlDbType.DateTime2) + { + Value = (object? )_value ?? DBNull.Value + }; + command.Parameters.Add(parameter); + } + } } \ No newline at end of file diff --git a/examples/MssqlDapperLegacyExample/QuerySql.cs b/examples/MssqlDapperLegacyExample/QuerySql.cs index b6d109ef6..a7b5d17a6 100644 --- a/examples/MssqlDapperLegacyExample/QuerySql.cs +++ b/examples/MssqlDapperLegacyExample/QuerySql.cs @@ -1095,10 +1095,10 @@ public class InsertMssqlDatetimeTypesArgs public async Task InsertMssqlDatetimeTypesAsync(InsertMssqlDatetimeTypesArgs args) { var queryParams = new Dictionary(); - queryParams.Add("c_date", args.CDate); - queryParams.Add("c_datetime", args.CDatetime); - queryParams.Add("c_datetime2", args.CDatetime2); - queryParams.Add("c_smalldatetime", args.CSmalldatetime); + queryParams.Add("c_date", new Utils.DateTime2Parameter(args.CDate)); + queryParams.Add("c_datetime", new Utils.DateTime2Parameter(args.CDatetime)); + queryParams.Add("c_datetime2", new Utils.DateTime2Parameter(args.CDatetime2)); + queryParams.Add("c_smalldatetime", new Utils.DateTime2Parameter(args.CSmalldatetime)); queryParams.Add("c_datetimeoffset", args.CDatetimeoffset); queryParams.Add("c_time", args.CTime); if (this.Transaction == null) @@ -1147,7 +1147,7 @@ public async Task InsertMssqlDatetimeTypesBatchAsync(List(); - queryParams.Add("c_datetime2_instant", args.CDatetime2Instant is null ? null : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc()); + queryParams.Add("c_datetime2_instant", new Utils.DateTime2Parameter(args.CDatetime2Instant is null ? null : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc())); queryParams.Add("c_datetimeoffset_instant", args.CDatetimeoffsetInstant is null ? null : (DateTimeOffset? )args.CDatetimeoffsetInstant.Value.ToDateTimeOffset()); if (this.Transaction == null) { diff --git a/examples/MssqlDapperLegacyExample/Utils.cs b/examples/MssqlDapperLegacyExample/Utils.cs index 5e8c04c3a..166056855 100644 --- a/examples/MssqlDapperLegacyExample/Utils.cs +++ b/examples/MssqlDapperLegacyExample/Utils.cs @@ -25,14 +25,12 @@ public override Instant Parse(object value) public override void SetValue(IDbDataParameter parameter, Instant value) { - parameter.Value = value.ToDateTimeUtc(); - parameter.DbType = DbType.DateTime2; + parameter.Value = value; } } public static void ConfigureSqlMapper() { - SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2); SqlMapper.AddTypeHandler(typeof(Instant), new NodaInstantTypeHandler()); } @@ -59,5 +57,23 @@ public void AddParameter(IDbCommand command, string name) command.Parameters.Add(parameter); } } + + public class DateTime2Parameter : SqlMapper.ICustomQueryParameter + { + private readonly DateTime? _value; + public DateTime2Parameter(DateTime? value) + { + _value = value; + } + + public void AddParameter(IDbCommand command, string name) + { + var parameter = new SqlParameter(name, SqlDbType.DateTime2) + { + Value = (object)_value ?? DBNull.Value + }; + command.Parameters.Add(parameter); + } + } } } \ No newline at end of file diff --git a/unit-tests/CodegenTests/MssqlDriverTests.cs b/unit-tests/CodegenTests/MssqlDriverTests.cs index 700557a44..80ea7a816 100644 --- a/unit-tests/CodegenTests/MssqlDriverTests.cs +++ b/unit-tests/CodegenTests/MssqlDriverTests.cs @@ -51,7 +51,10 @@ public async Task TestDapperConfiguresDateTimeAndBinaryParameters() { var files = await GenerateFilesFor("MssqlDapperExampleRequest.message"); - Assert.That(files["Utils.cs"], Does.Contain("SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2);")); + // Dapper type maps are global to the process and would leak into other databases, so values are wrapped instead + Assert.That(files["Utils.cs"], Does.Not.Contain("AddTypeMap")); + Assert.That(files["Utils.cs"], Does.Contain("class DateTime2Parameter")); + Assert.That(files["QuerySql.cs"], Does.Contain("new Utils.DateTime2Parameter(args.CDatetime2)")); Assert.That(files["Utils.cs"], Does.Contain("class BinaryParameter")); Assert.That(files["QuerySql.cs"], Does.Contain("new Utils.BinaryParameter(args.CBinary)")); } From 9eac436c0842634df09af3d8699f4ab5884273be Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 15:08:31 +0000 Subject: [PATCH 10/11] e2e: clear SQLite connection pools before deleting the test database On Windows the pooled connections keep tests.db open and the global teardown fails. Co-Authored-By: Claude Opus 5.5 --- end2end/EndToEndCommon/EndToEndCommon.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/end2end/EndToEndCommon/EndToEndCommon.cs b/end2end/EndToEndCommon/EndToEndCommon.cs index 2cff7bf39..dfdc21c57 100644 --- a/end2end/EndToEndCommon/EndToEndCommon.cs +++ b/end2end/EndToEndCommon/EndToEndCommon.cs @@ -87,6 +87,8 @@ public static void RemoveExistingSqliteDb() if (!File.Exists(dbFilename)) return; Console.WriteLine($"Removing sqlite db from {dbFilename}"); + // pooled connections keep the file open, which makes the delete fail on Windows + SqliteConnection.ClearAllPools(); File.Delete(dbFilename); } private static void InitSqliteDb(string[] schemaFiles) From 3015ef0f3e519cd424fbf593853d8599147e268d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 06:23:11 +0000 Subject: [PATCH 11/11] mssql: the teesql issues, ready to open Co-Authored-By: Claude Opus 5.5 --- end2end/MssqlSyntaxMatrix/ISSUES.md | 2 + end2end/MssqlSyntaxMatrix/TEESQL_ISSUES.md | 130 +++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 end2end/MssqlSyntaxMatrix/TEESQL_ISSUES.md diff --git a/end2end/MssqlSyntaxMatrix/ISSUES.md b/end2end/MssqlSyntaxMatrix/ISSUES.md index 019f40ffe..c98b24c9f 100644 --- a/end2end/MssqlSyntaxMatrix/ISSUES.md +++ b/end2end/MssqlSyntaxMatrix/ISSUES.md @@ -122,6 +122,8 @@ matter to clients that map by name). Case: [select/quoted_aliases](cases/select/ `WITH numbers (n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM numbers ...)` fails: the CTE is looked up as a table. Case: [cte/recursive_cte](cases/cte/recursive_cte.sql) +The teesql tickets below are written out in full, ready to open, in [TEESQL_ISSUES.md](TEESQL_ISSUES.md). + ## T1. `MERGE ... USING (VALUES ...) AS source (cols)` (teesql) `expected identifier for schema object name`; `USING table AS source` parses. Case: [merge/merge_upsert](cases/merge/merge_upsert.sql) diff --git a/end2end/MssqlSyntaxMatrix/TEESQL_ISSUES.md b/end2end/MssqlSyntaxMatrix/TEESQL_ISSUES.md new file mode 100644 index 000000000..8917914d3 --- /dev/null +++ b/end2end/MssqlSyntaxMatrix/TEESQL_ISSUES.md @@ -0,0 +1,130 @@ + + +TITLE: MERGE ... USING (VALUES ...) AS alias (columns) fails to parse + +**teesql version:** v1.1.0 (`5cd4b20`) + +A row constructor (`VALUES`) as the `MERGE` source fails to parse. This is the standard T-SQL upsert pattern, where the incoming row comes from parameters: + +```sql +MERGE INTO authors AS target +USING (VALUES (N'a', N'b')) AS source (name, bio) +ON target.name = source.name +WHEN MATCHED THEN UPDATE SET bio = source.bio +WHEN NOT MATCHED THEN INSERT (name, bio) VALUES (source.name, source.bio); +``` + +``` +expected identifier for schema object name +``` + +The other source forms parse: + +| `USING ...` | Result | +|---|---| +| `staging AS source` (table) | ✅ `*ast.MergeStatement` | +| `(SELECT N'a' AS name) AS source` (derived table) | ✅ `*ast.MergeStatement` | +| `(VALUES (...)) AS source (name, bio)` | ❌ parse error | + +**Expected:** a `MergeStatement` whose source is a derived table built from a row constructor (`InlineDerivedTable` in SqlScriptDOM), with the column alias list. SQL Server accepts the statement. `(VALUES ...) AS t (cols)` in a `FROM` clause already parses, so presumably only the `MERGE ... USING` path is missing it. + +**Context:** found by the SQL Server syntax matrix in [sqlc-gen-csharp](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/README.md), which runs T-SQL cases through sqlc (and so teesql) and checks them against SQL Server 2022. The case ([`merge/merge_upsert.sql`](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/cases/merge/merge_upsert.sql)): + +```sql +MERGE INTO authors AS target +USING (VALUES (@name, @bio)) AS source (name, bio) +ON target.name = source.name +WHEN MATCHED THEN UPDATE SET bio = source.bio +WHEN NOT MATCHED THEN INSERT (name, bio) VALUES (source.name, source.bio) +OUTPUT $action AS action, INSERTED.id; +``` + +--------------------------------------------------------------------------- + +TITLE: MESSAGE is treated as a reserved word in expressions and column lists + +**teesql version:** v1.1.0 (`5cd4b20`) + +A column named `message`, which is common in log and event tables, can be declared but not used: + +| Statement | Result | +|---|---| +| `CREATE TABLE logs (id INT, message NVARCHAR(200));` | ✅ | +| `SELECT [message] FROM logs;` | ✅ | +| `SELECT message FROM logs;` | ❌ `unexpected token in expression: message` | +| `INSERT INTO logs (message) VALUES (N'x');` | ❌ `expected ), got message` | + +**Expected:** all four parse. `MESSAGE` is not a reserved keyword in T-SQL. It is not in the [reserved keywords list](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/reserved-keywords-transact-sql); it is only a contextual keyword in Service Broker statements (`CREATE MESSAGE TYPE`, `SEND ON CONVERSATION ... MESSAGE TYPE`). SQL Server runs both failing statements unquoted. + +Other contextual keywords may be lexed the same way. The ones I'd check are `TYPE`, `STATUS`, `LEVEL`, `NAME`, `ROLE`, `STATE` and `VALUE`. + +**Context:** found by the SQL Server syntax matrix in [sqlc-gen-csharp](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/README.md). The case ([`insert/schema_scoped.sql`](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/cases/insert/schema_scoped.sql)): + +```sql +CREATE SCHEMA logging; +GO +CREATE TABLE logging.entries (id INT IDENTITY PRIMARY KEY, message NVARCHAR(200) NOT NULL); + +INSERT INTO logging.entries (message) VALUES (@message); +``` + +--------------------------------------------------------------------------- + +TITLE: Nested block comments fail to parse + +**teesql version:** v1.1.0 (`5cd4b20`) + +T-SQL block comments nest; a `/* ... */` inside another one does not end the outer comment: + +```sql +SELECT 1 /* outer /* inner */ still outer */ AS one; +``` + +``` +unexpected token: outer +``` + +Both the flat comment `SELECT 1 /* flat */ AS one;` and the nested one run on SQL Server. + +**Expected:** the whole `/* outer /* inner */ still outer */` is one comment. SQL Server [documents this](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/slash-star-comment-transact-sql): "Nested comments are supported". It matters in practice because commenting out a block that already contains a comment is a common habit. + +**Context:** found by the SQL Server syntax matrix in [sqlc-gen-csharp](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/README.md). The case ([`select/comments.sql`](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/cases/select/comments.sql)): + +```sql +SELECT id, /* the name */ name -- trailing +FROM authors /* outer /* nested */ still a comment */ +WHERE id > @min_id; +``` + +--------------------------------------------------------------------------- + +TITLE: The xml .query() method fails to parse + +**teesql version:** v1.1.0 (`5cd4b20`) + +Of the methods on an `xml` column, only `.query()` fails: + +| Expression | Result | +|---|---| +| `doc.value('(/order/@id)[1]', 'INT')` | ✅ | +| `doc.exist('/order')` | ✅ | +| `o.doc.nodes('/order/item') AS i(n)` in `CROSS APPLY` | ✅ | +| `doc.query('/order/item')` | ❌ `unexpected token: query` | + +```sql +SELECT doc.query('/order/item') FROM orders; +``` + +**Expected:** a function call on the column (`FunctionCall` with a `CallTarget` in SqlScriptDOM), like `.value()`. `QUERY` is probably lexed as a keyword after the `.`. That position is a method name, so any identifier, keywords included, should be accepted there. + +**Context:** found by the SQL Server syntax matrix in [sqlc-gen-csharp](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/README.md). The case ([`xml/xml_methods.sql`](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/cases/xml/xml_methods.sql)): + +```sql +CREATE TABLE orders_xml (id INT IDENTITY PRIMARY KEY, doc XML NOT NULL); + +SELECT id, doc.value('(/order/@id)[1]', 'INT') AS order_id, doc.query('/order/item') AS items +FROM orders_xml WHERE doc.exist('/order[@status=sql:variable("@status")]') = 1; + +SELECT o.id, i.n.value('@sku', 'NVARCHAR(20)') AS sku +FROM orders_xml o CROSS APPLY o.doc.nodes('/order/item') AS i(n); +```