Creating Database Build Artifacts when Running Flyway Migrations
This article provides PowerShell automation scripts for running Flyway database migrations. These scripts use SQL Compare to automatically generate all the required database build artifacts in the version control system, for each Flyway deployment.
When you use Flyway for building or upgrading a database to a particular version, you will run “Flyway migrate”, it will execute the set of migration scripts, in the correct order, and the result is a database at the version you need. You may need to run checks, update reports or send a message. You will often also want to produce other ‘build artifacts‘ such as scripts or data.
I’ll show how you can use two of the tools in Flyway Enterprise, Flyway and SQL Compare, along with PowerShell automation, to generate these database scripts automatically, as part of a Flyway deployment. For each database version delivered by Flyway, it will use SQL Compare to generate both a build script, and a script folder containing the DDL scripts for each database object. In a subsequent article, I show how you can use this object-level script folder to have Flyway check for ‘database drift‘, before running Flyway migrate.
This will then allow you to use a Flyway project together with other the Flyway Enterprise tools such as SQL Data Compare, SQL Source Control and SQL Change Automation.
What database scripts should go into source control?
Before tackling the topic of automating various database development tasks, such as generating build and object-level scripts, I ought to explain why a development team might need to generate several different types of database scripts in source control.
In any procedural application, it is generally obvious what the source code is. Not so with relational databases. Sure, we have modules such as functions, stored procedures, constraints, rules, views and triggers that all contain procedural code, but that’s not true of tables. A user database is a dynamic structure created from system tables, not from code. Our DDL code merely places the correct values in these system tables, and there are many legitimate and productive ways of doing this, in other words of designing and evolving the tables of a database. We can use entity-relationship design tools that generate code for building or migrating databases, or we can use table-building GUIs. Like most database developers I’ll choose the quickest and most effective way of doing any task. For example, I use a single data dictionary table for documenting the purpose of tables and columns rather than embedding extended properties in a script, and I would execute it separately, but would expect to see them in the script with the object they refer to.
To give a database development team sufficient flexibility to work in the most effective way, it pays to decide on what gets stored in source control as the ‘source of truth’, or ‘canonical source’ for a deployment. When working with Flyway, your ‘source of truth’ is the migrations folder. However, whatever you choose, it greatly assists the teamwork if all the other main ways of storing source code are available as well. An object-level build script is useful in development for telling us about all the changes, who made them and when. A migration script in development will tell us how it happened. A database-level build script allows us to re-create a version quickly and easily.
To move from one release to the next, you will use one or more migration scripts. Flyway will chain migration scripts in the right order. However, if you’ve used a variety of methods to develop your database, you might just use a single migration script, either one created by a tool like SQL Compare that summarizes the changes between the two versions into a migration or ‘deployment’ script, or one created by hand.
Here is a very generalized typical developmental process that will illustrate where script generation fits into the process.
Object-level source and build scripts
The most obvious advantage of generating a database build script and object-level source scripts, for each new database version produced by Flyway, is that you can then use other Flyway Enterprise tools, such as SQL Compare, to check that a database really is at the version you think it is before you try to migrate it to a new version.
Flyway’s migration approach encourages incremental change but doesn’t protect the database from being subject to ‘uncontrolled’ changes, otherwise known as database drift. Flyway will build any database at an exact version, but then over time that database is likely to drift, due to changes that don’t originate from a Flyway-controlled migration file. I find them in my own work, mostly applied by accident or carelessness.
Migrations only work if applied to the right version of the database. Flyway can tell you what migration files it has applied to a database but can’t detect changes made in other ways. If Flyway attempts to migrate a database that is not at the version it thinks it is, due to drift, then it is more likely that something will go wrong, although you can’t predict what.
Drift detection is important for any database, but is essential for a commercial database, where all changes must be auditable. This means that what is in production use is nothing more or less than what is in source control and has been signed-off by all the players in the deployment process.
Flyway, with the help of SQL Compare, can tell you if there have been ‘uncontrolled changes‘ to the version it is migrating that could affect the migration, security or testing. It does this by comparing the metadata of the database you wish to migrate, with the object scripts or build script for that version, which this article will show you how to generate, as part of the Flyway migration run that creates that version initially.
There are other ‘process’ advantages that are nice to have, such as being able to consolidate scripts, run a structured and automated deployment process, and get a better perspective of changes at the level of individual tables or other objects, but the check against a build script to make sure it is exactly the professed version can prevent a great deal of confusion and time-wasting in the process of developing a database.
Migration scripts
If a database is already in production and being modified, the indispensable artefact produced by the development team is the script, or chain of scripts, that migrates the current production version to the new release, whilst preserving the data. It is easy to forget this, because while ‘build’ can refer generally to creating a database version from scratch, teams will often use the term ‘build’ differently during the deployment process, from Testing, User Acceptance Testing (UAT), through to Staging and onwards to Production.
What they mean by build is ‘the artifact that will establish the required version of the database’. Unless the database doesn’t already exist, ‘the build’ in these environments is in fact a migration, and this will modify the existing database to the new version, while preserve existing data, rather than ‘tearing down and starting again’. Once you have in place a strict policy of applying a version to databases and preventing ‘drift’ from that version, then all you need in the deployment process is the well-tested migration script. If that release candidate fails somewhere along the route, the deployment process must start again with a new version.
Stamping a database with the version number
I show how to attach a version stamp directly to a database, using an afterMigrate callback script in Customizing Database Deployments using Flyway Callbacks and Placeholders.
All this reinforces the need for different types of scripts during the development cycle, whatever you choose to represent the source of truth in source control. If, for example, you can painlessly generate both the build script and the script folder as you use Flyway, then you will always have an artifact that SCA or SQL Source Control can use. If, conversely, you like to work predominately from object-level scripts, you can always generate a migration script from a ‘static’ object-level folder, allowing Flyway to be used in a mixed approach.
Creating build tasks in PowerShell using script blocks
Each database product has its own solution. For Oracle, or SQL Server, we can use SQL Compare, this is all very simple. The tool can create a build script and a script folder. Once there is a script folder for a version, it can use it for comparison as if it were a live database, and it do its best to do the script in a way that guarantees to preserve the data. If there is any doubt, SQL Compare will warn you if the script needs any tweaking to preserve data.
To persuade Flyway to produce a build script and object script folder, we need a set of reusable PowerShell Build tasks that use SQL Compare to generate them. We can also create a range of other useful, standalone build tasks such as one that gets the current version of a database, or fetches a password for a user, and
To implement these tasks in PowerShell, I’ll be using script blocks as ‘nameless’ local functions to provide whatever tasks need to be done to the databases, either before or after Flyway has completed its work. The intention is to provide ‘hooks’ for other Flyway Enterprise tools and processes. In this article, I’ll be demo’ing a hook for SQL Compare to generate the scripts (a hook for SQL Data Compare would work in a very similar manner), and a hook for SQLCMD that gets the current database version number from Flyway’s schema history table.
The main reason for using script blocks is that by stacking them up in an array, I can run as many tasks as I like or need, as a list of jobs. As I have to pass the same parameters to each, I pass in just one hash table containing all the variables I need. Any result is written back to the hash table.
You can find the code for all the build tasks in this article in the associated GitHub project.
Creating the object source scripts and a build script
Here, as an example, is a database build task that creates an object-level script folder for the version of the database specified, using SQL Compare, but only does it if the folder doesn’t already exist in the location you’ve specified.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<#this routine checks to see if a script folder already exists for this version of the database and, if not, it will create one and fill it with subdirectories for each type of object. A tables folder will, for example, have a file for every table each containing a build script to create that object. When this exists, it allows SQL Compare to do comparisons and check that a version has not drifted.#> $CreateScriptFoldersIfNecessary = { Param ($param1) # the parameter is hashtable that contains all the useful values $Problem=@(); #We check that it contains the keys for the values that we need @('version', 'server', 'database', 'project') | foreach{ if ($param1.$_ -eq $null) { $problems += "no key for the '$($_)'" } } #the alias must be set to the path of your installed version of SQL Compare Set-Alias SQLCompare "${env:ProgramFiles(x86)}\Red Gate\SQL Compare 13\sqlcompare.exe" -Scope Script #the database scripts path would be up to you to define, of course $MyDatabasePath = "$($env:USERPROFILE)\Documents\GitHub\$($param1.EscapedProject)\$($param1.Version)\Source" $Args = @( "/server1:$($param1.server)", "/database1:$($param1.database)", "/Makescripts:$($MyDatabasePath)", #special command to make a scripts directory "/force", "/LogLevel:Warning" ) if ($param1.uid -ne $NULL) #add the arguments for credentials where necessary { $Args += @( "/username1:$($param1.uid)", "/Password1:$($param1.pwd)" ) } if ($problems.Count -eq 0 -and(!(Test-Path -PathType Container $MyDatabasePath)))#if it doesn't already erxist { Sqlcompare @Args #write an object-level script folder that represents the vesion of the database if ($?) { "Written script folder for $($param1.Project) $($param1.Version) to $MyDatabasePath" } else { #report a problem and send back the args for diagnosis (hint, only for script development) $Arguments = ''; $Arguments += $args | foreach{ $_ } $problems+="SQL Compare responded with error code $LASTEXITCODE when used with paramaters $Arguments." } if ($problems.count -gt 0) { $param1.Problems += @{'Name'='CreateScriptFoldersIfNecessary';Issues=$problems}} } else { "This version is already scripted in $MyDatabasePath " } } |
As you can see, it takes a hash table as its only parameter. The routine checks to see that the required parameters are there. If not, it fills in a problem list in the hash table. In a working environment, I’d also do an initial check that all the key/value pairs I need are valid, but I don’t want to put any unnecessary code into this article. Normally, you call this as you would a function. I can, though, execute this easily by itself if I want, and debugging is easy.
This is how to create the folder of object scripts for v1.1.10 of the Pubs database. They will be placed in the folder \<user>\Documents\GitHub\publications\1.1.5\source.
1 |
$CreateScriptFoldersIfNecessary.Invoke(@{'pwd'='MySecretPassword';'uid'='Phil';Database='Pubs';'server'='MyServer';'Project'='publications';'Version'='1.1.10'}) |
I’ve also included in the GitHub project a very similar task that uses SQL Compare to create a build script for the database($CreateBuildScriptIfNecessary
), if one doesn’t exist, by comparing the database to an empty target, like this:
1 2 3 4 5 6 7 8 9 |
$Args = @(# we create an array in order to splat the parameters. With many command-line apps you # can use a hash-table "/server1:$($param1.server)", "/database1:$($param1.database)", "/empty2", "/force", # "/options:NoTransactions,NoErrorHandling", # so that we can use the script with Flyway more easily "/LogLevel:Warning", "/ScriptFile:$MyDatabasePath\V$($param1.Version)__Build.sql" |
Checking for drift
I’ll be going into the details of this code in a later article, but I’ve also included a build task called $IsDatabaseIdenticalToSource
that will verify that the database you wish to migrate hasn’t drifted and report back. It does this by using SQL Compare to compare the object-level scripts directory (if one already exists) with the schema of the database to be migrated.
Getting the version number of the database
Most of the information that gets passed in the hash table is straightforward, but not all of it. For most operations, you need to know the version of the database. If you first execute an info
action with Flyway on the database, then you get it. Otherwise, you can’t. It is possible to see what migrations were attempted, from Flyway’s output, but these do not report the state of each migration, so it isn’t clear whether they indicate success.
In this article, I use a $GetCurrentVersion
PowerShell build task that uses SQLCMD to read the version, if necessary, from the flyway_schema_history
table in the target database. It assumes that the table is in dbo.flyway_schema_history
(you can, with the Flyway config file, put it anywhere, but if you do, then you must have Flyway Enterprise or Flyway Teams, and execute the PowerShell script from a PowerShell callback). This task is also a nice illustration of a generic way of getting a SQL Result back to PowerShell.
Dealing with passwords
I also use a task called $FetchAnyRequiredPasswords
, to get the password for the user for this database server. It encapsulates into a build task the code I’ve included in other articles (see for example Customizing Database Deployments using Flyway Callbacks and Placeholders) to keep passwords in a secure place within the user folder, and avoid having to supply it repeatedly on the command line.
Running PowerShell build tasks during automated Flyway migrations
Let’s now demo a script that uses these tasks. It is designed for just working on a single database.
This script takes a database that you specify and migrates it to the latest version. I’m using the migration Scripts folder in the GitHub project, as I’ve done in previous articles.
Before running the migration, it runs build tasks to get the current version ($GetCurrentVersion
), and then to verify that the database hasn’t drifted ($IsDatabaseIdenticalToSource
). It will report back via the $databaseDetails
array whether it is OK to proceed. The whole purpose of checking for database drift is to check on what the database should be by comparing it with an existing source code directory. If you’re starting with an empty target database, and no build script or source folder, then there is obviously no way to check for drift, and you’ll get a warning, but the migration will proceed (nervously).
It then does the migration and checks to sees whether it is necessary to create a build script for the new version ($CreateBuildScriptIfNecessary
), and an object-level source folder ($CreateScriptFoldersIfNecessary
), and if so, does both, using schema comparison (SQL Compare). In this script we save the script folder only once for each version. This build artifact should be considered immutable so that it remains the ‘canonical representation’ of the database schema, for each database version.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
<# The tasks are in a separate script. It is placed in the same directory as this script. First, find out where we were executed from. Each environment has a different way of doing it. It all depends how you execute it. If you just past this in, you'll have to make this the working directory #> try { $executablepath = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition) } catch{$executablepath = ''} #didn't like that so remove it if ($executablepath -eq '') { $executablepath = "$(If ($psISE) # null if at the commandline { Split-Path -Path $psISE.CurrentFile.FullPath } Else { $global:PSScriptRoot })" } if ([string]::IsNullOrEmpty($ExecutablePath)){$ExecutablePath=$pwd} .("$executablepath\DatabaseBuildAndMigrateTasks.ps1") #create an alias for the commandline Flyway, Set-Alias Flyway 'C:\ProgramData\chocolatey\lib\flyway.commandline\tools\flyway-7.3.1\flyway.cmd' -Scope local # project-wide settings $ProjectFolder = '<MyPath>\Github\PubsAndFlyway\PubsFlywaySecondMigration' $ProjectDescription='A sample project to demonstrate Flyway, using the old Pubs database' # and here are our project details. The project folder. It works rather like Case-notes # in that it is read-write. It is passed between tasks. $DatabaseDetails=@{'pwd'=''; #Always leave blank 'uid'='MyUserID'#leave blank unless you use credentials ;'Database'='PubsOne';# fill this in please ;'server'='MyServer';# We need to know the server! ;'port' = $null; #Not normally needed with SQL Server. add if required #set to $null or leave it out if you want to let jdbc detect it 'Project'='Pubs';# the name of the project, needed for saving files 'Version'=''; # current version of database - # leave blank unless you know 'Checked'=$false; # has it been checked against a source directory? 'Problems'=@() } <# The tasks are in a separate script. It is placed in the same directory as this script. First, find out where we were executed from. Each environment has a different way of doing it. It all depends how you execute it. If you just past this in, you'll have to make this the working directory #> try { $executablepath = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition) } catch{$executablepath = ''} #didn't like that so remove it if ($executablepath -eq '') { $executablepath = "$(If ($psISE) # null if at the commandline { Split-Path -Path $psISE.CurrentFile.FullPath } Else { $global:PSScriptRoot })" } if ([string]::IsNullOrEmpty($ExecutablePath)){$ExecutablePath=$pwd} .("$executablepath\DatabaseBuildAndMigrateTasks.ps1") #create an alias for the commandline Flyway, Set-Alias Flyway 'C:\ProgramData\chocolatey\lib\flyway.commandline\tools\flyway-7.3.1\flyway.cmd' -Scope local # project-wide settings $ProjectFolder = 'S:\work\Github\PubsAndFlyway\PubsFlywaySecondMigration' $ProjectDescription='A sample project to demonstrate Flyway, using the old Pubs database' # and here are our project details. The project folder. It works rather like Case-notes # in that it is read-write. It is passed between tasks. $DatabaseDetails=@{'pwd'=''; #Always leave blank 'uid'='MyUserID'#leave blank unless you use credentials ;'Database'='PubsOne';# fill this in please ;'server'='MyServer';# We need to know the server! ;'port' = $null; #Not normally needed with SQL Server. add if required #set to $null or leave it out if you want to let jdbc detect it 'Project'='Pubs';# the name of the project, needed for saving files 'Version'=''; # current version of database - # leave blank unless you know 'Checked'=$false; # has it been checked against a source directory? 'Problems'=@() 'Warnings'=@() } # some values, especially server names, have to be escaped when used in file paths. $Extras=$DatabaseDetails.GetEnumerator()|where {$_.Name -in ('server','Database','Project')}|foreach{ @{"Escaped$($_.Name)" = ($_.Value.Split([IO.Path]::GetInvalidFileNameChars()) -join '_')} } $Extras|foreach{$DatabaseDetails+=$_} # We've now added the 'escaped' versions of the server, database and project for use in filepaths. # add a bit of error-checking. Is the project directory there if (-not (Test-Path "$ProjectFolder")) { Write-warning "Sorry, but I couldn't find a project directory at the $ProjectFolder location"} # ...and is the script directory there? if (-not (Test-Path "$ProjectFolder\Scripts")) { Write-warning "Sorry, but I couldn't find a scripts directory at the $ProjectFolder\Scripts location"} #now find or elicit the passwords, and get the current version of the database $FetchAnyRequiredPasswords.Invoke($DatabaseDetails)# get password if necessary $GetCurrentVersion.Invoke($DatabaseDetails) #and the current version of the database if ($DatabaseDetails.Problems.Count -gt 0) { Write-Warning "We hit a problem getting the credentials: $( $DatabaseDetails.Problems|Foreach{$_.issues})" } <# now we format the Flyway parameters #> $MaybePort= "$(if ($DatabaseDetails.port -eq $null) {''} else {":$($DatabaseDetails.port)"})" if (-not ($DatabaseDetails.uid -in @($null,''))){ $FlyWayArgs = @("-url=jdbc:sqlserver://$($DatabaseDetails.Server)$maybePort;databaseName=$($DatabaseDetails.Database)", "-locations=filesystem:$ProjectFolder\Scripts", <# the migration folder #> "-user=$($DatabaseDetails.uid)", "-password=$($DatabaseDetails.pwd)") } else <# we need to use an integrated security flag in the connection string #> { $FlyWayArgs= @("-url=jdbc:sqlserver://$($DatabaseDetails.Server)$maybePort;databaseName=$( $DatabaseDetails.Database );integratedSecurity=true", "-locations=filesystem:$ProjectFolder\Scripts")<# the migration folder #> } $FlyWayArgs+= <# the project variables that we reference with placeholders #> @("-placeholders.projectDescription=$ProjectDescription", "-placeholders.projectName=$($databaseDetails.Project)", "-community") <# the project variables #> $WhatWasReturned=''; <# if we haven't checked the database to see if it really as at this version #> if (!($DatabaseDetails.Checked -eq $true)) { $IsDatabaseIdenticalToSource.Invoke($DatabaseDetails) $DatabaseDetails.Warnings|where Name -like 'IsDatabaseIdenticalToSource'| Foreach{$_.issues}|Foreach{write-warning $_} } <# the 'checked value can be set as $null if the version hasn't already got a script directory to check against (like an empty database) . We do the migration anyway, but nervously #> if ($DatabaseDetails.Checked -ne $false) {flyway migrate @FlyWayArgs '-target=latest'} <# now we need to be certain what version we managed to reach, and then check that we have scripted out the script folder and the build script #> # we first line the scripts up as we want to do them. Obviously we need to make sure wnat # version we have first. $Invocations=@($GetCurrentVersion,$CreateScriptFoldersIfNecessary,$CreateBuildScriptIfNecessary) #Now create an object-level script folder and a build script $Invocations|foreach{if ($DatabaseDetails.Problems.Count -eq 0){ $WhatWasReturned+= $_.Invoke($DatabaseDetails)}} if ($DatabaseDetails.Problems.Count -gt 0) { $ourProblems=$DatabaseDetails.Problems | foreach{$TheName=$_.name;$_.Issues}|foreach{"$($TheName): $_"} Write-warning "We hit a problem with $($ourProblems -join '-')" } if ($DatabaseDetails.Warnings.Count -gt 0) { $ourWarnings=$DatabaseDetails.Warnings | foreach{$TheName=$_.name;$_.Issues}|foreach{"$($TheName): $_"} Write-warning "When using $($ourWarnings -join '-')" } |
Listing 1
Running database build tasks interactively
As well as doing running these tasks in a scripted process, we can use the PowerShell IDE to walk through some of them interactively. In the PowerShell ISE, we can work interactively with these objects even when you need extra parameters.
First let’s clean up all existing objects in the database:
1 |
Flyway clean @FlyWayArgs # remove all objects from the database, including baselines |
Successfully dropped pre-schema database level objects (execution time 00:00.004s) Successfully cleaned schema [dbo] (execution time 00:06.020s) Successfully dropped post-schema database level objects (execution time 00:00.046s)
Now, we just migrate to a version …
1 |
Flyway migrate $FlyWayArgs '-target=1.1.5' |
Successfully applied 5 migrations to schema [dbo] (execution time 00:24.496s) Executing SQL callback: afterMigrate - Add Version EP WARNING: DB: Recording the database's version number - 1.1.5 (SQL State: S0001 - Error Code: 0)
…and we can see what happened:
1 |
flyway info @FlyWayArgs |
We can check out our routine to get the version from the live database, using the $GetCurrentVersion
task:
1 |
$GetCurrentVersion.Invoke($DatabaseDetails);$DatabaseDetails.Version |
PS C:\WINDOWS\system32> $GetCurrentVersion.Invoke($DatabaseDetails);$DatabaseDetails.Version 1.1.5
As you can see from the script, we can easily find out the version, and, if necessary, create both the build script and the object-level source folder, after we do a migration.
We can stack up our script blocks and execute them in order, checking for problems after each execution.
1 2 3 |
$Invocations=@($GetCurrentVersion,$CreateScriptFoldersIfNecessary,$CreateBuildScriptIfNecessary) #Now create an object-level script folder and a build script $Invocations|foreach{if ($DatabaseDetails.Problems.Count -eq 0){ $WhatWasReturned+= $_.Invoke($DatabaseDetails)}} |
If we hit a problem, the list of tasks stops executing. If we execute that successfully, we can see the result. We have a directory for the 1.1.5 version.
Inside this directory is a Scripts directory with our build script for that version…
And a source folder…
And in the Tables subfolder, we have our object scripts:
So, let’s take migrate the database to the next version:
1 2 3 4 |
#So let's migrate a version up to 1.1.6 Flyway migrate $FlyWayArgs '-target=1.1.6' #check that it worked flyway info @FlyWayArgs |
Now if we rerun out stack of script blocks…
1 2 3 |
$Invocations=@($GetCurrentVersion,$CreateScriptFoldersIfNecessary,$CreateBuildScriptIfNecessary) #Now create an object-level script folder and a build script $Invocations|foreach{if ($DatabaseDetails.Problems.Count -eq 0){ $WhatWasReturned+= $_.Invoke($DatabaseDetails)}} |
We now have a folder for each version:
Now we have all the various scripts in place.
We can compare script directories, build versions from scratch and have an object-level script for each version. We are likely to be able to satisfy almost any configuration-management person with our record-keeping.
With the object level scripts folder in place, there is no problem with interfacing with SQL Change Automation, as it and SQL Compare can use a source scripts folder as easily as an actual database. If you have a good reference version as a script, as would be the case if you created a database from scratch, then you can check a database that you need to upgrade to make sure that it hasn’t drifted by having un-versioned migrations applied directly.
Keeping a library of build and migration tasks
The script blocks that perform a range of tasks are designed to slot into a list like Lego blocks. To allow this, they take as a parameter a hash table. In this table are all the required inputs and some useful strings such as the name of the database that are used in file paths, with all ‘illegal’ characters replaced. To have outputs in the hash table is slightly odd, but useful. There are other ways of arranging this, but I like to keep things simple where possible.
All the scripts are kept in GitHub and you are welcome to submit pull requests.
Conclusions
Nowadays, it is good to work with a system like flyway that works across a whole range of databases. When using Oracle, PostgreSQL or SQL Server, it is a dish which is improved with a side-helping of other database tools, with a garnish of automation.
I’ll be continuing an ‘automation’ theme because it is important for me. Whereas the reader may be blessed with a virtuoso precision on the keyboard and the patience of a mediaeval monk, I’m not, so I automate development and maintenance chores. In subsequent articles, I’ll be demonstrating various ways of integrating other tools with Flyway. After getting the generic requirements out of the way with the features in Flyway Community, I’ll explain how Flyway Teams can take this to the next level, using PowerShell scripting in Callbacks.