Skip to content

Commit 90e271e

Browse files
committed
System-versioned temporal tables end-to-end: CREATE TABLE … PERIOD FOR SYSTEM_TIME (start, end) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = …)) auto-creates a sibling HeapTable mirroring the parent's column shape (strips IDENTITY / GENERATED ALWAYS markers / inline constraints / DEFAULTs, keeps hidden + persisted-computed); DDL validation raises probe-confirmed Msg 13501 / 13504 / 13505 / 13506 / 13507 / 13509 / 13587 for asymmetric / type / nullability / orphan-GENERATED-column cases. New HeapColumn.GeneratedAs (None / Start / End) + IsHidden flags, HeapTable.PeriodColumns + SystemVersioning (parent → history pointer) + IsHistoryTable fields. INSERT auto-populates ROW START = UtcNow, ROW END = DateTime.MaxValue; explicit period values raise Msg 13536; direct INSERT into history raises Msg 13559; implicit column lists exclude GENERATED columns. UPDATE captures pre-update row, bumps new ROW START, writes history with adjusted ROW END = UtcNow; SET on GENERATED raises Msg 13537. DELETE writes history with ROW END = UtcNow before tombstoning. SELECT * excludes hidden columns; explicit references continue to bind (EF Core 10's OUTPUT INSERTED.[PeriodEnd] / explicit-list tracked SELECTs work). FOR SYSTEM_TIME ALL unions current + history; FOR SYSTEM_TIME AS OF <expr> filters by start <= t < end (expression evaluated once per query); BETWEEN / FROM-TO / CONTAINED IN raise NotSupportedException. ISO 8601 'Z'-suffixed datetime2 strings now parse (EF emits AS OF '<iso-Z>' inline). DROP TABLE on parent or history raises Msg 13552. 16 new TemporalTableTests cover DDL, INSERT auto-fill, hidden SELECT *, all error paths, UPDATE / DELETE history copies, FOR SYSTEM_TIME ALL / AS OF, and DROP rejection. The 4 previously-Ignored EFCoreTemporalTables tests un-disabled — EF Core 10's IsTemporal() end-to-end with .TemporalAll() / .TemporalAsOf(DateTime) all green. New ContextualKeywords: Always / Generated / Hidden / History_Table / Period / System_Time / System_Versioning. CLAUDE.md Feature reference entry + new docs/claude/temporal-tables.md document scope, EF emit shapes, and the deferred set (ALTER SET SYSTEM_VERSIONING = OFF, retention period, auto-named history, BETWEEN / FROM-TO / CONTAINED IN, LOB columns on temporal tables, sys.tables temporal_type column).
1 parent 781e80a commit 90e271e

16 files changed

Lines changed: 891 additions & 30 deletions

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
149149
- **Touching `CREATE TYPE … AS TABLE`, `DECLARE @t MyType`, TVP procedure parameters / `READONLY`, or the ADO.NET `DbParameter.TypeName` extension (DataTable / IDataReader as a TVP value source)**[`docs/claude/table-valued-parameters.md`](docs/claude/table-valued-parameters.md).
150150
- **Touching `CREATE SEQUENCE` / `ALTER SEQUENCE` / `DROP SEQUENCE`, `NEXT VALUE FOR`, the per-row dedup mechanism (`BatchContext.CurrentRowStamp` / `SequenceRowCache`), or `sys.sequences`**[`docs/claude/sequences.md`](docs/claude/sequences.md).
151151
- **Touching `CREATE TRIGGER` / `ALTER TRIGGER` / `DROP TRIGGER` / `DISABLE`/`ENABLE TRIGGER`, the `INSERTED` / `DELETED` pseudo-table materialization, the `TriggerFrame` / `FiringTriggerIds` recursion guard, `TRIGGER_NESTLEVEL()`, or `sys.triggers`**[`docs/claude/triggers.md`](docs/claude/triggers.md).
152+
- **Touching `PERIOD FOR SYSTEM_TIME` / `GENERATED ALWAYS AS ROW START / END [HIDDEN]` / `WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = …))` DDL, the auto-created history sibling, `FOR SYSTEM_TIME ALL / AS OF` query syntax, or `HeapTable.PeriodColumns` / `HeapTable.SystemVersioning` / `HeapColumn.GeneratedAs` / `HeapColumn.IsHidden`**[`docs/claude/temporal-tables.md`](docs/claude/temporal-tables.md).
152153
- **Adding a new top-level statement parser or changing the dispatch loop's statement-separator rules**[`docs/claude/grammar.md`](docs/claude/grammar.md) + [`docs/claude/control-flow.md`](docs/claude/control-flow.md).
153154

