JSPM

  • Created
  • Published
  • Downloads 12
  • Score
    100M100P100Q85257F
  • License MIT

pdcsc is a cli tool for managing .sql database repositories. It creates/updates changesets based on changes detected in feature branches, can be integrated in cicd pipelines and is able to apply changeset(s) on custom databases in cicd pipelines or even manually.

Package Exports

  • @puya/pdcsc
  • @puya/pdcsc/bin/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@puya/pdcsc) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

PDCSC - Puya Data Changeset Creator

License

This tool is licensed under MIT License - see the LICENSE file for details.

Introduction

@puya/pdcsc is a cli tool developed in nodejs for managing .sql database repositories. It creates/updates changesets based on changes detected in feature branches in their .sql files in a ./Scripts folder.

It can be integrated in cicd pipelines like gitlab pipeline and azurdevops pipeline and is able to apply changeset(s) on custom databases upon merging feature branches.

It can also be used to apply changesets on a custom database manually, making the database up-to-date with the lastest changes which can be a handy tool for support teams.

Features

  • Generate Changeset: generates changeset based on committed changes detected in a branch.
  • Test Changeset: creates a database backup and executes the changeset against that to see whether the changeset is ok or not.
  • Pipeline Mode: Using pipeline argument, it can be used in ci/cd pipelines (like gitlab or azuredevops) to provide a safe merge, preventing the merge if the changeset has errors.
  • Update database: Using apply argument, it can apply changesets(s) on a database and making the database up-to-date.

By default (withought specifying pipeline or apply arguments), @puya/pdcsc manages current branch's changeset.

Disclaimer

@puya/pdcsc IS AN IMPORTANT AND CRITICAL TOOL THAT TARGETS SQL SERVER DATABASES.

THE TOOL NEVER CHANGES ANY DATA IN TARGET DATABASES EXCEPT THE TABLE IT CREATES ITSELF TO TRACK CHANGESETS JOURNALING.

THE TOOL NEVER MANIPULATES ANY SCHEMA IN TARGET DATABASES. ALL SCHEMA CHANGES ARE PERFORMED BY DEVELOPERS WHO USE THE COMMAND AND PUT THEIR CUSTOM SCRIPTS IN THE CHANGESETS.

IT IS HIGHLY RECOMMENDED TO HAVE A DBA IN YOUR TEAM WHO PERFORMS CODE REVIEW UPON FEATURE BRANCH MERGING AND IS COMPLETELY SURE ABOUT THE CHANGES APPLYING TO YOUR DATABASES.

THIS TOOL IS DEVELOPED AS IS AND THE COMPANY AND DEVELOPERS WHO CREATED IT HAVE NO RESPONSIBILITY OVER ANY PROBLEMS HAPPEN OR CONSEQUENCES INCUR TO YOUR DATABASES.

YOU USE IT SOLELY BASED ON YOUR OWN DECISION.

PLEASE DO READ THE Best Practices and Guidelines SECTION OF THIS DOCUMENT TO FOLLOW BEST PRACTICES AND GUIDELINES.

Installation

Global

npm i @puya/pdcsc -g

Local

npm install @puya/pdcsc

Current Version

2.1.38

Usage

Once installed, you can use the pdcsc command in your terminal. You should run this tool only in the root of your database's scripts repository.

pdcsc [cmd] [arguments] [options]

Main commands

  • init: Initializes a new database repository in current path, creates a git repo in it (if no git repo found), creates default scripts folders and creates a pdcsc-config.json config file and gitlab ci/cd yaml file.
  • roll: Creates/Updates a changeset based on .sql changes in current branch in ./Scripts folder. This is the default command.
  • apply: Applies all changesets in ./Changes folder on a database (updates the database).
  • pipeline: Used in CICD pipelines, tests changeset of current branch that its merge is requested and if it succeeds, executes changeset over the database specified (making it up-to-date). If changeset execution was successful as well, it is journaled in the database (journaling is explained later in Changeset execution history section).
  • render: Renders a changeset and creates a .sql file for that (overwrites existing .sql file, but does not commit it)
  • check-update: checks whether a new version for pdcsc is available or not.

CLI arguments

  • -v or --version: Shows pdcsc version.
  • -? or --help: Shows pdcsc help.
  • -c or --config: specifying custom config file
  • -s or --server: database server address.
  • -u or --user: database user.
  • -p or --password: database password.
  • -d or --database: target database.
  • -e or --encrypt: encrypt database connection or not (default is false).
  • -dbm or --debug-mode: debug mode
  • -dbl or --debug-level: debug level

Note: -s, -u, -p, -d and -e cli args have more priority over same database settings in pdcsc-config.json config.

Debug Levels

  • 1: log app execution flow (default)
  • 2: show local variables
  • 3: show loop variables and more detailed variables
  • 4: show db queries
  • 5: show large db queries
  • 6: show used pdcsc-config
  • 7: show deepest variables (rarely used)
  • 8: resered
  • 9: show detailed exceptions and errors (expanded stack trace)

Examples

  1. Initializing a new database repository:
pdcsc -init

Initializing a new database repository with full config:

pdcsc -init -f
  1. Creating/Updating current feature branch's changeset:
pdcsc roll

or simply ...

pdcsc
  1. Updating master database upon merge requests in CI/CD:
pdcsc pipeline
  1. Manually updating an existing database
pdcsc apply -d MyDb
  1. Specifying database setting through cli:
pdcsc -s "192.168.10.120" -u "myUser" -p "myPassword" -d "MyDb"

As it was stated, database settings specified through cli have more priority over config file.

Configuration

The behavior of pdcsc can be customized through its config file.

The config file is named pdcsc-config.json file. It is automatically created upon initializing a new pdcsc repository using -init command. The file is placed at the root of the repo.

Here's an example of a simple pdcsc configuration file:

{
  "database": {
    "server": "localhost",
    "user": "db_user",
    "password": "db_password",
    "database": "my_database",
    "encrypt": false
  },
  "pipeline": "gitlabs",
  "masterBranchName": "origin/main"
}

Properties

The full pdcsc config file with all its options is as follows:

{
    "database": {
        "server": "...",	  // database server address (default = 'localhost')
        "user": "...",		  // database userid
        "password": "...",	// database password
        "database": "...",	  // master database name
        "encrypt": "..."	  // encrypt connection or not
    },
    "pipeline": "...",			    // pipeline type (gitlabs = default, azuredevops)
    "masterBranchName": "...",		// master branch name (default = 'origin/main')
    "appVersionSprocName": "..."	// appVersion sp name (default = 'dbo.getAppVersion')
    "appVersionFormat": "...",		// app version timestamp (default = 'YYYY-MM-DD HH:mm:ss')
    "timestampLocale": "...",		// timestamp locale (default = 'en')
    "changesetsTableName": "...",	// database changeset history table (default = 'dbo.Changesets')
    "backupDbName": "...",			// name of temp database when testing changesets (default = 'TempBackupDB')
    "defaultCodePage": "",			// default .sql files codepage (default = 'utf-8')
    "paths": {
        "backupDir": "...",				// default backup dir on database server (default = 'C:\\temp\\')
        "changesetFolderName": "...",	// changesets folder name (default = 'Changes')
        "scriptsFolderName": "...",		// .sql scripts folder name (default = 'Scripts')
    },
    "folders": {
        "procedures": "...",	// name of procedures folder (default = 'Procedures')
        "functions": "...",	// name of user-defined functions folder (default = 'Functions')
        "tables": "...",		// name of tables folder (default = 'Tables')
        "relations": "...",	// name of relations folder (default = 'Relations')
        "types": "...",		// name of user-defined types folder (default = 'Types')
        "views": "...",		// name of views folder (default = 'Views')
        "indexes": "...",	// name of indexes folder (default = 'Indexes')
        "triggers": "...",	// name of triggers folder (default = 'Triggers')
        "schemas": "..."		// name of schemas folder (default = 'Schemas')
    }
}

Customization

We can customize pdcsc configuration using an environment variable named PDCSC_CONFIG_MODE.

If pdcsc detects such envionment variable, it checks whether a pdcsc-config.{env.PDCSC_CONFIG_MODE}.config file exists or not. If so, it merges that file with pdcsc-config.json file.

This, enables us to customize master branch name or database name based on env or store sensitive data such as database password in a customized pdcsc config file.

In the second usage, we can add pdcsc-config.{env.PDCSC_CONFIG_MODE}.json in the .gitignore, so that the database password is not stored in the repository.

Using pdcsc in gitlab CI/CD pipeline

In GitLab, we can create a custom CI/CD pipeline, and use pdcsc in it with pipeline argument to ensure our database is updated automatically whenever a feature branche is merged.

Here is a sample gitlab pipeline:

stages:
  - build

variables:
  GIT_DEPTH: 0

before_merge_build:
  stage: build
  image: node:alpine
  script:
    - echo "Installing dependencies..."
    - apk update && apk add git
    - npm i @puya/pdcsc -g
    - |
      if [ "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" = "dev" ]; then
        echo "updating database ..."
        pdcsc apply -c "pdcsc-config-${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}.json" -dbm -f
      else
        echo "checking branch changeset before merge ..."
        pdcsc pipeline -c "pdcsc-config-${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}.json" -dbm
      fi
  rules:
    - when: manual`

Notes:

  • Here, we assumed we have one stage branch, named dev.
  • We merge our feature branches to dev, not main.
  • The dev branch is our development stage where incomplete features are pushed and tested.
  • This way, we do not push incomplete/not-tested features directly to main branch.
  • Whenever we are ok with our dev, we merge it to main branch (bringing features to production).
  • Upon merging dev to main, previous features are already merged into dev, there is no need to use pipeline switch.
  • We use apply switch instead and update master database (apply changeset files from dev upon master database).
  • In our pipeline, we explicitly specify config file for pdcsc through -c switch.
  • Name of the config file depends on the source branch that is going to be merged.
  • If it is dev, we are merging dev into main.
  • So, target branch (CI_MERGE_REQUEST_TARGET_BRANCH_NAME) should be main. We specify a config file named pdcsc-config-{CI_MERGE_REQUEST_TARGET_BRANCH_NAME}.json which would be pdcsc-config-main.json. So, the master database will be updated.
  • If the source branch is not dev, we are mereging a feature branch.
  • So, we know that our target branch is dev.
  • This time, the config file pdcsc-config-{CI_MERGE_REQUEST_TARGET_BRANCH_NAME}.json would be pdcsc-config-dev.json.
  • So, the development database will be updated.

Note that, we should have pdcsc-config-dev.json and pdcsc-config-main.json files in our repo.

pdcsc-config-dev.json

{
  "database": {
    "database": "MyDb_dev"
  },
  "masterBranchName": "origin/dev"
}

pdcsc-config-main.json

{
  "database": {
    "database": "MyDb_main"
  },
  "masterBranchName": "origin/main"
}

Speed-up pipeline

We can create a Docker container, install Node.js and Git in it, so that these steps are not executed over and over again. This can speed up pipeline execution.

stages:
  - build

variables:
  GIT_DEPTH: 0
  DOCKER_REGISTRY: "our-node-and-git-docker-address:port"

before_merge_build:
  stage: build
  image: "${DOCKER_REGISTRY}/our-docker-registry/node-git"
  script:
    ...

Updating a database

Using apply argument we can execute all changesets against a database and update it with the latest changes we have.

pdcsc apply -d MyDb

Changesets Journal

pdcsc uses a table named dbo.Changesets in databases in order to save history of executed changesets.

After a changeset executes successfully, pdcsc inserts its name into dbo.Changesets table. This is called journaling.

Before pdcsc executes a changeset on a database, it checks dbo.Changesets table to see whether the changeset is already executed or not.

If such table does not exist, it shows and error and exits.

Using -f or --force cli argument, we can ask pdcsc to create such table if it does not exits.

pdcsc apply -d MyDb

Output:

Journal table dbo.Changesets not found. Use -f or --force to create journal table.
pdcsc apply -d MyDb -f

This behavior (manually use -f or force mode) is intentional in order to avoid updating an old database that is far behind our changesets and other updates should be applied on that before hand.

The name of journal table can be customized in pdcsc config file through changesetsTableName prop.

Update mode

The apply command has 3 modes which can be customized through -m argument:

  • Test: test changesets against a backup of the database. This is useful when we want to make sure whether changesets will work correctly on the database or not.
  • TestAndUpdate (default): test changesets first and if they were ok, update database.
  • Update: execute changesets directly against the database.

Ideally, we should use a TestAndUpdate mode as it is the default mode. However, if we are completely sure about our changesets or the test phase takes a long time (database is very large or under heavy load and backup/restore will take a long time), we can directly execute them on the database.

Example 1: only test changesets on a backup of database, not directly on database.

pdcsc apply -d MyDb -m Test

Example 2: apply changesets directly on database, do not test them beforehand.

pdcsc apply -d MyDb -m Update

Test changesets one by one

By default, pdcsc apply creates a bundle out of changesets and executes the bundle against a database backup.

Using -11 or --one-by-one cli argument we can ask pdcsc to test changesets one by one.

This can better highlight faulting changesets in case of errors.

pdcsc apply -d MyDb -11

Manually render a changeset

By default pdcsc renders or generates .sql file of a changeset when using roll command (default).

Using render cli argument we can manually render a changeset.

Attention

MANUALLY RENDERING A CHANGESET IS NOT RECOMMENDED AND SHOULD BE AVOIDED AT ALL COSTS. RENDERING A CHANGESET SHOULD ONLY AND ONLY BE DONE EXACTLY IN THE BRANCH IT WAS CREATED AT.

If you render a changeset in another branch, the generated .sql may not be correct, may not be even generated and may not work or may lead to unwanted errors, bugs and disasters at worst case.

Discussion

Suppose we are in branch feature/fix-product-update and we fix a sproc named usp_Product_update.

We generate a changeset, make a PR and the Team Lead in who performs code reviews merges the branch.

Now, if we switch to branch feature/create-reports and we have not pulled our main branch to receive the changes, if we intend to render the changeset of feature/fix-product-update branch, the .sql file being generated definitely is not correct, since we are creating usp_Product_update sproc using the copy in our own branch which is not up-to-date.

That is why, it is never recommended to manually render a changeset and this should be done in scarse cases and performed only by DBAs who know what they are doing.

How to

We can specify the changeset for which we intend to create .sql file using -cs cli argument.

pdcsc render -cs 20250412082457_b6775a321_feature-add-otp

The -cs argument is optional. If it is not specified, pdcsc shows list of all changesets found in ./Changes folder and asks to choose which one to render.

Best Practices and Guidelines

  1. Do not store database password directly in pdcsc-config.json so that it is not stored in your source-control. Instead, use customized configs in the way described in Config customization section.
  2. Use a dev and/or test stage in your development workflow and do not directly push/merge on master/main branch.
  3. Employ a Sql Server DBA and Team Lead in your team who performs code review on feature branch merging and accepts merge only when he feels everything is all right.
  4. Use a separate database for dev/test and main/master branches.
  5. If possible, use a separate server for main/master database, other than dev/test server.
  6. In your pipelines, use pdcsc pipeline for merging feature PRs and pdcsc apply for merging dev/test branches with main/master branch.
  7. Do not change/alter main/master database directly. Let cicd pipelines and pdcsc update your database automatically.
  8. Merge dev/test branch with main/master branch only when you really intend to bring changesets to production.
  9. Do not use sa and/or sysadmin users in dev/test stages.
  10. Use a less privilaged user in dev/test stages who can access only to development and test databases, not master/main database.
  11. Use sa and/or sysadmin users in main/master branch who only DBAs have access to.
  12. Never change/manipulate old changesets that fall behind other branches.
  13. Never render existing changesets in a branch other than the branch they were created in. This can produce incorrect script, resulting in bugs, errors, data loss or any other bad consequence.
  14. Do not remove feature branches immediately upon merge in your pipelines.
  15. Keep feature branches for a period of time (like two or three weeks), so that you can refer to them and render their changes later if needed.
  16. Dispose of feature branches only when you are sure the branches are ok and have no error and you will not return back to them in the future.