Skip to content

Commit

Permalink
Merge pull request #57 from webmd-health-services/feature/initialize-…
Browse files Browse the repository at this point in the history
…database-with-schema-file

Feature/initialize database with schema file
  • Loading branch information
KhoiKy authored Oct 26, 2022
2 parents 46e85e0 + ccfafbc commit 83e8a5c
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 84 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@

# 0.17.0

## Changes

* When initializing a database, Rivet now runs the migrations found in the schema.ps1 file, which contains the baseline
database schema upon which all migrations should be applied. You can use the `Checkpoint-Migration` function to create
a baseline `schema.ps1` file for your database(s).


# 0.16.0

* Updated `Checkpoint-Migration` function:
Expand Down
20 changes: 2 additions & 18 deletions Rivet/Functions/Convert-FileInfoToMigration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,7 @@ Push-Migration function not found. All migrations are required to have a Push-Mi
Push-Migration | Add-Operation -OperationsList $m.PushOperations
if( $m.PushOperations.Count -eq 0 )
{
throw (@'
Push-Migration function is empty and contains no operations. Maybe you''d like to create a table? Here's some sample code to get you started:
function Push-Migration
{
Add-Table 'LetsCreateATable' {
int 'ID' -NotNull
}
}
'@)
return
}

if( -not (Test-Path -Path 'function:Pop-Migration') )
Expand All @@ -133,14 +124,7 @@ Pop-Migration function not found. All migrations are required to have a Pop-Migr
Pop-Migration | Add-Operation -OperationsList $m.PopOperations
if( $m.PopOperations.Count -eq 0 )
{
throw (@'
Pop-Migration function is empty and contains no operations. Maybe you''d like to drop a table? Here's some sample code to get you started:
function Pop-Migration
{
Remove-Table 'LetsCreateATable'
}
'@)
return
}

$afterMigrationLoadParameter = @{ Migration = $m }
Expand Down
15 changes: 11 additions & 4 deletions Rivet/Functions/Get-MigrationFile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,21 @@ function Get-MigrationFile
}
} |
ForEach-Object {
if( $_.BaseName -notmatch '^(\d{14})_(.+)' )
if( $_.BaseName -eq 'schema' )
{
$id = $script:schemaMigrationId # midnight on year 1, month 0, day 0.
$name = $_.BaseName
}
elseif( $_.BaseName -notmatch '^(\d{14})_(.+)' )
{
Write-Error ('Migration {0} has invalid name. Must be of the form `YYYYmmddhhMMss_MigrationName.ps1' -f $_.FullName)
return
}

$id = [int64]$matches[1]
$name = $matches[2]
else
{
$id = [int64]$matches[1]
$name = $matches[2]
}

$_ |
Add-Member -MemberType NoteProperty -Name 'MigrationID' -Value $id -PassThru |
Expand Down
16 changes: 14 additions & 2 deletions Rivet/Functions/Initialize-Database.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,21 @@ function Initialize-Database
)

Set-StrictMode -Version 'Latest'
Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

$who = ('{0}\{1}' -f $env:USERDOMAIN,$env:USERNAME);
$migrationsPath = Join-Path -Path $rivetModuleRoot -ChildPath 'Migrations'
$migrationPaths = [System.Collections.ArrayList]::new()
$rivetMigrationsPath = Join-Path -Path $rivetModuleRoot -ChildPath 'Migrations'
$migrationPaths.Add($rivetMigrationsPath) | Out-Null
Write-Debug -Message ('# {0}.{1}' -f $Connection.DataSource,$Connection.Database)
Update-Database -Path $migrationsPath -RivetSchema -Configuration $Configuration

# Add schema.ps1 file from database's migration directory if it exists
$databaseItem = $Configuration.Databases | Where-Object {$_.Name -eq $Connection.Database}
$schemaFilePath = Join-Path -Path $databaseItem.MigrationsRoot -ChildPath 'schema.ps1'
if( (Test-Path -Path $schemaFilePath) )
{
$migrationPaths.Add($schemaFilePath) | Out-Null
}

Update-Database -Path $migrationPaths -RivetSchema -Configuration $Configuration
}
12 changes: 12 additions & 0 deletions Rivet/Functions/New-Migration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ function New-Migration
$Path
)

Set-StrictMode -Version 'Latest'
Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

foreach( $nameItem in $Name )
{
$id = $null
Expand All @@ -38,6 +41,15 @@ function New-Migration
$migrationPath = [IO.Path]::GetFullPath( $migrationPath )
New-Item -Path $migrationPath -Force -ItemType File

$schemaPs1Path = Join-Path -Path $Path -ChildPath $script:schemaFileName
if (-not (Test-Path -Path $schemaPs1Path))
{
$script:defaultSchemaPs1Content | Set-Content -Path $schemaPs1Path
$msg = "Rivet created ""$($schemaPs1Path | Resolve-Path -Relative)"", a file where Rivet stores the " +
'database''s baseline schema. PLEASE CHECK THIS FILE INTO SOURCE CONTROL.'
Write-Warning $msg
}

$template = @"
<#
Your migration is ready to go! For the best development experience, please
Expand Down
5 changes: 3 additions & 2 deletions Rivet/Functions/Update-Database.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ function Update-Database
)

