├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── build ├── PackDbUp.cmd └── README.md └── src ├── dbup-cli.integration-tests ├── CockroachDbTests.cs ├── DockerBasedTest.cs ├── MySqlTests.cs ├── PostgreSqlTests.cs ├── README.md ├── Scripts │ ├── CockroachDb │ │ ├── EmptyScript │ │ │ ├── 001.sql │ │ │ └── dbup.yml │ │ ├── JournalTableScript │ │ │ ├── 001.sql │ │ │ └── dbup.yml │ │ └── Timeout │ │ │ ├── 001.sql │ │ │ └── dbup.yml │ ├── MySql │ │ ├── EmptyScript │ │ │ ├── 001.sql │ │ │ └── dbup.yml │ │ ├── JournalTableScript │ │ │ ├── 001.sql │ │ │ └── dbup.yml │ │ └── Timeout │ │ │ ├── 001.sql │ │ │ └── dbup.yml │ ├── PostgreSql │ │ ├── EmptyScript │ │ │ ├── 001.sql │ │ │ └── dbup.yml │ │ ├── JournalTableScript │ │ │ ├── 001.sql │ │ │ └── dbup.yml │ │ └── Timeout │ │ │ ├── 001.sql │ │ │ └── dbup.yml │ └── SqlServer │ │ ├── EmptyScript │ │ ├── 001.sql │ │ └── dbup.yml │ │ ├── JournalTableScript │ │ ├── 001.sql │ │ └── dbup.yml │ │ └── Timeout │ │ ├── 001.sql │ │ └── dbup.yml ├── SqlServerTests.cs └── dbup-cli.integration-tests.csproj ├── dbup-cli.sln ├── dbup-cli.tests.core ├── TestInfrastructure │ ├── CaptureLogsLogger.cs │ ├── CaptureLogsLoggerExtensions.cs │ ├── EmptyReader.cs │ ├── RecordingDataParameterCollection.cs │ ├── RecordingDbCommand.cs │ ├── RecordingDbConnection.cs │ ├── RecordingDbDataParameter.cs │ ├── RecordingDbTransaction.cs │ ├── ScriptReader.cs │ ├── Scrubbers.cs │ ├── SubstitutedConnectionConnectionManager.cs │ ├── TestConnectionManager.cs │ ├── TestDatabaseExtension.cs │ └── TestScriptProvider.cs └── dbup-cli.tests.core.csproj ├── dbup-cli.tests ├── ConfigLoaderTests.cs ├── ConfigurationHelperTests.cs ├── EnvVariableSubstitutionTests.cs ├── FilterTests.cs ├── NamingOptionsTests.cs ├── ScriptProviderHelperTests.cs ├── Scripts │ ├── Config │ │ ├── .env │ │ ├── DotEnv-CurrentFolder │ │ │ └── .env │ │ ├── DotEnv-VarsOverride │ │ │ ├── ConfigFolder │ │ │ │ ├── .env │ │ │ │ └── dotenv-vars.yml │ │ │ ├── CurrentFolder │ │ │ │ └── .env │ │ │ ├── file3.env │ │ │ └── file4.env │ │ ├── Encoding │ │ │ └── c001.sql │ │ ├── FilterRegex │ │ │ ├── d001.sql │ │ │ ├── d01.sql │ │ │ ├── d0a1.sql │ │ │ ├── d0aa1.sql │ │ │ └── d0b1.sql │ │ ├── FilterWildcard │ │ │ ├── c001.sql │ │ │ ├── c01.sql │ │ │ ├── c0a1.sql │ │ │ ├── c0aa1.sql │ │ │ └── c0b1.sql │ │ ├── FilterWithFullPath │ │ │ ├── e001.sql │ │ │ ├── e01.sql │ │ │ ├── e0a1.sql │ │ │ ├── e0aa1.sql │ │ │ └── e0b1.sql │ │ ├── MarkAsExecuted │ │ │ └── c001.sql │ │ ├── Naming │ │ │ └── SubFolder │ │ │ │ └── 001.sql │ │ ├── NoScripts │ │ │ └── FolderShouldExists.txt │ │ ├── OneScript │ │ │ └── c001.sql │ │ ├── Status │ │ │ ├── Scripts │ │ │ │ └── c001.sql │ │ │ ├── file1.env │ │ │ ├── file2.env │ │ │ └── status.yml │ │ ├── Vars │ │ │ └── c001.sql │ │ ├── c001.sql │ │ ├── disable-vars.yml │ │ ├── dotenv-vars.yml │ │ ├── encoding.yml │ │ ├── env-vars.yml │ │ ├── filter.yml │ │ ├── invalid-provider.yml │ │ ├── invalid-transaction.yml │ │ ├── invalid-vars.yml │ │ ├── journalTo-null.yml │ │ ├── journalTo.yml │ │ ├── log.yml │ │ ├── mark-as-executed.yml │ │ ├── min.yml │ │ ├── naming.yml │ │ ├── no-scripts.yml │ │ ├── no-vars.yml │ │ ├── noscripts.yml │ │ ├── onescript.yml │ │ ├── script.yml │ │ ├── syntax-error.yml │ │ ├── timeout.yml │ │ ├── tran.yml │ │ ├── varC.env │ │ ├── varD.env │ │ ├── vars.yml │ │ └── wrongversion.yml │ └── Default │ │ ├── 001.sql │ │ ├── 002.sql │ │ ├── SubFolder1 │ │ ├── 003.sql │ │ └── 004.sql │ │ └── SubFolder2 │ │ └── 005.sql ├── ToolEngineTests.cs ├── VariableSubstitutionTests.cs └── dbup-cli.tests.csproj └── dbup-cli ├── CliEnvironment.cs ├── CommandLineOptions ├── DropOptions.cs ├── InitOptions.cs ├── MarkAsExecutedOptions.cs ├── OptionsBase.cs ├── StatusOptions.cs ├── UpgradeOptions.cs └── VerbosityBase.cs ├── ConfigFile ├── ConfigFile.cs ├── Journal.cs ├── Migration.cs ├── NamingOptions.cs ├── Provider.cs ├── ScriptBatch.cs └── Transaction.cs ├── ConfigLoader.cs ├── ConfigurationHelper.cs ├── Constants.cs ├── DbUpCustomization ├── AzureSqlDatabaseWithIntegratedSecurity.cs ├── ConsoleLogger.cs ├── CustomFileSystemScriptOptions.cs └── CustomFileSystemScriptProvider.cs ├── DefaultOptions └── dbup.yml ├── Error.cs ├── How-to-add-a-new-db.md ├── How-to-create-a-new-release.md ├── IEnvironment.cs ├── Program.cs ├── README.md ├── ROADMAP.md ├── ResultBuilder.cs ├── ScriptProviderHelper.cs ├── StringUtils.cs ├── ToolEngine.cs └── dbup-cli.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | 332 | .vscode/ 333 | /build/*.exe 334 | /build/*.dll 335 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at drwatson1@yandex.ru . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sergey Tregub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DbUp Command Line Interface 2 | 3 | [![NuGet](https://img.shields.io/nuget/v/DbUp-CLI.svg)](https://www.nuget.org/packages/dbup-cli) 4 | 5 | This project is inspired and based on [DbUp](https://dbup.readthedocs.io/en/latest/) project. That is how its authors describe their project: 6 | 7 | > DbUp is a .NET library that helps you to deploy changes to SQL Server databases. It tracks which SQL scripts have been run already, and runs the change scripts that are needed to get your database up to date. [from official documentation](https://dbup.readthedocs.io/en/latest/) 8 | 9 | It does exactly that and does it pretty well, except that it supports not only SQL Server, but some other databases too. That is a great project that helps you a lot when you want to deploy database changes to your server, and you are a developer. Because it is merely a library and this is your responsibility to create an executable to run it. Of course, you can use PowerShell, but it is for fans only. Though it is pretty simple, however in every new project you have to create a new executable to deploy changes, and after the fifth project, it becomes a little annoying. 10 | 11 | However, what if you are not a developer, or you are a lazy developer (in a good sense) who doesn't want to do the same thing in every new project? You can use DbUp-CLI that is already do it. 12 | 13 | The tool has almost all the features the DbUp has, but without a single line of code, so I do not list them here, just the features of the tool itself. 14 | 15 | ## Features 16 | 17 | * Almost all of the DbUp features 18 | * Cross-platform (dotnet needed) 19 | * Easy to install - can be installed as a dotnet global tool 20 | * Minimum command line options 21 | * Uses a configuration file to store deploy options, so you can put it along with your SQL scripts under your favorite source control system 22 | * Uses YAML format for a configuration file to improve readability 23 | * Quick start: 24 | * Creates a configuration file with default options for you 25 | * Default configuration is suitable for the most cases, so you should set only a connection string to your database to run the first migration 26 | * The configuration file contains all options with default values and a brief explanation 27 | 28 | ## Documentation 29 | 30 | * [Installation](https://github.com/drwatson1/dbup-cli/wiki/Home#installation) 31 | * [Getting Started](https://github.com/drwatson1/dbup-cli/wiki/Home#getting-started) 32 | * [Supported DB Providers](https://github.com/drwatson1/dbup-cli/Home#supported-db-providers) 33 | * [Configuration File](https://github.com/drwatson1/dbup-cli/wiki/Home#configuration-file) 34 | * [Required Options](https://github.com/drwatson1/dbup-cli/wiki/Home#required-options) 35 | * [Transaction Related Options](https://github.com/drwatson1/dbup-cli/wiki/Home#transaction-related-options) 36 | * [Logging Options](https://github.com/drwatson1/dbup-cli/wiki/Home#logging-options) 37 | * [Script Selection](https://github.com/drwatson1/dbup-cli/wiki/Home#script-selection) 38 | * [Folders](https://github.com/drwatson1/dbup-cli/wiki/Home#folders) 39 | * [Scripts order](https://github.com/drwatson1/dbup-cli/wiki/Home#scripts-order) 40 | * [Filter](https://github.com/drwatson1/dbup-cli/wiki/Home#filter) 41 | * [Always Executed Scripts](https://github.com/drwatson1/dbup-cli/wiki/Home#always-executed-scripts) 42 | * [Encoding](https://github.com/drwatson1/dbup-cli/wiki/Home#encoding) 43 | * [Naming](https://github.com/drwatson1/dbup-cli/wiki#naming) 44 | * [Variables in the Scripts](https://github.com/drwatson1/dbup-cli/wiki/Home#variables-in-the-scripts) 45 | * [Environment Variables](https://github.com/drwatson1/dbup-cli/wiki/Home#environment-variables) 46 | * [Using .env Files](https://github.com/drwatson1/dbup-cli/wiki/Home#using-env-files) 47 | * [Custom journal table name](https://github.com/drwatson1/dbup-cli/wiki/Home#custom-journal-table-name) 48 | * [Command Line Options Reference](https://github.com/drwatson1/dbup-cli/wiki/Command-Line-Options) 49 | * [Original DbUp Documentation](https://dbup.readthedocs.io/en/latest/) 50 | 51 | ## Supported Databases 52 | 53 | * MS SQL Server 54 | * AzureSQL 55 | * PostgreSQL 56 | * MySQL 57 | * CockroachDB (by @lbguilherme) 58 | 59 | ## Release Notes 60 | 61 | |Date|Version|Description| 62 | |-|-|-| 63 | |2023-06-12|1.8.1|Improve error reporting 64 | |2023-01-18|1.8.0|Add support of .Net 7.0 65 | |2022-06-11|1.7.0|Add support of CockroachDB, thanks to @lbguilherme 66 | |2022-05-10|1.6.6|Add support of .Net 6 67 | |2022-02-14|1.6.5|Support of DisableVars 68 | |2022-02-06|1.6.4|Support of drop and ensure for Azure SQL 69 | |2022-02-02|1.6.3|Support of AzureSQL integrated security 70 | |2022-01-30|1.6.2|PostgreSQL SCRAM authentication support interim fix 71 | |2022-01-29|1.6.1|BUGFIX: 'version' and '--version' should return exit code 0 72 | |2021-10-03|1.6.0|Add a 'journalTo' option to dbup.yml 73 | |2021-03-28|1.5.0|Add support of .Net Core 3.1 and .Net 5.0 74 | |2021-03-27|1.4.0|Add script naming options
Load .env.local after .env 75 | |2020-05-30|1.3.0|Support of MySQL, improve stability of integration tests 76 | |2020-03-20|1.2.0|Add a connectionTimeoutSec option 77 | |2019-08-27|1.1.2|Minor fixes 78 | |2019-04-11|1.1.0|PostgreSQL support 79 | |2019-03-25|1.0.1|Initial version (DbUp 4.2) 80 | -------------------------------------------------------------------------------- /build/PackDbUp.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | SET BINDIR=..\src\dbup-cli\bin\Release\net462 4 | 5 | ILMerge.exe %BINDIR%\dbup-cli.exe /ndebug /out:dbup-cli.exe ^ 6 | %BINDIR%\CommandLine.dll ^ 7 | %BINDIR%\dbup-core.dll ^ 8 | %BINDIR%\dbup-mysql.dll ^ 9 | %BINDIR%\dbup-postgresql.dll ^ 10 | %BINDIR%\dbup-sqlserver.dll ^ 11 | %BINDIR%\dbup-cockroachdb.dll ^ 12 | %BINDIR%\DotNetEnv.dll ^ 13 | %BINDIR%\Microsoft.Azure.Services.AppAuthentication.dll ^ 14 | %BINDIR%\Microsoft.IdentityModel.Clients.ActiveDirectory.dll ^ 15 | %BINDIR%\Microsoft.Win32.Primitives.dll ^ 16 | %BINDIR%\MySql.Data.dll ^ 17 | %BINDIR%\Npgsql.dll ^ 18 | %BINDIR%\Optional.dll ^ 19 | %BINDIR%\Sprache.dll ^ 20 | %BINDIR%\System.AppContext.dll ^ 21 | %BINDIR%\System.Console.dll ^ 22 | %BINDIR%\System.Diagnostics.DiagnosticSource.dll ^ 23 | %BINDIR%\System.Diagnostics.Tracing.dll ^ 24 | %BINDIR%\System.Globalization.Calendars.dll ^ 25 | %BINDIR%\System.IO.Compression.dll ^ 26 | %BINDIR%\System.IO.Compression.ZipFile.dll ^ 27 | %BINDIR%\System.IO.dll ^ 28 | %BINDIR%\System.IO.FileSystem.dll ^ 29 | %BINDIR%\System.IO.FileSystem.Primitives.dll ^ 30 | %BINDIR%\System.Net.Http.dll ^ 31 | %BINDIR%\System.Net.Sockets.dll ^ 32 | %BINDIR%\System.Reflection.dll ^ 33 | %BINDIR%\System.Runtime.CompilerServices.Unsafe.dll ^ 34 | %BINDIR%\System.Runtime.dll ^ 35 | %BINDIR%\System.Runtime.Extensions.dll ^ 36 | %BINDIR%\System.Runtime.InteropServices.dll ^ 37 | %BINDIR%\System.Runtime.InteropServices.RuntimeInformation.dll ^ 38 | %BINDIR%\System.Security.Cryptography.Algorithms.dll ^ 39 | %BINDIR%\System.Security.Cryptography.Encoding.dll ^ 40 | %BINDIR%\System.Security.Cryptography.Primitives.dll ^ 41 | %BINDIR%\System.Security.Cryptography.X509Certificates.dll ^ 42 | %BINDIR%\System.Text.Encoding.CodePages.dll ^ 43 | %BINDIR%\System.Threading.Tasks.Extensions.dll ^ 44 | %BINDIR%\System.Xml.ReaderWriter.dll ^ 45 | %BINDIR%\YamlDotNet.dll 46 | -------------------------------------------------------------------------------- /build/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Pack the .NET Framework executable into one exe file 3 | 4 | You will need `ILMerge.exe` and `System.Compiler.dll` in this directory. 5 | Download them [here](https://www.nuget.org/packages/ilmerge/) and unpack from `tools\net452`. 6 | 7 | Then, run `PackDbUp.cmd` and you get a single self-contained `dbup-cli.exe`. 8 | 9 | Note that the script may need upgrading after a code change 10 | as the list of the dependent dlls is hardcoded in the script. 11 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/CockroachDbTests.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Cli.Tests.TestInfrastructure; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.IO; 5 | using System.Reflection; 6 | using FluentAssertions; 7 | using System.Data.SqlClient; 8 | using System.Collections.Generic; 9 | using System.Threading.Tasks; 10 | using Npgsql; 11 | using System.Data.Common; 12 | 13 | namespace DbUp.Cli.IntegrationTests 14 | { 15 | [TestClass] 16 | public class CocroachDbTests : DockerBasedTest 17 | { 18 | readonly CaptureLogsLogger Logger; 19 | readonly IEnvironment Env; 20 | 21 | public CocroachDbTests() 22 | { 23 | Env = new CliEnvironment(); 24 | Logger = new CaptureLogsLogger(); 25 | 26 | Environment.SetEnvironmentVariable("CONNSTR", "Host=127.0.0.1;Port=26257;SSL Mode=Disable;Database=dbup;Username=root"); 27 | } 28 | 29 | string GetBasePath(string subPath = "EmptyScript") => 30 | Path.Combine(Assembly.GetExecutingAssembly().Location, $@"..\Scripts\CockroachDb\{subPath}"); 31 | 32 | string GetConfigPath(string name = "dbup.yml", string subPath = "EmptyScript") => new DirectoryInfo(Path.Combine(GetBasePath(subPath), name)).FullName; 33 | 34 | Func CreateConnection = () => new NpgsqlConnection("Host=127.0.0.1;Port=26257;SSL Mode=Disable;Database=defaultdb;Username=root"); 35 | 36 | [TestInitialize] 37 | public Task TestInitialize() 38 | { 39 | /* 40 | * Before the first run, download the image: 41 | * docker pull cockroachdb/cockroach:v22.1.1 42 | * */ 43 | return DockerInitialize( 44 | "cockroachdb/cockroach:v22.1.1", 45 | new List() { }, 46 | new List() { "start-single-node", "--insecure" }, 47 | "26257", 48 | CreateConnection 49 | ); 50 | } 51 | 52 | [TestCleanup] 53 | public Task TestCleanup() 54 | { 55 | return DockerCleanup(CreateConnection, con => new NpgsqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", con as NpgsqlConnection)); 56 | } 57 | 58 | [TestMethod] 59 | public void Ensure_CreateANewDb() 60 | { 61 | var engine = new ToolEngine(Env, Logger); 62 | 63 | var result = engine.Run("upgrade", "--ensure", GetConfigPath()); 64 | result.Should().Be(0); 65 | 66 | using (var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 67 | using (var command = new NpgsqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", connection)) 68 | { 69 | connection.Open(); 70 | var count = command.ExecuteScalar(); 71 | 72 | count.Should().Be(1); 73 | } 74 | } 75 | 76 | /* 77 | // Drop database does not supported for PostgreSQL by DbUp 78 | [TestMethod] 79 | public void Drop_DropADb() 80 | { 81 | var engine = new ToolEngine(Env, Logger); 82 | 83 | engine.Run("upgrade", "--ensure", GetConfigPath()); 84 | var result = engine.Run("drop", GetConfigPath()); 85 | result.Should().Be(0); 86 | using (var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 87 | using (var command = new NpgsqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", connection)) 88 | { 89 | Action a = () => connection.Open(); 90 | a.Should().Throw("Database DbUp should not exist"); 91 | } 92 | } 93 | */ 94 | 95 | [TestMethod] 96 | public void DatabaseShouldNotExistBeforeTestRun() 97 | { 98 | using (var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 99 | using (var command = new NpgsqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", connection)) 100 | { 101 | Action a = () => { connection.Open(); command.ExecuteScalar(); }; 102 | a.Should().Throw("Database DbUp should not exist"); 103 | } 104 | } 105 | 106 | [TestMethod] 107 | public void UpgradeCommand_ShouldUseConnectionTimeoutForLongrunningQueries() 108 | { 109 | var engine = new ToolEngine(Env, Logger); 110 | 111 | var r = engine.Run("upgrade", "--ensure", GetConfigPath("dbup.yml", "Timeout")); 112 | r.Should().Be(1); 113 | } 114 | 115 | [TestMethod] 116 | public void UpgradeCommand_ShouldUseASpecifiedJournal() 117 | { 118 | var engine = new ToolEngine(Env, Logger); 119 | 120 | var result = engine.Run("upgrade", "--ensure", GetConfigPath("dbup.yml", "JournalTableScript")); 121 | result.Should().Be(0); 122 | 123 | using (var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 124 | using (var command = new NpgsqlCommand("select count(*) from public.journal where scriptname = '001.sql'", connection)) 125 | { 126 | connection.Open(); 127 | var count = command.ExecuteScalar(); 128 | 129 | count.Should().Be(1); 130 | } 131 | } 132 | 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/DockerBasedTest.cs: -------------------------------------------------------------------------------- 1 | using Docker.DotNet; 2 | using Docker.DotNet.Models; 3 | using FakeItEasy; 4 | using FluentAssertions; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Data.Common; 9 | using System.Threading.Tasks; 10 | 11 | namespace DbUp.Cli.IntegrationTests 12 | { 13 | public class DockerBasedTest 14 | { 15 | private const string DockerEngineUri = "npipe://./pipe/docker_engine"; 16 | private const string HostIp = "127.0.0.1"; 17 | 18 | DockerClient DockerClient; 19 | string ContainerId; 20 | 21 | protected Task DockerInitialize(string imageName, List environmentVariables, string port, Func createConnection) 22 | { 23 | return DockerInitialize(imageName, environmentVariables, new List(), port, createConnection); 24 | } 25 | 26 | protected async Task DockerInitialize(string imageName, List environmentVariables, List cmd, string port, Func createConnection) 27 | { 28 | DockerClient = new DockerClientConfiguration(new Uri(DockerEngineUri)).CreateClient(); 29 | var pars = new CreateContainerParameters(new Config() 30 | { 31 | Image = imageName, 32 | ExposedPorts = new Dictionary() 33 | { 34 | { port, new EmptyStruct() } 35 | }, 36 | Env = environmentVariables, 37 | NetworkDisabled = false, 38 | Cmd = cmd 39 | }); 40 | 41 | pars.HostConfig = new HostConfig() 42 | { 43 | AutoRemove = true, 44 | PortBindings = new Dictionary>() 45 | { 46 | { port, new List { new PortBinding() { HostPort = port, HostIP = HostIp } } } 47 | } 48 | }; 49 | 50 | try 51 | { 52 | await DockerClient.Images.CreateImageAsync( 53 | new ImagesCreateParameters 54 | { 55 | FromImage = imageName 56 | }, 57 | null, A.Fake>()); 58 | 59 | var cont = await DockerClient.Containers.CreateContainerAsync(pars); 60 | ContainerId = cont.ID; 61 | var res = await DockerClient.Containers.StartContainerAsync(ContainerId, new ContainerStartParameters()); 62 | res.Should().BeTrue(); 63 | } 64 | catch (Exception ex) 65 | { 66 | Assert.Fail(ex.Message); 67 | } 68 | 69 | var started = DateTime.Now; 70 | var connected = false; 71 | while (DateTime.Now - started < TimeSpan.FromMinutes(1)) 72 | { 73 | using (var connection = createConnection()) 74 | { 75 | try 76 | { 77 | connection.Open(); 78 | connected = true; 79 | break; 80 | } 81 | catch 82 | { 83 | await Task.Delay(1000); 84 | continue; 85 | } 86 | } 87 | } 88 | 89 | connected.Should().BeTrue("Server should be awailable to connect"); 90 | } 91 | 92 | protected async Task DockerCleanup(Func createConnection, Func createCommand) 93 | { 94 | await DockerClient.Containers.StopContainerAsync(ContainerId, new ContainerStopParameters() { WaitBeforeKillSeconds = 1 }); 95 | 96 | var started = DateTime.Now; 97 | while (DateTime.Now - started < TimeSpan.FromMinutes(2)) 98 | { 99 | using (var connection = createConnection()) 100 | using (var command = createCommand(connection)) 101 | { 102 | try 103 | { 104 | await connection.OpenAsync(); 105 | await command.ExecuteScalarAsync(); 106 | await Task.Delay(1000); 107 | } 108 | catch 109 | { 110 | await Task.Delay(1000); 111 | return; 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/MySqlTests.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Cli.Tests.TestInfrastructure; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.IO; 5 | using System.Reflection; 6 | using FluentAssertions; 7 | using System.Data.SqlClient; 8 | using System.Collections.Generic; 9 | using System.Threading.Tasks; 10 | using System.Data.Common; 11 | using MySql; 12 | using MySql.Data.MySqlClient; 13 | 14 | namespace DbUp.Cli.IntegrationTests 15 | { 16 | [TestClass] 17 | public class MySqlTests : DockerBasedTest 18 | { 19 | readonly CaptureLogsLogger Logger; 20 | readonly IEnvironment Env; 21 | 22 | readonly static string Pwd = "MyPwd2020"; 23 | readonly static string DbName = "DbUp"; 24 | 25 | public MySqlTests() 26 | { 27 | Env = new CliEnvironment(); 28 | Logger = new CaptureLogsLogger(); 29 | 30 | Environment.SetEnvironmentVariable("CONNSTR", $"Server=127.0.0.1;Database={DbName};Uid=root;Pwd={Pwd};"); 31 | } 32 | 33 | string GetBasePath(string subPath = "EmptyScript") 34 | => Path.Combine(Assembly.GetExecutingAssembly().Location, $@"..\Scripts\MySQL\{subPath}"); 35 | 36 | string GetConfigPath(string name = "dbup.yml", string subPath = "EmptyScript") 37 | => new DirectoryInfo(Path.Combine(GetBasePath(subPath), name)).FullName; 38 | 39 | Func CreateConnection = () 40 | => new MySqlConnection($"Server=127.0.0.1;Uid=root;Pwd={Pwd};"); 41 | 42 | [TestInitialize] 43 | public async Task TestInitialize() 44 | { 45 | /* 46 | * Before the first run, download the image: 47 | * docker pull mysql:8.0.20 48 | * */ 49 | 50 | await DockerInitialize( 51 | "mysql:8.0.20", 52 | new List() 53 | { 54 | $"MYSQL_ROOT_PASSWORD={Pwd}" 55 | }, 56 | "3306", 57 | CreateConnection 58 | ); 59 | } 60 | 61 | [TestCleanup] 62 | public async Task TestCleanup() 63 | { 64 | await DockerCleanup(CreateConnection, con => new MySqlCommand("select count(*) from schemaversions where scriptname = '001.sql'", con as MySqlConnection)); 65 | } 66 | 67 | [TestMethod] 68 | public void Ensure_CreateANewDb() 69 | { 70 | var engine = new ToolEngine(Env, Logger); 71 | 72 | var result = engine.Run("upgrade", "--ensure", GetConfigPath()); 73 | result.Should().Be(0); 74 | 75 | using (var connection = new MySqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 76 | using (var command = new MySqlCommand("select count(*) from schemaversions where scriptname = '001.sql'", connection)) 77 | { 78 | connection.Open(); 79 | var count = command.ExecuteScalar(); 80 | 81 | count.Should().Be(1); 82 | } 83 | } 84 | 85 | /* 86 | * // Don't supported 87 | [TestMethod] 88 | public void Drop_DropADb() 89 | { 90 | var engine = new ToolEngine(Env, Logger); 91 | 92 | engine.Run("upgrade", "--ensure", GetConfigPath()); 93 | var result = engine.Run("drop", GetConfigPath()); 94 | result.Should().Be(0); 95 | using (var connection = new MySqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 96 | using (var command = new MySqlCommand("select count(*) from schemaversions where scriptname = '001.sql'", connection)) 97 | { 98 | Action a = () => connection.Open(); 99 | a.Should().Throw($"Database {DbName} should not exist"); 100 | } 101 | } 102 | */ 103 | 104 | [TestMethod] 105 | public void DatabaseShouldNotExistBeforeTestRun() 106 | { 107 | using (var connection = new MySqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 108 | using (var command = new MySqlCommand("select count(*) from schemaversions where scriptname = '001.sql'", connection)) 109 | { 110 | Action a = () => connection.Open(); 111 | a.Should().Throw($"Database {DbName} should not exist"); 112 | } 113 | } 114 | 115 | [TestMethod] 116 | public void UpgradeCommand_ShouldUseConnectionTimeoutForLongrunningQueries() 117 | { 118 | var engine = new ToolEngine(Env, Logger); 119 | 120 | var r = engine.Run("upgrade", "--ensure", GetConfigPath("dbup.yml", "Timeout")); 121 | r.Should().Be(1); 122 | } 123 | 124 | [TestMethod] 125 | public void UpgradeCommand_ShouldUseASpecifiedJournal() 126 | { 127 | var engine = new ToolEngine(Env, Logger); 128 | 129 | var result = engine.Run("upgrade", "--ensure", GetConfigPath("dbup.yml", "JournalTableScript")); 130 | result.Should().Be(0); 131 | 132 | using (var connection = new MySqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 133 | using (var command = new MySqlCommand("select count(*) from DbUp.testTable where scriptname = '001.sql'", connection)) 134 | { 135 | connection.Open(); 136 | var count = command.ExecuteScalar(); 137 | 138 | count.Should().Be(1); 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/PostgreSqlTests.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Cli.Tests.TestInfrastructure; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.IO; 5 | using System.Reflection; 6 | using FluentAssertions; 7 | using System.Data.SqlClient; 8 | using System.Collections.Generic; 9 | using System.Threading.Tasks; 10 | using Npgsql; 11 | using System.Data.Common; 12 | 13 | namespace DbUp.Cli.IntegrationTests 14 | { 15 | [TestClass] 16 | public class PostgreSqlTests : DockerBasedTest 17 | { 18 | readonly CaptureLogsLogger Logger; 19 | readonly IEnvironment Env; 20 | 21 | public PostgreSqlTests() 22 | { 23 | Env = new CliEnvironment(); 24 | Logger = new CaptureLogsLogger(); 25 | 26 | Environment.SetEnvironmentVariable("CONNSTR", "Host=127.0.0.1;Database=dbup;Username=postgres;Password=PostgresPwd2019;Port=5432;Timeout=60;CommandTimeout=60"); 27 | } 28 | 29 | string GetBasePath(string subPath = "EmptyScript") => 30 | Path.Combine(Assembly.GetExecutingAssembly().Location, $@"..\Scripts\PostgreSql\{subPath}"); 31 | 32 | string GetConfigPath(string name = "dbup.yml", string subPath = "EmptyScript") => new DirectoryInfo(Path.Combine(GetBasePath(subPath), name)).FullName; 33 | 34 | Func CreateConnection = () => new NpgsqlConnection("Host=127.0.0.1;Database=postgres;Username=postgres;Password=PostgresPwd2019;Port=5432;Timeout=60;CommandTimeout=60"); 35 | 36 | [TestInitialize] 37 | public Task TestInitialize() 38 | { 39 | /* 40 | * Before the first run, download the image: 41 | * docker pull postgres:11.2 42 | * */ 43 | 44 | return DockerInitialize( 45 | "postgres:11.2", 46 | new List() 47 | { 48 | "POSTGRES_PASSWORD=PostgresPwd2019" 49 | }, 50 | "5432", 51 | CreateConnection 52 | ); 53 | } 54 | 55 | [TestCleanup] 56 | public Task TestCleanup() 57 | { 58 | return DockerCleanup(CreateConnection, con => new NpgsqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", con as NpgsqlConnection)); 59 | } 60 | 61 | [TestMethod] 62 | public void Ensure_CreateANewDb() 63 | { 64 | var engine = new ToolEngine(Env, Logger); 65 | 66 | var result = engine.Run("upgrade", "--ensure", GetConfigPath()); 67 | result.Should().Be(0); 68 | 69 | using (var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 70 | using (var command = new NpgsqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", connection)) 71 | { 72 | connection.Open(); 73 | var count = command.ExecuteScalar(); 74 | 75 | count.Should().Be(1); 76 | } 77 | } 78 | 79 | /* 80 | // Drop database does not supported for PostgreSQL by DbUp 81 | [TestMethod] 82 | public void Drop_DropADb() 83 | { 84 | var engine = new ToolEngine(Env, Logger); 85 | 86 | engine.Run("upgrade", "--ensure", GetConfigPath()); 87 | var result = engine.Run("drop", GetConfigPath()); 88 | result.Should().Be(0); 89 | using (var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 90 | using (var command = new NpgsqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", connection)) 91 | { 92 | Action a = () => connection.Open(); 93 | a.Should().Throw("Database DbUp should not exist"); 94 | } 95 | } 96 | */ 97 | 98 | [TestMethod] 99 | public void DatabaseShouldNotExistBeforeTestRun() 100 | { 101 | using (var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 102 | using (var command = new NpgsqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", connection)) 103 | { 104 | Action a = () => { connection.Open(); command.ExecuteScalar(); }; 105 | a.Should().Throw("Database DbUp should not exist"); 106 | } 107 | } 108 | 109 | [TestMethod] 110 | public void UpgradeCommand_ShouldUseConnectionTimeoutForLongrunningQueries() 111 | { 112 | var engine = new ToolEngine(Env, Logger); 113 | 114 | var r = engine.Run("upgrade", "--ensure", GetConfigPath("dbup.yml", "Timeout")); 115 | r.Should().Be(1); 116 | } 117 | 118 | [TestMethod] 119 | public void UpgradeCommand_ShouldUseASpecifiedJournal() 120 | { 121 | var engine = new ToolEngine(Env, Logger); 122 | 123 | var result = engine.Run("upgrade", "--ensure", GetConfigPath("dbup.yml", "JournalTableScript")); 124 | result.Should().Be(0); 125 | 126 | using (var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 127 | using (var command = new NpgsqlCommand("select count(*) from public.journal where scriptname = '001.sql'", connection)) 128 | { 129 | connection.Open(); 130 | var count = command.ExecuteScalar(); 131 | 132 | count.Should().Be(1); 133 | } 134 | } 135 | 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/README.md: -------------------------------------------------------------------------------- 1 | # Integration tests 2 | 3 | These integration tests are intended to ensure that the tool can interact with the real databases. Technically, it uses the Docker engine to run Databases, so you should install the Docker first. On Windows, I recommend using the Docker Desktop over the WSL 2. You can find a manual in the [Docker Docuntation](https://docs.docker.com/docker-for-windows/wsl/). 4 | 5 | Each test creates and runs a container with a database engine, then runs test, stops and remove the container. So, before running a test, you should download a container manually. For example: 6 | 7 | ``` 8 | docker pull mcr.microsoft.com/mssql/server:2017-CU12-ubuntu 9 | ``` 10 | 11 | You can find an appropriate command in the corresponding test code. 12 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/CockroachDb/EmptyScript/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/CockroachDb/EmptyScript/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: CockroachDb # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/CockroachDb/JournalTableScript/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/CockroachDb/JournalTableScript/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: CockroachDb # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | connectionTimeoutSec: 60 6 | journalTo: 7 | schema: public 8 | table: journal 9 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/CockroachDb/Timeout/001.sql: -------------------------------------------------------------------------------- 1 | SELECT pg_sleep(60); 2 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/CockroachDb/Timeout/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: CockroachDb # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | connectionTimeoutSec: 10 # Connection timeout in seconds 6 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/MySql/EmptyScript/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/MySql/EmptyScript/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: mysql # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/MySql/JournalTableScript/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/MySql/JournalTableScript/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: mysql # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | journalTo: 6 | schema: "DbUp" 7 | table: "testTable" -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/MySql/Timeout/001.sql: -------------------------------------------------------------------------------- 1 | SELECT NOW() 'BEFORE WAIT'; 2 | SELECT SLEEP (60); 3 | SELECT NOW() 'AFTER WAIT'; 4 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/MySql/Timeout/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: mysql # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | connectionTimeoutSec: 10 # Connection timeout in seconds -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/PostgreSql/EmptyScript/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/PostgreSql/EmptyScript/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: postgresql # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/PostgreSql/JournalTableScript/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/PostgreSql/JournalTableScript/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: postgresql # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | connectionTimeoutSec: 60 6 | journalTo: 7 | schema: public 8 | table: journal 9 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/PostgreSql/Timeout/001.sql: -------------------------------------------------------------------------------- 1 | SELECT pg_sleep(60); 2 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/PostgreSql/Timeout/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: postgresql # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | connectionTimeoutSec: 10 # Connection timeout in seconds 6 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/SqlServer/EmptyScript/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/SqlServer/EmptyScript/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: sqlserver # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/SqlServer/JournalTableScript/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/SqlServer/JournalTableScript/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: sqlserver # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | journalTo: 6 | schema: "dbo" 7 | table: "testTable" 8 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/SqlServer/Timeout/001.sql: -------------------------------------------------------------------------------- 1 | SELECT GETDATE() 'BEFORE WAIT' 2 | WAITFOR DELAY '00:01:00.000' 3 | SELECT GETDATE() 'AFTER WAIT' -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/Scripts/SqlServer/Timeout/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: sqlserver # DB provider: sqlserver 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\dbup;Initial Catalog=MyDb;Integrated Security=True" for sqlserver 5 | connectionTimeoutSec: 10 # Connection timeout in seconds -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/SqlServerTests.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Cli.Tests.TestInfrastructure; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.IO; 5 | using System.Reflection; 6 | using FluentAssertions; 7 | using System.Data.SqlClient; 8 | using System.Collections.Generic; 9 | using System.Threading.Tasks; 10 | using System.Data.Common; 11 | 12 | namespace DbUp.Cli.IntegrationTests 13 | { 14 | [TestClass] 15 | public class SqlServerTests : DockerBasedTest 16 | { 17 | readonly CaptureLogsLogger Logger; 18 | readonly IEnvironment Env; 19 | 20 | public SqlServerTests() 21 | { 22 | Env = new CliEnvironment(); 23 | Logger = new CaptureLogsLogger(); 24 | 25 | Environment.SetEnvironmentVariable("CONNSTR", "Data Source=127.0.0.1;Initial Catalog=DbUp;Persist Security Info=True;User ID=sa;Password=SaPwd2017"); 26 | } 27 | 28 | string GetBasePath(string subPath = "EmptyScript") 29 | => Path.Combine(Assembly.GetExecutingAssembly().Location, $@"..\Scripts\SqlServer\{subPath}"); 30 | 31 | string GetConfigPath(string name = "dbup.yml", string subPath = "EmptyScript") 32 | => new DirectoryInfo(Path.Combine(GetBasePath(subPath), name)).FullName; 33 | 34 | Func CreateConnection = () 35 | => new SqlConnection("Data Source=127.0.0.1;Persist Security Info=True;User ID=sa;Password=SaPwd2017"); 36 | 37 | [TestInitialize] 38 | public async Task TestInitialize() 39 | { 40 | /* 41 | * Before the first run, download the image: 42 | * docker pull mcr.microsoft.com/mssql/server:2017-CU12-ubuntu 43 | * */ 44 | 45 | await DockerInitialize( 46 | "mcr.microsoft.com/mssql/server:2017-CU12-ubuntu", 47 | new List() 48 | { 49 | "ACCEPT_EULA=Y", 50 | "SA_PASSWORD=SaPwd2017" 51 | }, 52 | "1433", 53 | CreateConnection 54 | ); 55 | } 56 | 57 | [TestCleanup] 58 | public async Task TestCleanup() 59 | { 60 | await DockerCleanup(CreateConnection, con => new SqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", con as SqlConnection)); 61 | } 62 | 63 | [TestMethod] 64 | public void Ensure_CreateANewDb() 65 | { 66 | var engine = new ToolEngine(Env, Logger); 67 | 68 | var result = engine.Run("upgrade", "--ensure", GetConfigPath()); 69 | result.Should().Be(0); 70 | 71 | using (var connection = new SqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 72 | using (var command = new SqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", connection)) 73 | { 74 | connection.Open(); 75 | var count = command.ExecuteScalar(); 76 | 77 | count.Should().Be(1); 78 | } 79 | } 80 | 81 | [TestMethod] 82 | public void Drop_DropADb() 83 | { 84 | var engine = new ToolEngine(Env, Logger); 85 | 86 | engine.Run("upgrade", "--ensure", GetConfigPath()); 87 | var result = engine.Run("drop", GetConfigPath()); 88 | result.Should().Be(0); 89 | using (var connection = new SqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 90 | using (var command = new SqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", connection)) 91 | { 92 | Action a = () => connection.Open(); 93 | a.Should().Throw("Database DbUp should not exist"); 94 | } 95 | } 96 | 97 | [TestMethod] 98 | public void DatabaseShouldNotExistBeforeTestRun() 99 | { 100 | using (var connection = new SqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 101 | using (var command = new SqlCommand("select count(*) from SchemaVersions where scriptname = '001.sql'", connection)) 102 | { 103 | Action a = () => connection.Open(); 104 | a.Should().Throw("Database DbUp should not exist"); 105 | } 106 | } 107 | 108 | [TestMethod] 109 | public void UpgradeCommand_ShouldUseConnectionTimeoutForLongrunningQueries() 110 | { 111 | var engine = new ToolEngine(Env, Logger); 112 | 113 | var r = engine.Run("upgrade", "--ensure", GetConfigPath("dbup.yml", "Timeout")); 114 | r.Should().Be(1); 115 | } 116 | 117 | [TestMethod] 118 | public void UpgradeCommand_ShouldUseASpecifiedJournal() 119 | { 120 | var engine = new ToolEngine(Env, Logger); 121 | 122 | var result = engine.Run("upgrade", "--ensure", GetConfigPath("dbup.yml", "JournalTableScript")); 123 | result.Should().Be(0); 124 | 125 | using (var connection = new SqlConnection(Environment.GetEnvironmentVariable("CONNSTR"))) 126 | using (var command = new SqlCommand("select count(*) from dbo.testTable where scriptname = '001.sql'", connection)) 127 | { 128 | connection.Open(); 129 | var count = command.ExecuteScalar(); 130 | 131 | count.Should().Be(1); 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/dbup-cli.integration-tests/dbup-cli.integration-tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0 5 | DbUp.Cli.IntegrationTests 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | PreserveNewest 32 | 33 | 34 | PreserveNewest 35 | 36 | 37 | PreserveNewest 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | PreserveNewest 47 | 48 | 49 | PreserveNewest 50 | 51 | 52 | PreserveNewest 53 | 54 | 55 | PreserveNewest 56 | 57 | 58 | PreserveNewest 59 | 60 | 61 | PreserveNewest 62 | 63 | 64 | PreserveNewest 65 | 66 | 67 | PreserveNewest 68 | 69 | 70 | PreserveNewest 71 | 72 | 73 | PreserveNewest 74 | 75 | 76 | PreserveNewest 77 | 78 | 79 | PreserveNewest 80 | 81 | 82 | PreserveNewest 83 | 84 | 85 | PreserveNewest 86 | 87 | 88 | PreserveNewest 89 | 90 | 91 | PreserveNewest 92 | 93 | 94 | PreserveNewest 95 | 96 | 97 | PreserveNewest 98 | 99 | 100 | PreserveNewest 101 | 102 | 103 | PreserveNewest 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/dbup-cli.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.329 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dbup-cli", "dbup-cli\dbup-cli.csproj", "{97DCD633-B24A-459E-B781-56D4E49F9879}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dbup-cli.integration-tests", "dbup-cli.integration-tests\dbup-cli.integration-tests.csproj", "{7E7DA3A5-AD27-4789-882A-D6C58924721B}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dbup-cli.tests.core", "dbup-cli.tests.core\dbup-cli.tests.core.csproj", "{8B42CF74-0B01-4A59-85F3-90515D079B6E}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dbup-cli.tests", "dbup-cli.tests\dbup-cli.tests.csproj", "{66AAAC19-6161-4898-A201-C0F3D9C88849}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {97DCD633-B24A-459E-B781-56D4E49F9879}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {97DCD633-B24A-459E-B781-56D4E49F9879}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {97DCD633-B24A-459E-B781-56D4E49F9879}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {97DCD633-B24A-459E-B781-56D4E49F9879}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {7E7DA3A5-AD27-4789-882A-D6C58924721B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {7E7DA3A5-AD27-4789-882A-D6C58924721B}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {7E7DA3A5-AD27-4789-882A-D6C58924721B}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {7E7DA3A5-AD27-4789-882A-D6C58924721B}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {8B42CF74-0B01-4A59-85F3-90515D079B6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {8B42CF74-0B01-4A59-85F3-90515D079B6E}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {8B42CF74-0B01-4A59-85F3-90515D079B6E}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {8B42CF74-0B01-4A59-85F3-90515D079B6E}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {66AAAC19-6161-4898-A201-C0F3D9C88849}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {66AAAC19-6161-4898-A201-C0F3D9C88849}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {66AAAC19-6161-4898-A201-C0F3D9C88849}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {66AAAC19-6161-4898-A201-C0F3D9C88849}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {ECE47877-4066-4EBC-BB5F-2AAAE5EDFFBB} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/CaptureLogsLogger.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine.Output; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace DbUp.Cli.Tests.TestInfrastructure 7 | { 8 | public class CaptureLogsLogger: IUpgradeLog 9 | { 10 | readonly StringBuilder logBuilder = new StringBuilder(); 11 | public List InfoMessages { get; } = new List(); 12 | public List WarnMessages { get; } = new List(); 13 | public List ErrorMessages { get; } = new List(); 14 | 15 | public string Log => logBuilder.ToString(); 16 | 17 | public void WriteInformation(string format, params object[] args) 18 | { 19 | var formattedMsg = string.Format(format, args); 20 | var value = "Info: " + formattedMsg; 21 | Console.WriteLine(value); 22 | logBuilder.AppendLine(value); 23 | InfoMessages.Add(formattedMsg); 24 | } 25 | 26 | public void WriteWarning(string format, params object[] args) 27 | { 28 | var formattedValue = string.Format(format, args); 29 | var value = "Warn: " + formattedValue; 30 | Console.WriteLine(value); 31 | logBuilder.AppendLine(value); 32 | WarnMessages.Add(formattedValue); 33 | } 34 | 35 | public void WriteError(string format, params object[] args) 36 | { 37 | var formattedMessage = string.Format(format, args); 38 | var value = "Error: " + formattedMessage; 39 | Console.WriteLine(value); 40 | logBuilder.AppendLine(value); 41 | ErrorMessages.Add(formattedMessage); 42 | } 43 | 44 | public void WriteDbOperation(string operation) 45 | { 46 | var value = "DB Operation: " + operation; 47 | Console.WriteLine(value); 48 | logBuilder.AppendLine(value); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/CaptureLogsLoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace DbUp.Cli.Tests.TestInfrastructure 5 | { 6 | static public class CaptureLogsLoggerExtensions 7 | { 8 | public static List GetExecutedScripts(this CaptureLogsLogger logger) 9 | { 10 | var scripts = new List(); 11 | var exp = new Regex(@"Executing Database Server script '(.+\.sql)'"); 12 | foreach (var msg in logger.InfoMessages) 13 | { 14 | var m = exp.Match(msg); 15 | if (!m.Success) 16 | { 17 | continue; 18 | } 19 | 20 | scripts.Add(m.Groups[1].Value); 21 | } 22 | 23 | return scripts; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/EmptyReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace DbUp.Cli.Tests.TestInfrastructure 5 | { 6 | public class EmptyReader: IDataReader 7 | { 8 | public bool GetBoolean(int i) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public byte GetByte(int i) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | 23 | public char GetChar(int i) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | 33 | public IDataReader GetData(int i) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | 38 | public string GetDataTypeName(int i) 39 | { 40 | throw new NotImplementedException(); 41 | } 42 | 43 | public DateTime GetDateTime(int i) 44 | { 45 | throw new NotImplementedException(); 46 | } 47 | 48 | public decimal GetDecimal(int i) 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | 53 | public double GetDouble(int i) 54 | { 55 | throw new NotImplementedException(); 56 | } 57 | 58 | public Type GetFieldType(int i) 59 | { 60 | throw new NotImplementedException(); 61 | } 62 | 63 | public float GetFloat(int i) 64 | { 65 | throw new NotImplementedException(); 66 | } 67 | 68 | public Guid GetGuid(int i) 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | 73 | public short GetInt16(int i) 74 | { 75 | throw new NotImplementedException(); 76 | } 77 | 78 | public int GetInt32(int i) 79 | { 80 | throw new NotImplementedException(); 81 | } 82 | 83 | public long GetInt64(int i) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | public string GetName(int i) 89 | { 90 | throw new NotImplementedException(); 91 | } 92 | 93 | public int GetOrdinal(string name) 94 | { 95 | throw new NotImplementedException(); 96 | } 97 | 98 | public string GetString(int i) 99 | { 100 | throw new NotImplementedException(); 101 | } 102 | 103 | public object GetValue(int i) 104 | { 105 | throw new NotImplementedException(); 106 | } 107 | 108 | public int GetValues(object[] values) 109 | { 110 | throw new NotImplementedException(); 111 | } 112 | 113 | public bool IsDBNull(int i) 114 | { 115 | throw new NotImplementedException(); 116 | } 117 | 118 | public int FieldCount { get; } 119 | 120 | object IDataRecord.this[string name] 121 | { 122 | get { throw new NotImplementedException(); } 123 | } 124 | 125 | object IDataRecord.this[int i] 126 | { 127 | get { throw new NotImplementedException(); } 128 | } 129 | 130 | public void Dispose() 131 | { 132 | } 133 | 134 | public void Close() 135 | { 136 | } 137 | 138 | public DataTable GetSchemaTable() 139 | { 140 | throw new NotImplementedException(); 141 | } 142 | 143 | public bool NextResult() 144 | { 145 | throw new NotImplementedException(); 146 | } 147 | 148 | public bool Read() 149 | { 150 | return false; 151 | } 152 | 153 | public int Depth { get; } 154 | public bool IsClosed { get; } 155 | public int RecordsAffected { get; } 156 | } 157 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/RecordingDataParameterCollection.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine.Output; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | 7 | namespace DbUp.Cli.Tests.TestInfrastructure 8 | { 9 | public class RecordingDataParameterCollection: IDataParameterCollection 10 | { 11 | readonly IUpgradeLog logger; 12 | readonly List backingList; 13 | 14 | public RecordingDataParameterCollection(IUpgradeLog logger) 15 | { 16 | this.logger = logger; 17 | backingList = new List(); 18 | } 19 | 20 | public IEnumerator GetEnumerator() 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | public void CopyTo(Array array, int index) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | 30 | public int Count { get; private set; } 31 | public object SyncRoot { get; private set; } 32 | public bool IsSynchronized { get; private set; } 33 | public int Add(object value) 34 | { 35 | logger.WriteInformation(string.Format("DB Operation: Add parameter to command: {0}", value)); 36 | backingList.Add(value); 37 | return backingList.Count - 1; 38 | } 39 | 40 | public bool Contains(object value) 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | public void Clear() 46 | { 47 | throw new NotImplementedException(); 48 | } 49 | 50 | public int IndexOf(object value) 51 | { 52 | throw new NotImplementedException(); 53 | } 54 | 55 | public void Insert(int index, object value) 56 | { 57 | throw new NotImplementedException(); 58 | } 59 | 60 | public void Remove(object value) 61 | { 62 | throw new NotImplementedException(); 63 | } 64 | 65 | public void RemoveAt(int index) 66 | { 67 | throw new NotImplementedException(); 68 | } 69 | 70 | object IList.this[int index] 71 | { 72 | get { throw new NotImplementedException(); } 73 | set { throw new NotImplementedException(); } 74 | } 75 | 76 | public bool IsReadOnly { get; private set; } 77 | public bool IsFixedSize { get; private set; } 78 | public bool Contains(string parameterName) 79 | { 80 | throw new NotImplementedException(); 81 | } 82 | 83 | public int IndexOf(string parameterName) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | public void RemoveAt(string parameterName) 89 | { 90 | throw new NotImplementedException(); 91 | } 92 | 93 | object IDataParameterCollection.this[string parameterName] 94 | { 95 | get { throw new NotImplementedException(); } 96 | set { throw new NotImplementedException(); } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/RecordingDbCommand.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Data.Common; 6 | 7 | namespace DbUp.Cli.Tests.TestInfrastructure 8 | { 9 | public class RecordingDbCommand: IDbCommand 10 | { 11 | readonly CaptureLogsLogger logger; 12 | readonly SqlScript[] runScripts; 13 | readonly string schemaTableName; 14 | readonly Dictionary> scalarResults; 15 | readonly Dictionary> nonQueryResults; 16 | 17 | public RecordingDbCommand(CaptureLogsLogger logger, SqlScript[] runScripts, string schemaTableName, 18 | Dictionary> scalarResults, Dictionary> nonQueryResults) 19 | { 20 | this.logger = logger; 21 | this.runScripts = runScripts; 22 | this.schemaTableName = schemaTableName; 23 | this.scalarResults = scalarResults; 24 | this.nonQueryResults = nonQueryResults; 25 | Parameters = new RecordingDataParameterCollection(logger); 26 | } 27 | 28 | public void Dispose() 29 | { 30 | logger.WriteDbOperation("Dispose command"); 31 | } 32 | 33 | public void Prepare() 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | 38 | public void Cancel() 39 | { 40 | throw new NotImplementedException(); 41 | } 42 | 43 | public IDbDataParameter CreateParameter() 44 | { 45 | logger.WriteDbOperation("Create parameter"); 46 | return new RecordingDbDataParameter(); 47 | } 48 | 49 | public int ExecuteNonQuery() 50 | { 51 | logger.WriteDbOperation($"Execute non query command: {CommandText}"); 52 | 53 | if (CommandText == "error") 54 | ThrowError(); 55 | 56 | if (nonQueryResults.ContainsKey(CommandText)) 57 | return nonQueryResults[CommandText](); 58 | return 0; 59 | } 60 | 61 | void ThrowError() 62 | { 63 | throw new TestDbException(); 64 | } 65 | 66 | public IDataReader ExecuteReader() 67 | { 68 | logger.WriteDbOperation($"Execute reader command: {CommandText}"); 69 | 70 | if (CommandText == "error") 71 | ThrowError(); 72 | 73 | // Reading SchemaVersions 74 | if (CommandText.IndexOf(schemaTableName, StringComparison.OrdinalIgnoreCase) != -1) 75 | { 76 | return new ScriptReader(runScripts); 77 | } 78 | 79 | return new EmptyReader(); 80 | } 81 | 82 | public IDataReader ExecuteReader(CommandBehavior behavior) 83 | { 84 | throw new NotImplementedException(); 85 | } 86 | 87 | public object ExecuteScalar() 88 | { 89 | logger.WriteDbOperation($"Execute scalar command: {CommandText}"); 90 | 91 | if (CommandText == "error") 92 | ThrowError(); 93 | 94 | // Are we checking if schemaversions exists 95 | if (CommandText.IndexOf(schemaTableName, StringComparison.OrdinalIgnoreCase) != -1) 96 | { 97 | if (runScripts != null) 98 | return 1; 99 | return 0; 100 | } 101 | 102 | if (scalarResults.ContainsKey(CommandText)) 103 | { 104 | return scalarResults[CommandText](); 105 | } 106 | 107 | return null; 108 | } 109 | 110 | public IDbConnection Connection { get; set; } 111 | 112 | public IDbTransaction Transaction { get; set; } 113 | 114 | /// 115 | /// Set to 'error' to throw when executed 116 | /// 117 | public string CommandText { get; set; } 118 | 119 | public int CommandTimeout { get; set; } 120 | 121 | public CommandType CommandType { get; set; } 122 | 123 | public IDataParameterCollection Parameters { get; } 124 | 125 | public UpdateRowSource UpdatedRowSource { get; set; } 126 | 127 | class TestDbException: DbException 128 | { 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/RecordingDbConnection.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | 6 | namespace DbUp.Cli.Tests.TestInfrastructure 7 | { 8 | public class RecordingDbConnection: IDbConnection 9 | { 10 | readonly Dictionary> scalarResults = new Dictionary>(); 11 | readonly Dictionary> nonQueryResults = new Dictionary>(); 12 | readonly CaptureLogsLogger logger; 13 | readonly string schemaTableName; 14 | SqlScript[] runScripts; 15 | 16 | public RecordingDbConnection(CaptureLogsLogger logger, string schemaTableName) 17 | { 18 | this.logger = logger; 19 | this.schemaTableName = schemaTableName; 20 | } 21 | 22 | public IDbTransaction BeginTransaction() 23 | { 24 | logger.WriteDbOperation("Begin transaction"); 25 | return new RecordingDbTransaction(logger); 26 | } 27 | 28 | public IDbTransaction BeginTransaction(IsolationLevel il) 29 | { 30 | logger.WriteDbOperation($"Begin transaction with isolationLevel of {il}"); 31 | return new RecordingDbTransaction(logger); 32 | } 33 | 34 | public void Close() 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | 39 | public void ChangeDatabase(string databaseName) 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | 44 | public IDbCommand CreateCommand() 45 | { 46 | return new RecordingDbCommand(logger, runScripts, schemaTableName, scalarResults, nonQueryResults); 47 | } 48 | 49 | public void Open() 50 | { 51 | logger.WriteDbOperation("Open connection"); 52 | } 53 | 54 | public void Dispose() 55 | { 56 | logger.WriteDbOperation("Dispose connection"); 57 | } 58 | 59 | public string ConnectionString { get; set; } 60 | 61 | public int ConnectionTimeout { get; private set; } 62 | 63 | public string Database { get; private set; } 64 | 65 | public ConnectionState State { get; private set; } 66 | 67 | public void SetupRunScripts(params SqlScript[] runScripts) 68 | { 69 | this.runScripts = runScripts; 70 | } 71 | 72 | public void SetupScalarResult(string sql, Func action) 73 | { 74 | scalarResults.Add(sql, action); 75 | } 76 | 77 | public void SetupNonQueryResult(string sql, Func result) 78 | { 79 | nonQueryResults.Add(sql, result); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/RecordingDbDataParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace DbUp.Cli.Tests.TestInfrastructure 5 | { 6 | public class RecordingDbDataParameter: IDbDataParameter 7 | { 8 | public DbType DbType { get; set; } 9 | public ParameterDirection Direction { get; set; } 10 | public bool IsNullable { get; private set; } 11 | public string ParameterName { get; set; } 12 | public string SourceColumn { get; set; } 13 | public DataRowVersion SourceVersion { get; set; } 14 | public object Value { get; set; } 15 | public byte Precision { get; set; } 16 | public byte Scale { get; set; } 17 | public int Size { get; set; } 18 | 19 | public override string ToString() 20 | { 21 | var format = "{0}={1}"; 22 | if ((DbType == DbType.Date) 23 | || (DbType == DbType.DateTime) 24 | || (DbType == DbType.DateTime2) 25 | || (DbType == DbType.DateTimeOffset) 26 | || (DbType == DbType.AnsiString // If DbType is not explicitly set it will default to AnsiString so check our Value's type 27 | && (Value != null && (Value is DateTime || Value is DateTimeOffset)))) 28 | { 29 | format = "{0}={1:dd\\/MM\\/yyyy hh\\:mm\\:ss}"; // Be explicit and don't rely on the system's formatting for dates so we can scrub them out later 30 | } 31 | 32 | return string.Format(format, ParameterName, Value); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/RecordingDbTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace DbUp.Cli.Tests.TestInfrastructure 5 | { 6 | public class RecordingDbTransaction: IDbTransaction 7 | { 8 | readonly CaptureLogsLogger logger; 9 | 10 | public RecordingDbTransaction(CaptureLogsLogger logger) 11 | { 12 | this.logger = logger; 13 | } 14 | 15 | public void Dispose() 16 | { 17 | logger.WriteDbOperation("Dispose transaction"); 18 | } 19 | 20 | public void Commit() 21 | { 22 | logger.WriteDbOperation("Commit transaction"); 23 | } 24 | 25 | public void Rollback() 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | 30 | public IDbConnection Connection { get; private set; } 31 | public IsolationLevel IsolationLevel { get; private set; } 32 | } 33 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/ScriptReader.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine; 2 | using System; 3 | using System.Data; 4 | 5 | namespace DbUp.Cli.Tests.TestInfrastructure 6 | { 7 | public class ScriptReader: IDataReader 8 | { 9 | readonly SqlScript[] runScripts; 10 | int currentIndex = -1; 11 | 12 | public ScriptReader(SqlScript[] runScripts) 13 | { 14 | this.runScripts = runScripts; 15 | } 16 | 17 | public bool GetBoolean(int i) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | 22 | public byte GetByte(int i) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | 27 | public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public char GetChar(int i) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | 37 | public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) 38 | { 39 | throw new NotImplementedException(); 40 | } 41 | 42 | public IDataReader GetData(int i) 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | 47 | public string GetDataTypeName(int i) 48 | { 49 | throw new NotImplementedException(); 50 | } 51 | 52 | public DateTime GetDateTime(int i) 53 | { 54 | throw new NotImplementedException(); 55 | } 56 | 57 | public decimal GetDecimal(int i) 58 | { 59 | throw new NotImplementedException(); 60 | } 61 | 62 | public double GetDouble(int i) 63 | { 64 | throw new NotImplementedException(); 65 | } 66 | 67 | public Type GetFieldType(int i) 68 | { 69 | throw new NotImplementedException(); 70 | } 71 | 72 | public float GetFloat(int i) 73 | { 74 | throw new NotImplementedException(); 75 | } 76 | 77 | public Guid GetGuid(int i) 78 | { 79 | throw new NotImplementedException(); 80 | } 81 | 82 | public short GetInt16(int i) 83 | { 84 | throw new NotImplementedException(); 85 | } 86 | 87 | public int GetInt32(int i) 88 | { 89 | throw new NotImplementedException(); 90 | } 91 | 92 | public long GetInt64(int i) 93 | { 94 | throw new NotImplementedException(); 95 | } 96 | 97 | public string GetName(int i) 98 | { 99 | throw new NotImplementedException(); 100 | } 101 | 102 | public int GetOrdinal(string name) 103 | { 104 | throw new NotImplementedException(); 105 | } 106 | 107 | public string GetString(int i) 108 | { 109 | throw new NotImplementedException(); 110 | } 111 | 112 | public object GetValue(int i) 113 | { 114 | throw new NotImplementedException(); 115 | } 116 | 117 | public int GetValues(object[] values) 118 | { 119 | throw new NotImplementedException(); 120 | } 121 | 122 | public bool IsDBNull(int i) 123 | { 124 | throw new NotImplementedException(); 125 | } 126 | 127 | public int FieldCount { get; } 128 | 129 | public object this[string name] 130 | { 131 | get { throw new NotImplementedException(); } 132 | } 133 | 134 | public object this[int i] => runScripts[currentIndex].Name; 135 | 136 | public void Dispose() 137 | { 138 | } 139 | 140 | public void Close() 141 | { 142 | throw new NotImplementedException(); 143 | } 144 | 145 | public DataTable GetSchemaTable() 146 | { 147 | throw new NotImplementedException(); 148 | } 149 | 150 | public bool NextResult() 151 | { 152 | throw new NotImplementedException(); 153 | } 154 | 155 | public bool Read() 156 | { 157 | if (runScripts == null) 158 | return false; 159 | currentIndex++; 160 | return runScripts.Length > currentIndex; 161 | } 162 | 163 | public int Depth { get; } 164 | public bool IsClosed { get; } 165 | public int RecordsAffected { get; } 166 | } 167 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/Scrubbers.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace DbUp.Cli.Tests.TestInfrastructure 4 | { 5 | public static class Scrubbers 6 | { 7 | public static string ScrubDates(string arg) 8 | { 9 | return Regex.Replace(arg, @"\d?\d/\d?\d/\d?\d?\d\d \d?\d:\d\d:\d\d", ""); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/SubstitutedConnectionConnectionManager.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine.Transactions; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | 5 | namespace DbUp.Cli.Tests.TestInfrastructure 6 | { 7 | public class SubstitutedConnectionConnectionManager: DatabaseConnectionManager 8 | { 9 | public SubstitutedConnectionConnectionManager(IDbConnection conn) : base(l => conn) 10 | { 11 | } 12 | 13 | public override IEnumerable SplitScriptIntoCommands(string scriptContents) 14 | { 15 | yield return scriptContents; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/TestConnectionManager.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine; 2 | using DbUp.Engine.Output; 3 | using DbUp.Engine.Transactions; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | 7 | namespace DbUp.Cli.Tests.TestInfrastructure 8 | { 9 | public class TestConnectionManager: DatabaseConnectionManager 10 | { 11 | public TestConnectionManager(IDbConnection connection, bool startUpgrade = false) : base(l => connection) 12 | { 13 | if (startUpgrade) 14 | OperationStarting(new ConsoleUpgradeLog(), new List()); 15 | } 16 | 17 | public override IEnumerable SplitScriptIntoCommands(string scriptContents) 18 | { 19 | return new[] { scriptContents }; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/TestDatabaseExtension.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Builder; 2 | using DbUp.Engine.Transactions; 3 | using System.Data; 4 | 5 | namespace DbUp.Cli.Tests.TestInfrastructure 6 | { 7 | /// 8 | /// Configures DbUp to use SqlServer with a fake connection 9 | /// 10 | public static class TestDatabaseExtension 11 | { 12 | public static UpgradeEngineBuilder OverrideConnectionFactory(this UpgradeEngineBuilder engineBuilder, IDbConnection connection) 13 | { 14 | return engineBuilder.OverrideConnectionFactory(new DelegateConnectionFactory(l => connection)); 15 | } 16 | 17 | public static UpgradeEngineBuilder OverrideConnectionFactory(this UpgradeEngineBuilder engineBuilder, IConnectionFactory connectionFactory) 18 | { 19 | engineBuilder.Configure(c => ((DatabaseConnectionManager)c.ConnectionManager).OverrideFactoryForTest(connectionFactory)); 20 | return engineBuilder; 21 | } 22 | 23 | public static UpgradeEngineBuilder TestDatabase(this SupportedDatabases supportedDatabases, IDbConnection connection) 24 | { 25 | var builder = supportedDatabases.SqlDatabase(""); 26 | builder.OverrideConnectionFactory(connection); 27 | return builder; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/TestInfrastructure/TestScriptProvider.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine; 2 | using DbUp.Engine.Transactions; 3 | using System.Collections.Generic; 4 | 5 | namespace DbUp.Cli.Tests.TestInfrastructure 6 | { 7 | public class TestScriptProvider: IScriptProvider 8 | { 9 | readonly List sqlScripts; 10 | 11 | public TestScriptProvider(List sqlScripts) 12 | { 13 | this.sqlScripts = sqlScripts; 14 | } 15 | 16 | public IEnumerable GetScripts(IConnectionManager connectionManager) 17 | { 18 | return sqlScripts; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/dbup-cli.tests.core/dbup-cli.tests.core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1;netcoreapp3.1;net5.0;net6.0 5 | DbUp.Cli.Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/ConfigurationHelperTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using DbUp.Cli; 3 | using FluentAssertions; 4 | using System; 5 | using DbUp.Cli.Tests.TestInfrastructure; 6 | using DbUp.Engine.Transactions; 7 | using DbUp.SqlServer; 8 | using System.Collections.Generic; 9 | using DbUp.Engine; 10 | using DbUp.Builder; 11 | using Optional; 12 | 13 | namespace DbUp.Cli.Tests 14 | { 15 | [TestClass] 16 | public class ConfigurationHelperTests 17 | { 18 | readonly List scripts; 19 | readonly CaptureLogsLogger logger; 20 | readonly DelegateConnectionFactory testConnectionFactory; 21 | readonly RecordingDbConnection recordingConnection; 22 | readonly UpgradeEngineBuilder upgradeEngineBuilder; 23 | 24 | public ConfigurationHelperTests() 25 | { 26 | scripts = new List 27 | { 28 | new SqlScript("Script1.sql", "create table Foo (Id int identity)") 29 | //new SqlScript("Script2.sql", "alter table Foo add column Name varchar(255)"), 30 | //new SqlScript("Script3.sql", "insert into Foo (Name) values ('test')") 31 | }; 32 | 33 | logger = new CaptureLogsLogger(); 34 | recordingConnection = new RecordingDbConnection(logger, "SchemaVersions"); 35 | testConnectionFactory = new DelegateConnectionFactory(_ => recordingConnection); 36 | 37 | upgradeEngineBuilder = DeployChanges.To 38 | .SqlDatabase("testconn") 39 | .WithScripts(new TestScriptProvider(scripts)) 40 | .OverrideConnectionFactory(testConnectionFactory) 41 | .LogTo(logger); 42 | } 43 | 44 | [TestMethod] 45 | public void SelectDbProvider_ShouldReturnNone_IfAProviderIsNotSupported() 46 | { 47 | var builder = ConfigurationHelper.SelectDbProvider(Provider.UnsupportedProfider, @"Data Source=(localdb)\dbup;Initial Catalog=dbup-tests;Integrated Security=True", 60); 48 | 49 | builder.HasValue.Should().BeFalse(); 50 | } 51 | 52 | [TestMethod] 53 | public void SelectDbProvider_ShouldReturnReturnAValidProvider_ForSqlServer() 54 | { 55 | var builder = ConfigurationHelper.SelectDbProvider(Provider.SqlServer, @"Data Source=(localdb)\dbup;Initial Catalog=dbup-tests;Integrated Security=True", 60); 56 | builder.MatchSome(b => b.Configure(c => c.ConnectionManager.Should().BeOfType(typeof(SqlConnectionManager)))); 57 | 58 | builder.HasValue.Should().BeTrue(); 59 | builder.MatchSome(x => 60 | { 61 | x.WithScripts(new TestScriptProvider(scripts)); 62 | x.Build(); 63 | }); 64 | } 65 | 66 | [TestMethod] 67 | public void PerformUpgrade_ShouldUseCustomVersionsTable_IfCustomJournalIsPassed() 68 | { 69 | upgradeEngineBuilder.Some() 70 | .SelectJournal( 71 | Provider.SqlServer, new Journal("test_scheme", "test_SchemaVersion") 72 | ); 73 | 74 | upgradeEngineBuilder.Build().PerformUpgrade(); 75 | 76 | logger.InfoMessages.Should().Contain("Creating the [test_scheme].[test_SchemaVersion] table"); 77 | } 78 | 79 | [TestMethod] 80 | public void PerformUpgrade_ShouldUseDefaultVersionsTable_IfDefaultJournalIsPassed() 81 | { 82 | upgradeEngineBuilder.Some() 83 | .SelectJournal(Provider.SqlServer, Journal.Default); 84 | 85 | upgradeEngineBuilder.Build().PerformUpgrade(); 86 | 87 | logger.InfoMessages.Should().Contain("Creating the [SchemaVersions] table"); 88 | } 89 | 90 | [TestMethod] 91 | public void SelectJournal_ShouldSelectNullJournal_IfNoneValueIsPassed() 92 | { 93 | upgradeEngineBuilder.Some() 94 | .SelectJournal(Provider.SqlServer, null); 95 | 96 | upgradeEngineBuilder.Build().PerformUpgrade(); 97 | logger.InfoMessages.Should().NotContain(x => x.StartsWith("Creating the ", StringComparison.Ordinal)); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/EnvVariableSubstitutionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using FluentAssertions; 3 | using System.IO; 4 | using System; 5 | using System.Collections.Generic; 6 | using DbUp.Cli.Tests.TestInfrastructure; 7 | using DbUp.Engine.Transactions; 8 | using Optional; 9 | using System.Reflection; 10 | using FakeItEasy; 11 | 12 | namespace DbUp.Cli.Tests 13 | { 14 | [TestClass] 15 | public class EnvVariableSubstitutionTests 16 | { 17 | readonly CaptureLogsLogger Logger; 18 | readonly DelegateConnectionFactory testConnectionFactory; 19 | readonly RecordingDbConnection recordingConnection; 20 | 21 | string GetBasePath() => 22 | Path.Combine(Assembly.GetExecutingAssembly().Location, @"..\Scripts\Config"); 23 | 24 | string GetConfigPath(string name) => new DirectoryInfo(Path.Combine(GetBasePath(), name)).FullName; 25 | 26 | public EnvVariableSubstitutionTests() 27 | { 28 | Logger = new CaptureLogsLogger(); 29 | recordingConnection = new RecordingDbConnection(Logger, "SchemaVersions"); 30 | testConnectionFactory = new DelegateConnectionFactory(_ => recordingConnection); 31 | } 32 | 33 | [TestMethod] 34 | public void LoadMigration_ShouldSubstituteEnvVars_ToConnectionString() 35 | { 36 | const string connstr = "connection string"; 37 | Environment.SetEnvironmentVariable(nameof(connstr), connstr); 38 | 39 | var migrationOrNone = ConfigLoader.LoadMigration(GetConfigPath("env-vars.yml").Some()); 40 | 41 | migrationOrNone.Match( 42 | some: migration => 43 | { 44 | migration.ConnectionString.Should().Be(connstr); 45 | }, 46 | none: (err) => Assert.Fail(err.Message)); 47 | } 48 | 49 | [TestMethod] 50 | public void LoadMigration_ShouldSubstituteEnvVars_ToFolders() 51 | { 52 | const string folder = "folder_name"; 53 | Environment.SetEnvironmentVariable(nameof(folder), folder); 54 | 55 | var migrationOrNone = ConfigLoader.LoadMigration(GetConfigPath("env-vars.yml").Some()); 56 | 57 | migrationOrNone.Match( 58 | some: migration => 59 | { 60 | migration.Scripts[0].Folder.Should().EndWith(folder); 61 | }, 62 | none: (err) => Assert.Fail(err.Message)); 63 | } 64 | 65 | [TestMethod] 66 | public void LoadMigration_ShouldSubstituteEnvVars_ToVarValues() 67 | { 68 | const string var1 = "variable_value"; 69 | Environment.SetEnvironmentVariable(nameof(var1), var1); 70 | 71 | var migrationOrNone = ConfigLoader.LoadMigration(GetConfigPath("env-vars.yml").Some()); 72 | 73 | migrationOrNone.Match( 74 | some: migration => 75 | { 76 | migration.Vars["Var1"].Should().Be(var1); 77 | }, 78 | none: (err) => Assert.Fail(err.Message)); 79 | } 80 | 81 | [TestMethod] 82 | public void LoadEnvironmentVariables_ShouldLoadDotEnv_FromCurrentFolder() 83 | { 84 | const string varA = "va1"; 85 | 86 | var env = A.Fake(); 87 | A.CallTo(() => env.GetCurrentDirectory()).Returns(new DirectoryInfo(Path.Combine(GetBasePath(), "DotEnv-CurrentFolder")).FullName); 88 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 89 | 90 | ConfigurationHelper.LoadEnvironmentVariables(env, GetConfigPath("dotenv-vars.yml"), new List()) 91 | .MatchNone(error => Assert.Fail(error.Message)); 92 | 93 | var migrationOrNone = ConfigLoader.LoadMigration(GetConfigPath("dotenv-vars.yml").Some()); 94 | 95 | migrationOrNone.Match( 96 | some: migration => 97 | { 98 | migration.Vars["VarA"].Should().Be(varA); 99 | }, 100 | none: (err) => Assert.Fail(err.Message)); 101 | } 102 | 103 | [TestMethod] 104 | public void LoadEnvironmentVariables_ShouldLoadDotEnv_FromConfigFileFolder() 105 | { 106 | const string varB = "vb2"; 107 | 108 | var env = A.Fake(); 109 | A.CallTo(() => env.GetCurrentDirectory()).Returns(new DirectoryInfo(Path.Combine(GetBasePath(), "DotEnv-CurrentFolder")).FullName); 110 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 111 | 112 | ConfigurationHelper.LoadEnvironmentVariables(env, GetConfigPath("dotenv-vars.yml"), new List()) 113 | .MatchNone(error => Assert.Fail(error.Message)); 114 | 115 | var migrationOrNone = ConfigLoader.LoadMigration(GetConfigPath("dotenv-vars.yml").Some()); 116 | 117 | migrationOrNone.Match( 118 | some: migration => 119 | { 120 | migration.Vars["VarB"].Should().Be(varB); 121 | }, 122 | none: (err) => Assert.Fail(err.Message)); 123 | } 124 | 125 | [TestMethod] 126 | public void LoadEnvironmentVariables_ShouldLoadVars_FromSpecifiedFiles() 127 | { 128 | const string varC = "vc3"; 129 | const string varD = "vd3"; 130 | 131 | var env = A.Fake(); 132 | A.CallTo(() => env.GetCurrentDirectory()).Returns(new DirectoryInfo(Path.Combine(GetBasePath(), "DotEnv-CurrentFolder")).FullName); 133 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 134 | 135 | ConfigurationHelper.LoadEnvironmentVariables(env, GetConfigPath("dotenv-vars.yml"), new List() 136 | { 137 | "../varC.env", // relative path 138 | GetConfigPath("varD.env") // absolute path 139 | }) 140 | .MatchNone(error => Assert.Fail(error.Message)); 141 | 142 | var migrationOrNone = ConfigLoader.LoadMigration(GetConfigPath("dotenv-vars.yml").Some()); 143 | 144 | migrationOrNone.Match( 145 | some: migration => 146 | { 147 | migration.Vars["VarC"].Should().Be(varC); 148 | migration.Vars["VarD"].Should().Be(varD); 149 | }, 150 | none: (err) => Assert.Fail(err.Message)); 151 | } 152 | 153 | [TestMethod] 154 | public void LoadEnvironmentVariables_ShouldProperlyOverrideLoadedVariables() 155 | { 156 | // Loading order should be: 157 | // .env in the current folder 158 | // .env in the config file folder 159 | // all specified files in order of their appearance 160 | 161 | /* 162 | * The folder Config/DotEnv-VarsOverride contains prepared files to conduct this test. The idea is that each of the files contains 163 | * the same variables varA - varD, but each one overrides the different variables with the different values. 164 | * 165 | * The order of the files should be: 166 | * 1. CurrentFolder/.env 167 | * 2. ConfigFolder/.env 168 | * 3. file3.env 169 | * 4. file4.env 170 | * 171 | * The values of the variables are (dependent of a file): 172 | * 173 | * 1 2 3 4 The result 174 | * varA va1 - - - va1 175 | * varB vb1 vb2 - - vb2 176 | * varC vc1 vc2 vc3 - vc3 177 | * varD vd1 vd2 vd3 vd4 vd4 178 | * 179 | * The numbers in the headers of the rows are the file numbers as mentioned before. A dash means that the variable is absent in a file. 180 | * The last column contains the right values of the variables after the overriding if it was correctly performed. 181 | */ 182 | 183 | const string varA = "va1"; 184 | const string varB = "vb2"; 185 | const string varC = "vc3"; 186 | const string varD = "vd4"; 187 | 188 | var env = A.Fake(); 189 | A.CallTo(() => env.GetCurrentDirectory()).Returns(new DirectoryInfo(Path.Combine(GetBasePath(), "DotEnv-VarsOverride/CurrentFolder")).FullName); 190 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 191 | 192 | ConfigurationHelper.LoadEnvironmentVariables(env, GetConfigPath("DotEnv-VarsOverride/ConfigFolder/dotenv-vars.yml"), new List() 193 | { 194 | "../file3.env", 195 | "../file4.env" 196 | }) 197 | .MatchNone(error => Assert.Fail(error.Message)); 198 | 199 | var migrationOrNone = ConfigLoader.LoadMigration(GetConfigPath("DotEnv-VarsOverride/ConfigFolder/dotenv-vars.yml").Some()); 200 | 201 | migrationOrNone.Match( 202 | some: migration => 203 | { 204 | migration.Vars["VarA"].Should().Be(varA); 205 | migration.Vars["VarB"].Should().Be(varB); 206 | migration.Vars["VarC"].Should().Be(varC); 207 | migration.Vars["VarD"].Should().Be(varD); 208 | }, 209 | none: (err) => Assert.Fail(err.Message)); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/FilterTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using FluentAssertions; 3 | using FakeItEasy; 4 | using DbUp.Cli.Tests.TestInfrastructure; 5 | using System.Reflection; 6 | using System.IO; 7 | using DbUp.Engine.Transactions; 8 | using Optional; 9 | 10 | namespace DbUp.Cli.Tests 11 | { 12 | [TestClass] 13 | public class FilterTests 14 | { 15 | readonly CaptureLogsLogger Logger; 16 | readonly DelegateConnectionFactory testConnectionFactory; 17 | readonly RecordingDbConnection recordingConnection; 18 | 19 | string GetBasePath() => 20 | Path.Combine(Assembly.GetExecutingAssembly().Location, @"..\Scripts\Config"); 21 | string GetConfigPath(string name) => new DirectoryInfo(Path.Combine(GetBasePath(), name)).FullName; 22 | 23 | public FilterTests() 24 | { 25 | Logger = new CaptureLogsLogger(); 26 | recordingConnection = new RecordingDbConnection(Logger, "SchemaVersions"); 27 | testConnectionFactory = new DelegateConnectionFactory(_ => recordingConnection); 28 | } 29 | 30 | [TestMethod] 31 | public void CreateFilter_NullOrWhiteSpaceString_ShouldReturnNull() 32 | { 33 | var filter = ScriptProviderHelper.CreateFilter(" "); 34 | filter.Should().BeNull(); 35 | 36 | filter = ScriptProviderHelper.CreateFilter(null); 37 | filter.Should().BeNull(); 38 | } 39 | 40 | [TestMethod] 41 | public void CreateFilter_GeneralString_ShouldMatchTheSameStringInTheDifferentLetterCase() 42 | { 43 | var filter = ScriptProviderHelper.CreateFilter("script.sql"); 44 | 45 | var result = filter("Script.SQL"); 46 | 47 | result.Should().BeTrue(); 48 | } 49 | 50 | [TestMethod] 51 | public void CreateFilter_GeneralString_ShouldNotMatchTheSubstring() 52 | { 53 | var filter = ScriptProviderHelper.CreateFilter("script.sql"); 54 | 55 | var result = filter("script"); 56 | 57 | result.Should().BeFalse(); 58 | } 59 | 60 | [DataRow("scr?ipt.sql", "scrAipt.SQL")] 61 | [DataRow("scr*ipt.sql", "script.SQL")] 62 | [DataRow("scr*ipt.sql", "scrAAAipt.SQL")] 63 | [DataTestMethod] 64 | public void CreateFilter_WildcardString_ShouldMatchTheTestedString(string filterString, string testedString) 65 | { 66 | var filter = ScriptProviderHelper.CreateFilter(filterString); 67 | 68 | var result = filter(testedString); 69 | result.Should().BeTrue(); 70 | } 71 | 72 | [DataRow("scr?ipt.sql", "script.sql")] 73 | [DataRow("scr?ipt.sql", "scrAAipt.sql")] 74 | [DataRow("scr?ipt.sql", "1scrAipt.sql")] 75 | [DataTestMethod] 76 | public void CreateFilter_WildcardString_ShouldNotMatchTheTestedString(string filterString, string testedString) 77 | { 78 | var filter = ScriptProviderHelper.CreateFilter(filterString); 79 | 80 | var result = filter(testedString); 81 | result.Should().BeFalse(); 82 | } 83 | 84 | [DataRow("/scr.ipt\\.sql/", "scrAipt.SQL")] 85 | [DataRow("/scr.?ipt\\.sql/", "script.SQL")] 86 | //[DataRow("//script\\.sql//", "/script.SQL/")] // TODO: test this later 87 | [DataRow("//", "it is equal to empty filter")] 88 | [DataTestMethod] 89 | public void CreateFilter_RegexString_ShouldMatchTheTestedString(string filterString, string testedString) 90 | { 91 | var filter = ScriptProviderHelper.CreateFilter(filterString); 92 | 93 | var result = filter(testedString); 94 | result.Should().BeTrue(); 95 | } 96 | 97 | [DataRow("/scr.ipt\\.sql/", "script.SQL")] 98 | [DataRow("/scr.ipt\\.sql/", "scrAAipt.SQL")] 99 | [DataRow("/scr.?ipt\\.sql/", "scrAAipt.SQL")] 100 | [DataRow("/scr.ipt\\.sql/", "1scrAipt.SQL")] 101 | [DataRow("/scr.ipt\\.sql/", "scrAipt.SQL1")] 102 | [DataTestMethod] 103 | public void CreateFilter_RegexString_ShouldNotMatchTheTestedString(string filterString, string testedString) 104 | { 105 | var filter = ScriptProviderHelper.CreateFilter(filterString); 106 | 107 | var result = filter(testedString); 108 | result.Should().BeFalse(); 109 | } 110 | 111 | [DataRow("d0a1.sql")] 112 | [DataRow("d0aa1.sql")] 113 | [DataRow("c001.sql")] 114 | [DataRow("c0a1.sql")] 115 | [DataRow("c0b1.sql")] 116 | [DataRow("e001.sql")] 117 | [DataRow("e0a1.sql")] 118 | [DataRow("e0b1.sql")] 119 | [DataTestMethod] 120 | public void ToolEngine_ShouldRespectScriptFiltersAndMatchFiles(string filename) 121 | { 122 | var env = A.Fake(); 123 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 124 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 125 | 126 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 127 | 128 | var result = engine.Run("upgrade", GetConfigPath("filter.yml")); 129 | result.Should().Be(0); 130 | 131 | Logger.Log.Should().Contain(filename); 132 | } 133 | 134 | [DataRow("d001.sql")] 135 | [DataRow("d01.sql")] 136 | [DataRow("d0b1.sql")] 137 | [DataRow("c01.sql")] 138 | [DataRow("c0aa1.sql")] 139 | [DataRow("e01.sql")] 140 | [DataRow("e0aa1.sql")] 141 | [DataTestMethod] 142 | public void ToolEngine_ShouldRespectScriptFiltersAndNotMatchFiles(string filename) 143 | { 144 | var env = A.Fake(); 145 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 146 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 147 | 148 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 149 | 150 | var result = engine.Run("upgrade", GetConfigPath("filter.yml")); 151 | result.Should().Be(0); 152 | 153 | Logger.Log.Should().NotContain(filename); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/ScriptProviderHelperTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using FluentAssertions; 3 | using System.IO; 4 | using System; 5 | using System.Collections.Generic; 6 | using DbUp.Cli.Tests.TestInfrastructure; 7 | using DbUp.Engine.Transactions; 8 | using Optional; 9 | using System.Reflection; 10 | using DbUp.Builder; 11 | 12 | namespace DbUp.Cli.Tests 13 | { 14 | [TestClass] 15 | public class ScriptProviderHelperTests 16 | { 17 | readonly CaptureLogsLogger Logger; 18 | readonly DelegateConnectionFactory testConnectionFactory; 19 | readonly RecordingDbConnection recordingConnection; 20 | 21 | public ScriptProviderHelperTests() 22 | { 23 | Logger = new CaptureLogsLogger(); 24 | recordingConnection = new RecordingDbConnection(Logger, "SchemaVersions"); 25 | testConnectionFactory = new DelegateConnectionFactory(_ => recordingConnection); 26 | } 27 | 28 | [TestMethod] 29 | public void ScriptProviderHelper_GetFolder_ShouldReturnCurrentFolder_IfTheFolderIsNullOrWhiteSpace() 30 | { 31 | var current = Directory.GetCurrentDirectory(); 32 | var path = ScriptProviderHelper.GetFolder(current, null); 33 | path.Should().Be(current); 34 | } 35 | 36 | [TestMethod] 37 | public void ScriptProviderHelper_GetFolder_ShouldThrowAnException_IfTheBaseFolderIsNullOrWhiteSpace() 38 | { 39 | Action nullAction = () => ScriptProviderHelper.GetFolder(null, null); 40 | Action whitespaceAction = () => ScriptProviderHelper.GetFolder(" ", null); 41 | 42 | nullAction.Should().Throw(); 43 | whitespaceAction.Should().Throw(); 44 | } 45 | 46 | [TestMethod] 47 | public void ScriptProviderHelper_GetFolder_ShouldReturnFullyQualifiedFolder_IfTheFolderIsARelativePath() 48 | { 49 | var current = Directory.GetCurrentDirectory(); 50 | var path = ScriptProviderHelper.GetFolder(current, "upgrades"); 51 | path.Should().Be($"{current}\\upgrades"); 52 | } 53 | 54 | [TestMethod] 55 | public void ScriptProviderHelper_GetFolder_ShouldReturnOriginalFolder_IfTheFolderIsAFullyQualifiedPath() 56 | { 57 | var current = Directory.GetCurrentDirectory(); 58 | var path = ScriptProviderHelper.GetFolder(current, "d:\\upgrades"); 59 | path.Should().Be("d:\\upgrades"); 60 | } 61 | 62 | [TestMethod] 63 | public void ScriptProviderHelper_GetSqlScriptOptions_ShouldSetScriptTypeToRunOnce_IfRunAlwaysIsSetToFalse() 64 | { 65 | var batch = new ScriptBatch("", runAlways: false, false, 1, ""); 66 | var options = ScriptProviderHelper.GetSqlScriptOptions(batch); 67 | 68 | options.ScriptType.Should().Be(Support.ScriptType.RunOnce); 69 | } 70 | 71 | [TestMethod] 72 | public void ScriptProviderHelper_GetSqlScriptOptions_ShouldSetScriptTypeToRunAlways_IfRunAlwaysIsSetToTrue() 73 | { 74 | var batch = new ScriptBatch("", runAlways: true, false, 1, ""); 75 | var options = ScriptProviderHelper.GetSqlScriptOptions(batch); 76 | 77 | options.ScriptType.Should().Be(Support.ScriptType.RunAlways); 78 | } 79 | 80 | [TestMethod] 81 | public void ScriptProviderHelper_GetSqlScriptOptions_ShouldSetGroupOrderToValidValue() 82 | { 83 | var batch = new ScriptBatch("", runAlways: true, false, 5, ""); 84 | var options = ScriptProviderHelper.GetSqlScriptOptions(batch); 85 | 86 | options.RunGroupOrder.Should().Be(5); 87 | } 88 | 89 | [TestMethod] 90 | public void ScriptProviderHelper_GetFileSystemScriptOptions_ShouldSetIncludeSubDirectoriesToFalse_IfSubFoldersIsSetToFalse() 91 | { 92 | var batch = new ScriptBatch("", true, subFolders: false, 5, Constants.Default.Encoding); 93 | ScriptProviderHelper.GetFileSystemScriptOptions(batch, NamingOptions.Default).Match( 94 | some: options => options.IncludeSubDirectories.Should().BeFalse(), 95 | none: error => Assert.Fail(error.Message) 96 | ); 97 | } 98 | 99 | [TestMethod] 100 | public void ScriptProviderHelper_GetFileSystemScriptOptions_ShouldSetIncludeSubDirectoriesToTrue_IfSubFoldersIsSetToTrue() 101 | { 102 | var batch = new ScriptBatch("", true, subFolders: true, 5, Constants.Default.Encoding); 103 | ScriptProviderHelper.GetFileSystemScriptOptions(batch, NamingOptions.Default).Match( 104 | some: options => options.IncludeSubDirectories.Should().BeTrue(), 105 | none: error => Assert.Fail(error.Message) 106 | ); 107 | } 108 | 109 | [TestMethod] 110 | public void ScriptProviderHelper_SelectJournal_ShouldAddAllTheScripts() 111 | { 112 | var scripts = new List() 113 | { 114 | new ScriptBatch(ScriptProviderHelper.GetFolder(GetBasePath(), "SubFolder1"), false, false, 0, Constants.Default.Encoding), 115 | new ScriptBatch(ScriptProviderHelper.GetFolder(GetBasePath(), "SubFolder2"), false, false, 0, Constants.Default.Encoding), 116 | }; 117 | 118 | var upgradeEngineBuilder = DeployChanges.To 119 | .SqlDatabase("testconn") 120 | .OverrideConnectionFactory(testConnectionFactory) 121 | .LogTo(Logger).Some() 122 | .SelectScripts(scripts, NamingOptions.Default); 123 | 124 | upgradeEngineBuilder.MatchSome(x => 125 | { 126 | x.Build().PerformUpgrade(); 127 | }); 128 | 129 | var excutedScripts = Logger.GetExecutedScripts(); 130 | 131 | excutedScripts.Should().HaveCount(3); 132 | excutedScripts[0].Should().Be("003.sql"); 133 | excutedScripts[1].Should().Be("004.sql"); 134 | excutedScripts[2].Should().Be("005.sql"); 135 | } 136 | 137 | [TestMethod] 138 | public void ScriptProviderHelper_SelectJournal_ShouldReturnNone_IfTheListOfScriptsIsEmpty() 139 | { 140 | var scripts = new List(); 141 | 142 | var upgradeEngineBuilder = DeployChanges.To 143 | .SqlDatabase("testconn") 144 | .OverrideConnectionFactory(testConnectionFactory) 145 | .LogTo(Logger).Some() 146 | .SelectScripts(scripts, NamingOptions.Default); 147 | 148 | upgradeEngineBuilder.HasValue.Should().BeFalse(); 149 | } 150 | 151 | string GetBasePath() => 152 | Path.Combine(Assembly.GetExecutingAssembly().Location, @"..\Scripts\Default"); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/.env: -------------------------------------------------------------------------------- 1 | varB=vb2 -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/DotEnv-CurrentFolder/.env: -------------------------------------------------------------------------------- 1 | varA=va1 -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/DotEnv-VarsOverride/ConfigFolder/.env: -------------------------------------------------------------------------------- 1 | varB=vb2 2 | varC=vc2 3 | varD=vd2 4 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/DotEnv-VarsOverride/ConfigFolder/dotenv-vars.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: connection string 5 | scripts: 6 | - folder: folder 7 | vars: 8 | VarA: $varA$ 9 | VarB: $varB$ 10 | VarC: $varC$ 11 | VarD: $varD$ 12 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/DotEnv-VarsOverride/CurrentFolder/.env: -------------------------------------------------------------------------------- 1 | varA=va1 2 | varB=vb1 3 | varC=vc1 4 | varD=vd1 5 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/DotEnv-VarsOverride/file3.env: -------------------------------------------------------------------------------- 1 | varC=vc3 2 | varD=vd3 3 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/DotEnv-VarsOverride/file4.env: -------------------------------------------------------------------------------- 1 | varD=vd4 2 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/Encoding/c001.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drwatson1/dbup-cli/81ac424f57abc21c0649319814d5700743731617/src/dbup-cli.tests/Scripts/Config/Encoding/c001.sql -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterRegex/d001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterRegex/d01.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterRegex/d0a1.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterRegex/d0aa1.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterRegex/d0b1.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWildcard/c001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWildcard/c01.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWildcard/c0a1.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWildcard/c0aa1.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWildcard/c0b1.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWithFullPath/e001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWithFullPath/e01.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWithFullPath/e0a1.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWithFullPath/e0aa1.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/FilterWithFullPath/e0b1.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/MarkAsExecuted/c001.sql: -------------------------------------------------------------------------------- 1 | print 'You should not see this message' 2 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/Naming/SubFolder/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/NoScripts/FolderShouldExists.txt: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/OneScript/c001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/Status/Scripts/c001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/Status/file1.env: -------------------------------------------------------------------------------- 1 | part1=Scri -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/Status/file2.env: -------------------------------------------------------------------------------- 1 | part2=pts -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/Status/status.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: $part1$$part2$ 7 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/Vars/c001.sql: -------------------------------------------------------------------------------- 1 | print '$Var1$' 2 | print '$Var2$' 3 | print '$Var_3-1$' 4 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/c001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/disable-vars.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: Vars 7 | disableVars: yes 8 | vars: 9 | Var1: Var1Value 10 | Var2: Var2Value 11 | Var_3-1: Var3 Value -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/dotenv-vars.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: connection string 5 | scripts: 6 | - folder: folder 7 | vars: 8 | VarA: $varA$ 9 | VarB: $varB$ 10 | VarC: $varC$ 11 | VarD: $varD$ 12 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/encoding.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: Encoding 7 | encoding: windows-1251 -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/env-vars.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: $connstr$ 5 | scripts: 6 | - folder: $folder$ 7 | vars: 8 | Var1: $var1$ 9 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/filter.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: FilterWildcard 7 | filter: c0?1.sql 8 | - folder: FilterRegex 9 | filter: /d0a+1\.sql/ 10 | - folder: FilterWithFullPath 11 | filter: "*\\FilterWithFullPath\\e0?1.sql" 12 | matchFullPath: yes -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/invalid-provider.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: postgre1 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/invalid-transaction.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | transaction: WrongTransaction 6 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/invalid-vars.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: Vars 7 | vars: 8 | Invalid-Var-Name$: Var1Value 9 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/journalTo-null.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | journalTo: null 6 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/journalTo.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | journalTo: 6 | schema: "test-schema" 7 | table: "test-table" 8 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/log.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | logScriptOutput: yes 6 | logToConsole: no 7 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/mark-as-executed.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: MarkAsExecuted 7 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/min.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/naming.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | naming: 6 | useOnlyFileName: yes 7 | includeBaseFolderName: yes 8 | prefix: scriptpreffix 9 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/no-scripts.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/no-vars.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: Vars 7 | vars: 8 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/noscripts.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: NoScripts 7 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/onescript.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: OneScript 7 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/script.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: upgrades 7 | subFolders: yes 8 | order: 1 9 | runAlways: no 10 | - folder: views 11 | subFolders: yes 12 | order: 2 13 | runAlways: yes 14 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/syntax-error.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: %ERR% 5 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/timeout.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | connectionTimeoutSec: 45 -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/tran.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | transaction: PerScript 6 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/varC.env: -------------------------------------------------------------------------------- 1 | varC=vc3 -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/varD.env: -------------------------------------------------------------------------------- 1 | varD=vd3 -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/vars.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | scripts: 6 | - folder: Vars 7 | vars: 8 | Var1: Var1Value 9 | Var2: Var2Value 10 | Var_3-1: Var3 Value -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Config/wrongversion.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 2 3 | provider: sqlserver 4 | connectionString: (localdb)\dbup;Initial Catalog=DbUpTest;Integrated Security=True 5 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Default/001.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Default/002.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Default/SubFolder1/003.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Default/SubFolder1/004.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/Scripts/Default/SubFolder2/005.sql: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/dbup-cli.tests/ToolEngineTests.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Cli.Tests.TestInfrastructure; 2 | using DbUp.Engine.Output; 3 | using DbUp.Engine.Transactions; 4 | using FakeItEasy; 5 | using FluentAssertions; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using Optional; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Reflection; 11 | 12 | namespace DbUp.Cli.Tests 13 | { 14 | [TestClass] 15 | public class ToolEngineTests 16 | { 17 | readonly CaptureLogsLogger Logger; 18 | readonly DelegateConnectionFactory testConnectionFactory; 19 | readonly RecordingDbConnection recordingConnection; 20 | 21 | string GetBasePath() => 22 | Path.Combine(Assembly.GetExecutingAssembly().Location, @"..\Scripts\Config"); 23 | string GetConfigPath(string name) => new DirectoryInfo(Path.Combine(GetBasePath(), name)).FullName; 24 | 25 | public ToolEngineTests() 26 | { 27 | Logger = new CaptureLogsLogger(); 28 | recordingConnection = new RecordingDbConnection(Logger, "SchemaVersions"); 29 | testConnectionFactory = new DelegateConnectionFactory(_ => recordingConnection); 30 | } 31 | 32 | [TestMethod] 33 | public void InitCommand_ShouldCreateDefaultConfig_IfItIsNotPresent() 34 | { 35 | var saved = false; 36 | 37 | var env = A.Fake(); 38 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 39 | A.CallTo(() => env.FileExists(@"c:\test\dbup.yml")).Returns(false); 40 | A.CallTo(() => env.WriteFile("", "")).WithAnyArguments().ReturnsLazily(x => { saved = true; return true.Some(); }); 41 | 42 | var engine = new ToolEngine(env, A.Fake()); 43 | 44 | engine.Run("init").Should().Be(0); 45 | saved.Should().BeTrue(); 46 | } 47 | 48 | [TestMethod] 49 | public void InitCommand_ShouldReturn1AndNotCreateConfig_IfItIsPresent() 50 | { 51 | var saved = false; 52 | 53 | var env = A.Fake(); 54 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 55 | A.CallTo(() => env.FileExists(@"c:\test\dbup.yml")).Returns(true); 56 | A.CallTo(() => env.WriteFile("", "")).WithAnyArguments().ReturnsLazily(x => { saved = true; return true.Some(); }); 57 | 58 | var engine = new ToolEngine(env, A.Fake()); 59 | 60 | engine.Run("init").Should().Be(1); 61 | saved.Should().BeFalse(); 62 | } 63 | 64 | [TestMethod] 65 | public void VersionCommand_ShouldReturnZero() 66 | { 67 | var env = A.Fake(); 68 | var engine = new ToolEngine(env, A.Fake()); 69 | 70 | var exit_code = engine.Run("version"); 71 | 72 | exit_code.Should().Be(0); 73 | } 74 | 75 | [TestMethod] 76 | public void StatusCommand_ShouldPrintGeneralInformation_IfNoScriptsToExecute() 77 | { 78 | var env = A.Fake(); 79 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 80 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 81 | 82 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 83 | 84 | var result = engine.Run("status", GetConfigPath("noscripts.yml")); 85 | result.Should().Be(0); 86 | 87 | Logger.InfoMessages.Last().Should().StartWith("Database is up-to-date"); 88 | } 89 | 90 | [TestMethod] 91 | public void StatusCommand_ShouldPrintGeneralInformation_IfThereAreTheScriptsToExecute() 92 | { 93 | var env = A.Fake(); 94 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 95 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 96 | 97 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 98 | 99 | var result = engine.Run("status", GetConfigPath("onescript.yml")); 100 | 101 | Logger.InfoMessages.Last().Should().StartWith("You have 1 more scripts"); 102 | } 103 | 104 | [TestMethod] 105 | public void StatusCommand_ShouldPrintScriptName_IfThereAreTheScriptsToExecute() 106 | { 107 | var env = A.Fake(); 108 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 109 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 110 | 111 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 112 | 113 | var result = engine.Run("status", GetConfigPath("onescript.yml"), "-n"); 114 | 115 | Logger.InfoMessages.Last().Should().EndWith("c001.sql"); 116 | } 117 | 118 | [TestMethod] 119 | public void StatusCommand_ShouldReturnMinusOne_IfThereAreTheScriptsToExecute() 120 | { 121 | var env = A.Fake(); 122 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 123 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 124 | 125 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 126 | 127 | var result = engine.Run("status", GetConfigPath("onescript.yml"), "-n"); 128 | result.Should().Be(-1); 129 | } 130 | 131 | [TestMethod] 132 | public void StatusCommand_ShouldUseSpecifiedEnvFiles() 133 | { 134 | var env = A.Fake(); 135 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 136 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 137 | 138 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 139 | 140 | var result = engine.Run("status", GetConfigPath("Status/status.yml"), "-n", 141 | "--env", GetConfigPath("Status/file1.env"), GetConfigPath("Status/file2.env")); 142 | 143 | Logger.InfoMessages.Last().Should().EndWith("c001.sql"); 144 | } 145 | 146 | [TestMethod] 147 | public void MarkAsExecutedCommand_WhenCalled_ShouldNotMakeAnyChangesInDb() 148 | { 149 | var env = A.Fake(); 150 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 151 | A.CallTo(() => env.FileExists("")).WithAnyArguments().ReturnsLazily(x => { return File.Exists(x.Arguments[0] as string); }); 152 | 153 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 154 | var result = engine.Run("mark-as-executed", GetConfigPath("mark-as-executed.yml")); 155 | result.Should().Be(0); 156 | 157 | Logger.Log.Should().NotContain("print 'You should not see this message'"); 158 | } 159 | 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/VariableSubstitutionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using FluentAssertions; 3 | using System.IO; 4 | using System; 5 | using System.Collections.Generic; 6 | using DbUp.Cli.Tests.TestInfrastructure; 7 | using DbUp.Engine.Transactions; 8 | using Optional; 9 | using System.Reflection; 10 | using FakeItEasy; 11 | 12 | namespace DbUp.Cli.Tests 13 | { 14 | [TestClass] 15 | public class VariableSubstitutionTests 16 | { 17 | readonly CaptureLogsLogger Logger; 18 | readonly DelegateConnectionFactory testConnectionFactory; 19 | readonly RecordingDbConnection recordingConnection; 20 | 21 | string GetBasePath() => 22 | Path.Combine(Assembly.GetExecutingAssembly().Location, @"..\Scripts\Config"); 23 | 24 | string GetConfigPath(string name) => new DirectoryInfo(Path.Combine(GetBasePath(), name)).FullName; 25 | 26 | public VariableSubstitutionTests() 27 | { 28 | Logger = new CaptureLogsLogger(); 29 | recordingConnection = new RecordingDbConnection(Logger, "SchemaVersions"); 30 | testConnectionFactory = new DelegateConnectionFactory(_ => recordingConnection); 31 | } 32 | 33 | [TestMethod] 34 | public void LoadMigration_ShouldLoadVariablesFromConfig() 35 | { 36 | var migrationOrNone = ConfigLoader.LoadMigration(GetConfigPath("vars.yml").Some()); 37 | 38 | migrationOrNone.Match( 39 | some: migration => 40 | { 41 | migration.Vars.Should().HaveCount(3); 42 | migration.Vars.Should().ContainKey("Var1"); 43 | migration.Vars.Should().ContainKey("Var2"); 44 | migration.Vars.Should().ContainKey("Var_3-1"); 45 | 46 | migration.Vars["Var1"].Should().Be("Var1Value"); 47 | migration.Vars["Var2"].Should().Be("Var2Value"); 48 | migration.Vars["Var_3-1"].Should().Be("Var3 Value"); 49 | }, 50 | none: (err) => Assert.Fail(err.Message)); 51 | } 52 | 53 | [TestMethod] 54 | public void LoadMigration_ShouldReturnAnError_IfVarNameContainsInvalidChars() 55 | { 56 | /* According to https://dbup.readthedocs.io/en/latest/more-info/variable-substitution/: 57 | * 58 | * Variables can only contain letters, digits, _ and -. 59 | */ 60 | var migrationOrNone = ConfigLoader.LoadMigration(GetConfigPath("invalid-vars.yml").Some()); 61 | 62 | migrationOrNone.MatchSome( 63 | migration => 64 | { 65 | Assert.Fail("LoadMigration should fail if a var name contains one of the invalid chars"); 66 | }); 67 | } 68 | 69 | [TestMethod] 70 | public void LoadMigration_ShouldSubstituteVariablesToScript() 71 | { 72 | var env = A.Fake(); 73 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 74 | A.CallTo(() => env.FileExists(A.Ignored)).ReturnsLazily(x => File.Exists(x.Arguments[0] as string)); 75 | 76 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 77 | 78 | var result = engine.Run("upgrade", GetConfigPath("vars.yml")); 79 | result.Should().Be(0); 80 | 81 | Logger.Log.Should().Contain("print 'Var1Value'"); 82 | Logger.Log.Should().Contain("print 'Var2Value'"); 83 | Logger.Log.Should().Contain("print 'Var3 Value'"); 84 | } 85 | 86 | [TestMethod] 87 | public void LoadMigration_ShouldNotSubstituteVariablesToScript() 88 | { 89 | var env = A.Fake(); 90 | A.CallTo(() => env.GetCurrentDirectory()).Returns(@"c:\test"); 91 | A.CallTo(() => env.FileExists(A.Ignored)).ReturnsLazily(x => File.Exists(x.Arguments[0] as string)); 92 | 93 | var engine = new ToolEngine(env, Logger, (testConnectionFactory as IConnectionFactory).Some()); 94 | 95 | var result = engine.Run("upgrade", GetConfigPath("disable-vars.yml")); 96 | result.Should().Be(0); 97 | 98 | Logger.Log.Should().Contain("print '$Var1$'"); 99 | Logger.Log.Should().Contain("print '$Var2$'"); 100 | Logger.Log.Should().Contain("print '$Var_3-1$'"); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/dbup-cli.tests/dbup-cli.tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0 5 | DbUp.Cli.Tests 6 | 7 | false 8 | 9 | latest 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | PreserveNewest 32 | 33 | 34 | PreserveNewest 35 | 36 | 37 | PreserveNewest 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | PreserveNewest 47 | 48 | 49 | PreserveNewest 50 | 51 | 52 | PreserveNewest 53 | 54 | 55 | PreserveNewest 56 | 57 | 58 | PreserveNewest 59 | 60 | 61 | PreserveNewest 62 | 63 | 64 | PreserveNewest 65 | 66 | 67 | PreserveNewest 68 | 69 | 70 | PreserveNewest 71 | 72 | 73 | PreserveNewest 74 | 75 | 76 | PreserveNewest 77 | 78 | 79 | PreserveNewest 80 | 81 | 82 | PreserveNewest 83 | 84 | 85 | PreserveNewest 86 | 87 | 88 | PreserveNewest 89 | 90 | 91 | PreserveNewest 92 | 93 | 94 | PreserveNewest 95 | 96 | 97 | PreserveNewest 98 | 99 | 100 | PreserveNewest 101 | 102 | 103 | PreserveNewest 104 | 105 | 106 | PreserveNewest 107 | 108 | 109 | PreserveNewest 110 | 111 | 112 | PreserveNewest 113 | 114 | 115 | PreserveNewest 116 | 117 | 118 | PreserveNewest 119 | 120 | 121 | PreserveNewest 122 | 123 | 124 | PreserveNewest 125 | 126 | 127 | PreserveNewest 128 | 129 | 130 | PreserveNewest 131 | 132 | 133 | PreserveNewest 134 | 135 | 136 | PreserveNewest 137 | 138 | 139 | PreserveNewest 140 | 141 | 142 | PreserveNewest 143 | 144 | 145 | PreserveNewest 146 | 147 | 148 | PreserveNewest 149 | 150 | 151 | PreserveNewest 152 | 153 | 154 | PreserveNewest 155 | 156 | 157 | PreserveNewest 158 | 159 | 160 | PreserveNewest 161 | 162 | 163 | PreserveNewest 164 | 165 | 166 | PreserveNewest 167 | 168 | 169 | PreserveNewest 170 | 171 | 172 | PreserveNewest 173 | 174 | 175 | PreserveNewest 176 | 177 | 178 | PreserveNewest 179 | 180 | 181 | PreserveNewest 182 | 183 | 184 | PreserveNewest 185 | 186 | 187 | PreserveNewest 188 | 189 | 190 | PreserveNewest 191 | 192 | 193 | PreserveNewest 194 | 195 | 196 | PreserveNewest 197 | 198 | 199 | PreserveNewest 200 | 201 | 202 | PreserveNewest 203 | 204 | 205 | PreserveNewest 206 | 207 | 208 | PreserveNewest 209 | 210 | 211 | PreserveNewest 212 | 213 | 214 | PreserveNewest 215 | 216 | 217 | PreserveNewest 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /src/dbup-cli/CliEnvironment.cs: -------------------------------------------------------------------------------- 1 | using Optional; 2 | using System; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace DbUp.Cli 7 | { 8 | /// 9 | /// Environment implementation to use in cli tool 10 | /// 11 | public class CliEnvironment: IEnvironment 12 | { 13 | public bool DirectoryExists(string path) => Directory.Exists(path); 14 | public bool FileExists(string path) => File.Exists(path); 15 | public string GetCurrentDirectory() => Directory.GetCurrentDirectory(); 16 | public Option WriteFile(string path, string content) 17 | { 18 | if (path == null) 19 | throw new ArgumentNullException(nameof(path)); 20 | if (content == null) 21 | throw new ArgumentNullException(nameof(content)); 22 | 23 | if (File.Exists(path)) 24 | { 25 | return Option.None(Error.Create(Constants.ConsoleMessages.FileAlreadyExists, path)); 26 | } 27 | 28 | try 29 | { 30 | Encoding utf8WithoutBom = new UTF8Encoding(false); 31 | File.WriteAllText(path, content, utf8WithoutBom); 32 | return true.Some(); 33 | } 34 | catch (Exception ex) 35 | { 36 | return Option.None(Error.Create(ex.Message)); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/dbup-cli/CommandLineOptions/DropOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System.Collections.Generic; 3 | 4 | namespace DbUp.Cli.CommandLineOptions 5 | { 6 | [Verb("drop", HelpText = "Drop database if exists")] 7 | class DropOptions: VerbosityBase 8 | { 9 | [Option('e', "env", Required = false, HelpText = "Path to an environment file. Can be more than one file specified. The path can be absolute or relative against a current directory")] 10 | public IEnumerable EnvFiles { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/dbup-cli/CommandLineOptions/InitOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace DbUp.Cli.CommandLineOptions 4 | { 5 | [Verb("init", HelpText = "Create a new config file")] 6 | class InitOptions: OptionsBase 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/dbup-cli/CommandLineOptions/MarkAsExecutedOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System.Collections.Generic; 3 | 4 | namespace DbUp.Cli.CommandLineOptions 5 | { 6 | [Verb("mark-as-executed", HelpText = "Mark all scripts as executed")] 7 | class MarkAsExecutedOptions: VerbosityBase 8 | { 9 | [Option("ensure", HelpText = "Create database if it is not exists")] 10 | public bool Ensure { get; set; } 11 | 12 | [Option('e', "env", Required = false, HelpText = "Path to an environment file. Can be more than one file specified. The path can be absolute or relative against a current directory")] 13 | public IEnumerable EnvFiles { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/dbup-cli/CommandLineOptions/OptionsBase.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace DbUp.Cli.CommandLineOptions 4 | { 5 | public abstract class OptionsBase 6 | { 7 | [Value(0, Default = Constants.Default.ConfigFileName, Required = false, HelpText = "Path to a configuration file. The path can be absolute or relative against a current directory")] 8 | public string File { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/dbup-cli/CommandLineOptions/StatusOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System.Collections.Generic; 3 | 4 | namespace DbUp.Cli.CommandLineOptions 5 | { 6 | [Verb("status", HelpText = "Show upgrade status")] 7 | class StatusOptions: OptionsBase 8 | { 9 | [Option('x', "show-executed", HelpText = "Print names of executed scripts", Default = false)] 10 | public bool Executed { get; set; } 11 | [Option('n', "show-not-executed", HelpText = "Print names of scripts to be execute", Default = false)] 12 | public bool NotExecuted { get; set; } 13 | 14 | [Option('e', "env", Required = false, HelpText = "Path to an environment file. Can be more than one file specified. The path can be absolute or relative against a current directory")] 15 | public IEnumerable EnvFiles { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/dbup-cli/CommandLineOptions/UpgradeOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System.Collections.Generic; 3 | 4 | namespace DbUp.Cli.CommandLineOptions 5 | { 6 | [Verb("upgrade", HelpText = "Upgrade database")] 7 | class UpgradeOptions: VerbosityBase 8 | { 9 | [Option("ensure", HelpText = "Create a database if not exists")] 10 | public bool Ensure { get; set; } 11 | 12 | [Option('e', "env", Required = false, HelpText = "Path to an environment file. Can be more than one file specified. The path can be absolute or relative against a current directory")] 13 | public IEnumerable EnvFiles { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/dbup-cli/CommandLineOptions/VerbosityBase.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace DbUp.Cli.CommandLineOptions 4 | { 5 | public enum VerbosityLevel 6 | { 7 | Detail, 8 | Normal, 9 | Min 10 | } 11 | 12 | public abstract class VerbosityBase: OptionsBase 13 | { 14 | [Option('v', "verbosity", Required = false, HelpText = "Verbosity level. Can be one of: detail, normal or min", Default = VerbosityLevel.Normal)] 15 | public VerbosityLevel Verbosity { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/dbup-cli/ConfigFile/ConfigFile.cs: -------------------------------------------------------------------------------- 1 | namespace DbUp.Cli 2 | { 3 | class ConfigFile 4 | { 5 | public Migration DbUp { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/dbup-cli/ConfigFile/Journal.cs: -------------------------------------------------------------------------------- 1 | using Optional; 2 | 3 | namespace DbUp.Cli 4 | { 5 | public class Journal 6 | { 7 | public string Schema { get; private set; } 8 | public string Table { get; private set; } 9 | 10 | public static Journal Default => new Journal(); 11 | public bool IsDefault => Schema == null && Table == null; 12 | 13 | public Journal(string schema, string table) 14 | { 15 | Schema = schema; 16 | Table = table; 17 | } 18 | 19 | public Journal() 20 | { 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/dbup-cli/ConfigFile/Migration.cs: -------------------------------------------------------------------------------- 1 | using Optional; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DbUp.Cli 6 | { 7 | public class Migration 8 | { 9 | public string Version { get; private set; } 10 | public Provider Provider { get; private set; } 11 | public string ConnectionString { get; private set; } 12 | public int ConnectionTimeoutSec { get; private set; } = 30; 13 | public bool DisableVars { get; private set; } = false; 14 | public Transaction Transaction { get; private set; } = Transaction.None; 15 | public Journal JournalTo { get; private set; } = Journal.Default; 16 | public NamingOptions Naming { get; private set; } = NamingOptions.Default; 17 | public List Scripts { get; set; } = new List(); 18 | 19 | public Dictionary Vars { get; set; } = new Dictionary(); 20 | 21 | internal void ExpandVariables() 22 | { 23 | ConnectionString = StringUtils.ExpandEnvironmentVariables(ConnectionString ?? ""); 24 | Scripts.ForEach(x => x.Folder = StringUtils.ExpandEnvironmentVariables(x.Folder ?? "")); 25 | 26 | var dic = new Dictionary(); 27 | foreach (var item in Vars) 28 | { 29 | dic.Add(item.Key, StringUtils.ExpandEnvironmentVariables(item.Value ?? "")); 30 | } 31 | 32 | Vars = dic; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/dbup-cli/ConfigFile/NamingOptions.cs: -------------------------------------------------------------------------------- 1 | using Optional; 2 | 3 | namespace DbUp.Cli 4 | { 5 | public class NamingOptions 6 | { 7 | public bool UseOnlyFileName { get; private set; } 8 | public bool IncludeBaseFolderName { get; private set; } 9 | public string Prefix { get; private set; } 10 | 11 | public static readonly Option None = Option.None(); 12 | 13 | public NamingOptions(bool useOnlyFileName, bool includeBaseFolderName, string prefix) 14 | { 15 | UseOnlyFileName = useOnlyFileName; 16 | IncludeBaseFolderName = includeBaseFolderName; 17 | Prefix = prefix; 18 | } 19 | 20 | public NamingOptions() 21 | { 22 | } 23 | 24 | public static NamingOptions Default => new(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/dbup-cli/ConfigFile/Provider.cs: -------------------------------------------------------------------------------- 1 | namespace DbUp.Cli 2 | { 3 | public enum Provider 4 | { 5 | UnsupportedProfider, 6 | SqlServer, 7 | PostgreSQL, 8 | MySQL, 9 | AzureSql, 10 | CockroachDB 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/dbup-cli/ConfigFile/ScriptBatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DbUp.Cli 4 | { 5 | public class ScriptBatch 6 | { 7 | public ScriptBatch() 8 | { 9 | } 10 | 11 | public ScriptBatch(string folder, bool runAlways, bool subFolders, int order, string encoding) 12 | { 13 | Folder = folder; 14 | RunAlways = runAlways; 15 | SubFolders = subFolders; 16 | Order = order; 17 | Encoding = encoding; 18 | } 19 | 20 | public string Folder { get; set; } 21 | public bool RunAlways { get; private set; } 22 | public bool SubFolders { get; private set; } 23 | public int Order { get; private set; } = Constants.Default.Order; // Default value in DbUp 24 | public string Encoding { get; set; } = Constants.Default.Encoding; 25 | public string Filter { get; private set; } 26 | public bool MatchFullPath { get; private set; } 27 | 28 | public static ScriptBatch Default => new ScriptBatch(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/dbup-cli/ConfigFile/Transaction.cs: -------------------------------------------------------------------------------- 1 | namespace DbUp.Cli 2 | { 3 | public enum Transaction 4 | { 5 | None, 6 | PerScript, 7 | Single 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/dbup-cli/ConfigLoader.cs: -------------------------------------------------------------------------------- 1 | using Optional; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using YamlDotNet.Core; 8 | using YamlDotNet.Serialization; 9 | using YamlDotNet.Serialization.NamingConventions; 10 | 11 | namespace DbUp.Cli 12 | { 13 | public static class ConfigLoader 14 | { 15 | public static Option GetFilePath(IEnvironment environment, string configFilePath, bool fileShouldExist = true) 16 | { 17 | if (environment == null) 18 | throw new ArgumentNullException(nameof(environment)); 19 | if (string.IsNullOrWhiteSpace(configFilePath)) 20 | throw new ArgumentException("Parameter can't be null or white space", nameof(configFilePath)); 21 | 22 | return new FileInfo(Path.IsPathRooted(configFilePath) 23 | ? configFilePath 24 | : Path.Combine(environment.GetCurrentDirectory(), configFilePath) 25 | ).FullName.SomeWhen(x => 26 | !fileShouldExist || (fileShouldExist && environment.FileExists(x)), 27 | Error.Create(Constants.ConsoleMessages.FileNotFound, configFilePath) 28 | ); 29 | } 30 | 31 | public static Option LoadMigration(Option configFilePath) => 32 | configFilePath.Match( 33 | some: path => 34 | { 35 | var input = new StringReader(File.ReadAllText(path, Encoding.UTF8)); 36 | 37 | var deserializer = new DeserializerBuilder() 38 | .WithNamingConvention(CamelCaseNamingConvention.Instance) 39 | .Build(); 40 | 41 | Migration migration = null; 42 | 43 | try 44 | { 45 | migration = deserializer.Deserialize(input).DbUp; 46 | } 47 | catch (YamlException ex) 48 | { 49 | var msg = (ex.InnerException != null ? ex.InnerException.Message + " " : "") + ex.Message; 50 | return Option.None(Error.Create(Constants.ConsoleMessages.ParsingError, msg)); 51 | } 52 | 53 | if( migration.Version != "1" ) 54 | { 55 | return Option.None(Error.Create(Constants.ConsoleMessages.NotSupportedConfigFileVersion, "1")); 56 | } 57 | 58 | migration.Scripts ??= new List(); 59 | 60 | if (migration.Scripts.Count == 0) 61 | { 62 | migration.Scripts.Add(ScriptBatch.Default); 63 | } 64 | 65 | migration.Vars ??= new Dictionary(); 66 | 67 | if (!ValidateVarNames(migration.Vars, out var errMessage)) 68 | { 69 | return Option.None(Error.Create(errMessage)); 70 | } 71 | 72 | migration.ExpandVariables(); 73 | 74 | NormalizeScriptFolders(path, migration.Scripts); 75 | 76 | return migration.Some(); 77 | }, 78 | none: error => Option.None(error)); 79 | 80 | static bool ValidateVarNames(Dictionary vars, out string errMessage) 81 | { 82 | errMessage = null; 83 | 84 | Regex exp = new("^[a-z0-9_-]+$", RegexOptions.IgnoreCase); 85 | 86 | foreach (var n in vars.Keys) 87 | { 88 | if (!exp.IsMatch(n)) 89 | { 90 | if (errMessage == null) 91 | { 92 | errMessage = "Found one or more variables with an invalid name. Variable name should contain only letters, digits, - and _."; 93 | errMessage += Environment.NewLine; 94 | errMessage += "Check these names:"; 95 | } 96 | errMessage += " " + n; 97 | } 98 | } 99 | 100 | return errMessage == null; 101 | } 102 | 103 | private static void NormalizeScriptFolders(string configFilePath, IList scripts) 104 | { 105 | foreach (var script in scripts) 106 | { 107 | var folder = ScriptProviderHelper.GetFolder(Path.Combine(configFilePath, ".."), script.Folder); 108 | var dir = new DirectoryInfo(folder); 109 | folder = dir.FullName; 110 | 111 | script.Folder = folder; 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/dbup-cli/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace DbUp.Cli 2 | { 3 | public static class Constants 4 | { 5 | public static class Default 6 | { 7 | public const string ConfigFileName = "dbup.yml"; 8 | public const string ConfigFileResourceName = "DbUp.Cli.DefaultOptions.dbup.yml"; 9 | public const string DotEnvFileName = ".env"; 10 | public const string DotEnvLocalFileName = ".env.local"; 11 | public const string Encoding = "utf-8"; 12 | public static int Order = 100; 13 | } 14 | 15 | public static class ConsoleMessages 16 | { 17 | 18 | public static string FileAlreadyExists => "File already exists: {0}"; 19 | public static string FileNotFound => "File is not found: {0}"; 20 | public static string FolderNotFound => "Folder is not found: {0}"; 21 | public static string UnsupportedProvider => "Unsupported provider: {0}"; 22 | public static string InvalidTransaction => "Unsupported transaction value: {0}"; 23 | public static string ScriptShouldPresent => "At least one script should be present"; 24 | public static string ParsingError => "Configuration file error: {0}"; 25 | public static string InvalidEncoding => "Invalid encoding for scripts' folder '{0}': {1}"; 26 | public static string NotSupportedConfigFileVersion => "The only supported version of a config file is '{0}'"; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/dbup-cli/DbUpCustomization/AzureSqlDatabaseWithIntegratedSecurity.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine.Output; 2 | using DbUp.SqlServer; 3 | using Microsoft.Azure.Services.AppAuthentication; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data; 7 | using System.Data.SqlClient; 8 | using System.Text; 9 | 10 | namespace DbUp.Cli.DbUpCustomization 11 | { 12 | static class AzureSqlDatabaseWithIntegratedSecurity 13 | { 14 | /* 15 | * CAUTION!!! This code is copied from original file https://github.com/DbUp/DbUp/blob/master/src/dbup-sqlserver/SqlServerExtensions.cs 16 | * The reason is that the DbUp does not fully support AzureSQL. 17 | * More discussions see in https://github.com/drwatson1/dbup-cli/issues/16 18 | */ 19 | 20 | /// 21 | /// Ensures that the database specified in the connection string exists. 22 | /// 23 | /// Fluent helper type. 24 | /// The connection string. 25 | /// The used to record actions. 26 | /// Use this to set the command time out for creating a database in case you're encountering a time out in this operation. 27 | /// Use to indicate that the SQL server database is in Azure 28 | /// The collation name to set during database creation 29 | /// 30 | public static void AzureSqlDatabase( 31 | this SupportedDatabasesForEnsureDatabase supported, 32 | string connectionString, 33 | IUpgradeLog logger, 34 | int timeout = -1, 35 | AzureDatabaseEdition azureDatabaseEdition = AzureDatabaseEdition.None, 36 | string collation = null) 37 | { 38 | GetMasterConnectionStringBuilder(connectionString, logger, out var masterConnectionString, out var databaseName); 39 | 40 | using var connection = new SqlConnection(masterConnectionString); 41 | connection.AccessToken = GetAccessToken(); 42 | try 43 | { 44 | connection.Open(); 45 | } 46 | catch (SqlException) 47 | { 48 | // Failed to connect to master, lets try direct 49 | if (DatabaseExistsIfConnectedToDirectly(logger, connectionString, databaseName)) 50 | return; 51 | 52 | throw; 53 | } 54 | 55 | if (DatabaseExists(connection, databaseName)) 56 | return; 57 | 58 | var collationString = string.IsNullOrEmpty(collation) ? "" : $@" COLLATE {collation}"; 59 | var sqlCommandText = $@"create database [{databaseName}]{collationString}"; 60 | 61 | switch (azureDatabaseEdition) 62 | { 63 | case AzureDatabaseEdition.None: 64 | sqlCommandText += ";"; 65 | break; 66 | case AzureDatabaseEdition.Basic: 67 | sqlCommandText += " ( EDITION = ''basic'' );"; 68 | break; 69 | case AzureDatabaseEdition.Standard: 70 | sqlCommandText += " ( EDITION = ''standard'' );"; 71 | break; 72 | case AzureDatabaseEdition.Premium: 73 | sqlCommandText += " ( EDITION = ''premium'' );"; 74 | break; 75 | } 76 | 77 | // Create the database... 78 | using (var command = new SqlCommand(sqlCommandText, connection) 79 | { 80 | CommandType = CommandType.Text 81 | }) 82 | { 83 | if (timeout >= 0) 84 | { 85 | command.CommandTimeout = timeout; 86 | } 87 | 88 | command.ExecuteNonQuery(); 89 | } 90 | 91 | logger.WriteInformation(@"Created database {0}", databaseName); 92 | } 93 | 94 | /// 95 | /// Drop the database specified in the connection string. 96 | /// 97 | /// Fluent helper type. 98 | /// The connection string. 99 | /// The used to record actions. 100 | /// Use this to set the command time out for dropping a database in case you're encountering a time out in this operation. 101 | /// 102 | public static void AzureSqlDatabase(this SupportedDatabasesForDropDatabase supported, string connectionString, IUpgradeLog logger) 103 | { 104 | GetMasterConnectionStringBuilder(connectionString, logger, out var masterConnectionString, out var databaseName); 105 | 106 | using var connection = new SqlConnection(masterConnectionString); 107 | connection.AccessToken = GetAccessToken(); 108 | 109 | connection.Open(); 110 | if (!DatabaseExists(connection, databaseName)) 111 | return; 112 | 113 | // Actually we should call ALTER DATABASE [{databaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; 114 | // before DROP as for the SQL Server, 115 | // but it does not work with the following error message: 116 | // 117 | // ODBC error: State: 42000: Error: 1468 Message:'[Microsoft][ODBC Driver 17 for SQL Server][SQL Server]The operation cannot be performed on database "MYNEWDB" because it is involved in a database mirroring session or an availability group. Some operations are not allowed on a database that is participating in a database mirroring session or in an availability group.'. 118 | // ALTER DATABASE statement failed. 119 | // 120 | // Experiment shows that DROP works fine even the other user is connected. 121 | // So single user mode is not necessary for Azure SQL 122 | var dropDatabaseCommand = new SqlCommand($"DROP DATABASE [{databaseName}];", connection) { CommandType = CommandType.Text }; 123 | using (var command = dropDatabaseCommand) 124 | { 125 | command.ExecuteNonQuery(); 126 | } 127 | 128 | logger.WriteInformation("Dropped database {0}", databaseName); 129 | } 130 | 131 | static void GetMasterConnectionStringBuilder(string connectionString, IUpgradeLog logger, out string masterConnectionString, out string databaseName) 132 | { 133 | if (string.IsNullOrEmpty(connectionString) || connectionString.Trim() == string.Empty) 134 | throw new ArgumentNullException("connectionString"); 135 | 136 | if (logger == null) 137 | throw new ArgumentNullException("logger"); 138 | 139 | var masterConnectionStringBuilder = new SqlConnectionStringBuilder(connectionString); 140 | databaseName = masterConnectionStringBuilder.InitialCatalog; 141 | 142 | if (string.IsNullOrEmpty(databaseName) || databaseName.Trim() == string.Empty) 143 | throw new InvalidOperationException("The connection string does not specify a database name."); 144 | 145 | masterConnectionStringBuilder.InitialCatalog = "master"; 146 | var logMasterConnectionStringBuilder = new SqlConnectionStringBuilder(masterConnectionStringBuilder.ConnectionString) 147 | { 148 | Password = string.Empty.PadRight(masterConnectionStringBuilder.Password.Length, '*') 149 | }; 150 | 151 | logger.WriteInformation("Master ConnectionString => {0}", logMasterConnectionStringBuilder.ConnectionString); 152 | masterConnectionString = masterConnectionStringBuilder.ConnectionString; 153 | } 154 | 155 | static bool DatabaseExists(SqlConnection connection, string databaseName) 156 | { 157 | var sqlCommandText = string.Format 158 | ( 159 | @"SELECT TOP 1 case WHEN dbid IS NOT NULL THEN 1 ELSE 0 end FROM sys.sysdatabases WHERE name = '{0}';", 160 | databaseName 161 | ); 162 | 163 | // check to see if the database already exists.. 164 | using var command = new SqlCommand(sqlCommandText, connection) 165 | { 166 | CommandType = CommandType.Text 167 | }; 168 | var results = (int?)command.ExecuteScalar(); 169 | 170 | if (results.HasValue && results.Value == 1) 171 | return true; 172 | else 173 | return false; 174 | } 175 | 176 | static bool DatabaseExistsIfConnectedToDirectly(IUpgradeLog logger, string connectionString, string databaseName) 177 | { 178 | try 179 | { 180 | using var connection = new SqlConnection(connectionString); 181 | connection.AccessToken = GetAccessToken(); 182 | 183 | connection.Open(); 184 | return DatabaseExists(connection, databaseName); 185 | } 186 | catch 187 | { 188 | logger.WriteInformation("Could not connect to the database directly"); 189 | return false; 190 | } 191 | } 192 | 193 | static string GetAccessToken(string resource = "https://database.windows.net/", string tenantId = null, string azureAdInstance = "https://login.microsoftonline.com/") 194 | { 195 | return new AzureServiceTokenProvider(azureAdInstance: azureAdInstance).GetAccessTokenAsync(resource, tenantId) 196 | .ConfigureAwait(false) 197 | .GetAwaiter() 198 | .GetResult(); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/dbup-cli/DbUpCustomization/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Engine.Output; 2 | using System; 3 | 4 | namespace DbUp.Cli 5 | { 6 | public class ConsoleLogger: IUpgradeLog 7 | { 8 | public void WriteError(string format, params object[] args) 9 | { 10 | Console.ForegroundColor = ConsoleColor.Red; 11 | try 12 | { 13 | Console.WriteLine($"[ERR] {string.Format(format, args)}"); 14 | } 15 | finally 16 | { 17 | Console.ResetColor(); 18 | } 19 | } 20 | 21 | public void WriteInformation(string format, params object[] args) 22 | { 23 | Console.WriteLine(string.Format(format, args)); 24 | } 25 | 26 | public void WriteWarning(string format, params object[] args) 27 | { 28 | Console.ForegroundColor = ConsoleColor.Yellow; 29 | try 30 | { 31 | Console.WriteLine($"[WRN] {string.Format(format, args)}"); 32 | } 33 | finally 34 | { 35 | Console.ResetColor(); 36 | } 37 | 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/dbup-cli/DbUpCustomization/CustomFileSystemScriptOptions.cs: -------------------------------------------------------------------------------- 1 | using DbUp.ScriptProviders; 2 | 3 | namespace DbUp.Cli 4 | { 5 | public class CustomFileSystemScriptOptions: FileSystemScriptOptions 6 | { 7 | public bool PrefixScriptNameWithBaseFolderName { get; set; } 8 | public string Prefix { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/dbup-cli/DbUpCustomization/CustomFileSystemScriptProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using DbUp.Engine; 6 | using DbUp.Engine.Transactions; 7 | using DbUp.ScriptProviders; 8 | 9 | namespace DbUp.Cli 10 | { 11 | public class CustomFileSystemScriptProvider : IScriptProvider 12 | { 13 | readonly string directoryPath; 14 | readonly CustomFileSystemScriptOptions options; 15 | readonly FileSystemScriptProvider scriptProvider; 16 | 17 | /// 18 | /// 19 | ///Path to SQL upgrade scripts 20 | public CustomFileSystemScriptProvider(string directoryPath) : this(directoryPath, new CustomFileSystemScriptOptions(), new SqlScriptOptions()) 21 | { 22 | } 23 | 24 | /// 25 | /// 26 | ///Path to SQL upgrade scripts 27 | ///Different options for the file system script provider 28 | public CustomFileSystemScriptProvider(string directoryPath, CustomFileSystemScriptOptions options) : this(directoryPath, options, new SqlScriptOptions()) 29 | { 30 | } 31 | 32 | /// 33 | /// 34 | /// Path to SQL upgrade scripts 35 | /// Different options for the file system script provider 36 | /// The sql script options 37 | public CustomFileSystemScriptProvider(string directoryPath, CustomFileSystemScriptOptions options, SqlScriptOptions sqlScriptOptions) 38 | { 39 | this.options = options ?? throw new ArgumentNullException(nameof(options)); 40 | if (sqlScriptOptions == null) 41 | throw new ArgumentNullException(nameof(sqlScriptOptions)); 42 | this.directoryPath = directoryPath ?? throw new ArgumentNullException(nameof(directoryPath)); 43 | 44 | scriptProvider = new FileSystemScriptProvider(directoryPath, options, sqlScriptOptions); 45 | } 46 | 47 | /// 48 | /// Gets all scripts that should be executed. 49 | /// 50 | public IEnumerable GetScripts(IConnectionManager connectionManager) 51 | { 52 | return scriptProvider.GetScripts(connectionManager).Select(x => new SqlScript(GetScriptName(directoryPath, x.Name), x.Contents, x.SqlScriptOptions)); 53 | } 54 | 55 | string GetScriptName(string basePath, string filename) 56 | { 57 | var prefixedFilename = filename; 58 | if (options.PrefixScriptNameWithBaseFolderName) 59 | { 60 | var dir = new DirectoryInfo(basePath); 61 | prefixedFilename = $"{dir.Name}.{filename}"; 62 | } 63 | 64 | var prefix = options.Prefix?.Trim(); 65 | if ( !string.IsNullOrEmpty(prefix) ) 66 | { 67 | prefixedFilename = prefix + prefixedFilename; 68 | } 69 | 70 | return prefixedFilename; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/dbup-cli/DefaultOptions/dbup.yml: -------------------------------------------------------------------------------- 1 | dbUp: 2 | version: 1 # should be 1 3 | provider: sqlserver # DB provider: sqlserver, postgresql, mysql, azuresql 4 | connectionString: $CONNSTR$ # Connection string to DB. For example, "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=dbup;Integrated Security=True" for sqlserver 5 | connectionTimeoutSec: 30 # Connection timeout in seconds 6 | disableVars: no # yes / no (default). If yes, then the variable substitution is disabled 7 | transaction: None # Single / PerScript / None (default) 8 | scripts: 9 | - folder: # absolute or relative (to this file) path to a folder with *.sql files 10 | subFolders: no # yes / no (default) 11 | order: 100 # script group order, default 100 12 | runAlways: no # yes / no (default) 13 | encoding: utf-8 # scripts' encoding, default utf-8 14 | filter: # Wildcard or regex filter. Regex should be surrounded by forward slashes - for example /\d2\.sql/. By default, all scripts are included 15 | matchFullPath: no # yes / no (default). If yes, then the filter is applied to a full file path 16 | naming: 17 | useOnlyFileName: no # Use only file name as script name. No by default 18 | includeBaseFolderName: no # Start script name from base folder name. No by default 19 | prefix: # Add prefix to the script name. Empty string by default 20 | vars: # Variables substitution. You can use these variables in your scripts as $variable_name$ 21 | # Var1: Value1 22 | # Var2: Value2 23 | # journalTo: # Use 'journalTo: null' if you want to execute scripts every time when upgrade is run, 24 | # schema: "schemaName" # or specify a custom schema name 25 | # table: "tableName" # and a custom table name to store DB schema versions. By default, the table name is "SchemaVersions" 26 | -------------------------------------------------------------------------------- /src/dbup-cli/Error.cs: -------------------------------------------------------------------------------- 1 | namespace DbUp.Cli 2 | { 3 | public class Error 4 | { 5 | #pragma warning disable RECS0154 // Parameter is never used 6 | public Error(string message) => Message = message; 7 | #pragma warning restore RECS0154 // Parameter is never used 8 | 9 | public string Message { get; private set; } 10 | 11 | public static Error Create(string template, params object[] args) => new Error(string.Format(template, args)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/dbup-cli/How-to-add-a-new-db.md: -------------------------------------------------------------------------------- 1 | # How to add a new database 2 | 3 | 1. Add a corresponding DbUp NuGet-package. Typically they are named as `dbup-`, for example `dbup-mysql` 4 | 1. Add a new provider name to the Provider enum in the `ConfigFile/Provider` file 5 | 1. Update methods `SelectDbProvider`, `EnsureDb`, `DropDb` in `ConfigurationHelper.cs` file 6 | 1. Create a new integration test in the `dbup-cli.integration-tests` project. The easiest way to do so is to copy one of the tests, already there. 7 | - Under the `Scripts` folder create a new folder for database scripts for tests. You can copy it from another folder. I don't recommend using one of the existing script folder. 8 | - Change a provider name in `dbup.yml` files 9 | - Change SQL in `Timeout` folder because different databases have different syntax for sleep or delay execution 10 | - Adjust connection strings 11 | - Adjust `TestInitialize` method in according to documenation 12 | - Add corresponding NuGet-package to the `dbup-cli.integration-tests` project 13 | - Replace connection and command classes all over the test 14 | -------------------------------------------------------------------------------- /src/dbup-cli/How-to-create-a-new-release.md: -------------------------------------------------------------------------------- 1 | # How to create a new release 2 | 3 | 1. Open Project Properties and update a version, release notes, tags and so on 4 | 1. Build a NuGet-package 5 | * Open a console 6 | * Go to the `src\dbup-cli` folder 7 | * Run `dotnet pack -c Release` 8 | 1. Build a .NetFramework 4.6 standalone utility 9 | * Run `dotnet build -c Release -p:GlobalTool=false` 10 | * Update `/build/PackDbUp.cmd` if needed 11 | * Pack the utility to a single exe-file: go to the `build` folder and run `PackDbUp.cmd`. See the `build/readme.md` for additional instructions 12 | 1. Update Release Notes on the project main [README](https://github.com/drwatson1/dbup-cli/blob/master/README.md) page. 13 | 1. Update Wiki-pages if needed 14 | 1. Publish NuGet-package. Don't remember to add additional documentation from the main [README](https://github.com/drwatson1/dbup-cli/blob/master/README.md) page. 15 | 1. Commit and push all changes 16 | 1. Merge the branch to the master 17 | 1. Create a new release on GitHub. Don't remember to add the standalone utility for .NetFramework 4.6.2 from `build` folder and the NuGet-package 18 | -------------------------------------------------------------------------------- /src/dbup-cli/IEnvironment.cs: -------------------------------------------------------------------------------- 1 | using Optional; 2 | 3 | namespace DbUp.Cli 4 | { 5 | /// 6 | /// Interface of an environment to mock it in tests 7 | /// 8 | public interface IEnvironment 9 | { 10 | string GetCurrentDirectory(); 11 | bool FileExists(string path); 12 | bool DirectoryExists(string path); 13 | Option WriteFile(string path, string content); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/dbup-cli/Program.cs: -------------------------------------------------------------------------------- 1 | namespace DbUp.Cli 2 | { 3 | class Program 4 | { 5 | static int Main(string[] args) 6 | { 7 | // Commands: 8 | // - upgrade 9 | // - mark as executed 10 | // - show executed scripts 11 | // - show scripts to execute (default?) 12 | // - is upgrade required (?) 13 | 14 | /* 15 | * Use minimatch or regex as a file pattern 16 | */ 17 | 18 | return new ToolEngine(new CliEnvironment(), new ConsoleLogger()).Run(args); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/dbup-cli/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Build as a dotnet tool 3 | 4 | * Update a version 5 | * Open a console 6 | * Go to the `src\dbup-cli` folder 7 | * Run `dotnet pack -c Release` 8 | * Run `dotnet tool install --global --add-source ./nupkg dbup-cli` 9 | 10 | Uninstall: 11 | `dotnet tool uninstall --global dbup-cli` 12 | 13 | ## Build as a console .NET Framework utility 14 | 15 | * Update a version 16 | * Open a console 17 | * Go to the `src\dbup-cli` folder 18 | * Run `dotnet build -c Release -p:GlobalTool=false` 19 | 20 | You will get an exe and a bunch of dlls in the `src\dbup-cli\bin\Release\net462` folder. 21 | Optionally, you can pack them into one executable, see `build` folder for instructions. 22 | -------------------------------------------------------------------------------- /src/dbup-cli/ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Road map for v.1.0 2 | 3 | ## Features to implement 4 | 5 | [x] Implement option MarkAsExecuted for upgrade command to mark scrips as executed without actual execution of the scripts 6 | [x] Support input encoding of script files 7 | [x] Script filters 8 | [x] Wildcards 9 | [x] Regex 10 | 11 | ## TODO List 12 | 13 | [ ] dbup init -> display information about successful file creation 14 | [ ] dbup status -x -n -> remove redundunt empty line 15 | [x] dbup status --show-executed -> brief option alias can't be -e because it is already used by --env (-e) option 16 | [ ] subFolders: yes -> Displays strange scripts' names: e.g. subfolder.005.sql 17 | [x] Move logToConsole and logScriptOutput options to command line from a configuration file 18 | 19 | ## Other stuff 20 | 21 | [x] Create test for default dbup.yml file 22 | [x] Check the existence of script folder 23 | [x] Check a version of a config file 24 | [x] Target the project as a tool 25 | [x] Publish an alpha version to NuGet.org and do some tests with it 26 | [ ] Add CI (travis or appveyor) 27 | [ ] Add CD (travis or appveyor) - publish NuGet 28 | [x] Getting started page 29 | [x] Wiki documentation 30 | [x] Start page 31 | [x] Publish version 1.0 32 | [ ] Create a TFS pipeline task 33 | [ ] Wiki documentation/getting started for the TFS task 34 | [ ] Publish the extension to the marketplace 35 | [ ] Add CI (travis or appveyor) - TFS Task 36 | [ ] Add CD (travis or appveyor) - TFS Task - publish to the marketplace 37 | 38 | ## Future plans 39 | 40 | v.1.1 41 | 42 | [x] Support of PostgreSql 43 | 44 | v.1.3 45 | 46 | [x] Support of MySql 47 | 48 | v.Next 49 | 50 | [ ] Drop database for PostgreSQL and MySQL 51 | [ ] Backup a database 52 | [ ] Automatic backup a database before upgrade 53 | [ ] Support of Sqlite 54 | [ ] Build with .Net Core 3.1 55 | [ ] Automate build and release 56 | -------------------------------------------------------------------------------- /src/dbup-cli/ResultBuilder.cs: -------------------------------------------------------------------------------- 1 | using Optional; 2 | using System; 3 | 4 | namespace DbUp.Cli 5 | { 6 | internal class ResultBuilder 7 | { 8 | public Option FromUpgradeResult(Engine.DatabaseUpgradeResult result) 9 | { 10 | if (result == null) 11 | { 12 | throw new ArgumentNullException(nameof(result)); 13 | } 14 | 15 | if (result.Successful) 16 | { 17 | return Option.Some(0); 18 | } 19 | 20 | var msg = "Failed to perform upgrade: "; 21 | if (result.Error == null) 22 | { 23 | msg += "Undefined error"; 24 | } 25 | else 26 | { 27 | msg += result.Error.Message; 28 | if (result.Error.InnerException != null) 29 | { 30 | msg += $"{Environment.NewLine} Details: {result.Error.InnerException.Message}"; 31 | } 32 | } 33 | if (result.ErrorScript != null) 34 | { 35 | msg += $"{Environment.NewLine} Script: {result.ErrorScript.Name}"; 36 | } 37 | 38 | return Option.None(Error.Create(msg)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/dbup-cli/ScriptProviderHelper.cs: -------------------------------------------------------------------------------- 1 | using DbUp.Builder; 2 | using DbUp.Engine; 3 | using DbUp.ScriptProviders; 4 | using Optional; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Text.RegularExpressions; 11 | 12 | namespace DbUp.Cli 13 | { 14 | public static class ScriptProviderHelper 15 | { 16 | public static string GetFolder(string basePath, string path) => 17 | string.IsNullOrWhiteSpace(basePath) 18 | ? throw new ArgumentException("param can't be a null or whitespace", nameof(basePath)) 19 | : string.IsNullOrWhiteSpace(path) 20 | ? basePath 21 | : Path.IsPathRooted(path) 22 | ? path 23 | : Path.Combine(basePath, path); 24 | 25 | public static SqlScriptOptions GetSqlScriptOptions(ScriptBatch batch) => 26 | new() 27 | { 28 | ScriptType = batch.RunAlways ? Support.ScriptType.RunAlways : Support.ScriptType.RunOnce, 29 | RunGroupOrder = batch.Order 30 | }; 31 | 32 | public static Option GetFileSystemScriptOptions(ScriptBatch batch, NamingOptions naming) 33 | { 34 | if (batch == null) 35 | throw new ArgumentNullException(nameof(batch)); 36 | if (batch.Encoding == null) 37 | throw new ArgumentNullException(nameof(batch.Encoding), "Encoding can't be null"); 38 | 39 | Encoding encoding = null; 40 | try 41 | { 42 | encoding = Encoding.GetEncoding(batch.Encoding); 43 | } 44 | catch 45 | { 46 | } 47 | if(encoding == null) 48 | { 49 | try 50 | { 51 | encoding = CodePagesEncodingProvider.Instance.GetEncoding(batch.Encoding); 52 | } 53 | catch (ArgumentException ex) 54 | { 55 | return Option.None(Error.Create(Constants.ConsoleMessages.InvalidEncoding, batch.Folder, ex.Message)); 56 | } 57 | } 58 | 59 | return new CustomFileSystemScriptOptions() 60 | { 61 | IncludeSubDirectories = batch.SubFolders, 62 | Encoding = encoding, 63 | Filter = CreateFilter(batch.Filter, batch.MatchFullPath), 64 | UseOnlyFilenameForScriptName = naming.UseOnlyFileName, 65 | PrefixScriptNameWithBaseFolderName = naming.IncludeBaseFolderName, 66 | Prefix = naming.Prefix 67 | }.Some(); 68 | } 69 | 70 | public static Func CreateFilter(string filterString, bool matchFullPath = false) 71 | { 72 | if( string.IsNullOrWhiteSpace(filterString)) 73 | { 74 | return null; 75 | } 76 | 77 | filterString = filterString.Trim(); 78 | 79 | if (filterString.StartsWith("/", StringComparison.Ordinal) && filterString.EndsWith("/", StringComparison.Ordinal) && filterString.Length >= 2) 80 | { 81 | // This is a regular expression 82 | 83 | if(filterString.Length == 2) 84 | { 85 | // equals to empty filter 86 | return s => true; 87 | } 88 | 89 | // We cannot use Trim('/') because we need to trim only one symbol on either side, but preserve all other symbols. 90 | // For example, '//script//' -> should be '/script/', not 'script' 91 | filterString = filterString.Substring(1); 92 | filterString = filterString.Substring(0, filterString.Length - 1); 93 | 94 | filterString = $"^{filterString}$"; 95 | } 96 | else 97 | { 98 | filterString = WildCardToRegular(filterString); 99 | } 100 | 101 | var regex = new Regex(filterString, RegexOptions.IgnoreCase | RegexOptions.Singleline); 102 | 103 | return fullFilePath => 104 | { 105 | var filename = matchFullPath ? fullFilePath : new FileInfo(fullFilePath).Name; 106 | var res = regex.IsMatch(filename); 107 | 108 | return res; 109 | }; 110 | } 111 | 112 | static string WildCardToRegular(string value) 113 | { 114 | return "^" + Regex.Escape(value).Replace("\\?", ".").Replace("\\*", ".*") + "$"; 115 | } 116 | 117 | public static Option SelectScripts(this Option builderOrNone, IList scripts, NamingOptions naming) 118 | { 119 | if (scripts == null) 120 | throw new ArgumentNullException(nameof(scripts)); 121 | 122 | if (scripts.Count == 0) 123 | { 124 | return Option.None(Error.Create(Constants.ConsoleMessages.ScriptShouldPresent)); 125 | } 126 | 127 | foreach (var script in scripts) 128 | { 129 | if (!Directory.Exists(script.Folder)) 130 | { 131 | return Option.None(Error.Create(Constants.ConsoleMessages.FolderNotFound, script.Folder)); 132 | } 133 | } 134 | 135 | foreach (var script in scripts) 136 | { 137 | builderOrNone = builderOrNone.AddScripts(script, naming ?? NamingOptions.Default); 138 | } 139 | 140 | return builderOrNone; 141 | } 142 | 143 | static Option AddScripts(this Option builderOrNone, ScriptBatch script, NamingOptions naming) => 144 | builderOrNone.Match( 145 | some: builder => 146 | GetFileSystemScriptOptions(script, naming).Match( 147 | some: options => 148 | { 149 | builder.WithScripts( 150 | new CustomFileSystemScriptProvider( 151 | script.Folder, 152 | options, 153 | GetSqlScriptOptions(script))); 154 | return builder.Some(); 155 | }, 156 | none: error => Option.None(error)), 157 | none: error => Option.None(error)); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/dbup-cli/StringUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace DbUp.Cli 5 | { 6 | public static class StringUtils 7 | { 8 | public static string ExpandEnvironmentVariables(string name) 9 | { 10 | // Implementation grabbed fromn here: https://github.com/mono/mono/blob/master/mcs/class/corlib/System/Environment.cs 11 | 12 | if (name == null) 13 | throw new ArgumentNullException(nameof(name)); 14 | 15 | char marker = '$'; 16 | 17 | int off1 = name.IndexOf(marker); 18 | if (off1 == -1) 19 | return name; 20 | 21 | int len = name.Length; 22 | int off2; 23 | if (off1 == len - 1 || (off2 = name.IndexOf(marker, off1 + 1)) == -1) 24 | return name; 25 | 26 | var result = new StringBuilder(); 27 | result.Append(name, 0, off1); 28 | do 29 | { 30 | string var = name.Substring(off1 + 1, off2 - off1 - 1); 31 | string value = Environment.GetEnvironmentVariable(var); 32 | 33 | // If value not found, add $FOO to stream, 34 | // and use the closing $ for the next iteration. 35 | // If value found, expand it in place of $FOO$ 36 | int realOldOff2 = off2; 37 | if (value == null) 38 | { 39 | result.Append(marker); 40 | result.Append(var); 41 | off2--; 42 | } 43 | else 44 | { 45 | result.Append(value); 46 | } 47 | int oldOff2 = off2; 48 | off1 = name.IndexOf(marker, off2 + 1); 49 | // If no $ found for off1, don't look for one for off2 50 | off2 = (off1 == -1 || off2 > len - 1) ? -1 : name.IndexOf(marker, off1 + 1); 51 | // textLen is the length of text between the closing $ of current iteration 52 | // and the starting $ of the next iteration if any. This text is added to output 53 | int textLen; 54 | // If no new $ found, use all the remaining text 55 | if (off1 == -1 || off2 == -1) 56 | textLen = len - oldOff2 - 1; 57 | // If value found in current iteration, use text after current closing $ and next $ 58 | else if (value != null) 59 | textLen = off1 - oldOff2 - 1; 60 | // If value not found in current iteration, but a $ was found for next iteration, 61 | // use text from current closing $ to the next $. 62 | else 63 | textLen = off1 - realOldOff2; 64 | if (off1 >= oldOff2 || off1 == -1) 65 | result.Append(name, oldOff2 + 1, textLen); 66 | } while (off2 > -1 && off2 < len); 67 | 68 | return result.ToString(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/dbup-cli/dbup-cli.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0 5 | true 6 | 7 | 8 | 9 | net462 10 | false 11 | 12 | 13 | 14 | Exe 15 | DbUp.Cli 16 | dbup 17 | ./nupkg 18 | latest 19 | DbUp Command Line Interface 20 | Sergey Tregub 21 | 1.8.1 22 | https://github.com/drwatson1/dbup-cli 23 | 24 | Copyright (c) 2023 Sergey Tregub 25 | https://github.com/drwatson1/dbup-cli 26 | GitHub 27 | dbup database migration sqlserver postgresql mysql cockroachdb 28 | true 29 | dbup 30 | ./nupkg 31 | - Improve error reporting 32 | DbUp Command Line Interface 33 | Command line tool, that can be installed as a .Net global tool, that helps you to deploy changes to databases. It tracks which SQL scripts have been run already, and runs the change scripts that are needed to get your database up to date. 34 | 35 | LICENSE 36 | README.md 37 | 38 | 39 | 40 | DEBUG;TRACE 41 | 42 | 43 | 44 | 45 | 46 | True 47 | 48 | 49 | 50 | True 51 | \ 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | --------------------------------------------------------------------------------