Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions EFCORE_IMPROVEMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,22 +128,24 @@ Nineteen comparison tests across the permutation matrix:

## Known gaps (documented, deliberate)

- **Check constraints** are not modeled by Weasel and are invisible to its
delta detection; a dedicated test documents the behavior.
- **Computed columns** (`HasComputedColumnSql`) are not mapped.
- **Per-column descending sort** and provider index methods (gin/gist) need
the `customizeTables` escape hatch.
- **HiLo / TPC / `HasSequence` sequences** are not managed by Weasel (the
columns themselves map correctly as plain columns).
- **Per-column descending sort** needs the `customizeTables` escape hatch
(provider index methods like gin/gist now map automatically via
`ITableIndex.Method`).
- Npgsql `UseIdentityAlwaysColumn` is created as `GENERATED BY DEFAULT`
(more permissive; inserts behave identically through EF).
- Alternate keys are created as unique **indexes** rather than unique
**constraints** — functionally equivalent, including as FK targets, and
reported as a tolerated difference category by the harness.
- **Column-default drift is not detected** by delta detection (defaults are
applied at creation but `FetchExisting` does not read `column_default`).
Reading defaults back would risk spurious migrations for existing Marten
schemas, so this remains write-once by design for now.
- **Column-default drift detection is opt-in** (`ITable.DetectColumnDrift`) —
it stays off by default because canonicalizing datetime literals across
providers is not stable enough to be safe for existing Marten schemas.

Previously listed gaps now closed: check constraints (modeled with
conservative delta comparison), computed columns (`HasComputedColumnSql`
maps to `ITableColumn.ComputedExpression`, is read back by `FetchExisting`
on PostgreSQL and SQL Server, and participates in delta detection with
canonicalized expression comparison), and HiLo / `HasSequence` sequences
(mapped through `Migrator.CreateSequence`).

## Test infrastructure

Expand Down
32 changes: 27 additions & 5 deletions docs/postgresql/tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The fluent `ColumnExpression` returned by `AddColumn` supports:
- `DefaultValueByExpression(expr)` -- sets a raw SQL default expression
- `DefaultValueFromSequence(sequence)` -- uses `nextval()` from a sequence
- `ForeignKeyTo(table, column)` -- adds an inline foreign key
- `GeneratedAs(expression)` -- makes this a stored generated column

## Primary Keys

Expand Down Expand Up @@ -78,7 +79,7 @@ table.AddColumn<int>("company_id")
.ForeignKeyTo("companies", "id",
onDelete: CascadeAction.Cascade);
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L50-L56' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_foreign_keys' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L66-L72' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_foreign_keys' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Or add foreign keys directly to the `ForeignKeys` collection for multi-column keys.
Expand All @@ -99,7 +100,7 @@ var index = new IndexDefinition("idx_users_email")
index.Columns = new[] { "email" };
table.Indexes.Add(index);
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L61-L72' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_indexes' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L77-L88' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_indexes' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Indexes support GIN, GiST, BRIN, and hash methods via the `IndexMethod` enum. Expression-based indexes and sort order (`SortOrder`, `NullsSortOrder`) are also available.
Expand All @@ -117,7 +118,28 @@ table.AddColumn<string>("status").DefaultValueByString("pending");
table.AddColumn<DateTimeOffset>("created_at")
.DefaultValueByExpression("now()");
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L77-L85' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_default_values' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L93-L101' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_default_values' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Generated Columns

PostgreSQL 12+ supports stored generated columns (`GENERATED ALWAYS AS (...) STORED`). The generation expression is read back from the database catalog by `FetchExisting`, and participates in delta detection with canonicalized expression comparison — changing the expression migrates the column with a lossless drop and re-add (the data is derived). Generated columns the model does not declare are left untouched.