Set-StrictMode -Version 'Latest'
Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

function ConvertTo-RelativeTime
{
Expand Down Expand Up @@ -147,9 +148,9 @@ function Update-Database
return $true
}

if( [int64]$_.MigrationID -lt 1000000000000 )
if( [int64]$_.MigrationID -lt $script:firstMigrationId )
{
Write-Error ('Migration "{0}" has an invalid ID. IDs lower than 01000000000000 are reserved for internal use.' -f $_.FullName) -ErrorAction Stop
Write-Error "Migration '$($_.FullName)' has an invalid ID. IDs lower than $($script:firstMigrationId) are reserved for internal use." -ErrorAction Stop
return $false
}
return $true
Expand Down
2 changes: 1 addition & 1 deletion Rivet/Rivet.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
RootModule = 'Rivet.psm1'

# Version number of this module.
ModuleVersion = '0.16.0'
ModuleVersion = '0.17.0'

# ID used to uniquely identify this module
GUID = '8af34b47-259b-4630-a945-75d38c33b94d'
Expand Down
17 changes: 16 additions & 1 deletion Rivet/Rivet.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,22 @@ $RivetMigrationsTableName = 'Migrations'
$RivetMigrationsTableFullName = "[$($RivetSchemaName)].[$($RivetMigrationsTableName)]"
$RivetActivityTableName = 'Activity'
$rivetModuleRoot = $PSScriptRoot
[Int64] $script:firstMigrationId = 00010101000000
$script:firstMigrationId = [Int64]'00010101000000' # 1/1/1 00:00:00
$script:schemaMigrationId = [Int64]'00010000000000' # Special ID for schema.ps1, 1/0/0 00:00:00.
$script:schemaFileName = 'schema.ps1'
$script:defaultSchemaPs1Content = @"
# DO NOT MODIFY THIS FILE. The current database schema is saved to this file as a Rivet migration when you checkpoint
# your database. Any changes manually made to this file will eventually be lost. This file should be checked into source
# control alongside normal database migrations.
function Push-Migration
{
}
function Pop-Migration
{
}
"@

$timer = New-Object 'Diagnostics.Stopwatch'
$timerForWrites = New-Object 'Diagnostics.Stopwatch'
Expand Down
119 changes: 77 additions & 42 deletions Test/Checkpoint-Migration.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ Set-StrictMode -Version 'Latest'

$checkpointedMigrations = [System.Collections.ArrayList]::new()
$migrationPaths = [System.Collections.ArrayList]::new()
$existingSchemaContents = @"
function Push-Migration
{
Add-Table -Name 'Existing' -Column {
int 'ID' -Identity
}
}
function Pop-Migration
{
Remove-Table 'Existing'
}
"@

function Init
{
Expand Down Expand Up @@ -35,6 +48,23 @@ function GivenMigrationContent
}
}

function Reset
{
param(
[String[]] $Database
)

if( -not $Database )
{
$Database = $RTDatabaseName
}

foreach( $databaseItem in $Database )
{
Remove-RivetTestDatabase -Name $databaseItem
}
}

function ThenFailed
{
param(
Expand Down Expand Up @@ -82,6 +112,27 @@ function ThenMigration
}
}

function ThenSchemaFileRunnable
{
foreach( $path in $script:migrationPaths )
{
$databaseName = $path.Directory.Parent.Name
$schemaFilePath = Join-Path -Path (Split-Path $path) -ChildPath 'schema.ps1'
$schemaFileContents = Get-Content -Path $schemaFilePath
$checkpointedMigration = @{
Database = $path.Directory.Parent.Name;
Migration = $schemaFileContents
}
$script:checkpointedMigrations.Add($checkpointedMigration)
It ('should export a runnable migration') {
Remove-RivetTestDatabase -Name $databaseName
# Now, check that the migration is runnable
Invoke-RTRivet -Push -Database $databaseName -ErrorAction Stop
Invoke-RTRivet -Pop -Database $databaseName -ErrorAction Stop
}
}
}

function WhenCheckpointingMigration
{
[CmdletBinding()]
Expand All @@ -95,56 +146,29 @@ function WhenCheckpointingMigration
[String[]] $Exclude
)

try
if(-not $Database )
{
if(-not $Database )
{
$Database = $RTDatabaseName
}

if( $ExistingSchemaFile )
{
foreach( $path in $script:migrationPaths )
{
New-Item -Path (Split-Path -Path $path) -Name 'schema.ps1' -ItemType 'File'
}
}

Invoke-RTRivet -Checkpoint -Database $Database -Force:$Force

foreach( $migration in $Exclude )
{
$migrationPathToRemove = $script:migrationPaths | Where-Object {$_ -like $migration}
if( $migrationPathToRemove )
{
$script:migrationPaths.Remove($migrationPathToRemove)
}
}
$Database = $RTDatabaseName
}

