Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
028b07f
quote table, column and alias names with SqlSyntaxProvider methods in…
idseefeld Jan 29, 2026
e406c32
refactoring private methods into new file as internal methods,
idseefeld Jan 29, 2026
8344bfb
refactor GetAlias method
idseefeld Jan 29, 2026
6511dea
Double check the change
idseefeld Jan 29, 2026
12072a1
improve code health
idseefeld Jan 30, 2026
95388db
change new static classes into public static partial class NPocoSqlEx…
idseefeld Jan 30, 2026
c090be3
Merge branch 'main' into v173/21451-quote-raw-sql-names-with-SqlSynta…
idseefeld Jan 30, 2026
4d610a6
Merge branch 'main' into v173/21451-quote-raw-sql-names-with-SqlSynta…
idseefeld Jan 30, 2026
b6dfd5b
Merge branch 'v173/21451-quote-raw-sql-names-with-SqlSyntaxProvider-m…
idseefeld Jan 30, 2026
a88479f
resolve some Copilot review suggestions
idseefeld Jan 30, 2026
f140a57
revert Copilot suggestion because it decreases code health
idseefeld Jan 30, 2026
c9deac7
revert test
idseefeld Jan 30, 2026
72c0ca7
revert refactoring for CodeScene
idseefeld Feb 2, 2026
100b472
delete obsolete Test
idseefeld Feb 2, 2026
a6608c8
remove new methods and updates, which are not relevat for this PR
idseefeld Feb 2, 2026
e549394
prepare for additional states in the future
idseefeld Feb 2, 2026
968a7a6
don't mix string building methods
idseefeld Feb 2, 2026
3eddfbd
fix SQL injection danger
idseefeld Feb 2, 2026
e44abf8
Merge branch 'main' into v173/21451-quote-raw-sql-names-with-SqlSynta…
idseefeld Feb 2, 2026
a4addf9
fix test for reverted methods
idseefeld Feb 2, 2026
6ece7b9
Merge branch 'main' into v173/21451-quote-raw-sql-names-with-SqlSynta…
idseefeld Feb 2, 2026
6ba7569
another SqlSyntax issue
idseefeld Feb 2, 2026
3a264f5
Add additional unit and integration tests verifying the refactorings …
AndyButland Feb 3, 2026
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
16 changes: 14 additions & 2 deletions src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,22 @@ public static Sql<ISqlContext>.SqlJoinClause<ISqlContext> InnerJoinNested(this S
.Append($") {sql.SqlContext.SqlSyntax.GetQuotedName(alias)}"));
}

/// <summary>
/// Adds a WHERE clause to the SQL query that filters results based on a LIKE comparison for the specified
/// field.
/// </summary>
/// <remarks>Use this method to perform pattern matching queries, such as searching for records
/// where a field contains, starts with, or ends with a specified substring. The method does not automatically
/// add wildcard characters; include them in the likeValue parameter as needed.</remarks>
/// <typeparam name="TDto">The type of the data transfer object representing the table or entity being queried.</typeparam>
/// <param name="sql">The SQL builder instance to which the WHERE clause will be appended.</param>
/// <param name="fieldSelector">An expression that selects the field of the entity to apply the LIKE filter to.</param>
/// <param name="likeValue">The value to use in the LIKE comparison. This can include SQL wildcard characters such as '%' or '_'.</param>
/// <returns>The updated SQL builder instance with the appended WHERE LIKE clause.</returns>
public static Sql<ISqlContext> WhereLike<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, object?>> fieldSelector, string likeValue)
{
var fieldName = sql.SqlContext.SqlSyntax.GetFieldName(fieldSelector);
sql.Where($"{fieldName} LIKE ('{likeValue}')");
sql.Where($"{fieldName} LIKE @0", likeValue);
return sql;
}

