Partition bound values are interpolated into single-quoted SQL literals without escaping. Partition suffixes are fine — ListPartition.SanitizeSuffix character-whitelists them to [a-z0-9_] — but the values are not, and they are emitted straight into DDL.
Sites
1. FormatSqlValue does not double an embedded ' — src/Weasel.Postgresql/Tables/Partitioning/PartitionExtensions.cs:52
return $"'{value.ToString()}'";
2. FormatSqlValue returns already-quoted strings completely verbatim — same file, :50
if (value is string v && v.StartsWith("'") && v.EndsWith("'")) return v;
A value that merely starts and ends with ' gets no quoting or escaping at all. If some caller genuinely needs to pass a pre-formatted literal, that should be an explicit, separately named path rather than a shape heuristic applied to arbitrary input.
3. Same pattern in ManagedListPartitions.Partitions() — src/Weasel.Postgresql/Tables/Partitioning/ManagedListPartitions.cs:53
yield return new ListPartition(path.Key, path.Select(x => $"'{x.Key}'").ToArray());
Note this also feeds branch 2, since these values now start and end with '.
Where the values reach DDL
ListPartition.cs:52-57 — CREATE TABLE … partition of … for values in ({Values.Join(", ")});
ManagedListPartitions.cs:485 — alter table … attach partition … for values in ({values.Join(", ")});
Reached from AddPartitionToAllTables → additivelyMigrateTablesForNewPartitions (:349) → resolveBuckets (:397) → createOrWidenPartitionAsync (:357), and from ListPartitioning.AddPartition<T> (ListPartitioning.cs:43). The drop path reaches it too, via DropPartitionFromAllTablesForValue → rebindPartitionAsync (:142, :485).
Consumers use tenant ids as partition values, so any tenant id containing an apostrophe currently produces invalid DDL.
Also worth tightening
PostgresqlMigrator.AssertValidIdentifier (src/Weasel.Postgresql/PostgresqlMigrator.cs:337-355) rejects only null/whitespace, an embedded space, and over-length. It permits " and ;. Given it is the only identifier check in the stack, it should reject those too.
Related: there is no identifier validation in DbObjectName (src/Weasel.Core/DbObjectName.cs:9-20) or PostgresqlObjectName (src/Weasel.Postgresql/PostgresqlObjectName.cs:13-55) — neither contains a single throw. PostgresqlObjectName.From(...) is sometimes assumed to be a sanitizing boundary and is not; worth a doc comment saying so.
Quoting helpers that quote without escaping — lower priority, mostly configuration-derived input, but the same class:
| File:line |
Code |
SchemaUtils.cs:79 |
return $"\"{name}\""; (and only quotes at all when the name is a keyword or has an uppercase char) |
PostgresqlProvider.cs:378-384 |
returns the name verbatim when it already begins and ends with " |
Tables/IndexDefinition.cs:348,357 |
$"\"{x}\"", COLLATE \"{Collation}\" |
Migrations/DatabaseSpecification.cs:49 |
CREATE DATABASE \"{databaseName}\" |
Functions/FunctionBody.cs:22 |
OWNER TO \"{owner}\"; |
Checked and clean
ManagedListPartitions' registry table DML binds every value as a real parameter (:78-81, :302-306, :499-503, :136-137, :194-197, :534-536, :221-224, :446-451). Only the table identifier is interpolated, from the constructor arg.
Tasks
Not established: whether the SQL Server / Oracle / MySql / Sqlite Migrator subclasses have the equivalent AssertValidIdentifier gap (SqlServerMigrator.cs:140, OracleMigrator.cs:119, MySqlMigrator.cs:110, SqliteMigrator.cs:87).
Partition bound values are interpolated into single-quoted SQL literals without escaping. Partition suffixes are fine —
ListPartition.SanitizeSuffixcharacter-whitelists them to[a-z0-9_]— but the values are not, and they are emitted straight into DDL.Sites
1.
FormatSqlValuedoes not double an embedded'—src/Weasel.Postgresql/Tables/Partitioning/PartitionExtensions.cs:522.
FormatSqlValuereturns already-quoted strings completely verbatim — same file,:50A value that merely starts and ends with
'gets no quoting or escaping at all. If some caller genuinely needs to pass a pre-formatted literal, that should be an explicit, separately named path rather than a shape heuristic applied to arbitrary input.3. Same pattern in
ManagedListPartitions.Partitions()—src/Weasel.Postgresql/Tables/Partitioning/ManagedListPartitions.cs:53Note this also feeds branch 2, since these values now start and end with
'.Where the values reach DDL
ListPartition.cs:52-57—CREATE TABLE … partition of … for values in ({Values.Join(", ")});ManagedListPartitions.cs:485—alter table … attach partition … for values in ({values.Join(", ")});Reached from
AddPartitionToAllTables→additivelyMigrateTablesForNewPartitions(:349) →resolveBuckets(:397) →createOrWidenPartitionAsync(:357), and fromListPartitioning.AddPartition<T>(ListPartitioning.cs:43). The drop path reaches it too, viaDropPartitionFromAllTablesForValue→rebindPartitionAsync(:142,:485).Consumers use tenant ids as partition values, so any tenant id containing an apostrophe currently produces invalid DDL.
Also worth tightening
PostgresqlMigrator.AssertValidIdentifier(src/Weasel.Postgresql/PostgresqlMigrator.cs:337-355) rejects only null/whitespace, an embedded space, and over-length. It permits"and;. Given it is the only identifier check in the stack, it should reject those too.Related: there is no identifier validation in
DbObjectName(src/Weasel.Core/DbObjectName.cs:9-20) orPostgresqlObjectName(src/Weasel.Postgresql/PostgresqlObjectName.cs:13-55) — neither contains a singlethrow.PostgresqlObjectName.From(...)is sometimes assumed to be a sanitizing boundary and is not; worth a doc comment saying so.Quoting helpers that quote without escaping — lower priority, mostly configuration-derived input, but the same class:
SchemaUtils.cs:79return $"\"{name}\"";(and only quotes at all when the name is a keyword or has an uppercase char)PostgresqlProvider.cs:378-384"Tables/IndexDefinition.cs:348,357$"\"{x}\"",COLLATE \"{Collation}\"Migrations/DatabaseSpecification.cs:49CREATE DATABASE \"{databaseName}\"Functions/FunctionBody.cs:22OWNER TO \"{owner}\";Checked and clean
ManagedListPartitions' registry table DML binds every value as a real parameter (:78-81,:302-306,:499-503,:136-137,:194-197,:534-536,:221-224,:446-451). Only the table identifier is interpolated, from the constructor arg.Tasks
FormatSqlValuedoubles embedded':50ManagedListPartitions.Partitions()uses the escaping helperAssertValidIdentifierrejects"and;DbObjectName/PostgresqlObjectNamethat they do not validate'creates, attaches, detaches and drops correctlyNot established: whether the SQL Server / Oracle / MySql / Sqlite
Migratorsubclasses have the equivalentAssertValidIdentifiergap (SqlServerMigrator.cs:140,OracleMigrator.cs:119,MySqlMigrator.cs:110,SqliteMigrator.cs:87).