if( $ExistingSchemaFile )
{
foreach( $path in $script:migrationPaths )
{
$databaseName = $path.Directory.Parent.Name
$schemaFilePath = Join-Path -Path (Split-Path $path) -ChildPath 'schema.ps1'
$schemaFileContents = Get-Content -Path $schemaFilePath
$checkpointedMigration = @{
Database = $path.Directory.Parent.Name;
Migration = $schemaFileContents
}
$script:checkpointedMigrations.Add($checkpointedMigration)
It ('should export a runnable migration') {
# Now, check that the migration is runnable
Invoke-RTRivet -Pop -Database $databaseName

$checkpointedMigration.Migration | Set-Content -Path $path
Invoke-RTRivet -Push -Database $databaseName -ErrorAction Stop
Invoke-RTRivet -Pop -Force -Database $databaseName -ErrorAction Stop
}
Set-Content -Path (Join-Path -Path $path.Directory.FullName -ChildPath 'schema.ps1') -Value $existingSchemaContents
}
}
finally

foreach( $migration in $Exclude )
{
Stop-RivetTest -DatabaseName $Database
$migrationPathToRemove = $script:migrationPaths | Where-Object {$_ -like $migration}
if( $migrationPathToRemove )
{
$script:migrationPaths.Remove($migrationPathToRemove)
}
}

Invoke-RTRivet -Checkpoint -Database $Database -Force:$Force
}

Describe 'Checkpoint-Migration.when there are multiple databases' {
Expand All @@ -168,6 +192,7 @@ function Pop-Migration
'@ -Database ('RivetTest', 'RivetTest2')
Invoke-RTRivet -Push -Database ('RivetTest', 'RivetTest2')
WhenCheckpointingMigration -Database ('RivetTest', 'RivetTest2')
ThenSchemaFileRunnable
ThenMigration -HasContent @'
Add-Table -Name 'Replicated' -Column {
int 'ID' -Identity
Expand All @@ -179,6 +204,7 @@ function Pop-Migration
}
'@ -Database ('RivetTest', 'RivetTest2')
ThenNoErrors
Reset -Database ('RivetTest', 'RivetTest2')
}

Describe 'Checkpoint-Migration.when schema.ps1 file already exists' {
Expand All @@ -199,6 +225,7 @@ function Pop-Migration
Invoke-RTRivet -Push -Database $RTDatabaseName
WhenCheckpointingMigration -ExistingSchemaFile -ErrorAction SilentlyContinue
ThenFailed -WithError 'schema.ps1" already exists.'
Reset
}

Describe 'Checkpoint-Migration.when schema.ps1 file already exists but -Force switch is given' {
Expand All @@ -218,12 +245,14 @@ function Pop-Migration
'@
Invoke-RTRivet -Push -Database $RTDatabaseName
WhenCheckpointingMigration -ExistingSchemaFile -Force
ThenSchemaFileRunnable
ThenMigration -HasContent @'
Add-Table -Name 'NotReplicated' -Column {
int 'ID' -Identity -NotForReplication
}
'@ -Database $RTDatabaseName
ThenNoErrors
Reset
}

Describe 'Checkpoint-Migration.when checkpointing a migration' {
Expand Down Expand Up @@ -281,6 +310,7 @@ function Pop-Migration
'@
Invoke-RTRivet -Push -Database $RTDatabaseName
WhenCheckpointingMigration
ThenSchemaFileRunnable
ThenMigration -Not -HasContent 'Add-Schema -Name ''dbo''' -Database $RTDatabaseName
ThenMigration -HasContent @'
Add-Table -Name 'Migrations' -Description 'some table''s description' -Column {
Expand Down Expand Up @@ -341,6 +371,7 @@ ON [dbo].[Migrations] for insert as select 1
ThenMigration -Not -HasContent 'Remove-UniqueKey' -Database $RTDatabaseName
ThenMigration -Not -HasContent 'Remove-Trigger' -Database $RTDatabaseName
ThenNoErrors
Reset
}

Describe 'Checkpoint-Migration.when there are multiple migrations but only one has been pushed' {
Expand Down Expand Up @@ -373,6 +404,7 @@ function Pop-Migration
}
'@
WhenCheckpointingMigration -Exclude $script:migrationPaths[1]
ThenSchemaFileRunnable
ThenMigration -HasContent @'
Add-Table -Name 'Test1' -Column {
int 'ID' -Identity
Expand All @@ -384,10 +416,13 @@ function Pop-Migration
}
'@ -Database $RTDatabaseName
ThenNoErrors
Reset
}

Describe 'Checkpoint-Migration.when no migrations have been pushed' {
Init
# Nothing to push here. Just running push here to initialize database with rivet.migrations table.
Invoke-RTRivet -Push -Database $RTDatabaseName
WhenCheckpointingMigration
ThenNoErrors
}
Loading

0 comments on commit 83e8a5c

Please sign in to comment.