Browse Source

smart collection names must be case insensitive (#2721)

* smart collection names must be case insensitive

* use explicit ci collation for mysql
pull/2723/head
Jason Dove 3 weeks ago committed by GitHub
parent
commit
6562d616fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs
  2. 2
      ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollectionHandler.cs
  3. 6886
      ErsatzTV.Infrastructure.MySql/Migrations/20251214002940_Fix_SmartCollectionNameSensitivity.Designer.cs
  4. 56
      ErsatzTV.Infrastructure.MySql/Migrations/20251214002940_Fix_SmartCollectionNameSensitivity.cs
  5. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  6. 6713
      ErsatzTV.Infrastructure.Sqlite/Migrations/20251214000517_Fix_SmartCollectionNameSensitivity.Designer.cs
  7. 53
      ErsatzTV.Infrastructure.Sqlite/Migrations/20251214000517_Fix_SmartCollectionNameSensitivity.cs
  8. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  9. 8
      ErsatzTV.Infrastructure/Data/TvContext.cs

2
ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs

@ -67,7 +67,7 @@ public class CreateSmartCollectionHandler : @@ -67,7 +67,7 @@ public class CreateSmartCollectionHandler :
.Bind(_ => createSmartCollection.NotLongerThan(50)(c => c.Name));
var result2 = Optional(createSmartCollection.Name)
.Where(name => !allNames.Contains(name))
.Where(name => !allNames.Contains(name, StringComparer.OrdinalIgnoreCase))
.ToValidation<BaseError>("SmartCollection name must be unique");
return (result1, result2).Apply((_, _) => createSmartCollection.Name);

2
ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollectionHandler.cs

@ -97,7 +97,7 @@ public class @@ -97,7 +97,7 @@ public class
.Bind(_ => updateCollection.NotLongerThan(50)(c => c.Name));
var result2 = Optional(updateCollection.Name)
.Where(name => !allNames.Contains(name))
.Where(name => !allNames.Contains(name, StringComparer.OrdinalIgnoreCase))
.ToValidation<BaseError>("SmartCollection name must be unique");
return (result1, result2).Apply((_, _) => updateCollection.Name);

6886
ErsatzTV.Infrastructure.MySql/Migrations/20251214002940_Fix_SmartCollectionNameSensitivity.Designer.cs generated

File diff suppressed because it is too large Load Diff

56
ErsatzTV.Infrastructure.MySql/Migrations/20251214002940_Fix_SmartCollectionNameSensitivity.cs

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Fix_SmartCollectionNameSensitivity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"
WITH Numbered AS (
SELECT
Id,
ROW_NUMBER() OVER (PARTITION BY LOWER(Name) ORDER BY Id) as RowNum
FROM SmartCollection
)
UPDATE SmartCollection sc
JOIN Numbered n ON sc.Id = n.Id
SET sc.Name = CONCAT(sc.Name, ' (', n.RowNum - 1, ')')
WHERE n.RowNum > 1;
");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "SmartCollection",
type: "varchar(255)",
nullable: true,
collation: "utf8mb4_general_ci",
oldClrType: typeof(string),
oldType: "varchar(255)",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "SmartCollection",
type: "varchar(255)",
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(255)",
oldNullable: true,
oldCollation: "utf8mb4_general_ci")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}

3
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -3235,7 +3235,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -3235,7 +3235,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.HasColumnType("varchar(255)");
.HasColumnType("varchar(255)")
.UseCollation("utf8mb4_general_ci");
b.Property<string>("Query")
.HasColumnType("longtext");

6713
ErsatzTV.Infrastructure.Sqlite/Migrations/20251214000517_Fix_SmartCollectionNameSensitivity.Designer.cs generated

File diff suppressed because it is too large Load Diff

53
ErsatzTV.Infrastructure.Sqlite/Migrations/20251214000517_Fix_SmartCollectionNameSensitivity.cs

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Fix_SmartCollectionNameSensitivity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
WITH Numbered AS (
SELECT
Id,
-- Partition by case-insensitive name
ROW_NUMBER() OVER (PARTITION BY Name COLLATE NOCASE ORDER BY Id) AS RowNum
FROM SmartCollection
)
UPDATE SmartCollection
SET Name = Name || ' (' || (Numbered.RowNum - 1) || ')'
FROM Numbered
WHERE SmartCollection.Id = Numbered.Id
AND Numbered.RowNum > 1;
");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "SmartCollection",
type: "TEXT",
nullable: true,
collation: "NOCASE",
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "SmartCollection",
type: "TEXT",
nullable: true,
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true,
oldCollation: "NOCASE");
}
}
}

3
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -3084,7 +3084,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -3084,7 +3084,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
.HasColumnType("TEXT")
.UseCollation("NOCASE");
b.Property<string>("Query")
.HasColumnType("TEXT");

8
ErsatzTV.Infrastructure/Data/TvContext.cs

@ -130,6 +130,14 @@ public class TvContext : DbContext @@ -130,6 +130,14 @@ public class TvContext : DbContext
if ((Database.ProviderName ?? string.Empty).Contains("MySql", StringComparison.InvariantCultureIgnoreCase))
{
modelBuilder.Entity<MediaFile>().Property(mf => mf.Path).HasColumnType("longtext");
modelBuilder.Entity<SmartCollection>().Property(mc => mc.Name).UseCollation("utf8mb4_general_ci");
}
// sqlite-specific configuration
if ((Database.ProviderName ?? string.Empty).Contains("Sqlite", StringComparison.InvariantCultureIgnoreCase))
{
modelBuilder.Entity<SmartCollection>().Property(sc => sc.Name).UseCollation("NOCASE");
}
modelBuilder.ApplyConfigurationsFromAssembly(typeof(TvContext).Assembly);

Loading…
Cancel
Save