#!/bin/sh
# gtid-merge -- compute the gtid_slave_pos a replica needs after loading
# a dump, and print it. It does not set it.
#
# SET GLOBAL gtid_slave_pos replaces the whole position list, one entry
# per GTID domain, and there is no way to update a single domain. On a
# replica with several replication connections, writing the dump's
# position alone therefore drops the other connections' positions.
#
# This takes the domains named by the dump from the dump, and keeps
# every other domain as the replica has it. No domain id is written
# here, so a multi-source master needs no special case.
#
#   gtid-merge DUMP [CLIENT ARGUMENT...]
#
# Arguments after DUMP go to the client unchanged, so -p, --host and
# --user work as they do for mariadb. The client is run once, which is
# why the password can be left to it:
#
#   pos=$(gtid-merge dump.sql -p --host=replica)
#   mariadb -e "SET GLOBAL gtid_slave_pos='$pos'"
#
# $MARIADB names the client if it is not `mariadb`.
#
# The dump must come from mariadb-dump --gtid. Without it there is no
# position, and this fails rather than print an empty value: written to
# gtid_slave_pos, that clears the replica's position.
#
# Only the position goes to stdout, so the output substitutes cleanly;
# everything else goes to stderr.
#
# Exit 0 on success, 1 on a dump or server problem, 2 on usage.
# https://www.sdalu.com/sysadmin/mariadb/

set -e

prog=${0##*/}

usage() {
	cat >&2 <<EOF
usage: $prog DUMP [CLIENT ARGUMENT...]
  DUMP    a dump produced by mariadb-dump --gtid
  the rest goes to the client unchanged: -p, --host, --user and the
  others behave as they do for mariadb.

prints the merged gtid_slave_pos, it does not set it:
  pos=\$($prog dump.sql -p --host=replica)
EOF
	exit 2
}

case ${1:-} in
-\?|--help)	usage ;;
esac
[ $# -ge 1 ] || usage
dump=$1
shift

[ -r "$dump" ] || {
	echo "$prog: cannot read $dump" >&2
	exit 1
}

# The dump's own position, with every domain it lists. Matched whether
# mariadb-dump wrote the line as a statement or a comment, and the scan
# stops at the first match rather than reading the whole dump. Checked
# before connecting, so an unusable dump costs no password prompt.
pos=$(sed -n "s/.*gtid_slave_pos='\([^']*\)'.*/\1/p; /gtid_slave_pos=/q" "$dump")
[ -n "$pos" ] || {
	echo "$prog: no GTID position in $dump; was it dumped with --gtid?" >&2
	exit 1
}

# $MARIADB is unquoted so it can carry arguments; "$@" is quoted so the
# caller's arguments survive spaces.
cur=$(${MARIADB:-mariadb} "$@" -NB -e 'SELECT @@gtid_slave_pos') || {
	echo "$prog: cannot read @@gtid_slave_pos from the server" >&2
	exit 1
}

# Domains named by the dump win, the rest are carried over. The domain
# is the first dash-separated field, compared whole, so domain 1 is not
# taken for a prefix of domain 11.
printf '%s\n' "$cur" | awk -F, -v pos="$pos" '
	BEGIN {	n = split(pos, p, ",")
		for (i = 1; i <= n; i++) {
			split(p[i], f, "-")
			seen[f[1]] = 1
			out = out sep p[i]; sep = ","
		} }
	      {	for (i = 1; i <= NF; i++) {
			split($i, f, "-")
			if (!(f[1] in seen)) { out = out sep $i; sep = "," }
		} }
	END   {	print out }'
