Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions Drivers/SqliteDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can extract the regex to a partial static class, like in one of the other drivers. See here for example. We can fix in another PR though

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, let's improve on the next PR

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;
}
Expand Down
7 changes: 5 additions & 2 deletions end2end/EndToEndScaffold/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum KnownTestType

// Sqlite
SqliteDataTypes,
SqliteMultipleNamedParam,
SqliteDataTypesOverride,
SqliteCopyFrom,
SqliteTransaction,
Expand Down Expand Up @@ -274,7 +275,8 @@ internal static class Config
KnownTestType.SqliteTransactionRollback,
KnownTestType.SqliteDataTypes,
KnownTestType.SqliteCopyFrom,
KnownTestType.SqliteDataTypesOverride
KnownTestType.SqliteDataTypesOverride,
KnownTestType.SqliteMultipleNamedParam
]
}
},
Expand All @@ -299,7 +301,8 @@ internal static class Config
KnownTestType.SqliteTransactionRollback,
KnownTestType.SqliteDataTypes,
KnownTestType.SqliteCopyFrom,
KnownTestType.SqliteDataTypesOverride
KnownTestType.SqliteDataTypesOverride,
KnownTestType.SqliteMultipleNamedParam
]
}
},
Expand Down
27 changes: 27 additions & 0 deletions end2end/EndToEndScaffold/Templates/SqliteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
"""
}
};
}
19 changes: 19 additions & 0 deletions end2end/EndToEndTests/SqliteDapperTester.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
19 changes: 19 additions & 0 deletions end2end/EndToEndTests/SqliteTester.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
19 changes: 19 additions & 0 deletions end2end/EndToEndTestsLegacy/SqliteDapperTester.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
19 changes: 19 additions & 0 deletions end2end/EndToEndTestsLegacy/SqliteTester.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
34 changes: 34 additions & 0 deletions examples/SqliteDapperExample/QuerySql.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,40 @@ public class GetAuthorByIdArgs
return await this.Transaction.Connection.QueryFirstOrDefaultAsync<GetAuthorByIdRow?>(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<GetAuthorByIdWithMultipleNamedParamRow?> GetAuthorByIdWithMultipleNamedParam(GetAuthorByIdWithMultipleNamedParamArgs args)
{
var queryParams = new Dictionary<string, object?>();
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<GetAuthorByIdWithMultipleNamedParamRow?>(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<GetAuthorByIdWithMultipleNamedParamRow?>(GetAuthorByIdWithMultipleNamedParamSql, queryParams, transaction: this.Transaction);
}

private const string GetAuthorByNamePatternSql = "SELECT id, name, bio FROM authors WHERE name LIKE COALESCE ( @name_pattern , '%' ) ";
public class GetAuthorByNamePatternRow
{
Expand Down
72 changes: 72 additions & 0 deletions examples/SqliteDapperExample/request.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file modified examples/SqliteDapperExample/request.message
Binary file not shown.
34 changes: 34 additions & 0 deletions examples/SqliteDapperLegacyExample/QuerySql.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,40 @@ public async Task<GetAuthorByIdRow> GetAuthorById(GetAuthorByIdArgs args)
return await this.Transaction.Connection.QueryFirstOrDefaultAsync<GetAuthorByIdRow>(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<GetAuthorByIdWithMultipleNamedParamRow> GetAuthorByIdWithMultipleNamedParam(GetAuthorByIdWithMultipleNamedParamArgs args)
{
var queryParams = new Dictionary<string, object>();
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<GetAuthorByIdWithMultipleNamedParamRow>(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<GetAuthorByIdWithMultipleNamedParamRow>(GetAuthorByIdWithMultipleNamedParamSql, queryParams, transaction: this.Transaction);
}

private const string GetAuthorByNamePatternSql = "SELECT id, name, bio FROM authors WHERE name LIKE COALESCE ( @name_pattern , '%' ) ";
public class GetAuthorByNamePatternRow
{
Expand Down
Loading
Loading