From 721704b0764a6c87834e2d491830885141d1fc5d Mon Sep 17 00:00:00 2001 From: Ilan Uzan Date: Fri, 26 Jul 2024 21:36:14 +0300 Subject: [PATCH] 1. Refactor around DB specific methods - adding proper interfaces and adjusting downstream code 2. Change codegen test in CI method - moved the configuration matrix to a bash script, enabling local run --- .github/workflows/ci.yml | 14 +--- CodeGenerator/CodeGenerator.cs | 12 ++-- Drivers/DbDriver.cs | 8 +-- Drivers/Generators/CopyFromDeclareGen.cs | 81 --------------------- Drivers/Generators/ExecDeclareGen.cs | 14 ++-- Drivers/Generators/ExecLastIdDeclareGen.cs | 71 ------------------- Drivers/Generators/ManyDeclareGen.cs | 8 +-- Drivers/Generators/OneDeclareGen.cs | 16 ++--- Drivers/ICopyFrom.cs | 9 +++ Drivers/IExecLastId.cs | 9 +++ Drivers/MySqlConnectorDriver.cs | 75 ++++++++++++++++---- Drivers/NpgsqlDriver.cs | 82 ++++++++++++++++++---- EndToEndTests/DataGenerator.cs | 3 - Makefile | 13 ++-- scripts/tests/run_codegen.sh | 19 +++-- scripts/tests/run_codegen_matrix.sh | 38 ++++++++++ sqlc.local.yaml | 46 ++++++------ 17 files changed, 263 insertions(+), 255 deletions(-) delete mode 100644 Drivers/Generators/CopyFromDeclareGen.cs delete mode 100644 Drivers/Generators/ExecLastIdDeclareGen.cs create mode 100644 Drivers/ICopyFrom.cs create mode 100644 Drivers/IExecLastId.cs create mode 100755 scripts/tests/run_codegen_matrix.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7755a0c..3d2b20a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,13 +102,9 @@ jobs: path: dist/plugin.wasm codegen-tests: - name: Codegen Test + name: Codegen Tests runs-on: ubuntu-latest needs: [build] - strategy: - matrix: - generate-csproj: [ 'true', 'false' ] - target-framework: [ 'net8.0', 'netstandard2.0', 'netstandard2.1' ] steps: - uses: actions/checkout@v4 @@ -129,12 +125,8 @@ jobs: - name: Updating plugin sha run: ./scripts/wasm/update_sha.sh ${SQLC_CI_FILE} - - name: Codegen Test - run: ./scripts/tests/run_codegen.sh ${SQLC_CI_FILE} ${{ matrix.generate-csproj }} ${{ matrix.target-framework }} - - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ matrix.target-framework == 'net8.0' && '8.0.x' || '3.1.x' }} + - name: Codegen Tests against matrix of configurations + run: ./scripts/tests/run_codegen_matrix.sh ${SQLC_CI_FILE} end2end-tests: name: End-to-End Tests diff --git a/CodeGenerator/CodeGenerator.cs b/CodeGenerator/CodeGenerator.cs index de0b6d3..322f63d 100644 --- a/CodeGenerator/CodeGenerator.cs +++ b/CodeGenerator/CodeGenerator.cs @@ -233,13 +233,11 @@ private MemberDeclarationSyntax AddMethodDeclaration(Query query) return query.Cmd switch { - ":exec" => DbDriver.ExecDeclare(query.Name, queryTextConstant, argInterface, query), - ":one" => DbDriver.OneDeclare(query.Name, queryTextConstant, argInterface, returnInterface, query), - ":many" => DbDriver.ManyDeclare(query.Name, queryTextConstant, argInterface, returnInterface, query), - ":execlastid" => ((MySqlConnectorDriver)DbDriver) - .ExecLastIdDeclare(query.Name, queryTextConstant, argInterface, query), - ":copyfrom" => ((NpgsqlDriver)DbDriver) - .CopyFromDeclare(query.Name, queryTextConstant, argInterface, query), + ":exec" => DbDriver.ExecDeclare(queryTextConstant, argInterface, query), + ":one" => DbDriver.OneDeclare(queryTextConstant, argInterface, returnInterface, query), + ":many" => DbDriver.ManyDeclare(queryTextConstant, argInterface, returnInterface, query), + ":execlastid" => ((IExecLastId)DbDriver).ExecLastIdDeclare(queryTextConstant, argInterface, query), + ":copyfrom" => ((ICopyFrom)DbDriver).CopyFromDeclare(queryTextConstant, argInterface, query), _ => throw new InvalidDataException() }; diff --git a/Drivers/DbDriver.cs b/Drivers/DbDriver.cs index 87d1106..075e7f7 100644 --- a/Drivers/DbDriver.cs +++ b/Drivers/DbDriver.cs @@ -59,17 +59,17 @@ public string GetColumnReader(Column column, int ordinal) public abstract string TransformQueryText(Query query); - public abstract (string, string) EstablishConnection(bool isCopyCommand = false); // TODO fix codesmell - should act upon the query object + public abstract (string, string) EstablishConnection(Query query); public abstract string CreateSqlCommand(string sqlTextConstant); - public abstract MemberDeclarationSyntax OneDeclare(string name, string sqlTextConstant, string argInterface, + public abstract MemberDeclarationSyntax OneDeclare(string sqlTextConstant, string argInterface, string returnInterface, Query query); - public abstract MemberDeclarationSyntax ManyDeclare(string funcName, string sqlTextConstant, string argInterface, + public abstract MemberDeclarationSyntax ManyDeclare(string sqlTextConstant, string argInterface, string returnInterface, Query query); - public abstract MemberDeclarationSyntax ExecDeclare(string funcName, string text, string argInterface, Query query); + public abstract MemberDeclarationSyntax ExecDeclare(string text, string argInterface, Query query); public static bool IsCsharpPrimitive(string csharpType) { diff --git a/Drivers/Generators/CopyFromDeclareGen.cs b/Drivers/Generators/CopyFromDeclareGen.cs deleted file mode 100644 index 8fee9bb..0000000 --- a/Drivers/Generators/CopyFromDeclareGen.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Plugin; -using System.Collections.Generic; -using System.Linq; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - -namespace SqlcGenCsharp.Drivers.Generators; - -public class CopyFromDeclareGen(DbDriver dbDriver) -{ - public MemberDeclarationSyntax Generate(string funcName, string queryTextConstant, string argInterface, Query query) - { - return ParseMemberDeclaration($$""" - public async Task {{funcName}}(List<{{argInterface}}> args) - { - {{GetMethodBody(queryTextConstant, query)}} - } - """)!; - } - - private string GetMethodBody(string queryTextConstant, Query query) - { - var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(true); - var beginBinaryImport = $"{Variable.Connection.Name()}.BeginBinaryImportAsync({queryTextConstant}"; - - return dbDriver.DotnetFramework.LatestDotnetSupported() - ? GetAsModernDotnet() - : GetAsLegacyDotnet(); - - string GetAsModernDotnet() - { - var addRowsToCopyCommand = AddRowsToCopyCommand(); - return $$""" - { - await using {{establishConnection}}; - {{connectionOpen.AppendSemicolonUnlessEmpty()}} - await {{Variable.Connection.Name()}}.OpenAsync(); - await using var {{Variable.Writer.Name()}} = await {{beginBinaryImport}}); - {{addRowsToCopyCommand}} - await {{Variable.Writer.Name()}}.CompleteAsync(); - await {{Variable.Connection.Name()}}.CloseAsync(); - } - """; - } - - string GetAsLegacyDotnet() - { - var addRowsToCopyCommand = AddRowsToCopyCommand(); - return $$""" - { - using ({{establishConnection}}) - { - {{connectionOpen.AppendSemicolonUnlessEmpty()}} - await {{Variable.Connection.Name()}}.OpenAsync(); - using (var {{Variable.Writer.Name()}} = await {{beginBinaryImport}})) - { - {{addRowsToCopyCommand}} - await {{Variable.Writer.Name()}}.CompleteAsync(); - } - await {{Variable.Connection.Name()}}.CloseAsync(); - } - } - """; - } - - string AddRowsToCopyCommand() - { - var constructRow = new List() - .Append($"await {Variable.Writer.Name()}.StartRowAsync();") - .Concat(query.Params.Select(p => - $"await {Variable.Writer.Name()}.WriteAsync({Variable.Row.Name()}.{p.Column.Name.FirstCharToUpper()});")) - .JoinByNewLine(); - return $$""" - foreach (var {{Variable.Row.Name()}} in args) - { - {{constructRow}} - } - """; - } - } -} \ No newline at end of file diff --git a/Drivers/Generators/ExecDeclareGen.cs b/Drivers/Generators/ExecDeclareGen.cs index c37390b..9561e7a 100644 --- a/Drivers/Generators/ExecDeclareGen.cs +++ b/Drivers/Generators/ExecDeclareGen.cs @@ -1,29 +1,27 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Plugin; -using System.Collections.Generic; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace SqlcGenCsharp.Drivers.Generators; public class ExecDeclareGen(DbDriver dbDriver) { - public MemberDeclarationSyntax Generate(string funcName, string queryTextConstant, string argInterface, - Query query) + public MemberDeclarationSyntax Generate(string queryTextConstant, string argInterface, Query query) { var parametersStr = CommonGen.GetParameterListAsString(argInterface, query.Params); return ParseMemberDeclaration($$""" - public async Task {{funcName}}({{parametersStr}}) + public async Task {{query.Name}}({{parametersStr}}) { - {{GetMethodBody(queryTextConstant, query.Params)}} + {{GetMethodBody(queryTextConstant, query)}} } """)!; } - private string GetMethodBody(string queryTextConstant, IEnumerable parameters) + private string GetMethodBody(string queryTextConstant, Query query) { - var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(); + var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(query); var createSqlCommand = dbDriver.CreateSqlCommand(queryTextConstant); - var commandParameters = CommonGen.GetCommandParameters(parameters); + var commandParameters = CommonGen.GetCommandParameters(query.Params); var executeScalar = $"await {Variable.Command.Name()}.ExecuteScalarAsync();"; return dbDriver.DotnetFramework.LatestDotnetSupported() diff --git a/Drivers/Generators/ExecLastIdDeclareGen.cs b/Drivers/Generators/ExecLastIdDeclareGen.cs deleted file mode 100644 index 283b9c6..0000000 --- a/Drivers/Generators/ExecLastIdDeclareGen.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Plugin; -using System.Collections.Generic; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - -namespace SqlcGenCsharp.Drivers.Generators; - -public class ExecLastIdDeclareGen(DbDriver dbDriver) -{ - public MemberDeclarationSyntax Generate(string funcName, string queryTextConstant, string argInterface, Query query) - { - var parametersStr = CommonGen.GetParameterListAsString(argInterface, query.Params); - return ParseMemberDeclaration($$""" - public async Task {{funcName}}({{parametersStr}}) - { - {{GetMethodBody(queryTextConstant, query)}} - } - """)!; - } - - private string GetMethodBody(string queryTextConstant, Query query) - { - var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(); - var createSqlCommand = dbDriver.CreateSqlCommand(queryTextConstant); - var commandParameters = CommonGen.GetCommandParameters(query.Params); - var executeScalarAndReturnCreated = ExecuteScalarAndReturnCreated(); - - return dbDriver.DotnetFramework.LatestDotnetSupported() - ? GetWithUsingAsStatement() - : GetWithUsingAsBlock(); - - string GetWithUsingAsStatement() - { - return $$""" - { - await using {{establishConnection}}; - {{connectionOpen.AppendSemicolonUnlessEmpty()}} - await using {{createSqlCommand}}; - {{commandParameters.JoinByNewLine()}} - {{executeScalarAndReturnCreated.JoinByNewLine()}} - } - """; - } - - string GetWithUsingAsBlock() - { - return $$""" - { - using ({{establishConnection}}) - { - {{connectionOpen.AppendSemicolonUnlessEmpty()}} - using ({{createSqlCommand}}) - { - {{commandParameters.JoinByNewLine()}} - {{executeScalarAndReturnCreated.JoinByNewLine()}} - } - } - } - """; - } - - IEnumerable ExecuteScalarAndReturnCreated() - { - return new[] - { - $"await {Variable.Command.Name()}.ExecuteNonQueryAsync();", - $"return {Variable.Command.Name()}.LastInsertedId;" - }; - } - } -} \ No newline at end of file diff --git a/Drivers/Generators/ManyDeclareGen.cs b/Drivers/Generators/ManyDeclareGen.cs index c8ecc1d..c3b6619 100644 --- a/Drivers/Generators/ManyDeclareGen.cs +++ b/Drivers/Generators/ManyDeclareGen.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Plugin; -using System.Collections.Generic; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; @@ -10,13 +9,12 @@ public class ManyDeclareGen(DbDriver dbDriver) { private CommonGen CommonGen { get; } = new(dbDriver); - public MemberDeclarationSyntax Generate(string funcName, string queryTextConstant, string argInterface, - string returnInterface, Query query) + public MemberDeclarationSyntax Generate(string queryTextConstant, string argInterface, string returnInterface, Query query) { var parametersStr = CommonGen.GetParameterListAsString(argInterface, query.Params); var returnType = $"Task>"; return ParseMemberDeclaration($$""" - public async {{returnType}} {{funcName}}({{parametersStr}}) + public async {{returnType}} {{query.Name}}({{parametersStr}}) { {{GetMethodBody(queryTextConstant, returnInterface, query)}} } @@ -25,7 +23,7 @@ public MemberDeclarationSyntax Generate(string funcName, string queryTextConstan private string GetMethodBody(string queryTextConstant, string returnInterface, Query query) { - var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(); + var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(query); var createSqlCommand = dbDriver.CreateSqlCommand(queryTextConstant); var commandParameters = CommonGen.GetCommandParameters(query.Params); var initDataReader = CommonGen.InitDataReader(); diff --git a/Drivers/Generators/OneDeclareGen.cs b/Drivers/Generators/OneDeclareGen.cs index 0be13a2..f66f44e 100644 --- a/Drivers/Generators/OneDeclareGen.cs +++ b/Drivers/Generators/OneDeclareGen.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Plugin; -using System.Collections.Generic; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; @@ -10,28 +9,27 @@ public class OneDeclareGen(DbDriver dbDriver) { private CommonGen CommonGen { get; } = new(dbDriver); - public MemberDeclarationSyntax Generate(string funcName, string queryTextConstant, string argInterface, + public MemberDeclarationSyntax Generate(string queryTextConstant, string argInterface, string returnInterface, Query query) { var returnType = $"Task<{dbDriver.AddNullableSuffix(returnInterface, false)}>"; var parametersStr = CommonGen.GetParameterListAsString(argInterface, query.Params); return ParseMemberDeclaration($$""" - public async {{returnType}} {{funcName}}({{parametersStr}}) + public async {{returnType}} {{query.Name}}({{parametersStr}}) { - {{GetMethodBody(queryTextConstant, returnInterface, query.Columns, query.Params)}} + {{GetMethodBody(queryTextConstant, returnInterface, query)}} } """)!; } - private string GetMethodBody(string queryTextConstant, string returnInterface, - IEnumerable columns, IEnumerable parameters) + private string GetMethodBody(string queryTextConstant, string returnInterface, Query query) { - var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(); + var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(query); var createSqlCommand = dbDriver.CreateSqlCommand(queryTextConstant); - var commandParameters = CommonGen.GetCommandParameters(parameters); + var commandParameters = CommonGen.GetCommandParameters(query.Params); var initDataReader = CommonGen.InitDataReader(); var awaitReaderRow = CommonGen.AwaitReaderRow(); - var returnDataclass = CommonGen.InstantiateDataclass(columns, returnInterface); + var returnDataclass = CommonGen.InstantiateDataclass(query.Columns, returnInterface); return dbDriver.DotnetFramework.LatestDotnetSupported() ? GetWithUsingAsStatement() diff --git a/Drivers/ICopyFrom.cs b/Drivers/ICopyFrom.cs new file mode 100644 index 0000000..e6e1651 --- /dev/null +++ b/Drivers/ICopyFrom.cs @@ -0,0 +1,9 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Plugin; + +namespace SqlcGenCsharp.Drivers; + +public interface ICopyFrom +{ + MemberDeclarationSyntax CopyFromDeclare(string queryTextConstant, string argInterface, Query query); +} \ No newline at end of file diff --git a/Drivers/IExecLastId.cs b/Drivers/IExecLastId.cs new file mode 100644 index 0000000..f4d28b3 --- /dev/null +++ b/Drivers/IExecLastId.cs @@ -0,0 +1,9 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Plugin; + +namespace SqlcGenCsharp.Drivers; + +public interface IExecLastId +{ + MemberDeclarationSyntax ExecLastIdDeclare(string queryTextConstant, string argInterface, Query query); +} \ No newline at end of file diff --git a/Drivers/MySqlConnectorDriver.cs b/Drivers/MySqlConnectorDriver.cs index cfc336e..fd615f2 100644 --- a/Drivers/MySqlConnectorDriver.cs +++ b/Drivers/MySqlConnectorDriver.cs @@ -10,7 +10,7 @@ namespace SqlcGenCsharp.Drivers; -public partial class MySqlConnectorDriver(DotnetFramework dotnetFramework) : DbDriver(dotnetFramework) +public partial class MySqlConnectorDriver(DotnetFramework dotnetFramework) : DbDriver(dotnetFramework), IExecLastId { protected override List<(string, Func, HashSet)> GetColumnMapping() { @@ -58,7 +58,7 @@ public override UsingDirectiveSyntax[] GetUsingDirectives() .ToArray(); } - public override (string, string) EstablishConnection(bool isCopyCommand = false) + public override (string, string) EstablishConnection(Query query) { return ( $"var {Variable.Connection.Name()} = new MySqlConnection({Variable.ConnectionString.Name()})", @@ -76,28 +76,79 @@ public override string TransformQueryText(Query query) return QueryParamRegex().Replace(query.Text, _ => "@" + query.Params[counter++].Column.Name); } - public override MemberDeclarationSyntax OneDeclare(string funcName, string queryTextConstant, string argInterface, + public override MemberDeclarationSyntax OneDeclare(string queryTextConstant, string argInterface, string returnInterface, Query query) { - return new OneDeclareGen(this).Generate(funcName, queryTextConstant, argInterface, returnInterface, query); + return new OneDeclareGen(this).Generate(queryTextConstant, argInterface, returnInterface, query); } - public override MemberDeclarationSyntax ExecDeclare(string funcName, string queryTextConstant, string argInterface, - Query query) + public override MemberDeclarationSyntax ExecDeclare(string queryTextConstant, string argInterface, Query query) { - return new ExecDeclareGen(this).Generate(funcName, queryTextConstant, argInterface, query); + return new ExecDeclareGen(this).Generate(queryTextConstant, argInterface, query); } - public MemberDeclarationSyntax ExecLastIdDeclare(string funcName, string queryTextConstant, - string argInterface, Query query) + public MemberDeclarationSyntax ExecLastIdDeclare(string queryTextConstant, string argInterface, Query query) { - return new ExecLastIdDeclareGen(this).Generate(funcName, queryTextConstant, argInterface, query); + var parametersStr = CommonGen.GetParameterListAsString(argInterface, query.Params); + var (establishConnection, connectionOpen) = EstablishConnection(query); + var createSqlCommand = CreateSqlCommand(queryTextConstant); + var commandParameters = CommonGen.GetCommandParameters(query.Params); + var executeScalarAndReturnCreated = ExecuteScalarAndReturnCreated(); + var methodBody = DotnetFramework.LatestDotnetSupported() + ? GetWithUsingAsStatement() + : GetWithUsingAsBlock(); + + return ParseMemberDeclaration($$""" + public async Task {{query.Name}}({{parametersStr}}) + { + {{methodBody}} + } + """)!; + + string GetWithUsingAsStatement() + { + return $$""" + { + await using {{establishConnection}}; + {{connectionOpen.AppendSemicolonUnlessEmpty()}} + await using {{createSqlCommand}}; + {{commandParameters.JoinByNewLine()}} + {{executeScalarAndReturnCreated.JoinByNewLine()}} + } + """; + } + + string GetWithUsingAsBlock() + { + return $$""" + { + using ({{establishConnection}}) + { + {{connectionOpen.AppendSemicolonUnlessEmpty()}} + using ({{createSqlCommand}}) + { + {{commandParameters.JoinByNewLine()}} + {{executeScalarAndReturnCreated.JoinByNewLine()}} + } + } + } + """; + } + + IEnumerable ExecuteScalarAndReturnCreated() + { + return new[] + { + $"await {Variable.Command.Name()}.ExecuteNonQueryAsync();", + $"return {Variable.Command.Name()}.LastInsertedId;" + }; + } } - public override MemberDeclarationSyntax ManyDeclare(string funcName, string queryTextConstant, string argInterface, + public override MemberDeclarationSyntax ManyDeclare(string queryTextConstant, string argInterface, string returnInterface, Query query) { - return new ManyDeclareGen(this).Generate(funcName, queryTextConstant, argInterface, returnInterface, query); + return new ManyDeclareGen(this).Generate(queryTextConstant, argInterface, returnInterface, query); } [GeneratedRegex(@"\?")] diff --git a/Drivers/NpgsqlDriver.cs b/Drivers/NpgsqlDriver.cs index 1f2ba9d..9c30249 100644 --- a/Drivers/NpgsqlDriver.cs +++ b/Drivers/NpgsqlDriver.cs @@ -9,7 +9,7 @@ namespace SqlcGenCsharp.Drivers; -public class NpgsqlDriver(DotnetFramework dotnetFramework) : DbDriver(dotnetFramework) +public class NpgsqlDriver(DotnetFramework dotnetFramework) : DbDriver(dotnetFramework), ICopyFrom { protected override List<(string, Func, HashSet)> GetColumnMapping() { @@ -47,9 +47,9 @@ public override UsingDirectiveSyntax[] GetUsingDirectives() .ToArray(); } - public override (string, string) EstablishConnection(bool isCopyCommand = false) + public override (string, string) EstablishConnection(Query query) { - if (isCopyCommand) + if (query.Cmd == ":copyfrom") return ( $"var ds = NpgsqlDataSource.Create({Variable.ConnectionString.Name()})", $"var {Variable.Connection.Name()} = ds.CreateConnection()" @@ -83,26 +83,84 @@ string GetCopyCommand() } } - public override MemberDeclarationSyntax OneDeclare(string funcName, string queryTextConstant, string argInterface, + public override MemberDeclarationSyntax OneDeclare(string queryTextConstant, string argInterface, string returnInterface, Query query) { - return new OneDeclareGen(this).Generate(funcName, queryTextConstant, argInterface, returnInterface, query); + return new OneDeclareGen(this).Generate(queryTextConstant, argInterface, returnInterface, query); } - public override MemberDeclarationSyntax ExecDeclare(string funcName, string queryTextConstant, string argInterface, - Query query) + public override MemberDeclarationSyntax ExecDeclare(string queryTextConstant, string argInterface, Query query) { - return new ExecDeclareGen(this).Generate(funcName, queryTextConstant, argInterface, query); + return new ExecDeclareGen(this).Generate(queryTextConstant, argInterface, query); } - public override MemberDeclarationSyntax ManyDeclare(string funcName, string queryTextConstant, string argInterface, + public override MemberDeclarationSyntax ManyDeclare(string queryTextConstant, string argInterface, string returnInterface, Query query) { - return new ManyDeclareGen(this).Generate(funcName, queryTextConstant, argInterface, returnInterface, query); + return new ManyDeclareGen(this).Generate(queryTextConstant, argInterface, returnInterface, query); } - public MemberDeclarationSyntax CopyFromDeclare(string funcName, string queryTextConstant, string argInterface, Query query) + public MemberDeclarationSyntax CopyFromDeclare(string queryTextConstant, string argInterface, Query query) { - return new CopyFromDeclareGen(this).Generate(funcName, queryTextConstant, argInterface, query); + var (establishConnection, connectionOpen) = EstablishConnection(query); + var beginBinaryImport = $"{Variable.Connection.Name()}.BeginBinaryImportAsync({queryTextConstant}"; + var methodBody = DotnetFramework.LatestDotnetSupported() ? GetAsModernDotnet() : GetAsLegacyDotnet(); + + return ParseMemberDeclaration($$""" + public async Task {{query.Name}}(List<{{argInterface}}> args) + { + {{methodBody}} + } + """)!; + + string GetAsModernDotnet() + { + var addRowsToCopyCommand = AddRowsToCopyCommand(); + return $$""" + { + await using {{establishConnection}}; + {{connectionOpen.AppendSemicolonUnlessEmpty()}} + await {{Variable.Connection.Name()}}.OpenAsync(); + await using var {{Variable.Writer.Name()}} = await {{beginBinaryImport}}); + {{addRowsToCopyCommand}} + await {{Variable.Writer.Name()}}.CompleteAsync(); + await {{Variable.Connection.Name()}}.CloseAsync(); + } + """; + } + + string GetAsLegacyDotnet() + { + var addRowsToCopyCommand = AddRowsToCopyCommand(); + return $$""" + { + using ({{establishConnection}}) + { + {{connectionOpen.AppendSemicolonUnlessEmpty()}} + await {{Variable.Connection.Name()}}.OpenAsync(); + using (var {{Variable.Writer.Name()}} = await {{beginBinaryImport}})) + { + {{addRowsToCopyCommand}} + await {{Variable.Writer.Name()}}.CompleteAsync(); + } + await {{Variable.Connection.Name()}}.CloseAsync(); + } + } + """; + } + + string AddRowsToCopyCommand() + { + var constructRow = new List() + .Append($"await {Variable.Writer.Name()}.StartRowAsync();") + .Concat(query.Params.Select(p => $"await {Variable.Writer.Name()}.WriteAsync({Variable.Row.Name()}.{p.Column.Name.FirstCharToUpper()});")) + .JoinByNewLine(); + return $$""" + foreach (var {{Variable.Row.Name()}} in args) + { + {{constructRow}} + } + """; + } } } \ No newline at end of file diff --git a/EndToEndTests/DataGenerator.cs b/EndToEndTests/DataGenerator.cs index 5fd0186..70b0b6f 100644 --- a/EndToEndTests/DataGenerator.cs +++ b/EndToEndTests/DataGenerator.cs @@ -1,6 +1,3 @@ -using NpgsqlExample; -using System; - namespace SqlcGenCsharpTests; public static class DataGenerator diff --git a/Makefile b/Makefile index e3f1d2a..a657f12 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,7 @@ dockerfile-generate: protobuf-generate: ./scripts/generate_protobuf.sh -# tests are run against generated code - can be generated either via a "process" or "wasm" SQLC plugins -run-tests: +run-end2end-tests: ./scripts/tests/run_end2end.sh # process type plugin @@ -24,10 +23,13 @@ dotnet-build-process: protobuf-generate dotnet-format dotnet-publish-process: dotnet-build-process dotnet publish LocalRunner -c release --output dist/ +run-codegen-tests-process: + ./scripts/tests/run_codegen_matrix.sh sqlc.local.yaml + sqlc-generate-process: dotnet-publish-process sqlc -f sqlc.local.yaml generate -test-process-plugin: sqlc-generate-process dockerfile-generate run-tests +test-process-plugin: sqlc-generate-process dockerfile-generate run-codegen-tests-process run-end2end-tests # WASM type plugin dotnet-publish-wasm: protobuf-generate @@ -40,4 +42,7 @@ update-wasm-plugin: sqlc-generate-wasm: dotnet-publish-wasm update-wasm-plugin SQLCCACHE=./; sqlc -f sqlc.ci.yaml generate -test-wasm-plugin: sqlc-generate-wasm update-wasm-plugin dockerfile-generate run-tests \ No newline at end of file +run-codegen-tests-wasm: + ./scripts/tests/run_codegen_matrix.sh sqlc.ci.yaml + +test-wasm-plugin: sqlc-generate-wasm update-wasm-plugin dockerfile-generate run-codegen-tests-wasm run-end2end-tests \ No newline at end of file diff --git a/scripts/tests/run_codegen.sh b/scripts/tests/run_codegen.sh index efadf1b..7240af2 100755 --- a/scripts/tests/run_codegen.sh +++ b/scripts/tests/run_codegen.sh @@ -1,7 +1,6 @@ #!/usr/bin/env bash -set -ex -source .env +set -e mapfile -t examples < <(dotnet sln list | grep Example | xargs -n 1 dirname) # TODO standardize across scripts @@ -15,14 +14,23 @@ generated_files_cleanup() { echo "Deleting .cs files in ${example_dir}" find "${example_dir}/" -type f -name "*.cs" -exec rm -f {} \; if [ "${generate_csproj}" = "true" ]; then - echo "Deleting .csproj file" && rm "${example_dir}/${example_dir}.csproj" + echo "Deleting .csproj file" && mv "${example_dir}/${example_dir}.csproj" "/tmp/${example_dir}.csproj" + fi + done +} + +generated_files_restore() { + for example_dir in "${examples[@]}" + do + if [ "${generate_csproj}" = "true" ]; then + echo "Restoring .csproj file" && mv "/tmp/${example_dir}.csproj" "${example_dir}/${example_dir}.csproj" fi done } change_config() { for ((i=0; i<${#examples[@]}; i++)); do - echo "Changing configuration for project ${example_dir}" + echo "Changing configuration for project ${examples[i]}" yq -i " .sql[${i}].codegen[0].options.generateCsproj = ${generate_csproj} | .sql[${i}].codegen[0].options.targetFramework = \"${target_framework}\" @@ -64,4 +72,5 @@ for test_function in "${test_functions[@]}"; do exit "${status_code}" fi echo "Test ${test_function} passed" -done \ No newline at end of file +done +generated_files_restore \ No newline at end of file diff --git a/scripts/tests/run_codegen_matrix.sh b/scripts/tests/run_codegen_matrix.sh new file mode 100755 index 0000000..00f2f1d --- /dev/null +++ b/scripts/tests/run_codegen_matrix.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -e +source .env + +config_file=$1 + +examples=() +while IFS= read -r example; do + examples+=("${example}") +done < <(dotnet sln list | grep Example | xargs -n 1 dirname) + +change_config_to_default() { + generate_csproj="true" + target_framework="net8.0" + for ((i=0; i<${#examples[@]}; i++)); do + echo "Changing configuration back to default for project ${examples[i]}" + yq -i " + .sql[${i}].codegen[0].options.generateCsproj = ${generate_csproj} | + .sql[${i}].codegen[0].options.targetFramework = \"${target_framework}\" + " "${config_file}" + echo "${examples[i]} codegen config:" && yq ".sql[${i}].codegen[0]" "${config_file}" + done +} + +generate_csproj="true false" +target_framework="net8.0 netstandard2.0 netstandard2.1" + +for x in $generate_csproj; do + for y in $target_framework; do + echo "Running with generate-csproj=$x, target-framework=$y" + ./scripts/tests/run_codegen.sh "${config_file}" "$x" "$y" + echo "Finished combination" + echo "-------------------" + done +done + +change_config_to_default \ No newline at end of file diff --git a/sqlc.local.yaml b/sqlc.local.yaml index a39dd2b..b365561 100644 --- a/sqlc.local.yaml +++ b/sqlc.local.yaml @@ -1,26 +1,26 @@ version: "2" plugins: -- name: csharp - process: - cmd: ./dist/LocalRunner + - name: csharp + process: + cmd: ./dist/LocalRunner sql: -- schema: "examples/authors/postgresql/schema.sql" - queries: "examples/authors/postgresql/query.sql" - engine: "postgresql" - codegen: - - plugin: csharp - out: NpgsqlExample - options: - driver: Npgsql - targetFramework: net8.0 - generateCsproj: true -- schema: "examples/authors/mysql/schema.sql" - queries: "examples/authors/mysql/query.sql" - engine: "mysql" - codegen: - - plugin: csharp - out: MySqlConnectorExample - options: - driver: MySqlConnector - targetFramework: net8.0 - generateCsproj: true \ No newline at end of file + - schema: "examples/authors/postgresql/schema.sql" + queries: "examples/authors/postgresql/query.sql" + engine: "postgresql" + codegen: + - plugin: csharp + out: NpgsqlExample + options: + driver: Npgsql + targetFramework: net8.0 + generateCsproj: true + - schema: "examples/authors/mysql/schema.sql" + queries: "examples/authors/mysql/query.sql" + engine: "mysql" + codegen: + - plugin: csharp + out: MySqlConnectorExample + options: + driver: MySqlConnector + targetFramework: net8.0 + generateCsproj: true