<!-- snippet: sample_pg_generated_columns -->
<a id='snippet-sample_pg_generated_columns'></a>
```cs
var table = new Table("people");

table.AddColumn<string>("first_name");
table.AddColumn<string>("last_name");

// GENERATED ALWAYS AS (...) STORED — PostgreSQL only supports
// stored generated columns. The expression is read back from the
// database catalog and participates in delta detection.
table.AddColumn("full_name", "text")
.GeneratedAs("first_name || ' ' || last_name");
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L50-L61' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_generated_columns' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Delta Detection and Migration
Expand All @@ -143,7 +165,7 @@ var existing = await table.FetchExistingAsync(conn);
var delta = new TableDelta(table, existing);
// delta.Difference tells you: None, Create, Update, or Recreate
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L90-L106' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_table_delta_detection' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L106-L122' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_table_delta_detection' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Generating DDL
Expand All @@ -158,5 +180,5 @@ var writer = new StringWriter();
table.WriteCreateStatement(migrator, writer);
Console.WriteLine(writer.ToString());
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L111-L118' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_table_generate_ddl' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/PostgresqlTableSamples.cs#L127-L134' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_pg_table_generate_ddl' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
10 changes: 5 additions & 5 deletions docs/sqlserver/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ RETURN @Price * @Rate;
END;
");
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L201-L210' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_from_sql' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L220-L229' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_from_sql' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Constructor-Based Creation
Expand All @@ -39,7 +39,7 @@ RETURN (SELECT COUNT(*) FROM dbo.users);
END;
");
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L215-L226' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_constructor' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L234-L245' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_constructor' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Custom Drop Statements
Expand All @@ -54,7 +54,7 @@ var fn = new Function(identifier, body, new[]
"DROP FUNCTION IF EXISTS dbo.GetUserCount;"
});
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L234-L239' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_custom_drop' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L253-L258' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_custom_drop' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Delta Detection
Expand All @@ -70,7 +70,7 @@ await conn.OpenAsync();
var delta = await fn.FindDeltaAsync(conn);
// delta.Difference: None, Create, or Update
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L248-L254' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_delta_detection' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L267-L273' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_delta_detection' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Marking for Removal
Expand All @@ -82,5 +82,5 @@ To generate a drop statement for a function that should be removed:
```cs
var removed = Function.ForRemoval("dbo.ObsoleteFunction");
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L259-L261' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_for_removal' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L278-L280' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_function_for_removal' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
8 changes: 4 additions & 4 deletions docs/sqlserver/procedures.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ WHERE Active = 1 AND Age >= @MinAge;
END;
");
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L120-L134' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_define_stored_procedure' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L139-L153' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_define_stored_procedure' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Generating DDL
Expand All @@ -43,7 +43,7 @@ proc.WriteCreateOrAlterStatement(migrator, writer);
// DROP PROCEDURE IF EXISTS
proc.WriteDropStatement(migrator, writer);
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L142-L154' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_procedure_ddl' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L161-L173' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_procedure_ddl' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Delta Detection
Expand All @@ -66,7 +66,7 @@ else if (delta.Difference == SchemaPatchDifference.Update)
// Procedure body has changed
}
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L163-L176' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_procedure_delta_detection' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L182-L195' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_procedure_delta_detection' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Fetching Existing Definitions
Expand All @@ -80,5 +80,5 @@ if (existing != null)
// existing contains the current procedure body from the database
}
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L188-L194' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_procedure_fetch_existing' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L207-L213' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_procedure_fetch_existing' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
10 changes: 5 additions & 5 deletions docs/sqlserver/sequences.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var seq2 = new Sequence(
startWith: 1000
);
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L268-L277' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_define_sequence' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L287-L296' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_define_sequence' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Sequence Ownership
Expand All @@ -29,7 +29,7 @@ A sequence can be associated with a table column:
seq.Owner = DbObjectName.Parse(SqlServerProvider.Instance, "dbo.orders");
seq.OwnerColumn = "id";
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L284-L287' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_sequence_ownership' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L303-L306' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_sequence_ownership' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Using Sequences with Table Columns
Expand All @@ -45,7 +45,7 @@ var seq = new Sequence("dbo.order_seq");
table.AddColumn<long>("id").AsPrimaryKey()
.DefaultValueFromSequence(seq);
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L292-L298' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_sequence_with_table' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L311-L317' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_sequence_with_table' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

This generates: `DEFAULT next value for dbo.order_seq`.
Expand All @@ -64,7 +64,7 @@ seq.WriteCreateStatement(migrator, writer);
seq.WriteDropStatement(migrator, writer);
// Output: DROP SEQUENCE IF EXISTS dbo.order_seq;
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L305-L314' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_sequence_ddl' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L324-L333' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_sequence_ddl' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Delta Detection
Expand All @@ -80,5 +80,5 @@ await conn.OpenAsync();
var delta = await seq.FindDeltaAsync(conn);
// delta.Difference: None or Create
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L322-L328' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_sequence_delta_detection' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L341-L347' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_sequence_delta_detection' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
8 changes: 4 additions & 4 deletions docs/sqlserver/table-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ tableType.AddColumn<int>("product_id").NotNull();
tableType.AddColumn<int>("quantity").NotNull();
tableType.AddColumn("unit_price", "decimal(10,2)");
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L335-L342' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_define_table_type' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L354-L361' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_define_table_type' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Column Configuration
Expand All @@ -32,7 +32,7 @@ You can add columns by .NET type or by explicit database type string:
tableType.AddColumn<string>("name"); // maps to varchar(100)
tableType.AddColumn("notes", "nvarchar(max)"); // explicit type
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L350-L353' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_table_type_columns' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L369-L372' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_table_type_columns' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Generating DDL
Expand All @@ -45,7 +45,7 @@ var writer = new StringWriter();
tableType.WriteCreateStatement(migrator, writer);
// Output: CREATE TYPE dbo.OrderItemType AS TABLE (product_id int NOT NULL, ...)
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L361-L366' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_table_type_ddl' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L380-L385' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_table_type_ddl' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Delta Detection
Expand All @@ -61,7 +61,7 @@ await conn.OpenAsync();
var delta = await tableType.FindDeltaAsync(conn);
// delta.Difference: None, Create, or Update
```
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L375-L381' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_table_type_delta_detection' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/weasel/blob/master/src/DocSamples/SqlServerSamples.cs#L394-L400' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ss_table_type_delta_detection' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

When an update is needed, the delta generates a DROP followed by CREATE since SQL Server does not support `ALTER TYPE`.
Expand Down
Loading
Loading