Skip to content

v6.7.0

Choose a tag to compare

@github-actions github-actions released this 26 Jul 15:02
· 5 commits to master since this release

The tooling reaches all six stores

eqdata migrations add worked on the relational stores only, for one reason: they were the only ones whose model could describe itself. Cassandra, MongoDB and Cosmos DB now describe themselves too — each in the way it honestly can — and the two things a document store needs beyond that are here as well.

Every store can now be compared against its own history

Store Described from
Cassandra its configuration: table, columns, CQL types, and the partition and clustering keys that together identify a row
MongoDB the driver's own class maps — element renames and exclusions included
Cosmos DB the containers and partition key paths from the model, the properties from the type, named as the serializer names them

MongoDB reads class maps rather than reflecting over the type on purpose. A collection has no schema, so the only truthful answer to "what shape is this" is the mapping the driver will actually use when it writes. Reflecting over the type would describe a shape nobody writes.

AddField stops being a no-op on document stores

This was the gap worth closing. A collection needs no declaration to accept a new field — documents gain one on write. The documents already there are the problem. Absent the field, deserialization hands your application default(T): a 0, an Unspecified date, the first value of an enum. None of those is distinguishable from a value somebody meant.

public sealed class Ledger
{
    [DefaultValue("web")] public string Channel { get; set; } = "";
}

migration.For<Ledger>(ledger => ledger.AddField(x => x.Channel));
// every document without `channel` now holds "web" — the ones that had a value keep it

Declare nothing and it stays a no-op, deliberately: an absent field is at least visible, and a value nobody chose is not. Cosmos has no set-based update, so this costs one read and one patch per document that lacks the field — which is why the query filters on absence rather than rewriting everything.

Cassandra needs none of it. It has a real schema, so AddField is an ALTER TABLE ... ADD and a missing value reads as null.

[DefaultValue] and [PreviousName] now work everywhere

Both are read from the member itself, through one shared reader, rather than only from a relational column. A document store has nowhere else to put them — the class is the schema. The fluent .Default and .PreviousName stay relational, where the model has a column to hang them on, and still take precedence there.

Drift checking reaches Cassandra and Cosmos DB

Store What is compared
PostgreSQL · MySQL · MariaDB · SQL Server every mapped table and column, each type and nullability
Cassandra the same, through system_schema, plus the partition key
Cosmos DB the containers and the partition key paths they were created with
MongoDB nothing — and it says so

The partition key is the finding worth having. It is fixed when a table or container is created, so a different one cannot be migrated at all — only rebuilt alongside and copied into. Both stores now report it, and the report says that outright instead of implying a fix exists.

MongoDB stays unanswerable and explains why: a collection has no shape beyond the documents in it, and sampling those would describe the documents that came back rather than the collection.

One bug fixed on the way

Cassandra folds every unquoted identifier to lower case, and the provider never quotes one — so a model saying OpenedAt describes a column called openedat. Comparing the two spellings reported every column of every correct table. A drift check that cries wolf on a healthy keyspace is worse than none, so this was the difference between shipping the feature and not.


Verified against real stores: MongoDB 139, Cassandra 103, Cosmos DB 51, PostgreSQL 83, MySQL 19, core 83, analyzers 13. SQL Server's 14 skip locally — its image is amd64 only — and run in CI.

Docs: Generating and drift checking