Expand Down Expand Up @@ -1505,7 +1517,7 @@ internal static string GetAliasedField(this Sql<ISqlContext> sql, string field)
public static Sql<ISqlContext> AppendSubQuery(this Sql<ISqlContext> sql, Sql<ISqlContext> subQuery, string alias)
{
// Append the subquery as a derived table with an alias
sql.Append("(").Append(subQuery.SQL, subQuery.Arguments).Append($") AS {alias}");
sql.Append("(").Append(subQuery.SQL, subQuery.Arguments).Append($") AS {sql.SqlContext.SqlSyntax.GetQuotedName(alias)}");

return sql;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public AuditRepository(
public IEnumerable<IAuditItem> Get(AuditType type, IQuery<IAuditItem> query)
{
Sql<ISqlContext>? sqlClause = GetBaseQuery(false)
.Where("(logHeader=@0)", type.ToString());
.Where($"({SqlSyntax.GetQuotedColumnName("logHeader")}=@0)", type.ToString());

var translator = new SqlTranslator<IAuditItem>(sqlClause, query);
Sql<ISqlContext> sql = translator.Translate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,10 +545,10 @@ private string ApplyCustomOrdering(ref Sql<ISqlContext> sql, Ordering ordering)
{
// sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through value
// from 'current' content version for the given order by field
var sortedInt = string.Format(SqlContext.SqlSyntax.ConvertIntegerToOrderableString, "intValue");
var sortedDecimal = string.Format(SqlContext.SqlSyntax.ConvertDecimalToOrderableString, "decimalValue");
var sortedDate = string.Format(SqlContext.SqlSyntax.ConvertDateToOrderableString, "dateValue");
var sortedString = "COALESCE(varcharValue,'')"; // assuming COALESCE is ok for all syntaxes
var sortedInt = string.Format(SqlContext.SqlSyntax.ConvertIntegerToOrderableString, QuoteColumnName("intValue"));
var sortedDecimal = string.Format(SqlContext.SqlSyntax.ConvertDecimalToOrderableString, QuoteColumnName("decimalValue"));
var sortedDate = string.Format(SqlContext.SqlSyntax.ConvertDateToOrderableString, QuoteColumnName("dateValue"));
var sortedString = $"COALESCE({QuoteColumnName("varcharValue")},'')"; // assuming COALESCE is ok for all syntaxes

// needs to be an outer join since there's no guarantee that any of the nodes have values for this property
Sql<ISqlContext> innerSql = Sql().Select($@"CASE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,10 @@ protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
protected override IEnumerable<string> GetDeleteClauses()
{
var l = (List<string>)base.GetDeleteClauses(); // we know it's a list
l.Add($"DELETE FROM {QuoteTableName(ContentVersionCleanupPolicyDto.TableName)} WHERE {QuoteColumnName("contentTypeId")} = @id");
l.Add($"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.DocumentType)} WHERE {QuoteColumnName("contentTypeNodeId")} = @id");
l.Add($"DELETE FROM {QuoteTableName(ContentTypeDto.TableName)} WHERE {QuoteColumnName("nodeId")} = @id");
l.Add($"DELETE FROM {QuoteTableName(NodeDto.TableName)} WHERE id = @id");
l.Add($"DELETE FROM {QuoteTableName(ContentVersionCleanupPolicyDto.TableName)} WHERE {QuoteColumnName(ContentVersionCleanupPolicyDto.PrimaryKeyName)} = @id");
l.Add($"DELETE FROM {QuoteTableName(ContentTypeTemplateDto.TableName)} WHERE {QuoteColumnName(ContentTypeTemplateDto.PrimaryKeyName)} = @id");
l.Add($"DELETE FROM {QuoteTableName(ContentTypeDto.TableName)} WHERE {QuoteColumnName(ContentTypeDto.NodeIdColumnName)} = @id");
l.Add($"DELETE FROM {QuoteTableName(NodeDto.TableName)} WHERE {QuoteColumnName(NodeDto.PrimaryKeyColumnName)} = @id");
return l;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1078,10 +1078,10 @@ private void CopyTagData(
// select tags to insert: tags pointed to by a relation ship, for proper property/content types,
// and of source language, and where we cannot left join to an existing tag with same text,
// group and languageId
var targetLanguageIdS = targetLanguageId.HasValue ? targetLanguageId.ToString() : "NULL";
var targetLanguageIdS = targetLanguageId.HasValue ? targetLanguageId.ToString() : "NULL" + SqlSyntax.GetNullCastSuffix<int?>();
Sql<ISqlContext> sqlSelectTagsToInsert1 = Sql()
.SelectDistinct<TagDto>(x => x.Text, x => x.Group)
.Append(", " + targetLanguageIdS)
.Append($", {targetLanguageIdS} ")
.From<TagDto>();

sqlSelectTagsToInsert1
Expand Down Expand Up @@ -1234,7 +1234,7 @@ private void CopyPropertyData(
Database.Execute(sqlDelete);

// now insert all property data into the target language that exists under the source language
var targetLanguageIdS = targetLanguageId.HasValue ? targetLanguageId.ToString() : "NULL";
var targetLanguageIdS = targetLanguageId.HasValue ? targetLanguageId.ToString() : "NULL" + SqlSyntax.GetNullCastSuffix<int?>();
var cols = Sql().ColumnsForInsert<PropertyDataDto>(
x => x.VersionId,
x => x.PropertyTypeId,
Expand Down Expand Up @@ -1658,7 +1658,7 @@ public bool HasContainerInPath(params int[] ids)
public bool HasContentNodes(int id)
{
var sql = new Sql(
$"SELECT CASE WHEN EXISTS (SELECT * FROM {QuoteTableName(ContentDto.TableName)} WHERE {QuoteColumnName("contentTypeId")} = @id) THEN 1 ELSE 0 END",
$"SELECT CASE WHEN EXISTS (SELECT * FROM {QuoteTableName(ContentDto.TableName)} WHERE {QuoteColumnName(ContentDto.ContentTypeIdColumnName)} = @id) THEN 1 ELSE 0 END",
new { id });
return Database.ExecuteScalar<int>(sql) == 1;
}
Expand All @@ -1670,18 +1670,18 @@ protected override IEnumerable<string> GetDeleteClauses()
// is included here just to be 100% sure since it has a FK on cmsPropertyType.
var list = new List<string>
{
$"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.User2NodeNotify)} WHERE {QuoteColumnName("nodeId")} = @id",
$@"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.UserGroup2GranularPermission)} WHERE {QuoteColumnName("uniqueId")} IN
(SELECT {QuoteColumnName("uniqueId")} FROM {QuoteTableName(NodeDto.TableName)} WHERE id = @id)",
$"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.TagRelationship)} WHERE {QuoteColumnName("nodeId")} = @id",
$"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.ContentChildType)} WHERE {QuoteColumnName("Id")} = @id",
$"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.ContentChildType)} WHERE {QuoteColumnName("AllowedId")} = @id",
$"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.ContentTypeTree)} WHERE {QuoteColumnName("parentContentTypeId")} = @id",
$"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.ContentTypeTree)} WHERE {QuoteColumnName("childContentTypeId")} = @id",
$@"DELETE FROM {QuoteTableName(PropertyDataDto.TableName)} WHERE {QuoteColumnName("propertyTypeId")} IN
(SELECT id FROM {QuoteTableName(Constants.DatabaseSchema.Tables.PropertyType)} WHERE {QuoteColumnName("contentTypeId")} = @id)",
$"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.PropertyType)} WHERE {QuoteColumnName("contentTypeId")} = @id",
$"DELETE FROM {QuoteTableName(Constants.DatabaseSchema.Tables.PropertyTypeGroup)} WHERE {QuoteColumnName("contenttypeNodeId")} = @id",
$"DELETE FROM {QuoteTableName(User2NodeNotifyDto.TableName)} WHERE {QuoteColumnName(User2NodeNotifyDto.NodeIdColumnName)} = @id",
$@"DELETE FROM {QuoteTableName(UserGroup2GranularPermissionDto.TableName)} WHERE {QuoteColumnName(UserGroup2GranularPermissionDto.UniqueIdColumnName)} IN
(SELECT {QuoteColumnName("uniqueId")} FROM {QuoteTableName(NodeDto.TableName)} WHERE {QuoteColumnName(NodeDto.PrimaryKeyColumnName)} = @id)",
$"DELETE FROM {QuoteTableName(TagRelationshipDto.TableName)} WHERE {QuoteColumnName(TagRelationshipDto.PrimaryKeyColumnName)} = @id",
$"DELETE FROM {QuoteTableName(ContentTypeAllowedContentTypeDto.TableName)} WHERE {QuoteColumnName(ContentTypeAllowedContentTypeDto.PrimaryKeyColumnName)} = @id",
$"DELETE FROM {QuoteTableName(ContentTypeAllowedContentTypeDto.TableName)} WHERE {QuoteColumnName(ContentTypeAllowedContentTypeDto.AllowedIdColumnName)} = @id",
$"DELETE FROM {QuoteTableName(ContentType2ContentTypeDto.TableName)} WHERE {QuoteColumnName(ContentType2ContentTypeDto.PrimaryKeyColumnName)} = @id",
$"DELETE FROM {QuoteTableName(ContentType2ContentTypeDto.TableName)} WHERE {QuoteColumnName(ContentType2ContentTypeDto.ChildIdColumnName)} = @id",
$@"DELETE FROM {QuoteTableName(PropertyDataDto.TableName)} WHERE {QuoteColumnName(PropertyDataDto.PropertyTypeIdColumnName)} IN
(SELECT id FROM {QuoteTableName(PropertyTypeDto.TableName)} WHERE {QuoteColumnName(PropertyTypeDto.ContentTypeIdColumnName)} = @id)",
$"DELETE FROM {QuoteTableName(PropertyTypeDto.TableName)} WHERE {QuoteColumnName(PropertyTypeDto.ContentTypeIdColumnName)} = @id",
$"DELETE FROM {QuoteTableName(PropertyTypeGroupDto.TableName)} WHERE {QuoteColumnName(PropertyTypeGroupDto.ContentTypeNodeIdColumnName)} = @id",
};
return list;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,15 @@ private Sql<ISqlContext> SiblingsSql(
// the final query for before and after positions will increase. So we need to calculate the offset based on the provided values.
int beforeAfterParameterIndexOffset = GetBeforeAfterParameterOffset(objectTypes, filter);

// use all lower case alias names to avoid sql syntax issues
string targetAlias = "target";

// Find the specific row number of the target node.
// We need this to determine the bounds of the row numbers to select.
Sql<ISqlContext> targetRowSql = Sql()
.Select("rn")
.From().AppendSubQuery(rowNumberSql, "Target")
.Where<NodeDto>(x => x.UniqueId == targetKey, "Target");
.From().AppendSubQuery(rowNumberSql, targetAlias)
.Where<NodeDto>(x => x.UniqueId == targetKey, targetAlias);

// We have to reuse the target row sql arguments, however, we also need to add the "before" and "after" values to the arguments.
// If we try to do this directly in the params array it'll consider the initial argument array as a single argument.
Expand All @@ -316,8 +319,8 @@ private Sql<ISqlContext> SiblingsSql(
totalAfter = GetNumberOfSiblingsOutsideSiblingRange(rowNumberSql, targetRowSql, beforeAfterParameterIndex, afterArgumentsArray, false);

return Sql()
.Select("UniqueId")
.From().AppendSubQuery(rowNumberSql, "NumberedNodes")
.Select(QuoteColumnName("uniqueId"))
.From().AppendSubQuery(rowNumberSql, "nn")
.Where($"rn >= ({targetRowSql.SQL}) - @{beforeAfterParameterIndex}", beforeArgumentsArray)
.Where($"rn <= ({targetRowSql.SQL}) + @{beforeAfterParameterIndex}", afterArgumentsArray)
.OrderBy("rn");
Expand Down Expand Up @@ -355,9 +358,9 @@ private long GetNumberOfSiblingsOutsideSiblingRange(
{
Sql<ISqlContext>? sql = Sql()
.SelectCount()
.From().AppendSubQuery(rowNumberSql, "NumberedNodes")
.From().AppendSubQuery(rowNumberSql, "nn")
.Where($"rn {(getBefore ? "<" : ">")} ({targetRowSql.SQL}) {(getBefore ? "-" : "+")} @{parameterIndex}", arguments);
return Database.ExecuteScalar<long>(sql);
return Database.FirstOrDefault<long>(sql);
}


Expand Down Expand Up @@ -498,14 +501,14 @@ public UmbracoObjectTypes GetObjectType(int id)
{
Sql<ISqlContext> sql = Sql().Select<NodeDto>(x => x.NodeObjectType).From<NodeDto>()
.Where<NodeDto>(x => x.NodeId == id);
return ObjectTypes.GetUmbracoObjectType(Database.ExecuteScalar<Guid>(sql));
return ObjectTypes.GetUmbracoObjectType(Database.First<Guid>(sql));
}

public UmbracoObjectTypes GetObjectType(Guid key)
{
Sql<ISqlContext> sql = Sql().Select<NodeDto>(x => x.NodeObjectType).From<NodeDto>()
.Where<NodeDto>(x => x.UniqueId == key);
return ObjectTypes.GetUmbracoObjectType(Database.ExecuteScalar<Guid>(sql));
return ObjectTypes.GetUmbracoObjectType(Database.First<Guid>(sql));
}

public int ReserveId(Guid key)
Expand Down Expand Up @@ -972,11 +975,13 @@ private void ApplyOrdering(ref Sql<ISqlContext> sql, Ordering ordering)
orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path");
break;
case "NODEID":
orderBy = runner.OrderBy;
orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "id");
orderingIncludesNodeId = true;
break;
default:
orderBy = QuoteColumnName(runner.OrderBy) ?? string.Empty;
orderBy = runner.OrderBy != null
? SqlSyntax.GetQuotedColumn(NodeDto.TableName, runner.OrderBy)
: string.Empty;
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ private ILanguage GetDefault()

#region Overrides of RepositoryBase<int,Language>

protected override ILanguage? PerformGet(int id) => PerformGetAll(id).FirstOrDefault();
protected override ILanguage? PerformGet(int id) => PerformGetAll([id]).FirstOrDefault();

protected override IEnumerable<ILanguage> PerformGetAll(params int[]? ids)
{
Expand Down Expand Up @@ -260,15 +260,15 @@ protected override IEnumerable<string> GetDeleteClauses()
{
// NOTE: There is no constraint between the Language and cmsDictionary/cmsLanguageText tables (?)
// but we still need to remove them
$"DELETE FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.DictionaryValue)} {lIdWhere}",
$"DELETE FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.PropertyData)} {lIdWhere}",
$"DELETE FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation)} {lIdWhere}",
$"DELETE FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.DocumentCultureVariation)} {lIdWhere}",
$"DELETE FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.TagRelationship)} WHERE {QuoteColumnName("tagId")} IN (SELECT id FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.Tag)} {lIdWhere})",
$"DELETE FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.Tag)} {lIdWhere}",
$"DELETE FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.DocumentUrl)} {lIdWhere}",
$"DELETE FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.DocumentUrlAlias)} {lIdWhere}",
$"DELETE FROM {SqlSyntax.GetQuotedName(Constants.DatabaseSchema.Tables.Language)} WHERE id = @id",
$"DELETE FROM {QuoteName(Constants.DatabaseSchema.Tables.DictionaryValue)} {lIdWhere}",
$"DELETE FROM {QuoteName(Constants.DatabaseSchema.Tables.PropertyData)} {lIdWhere}",
$"DELETE FROM {QuoteName(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation)} {lIdWhere}",
$"DELETE FROM {QuoteName(Constants.DatabaseSchema.Tables.DocumentCultureVariation)} {lIdWhere}",
$"DELETE FROM {QuoteName(Constants.DatabaseSchema.Tables.TagRelationship)} WHERE {QuoteColumnName("tagId")} IN (SELECT id FROM {QuoteName(Constants.DatabaseSchema.Tables.Tag)} {lIdWhere})",
$"DELETE FROM {QuoteName(Constants.DatabaseSchema.Tables.Tag)} {lIdWhere}",
$"DELETE FROM {QuoteName(Constants.DatabaseSchema.Tables.DocumentUrl)} {lIdWhere}",
$"DELETE FROM {QuoteName(Constants.DatabaseSchema.Tables.DocumentUrlAlias)} {lIdWhere}",
$"DELETE FROM {QuoteName(Constants.DatabaseSchema.Tables.Language)} WHERE id = @id",
};
return list;
}
Expand Down
Loading
Loading