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 a02c8d33..b774d0fe 100644 Binary files a/examples/SqliteDapperExample/request.message and b/examples/SqliteDapperExample/request.message differ diff --git a/examples/SqliteDapperLegacyExample/QuerySql.cs b/examples/SqliteDapperLegacyExample/QuerySql.cs index 0ae3b549..b28123ce 100644 --- a/examples/SqliteDapperLegacyExample/QuerySql.cs +++ b/examples/SqliteDapperLegacyExample/QuerySql.cs @@ -195,6 +195,40 @@ public async Task 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 4e6a0021..8b455052 100644 Binary files a/examples/SqliteDapperLegacyExample/request.message and b/examples/SqliteDapperLegacyExample/request.message differ diff --git a/examples/SqliteExample/QuerySql.cs b/examples/SqliteExample/QuerySql.cs index 0123d6ee..f4506830 100644 --- a/examples/SqliteExample/QuerySql.cs +++ b/examples/SqliteExample/QuerySql.cs @@ -266,6 +266,66 @@ public async Task 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 9ed6b861..84b7f159 100644 Binary files a/examples/SqliteExample/request.message and b/examples/SqliteExample/request.message differ 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 23341d27..d6570860 100644 Binary files a/examples/SqliteLegacyExample/request.message and b/examples/SqliteLegacyExample/request.message differ diff --git a/examples/config/sqlite/query.sql b/examples/config/sqlite/query.sql index cf874286..be738afb 100644 --- a/examples/config/sqlite/query.sql +++ b/examples/config/sqlite/query.sql @@ -18,6 +18,10 @@ INSERT INTO authors (name, bio) VALUES (?, ?) RETURNING id; SELECT * FROM authors WHERE id = ? LIMIT 1; +-- name: GetAuthorByIdWithMultipleNamedParam :one +SELECT * FROM authors WHERE id = sqlc.arg('id_arg') AND id = sqlc.arg('id_arg') LIMIT sqlc.narg('take'); + + -- name: GetAuthorByNamePattern :many SELECT * FROM authors WHERE name LIKE COALESCE(sqlc.narg('name_pattern'), '%');