1616using Microsoft . EntityFrameworkCore ;
1717using Microsoft . EntityFrameworkCore . Migrations ;
1818using System ;
19+ using System . Collections . Generic ;
20+ using System . Linq ;
21+ using System . Threading . Tasks ;
22+ using Google . Cloud . EntityFrameworkCore . Spanner . Extensions ;
23+ using Google . Cloud . Spanner . V1 ;
1924using Xunit ;
2025
2126namespace Google . Cloud . EntityFrameworkCore . Spanner . Tests . MigrationTests
@@ -33,6 +38,7 @@ public MigrationMockServerTests(SpannerMockServerFixture service)
3338 {
3439 _fixture = service ;
3540 service . SpannerMock . Reset ( ) ;
41+ service . DatabaseAdminMock . Reset ( ) ;
3642 }
3743
3844 private string ConnectionString => $ "Data Source=projects/p1/instances/i1/databases/d1;Host={ _fixture . Host } ;Port={ _fixture . Port } ";
@@ -54,6 +60,14 @@ public void TestMigrateUsesDdlBatch()
5460 using var db = new MockMigrationSampleDbContext ( ConnectionString ) ;
5561 db . Database . Migrate ( ) ;
5662
63+ var requests = _fixture . SpannerMock . Requests . ToList ( ) ;
64+ // There is one BatchDmlRequest per migration.
65+ var batchDmlRequests = requests . OfType < ExecuteBatchDmlRequest > ( ) ;
66+ Assert . Equal ( 2 , batchDmlRequests . Count ( ) ) ;
67+ // Each BatchDmlRequest is executed as a separate transaction.
68+ var commitRequests = requests . OfType < CommitRequest > ( ) . ToList ( ) ;
69+ Assert . Equal ( 2 , commitRequests . Count ) ;
70+
5771 Assert . Collection ( _fixture . DatabaseAdminMock . Requests ,
5872 // The initial request will create an empty database and then create the migrations history table.
5973 request => Assert . IsType < CreateDatabaseRequest > ( request ) ,
@@ -101,5 +115,83 @@ public void TestMigrateUsesDdlBatch()
101115 }
102116 ) ;
103117 }
118+
119+ [ Fact ]
120+ public async Task TestStartMigrateAsync ( )
121+ {
122+ var version = typeof ( Migration ) . Assembly . GetName ( ) . Version ?? new Version ( ) ;
123+ var formattedVersion = $ "{ version . Major } .{ version . Minor } .{ version . Build } ";
124+ _fixture . SpannerMock . AddOrUpdateStatementResult ( "SELECT 1" , StatementResult . CreateSelect1ResultSet ( ) ) ;
125+ _fixture . SpannerMock . AddOrUpdateStatementResult (
126+ "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema = '' AND table_name = 'EFMigrationsHistory')" ,
127+ StatementResult . CreateSelectTrueResultSet ( ) ) ;
128+ _fixture . SpannerMock . AddOrUpdateStatementResult ( $ "SELECT `MigrationId`, `ProductVersion`{ Environment . NewLine } " +
129+ $ "FROM `EFMigrationsHistory`{ Environment . NewLine } " +
130+ $ "ORDER BY `MigrationId`",
131+ StatementResult . CreateResultSet ( new List < Tuple < Cloud . Spanner . V1 . TypeCode , string > >
132+ {
133+ Tuple . Create ( Cloud . Spanner . V1 . TypeCode . String , "MigrationId" ) ,
134+ Tuple . Create ( Cloud . Spanner . V1 . TypeCode . String , "ProductVersion" ) ,
135+ } ,
136+ new List < object [ ] >
137+ {
138+ new object [ ] { "20210309110233_Initial" , formattedVersion } ,
139+ } ) ) ;
140+ _fixture . SpannerMock . AddOrUpdateStatementResult (
141+ $ "INSERT INTO `EFMigrationsHistory` (`MigrationId`, `ProductVersion`)\n VALUES ('''20210830_V2''', '''{ formattedVersion } ''')",
142+ StatementResult . CreateUpdateCount ( 1 )
143+ ) ;
144+ await using var db = new MockMigrationSampleDbContext ( ConnectionString ) ;
145+ // This starts an asynchronous database migration, but does not wait for the DDL operation to finish.
146+ await db . Database . StartMigrateAsync ( ) ;
147+
148+ Assert . Collection ( _fixture . DatabaseAdminMock . Requests ,
149+ // Each migration will be executed as a separate DDL batch.
150+ request =>
151+ {
152+ var update = request as UpdateDatabaseDdlRequest ;
153+ Assert . NotNull ( update ) ;
154+ Assert . Collection ( update . Statements ,
155+ // Entity Framework 10 executes this regardless whether database provider has already told it
156+ // that the table exists or not.
157+ sql => Assert . StartsWith ( "CREATE TABLE IF NOT EXISTS `EFMigrationsHistory`" , sql )
158+ ) ;
159+ } , request =>
160+ {
161+ var update = request as UpdateDatabaseDdlRequest ;
162+ Assert . NotNull ( update ) ;
163+ Assert . Collection ( update . Statements ,
164+ sql => Assert . StartsWith ( " DROP INDEX `IDX_TableWithAllColumnTypes_ColDate_ColCommitTS`" , sql ) ,
165+ sql => Assert . StartsWith ( "DROP TABLE `TableWithAllColumnTypes`" , sql ) ,
166+ sql => Assert . StartsWith ( "CREATE TABLE `OtherSequenceKind` (\n `Id` INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (NON_EXISTING_KIND),\n " , sql ) ,
167+ sql => Assert . StartsWith ( "CREATE TABLE `NoSequenceKind` (\n `Id` INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n " , sql ) ,
168+ sql => Assert . StartsWith ( "CREATE TABLE `GenerationStrategyAlways` (\n `Id` INT64 NOT NULL GENERATED ALWAYS AS IDENTITY (BIT_REVERSED_POSITIVE),\n " , sql ) ,
169+ sql => Assert . StartsWith ( "CREATE TABLE `AutoIncrement` (\n `Id` INT64 NOT NULL AUTO_INCREMENT,\n " , sql )
170+ ) ;
171+ }
172+ ) ;
173+ }
174+
175+ [ Fact ]
176+ public async Task TestStartDdlAsync ( )
177+ {
178+ await using var db = new MockMigrationSampleDbContext ( ConnectionString ) ;
179+ await db . Database . StartDdlAsync ( [
180+ "create table my_table (id int64 primary key, value string(max))" ,
181+ "create index my_index on my_table (value)" ,
182+ ] ) ;
183+
184+ Assert . Collection ( _fixture . DatabaseAdminMock . Requests ,
185+ request =>
186+ {
187+ var update = request as UpdateDatabaseDdlRequest ;
188+ Assert . NotNull ( update ) ;
189+ Assert . Collection ( update . Statements ,
190+ sql => Assert . Equal ( "create table my_table (id int64 primary key, value string(max))" , sql ) ,
191+ sql => Assert . Equal ( "create index my_index on my_table (value)" , sql )
192+ ) ;
193+ }
194+ ) ;
195+ }
104196 }
105197}
0 commit comments