Reporting on the Progress of a Flyway Database Development Project
This article demos a novel way to report on the progress of your Flyway development project. It provides both SQL and PowerShell versions of code that extracts information for each database version from the Flyway schema history table and then plots it in a Gantt chart.
It is tempting to get a bit vague when reporting on the progress of a database development project. In fact, though, I’m an enthusiast for copious reporting. It reveals, quickly, what is and isn’t going on for the team. If you make all the documentation available on an internal Wiki, then it also makes it more difficult for anyone in the deployment chain to use the hackneyed defense ‘You never told me that you were doing x‘ (where x can stand for almost every database change). As a bonus, I’ve always found that the quickest way to subdue senior managers is to drown them in a tsunami of reporting. It is the project leader’s equivalent of the Vulcan Nerve Grip.
When I say that I am a reporting enthusiast, I should add the rider ‘only if it can be automated‘. When required to write conventional documentation I am, like all developers, gripped with an extraordinary and inexplicable torpor, as if drifting off into semi-consciousness. It is only the idea that I can automate the task that gives me the necessary mental stamina to do it.
How to plot the progress of a Flyway project
How can Flyway help the busy database developer keep track of development progress? Well, it has the Flyway Schema History table. For each database managed by Flyway, this table keeps a record of what happened and when. If you maintain a strict one-to-one correspondence between the folder(s) of migration files and the branch database (e.g., Production, Develop, Bugfix, and so on) then you have, in the Flyway Schema History table, a complete history of changes and who made them.
We can extract this data and present it in a Gantt Chart, for example:
You will notice that I’ve plotted the time it took to develop and apply each migration. It is the elapsed time between the previous and current migration (i.e., the “end” time for V1.1.1 migration is the “start” time for the v1.1.2 migration, and so on).
I’ve also shown who installed each version. The name that appears as a resource (‘Phil Factor’ in my case) is taken from the installedBy
parameter, not the database user_ID
. Unfortunately, this project was done just by me so, we can’t do much with the ‘resources’ information. If you want to keep a check on resources, generally, you’ll need to be sure that each user has this value set appropriately, in the flyway.conf file within their user directory.
I produced this Gantt chart by generating it in SQL (I’ll show the code shortly). It extracts the data from the Flyway History Table and then transforms it into PlantUML (PUML) code. The resulting source (.puml) for the above Gantt chart is as follows:
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 |
@startgantt skinparam LegendBorderRoundCorner 2 skinparam LegendBorderThickness 1 skinparam LegendBorderColor silver skinparam LegendBackgroundColor white skinparam LegendFontSize 11 printscale weekly saturday are closed sunday are closed title Gantt Chart for version 1.1.11 legend top left Database: MyDatabase Server: MyServer RDBMS: sqlserver Flyway Version: 1.1.11 endlegend printscale weekly saturday are closed sunday are closed Project starts 2021-01-03 [1.1.1 - Initial Build] on {Phil Factor} starts 2021-01-03 [1.1.2 - Pubs Original Data] on {Phil Factor} starts 2021-01-05 [1.1.1 - Initial Build] ends 2021-01-05 [1.1.3 - UseNVarcharetc] on {Phil Factor} starts 2021-01-08 [1.1.2 - Pubs Original Data] ends 2021-01-08 [1.1.4 - RenameConstraintsAdd tables] on {Phil Factor} starts 2021-01-12 [1.1.3 - UseNVarcharetc] ends 2021-01-12 [1.1.5 - Add New Data] on {Phil Factor} starts 2021-01-20 [1.1.4 - RenameConstraintsAdd tables] ends 2021-01-20 [1.1.6 - Add Tags] on {Phil Factor} starts 2021-01-25 [1.1.5 - Add New Data] ends 2021-01-25 [1.1.7 - Add Indexes] on {Phil Factor} starts 2021-02-04 [1.1.6 - Add Tags] ends 2021-02-04 [1.1.8 - AddEditions] on {Phil Factor} starts 2021-02-15 [1.1.7 - Add Indexes] ends 2021-02-15 [1.1.9 - AddconditionalVersion] on {Phil Factor} starts 2021-03-01 [1.1.8 - AddEditions] ends 2021-03-01 [1.1.10 - AddAddressesPhonesEtc] on {Phil Factor} starts 2021-03-10 [1.1.9 - AddconditionalVersion] ends 2021-03-10 [1.1.11 - AddProcedureWithTest] on {Phil Factor} starts 2021-03-12 [1.1.10 - AddAddressesPhonesEtc] ends 2021-03-12 @endgantt |
It is best to review the Gantt Diagram page of the PlantUML site for the explanation of this script. They are adding useful extensions regularly.
Using this script as the source, we can then draw the chart using plantumlc.exe, or by using a PUML editor such as ATOM. There are online resources to help with the latter, but I just use plantumlc.exe, especially as this allows me to decide the format, such as PDF.
Generating the Gantt Chart
There are two main approaches to getting to this information and turning it into a PUML script like this: you can use the RDBMS-specific SQL to query the table, or you can use the slower RDBMS-independent approach of using the Flyway info
command to get the information (Flyway does additional checks) and presenting it in JSON form for processing.
Using SQL to generate the PUML
We’ll start with the RDBMS-specific way. This query is for SQL Server.
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 |
/* we read the Flyway Schema History into a table variable so we can then do a line--by-line select with a guarantee of doing it in the order of the primary key */ DECLARE @FlywaySchemaTable TABLE ([installed_rank] [INT] NOT NULL PRIMARY KEY, [version] [NVARCHAR](50) NULL, [description] [NVARCHAR](200) NULL, [installed_by] [NVARCHAR](100) NOT NULL, [installed_on] [DATETIME] NOT NULL) /* now read in the table */ INSERT INTO @FlywaySchemaTable (Installed_rank, version, installed_by, installed_on, description ) --I've added the placeholders in case you want to execute this in a callback SELECT fsh.installed_rank, version, installed_by, installed_on, description FROM dbo.flyway_schema_history FSH --FROM $(flyway:defaultSchema).$(flyway:TABLE) INNER JOIN (SELECT Max (installed_rank) AS installed_rank FROM dbo.flyway_schema_history --FROM $(flyway:defaultSchema).$(flyway:TABLE) WHERE success = 1 AND type = 'SQL' AND version IS NOT NULL GROUP BY version) f ON f.installed_rank = fSH.installed_rank ORDER BY fSH.installed_rank; /* now we calculate the version. This is slightly complicated by the possibility that you've done an UNDO. I've added the placeholders in case you want to execute this in a callback */ DECLARE @Version [NVARCHAR](50) = (SELECT TOP 1 [version] --we need to find the greatest successful version. FROM dbo.flyway_schema_history --FROM $(flyway:defaultSchema).$(flyway:TABLE) -- WHERE installed_rank = (SELECT Max (installed_rank) FROM PubsSix.dbo.flyway_schema_history --FROM $(flyway:defaultSchema).$(flyway:TABLE) WHERE success = 1)); DECLARE @PlantUMLCode NVARCHAR(MAX)='@startgantt skinparam LegendBorderRoundCorner 2 skinparam LegendBorderThickness 1 skinparam LegendBorderColor silver skinparam LegendBackgroundColor white skinparam LegendFontSize 11 printscale weekly saturday are closed sunday are closed title Gantt Chart for version '+@Version+' legend top left Database: '+Db_Name()+' Server: '+@@ServerName+' RDBMS: sqlserver Flyway Version: '+@Version+' endlegend printscale weekly saturday are closed sunday are closed ' DECLARE @PreviousDescription NVARCHAR(100) --used to temporarily hold the previous description SELECT @PlantUMLCode=@PlantUMLCode + CASE WHEN @PreviousDescription IS NULL THEN 'Project starts '+Convert(NCHAR(11),Convert(DATETIME2,Installed_on,112)) +' ' ELSE '' END+ '['+version+' - '+description+'] on {'+[installed_by]+'} starts '+ Convert(NCHAR(11),Convert(DATETIME2,Installed_on,112))+' ' + CASE WHEN @PreviousDescription IS NOT NULL THEN '['+@Previousdescription+'] ends '+Convert(NCHAR(11),Convert(DATETIME2,Installed_on,112))+' ' ELSE '' END, @PreviousDescription = version+' - '+description FROM @FlywaySchemaTable WHERE version IS NOT null SELECT @PlantUMLCode=@PlantUMLCode+'@endgantt' SELECT @PlantUMLCode |
This will produce the PUML script I showed earlier. You should use ‘Results to text’ (Ctrl T) in SSMS if you wish to generate the PUML directly from the query. For the sake of this article, you can then use the PlantUML online demo server to render it into the Gantt chart. Simply paste in the code and hit “Submit“.
Using PowerShell to generate the PUML
The alternative RDBMS-independent approach is to execute a Flyway Info
command that specifies JSON output. We can then use a script to create the PUML. In the interests of avoiding repetition, I’ll leave it to you as how you get those Flyway args into a hashtable but I’ve already covered this in several articles. Note that this code actually calls Flyway so using it in certain callbacks will cause blocking, but it is safe to do after the end of a ‘run’ (e.g. ‘afterVersioned’), because the transaction for the migration will have ended.
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 |
$report = Flyway info -outputType=json @FlywayArgs | convertFrom-json #get a JSON report of the history. if ($report.error -ne $null) #if an error was reported by Flyway { #if an error (usually bad parameters) error out. $report.error | foreach { Write-error "$($_.errorCode): $($_.message)" } } if ($report.allSchemasEmpty) #if it is an empty database { write-verbose "all schemas are empty. No version has been created here" } else { <# we first create the PlantUML Gantt header as a single string #> $PUML=@" @startgantt skinparam LegendBorderRoundCorner 2 skinparam LegendBorderThickness 1 skinparam LegendBorderColor silver skinparam LegendBackgroundColor white skinparam LegendFontSize 11 printscale weekly saturday are closed sunday are closed title Gantt Chart for version $($Report.schemaVersion) legend top left Database: $($Report.database) Server: $server RDBMS: $rdbms Flyway Version: $($Report.flywayVersion) endlegend "@ <# now we add to the header each line that represents a line in the gantt chart. To make things more interesting, we have to terminate the period of the previous line if there is one at the same time so that the period for the next version starts when the previous one ends #> $puml+= $Report.migrations| where {$_.version -ne '' -and $_.state -in @('Success','Future')}| group version | Sort-Object -Property @{Expression = {[version]$_.version}; Descending = $true}| %{$theGroup=$_; [PSCustomObject]@{ version = $_.name; installedBy=$_.Group.installedBy|select -First 1; description=$_.Group.description|select -First 1; installedOnUTC = $_.Group.installedOnUTC|sort-object|select -Last 1 }}|sort-object -Property installedOnUTC| foreach -Begin {$oldmigration=$null} -Process { #first, do a 'Project starts, taking it back to the start of the initial week. $migration=$_ if ($oldmigration -eq $null) { $StartDate=[datetime]$migration.installedOnUTC "Project starts $($StartDate.AddDays(1 - $StartDate.DayOfWeek.value__).ToString("yyyy-MM-dd")) "} "[$($migration.version) - $($migration.Description)] on {$($migration.installedBy)} starts $($migration.installedOnUTC.Remove(10)) " if ($oldmigration -ne $null) {"[$($oldmigration.version) - $($oldmigration.Description)] ends $($oldmigration.installedOnUTC.Remove(10)) "} $oldmigration=$_ } -End {'@endgantt'} #we end the chart with this # we create a filename to write the result into, and the same filename for the graphic $filename="$($env:tmp)\$($Report.database)-v$($Report.schemaVersion)--$(Get-random -maximum 99).puml" [IO.File]::WriteAllLines($filename, $puml) # It must be UTF8!!! <# I've chosen to output a svg image because it looks better in a website but you can use a number of different report types #> plantumlc -tsvg $filename } |
Conclusions
A Gantt chart showing the migration run, and who did what, is a good place to start with reporting. I like to add a report on what changed at every version, but I’ve already shown how to do that in Reporting on Changes Made by Flyway Migration Scripts. In another article, I’ll show how to embed this code in a PowerShell script and in a Callback for Flyway Teams.
PlantUML is useful because it is well-maintained by enthusiasts and does ER Diagramming, documenting JSON, and a whole range of other useful tasks as well as Gantt charts. I haven’t mentioned graphs. These are easily generated too and are very handy for the more complex database performance tuning work because it makes it much easier to detect performance changes that affect more than one metric. PlantUML doesn’t support this but there are many graphing packages that do.