From c1c76c4276683325ddca947ab8b9339e7a286d4e Mon Sep 17 00:00:00 2001 From: Doron Eli Rachman Date: Tue, 12 Aug 2025 23:39:22 +0300 Subject: [PATCH] fix multiple use of name param in sqlite --- Drivers/SqliteDriver.cs | 26 +++++-- end2end/EndToEndScaffold/Config.cs | 7 +- .../EndToEndScaffold/Templates/SqliteTests.cs | 27 +++++++ .../SqliteDapperTester.generated.cs | 19 +++++ .../EndToEndTests/SqliteTester.generated.cs | 19 +++++ .../SqliteDapperTester.generated.cs | 19 +++++ .../SqliteTester.generated.cs | 19 +++++ examples/SqliteDapperExample/QuerySql.cs | 34 +++++++++ examples/SqliteDapperExample/request.json | 72 ++++++++++++++++++ examples/SqliteDapperExample/request.message | Bin 7462 -> 7813 bytes .../SqliteDapperLegacyExample/QuerySql.cs | 34 +++++++++ .../SqliteDapperLegacyExample/request.json | 72 ++++++++++++++++++ .../SqliteDapperLegacyExample/request.message | Bin 7496 -> 7847 bytes examples/SqliteExample/QuerySql.cs | 60 +++++++++++++++ examples/SqliteExample/request.json | 72 ++++++++++++++++++ examples/SqliteExample/request.message | Bin 7446 -> 7797 bytes examples/SqliteLegacyExample/QuerySql.cs | 69 +++++++++++++++++ examples/SqliteLegacyExample/request.json | 72 ++++++++++++++++++ examples/SqliteLegacyExample/request.message | Bin 7480 -> 7831 bytes examples/config/sqlite/query.sql | 4 + 20 files changed, 617 insertions(+), 8 deletions(-) diff --git a/Drivers/SqliteDriver.cs b/Drivers/SqliteDriver.cs index 2796d406..d8bbeacc 100644 --- a/Drivers/SqliteDriver.cs +++ b/Drivers/SqliteDriver.cs @@ -131,15 +131,29 @@ public override string CreateSqlCommand(string sqlTextConstant) public override string TransformQueryText(Query query) { - var areArgumentsNumbered = new Regex($@"\?\d\b*").IsMatch(query.Text); + // Regex to detect numbered parameters like ?1, ?2 + var areArgumentsNumbered = new Regex(@"\?\d+\b").IsMatch(query.Text); var queryText = query.Text; - for (var i = 0; i < query.Params.Count; i++) + if (areArgumentsNumbered) { - var currentParameter = query.Params[i]; - var column = GetColumnFromParam(currentParameter, query); - var regexToUse = areArgumentsNumbered ? $@"\?{i + 1}\b*" : $@"\?\b*"; - queryText = new Regex(regexToUse).Replace(queryText, $"@{column.Name}", 1); + // For numbered parameters, we replace all occurrences of each parameter number. + foreach (var p in query.Params) + { + var column = GetColumnFromParam(p, query); + var regex = new Regex($@"\?{p.Number}\b"); + queryText = regex.Replace(queryText, $"@{column.Name}"); + } + } + else + { + // For positional '?' parameters, we must replace them one by one in order. + var regex = new Regex(@"\?"); + foreach (var p in query.Params) + { + var column = GetColumnFromParam(p, query); + queryText = regex.Replace(queryText, $"@{column.Name}", 1); + } } return queryText; } diff --git a/end2end/EndToEndScaffold/Config.cs b/end2end/EndToEndScaffold/Config.cs index 07c49355..880bdd8c 100644 --- a/end2end/EndToEndScaffold/Config.cs +++ b/end2end/EndToEndScaffold/Config.cs @@ -28,6 +28,7 @@ public enum KnownTestType // Sqlite SqliteDataTypes, + SqliteMultipleNamedParam, SqliteDataTypesOverride, SqliteCopyFrom, SqliteTransaction, @@ -274,7 +275,8 @@ internal static class Config KnownTestType.SqliteTransactionRollback, KnownTestType.SqliteDataTypes, KnownTestType.SqliteCopyFrom, - KnownTestType.SqliteDataTypesOverride + KnownTestType.SqliteDataTypesOverride, + KnownTestType.SqliteMultipleNamedParam ] } }, @@ -299,7 +301,8 @@ internal static class Config KnownTestType.SqliteTransactionRollback, KnownTestType.SqliteDataTypes, KnownTestType.SqliteCopyFrom, - KnownTestType.SqliteDataTypesOverride + KnownTestType.SqliteDataTypesOverride, + KnownTestType.SqliteMultipleNamedParam ] } }, diff --git a/end2end/EndToEndScaffold/Templates/SqliteTests.cs b/end2end/EndToEndScaffold/Templates/SqliteTests.cs index 03f6f8c0..deddb5aa 100644 --- a/end2end/EndToEndScaffold/Templates/SqliteTests.cs +++ b/end2end/EndToEndScaffold/Templates/SqliteTests.cs @@ -174,6 +174,33 @@ private static void AssertSingularEquals(QuerySql.GetSqliteFunctionsRow expected Assert.That(actual.MaxText, Is.EqualTo(expected.MaxText)); } """ + }, + [KnownTestType.SqliteMultipleNamedParam] = new TestImpl + { + Impl = $$""" + [Test] + public async Task TestGetAuthorByIdWithMultipleNamedParam() + { + {{Consts.CreateBojackAuthor}} + var expected = new QuerySql.GetAuthorByIdWithMultipleNamedParamRow + { + Id = {{Consts.BojackId}}, + Name = {{Consts.BojackAuthor}}, + Bio = {{Consts.BojackTheme}} + }; + var actual = await this.QuerySql.GetAuthorByIdWithMultipleNamedParam(new QuerySql.GetAuthorByIdWithMultipleNamedParamArgs + { + IdArg = {{Consts.BojackId}}, + Take = 1 + }); + Assert.That(SingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}})); + } + + private static bool SingularEquals(QuerySql.GetAuthorByIdWithMultipleNamedParamRow x, QuerySql.GetAuthorByIdWithMultipleNamedParamRow y) + { + return x.Id.Equals(y.Id) && x.Name.Equals(y.Name) && x.Bio.Equals(y.Bio); + } + """ } }; } \ No newline at end of file diff --git a/end2end/EndToEndTests/SqliteDapperTester.generated.cs b/end2end/EndToEndTests/SqliteDapperTester.generated.cs index 75cb0272..fd786025 100644 --- a/end2end/EndToEndTests/SqliteDapperTester.generated.cs +++ b/end2end/EndToEndTests/SqliteDapperTester.generated.cs @@ -333,6 +333,25 @@ private static void AssertSingularEquals(QuerySql.GetSqliteTypesRow expected, Qu Assert.That(actual.CBlob, Is.EqualTo(expected.CBlob)); } + [Test] + public async Task TestGetAuthorByIdWithMultipleNamedParam() + { + await this.QuerySql.CreateAuthor(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var expected = new QuerySql.GetAuthorByIdWithMultipleNamedParamRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + var actual = await this.QuerySql.GetAuthorByIdWithMultipleNamedParam(new QuerySql.GetAuthorByIdWithMultipleNamedParamArgs { IdArg = 1111, Take = 1 }); + Assert.That(SingularEquals(expected, actual)); + } + + private static bool SingularEquals(QuerySql.GetAuthorByIdWithMultipleNamedParamRow x, QuerySql.GetAuthorByIdWithMultipleNamedParamRow y) + { + return x.Id.Equals(y.Id) && x.Name.Equals(y.Name) && x.Bio.Equals(y.Bio); + } + [Test] [TestCase(-54355, 9787.66, "Have One On Me")] [TestCase(null, 0.0, null)] diff --git a/end2end/EndToEndTests/SqliteTester.generated.cs b/end2end/EndToEndTests/SqliteTester.generated.cs index d2070c6f..cd1fc078 100644 --- a/end2end/EndToEndTests/SqliteTester.generated.cs +++ b/end2end/EndToEndTests/SqliteTester.generated.cs @@ -333,6 +333,25 @@ private static void AssertSingularEquals(QuerySql.GetSqliteTypesRow expected, Qu Assert.That(actual.CBlob, Is.EqualTo(expected.CBlob)); } + [Test] + public async Task TestGetAuthorByIdWithMultipleNamedParam() + { + await this.QuerySql.CreateAuthor(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var expected = new QuerySql.GetAuthorByIdWithMultipleNamedParamRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + var actual = await this.QuerySql.GetAuthorByIdWithMultipleNamedParam(new QuerySql.GetAuthorByIdWithMultipleNamedParamArgs { IdArg = 1111, Take = 1 }); + Assert.That(SingularEquals(expected, actual.Value)); + } + + private static bool SingularEquals(QuerySql.GetAuthorByIdWithMultipleNamedParamRow x, QuerySql.GetAuthorByIdWithMultipleNamedParamRow y) + { + return x.Id.Equals(y.Id) && x.Name.Equals(y.Name) && x.Bio.Equals(y.Bio); + } + [Test] [TestCase(-54355, 9787.66, "Have One On Me")] [TestCase(null, 0.0, null)] diff --git a/end2end/EndToEndTestsLegacy/SqliteDapperTester.generated.cs b/end2end/EndToEndTestsLegacy/SqliteDapperTester.generated.cs index 4224f7e1..166a6016 100644 --- a/end2end/EndToEndTestsLegacy/SqliteDapperTester.generated.cs +++ b/end2end/EndToEndTestsLegacy/SqliteDapperTester.generated.cs @@ -333,6 +333,25 @@ private static void AssertSingularEquals(QuerySql.GetSqliteTypesRow expected, Qu Assert.That(actual.CBlob, Is.EqualTo(expected.CBlob)); } + [Test] + public async Task TestGetAuthorByIdWithMultipleNamedParam() + { + await this.QuerySql.CreateAuthor(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var expected = new QuerySql.GetAuthorByIdWithMultipleNamedParamRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + var actual = await this.QuerySql.GetAuthorByIdWithMultipleNamedParam(new QuerySql.GetAuthorByIdWithMultipleNamedParamArgs { IdArg = 1111, Take = 1 }); + Assert.That(SingularEquals(expected, actual)); + } + + private static bool SingularEquals(QuerySql.GetAuthorByIdWithMultipleNamedParamRow x, QuerySql.GetAuthorByIdWithMultipleNamedParamRow y) + { + return x.Id.Equals(y.Id) && x.Name.Equals(y.Name) && x.Bio.Equals(y.Bio); + } + [Test] [TestCase(-54355, 9787.66, "Have One On Me")] [TestCase(null, 0.0, null)] diff --git a/end2end/EndToEndTestsLegacy/SqliteTester.generated.cs b/end2end/EndToEndTestsLegacy/SqliteTester.generated.cs index 39ea0f7f..70ea0b2b 100644 --- a/end2end/EndToEndTestsLegacy/SqliteTester.generated.cs +++ b/end2end/EndToEndTestsLegacy/SqliteTester.generated.cs @@ -333,6 +333,25 @@ private static void AssertSingularEquals(QuerySql.GetSqliteTypesRow expected, Qu Assert.That(actual.CBlob, Is.EqualTo(expected.CBlob)); } + [Test] + public async Task TestGetAuthorByIdWithMultipleNamedParam() + { + await this.QuerySql.CreateAuthor(new QuerySql.CreateAuthorArgs { Id = 1111, Name = "Bojack Horseman", Bio = "Back in the 90s he was in a very famous TV show" }); + var expected = new QuerySql.GetAuthorByIdWithMultipleNamedParamRow + { + Id = 1111, + Name = "Bojack Horseman", + Bio = "Back in the 90s he was in a very famous TV show" + }; + var actual = await this.QuerySql.GetAuthorByIdWithMultipleNamedParam(new QuerySql.GetAuthorByIdWithMultipleNamedParamArgs { IdArg = 1111, Take = 1 }); + Assert.That(SingularEquals(expected, actual)); + } + + private static bool SingularEquals(QuerySql.GetAuthorByIdWithMultipleNamedParamRow x, QuerySql.GetAuthorByIdWithMultipleNamedParamRow y) + { + return x.Id.Equals(y.Id) && x.Name.Equals(y.Name) && x.Bio.Equals(y.Bio); + } + [Test] [TestCase(-54355, 9787.66, "Have One On Me")] [TestCase(null, 0.0, null)] diff --git a/examples/SqliteDapperExample/QuerySql.cs b/examples/SqliteDapperExample/QuerySql.cs index 46afa735..62b430ee 100644 --- a/examples/SqliteDapperExample/QuerySql.cs +++ b/examples/SqliteDapperExample/QuerySql.cs @@ -194,6 +194,40 @@ public class GetAuthorByIdArgs return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorByIdSql, queryParams, transaction: this.Transaction); } + private const string GetAuthorByIdWithMultipleNamedParamSql = "SELECT id, name, bio FROM authors WHERE id = @id_arg AND id = @id_arg LIMIT @take"; + public class GetAuthorByIdWithMultipleNamedParamRow + { + public required int Id { get; init; } + public required string Name { get; init; } + public string? Bio { get; init; } + }; + public class GetAuthorByIdWithMultipleNamedParamArgs + { + public required int IdArg { get; init; } + public int? Take { get; init; } + }; + public async Task GetAuthorByIdWithMultipleNamedParam(GetAuthorByIdWithMultipleNamedParamArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("id_arg", args.IdArg); + queryParams.Add("take", args.Take); + if (this.Transaction == null) + { + using (var connection = new SqliteConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetAuthorByIdWithMultipleNamedParamSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != System.Data.ConnectionState.Open) + { + throw new System.InvalidOperationException("Transaction is provided, but its connection is null."); + } + + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorByIdWithMultipleNamedParamSql, queryParams, transaction: this.Transaction); + } + private const string GetAuthorByNamePatternSql = "SELECT id, name, bio FROM authors WHERE name LIKE COALESCE ( @name_pattern , '%' ) "; public class GetAuthorByNamePatternRow { diff --git a/examples/SqliteDapperExample/request.json b/examples/SqliteDapperExample/request.json index b26edf17..89eef44a 100644 --- a/examples/SqliteDapperExample/request.json +++ b/examples/SqliteDapperExample/request.json @@ -467,6 +467,78 @@ ], "filename": "query.sql" }, + { + "text": "SELECT id, name, bio FROM authors WHERE id = ?1 AND id = ?1 LIMIT ?2", + "name": "GetAuthorByIdWithMultipleNamedParam", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "INTEGER" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "TEXT" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "TEXT" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id_arg", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "name": "authors" + }, + "type": { + "name": "INTEGER" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "take", + "length": -1, + "isNamedParam": true, + "type": { + "name": "integer" + } + } + } + ], + "filename": "query.sql" + }, { "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(?1, '%')", "name": "GetAuthorByNamePattern", diff --git a/examples/SqliteDapperExample/request.message b/examples/SqliteDapperExample/request.message index a02c8d3368fc80b6ee273c0e783ed01bc34c6d73..b774d0fe7120f2830bbdcfcf6a69455b93bcea1d 100644 GIT binary patch delta 127 zcmZ2x)oQz87bnXdCN7uB^LVuFY!&Pc6&(Fs6f#p3z-%8+U(XN)dm|xb_tX-{(vpn) zBBx5vl<>@w4Byh6lFWjfRKLXB)Rch4qQu62IUstQyw cWu|B;aWDzVaj}#nW~WZhWmDO_i!( GetAuthorById(GetAuthorByIdArgs args) return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorByIdSql, queryParams, transaction: this.Transaction); } + private const string GetAuthorByIdWithMultipleNamedParamSql = "SELECT id, name, bio FROM authors WHERE id = @id_arg AND id = @id_arg LIMIT @take"; + public class GetAuthorByIdWithMultipleNamedParamRow + { + public int Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorByIdWithMultipleNamedParamArgs + { + public int IdArg { get; set; } + public int? Take { get; set; } + }; + public async Task GetAuthorByIdWithMultipleNamedParam(GetAuthorByIdWithMultipleNamedParamArgs args) + { + var queryParams = new Dictionary(); + queryParams.Add("id_arg", args.IdArg); + queryParams.Add("take", args.Take); + if (this.Transaction == null) + { + using (var connection = new SqliteConnection(ConnectionString)) + { + var result = await connection.QueryFirstOrDefaultAsync(GetAuthorByIdWithMultipleNamedParamSql, queryParams); + return result; + } + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != System.Data.ConnectionState.Open) + { + throw new System.InvalidOperationException("Transaction is provided, but its connection is null."); + } + + return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorByIdWithMultipleNamedParamSql, queryParams, transaction: this.Transaction); + } + private const string GetAuthorByNamePatternSql = "SELECT id, name, bio FROM authors WHERE name LIKE COALESCE ( @name_pattern , '%' ) "; public class GetAuthorByNamePatternRow { diff --git a/examples/SqliteDapperLegacyExample/request.json b/examples/SqliteDapperLegacyExample/request.json index d27f139d..d7a8b08b 100644 --- a/examples/SqliteDapperLegacyExample/request.json +++ b/examples/SqliteDapperLegacyExample/request.json @@ -467,6 +467,78 @@ ], "filename": "query.sql" }, + { + "text": "SELECT id, name, bio FROM authors WHERE id = ?1 AND id = ?1 LIMIT ?2", + "name": "GetAuthorByIdWithMultipleNamedParam", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "INTEGER" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "TEXT" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "TEXT" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id_arg", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "name": "authors" + }, + "type": { + "name": "INTEGER" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "take", + "length": -1, + "isNamedParam": true, + "type": { + "name": "integer" + } + } + } + ], + "filename": "query.sql" + }, { "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(?1, '%')", "name": "GetAuthorByNamePattern", diff --git a/examples/SqliteDapperLegacyExample/request.message b/examples/SqliteDapperLegacyExample/request.message index 4e6a00214ff7eaf6ac546291b16be352fe3e16c5..8b455052ef1ebc59c9f636ba775b1efed2dc62ca 100644 GIT binary patch delta 133 zcmX?MwcK{YIZl>4Ok6ILCvt1M*eci?DmePNC}gH6fZ0BtzMdfp_C`X=?x`h?r6n2p zMNXBTDdCwV8NQ`CC7A^|seXyMsVM=8MTxmmELQn>sgw12+jvbm7=;YE*fLY%6N}O( i@8?yWEWpFUqou^bBqYbhQj(aRI=PWeW%D`CDq#S@n CreateAuthorReturnId(CreateAuthorReturnIdArgs args) return null; } + private const string GetAuthorByIdWithMultipleNamedParamSql = "SELECT id, name, bio FROM authors WHERE id = @id_arg AND id = @id_arg LIMIT @take"; + public readonly record struct GetAuthorByIdWithMultipleNamedParamRow(int Id, string Name, string? Bio); + public readonly record struct GetAuthorByIdWithMultipleNamedParamArgs(int IdArg, int? Take); + public async Task GetAuthorByIdWithMultipleNamedParam(GetAuthorByIdWithMultipleNamedParamArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqliteConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqliteCommand(GetAuthorByIdWithMultipleNamedParamSql, connection)) + { + command.Parameters.AddWithValue("@id_arg", args.IdArg); + command.Parameters.AddWithValue("@take", args.Take ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdWithMultipleNamedParamRow + { + Id = reader.GetInt32(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + } + + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != System.Data.ConnectionState.Open) + { + throw new System.InvalidOperationException("Transaction is provided, but its connection is null."); + } + + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByIdWithMultipleNamedParamSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@id_arg", args.IdArg); + command.Parameters.AddWithValue("@take", args.Take ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdWithMultipleNamedParamRow + { + Id = reader.GetInt32(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(int Id, string Name, string? Bio); public readonly record struct GetAuthorByNamePatternArgs(string? NamePattern); diff --git a/examples/SqliteExample/request.json b/examples/SqliteExample/request.json index f5c7e569..14a1c32d 100644 --- a/examples/SqliteExample/request.json +++ b/examples/SqliteExample/request.json @@ -467,6 +467,78 @@ ], "filename": "query.sql" }, + { + "text": "SELECT id, name, bio FROM authors WHERE id = ?1 AND id = ?1 LIMIT ?2", + "name": "GetAuthorByIdWithMultipleNamedParam", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "INTEGER" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "TEXT" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "TEXT" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id_arg", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "name": "authors" + }, + "type": { + "name": "INTEGER" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "take", + "length": -1, + "isNamedParam": true, + "type": { + "name": "integer" + } + } + } + ], + "filename": "query.sql" + }, { "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(?1, '%')", "name": "GetAuthorByNamePattern", diff --git a/examples/SqliteExample/request.message b/examples/SqliteExample/request.message index 9ed6b86154b91e46ec59c8aef52012d2b348b5ee..84b7f159f4c4f4dfc0df481b799c3ae9ccdc3830 100644 GIT binary patch delta 155 zcmbPc_0?v>dQO%*Ok6IL9at3o6v91RgIpCdQxt3!>}O3#$=%#sY>(wvgaf}B*p#N5=BfW)H2Tqzc-{Jhl30=#X!rW}kyhFol! uDe;L#>5~`ms>)O`Wu|B;aWDzVaj}#nW~UnbhXF>5$qytYHm~PQ7X|=@!88B> delta 12 TcmexrGtFwldd|&!-08vqB{~GK diff --git a/examples/SqliteLegacyExample/QuerySql.cs b/examples/SqliteLegacyExample/QuerySql.cs index 2f13abfc..999fc912 100644 --- a/examples/SqliteLegacyExample/QuerySql.cs +++ b/examples/SqliteLegacyExample/QuerySql.cs @@ -304,6 +304,75 @@ public async Task GetAuthorById(GetAuthorByIdArgs args) return null; } + private const string GetAuthorByIdWithMultipleNamedParamSql = "SELECT id, name, bio FROM authors WHERE id = @id_arg AND id = @id_arg LIMIT @take"; + public class GetAuthorByIdWithMultipleNamedParamRow + { + public int Id { get; set; } + public string Name { get; set; } + public string Bio { get; set; } + }; + public class GetAuthorByIdWithMultipleNamedParamArgs + { + public int IdArg { get; set; } + public int? Take { get; set; } + }; + public async Task GetAuthorByIdWithMultipleNamedParam(GetAuthorByIdWithMultipleNamedParamArgs args) + { + if (this.Transaction == null) + { + using (var connection = new SqliteConnection(ConnectionString)) + { + await connection.OpenAsync(); + using (var command = new SqliteCommand(GetAuthorByIdWithMultipleNamedParamSql, connection)) + { + command.Parameters.AddWithValue("@id_arg", args.IdArg); + command.Parameters.AddWithValue("@take", args.Take ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdWithMultipleNamedParamRow + { + Id = reader.GetInt32(0), + Name = reader.GetString(1), + Bio = reader.IsDBNull(2) ? null : reader.GetString(2) + }; + } + } + } + } + + return null; + } + + if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != System.Data.ConnectionState.Open) + { + throw new System.InvalidOperationException("Transaction is provided, but its connection is null."); + } + + using (var command = this.Transaction.Connection.CreateCommand()) + { + command.CommandText = GetAuthorByIdWithMultipleNamedParamSql; + command.Transaction = this.Transaction; + command.Parameters.AddWithValue("@id_arg", args.IdArg); + command.Parameters.AddWithValue("@take", args.Take ?? (object)DBNull.Value); + using (var reader = await command.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + return new GetAuthorByIdWithMultipleNamedParamRow + { + Id = reader.GetInt32(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 { diff --git a/examples/SqliteLegacyExample/request.json b/examples/SqliteLegacyExample/request.json index 10861a07..b2a9fb66 100644 --- a/examples/SqliteLegacyExample/request.json +++ b/examples/SqliteLegacyExample/request.json @@ -467,6 +467,78 @@ ], "filename": "query.sql" }, + { + "text": "SELECT id, name, bio FROM authors WHERE id = ?1 AND id = ?1 LIMIT ?2", + "name": "GetAuthorByIdWithMultipleNamedParam", + "cmd": ":one", + "columns": [ + { + "name": "id", + "notNull": true, + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "INTEGER" + }, + "originalName": "id" + }, + { + "name": "name", + "notNull": true, + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "TEXT" + }, + "originalName": "name" + }, + { + "name": "bio", + "length": -1, + "table": { + "name": "authors" + }, + "type": { + "name": "TEXT" + }, + "originalName": "bio" + } + ], + "parameters": [ + { + "number": 1, + "column": { + "name": "id_arg", + "notNull": true, + "length": -1, + "isNamedParam": true, + "table": { + "name": "authors" + }, + "type": { + "name": "INTEGER" + }, + "originalName": "id" + } + }, + { + "number": 2, + "column": { + "name": "take", + "length": -1, + "isNamedParam": true, + "type": { + "name": "integer" + } + } + } + ], + "filename": "query.sql" + }, { "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(?1, '%')", "name": "GetAuthorByNamePattern", diff --git a/examples/SqliteLegacyExample/request.message b/examples/SqliteLegacyExample/request.message index 23341d278b8e13aa8578bcb13c0da4f06699b5d3..d657086028151a8fce75f68b9ef0951fe59b5f4a 100644 GIT binary patch delta 131 zcmdmCHQjc@VNRAiOk6ILm-A@b*(%r@DmePNC}gH6fZ0BtzMdfp_C`X=?x`h?r6n2p zMNXBTDdCwV8NQ`CC7A^|seXyMsVM=8MTxnSe{=NknsP7-8FH~@ro<-}rBBY`Q5C3S g%1qHx;$RYz<6