154155
## Not modeled

SqlServerSimulator.Tests.EFCore/EFCoreTemporalTables.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,16 @@ create table Customers (
5757
Id int not null primary key,
5858
Name nvarchar(30) not null,
5959
Credit decimal(18, 2) not null,
60-
ValidFrom datetime2 generated always as row start hidden not null,
61-
ValidTo datetime2 generated always as row end hidden not null,
62-
period for system_time (ValidFrom, ValidTo)
60+
PeriodStart datetime2 generated always as row start hidden not null,
61+
PeriodEnd datetime2 generated always as row end hidden not null,
62+
period for system_time (PeriodStart, PeriodEnd)
6363
) with (system_versioning = on (history_table = dbo.CustomersHistory))
6464
""")
6565
.ExecuteNonQuery();
6666
return simulation;
6767
}
6868

6969
[TestMethod]
70-
[Ignore("Needs: temporal table DDL")]
7170
public void Insert_AppearsInCurrentTable()
7271
{
7372
using var context = new CustomerContext(CreateSimulation());
@@ -77,7 +76,6 @@ public void Insert_AppearsInCurrentTable()
7776
}
7877

7978
[TestMethod]
80-
[Ignore("Needs: temporal table DDL + history tracking")]
8179
public void Update_PreservesPriorVersionInHistory()
8280
{
8381
using var context = new CustomerContext(CreateSimulation());
@@ -98,28 +96,41 @@ public void Update_PreservesPriorVersionInHistory()
9896
}
9997

10098
[TestMethod]
101-
[Ignore("Needs: temporal table DDL + FOR SYSTEM_TIME queries")]
10299
public void TemporalAsOf_ReturnsStateAtPointInTime()
103100
{
104101
using var context = new CustomerContext(CreateSimulation());
105102
_ = context.Customers.Add(new Customer { Id = 1, Name = "alice", Credit = 100m });
106103
_ = context.SaveChanges();
107104

108-
var beforeUpdate = DateTime.UtcNow.AddMilliseconds(-1);
105+
// Read the row's actual ROW START from the database — sidesteps host
106+
// clock granularity entirely. Windows DateTime.UtcNow's ~15.6ms tick
107+
// precision made the previous "DateTime.UtcNow as a midpoint" approach
108+
// fragile: under the wrong tick alignment T_insert and T_update could
109+
// both land on the same DateTime value, leaving no AS-OF window.
110+
var insertTime = context.Customers
111+
.Where(c => c.Id == 1)
112+
.Select(c => EF.Property<DateTime>(c, "PeriodStart"))
113+
.Single();
114+
115+
// Sleep so the upcoming UPDATE's frozen UtcNow lands at a strictly
116+
// later tick than insertTime, even on coarse-tick hosts.
109117
System.Threading.Thread.Sleep(50);
110118

111119
var alice = context.Customers.Single(c => c.Id == 1);
112120
alice.Credit = 999m;
113121
_ = context.SaveChanges();
114122

123+
// Pick a time strictly inside [T_insert, T_update). T_insert + 1 tick
124+
// is guaranteed to satisfy ROW START <= t < ROW END for the history
125+
// row because T_update is at least 50ms (≥ 3 Windows ticks) later.
126+
var betweenTime = insertTime.AddTicks(1);
115127
var historical = context.Customers
116-
.TemporalAsOf(beforeUpdate)
128+
.TemporalAsOf(betweenTime)
117129
.Single(c => c.Id == 1);
118130
Assert.AreEqual(100m, historical.Credit);
119131
}
120132

121133
[TestMethod]
122-
[Ignore("Needs: temporal table DDL + history tracking")]
123134
public void Delete_MovesRowToHistory()
124135
{
125136
using var context = new CustomerContext(CreateSimulation());
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Tests for system-versioned temporal tables: <c>CREATE TABLE … PERIOD FOR
7+
/// SYSTEM_TIME (start, end) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = …))</c>,
8+
/// INSERT / UPDATE / DELETE history maintenance, and <c>FOR SYSTEM_TIME</c>
9+
/// query syntax. Probe-confirmed verbatim against SQL Server 2025 on
10+
/// 2026-05-13: <c>sys.tables.temporal_type</c> shape, period-column types
11+
/// (datetime2(7)), engine-populated period values on INSERT (ROW START =
12+
/// SYSUTCDATETIME(), ROW END = max datetime2), Msg 13501 / 13504 / 13505 /
13+
/// 13506 / 13507 / 13509 / 13536 / 13537 / 13552 / 13559 / 13587 error paths.
14+
/// </summary>
15+
[TestClass]
16+
public sealed class TemporalTableTests
17+
{
18+
private const string CreateTemporalCustomers = """
19+
create table Customers (
20+
Id int not null primary key,
21+
Name nvarchar(30) not null,
22+
Vf datetime2 generated always as row start hidden not null,
23+
Vt datetime2 generated always as row end hidden not null,
24+
period for system_time (Vf, Vt)
25+
) with (system_versioning = on (history_table = dbo.CustomersHistory))
26+
""";
27+
28+
[TestMethod]
29+
public void Ddl_CreatesParentAndHistory()
30+
{
31+
var simulation = new Simulation();
32+
_ = simulation.ExecuteNonQuery(CreateTemporalCustomers);
33+
AreEqual(2, simulation.ExecuteScalar("select count(*) from sys.tables where name in ('Customers', 'CustomersHistory')"));
34+
}
35+
36+
[TestMethod]
37+
public void Insert_AutoPopulatesPeriodColumns()
38+
{
39+
var simulation = new Simulation();
40+
simulation.ExecuteBatches(
41+
CreateTemporalCustomers,
42+
"insert Customers (Id, Name) values (1, 'alice')");
43+
AreEqual(1, simulation.ExecuteScalar("select count(*) from Customers"));
44+
// Explicit projection — hidden columns reachable by name.
45+
var maxEnd = simulation.ExecuteScalar("select Vt from Customers where Id = 1");
46+
AreEqual(DateTime.MaxValue.Date, ((DateTime)maxEnd!).Date);
47+
}
48+
49+
[TestMethod]
50+
public void SelectStar_ExcludesHiddenPeriodColumns()
51+
{
52+
var simulation = new Simulation();
53+
simulation.ExecuteBatches(
54+
CreateTemporalCustomers,
55+
"insert Customers (Id, Name) values (1, 'a')");
56+
using var con = simulation.CreateOpenConnection();
57+
using var cmd = con.CreateCommand();
58+
cmd.CommandText = "select * from Customers";
59+
using var r = cmd.ExecuteReader();
60+
AreEqual(2, r.FieldCount);
61+
AreEqual("Id", r.GetName(0));
62+
AreEqual("Name", r.GetName(1));
63+
}
64+
65+
[TestMethod]
66+
public void Insert_ExplicitGeneratedAlwaysValue_RaisesMsg13536()
67+
=> new Simulation().AssertSqlError(
68+
$"{CreateTemporalCustomers}; insert Customers (Id, Name, Vf) values (1, 'a', sysutcdatetime())",
69+
13536,
70+
"Cannot insert an explicit value into a GENERATED ALWAYS column in table 'simulated.dbo.Customers'. Use INSERT with a column list to exclude the GENERATED ALWAYS column, or insert a DEFAULT into GENERATED ALWAYS column.");
71+
72+
[TestMethod]
73+
public void Insert_IntoHistoryTable_RaisesMsg13559()
74+
=> new Simulation().AssertSqlError(
75+
$"{CreateTemporalCustomers}; insert CustomersHistory (Id, Name, Vf, Vt) values (1, 'a', sysutcdatetime(), sysutcdatetime())",
76+
13559,
77+
"Cannot insert rows in a temporal history table 'simulated.dbo.CustomersHistory'.");
78+
79+
[TestMethod]
80+
public void Ddl_GeneratedColumnWithoutPeriod_RaisesMsg13509()
81+
=> new Simulation().AssertSqlError(
82+
"create table t (id int, Vf datetime2 generated always as row start hidden not null)",
83+
13509,
84+
"Cannot create generated always column when SYSTEM_TIME period is not defined.");
85+
86+
[TestMethod]
87+
public void Ddl_PeriodEndDoesntMatch_RaisesMsg13507()
88+
=> new Simulation().AssertSqlError(
89+
"create table t (id int, Vf datetime2 generated always as row start hidden not null, Vt datetime2 generated always as row end hidden not null, Other int, period for system_time (Vf, Other))",
90+
13507,
91+
"System-versioned table SYSTEM_TIME period definition end column name not matching 'GENERATED ALWAYS AS ROW END' column name.");
92+
93+
[TestMethod]
94+
public void Ddl_PeriodColumnNullable_RaisesMsg13587()
95+
=> new Simulation().AssertSqlError(
96+
"create table t (id int, Vf datetime2 generated always as row start hidden null, Vt datetime2 generated always as row end hidden null, period for system_time (Vf, Vt))",
97+
13587,
98+
"Period column 'Vf' in a system-versioned temporal table cannot be nullable.");
99+
100+
[TestMethod]
101+
public void ForSystemTimeAll_UnionsCurrentAndHistory()
102+
{
103+
var simulation = new Simulation();
104+
simulation.ExecuteBatches(
105+
CreateTemporalCustomers,
106+
"insert Customers (Id, Name) values (1, 'a'), (2, 'b')",
107+
"update Customers set Name = 'A' where Id = 1");
108+
AreEqual(3, simulation.ExecuteScalar("select count(*) from Customers for system_time all"));
109+
}
110+
111+
[TestMethod]
112+
public void ForSystemTimeAsOf_ReturnsPriorState()
113+
{
114+
var simulation = new Simulation();
115+
simulation.ExecuteBatches(
116+
CreateTemporalCustomers,
117+
"insert Customers (Id, Name) values (1, 'before')");
118+
// Capture the insert's ROW START + 1 tick as the AS-OF time point.
119+
// Sleep before the UPDATE so its statement-frozen UtcNow advances
120+
// strictly past betweenTime on hosts with coarse clock granularity
121+
// (Windows DateTime.UtcNow ticks at ~15.6ms — without the sleep,
122+
// T_insert and T_update can land on the same tick, leaving no
123+
// (T_insert, T_update) window for the AS-OF filter to find).
124+
var betweenTime = ((DateTime)simulation.ExecuteScalar("select Vf from Customers where Id = 1")!).AddTicks(1);
125+
System.Threading.Thread.Sleep(50);
126+
_ = simulation.ExecuteNonQuery("update Customers set Name = 'after' where Id = 1");
127+
var asOf = simulation.ExecuteScalar($"select Name from Customers for system_time as of '{betweenTime:O}' where Id = 1");
128+
AreEqual("before", asOf);
129+
}
130+
131+
[TestMethod]
132+
public void Update_CopiesOldRowToHistory()
133+
{
134+
var simulation = new Simulation();
135+
simulation.ExecuteBatches(
136+
CreateTemporalCustomers,
137+
"insert Customers (Id, Name) values (1, 'alice')",
138+
"update Customers set Name = 'ALICE' where Id = 1");
139+
AreEqual(1, simulation.ExecuteScalar("select count(*) from Customers where Name = 'ALICE'"));
140+
AreEqual(1, simulation.ExecuteScalar("select count(*) from CustomersHistory where Name = 'alice'"));
141+
}
142+
143+
[TestMethod]
144+
public void Update_SetGeneratedAlwaysColumn_RaisesMsg13537()
145+
=> new Simulation().AssertSqlError(
146+
$"{CreateTemporalCustomers}; insert Customers (Id, Name) values (1, 'a'); update Customers set Vf = sysutcdatetime() where Id = 1",
147+
13537,
148+
"Cannot update GENERATED ALWAYS columns in table 'simulated.dbo.Customers'.");
149+
150+
[TestMethod]
151+
public void Delete_MovesRowToHistory()
152+
{
153+
var simulation = new Simulation();
154+
simulation.ExecuteBatches(
155+
CreateTemporalCustomers,
156+
"insert Customers (Id, Name) values (1, 'alice')",
157+
"delete from Customers where Id = 1");
158+
AreEqual(0, simulation.ExecuteScalar("select count(*) from Customers"));
159+
AreEqual(1, simulation.ExecuteScalar("select count(*) from CustomersHistory where Name = 'alice'"));
160+
}
161+
162+
[TestMethod]
163+
public void DropTable_SystemVersionedParent_RaisesMsg13552()
164+
=> new Simulation().AssertSqlError(
165+
$"{CreateTemporalCustomers}; drop table Customers",
166+
13552,
167+
"Drop table operation failed on table 'simulated.dbo.Customers' because it is not a supported operation on system-versioned temporal tables.");
168+
169+
[TestMethod]
170+
public void DropTable_HistorySibling_RaisesMsg13552()
171+
=> new Simulation().AssertSqlError(
172+
$"{CreateTemporalCustomers}; drop table CustomersHistory",
173+
13552,
174+
"Drop table operation failed on table 'simulated.dbo.CustomersHistory' because it is not a supported operation on system-versioned temporal tables.");
175+
176+
[TestMethod]
177+
public void Ddl_GeneratedColumnNotDatetime2_RaisesMsg13501()
178+
=> new Simulation().AssertSqlError(
179+
"create table t (id int, Vf datetime generated always as row start hidden not null, Vt datetime generated always as row end hidden not null, period for system_time (Vf, Vt))",
180+
13501,
181+
"Temporal generated always column 'Vf' has invalid data type.");
182+
}

SqlServerSimulator/Errors/SimulatedSqlException.SchemaErrors.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,97 @@ internal static SimulatedSqlException ObjectAlreadyExistsInDestination(string ob
508508
/// </summary>
509509
internal static SimulatedSqlException CannotTransferObjectOwnedByParent() =>
510510
new("Cannot transfer an object that is owned by a parent object.", 15347, 16, 1);
511+
512+
/// <summary>
513+
/// Mimics SQL Server error 13501: a column declared <c>GENERATED ALWAYS
514+
/// AS ROW START / END</c> must be typed <c>datetime2</c>. Probe-confirmed
515+
/// wording against SQL Server 2025.
516+
/// </summary>
517+
internal static SimulatedSqlException TemporalGeneratedColumnInvalidType(string columnName) =>
518+
new($"Temporal generated always column '{columnName}' has invalid data type.", 13501, 16, 1);
519+
520+
/// <summary>
521+
/// Mimics SQL Server error 13504: <c>PERIOD FOR SYSTEM_TIME (startCol, endCol)</c>
522+
/// was declared but no column was declared <c>GENERATED ALWAYS AS ROW
523+
/// START</c> to back it (or the start name didn't match any such column).
524+
/// Probe-confirmed wording.
525+
/// </summary>
526+
internal static SimulatedSqlException TemporalRowStartMissing() =>
527+
new("Temporal 'GENERATED ALWAYS AS ROW START' column definition missing.", 13504, 16, 1);
528+
529+
/// <summary>
530+
/// Mimics SQL Server error 13505: symmetric to <see cref="TemporalRowStartMissing"/>
531+
/// — no <c>GENERATED ALWAYS AS ROW END</c> column matched the period
532+
/// definition's end name. Probe-confirmed wording.
533+
/// </summary>
534+
internal static SimulatedSqlException TemporalRowEndMissing() =>
535+
new("Temporal 'GENERATED ALWAYS AS ROW END' column definition missing.", 13505, 16, 1);
536+
537+
/// <summary>
538+
/// Mimics SQL Server error 13506: <c>PERIOD FOR SYSTEM_TIME (start, end)</c>
539+
/// names <c>start</c>, but the column either doesn't exist or isn't a
540+
/// <c>GENERATED ALWAYS AS ROW START</c> column. Probe-confirmed wording.
541+
/// </summary>
542+
internal static SimulatedSqlException TemporalPeriodStartNotMatching() =>
543+
new("System-versioned table SYSTEM_TIME period definition start column name not matching 'GENERATED ALWAYS AS ROW START' column name.", 13506, 16, 1);
544+
545+
/// <summary>
546+
/// Mimics SQL Server error 13507: symmetric to <see cref="TemporalPeriodStartNotMatching"/>
547+
/// — the period's end column name doesn't match any <c>GENERATED ALWAYS
548+
/// AS ROW END</c> column. Probe-confirmed wording (raised both when the
549+
/// referenced column doesn't exist and when it exists but isn't
550+
/// generated-as-row-end).
551+
/// </summary>
552+
internal static SimulatedSqlException TemporalPeriodEndNotMatching() =>
553+
new("System-versioned table SYSTEM_TIME period definition end column name not matching 'GENERATED ALWAYS AS ROW END' column name.", 13507, 16, 1);
554+
555+
/// <summary>
556+
/// Mimics SQL Server error 13509: at least one column is declared
557+
/// <c>GENERATED ALWAYS AS ROW START / END</c> but the table has no
558+
/// <c>PERIOD FOR SYSTEM_TIME</c> declaration backing it. Probe-confirmed.
559+
/// </summary>
560+
internal static SimulatedSqlException TemporalGeneratedColumnWithoutPeriod() =>
561+
new("Cannot create generated always column when SYSTEM_TIME period is not defined.", 13509, 16, 1);
562+
563+
/// <summary>
564+
/// Mimics SQL Server error 13587: a period column on a system-versioned
565+
/// temporal table was declared with explicit <c>NULL</c>. Probe-confirmed
566+
/// wording (the implicit <c>NOT NULL</c> form is required).
567+
/// </summary>
568+
internal static SimulatedSqlException TemporalPeriodColumnNullable(string columnName) =>
569+
new($"Period column '{columnName}' in a system-versioned temporal table cannot be nullable.", 13587, 16, 1);
570+
571+
/// <summary>
572+
/// Mimics SQL Server error 13536: <c>INSERT</c> supplied an explicit
573+
/// value for a column declared <c>GENERATED ALWAYS AS ROW START / END</c>.
574+
/// Period columns are engine-populated and not user-writable. Probe-
575+
/// confirmed wording against SQL Server 2025.
576+
/// </summary>
577+
internal static SimulatedSqlException CannotInsertExplicitGeneratedAlways(string qualifiedTableName) =>
578+
new($"Cannot insert an explicit value into a GENERATED ALWAYS column in table '{qualifiedTableName}'. Use INSERT with a column list to exclude the GENERATED ALWAYS column, or insert a DEFAULT into GENERATED ALWAYS column.", 13536, 16, 1);
579+
580+
/// <summary>
581+
/// Mimics SQL Server error 13537: <c>UPDATE</c> set a value on a column
582+
/// declared <c>GENERATED ALWAYS AS ROW START / END</c>. Probe-confirmed.
583+
/// </summary>
584+
internal static SimulatedSqlException CannotUpdateGeneratedAlways(string qualifiedTableName) =>
585+
new($"Cannot update GENERATED ALWAYS columns in table '{qualifiedTableName}'.", 13537, 16, 1);
586+
587+
/// <summary>
588+
/// Mimics SQL Server error 13559: a direct <c>INSERT</c> targeted the
589+
/// history sibling of a system-versioned temporal table. History rows
590+
/// are populated by the engine via UPDATE / DELETE on the parent.
591+
/// Probe-confirmed wording.
592+
/// </summary>
593+
internal static SimulatedSqlException CannotInsertIntoTemporalHistoryTable(string qualifiedTableName) =>
594+
new($"Cannot insert rows in a temporal history table '{qualifiedTableName}'.", 13559, 16, 1);
595+
596+
/// <summary>
597+
/// Mimics SQL Server error 13552: <c>DROP TABLE</c> rejected because the
598+
/// target is a system-versioned temporal table (parent or history). The
599+
/// caller must first <c>ALTER TABLE … SET (SYSTEM_VERSIONING = OFF)</c>.
600+
/// Probe-confirmed wording.
601+
/// </summary>
602+
internal static SimulatedSqlException CannotDropTemporalTable(string qualifiedTableName) =>
603+
new($"Drop table operation failed on table '{qualifiedTableName}' because it is not a supported operation on system-versioned temporal tables.", 13552, 16, 1);
511604
}

0 commit comments

Comments
 (0)