Skip to content

Commit 796270c

Browse files
jeremydmillerclaude
andcommitted
fix: replace ToLower/ToUpper with culture-invariant equivalents for SQL identifiers
In Turkish locale (tr-TR), String.ToLower() converts 'I' to dotless 'ı' (U+0131) instead of 'i', corrupting SQL identifiers (e.g. INFORMATION_SCHEMA → ınformation_schema). Replace all .ToLower()/.ToUpper() calls that operate on SQL identifiers, schema names, column names, type names, and keywords with .ToLowerInvariant()/.ToUpperInvariant(). Adds unit tests that set CultureInfo.CurrentCulture = tr-TR to verify correct normalization. Upstream fix for JasperFx/wolverine#2472 (companion PR to JasperFx/wolverine#2480). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a9bf41e commit 796270c

11 files changed

Lines changed: 54 additions & 14 deletions

File tree

src/Weasel.Core/Migrator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ public async Task ReadTemplatesAsync(string directory, CancellationToken ct = de
7878
{
7979
foreach (var file in FileSystem.FindFiles(directory, FileSet.Shallow("*.function")))
8080
{
81-
var name = Path.GetFileNameWithoutExtension(file).ToLower();
81+
var name = Path.GetFileNameWithoutExtension(file).ToLowerInvariant();
8282
Templates[name].FunctionCreation = await File.ReadAllTextAsync(file, ct).ConfigureAwait(false);
8383
}
8484

8585
foreach (var file in FileSystem.FindFiles(directory, FileSet.Shallow("*.table")))
8686
{
87-
var name = Path.GetFileNameWithoutExtension(file).ToLower();
87+
var name = Path.GetFileNameWithoutExtension(file).ToLowerInvariant();
8888

8989
Templates[name].TableCreation = await File.ReadAllTextAsync(file, ct).ConfigureAwait(false);
9090
}

src/Weasel.Postgresql.Tests/Tables/TableColumnTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Globalization;
12
using NetTopologySuite.Geometries;
23
using Shouldly;
34
using Weasel.Postgresql.Tables;
@@ -143,4 +144,23 @@ public void to_function_update_should_quote_reserved_keywords(string keyword)
143144

144145
column.ToFunctionUpdate().ShouldBe($"\"{keyword}\" = p_{keyword}");
145146
}
147+
148+
[Fact]
149+
public void column_name_normalization_is_culture_invariant()
150+
{
151+
// In Turkish locale, "I".ToLower() produces dotless 'ı' (U+0131) instead of 'i',
152+
// which would corrupt SQL identifiers like "INFORMATION_SCHEMA" → "ınformation_schema".
153+
var originalCulture = CultureInfo.CurrentCulture;
154+
try
155+
{
156+
CultureInfo.CurrentCulture = new CultureInfo("tr-TR");
157+
var column = new TableColumn("INFORMATION_SCHEMA", "VARCHAR");
158+
column.Name.ShouldBe("information_schema");
159+
column.Type.ShouldBe("varchar");
160+
}
161+
finally
162+
{
163+
CultureInfo.CurrentCulture = originalCulture;
164+
}
165+
}
146166
}

src/Weasel.Postgresql.Tests/Tables/detecting_table_deltas.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ public async Task using_reserved_keywords_for_columns()
6767
[MemberData(nameof(PostgresReservedKeywords))]
6868
public async Task verify_all_postgres_reserved_keywords_work_as_column_names(string keyword)
6969
{
70-
var tableName = $"deltas.keyword_{keyword.ToLower()}";
70+
var tableName = $"deltas.keyword_{keyword.ToLowerInvariant()}";
7171
var table = new Table(tableName);
7272
table.AddColumn<int>("id").AsPrimaryKey();
7373

7474
await CreateSchemaObjectInDatabase(table);
7575

76-
table.AddColumn<string>(keyword.ToLower());
76+
table.AddColumn<string>(keyword.ToLowerInvariant());
7777

7878
await AssertNoDeltasAfterPatching(table);
7979
}

