Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When Sql.Param is reused, reuse the same parameter name. #57

Merged
merged 1 commit into from
Oct 15, 2024
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
8 changes: 4 additions & 4 deletions src/Faithlife.Data/SqlFormatting/Sql.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ internal override string Render(SqlContext context)
if (m_filter is not null)
filteredProperties = filteredProperties.Where(x => m_filter(x.Name));

var text = string.Join(", ", filteredProperties.Select(x => context.RenderParam(x.GetValue(m_dto))));
var text = string.Join(", ", filteredProperties.Select(x => context.RenderParam(key: null, value: x.GetValue(m_dto))));
if (text.Length == 0)
throw new InvalidOperationException($"The specified type has no remaining columns: {type.FullName}");
return text;
Expand Down Expand Up @@ -401,7 +401,7 @@ private sealed class JoinSql : Sql
private sealed class LikePrefixParamSql : Sql
{
public LikePrefixParamSql(string prefix) => m_prefix = prefix;
internal override string Render(SqlContext context) => context.RenderParam(context.Syntax.EscapeLikeFragment(m_prefix) + "%");
internal override string Render(SqlContext context) => context.RenderParam(key: this, value: context.Syntax.EscapeLikeFragment(m_prefix) + "%");
private readonly string m_prefix;
}

Expand All @@ -415,7 +415,7 @@ private sealed class NameSql : Sql
private sealed class ParamSql : Sql
{
public ParamSql(object? value) => m_value = value;
internal override string Render(SqlContext context) => context.RenderParam(m_value);
internal override string Render(SqlContext context) => context.RenderParam(key: this, value: m_value);
private readonly object? m_value;
}

Expand All @@ -436,7 +436,7 @@ public string Format(string? format, object? arg, IFormatProvider? formatProvide
{
if (format is not null)
throw new FormatException($"Format specifier '{format}' is not supported.");
return arg is Sql sql ? sql.Render(m_context) : m_context.RenderParam(arg);
return arg is Sql sql ? sql.Render(m_context) : m_context.RenderParam(key: null, value: arg);
}

private readonly SqlContext m_context;
Expand Down
18 changes: 15 additions & 3 deletions src/Faithlife.Data/SqlFormatting/SqlContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,25 @@ public SqlContext(SqlSyntax syntax)

public DbParameters Parameters => m_parameters is null ? DbParameters.Empty : DbParameters.Create(m_parameters);

public string RenderParam(object? value)
public string RenderParam(object? key, object? value)
{
m_parameters ??= new List<(string Name, object? Value)>();
if (key is not null && m_renderedParams is not null && m_renderedParams.TryGetValue(key, out var rendered))
return rendered;

m_parameters ??= [];
var name = Invariant($"fdp{m_parameters.Count}");
m_parameters.Add((name, value));
return Syntax.ParameterPrefix + name;
rendered = Syntax.ParameterPrefix + name;

if (key is not null)
{
m_renderedParams ??= new();
m_renderedParams.Add(key, rendered);
}

return rendered;
}

private List<(string Name, object? Value)>? m_parameters;
private Dictionary<object, string>? m_renderedParams;
}
12 changes: 12 additions & 0 deletions tests/Faithlife.Data.Tests/SqlFormatting/SqlSyntaxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ public void FormatSql(int? id)
}
}

[Test]
public void SameParamTwice()
{
var id = 42;
var name = "xyzzy";
var desc = "long description";
var descParam = Sql.Param(desc);
var (text, parameters) = Render(Sql.Format($"insert into widgets (Id, Name, Desc) values ({id}, {name}, {descParam}) on duplicate key update Name = {name}, Desc = {descParam}"));
text.Should().Be("insert into widgets (Id, Name, Desc) values (@fdp0, @fdp1, @fdp2) on duplicate key update Name = @fdp3, Desc = @fdp2");
parameters.Should().Equal(("fdp0", id), ("fdp1", name), ("fdp2", desc), ("fdp3", name));
}

[Test]
public void FormatBadFormat()
{
Expand Down
Loading