#!/bin/dash #version="$(psql "$DB_URL" -c "SELECT version FROM hectic.version WHERE name = 'migrator';")" # error codes # 1 - generic error # 2 - ambiguous, when you try to use something that cannot be used in same time # 3 - missing required argument / variable # 4 - # 5 - provided table that not exists # 9 - argument or command not found # 13 - program bug / unexpected system / database incompatibles # 127 - command not found (dependency) set -eu if ! command -v psql >/dev/null; then log error "Required tool (psql) are not installed." exit 127 fi VERSION='0.0.1' MIGRATION_DIR="${MIGRATION_DIR:-migration}" quote() { printf "'%s'" "$(printf %s "$1" | sed "s/'/'\\\\''/g")"; } REMAINING_ARS= # cat filename | sha256sum() # sha256sum(filename) sha256sum() { local file file="${1:-'-'}" cksum --algorithm=sha256 --untagged "$file" | awk '{printf $1}' } while [ $# -gt 0 ]; do log debug "arg: $1" case $1 in migrate|create|fetch|list|init) [ "${SUBCOMMAND+x}" ] && { log error "ambiguous subcommand, decide ${WHITE}$SUBCOMMAND ${NC}or ${WHITE}$1"; exit 2; } SUBCOMMAND=$1 shift ;; --migration-dir|-d) MIGRATION_DIR=$2 shift 2 ;; --inherits) INHERITS_LIST="${INHERITS_LIST+$INHERITS_LIST\"}$2" shift 2 ;; --*|-*) REMAINING_ARS="$REMAINING_ARS $(quote "$1")"; shift ;; # unknown global -> pass through *) REMAINING_ARS="$REMAINING_ARS $(quote "$1")"; shift ;; esac done [ ${INHERITS_LIST+x} ] && INHERITS_LIST="$(printf '%s' "$INHERITS_LIST" | sed -E 's/"/,/g; s/([^,]+)/"\1"/g')" # shellcheck disable=SC2120 init() { while [ $# -gt 0 ]; do case $1 in --dry-run) INIT_DRY_RUN=1 shift ;; --db-url|-u) DB_URL="$2" shift 2 ;; --set|-v) VARIABLE_LIST="${VARIABLE_LIST+$VARIABLE_LIST }$2" shift 2 ;; --*|-*) printf 'init argument %s does not exists' "$1" exit 9 ;; *) printf 'init command %s does not exists' "$1" exit 9 ;; esac done [ "${INIT_DRY_RUN+x}" ] && { printf '%s\n' "$(init_sql)"; exit; } error_handler_no_db_url psql_args="$(form_psql_args)" [ ${INHERITS_LIST+x} ] && { oldIFS="$IFS" IFS=',' check_inherits= for table in $INHERITS_LIST; do check_inherits="$(printf '%s\nSELECT 1 FROM %s LIMIT 1;' "$check_inherits" "$table")" done IFS="$oldIFS" check_inherits=$(printf '%s\n' \ 'BEGIN;' \ "$check_inherits" \ 'COMMIT;') # shellcheck disable=SC2086 if ! psql $psql_args -c "$check_inherits"; then log error "init failed: ${WHITE}one of inherits table does not exists: ${CYAN}$INHERITS_LIST" exit 5 fi } # shellcheck disable=SC2086 if ! psql $psql_args -c "$(init_sql)"; then log error "init failed" exit 13 fi } # error_handler_no_db_url() error_handler_no_db_url() { [ "${DB_URL+x}" ] || { log error "no ${WHITE}DB_URL${NC} or ${WHITE}--db-url${NC} specified"; exit 3; } } init_sql() { local sql inherits= [ ${INHERITS_LIST+x} ] && inherits="$(printf 'INHERITS(%s)' "$INHERITS_LIST")" sql="$(cat < pass through *) MIGRATOR_REMAINING_ARS="$MIGRATOR_REMAINING_ARS $(quote "$1")"; shift ;; esac done log debug "migrate REMAINING_ARGS: $WHITE$MIGRATOR_REMAINING_ARS" [ "${FORCE+x}" ] && { log error "migrate --force not implemented" exit 1 } init fs_migrations=$(migration_list) db_migrations=$( psql "$DB_URL" --no-align --tuples-only --quiet \ --command "SELECT name FROM hectic.migration ORDER BY name ASC" \ | awk NF ) log debug "db mig: $db_migrations" db_mig_count=$(printf '%s' "$db_migrations" | wc -l) log debug "mig count: $db_mig_count" # Check if the DB migrations form a proper prefix of disk migrations # (meaning all DB-applied migration filenames should appear in the same order at the start). i=0 for db_migration in $db_migrations; do fs_migration=$(printf '%s' "$fs_migrations" | sed -n "$((i+1))p") if [ -z "$fs_migration" ] || [ "$fs_migration" != "$db_migration" ]; then if [ -z "$FORCE" ]; then log error "unrelated migration tree detected. Use --force to proceed." exit 2 else log error "unrelated migration tree forced. Proceeding..." break fi fi i=$((i+1)) done eval "set -- $MIGRATOR_REMAINING_ARS" target_migration="$("migrate_$MIGRATE_SUBCOMMAND" "$@")" log debug "target_migration: ${target_migration}" index_of } idx_of() { name=$1 [ -z "$name" ] && { echo 0; return; } i=1 printf '%s\n' "$fs_migrations" | while IFS= read -r m; do [ "$m" = "$name" ] && { echo "$i"; return; } i=$((i+1)) done echo 0 } form_psql_args() { psql_args="-d $DB_URL -v ON_ERROR_STOP=1" for var in ${VARIABLE_LIST:-}; do psql_args="$psql_args -v $var" done } migrate_inner() { printf '%s\n' "$fs_migrations" | while IFS= read -r fs_migration; do # skip already applied migrations printf '%s' "$db_migrations" | grep -qxF "$fs_migration" && continue psql_args="$(form_psql_args)" direction=1 mig_direction=$([ "$direction" -gt 0 ] && printf 'up.sql' || printf 'down.sql') escaped_name=$(printf '%s' "$fs_migration" | sed "s/'/''/g") mig_path=$(printf '%s/%s/%s' "$MIGRATION_DIR" "$fs_migration" "$mig_direction") escaped_path=$(printf '%s' "$mig_path" | sed "s/'/''/g") log trace "mig name: $escaped_name; mig path: $escaped_path" # shellcheck disable=SC2086 if ! psql $psql_args < "$file_path" log notice "created migration: ${WHITE}${file_path}${NC}" } fetch() { while [ $# -gt 0 ]; do case $1 in --db-url|-u) # shellcheck disable=SC2034 DB_URL=$2 shift 2 ;; esac done error_handler_no_db_url } list() { while [ $# -gt 0 ]; do case $1 in --raw|-r) RAW=1 shift ;; --*|-*) log error "init argument $1 does not exists" exit 9 ;; *) log error "init subcommand $1 does not exists" exit 9 ;; esac done [ "${RAW+x}" ] && { migration_list exit } migration_list | while read -r name; do dir="./${MIGRATION_DIR}/${name}" up="$dir/up.sql" down="$dir/down.sql" if [ ! -f "$up" ] || [ ! -f "$down" ]; then echo "$name: missing $( [ ! -f "$up" ] && echo up.sql ) $( [ ! -f "$down" ] && echo down.sql )" else echo "$name" fi done } generate_word() { C="b c d f g h j k l m n p r s t v w z" V="a e i o u" N=${N:-5} w= for i in $(seq 3); do c=$(echo "$C" | tr ' ' '\n' | shuf -n1) v=$(echo "$V" | tr ' ' '\n' | shuf -n1) w="${w}${c}${v}" done printf '%s' "$w" } log debug "subcommand: $WHITE$SUBCOMMAND" log debug "subcommand args: $WHITE$REMAINING_ARS" eval "set -- $REMAINING_ARS" "$SUBCOMMAND" "$@"