src/Weasel.Postgresql/Extension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class Extension: ISchemaObject
1111
{
1212
public Extension(string extensionName)
1313
{
14-
ExtensionName = extensionName.Trim().ToLower();
14+
ExtensionName = extensionName.Trim().ToLowerInvariant();
1515
}
1616

1717
public string ExtensionName { get; }

src/Weasel.Postgresql/PostgresqlProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ protected override Type[] determineClrTypesForParameterType(NpgsqlDbType dbType)
9898

9999
public string ConvertSynonyms(string type)
100100
{
101-
switch (type.ToLower())
101+
switch (type.ToLowerInvariant())
102102
{
103103
case "character varying":
104104
case "varchar":

src/Weasel.Postgresql/Tables/IndexDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ public static IndexDefinition Parse(string definition)
390390
while (tokens.Any())
391391
{
392392
var current = tokens.Dequeue();
393-
switch (current.ToUpper())
393+
switch (current.ToUpperInvariant())
394394
{
395395
case "CREATE":
396396
case "CONCURRENTLY":

src/Weasel.Postgresql/Tables/TableColumn.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public TableColumn(string name, string type)
1919
throw new ArgumentOutOfRangeException(nameof(type));
2020
}
2121

22-
Name = name.ToLower().Trim().Replace(' ', '_');
23-
Type = type.ToLower();
22+
Name = name.ToLowerInvariant().Trim().Replace(' ', '_');
23+
Type = type.ToLowerInvariant();
2424
}
2525

2626

src/Weasel.SqlServer.Tests/Tables/TableColumnTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Globalization;
12
using Shouldly;
23
using Weasel.SqlServer.Tables;
34
using Xunit;
@@ -134,4 +135,23 @@ public void change_column_type_sql()
134135
table.ColumnFor(columnName)!.AlterColumnTypeSql(table, actualColumn)
135136
.ShouldBe($"alter table dbo.people alter column [{columnName}] varchar(200) NOT NULL;");
136137
}
138+
139+
[Fact]
140+
public void column_name_normalization_is_culture_invariant()
141+
{
142+
// In Turkish locale, "I".ToLower() produces dotless 'ı' (U+0131) instead of 'i',
143+
// which would corrupt SQL identifiers like "INFORMATION_SCHEMA" → "ınformation_schema".
144+
var originalCulture = CultureInfo.CurrentCulture;
145+
try
146+
{
147+
CultureInfo.CurrentCulture = new CultureInfo("tr-TR");
148+
var column = new TableColumn("INFORMATION_SCHEMA", "VARCHAR");
149+
column.Name.ShouldBe("information_schema");
150+
column.Type.ShouldBe("varchar");
151+
}
152+
finally
153+
{
154+
CultureInfo.CurrentCulture = originalCulture;
155+
}
156+
}
137157
}

src/Weasel.SqlServer/SqlServerProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public override string AddApplicationNameToConnectionString(string connectionStr
8787

8888
public string ConvertSynonyms(string type)
8989
{
90-
switch (type.ToLower())
90+
switch (type.ToLowerInvariant())
9191
{
9292
case "text":
9393
case "varchar":
@@ -189,7 +189,7 @@ public override void SetParameterType(SqlParameter parameter, SqlDbType dbType)
189189

190190
public static CascadeAction ReadAction(string description)
191191
{
192-
switch (description.ToUpper().Trim())
192+
switch (description.ToUpperInvariant().Trim())
193193
{
194194
case "CASCADE":
195195
return CascadeAction.Cascade;

src/Weasel.SqlServer/Tables/Table.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public void WriteCreateStatement(Migrator migrator, TextWriter writer)
128128
if (migrator.TableCreation == CreationStyle.DropThenCreate)
129129
{
130130
// drop all FK constraints
131-
var sqlVariableName = $"@sql_{Guid.NewGuid().ToString().ToLower().Replace("-", "_")}";
131+
var sqlVariableName = $"@sql_{Guid.NewGuid().ToString().ToLowerInvariant().Replace("-", "_")}";
132132
writer.WriteLine("DECLARE {0} NVARCHAR(MAX) = '';", sqlVariableName);
133133
writer.WriteLine("SELECT {0} = {1} + 'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(fk.parent_object_id)) + '.' + QUOTENAME(OBJECT_NAME(fk.parent_object_id)) + ' DROP CONSTRAINT ' + QUOTENAME(fk.name) + ';'",
134134
sqlVariableName, sqlVariableName);

0 commit comments

Comments
 (0)