Skip to content

Escape single quotes when building LIST partition bound literals #416

Description

@jeremydmiller

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-57CREATE TABLE … partition of … for values in ({Values.Join(", ")});
  • ManagedListPartitions.cs:485alter table … attach partition … for values in ({values.Join(", ")});

Reached from AddPartitionToAllTablesadditivelyMigrateTablesForNewPartitions (:349) → resolveBuckets (:397) → createOrWidenPartitionAsync (:357), and from ListPartitioning.AddPartition<T> (ListPartitioning.cs:43). The drop path reaches it too, via DropPartitionFromAllTablesForValuerebindPartitionAsync (: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

  • FormatSqlValue doubles embedded '
  • Remove or explicitly gate the pre-quoted passthrough branch at :50
  • ManagedListPartitions.Partitions() uses the escaping helper
  • AssertValidIdentifier rejects " and ;
  • Note on DbObjectName/PostgresqlObjectName that they do not validate
  • Round-trip tests: a partition value containing ' creates, attaches, detaches and drops correctly

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions