first commit

This commit is contained in:
Juan Ferrer 2020-11-14 02:38:56 +01:00
commit e11a7730d9
33 changed files with 1696 additions and 0 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
node_modules
package.json
package-lock.json
README.md

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
node_modules
db.*.ini
.procs-priv.sql

60
Dockerfile Normal file
View File

@ -0,0 +1,60 @@
FROM mariadb:10.4.13
ENV MYSQL_ROOT_PASSWORD root
ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& curl -sL https://apt.verdnatura.es/conf/verdnatura.gpg | apt-key add - \
&& echo "deb http://apt.verdnatura.es/ jessie main" > /etc/apt/sources.list.d/vn.list \
&& apt-get update \
&& apt-get install -y vn-mariadb \
&& apt-get purge -y --auto-remove curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY docker/docker.cnf /etc/mysql/conf.d/
COPY \
docker/docker-init.sh \
docker/docker-temp-start.sh \
docker/docker-temp-stop.sh \
docker/docker-dump.sh \
docker/docker-start.sh \
/usr/local/bin/
RUN mkdir /mysql-data \
&& chown -R mysql:mysql /mysql-data
WORKDIR /docker-boot
COPY \
import-changes.sh \
db.ini \
dump/structure.local.sql \
dump/structure.sql \
dump/fixtures.sql \
./
RUN gosu mysql docker-init.sh \
&& docker-dump.sh structure.local \
&& docker-dump.sh structure \
&& docker-dump.sh fixtures \
&& gosu mysql docker-temp-stop.sh
COPY changes ./changes
COPY dump/fixtures.local.sql ./
ARG STAMP=unknown
RUN gosu mysql docker-temp-start.sh \
&& ./import-changes.sh \
&& docker-dump.sh fixtures.local \
&& gosu mysql docker-temp-stop.sh
RUN echo "[INFO] -> Import finished" \
&& rm -rf /docker-boot
USER mysql
ENTRYPOINT ["docker-start.sh"]
CMD ["mysqld"]
HEALTHCHECK --interval=2s --timeout=10s --retries=200 \
CMD mysqladmin ping -h 127.0.0.1 -u root --password=root || exit 1

23
Dockerfile.client Normal file
View File

@ -0,0 +1,23 @@
FROM debian:bullseye-slim
ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
mariadb-client \
libmariadb3 \
git \
jq \
iputils-ping \
dnsutils \
&& rm -rf /var/lib/apt/lists/*
COPY \
export-fixtures.sh \
export-structure.sh \
apply-changes.sh \
db.ini \
/usr/local/bin/
WORKDIR /workdir

85
README.md Normal file
View File

@ -0,0 +1,85 @@
# MyVC (MySQL Version Control)
Utilities to ease the maintenance of MySQL database versioning using a Git
repository.
## Prerequisites
Required applications.
* Git
* Node.js = 12.17.0 LTS
* Docker
## How to use
Export structure (uses production configuration).
```
$ myvc structure
```
Export fixtures (uses production configuration).
```
$ myvc fixtures
```
Export routines.
```
$ myvc routines [environment]
```
Apply changes into database.
```
$ myvc apply [-f] [-u] [environment]
```
## Basic information
Create database connection configuration files for each environment at main
project folder using the standard MySQL parameters. The predefined environment
names are *production* and *testing*.
```
db.[environment].ini
```
Structure and fixture dumps are located inside *dump* folder.
* *structure.sql*
* *fixtures.sql*
* *fixtures.local.sql*
Routines are located inside *routines* folder. It includes procedures,
functions, triggers, views and events with the following structure.
```
routines
`- schema
|- events
| `- eventName.sql
|- functions
| `- functionName.sql
|- procedures
| `- procedureName.sql
|- triggers
| `- triggerName.sql
`- views
`- viewName.sql
```
## Versions
Place your versions inside *changes* folder with the following structure.
```
changes
|- 00001-firstVersionCodeName
| |- 00-firstExecutedScript.sql
| |- 01-secondScript.sql
| `- 99-lastScript.sql
`- 00002-secondVersion
|- 00-firstExecutedScript.sql
`- 00-sameNumbers.sql
```
## Built With
* [Git](https://git-scm.com/)
* [nodejs](https://nodejs.org/)
* [docker](https://www.docker.com/)

316
apply-changes.sh Executable file
View File

@ -0,0 +1,316 @@
#!/bin/bash
FORCE=FALSE
IS_USER=FALSE
usage() {
echo "[ERROR] Usage: $0 [-f] [-u] [environment]"
exit 1
}
while getopts ":fu" option
do
case $option in
f)
FORCE=TRUE
;;
u)
IS_USER=TRUE
;;
\?|:)
usage
;;
esac
done
shift $(($OPTIND - 1))
ENV=$1
CONFIG_FILE="myvc.config.json"
if [ ! -f "$CONFIG_FILE" ]; then
echo "[ERROR] Config file not found in working directory."
exit 2
fi
DIR="$(dirname "${BASH_SOURCE[0]}")"
CODE=$(jq -r ".code" "$CONFIG_FILE")
# Production protection
if [ "$ENV" == "production" ]; then
echo ""
echo " ( ( ) ( ( ) ) "
echo " )\ ))\ ) ( /( )\ ) ( ))\ ) ( /( ( /( "
echo "(()/(()/( )\()|()/( ( )\ ) /(()/( )\()) )\())"
echo " /(_))(_)|(_)\ /(_)) )\ (((_) ( )(_))(_)|(_)\ ((_)\ "
echo "(_))(_)) ((_|_))_ _ ((_))\___(_(_()|__)) ((_) _((_)"
echo "| _ \ _ \ / _ \| \| | | ((/ __|_ _|_ _| / _ \| \| |"
echo "| _/ /| (_) | |) | |_| || (__ | | | | | (_) | . |"
echo "|_| |_|_\ \___/|___/ \___/ \___| |_| |___| \___/|_|\_|"
echo ""
if [ "$FORCE" != "TRUE" ]; then
read -p "[INTERACTIVE] Are you sure? (Default: no) [yes|no]: " ANSWER
if [ "$ANSWER" != "yes" ]; then
echo "[INFO] Aborting changes."
exit
fi
fi
fi
# Configuration file
if [ -z "$ENV" ]; then
INI_FILE="$DIR/db.ini"
else
INI_FILE="$PWD/db.$ENV.ini"
fi
if [ ! -f "$INI_FILE" ]; then
echo "[ERROR] DB config file doesn't exists: $INI_FILE"
exit 2
fi
echo "[INFO] Using config file: $INI_FILE"
# Query functions
dbQuery() {
SQL=$1
RETVAL=`echo "$SQL" | mysql --defaults-file="$INI_FILE" --silent --raw`
}
dbExec() {
SQL=$1
echo "$SQL" | mysql --defaults-file="$INI_FILE"
}
dbExecFromFile() {
FILE_PATH=$1
SCHEMA=$2
mysql --defaults-file="$INI_FILE" --default-character-set=utf8 --comments "$SCHEMA" < $FILE_PATH
}
# Fetches database version
COMMIT_SHA=$(git rev-parse HEAD)
echo "[INFO] Commit: $COMMIT_SHA"
dbQuery "SELECT number, gitCommit FROM util.version WHERE code = '$CODE'"
RETVAL=($RETVAL)
DB_VERSION=${RETVAL[0]}
DB_COMMIT=${RETVAL[1]}
echo "[INFO] Database information:"
echo "[INFO] -> Version: $DB_VERSION"
echo "[INFO] -> Commit: $DB_COMMIT"
if [[ ! "$DB_VERSION" =~ ^[0-9]*$ ]]; then
echo "[ERROR] Wrong database version."
exit 3
fi
if [[ -z "$DB_VERSION" ]]; then
DB_VERSION=10000
fi
if [ "$IS_USER" == "TRUE" ]; then
echo "[INFO] User information:"
dbQuery "SELECT LEFT(USER(), INSTR(USER(), '@') - 1)"
DB_USER=$RETVAL
echo "[INFO] -> Name: $DB_USER"
dbQuery "SELECT number, gitCommit FROM util.versionUser WHERE code = '$CODE' AND user = '$DB_USER'"
RETVAL=($RETVAL)
USER_VERSION=${RETVAL[0]}
USER_COMMIT=${RETVAL[1]}
echo "[INFO] -> Version: $USER_VERSION"
echo "[INFO] -> Commit: $USER_COMMIT"
if [ ! -z "$USER_VERSION" ]; then
if [ "$USER_VERSION" -gt "$DB_VERSION" ]; then
DB_VERSION=$USER_VERSION
DB_COMMIT=$USER_COMMIT
fi
fi
fi
# Applies changes
N_CHANGES=0
LAST_APPLIED_VERSION=$DB_VERSION
for DIR_PATH in "$PWD/changes/"*; do
DIR_NAME=$(basename $DIR_PATH)
DIR_VERSION=${DIR_NAME:0:5}
if [ "$DIR_NAME" == "README.md" ]; then
continue
fi
if [[ ! "$DIR_NAME" =~ ^[0-9]{5}(-[a-zA-Z0-9]+)?$ ]]; then
echo "[WARN] Ignoring wrong directory name: $DIR_NAME"
continue
fi
if [ "$DB_VERSION" -ge "$DIR_VERSION" ]; then
echo "[INFO] Ignoring already applied version: $DIR_NAME"
continue
fi
echo "[INFO] Applying version: $DIR_NAME"
for FILE in "$DIR_PATH/"*; do
FILE_NAME=$(basename "$FILE")
if [ "$FILE_NAME" == "*" ]; then
continue
fi
if [[ ! "$FILE_NAME" =~ ^[0-9]{2}-[a-zA-Z0-9_]+\.sql$ ]]; then
echo "[WARN] Ignoring wrong file name: $FILE_NAME"
continue
fi
echo "[INFO] -> $FILE_NAME"
dbExecFromFile "$FILE"
N_CHANGES=$((N_CHANGES + 1))
done
LAST_APPLIED_VERSION=$DIR_VERSION
done
# Applies routines
applyRoutines() {
FILES_CMD=$1
for FILE_PATH in `$FILES_CMD`; do
FILE_NAME=$(basename $FILE_PATH)
if [[ ! "$FILE_PATH" =~ ^routines/ ]]; then
continue
fi
if [[ ! "$FILE_NAME" =~ ^[a-zA-Z0-9_]+\.sql$ ]]; then
echo "[WARN] Ignoring wrong file name: $FILE_NAME"
continue
fi
FILE_REL_PATH=${FILE_PATH//routines\/}
IFS='/' read -ra SPLIT <<< "$FILE_REL_PATH"
SCHEMA=${SPLIT[0]}
NAME=${SPLIT[2]}
NAME=${NAME//\.sql/}
ROUTINE_TYPE=${SPLIT[1]}
case "$ROUTINE_TYPE" in
events)
ROUTINE_TYPE=EVENTS
;;
functions)
ROUTINE_TYPE=FUNCTION
;;
procedures)
ROUTINE_TYPE=PROCEDURE
;;
triggers)
ROUTINE_TYPE=TRIGGER
;;
views)
ROUTINE_TYPE=VIEW
;;
*)
echo "[WARN] Ignoring unknown routine type: $ROUTINE_TYPE"
continue
;;
esac
ROUTINE_NAME="\`$SCHEMA\`.\`$NAME\`"
if [[ -f "$FILE_PATH" ]]; then
ACTION="REPLACE"
else
ACTION="DROP"
fi
echo "[INFO] -> $ROUTINE_TYPE $ROUTINE_NAME: $ACTION"
if [ "$ACTION" == "REPLACE" ]; then
dbExecFromFile "$FILE_PATH" "$SCHEMA"
else
dbExec "DROP $ROUTINE_TYPE IF EXISTS $ROUTINE_NAME"
fi
ROUTINES_CHANGED=$((ROUTINES_CHANGED + 1))
done
}
echo "[INFO] Applying changed routines."
ROUTINES_CHANGED=0
PROCS_FILE=.procs-priv.sql
mysqldump \
--defaults-file="$INI_FILE" \
--no-create-info \
--skip-triggers \
--insert-ignore \
mysql procs_priv > "$PROCS_FILE"
if [[ -z "$DB_COMMIT" ]]; then
ROUTINES_CMD="find routines -type f"
else
ROUTINES_CMD="git diff --name-only $DB_COMMIT -- routines"
fi
applyRoutines "$ROUTINES_CMD"
applyRoutines "git ls-files --others --exclude-standard"
if [ "$ROUTINES_CHANGED" -gt "0" ]; then
dbExecFromFile "$PROCS_FILE" "mysql"
if [ "$?" -eq "0" ]; then
dbExec "FLUSH PRIVILEGES"
rm "$PROCS_FILE"
else
echo "[WARN] An error ocurred when restoring routine privileges, backup saved at $PROCS_FILE"
fi
echo "[INFO] -> $ROUTINES_CHANGED routines have changed."
else
echo "[INFO] -> No routines changed."
rm "$PROCS_FILE"
fi
N_CHANGES=$((N_CHANGES + ROUTINES_CHANGED))
# Displaying summary
if [ "$N_CHANGES" -gt "0" ]; then
if [ "$IS_USER" == "TRUE" ]; then
SQL=(
"INSERT INTO util.versionUser SET "
"code = '$CODE', "
"user = '$DB_USER', "
"number = '$LAST_APPLIED_VERSION', "
"gitCommit = '$COMMIT_SHA' "
"ON DUPLICATE KEY UPDATE "
"number = VALUES(number), "
"gitCommit = VALUES(gitCommit)"
)
else
SQL=(
"INSERT INTO util.version SET "
"code = '$CODE', "
"number = '$LAST_APPLIED_VERSION', "
"gitCommit = '$COMMIT_SHA' "
"ON DUPLICATE KEY UPDATE "
"number = VALUES(number), "
"gitCommit = VALUES(gitCommit)"
)
fi
dbExec "${SQL[*]}"
echo "[INFO] Changes applied succesfully."
else
echo "[INFO] No changes applied."
fi

5
config.ini Normal file
View File

@ -0,0 +1,5 @@
[client]
host = 172.17.0.1
port = 3306
user = root
password = root

30
docker-run.js Normal file
View File

@ -0,0 +1,30 @@
#!/bin/node
const execFileSync = require('child_process').execFileSync;
const spawn = require('child_process').spawn;
module.exports = function(command) {
const buildArgs = [
'build',
'-t', 'vn-db-client',
'-f', `${__dirname}/Dockerfile.client`,
`${__dirname}/`
];
execFileSync('docker', buildArgs);
let args = [
'run',
'-v', `${process.cwd()}:/workdir`,
'vn-db-client',
command
];
args = args.concat(process.argv.slice(2));
const child = spawn('docker', args, {
stdio: [
process.stdin,
process.stdout,
process.stderr
]
});
child.on('exit', code => process.exit(code));
};

204
docker.js Normal file
View File

@ -0,0 +1,204 @@
const exec = require('child_process').exec;
const log = require('fancy-log');
const dataSources = require('../loopback/server/datasources.json');
const serverImage = require(`${process.cwd()}/myvc.config.json`).serverImage;
module.exports = class Docker {
constructor(name) {
Object.assign(this, {
id: name,
name,
isRandom: name == null,
dbConf: Object.assign({}, dataSources.vn)
});
}
/**
* Builds the database image and runs a container. It only rebuilds the
* image when fixtures have been modified or when the day on which the
* image was built is different to today. Some workarounds have been used
* to avoid a bug with OverlayFS driver on MacOS.
*
* @param {Boolean} ci continuous integration environment argument
*/
async run(ci) {
let d = new Date();
let pad = v => v < 10 ? '0' + v : v;
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
await this.execP(`docker build --build-arg STAMP=${stamp} -t ${serverImage} ./db`);
let dockerArgs;
if (this.isRandom)
dockerArgs = '-p 3306';
else {
try {
await this.rm();
} catch (e) {}
dockerArgs = `--name ${this.name} -p 3306:${this.dbConf.port}`;
}
let runChown = process.platform != 'linux';
const container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} ${serverImage}`);
this.id = container.stdout.trim();
try {
if (this.isRandom) {
let inspect = await this.execP(`docker inspect -f "{{json .NetworkSettings}}" ${this.id}`);
let netSettings = JSON.parse(inspect.stdout);
if (ci)
this.dbConf.host = netSettings.Gateway;
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
}
await this.wait();
} catch (err) {
if (this.isRandom)
await this.rm();
throw err;
}
}
/**
* Does the minium effort to start the database container, if it doesn't
* exists calls the 'docker' task, if it is started does nothing. Keep in
* mind that when you do not rebuild the docker you may be using an outdated
* version of it. See the 'docker' task for more info.
*/
async start() {
let state;
try {
let result = await this.execP(`docker inspect -f "{{json .State}}" ${this.id}`);
state = JSON.parse(result.stdout);
} catch (err) {
return await this.run();
}
switch (state.Status) {
case 'running':
return;
case 'exited':
await this.execP(`docker start ${this.id}`);
await this.wait();
return;
default:
throw new Error(`Unknown docker status: ${state.Status}`);
}
}
waitForHealthy() {
return new Promise((resolve, reject) => {
let interval = 100;
let elapsedTime = 0;
let maxInterval = 4 * 60 * 1000;
log('Waiting for MySQL init process...');
async function checker() {
elapsedTime += interval;
let status;
try {
let result = await this.execP(`docker inspect -f "{{.State.Health.Status}}" ${this.id}`);
status = result.stdout.trimEnd();
} catch (err) {
return reject(new Error(err.message));
}
if (status == 'unhealthy')
return reject(new Error('Docker exited, please see the docker logs for more info'));
if (status == 'healthy') {
log('MySQL process ready.');
return resolve();
}
if (elapsedTime >= maxInterval)
reject(new Error(`MySQL not initialized whithin ${elapsedTime / 1000} secs`));
else
setTimeout(bindedChecker, interval);
}
let bindedChecker = checker.bind(this);
bindedChecker();
});
}
wait() {
return new Promise((resolve, reject) => {
const mysql = require('mysql2');
let interval = 100;
let elapsedTime = 0;
let maxInterval = 4 * 60 * 1000;
let myConf = {
user: this.dbConf.username,
password: this.dbConf.password,
host: this.dbConf.host,
port: this.dbConf.port
};
log('Waiting for MySQL init process...');
async function checker() {
elapsedTime += interval;
let state;
try {
let result = await this.execP(`docker inspect -f "{{json .State}}" ${this.id}`);
state = JSON.parse(result.stdout);
} catch (err) {
return reject(new Error(err.message));
}
if (state.Status === 'exited')
return reject(new Error('Docker exited, please see the docker logs for more info'));
let conn = mysql.createConnection(myConf);
conn.on('error', () => {});
conn.connect(err => {
conn.destroy();
if (!err) {
log('MySQL process ready.');
return resolve();
}
if (elapsedTime >= maxInterval)
reject(new Error(`MySQL not initialized whithin ${elapsedTime / 1000} secs`));
else
setTimeout(bindedChecker, interval);
});
}
let bindedChecker = checker.bind(this);
bindedChecker();
});
}
rm() {
return this.execP(`docker stop ${this.id} && docker rm -v ${this.id}`);
}
/**
* Promisified version of exec().
*
* @param {String} command The exec command
* @return {Promise} The promise
*/
execP(command) {
return new Promise((resolve, reject) => {
exec(command, (err, stdout, stderr) => {
if (err)
reject(err);
else {
resolve({
stdout: stdout,
stderr: stderr
});
}
});
});
}
};

6
docker/docker-dump.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
export MYSQL_PWD=root
FILE="/docker-boot/$1.sql"
echo "[INFO] -> Importing $FILE"
mysql -u root --default-character-set=utf8 --comments -f < "$FILE"

14
docker/docker-init.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
. /usr/local/bin/docker-entrypoint.sh
CMD=mysqld
mysql_check_config "$CMD"
docker_setup_env "$CMD"
docker_create_db_directories
docker_verify_minimum_env
docker_init_database_dir "$CMD"
docker_temp_server_start "$CMD"
docker_setup_db
docker_process_init_files /docker-entrypoint-initdb.d/*

10
docker/docker-start.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
# XXX: Workaround to avoid OverlayFS bug on MacOs
# https://docs.docker.com/storage/storagedriver/overlayfs-driver/#limitations-on-overlayfs-compatibility
if [ "$RUN_CHOWN" = "true" ]; then
chown -R mysql:mysql /mysql-data
fi
exec "$@"

7
docker/docker-temp-start.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
. /usr/local/bin/docker-entrypoint.sh
CMD=mysqld
docker_setup_env "$CMD"
docker_temp_server_start "$CMD"

7
docker/docker-temp-stop.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
. /usr/local/bin/docker-entrypoint.sh
CMD=mysqld
docker_setup_env "$CMD"
docker_temp_server_stop

8
docker/docker.cnf Normal file
View File

@ -0,0 +1,8 @@
[mysqld]
innodb_log_file_size = 4M
innodb_autoextend_increment = 4
innodb_page_size = 8K
log_bin_trust_function_creators = ON
datadir = /mysql-data
sql_mode = NO_ENGINE_SUBSTITUTION
skip-log-bin

28
export-fixtures.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
set -e
CONFIG_FILE="myvc.config.json"
DUMP_FILE="dump/fixtures.sql"
INI_FILE="db.production.ini"
if [ ! -f "$CONFIG_FILE" ]; then
echo "Config file not found in working directory."
exit 1
fi
echo "SELECT 1;" | mysql --defaults-file="$INI_FILE" >> /dev/null
echo "Exporting fixtures"
echo "" > "$DUMP_FILE"
for SCHEMA in $(jq -r ".fixtures | keys[]" "$CONFIG_FILE"); do
TABLES=( $(jq -r ".fixtures.$SCHEMA[]" "$CONFIG_FILE") )
echo " -> $SCHEMA"
echo "USE \`$SCHEMA\`;" >> "$DUMP_FILE"
mysqldump \
--defaults-file="$INI_FILE" \
--no-create-info \
--skip-triggers \
$SCHEMA ${TABLES[@]} >> "$DUMP_FILE"
done

108
export-routines.js Executable file
View File

@ -0,0 +1,108 @@
#!/usr/bin/node
const fs = require('fs-extra');
const ini = require('ini');
const mysql = require('mysql2/promise');
const ejs = require('ejs');
let cwd = process.cwd();
let env = process.argv[2];
let iniFile = env ? `db.${env}.ini` : `${__dirname}/db.ini`;
let dbConf = ini.parse(fs.readFileSync(iniFile, 'utf8')).client;
let exportDir = `${cwd}/routines`;
let config = require(`${cwd}/myvc.config.json`);
class Exporter {
constructor(objectName, callback) {
this.objectName = objectName;
this.callback = callback;
this.dstDir = `${objectName}s`;
let templateDir = `${__dirname}/templates/${objectName}`;
this.query = fs.readFileSync(`${templateDir}.sql`, 'utf8');
let templateFile = fs.readFileSync(`${templateDir}.ejs`, 'utf8');
this.template = ejs.compile(templateFile);
if (fs.existsSync(`${templateDir}.js`))
this.formatter = require(`${templateDir}.js`);
}
async export(conn, exportDir, schema) {
let res = await conn.execute(this.query, [schema]);
if (!res[0].length) return;
let routineDir = `${exportDir}/${schema}/${this.dstDir}`;
if (!fs.existsSync(routineDir))
fs.mkdirSync(routineDir);
for (let params of res[0]) {
if (this.formatter)
this.formatter(params, schema)
params.schema = schema;
let sql = this.template(params);
fs.writeFileSync(`${routineDir}/${params.name}.sql`, sql);
}
}
}
let exporters = [
new Exporter('function'),
new Exporter('procedure'),
new Exporter('view'),
new Exporter('trigger'),
new Exporter('event')
];
// Exports objects for all schemas
async function main() {
let ssl;
if (dbConf.ssl_ca) {
ssl = {
ca: fs.readFileSync(`${cwd}/${dbConf.ssl_ca}`),
rejectUnauthorized: dbConf.ssl_verify_server_cert != undefined
}
}
let conn = await mysql.createConnection({
host: !env ? 'localhost' : dbConf.host,
port: dbConf.port,
user: dbConf.user,
password: dbConf.password,
authPlugins: {
mysql_clear_password() {
return () => dbConf.password + '\0';
}
},
ssl
});
conn.queryFromFile = function(file, params) {
return this.execute(
fs.readFileSync(`${file}.sql`, 'utf8'),
params
);
}
try {
if (fs.existsSync(exportDir))
fs.removeSync(exportDir, {recursive: true});
fs.mkdirSync(exportDir);
for (let schema of config.structure) {
let schemaDir = `${exportDir}/${schema}`;
if (!fs.existsSync(schemaDir))
fs.mkdirSync(schemaDir);
for (let exporter of exporters)
await exporter.export(conn, exportDir, schema);
}
} catch(err) {
console.error(err);
} finally {
await conn.end();
}
}
main();

24
export-structure.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
set -e
CONFIG_FILE="myvc.config.json"
DUMP_FILE="dump/structure.sql"
INI_FILE="db.production.ini"
if [ ! -f "$CONFIG_FILE" ]; then
echo "Config file found in working directory."
exit 1
fi
SCHEMAS=( $(jq -r ".structure[]" "$CONFIG_FILE") )
mysqldump \
--defaults-file="$INI_FILE" \
--default-character-set=utf8 \
--no-data \
--comments \
--triggers --routines --events \
--databases \
${SCHEMAS[@]} \
| sed 's/ AUTO_INCREMENT=[0-9]* //g' \
> "$DUMP_FILE"

38
index.js Normal file
View File

@ -0,0 +1,38 @@
const getopts = require('getopts');
const colors = require('colors');
const package = require('./package.json');
const dockerRun = require('./docker-run');
const options = getopts(process.argv.slice(2), {
alias: {
dir: 'd',
env: 'e',
help: 'h'
},
default: {}
})
if (options.help) {
console.log('usage: myvc [-d|--dir] [-e|--env] [-h|--help] action');
process.exit(0)
}
let action = options._[0];
console.log('MyVC (MySQL Version Control)'.green, `v${package.version}`.blue);
console.log('Action:'.gray, action.magenta);
switch (action) {
case 'structure':
dockerRun('export-structure.sh');
break;
case 'fixtures':
dockerRun('export-fixtures.sh');
break;
case 'routines':
require('./export-routines');
break;
case 'apply':
dockerRun('apply-changes.sh');
break;
}

3
myvc.js Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env node
require('./');

590
package-lock.json generated Normal file
View File

@ -0,0 +1,590 @@
{
"name": "myvc",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@sqltools/formatter": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz",
"integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q=="
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "^1.9.0"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"async": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"denque": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
},
"ejs": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz",
"integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==",
"requires": {
"jake": "^10.6.1"
}
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"filelist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz",
"integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==",
"requires": {
"minimatch": "^3.0.4"
}
},
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"requires": {
"is-property": "^1.0.2"
}
},
"getopts": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz",
"integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA=="
},
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ="
},
"jake": {
"version": "10.8.2",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz",
"integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==",
"requires": {
"async": "0.9.x",
"chalk": "^2.4.2",
"filelist": "^1.0.1",
"minimatch": "^3.0.4"
}
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "^4.1.6"
}
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"mysql2": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz",
"integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==",
"requires": {
"denque": "^1.4.1",
"generate-function": "^2.3.1",
"long": "^4.0.0",
"lru-cache": "^6.0.0",
"named-placeholders": "^1.1.2",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"dependencies": {
"sqlstring": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz",
"integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg=="
}
}
},
"myvc": {
"version": "file:",
"requires": {
"@sqltools/formatter": "^1.2.2",
"ejs": "^3.1.5",
"fs-extra": "^8.1.0",
"getopts": "^2.2.5",
"ini": "^1.3.5",
"mysql2": "^2.2.5",
"myvc": "file:",
"require-yaml": "0.0.1"
},
"dependencies": {
"@sqltools/formatter": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz",
"integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q=="
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "^1.9.0"
}
},
"ansicolors": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
"integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk="
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"async": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"cardinal": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz",
"integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=",
"requires": {
"ansicolors": "~0.3.2",
"redeyed": "~2.1.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"denque": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
},
"ejs": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz",
"integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==",
"requires": {
"jake": "^10.6.1"
}
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"filelist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz",
"integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==",
"requires": {
"minimatch": "^3.0.4"
}
},
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"requires": {
"is-property": "^1.0.2"
}
},
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"iconv-lite": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz",
"integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q=="
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ="
},
"jake": {
"version": "10.8.2",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz",
"integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==",
"requires": {
"async": "0.9.x",
"chalk": "^2.4.2",
"filelist": "^1.0.1",
"minimatch": "^3.0.4"
}
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "^4.1.6"
}
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"mysql2": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz",
"integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==",
"requires": {
"denque": "^1.4.1",
"generate-function": "^2.3.1",
"long": "^4.0.0",
"lru-cache": "^6.0.0",
"named-placeholders": "^1.1.2",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"dependencies": {}
},
"named-placeholders": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz",
"integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==",
"requires": {
"lru-cache": "^4.1.3"
},
"dependencies": {
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
}
}
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"redeyed": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz",
"integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=",
"requires": {
"esprima": "~4.0.0"
}
},
"require-yaml": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
"integrity": "sha1-LhsY2RPDuqcqWk03O28Tjd0sMr0=",
"requires": {
"js-yaml": "^3.13.1"
}
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sqlstring": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
"integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
}
}
},
"named-placeholders": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz",
"integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==",
"requires": {
"lru-cache": "^4.1.3"
},
"dependencies": {}
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"require-yaml": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
"integrity": "sha1-LhsY2RPDuqcqWk03O28Tjd0sMr0=",
"requires": {
"js-yaml": "^3.13.1"
}
},
"seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
}

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "myvc",
"version": "1.0.0",
"author": "Verdnatura Levante SL",
"description": "MySQL version control",
"license": "GPL-3.0",
"bin": "./myvc.js",
"repository": {
"type": "git",
"url": "https://gitea.verdnatura.es/verdnatura/myvc"
},
"dependencies": {
"@sqltools/formatter": "^1.2.2",
"colors": "^1.4.0",
"ejs": "^3.1.5",
"fs-extra": "^8.1.0",
"getopts": "^2.2.5",
"ini": "^1.3.5",
"mysql2": "^2.2.5",
"myvc": "file:../myvc",
"require-yaml": "0.0.1"
}
}

8
templates/event.ejs Executable file
View File

@ -0,0 +1,8 @@
DROP EVENT IF EXISTS `<%- schema %>`.`<%- name %>`;
DELIMITER $$
CREATE DEFINER=`root`@`%` EVENT `<%- schema %>`.`<%- name %>`
ON SCHEDULE EVERY <%- intervalValue %> <%- intervalField %>
ON COMPLETION <%- onCompletion %>
<% if (status == 'ENABLED') { %>ENABLE<% } else { %>DISABLE<% } %>
DO <%- body %>$$
DELIMITER ;

17
templates/event.sql Executable file
View File

@ -0,0 +1,17 @@
SELECT
EVENT_NAME `name`,
DEFINER `definer`,
EVENT_DEFINITION `body`,
EVENT_TYPE `type`,
EXECUTE_AT `execute_at`,
INTERVAL_VALUE `intervalValue`,
INTERVAL_FIELD `intervalField`,
STARTS `starts`,
ENDS `ends`,
STATUS `status`,
ON_COMPLETION `onCompletion`,
EVENT_COMMENT `comment`,
LAST_ALTERED `modified`
FROM information_schema.EVENTS
WHERE EVENT_SCHEMA = ?

7
templates/function.ejs Executable file
View File

@ -0,0 +1,7 @@
DROP FUNCTION IF EXISTS `<%- schema %>`.`<%- name %>`;
DELIMITER $$
CREATE DEFINER='root'@'%' FUNCTION `<%- schema %>`.`<%- name %>`(<%- paramList %>)
RETURNS <%- returns %>
<% if (isDeterministic != 'NO') { %>DETERMINISTIC<% } else { %>NOT DETERMINISTIC<% } %>
<%- body %>$$
DELIMITER ;

11
templates/function.sql Executable file
View File

@ -0,0 +1,11 @@
SELECT
`name`,
`definer`,
`param_list` paramList,
`returns`,
`is_deterministic` isDeterministic,
`body`,
`modified`
FROM mysql.proc
WHERE `db` = ? AND `type` = 'FUNCTION'

5
templates/procedure.ejs Executable file
View File

@ -0,0 +1,5 @@
DROP PROCEDURE IF EXISTS `<%- schema %>`.`<%- name %>`;
DELIMITER $$
CREATE DEFINER='root'@'%' PROCEDURE `<%- schema %>`.`<%- name %>`(<%- paramList %>)
<%- body %>$$
DELIMITER ;

9
templates/procedure.sql Executable file
View File

@ -0,0 +1,9 @@
SELECT
`name`,
`definer`,
`param_list` paramList,
`body`,
`modified`
FROM mysql.proc
WHERE db = ? AND type = 'PROCEDURE'

7
templates/trigger.ejs Executable file
View File

@ -0,0 +1,7 @@
DROP TRIGGER IF EXISTS `<%- schema %>`.`<%- name %>`;
DELIMITER $$
CREATE DEFINER=`root`@`%` TRIGGER `<%- schema %>`.`<%- name %>`
<%- actionTiming %> <%- actionType %> ON `<%- table %>`
FOR EACH ROW
<%- body %>$$
DELIMITER ;

11
templates/trigger.sql Executable file
View File

@ -0,0 +1,11 @@
SELECT
TRIGGER_NAME `name`,
DEFINER `definer`,
ACTION_TIMING `actionTiming`,
EVENT_MANIPULATION `actionType`,
EVENT_OBJECT_TABLE `table`,
ACTION_STATEMENT `body`,
CREATED `modified`
FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA = ?

5
templates/view.ejs Executable file
View File

@ -0,0 +1,5 @@
CREATE OR REPLACE DEFINER = `root`@`%`
SQL SECURITY <%- securityType %>
VIEW `<%- schema %>`.`<%- name %>`
AS <%- definition %><% if (checkOption != 'NONE') { %>
WITH CASCADED CHECK OPTION<% } %>

9
templates/view.js Normal file
View File

@ -0,0 +1,9 @@
const sqlFormatter = require('@sqltools/formatter');
module.exports = function(params) {
params.definition = sqlFormatter.format(params.definition, {
indent: '\t',
reservedWordCase: 'upper'
});
}

10
templates/view.sql Executable file
View File

@ -0,0 +1,10 @@
SELECT
TABLE_NAME `name`,
VIEW_DEFINITION `definition`,
CHECK_OPTION `checkOption`,
IS_UPDATABLE `isUpdatable`,
DEFINER `definer`,
SECURITY_TYPE `securityType`
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = ?