chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:32:25 +08:00
commit e014feafe1
2285 changed files with 1131979 additions and 0 deletions
+306
View File
@@ -0,0 +1,306 @@
#!/bin/bash
# Doltgresql compatibility test runner.
#
# Downloads specified doltgresql release binaries, creates test repositories
# using them, and runs BATS test suites to verify backward, forward, and
# bidirectional compatibility.
#
# Usage: ./runner.sh
# Run from the integration-tests/compatibility directory.
#
# Environment variables (optional):
# DOLTGRES_SKIP_BACKWARD — skip backward-compatibility tests if set
# DOLTGRES_SKIP_FORWARD — skip forward-compatibility tests if set
# DOLTGRES_SKIP_BIDIR — skip bidirectional-compatibility tests if set
set -eo pipefail
PLATFORM_TUPLE=""
# ---------------------------------------------------------------------------
# Platform detection
# ---------------------------------------------------------------------------
get_platform_tuple() {
local OS ARCH
OS=$(uname)
ARCH=$(uname -m)
if [ "$OS" != Linux ] && [ "$OS" != Darwin ]; then
echo "tests only support linux or macOS." >&2
exit 1
fi
if [ "$OS" = Linux ]; then
PLATFORM_TUPLE=linux
else
PLATFORM_TUPLE=darwin
fi
if [ "$ARCH" = x86_64 ]; then
PLATFORM_TUPLE="${PLATFORM_TUPLE}-amd64"
elif [ "$ARCH" = arm64 ] || [ "$ARCH" = aarch64 ]; then
PLATFORM_TUPLE="${PLATFORM_TUPLE}-arm64"
else
echo "unsupported architecture: $ARCH" >&2
exit 1
fi
echo "$PLATFORM_TUPLE"
}
# ---------------------------------------------------------------------------
# Release download
# ---------------------------------------------------------------------------
# download_release <version>
# Downloads doltgresql-<platform>.tar.gz for the given version tag, extracts it
# into binaries/<version>/, and prints the path to the bin directory.
download_release() {
local ver="$1"
local dirname="binaries/$ver"
mkdir -p "$dirname"
local basename="doltgresql-${PLATFORM_TUPLE}"
local filename="${basename}.tar.gz"
local filepath="${dirname}/${filename}"
local url="https://github.com/dolthub/doltgresql/releases/download/${ver}/${filename}"
echo "Downloading doltgresql ${ver} for ${PLATFORM_TUPLE} ..." >&2
curl -L -o "$filepath" "$url"
tar -zxf "$filepath" -C "$dirname"
# Binary lives at doltgresql-<os>-<arch>/bin/doltgres
echo "${dirname}/${basename}/bin"
}
# ---------------------------------------------------------------------------
# Server management helpers used by the runner (not BATS)
# ---------------------------------------------------------------------------
_pick_port() {
for i in {0..99}; do
local port=$((RANDOM % 4096 + 2048))
if ! nc -z localhost "$port" 2>/dev/null; then
echo "$port"
return 0
fi
done
echo "ERROR: could not find a free port" >&2
return 1
}
_write_config() {
local dir="$1" port="$2"
cat > "$dir/runner-config.yaml" <<EOF
log_level: warning
behavior:
read_only: false
disable_client_multi_statements: false
listener:
host: localhost
port: $port
EOF
}
# start_server <binary> <datadir> <logfile> → sets RUNNER_SERVER_PID and RUNNER_SERVER_PORT
start_server() {
local binary="$1" datadir="$2" logfile="$3"
RUNNER_SERVER_PORT=$(_pick_port)
_write_config "$datadir" "$RUNNER_SERVER_PORT"
PGPASSWORD=password "$binary" -data-dir="$datadir" \
--config="$datadir/runner-config.yaml" > "$logfile" 2>&1 &
RUNNER_SERVER_PID=$!
local end=$((SECONDS + 20))
while [ $SECONDS -lt $end ]; do
if PGPASSWORD=password psql -U postgres -h localhost -p "$RUNNER_SERVER_PORT" \
-c "SELECT 1;" postgres >/dev/null 2>&1; then
return 0
fi
sleep 0.5
done
echo "ERROR: server failed to start on port $RUNNER_SERVER_PORT" >&2
cat "$logfile" >&2
kill "$RUNNER_SERVER_PID" 2>/dev/null
return 1
}
stop_server() {
if [ -n "$RUNNER_SERVER_PID" ]; then
kill "$RUNNER_SERVER_PID" 2>/dev/null || true
wait "$RUNNER_SERVER_PID" 2>/dev/null || true
RUNNER_SERVER_PID=""
RUNNER_SERVER_PORT=""
fi
}
RUNNER_SERVER_PID=""
RUNNER_SERVER_PORT=""
# ---------------------------------------------------------------------------
# Repository setup
# ---------------------------------------------------------------------------
# setup_repo <label> [<binary>]
# Creates a test repository under repos/<label>/ using the given binary
# (defaults to doltgres from PATH). Sets REPO_DIR to the resulting directory.
setup_repo() {
local label="$1"
local binary="${2:-doltgres}"
REPO_DIR="$(pwd)/repos/${label}"
mkdir -p "$REPO_DIR"
./test_files/setup_repo.sh "$REPO_DIR" "$binary"
}
# ---------------------------------------------------------------------------
# Version list helpers
# ---------------------------------------------------------------------------
list_backward_compatible_versions() {
grep -v '^ *#' < test_files/backward_compatible_versions.txt
}
list_forward_compatible_versions() {
grep -v '^ *#' < test_files/forward_compatible_versions.txt
}
# ---------------------------------------------------------------------------
# Test runners
# ---------------------------------------------------------------------------
test_backward_compatibility() {
local ver="$1"
local bin
bin=$(download_release "$ver")
echo "=== Backward compat: creating repo with doltgresql ${ver} ==="
setup_repo "$ver" "${bin}/doltgres"
echo "=== Backward compat: testing HEAD doltgresql against repo from ${ver} ==="
DOLTGRES_TEST_BIN="$(which doltgres)" \
REPO_DIR="$(pwd)/repos/${ver}" \
bats --print-output-on-failure ./test_files/bats/
# Backward-only workflows (test_files/bats/backwards/): tests that only make
# sense with the old binary writing first and HEAD continuing. Each test
# manages its own server lifecycle and needs both binaries.
if [ -d ./test_files/bats/backwards ]; then
local scratch="$(pwd)/repos/${ver}-backward-workflow"
mkdir -p "$scratch"
echo "=== Backward workflow: old=${ver}, new=HEAD ==="
DOLTGRES_LEGACY_BIN="${bin}/doltgres" \
DOLTGRES_NEW_BIN="$(which doltgres)" \
REPO_DIR="$scratch" \
bats --print-output-on-failure ./test_files/bats/backwards/
fi
}
test_forward_compatibility() {
if [ -z $1 ]; then
return
fi
local ver="$1"
local bin
bin=$(download_release "$ver")
echo "=== Forward compat: testing doltgresql ${ver} against repo from HEAD ==="
# repos/HEAD was already created by the main flow (see _main).
DOLTGRES_TEST_BIN="${bin}/doltgres" \
REPO_DIR="$(pwd)/repos/HEAD" \
bats --print-output-on-failure ./test_files/bats/
}
test_bidirectional_compatibility() {
if [ -z $1 ]; then
return
fi
local ver="$1"
local bin
bin=$(download_release "$ver")
local head_bin
head_bin="$(which doltgres)"
# Forward direction: old = released version, new = HEAD
local scratch_fwd="$(pwd)/repos/${ver}-bidir-forward"
mkdir -p "$scratch_fwd"
echo "=== Bidirectional (forward): old=${ver}, new=HEAD ==="
DOLTGRES_LEGACY_BIN="${bin}/doltgres" \
DOLTGRES_NEW_BIN="$head_bin" \
REPO_DIR="$scratch_fwd" \
bats --print-output-on-failure ./test_files/bats/bidirectional/bidirectional_compat.bats
# Reverse direction: old = HEAD, new = released version
local scratch_rev="$(pwd)/repos/${ver}-bidir-reverse"
mkdir -p "$scratch_rev"
echo "=== Bidirectional (reverse): old=HEAD, new=${ver} ==="
DOLTGRES_LEGACY_BIN="$head_bin" \
DOLTGRES_NEW_BIN="${bin}/doltgres" \
REPO_DIR="$scratch_rev" \
bats --print-output-on-failure ./test_files/bats/bidirectional/bidirectional_compat.bats
}
# ---------------------------------------------------------------------------
# Cleanup
# ---------------------------------------------------------------------------
cleanup() {
stop_server
rm -rf repos binaries
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
_main() {
PLATFORM_TUPLE=$(get_platform_tuple)
mkdir -p repos binaries
trap cleanup EXIT
# --- Backward compatibility ---
if [ -z "$DOLTGRES_SKIP_BACKWARD" ] && [ -s "test_files/backward_compatible_versions.txt" ]; then
echo "=== Running backward compatibility tests ==="
while IFS= read -r ver; do
test_backward_compatibility "$ver"
done < <(list_backward_compatible_versions)
fi
# --- Create HEAD repo (used for forward compat and the sanity check) ---
echo "=== Creating HEAD repo ==="
setup_repo HEAD
# --- Forward compatibility ---
if [ -z "$DOLTGRES_SKIP_FORWARD" ] && [ -s "test_files/forward_compatible_versions.txt" ]; then
echo "=== Running forward compatibility tests ==="
while IFS= read -r ver; do
test_forward_compatibility "$ver"
done < <(list_forward_compatible_versions)
fi
# --- Bidirectional compatibility (uses forward_compatible_versions list) ---
if [ -z "$DOLTGRES_SKIP_BIDIR" ] && [ -s "test_files/forward_compatible_versions.txt" ]; then
echo "=== Running bidirectional compatibility tests ==="
while IFS= read -r ver; do
test_bidirectional_compatibility "$ver"
done < <(list_forward_compatible_versions)
fi
# --- Sanity check: HEAD against HEAD ---
echo "=== Sanity check: HEAD doltgresql against HEAD repo ==="
DOLTGRES_TEST_BIN="$(which doltgres)" \
REPO_DIR="$(pwd)/repos/HEAD" \
bats --print-output-on-failure ./test_files/bats/compatibility.bats
DOLTGRES_TEST_BIN="$(which doltgres)" \
REPO_DIR="$(pwd)/repos/HEAD" \
bats --print-output-on-failure ./test_files/bats/types_compatibility.bats
}
_main
@@ -0,0 +1,7 @@
# Doltgresql versions to test backward compatibility against.
# Current HEAD must be able to read and write repos created by these older versions.
# Format: one version tag per line, e.g. v0.8.0
# Keep this list reasonably short; each entry downloads a binary and runs the full
# test suite, so CI time grows linearly.
v0.56.0
v0.56.2
@@ -0,0 +1,420 @@
#!/usr/bin/env bats
# Backward-only compatibility tests for doltgresql.
#
# These tests exercise workflows where an older doltgresql release writes the
# initial state and the current HEAD build then continues operating on the same
# repo (schema changes, DML, branching, merging). Unlike bidirectional tests,
# they are only meaningful in one direction — old writes, then new operates —
# so we do not run them with the LEGACY / NEW roles swapped.
#
# Each test manages its own server lifecycle so that it can switch between the
# LEGACY and NEW binaries mid-test. setup() and teardown() only manage the
# scratch directory.
#
# Environment variables:
# DOLTGRES_LEGACY_BIN — path to the "old" doltgres binary
# DOLTGRES_NEW_BIN — path to the "new" (HEAD) doltgres binary
# REPO_DIR — scratch directory base (empty, just needs to exist)
load $BATS_TEST_DIRNAME/../helper/common.bash
BATS_REPO=""
setup() {
BATS_REPO="$BATS_TMPDIR/backward-$$-$RANDOM"
mkdir -p "$BATS_REPO"
}
teardown() {
stop_doltgres
rm -rf "$BATS_REPO"
}
# Convenience wrappers so test bodies read cleanly.
old_server_start() { start_doltgres "$DOLTGRES_LEGACY_BIN" "$BATS_REPO" "$BATS_REPO/old.log"; }
new_server_start() { start_doltgres "$DOLTGRES_NEW_BIN" "$BATS_REPO" "$BATS_REPO/new.log"; }
@test "backward_workflow: fk on varchar column from old repo, then merge" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
# --- Old: create parent with a VARCHAR(10) column and seed rows ---
old_server_start
sql <<SQL
CREATE TABLE parent (
id INT NOT NULL PRIMARY KEY,
val VARCHAR(10) NOT NULL UNIQUE
);
INSERT INTO parent VALUES (1, 'apple'), (2, 'banana'), (3, 'cherry');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: create parent');"
stop_doltgres
# --- New: create child with FK on VARCHAR(10) referencing parent(val) ---
new_server_start
sql <<SQL
CREATE TABLE child (
id INT NOT NULL PRIMARY KEY,
ref VARCHAR(10) NOT NULL,
CONSTRAINT child_ref_fk FOREIGN KEY (ref) REFERENCES parent(val)
);
SQL
# Valid inserts — ref values that exist in parent.
sql -c "INSERT INTO child VALUES (10, 'apple'), (11, 'banana');"
run sql_csv -c "SELECT count(*) FROM child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2" ]] || false
# Invalid insert — 'grape' is not present in parent, FK must reject.
run sql -c "INSERT INTO child VALUES (12, 'grape');"
[ "$status" -ne 0 ]
# Row count unchanged after the rejected insert.
run sql_csv -c "SELECT count(*) FROM child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2" ]] || false
# dolt_verify_constraints reports no violations. Returns {0} on success.
run sql_csv -c "SELECT dolt_verify_constraints('--all');"
[ "$status" -eq 0 ]
[[ "$output" =~ "{0}" ]] || false
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: create child + fk');"
# --- Branch, write on both branches, then merge new into main ---
sql -c "SELECT dolt_branch('new');"
sql -c "INSERT INTO child VALUES (20, 'cherry');"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'main: add cherry');"
sql <<SQL
SELECT dolt_checkout('new');
INSERT INTO child VALUES (30, 'banana');
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'new: add banana on new branch');
SQL
run sql -c "SELECT dolt_merge('new');"
[ "$status" -eq 0 ]
# After merge, main should see rows from both branches.
run sql_csv -c "SELECT count(*) FROM child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "4" ]] || false
run sql_csv -c "SELECT id, ref FROM child ORDER BY id;"
[ "$status" -eq 0 ]
[[ "$output" =~ "10,apple" ]] || false
[[ "$output" =~ "11,banana" ]] || false
[[ "$output" =~ "20,cherry" ]] || false
[[ "$output" =~ "30,banana" ]] || false
# FK still enforced after merge.
run sql -c "INSERT INTO child VALUES (99, 'grape');"
[ "$status" -ne 0 ]
# And still no reported constraint violations.
run sql_csv -c "SELECT dolt_verify_constraints('--all');"
[ "$status" -eq 0 ]
[[ "$output" =~ "{0}" ]] || false
stop_doltgres
}
@test "backward_workflow: dangling child violation surfaces on merge" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
# --- Old: create parent, seed rows ---
old_server_start
sql <<SQL
CREATE TABLE parent (
id INT NOT NULL PRIMARY KEY,
val VARCHAR(10) NOT NULL UNIQUE
);
INSERT INTO parent VALUES (1, 'apple'), (2, 'banana'), (3, 'cherry');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: create parent');"
stop_doltgres
# --- New: create child + FK, seed a child that will become dangling ---
new_server_start
sql <<SQL
CREATE TABLE child (
id INT NOT NULL PRIMARY KEY,
ref VARCHAR(10) NOT NULL,
CONSTRAINT child_ref_fk FOREIGN KEY (ref) REFERENCES parent(val)
);
INSERT INTO child VALUES (10, 'apple');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: create child + fk, seed');"
# No violations at this point.
run sql_csv -c "SELECT dolt_verify_constraints('--all');"
[ "$status" -eq 0 ]
[[ "$output" =~ "{0}" ]] || false
# Branch 'drop_banana': remove banana from parent (no child refs banana yet).
sql <<SQL
SELECT dolt_branch('drop_banana');
SELECT dolt_checkout('drop_banana');
DELETE FROM parent WHERE val='banana';
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'drop_banana: remove banana');
SQL
# Back on main: add a child row that references banana (still valid on main).
sql <<SQL
SELECT dolt_checkout('main');
INSERT INTO child VALUES (20, 'banana');
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'main: add child(20, banana)');
SQL
# merge should fail
run sql -c "SELECT dolt_merge('drop_banana');"
[ "$status" -ne 0 ]
# merge succeeds with constraint violation if forced
run sql -c "SET dolt_force_transaction_commit=1; SELECT dolt_merge('drop_banana');"
[ "$status" -eq 0 ]
# Verify the violation was recorded against child.
run sql_csv -c "SELECT count(*) FROM dolt_constraint_violations_child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1" ]] || false
# And that the violation row is the banana reference (id=20).
run sql_csv -c "SELECT id, ref FROM dolt_constraint_violations_child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "20,banana" ]] || false
stop_doltgres
}
@test "backward_workflow: fk on text column from old repo, then merge" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
old_server_start
sql <<SQL
CREATE TABLE parent (
id INT NOT NULL PRIMARY KEY,
val TEXT NOT NULL UNIQUE
);
INSERT INTO parent VALUES (1, 'apple'), (2, 'banana'), (3, 'cherry');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: create parent (text)');"
stop_doltgres
new_server_start
sql <<SQL
CREATE TABLE child (
id INT NOT NULL PRIMARY KEY,
ref TEXT NOT NULL,
CONSTRAINT child_ref_fk FOREIGN KEY (ref) REFERENCES parent(val)
);
SQL
sql -c "INSERT INTO child VALUES (10, 'apple'), (11, 'banana');"
run sql_csv -c "SELECT count(*) FROM child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2" ]] || false
run sql_csv -c "SELECT dolt_verify_constraints('--all');"
[ "$status" -eq 0 ]
[[ "$output" =~ "{0}" ]] || false
# Invalid insert — 'grape' is not present in parent, FK must reject.
run sql -c "INSERT INTO child VALUES (12, 'grape');"
[ "$status" -ne 0 ]
run sql_csv -c "SELECT count(*) FROM child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2" ]] || false
# No reported violations.
run sql_csv -c "SELECT dolt_verify_constraints('--all');"
[ "$status" -eq 0 ]
[[ "$output" =~ "{0}" ]] || false
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: create child + fk');"
# Branch, write on both, merge.
sql -c "SELECT dolt_branch('new');"
sql -c "INSERT INTO child VALUES (20, 'cherry');"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'main: add cherry');"
sql <<SQL
SELECT dolt_checkout('new');
INSERT INTO child VALUES (30, 'banana');
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'new: add banana on new branch');
SQL
run sql -c "SELECT dolt_merge('new');"
[ "$status" -eq 0 ]
run sql_csv -c "SELECT count(*) FROM child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "4" ]] || false
# FK still enforced after merge.
run sql -c "INSERT INTO child VALUES (99, 'grape');"
[ "$status" -ne 0 ]
# Still no violations reported.
run sql_csv -c "SELECT dolt_verify_constraints('--all');"
[ "$status" -eq 0 ]
[[ "$output" =~ "{0}" ]] || false
stop_doltgres
}
@test "backward_workflow: fk from varchar child to text parent" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
# Cross-type FK: parent's TEXT column vs child's VARCHAR(10) column.
old_server_start
sql <<SQL
CREATE TABLE parent (
id INT NOT NULL PRIMARY KEY,
val TEXT NOT NULL UNIQUE
);
INSERT INTO parent VALUES (1, 'apple'), (2, 'banana'), (3, 'cherry');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: create parent (text)');"
stop_doltgres
new_server_start
sql <<SQL
CREATE TABLE child (
id INT NOT NULL PRIMARY KEY,
ref VARCHAR(10) NOT NULL,
CONSTRAINT child_ref_fk FOREIGN KEY (ref) REFERENCES parent(val)
);
SQL
sql -c "INSERT INTO child VALUES (10, 'apple'), (11, 'banana');"
run sql_csv -c "SELECT count(*) FROM child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2" ]] || false
run sql -c "INSERT INTO child VALUES (12, 'grape');"
[ "$status" -ne 0 ]
run sql_csv -c "SELECT dolt_verify_constraints('--all');"
[ "$status" -eq 0 ]
[[ "$output" =~ "{0}" ]] || false
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: create child + fk (varchar → text)');"
stop_doltgres
}
@test "backward_workflow: fk from text child to varchar parent" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
old_server_start
sql <<SQL
CREATE TABLE parent (
id INT NOT NULL PRIMARY KEY,
val VARCHAR(10) NOT NULL UNIQUE
);
INSERT INTO parent VALUES (1, 'apple'), (2, 'banana'), (3, 'cherry');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: create parent (varchar)');"
stop_doltgres
new_server_start
sql <<SQL
CREATE TABLE child (
id INT NOT NULL PRIMARY KEY,
ref TEXT NOT NULL,
CONSTRAINT child_ref_fk FOREIGN KEY (ref) REFERENCES parent(val)
);
SQL
sql -c "INSERT INTO child VALUES (10, 'apple'), (11, 'banana');"
run sql_csv -c "SELECT count(*) FROM child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2" ]] || false
run sql -c "INSERT INTO child VALUES (12, 'grape');"
[ "$status" -ne 0 ]
run sql_csv -c "SELECT dolt_verify_constraints('--all');"
[ "$status" -eq 0 ]
[[ "$output" =~ "{0}" ]] || false
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: create child + fk (text → varchar)');"
stop_doltgres
}
@test "backward_workflow: dangling child violation surfaces on merge (text)" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
old_server_start
sql <<SQL
CREATE TABLE parent (
id INT NOT NULL PRIMARY KEY,
val TEXT NOT NULL UNIQUE
);
INSERT INTO parent VALUES (1, 'apple'), (2, 'banana'), (3, 'cherry');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: create parent (text)');"
stop_doltgres
new_server_start
sql <<SQL
CREATE TABLE child (
id INT NOT NULL PRIMARY KEY,
ref TEXT NOT NULL,
CONSTRAINT child_ref_fk FOREIGN KEY (ref) REFERENCES parent(val)
);
INSERT INTO child VALUES (10, 'apple');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: create child + fk, seed');"
run sql_csv -c "SELECT dolt_verify_constraints('--all');"
[ "$status" -eq 0 ]
[[ "$output" =~ "{0}" ]] || false
# Branch 'drop_banana': remove banana from parent (no child refs banana yet).
sql <<SQL
SELECT dolt_branch('drop_banana');
SELECT dolt_checkout('drop_banana');
DELETE FROM parent WHERE val='banana';
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'drop_banana: remove banana');
SQL
# Back on main: add a child row that references banana (still valid on main).
sql <<SQL
SELECT dolt_checkout('main');
INSERT INTO child VALUES (20, 'banana');
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'main: add child(20, banana)');
SQL
# Merge should fail without forced commit due to constraint violation.
run sql -c "SELECT dolt_merge('drop_banana');"
[ "$status" -ne 0 ]
# Merge succeeds with constraint violation when forced.
run sql -c "SET dolt_force_transaction_commit=1; SELECT dolt_merge('drop_banana');"
[ "$status" -eq 0 ]
run sql_csv -c "SELECT count(*) FROM dolt_constraint_violations_child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1" ]] || false
run sql_csv -c "SELECT id, ref FROM dolt_constraint_violations_child;"
[ "$status" -eq 0 ]
[[ "$output" =~ "20,banana" ]] || false
stop_doltgres
}
@@ -0,0 +1,482 @@
#!/usr/bin/env bats
# Bidirectional compatibility tests for doltgresql.
#
# Each test creates an isolated repository and alternates reads/writes between
# two doltgres binaries — an older release (DOLTGRES_LEGACY_BIN) and the current
# HEAD build (DOLTGRES_NEW_BIN). The tests run in both directions: the runner
# swaps LEGACY and NEW to exercise forward and backward compatibility.
#
# Unlike the other compatibility tests, each test body manages its own server
# lifecycle; setup() and teardown() only manage the scratch directory.
#
# Environment variables:
# DOLTGRES_LEGACY_BIN — path to the "old" doltgres binary
# DOLTGRES_NEW_BIN — path to the "new" (HEAD) doltgres binary
# REPO_DIR — scratch directory base (empty, just needs to exist)
# Note: helper is one directory up from this file.
load $BATS_TEST_DIRNAME/../helper/common.bash
BATS_REPO=""
setup() {
BATS_REPO="$BATS_TMPDIR/bidir-$$-$RANDOM"
mkdir -p "$BATS_REPO"
}
teardown() {
stop_doltgres
rm -rf "$BATS_REPO"
}
# Convenience wrappers so test bodies read cleanly.
old_server_start() { start_doltgres "$DOLTGRES_LEGACY_BIN" "$BATS_REPO" "$BATS_REPO/old.log"; }
new_server_start() { start_doltgres "$DOLTGRES_NEW_BIN" "$BATS_REPO" "$BATS_REPO/new.log"; }
# ---------------------------------------------------------------------------
# Test 1: Scalar types DML — INT, VARCHAR, NUMERIC, TIMESTAMP round-trip.
# Four rounds: old → HEAD → old → HEAD.
# ---------------------------------------------------------------------------
@test "bidirectional: scalar types round-trip across versions" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
# --- Setup: old doltgres creates schema and seeds two rows ---
old_server_start
sql <<SQL
CREATE TABLE scalars (
pk INT NOT NULL PRIMARY KEY,
c_int INT,
c_varchar VARCHAR(255),
c_numeric NUMERIC(10,2),
c_ts TIMESTAMP
);
INSERT INTO scalars VALUES
(1, 100, 'old-row-1', 10.50, '2024-01-01 10:00:00'),
(2, 200, 'old-row-2', 20.75, '2024-06-15 12:30:00');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: initial data');"
stop_doltgres
# --- Round 1: HEAD reads old's rows, inserts its own ---
new_server_start
run sql_csv -c "SELECT pk, c_varchar, c_numeric FROM scalars WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,old-row-1,10.50" ]] || false
run sql_csv -c "SELECT count(*) FROM scalars;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2" ]] || false
sql -c "INSERT INTO scalars VALUES (3, 300, 'head-row-3', 30.25, '2025-01-15 08:00:00');"
sql -c "UPDATE scalars SET c_varchar='head-updated-1' WHERE pk=1;"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: insert row 3, update row 1');"
stop_doltgres
# --- Round 2: old reads HEAD's changes ---
old_server_start
run sql_csv -c "SELECT pk, c_varchar FROM scalars WHERE pk=3;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3,head-row-3" ]] || false
run sql_csv -c "SELECT pk, c_varchar FROM scalars WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,head-updated-1" ]] || false
sql -c "INSERT INTO scalars VALUES (4, 400, 'old-row-4', 40.00, '2025-03-01 09:00:00');"
sql -c "DELETE FROM scalars WHERE pk=2;"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: insert row 4, delete row 2');"
stop_doltgres
# --- Round 3: HEAD reads old's changes ---
new_server_start
run sql_csv -c "SELECT pk, c_varchar, c_numeric FROM scalars WHERE pk=4;"
[ "$status" -eq 0 ]
[[ "$output" =~ "4,old-row-4,40.00" ]] || false
run sql_csv -c "SELECT count(*) FROM scalars WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "0" ]] || false
sql -c "INSERT INTO scalars VALUES (5, 500, 'head-row-5', 50.50, '2025-06-01 14:00:00');"
sql -c "UPDATE scalars SET c_numeric=99.99 WHERE pk=4;"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: insert row 5, update row 4 numeric');"
stop_doltgres
# --- Round 4: old reads final state ---
old_server_start
run sql_csv -c "SELECT count(*) FROM scalars;"
[ "$status" -eq 0 ]
[[ "$output" =~ "4" ]] || false # pks 1, 3, 4, 5
run sql_csv -c "SELECT pk, c_numeric FROM scalars WHERE pk=4;"
[ "$status" -eq 0 ]
[[ "$output" =~ "4,99.99" ]] || false
run sql_csv -c "SELECT pk, c_varchar FROM scalars WHERE pk=5;"
[ "$status" -eq 0 ]
[[ "$output" =~ "5,head-row-5" ]] || false
stop_doltgres
}
# ---------------------------------------------------------------------------
# Test 2: Large TEXT values — out-of-band storage round-trip.
# ---------------------------------------------------------------------------
@test "bidirectional: large text values round-trip" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
# Setup: old creates table, inserts small and large text
old_server_start
sql <<SQL
CREATE TABLE texts (
pk INT NOT NULL PRIMARY KEY,
c_text TEXT
);
INSERT INTO texts VALUES
(1, 'old-small'),
(2, repeat('A', 70000));
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: initial texts');"
stop_doltgres
# Round 1: HEAD reads both rows, inserts its own large text
new_server_start
run sql_csv -c "SELECT pk, c_text FROM texts WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "old-small" ]] || false
run sql_csv -c "SELECT pk, length(c_text) FROM texts WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2,70000" ]] || false
sql -c "INSERT INTO texts VALUES (3, repeat('H', 80000));"
sql -c "UPDATE texts SET c_text=repeat('U', 75000) WHERE pk=1;"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: large insert, large update');"
stop_doltgres
# Round 2: old reads HEAD's large values
old_server_start
run sql_csv -c "SELECT pk, length(c_text) FROM texts WHERE pk=3;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3,80000" ]] || false
run sql_csv -c "SELECT pk, length(c_text) FROM texts WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,75000" ]] || false
sql -c "UPDATE texts SET c_text=repeat('V', 90000) WHERE pk=2;"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: update row 2 to even larger');"
stop_doltgres
# Round 3: HEAD reads old's in-place update
new_server_start
run sql_csv -c "SELECT pk, length(c_text) FROM texts WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2,90000" ]] || false
run sql_csv -c "SELECT count(*) FROM texts;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3" ]] || false
stop_doltgres
}
# ---------------------------------------------------------------------------
# Test 3: ADD COLUMN DDL — both versions add columns to the same table.
# ---------------------------------------------------------------------------
@test "bidirectional: add columns from both versions" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
# Setup: old creates minimal table
old_server_start
sql <<SQL
CREATE TABLE evolving (
pk INT NOT NULL PRIMARY KEY,
c_base INT
);
INSERT INTO evolving VALUES (1, 10), (2, 20), (3, 30);
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: base schema');"
stop_doltgres
# Round 1: HEAD adds TEXT and DATE columns
new_server_start
sql -c "ALTER TABLE evolving ADD COLUMN c_text TEXT;"
sql -c "ALTER TABLE evolving ADD COLUMN c_date DATE;"
sql -c "UPDATE evolving SET c_text='text-' || pk::text, c_date='2025-01-01';"
sql -c "INSERT INTO evolving VALUES (4, 40, 'text-4', '2025-02-01');"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: add text and date columns');"
stop_doltgres
# Round 2: old reads HEAD's new columns, adds its own
old_server_start
run sql_csv -c "SELECT pk, c_text, c_date FROM evolving WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,text-1,2025-01-01" ]] || false
run sql_csv -c "SELECT pk, c_text FROM evolving WHERE pk=4;"
[ "$status" -eq 0 ]
[[ "$output" =~ "4,text-4" ]] || false
sql -c "ALTER TABLE evolving ADD COLUMN c_int2 INT;"
sql -c "ALTER TABLE evolving ADD COLUMN c_numeric NUMERIC(8,2);"
sql -c "UPDATE evolving SET c_int2=pk*100, c_numeric=pk*1.5;"
sql -c "INSERT INTO evolving VALUES (5, 50, 'text-5', '2025-03-01', 500, 7.50);"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: add int2 and numeric columns');"
stop_doltgres
# Round 3: HEAD reads all 4 added columns
new_server_start
run sql_csv -c "SELECT pk, c_text, c_date, c_int2, c_numeric FROM evolving WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,text-1,2025-01-01,100,1.50" ]] || false
run sql_csv -c "SELECT pk, c_text, c_int2, c_numeric FROM evolving WHERE pk=5;"
[ "$status" -eq 0 ]
[[ "$output" =~ "5,text-5,500,7.50" ]] || false
stop_doltgres
}
# ---------------------------------------------------------------------------
# Test 4: Branch and merge — both versions create branches, merge across
# version boundaries.
# ---------------------------------------------------------------------------
@test "bidirectional: branch and merge across versions" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
# Setup: old creates repo with base table
old_server_start
sql <<SQL
CREATE TABLE shared (
pk INT NOT NULL PRIMARY KEY,
val VARCHAR(100),
src VARCHAR(20)
);
INSERT INTO shared VALUES (1, 'base-1', 'old'), (2, 'base-2', 'old');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: base data');"
stop_doltgres
# Round 1: HEAD creates a feature branch
new_server_start
sql <<SQL
SELECT dolt_branch('head_feature');
SELECT dolt_checkout('head_feature');
INSERT INTO shared VALUES (10, 'head-feature-10', 'head'), (11, 'head-feature-11', 'head');
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'head: feature branch inserts');
SQL
stop_doltgres
# Round 2: old creates its own branch, merges HEAD's feature branch
old_server_start
sql <<SQL
SELECT dolt_branch('old_branch');
SELECT dolt_checkout('old_branch');
INSERT INTO shared VALUES (20, 'old-branch-20', 'old');
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'old: old_branch insert');
SQL
sql <<SQL
INSERT INTO shared VALUES (3, 'base-3', 'old');
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: main insert');"
sql -c "SELECT dolt_merge('head_feature');"
run sql_csv -c "SELECT count(*) FROM shared;"
[ "$status" -eq 0 ]
[[ "$output" =~ "5" ]] || false # 1,2,3,10,11
run sql_csv -c "SELECT pk, val FROM shared WHERE pk=10;"
[ "$status" -eq 0 ]
[[ "$output" =~ "10,head-feature-10" ]] || false
stop_doltgres
# Round 3: HEAD reads merged state, merges old_branch
new_server_start
run sql_csv -c "SELECT count(*) FROM shared;"
[ "$status" -eq 0 ]
[[ "$output" =~ "5" ]] || false
run sql_csv -c "SELECT pk, val FROM shared WHERE pk=3;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3,base-3" ]] || false
sql -c "SELECT dolt_merge('old_branch');"
run sql_csv -c "SELECT count(*) FROM shared;"
[ "$status" -eq 0 ]
[[ "$output" =~ "6" ]] || false # 1,2,3,10,11,20
run sql_csv -c "SELECT pk, val FROM shared WHERE pk=20;"
[ "$status" -eq 0 ]
[[ "$output" =~ "20,old-branch-20" ]] || false
stop_doltgres
}
# ---------------------------------------------------------------------------
# Test 5: Comprehensive type coverage — both versions add columns of different
# type families across rounds.
# ---------------------------------------------------------------------------
@test "bidirectional: comprehensive type coverage across versions" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
# Setup: old creates minimal table
old_server_start
sql <<SQL
CREATE TABLE typed (
pk INT NOT NULL PRIMARY KEY
);
INSERT INTO typed (pk) VALUES (1), (2), (3);
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: pk-only base');"
stop_doltgres
# Round 1: HEAD adds numeric columns
new_server_start
sql -c "ALTER TABLE typed ADD COLUMN c_smallint SMALLINT;"
sql -c "ALTER TABLE typed ADD COLUMN c_bigint BIGINT;"
sql -c "ALTER TABLE typed ADD COLUMN c_real REAL;"
sql -c "ALTER TABLE typed ADD COLUMN c_double DOUBLE PRECISION;"
sql -c "UPDATE typed SET c_smallint=pk*10, c_bigint=pk*1000000, c_real=pk*1.5, c_double=pk*2.5;"
sql -c "INSERT INTO typed (pk, c_smallint, c_bigint, c_real, c_double) VALUES (4, 40, 4000000, 6.0, 10.0);"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: add numeric columns');"
stop_doltgres
# Round 2: old reads HEAD's numeric columns, adds text/binary columns
old_server_start
run sql_csv -c "SELECT pk, c_smallint, c_bigint FROM typed WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,10,1000000" ]] || false
sql -c "ALTER TABLE typed ADD COLUMN c_varchar VARCHAR(255);"
sql -c "ALTER TABLE typed ADD COLUMN c_bytea BYTEA;"
sql -c "UPDATE typed SET c_varchar='varchar-' || pk::text, c_bytea='\xDEAD';"
sql -c "INSERT INTO typed (pk, c_varchar, c_bytea) VALUES (5, 'varchar-5', '\xBEEF');"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: add varchar and bytea columns');"
stop_doltgres
# Round 3: HEAD reads old's string/binary columns, adds temporal/numeric
new_server_start
run sql_csv -c "SELECT pk, c_varchar FROM typed WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,varchar-1" ]] || false
run sql_csv -c "SELECT pk, c_varchar FROM typed WHERE pk=5;"
[ "$status" -eq 0 ]
[[ "$output" =~ "5,varchar-5" ]] || false
sql -c "ALTER TABLE typed ADD COLUMN c_date DATE;"
sql -c "ALTER TABLE typed ADD COLUMN c_numeric NUMERIC(10,3);"
sql -c "UPDATE typed SET c_date='2025-01-01', c_numeric=pk*3.141;"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: add temporal and numeric columns');"
stop_doltgres
# Round 4: old reads HEAD's temporal/numeric columns, adds boolean/jsonb
old_server_start
run sql_csv -c "SELECT pk, c_date, c_numeric FROM typed WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,2025-01-01,3.141" ]] || false
sql -c "ALTER TABLE typed ADD COLUMN c_boolean BOOLEAN;"
sql -c "ALTER TABLE typed ADD COLUMN c_jsonb JSONB;"
sql -c "UPDATE typed SET c_boolean=(pk % 2 = 1), c_jsonb=json_build_object('pk', pk);"
sql -c "INSERT INTO typed (pk, c_varchar, c_date, c_numeric, c_boolean, c_jsonb) VALUES (6, 'varchar-6', '2025-06-01', 18.847, false, '{\"old\":true}');"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: add boolean and jsonb columns');"
stop_doltgres
# Round 5: HEAD reads old's boolean/jsonb, does a final insert using all columns
new_server_start
run sql_csv -c "SELECT pk, c_boolean FROM typed WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,t" ]] || false
run sql_csv -c "SELECT pk, c_boolean FROM typed WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2,f" ]] || false
run sql_csv -c "SELECT pk, c_jsonb->>'old' FROM typed WHERE pk=6;"
[ "$status" -eq 0 ]
[[ "$output" =~ "true" ]] || false
sql -c "INSERT INTO typed (pk, c_smallint, c_bigint, c_varchar, c_date, c_numeric, c_boolean, c_jsonb) VALUES (7, 70, 7000000, 'varchar-7', '2025-07-01', 21.988, true, '{\"head\":true}');"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: final insert using all columns');"
stop_doltgres
# Round 6: old reads HEAD's final insert — all columns visible
old_server_start
run sql_csv -c "SELECT pk, c_smallint, c_bigint, c_varchar, c_numeric FROM typed WHERE pk=7;"
[ "$status" -eq 0 ]
[[ "$output" =~ "7,70,7000000,varchar-7,21.988" ]] || false
run sql_csv -c "SELECT count(*) FROM typed;"
[ "$status" -eq 0 ]
[[ "$output" =~ "7" ]] || false
stop_doltgres
}
# ---------------------------------------------------------------------------
# Test 6: JSONB round-trip — inline and large JSONB documents across versions.
# ---------------------------------------------------------------------------
@test "bidirectional: jsonb round-trip across versions" {
[ -n "$DOLTGRES_LEGACY_BIN" ] || skip "requires DOLTGRES_LEGACY_BIN"
[ -n "$DOLTGRES_NEW_BIN" ] || skip "requires DOLTGRES_NEW_BIN"
# Setup: old creates table with small and large JSONB
old_server_start
sql <<SQL
CREATE TABLE jsondocs (
pk INT NOT NULL PRIMARY KEY,
c_small JSONB,
c_big JSONB
);
INSERT INTO jsondocs VALUES
(1, '{"key":"val1","num":100}', '{"meta":"small"}'),
(2, '{"key":"val2"}', json_build_object('big', repeat('x', 5000))::jsonb);
SQL
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'old: initial jsonb');"
stop_doltgres
# Round 1: HEAD reads both rows, inserts its own
new_server_start
run sql_csv -c "SELECT pk, c_small->>'key', c_small->>'meta' FROM jsondocs WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "val1" ]] || false
run sql_csv -c "SELECT pk, length(c_big->>'big') FROM jsondocs WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2,5000" ]] || false
sql -c "INSERT INTO jsondocs VALUES (3, '{\"head\":true}', json_build_object('hbig', repeat('H', 6000))::jsonb);"
sql -c "UPDATE jsondocs SET c_small=c_small || '{\"updated\":true}' WHERE pk=1;"
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'head: insert row 3, update row 1');"
stop_doltgres
# Round 2: old reads HEAD's changes
old_server_start
run sql_csv -c "SELECT pk, c_small->>'head' FROM jsondocs WHERE pk=3;"
[ "$status" -eq 0 ]
[[ "$output" =~ "true" ]] || false
run sql_csv -c "SELECT pk, c_small->>'updated' FROM jsondocs WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "true" ]] || false
run sql_csv -c "SELECT pk, length(c_big->>'hbig') FROM jsondocs WHERE pk=3;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3,6000" ]] || false
run sql_csv -c "SELECT count(*) FROM jsondocs;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3" ]] || false
stop_doltgres
}
@@ -0,0 +1,270 @@
#!/usr/bin/env bats
# Backward / forward compatibility tests for doltgresql.
#
# Each test starts a fresh doltgres server against a copy of the pre-created repo
# (REPO_DIR) and verifies that the binary under test can correctly read and write
# the data created by the other version.
#
# Environment variables consumed by this file:
# REPO_DIR — path to the data directory created by setup_repo.sh
# DOLTGRES_TEST_BIN — doltgres binary to use (default: doltgres from PATH)
load $BATS_TEST_DIRNAME/helper/common.bash
BATS_REPO=""
setup() {
BATS_REPO="$BATS_TMPDIR/compat-repo-$$-$RANDOM"
copy_repo "$REPO_DIR" "$BATS_REPO"
start_doltgres "${DOLTGRES_TEST_BIN:-doltgres}" "$BATS_REPO" "$BATS_REPO/server.log"
}
teardown() {
stop_doltgres
rm -rf "$BATS_REPO"
}
# ---------------------------------------------------------------------------
# Sanity / version checks
# ---------------------------------------------------------------------------
@test "compatibility: server is accessible" {
run sql -c "SELECT 1;"
[ "$status" -eq 0 ]
}
@test "compatibility: dolt_version returns a version string" {
run sql -c "SELECT dolt_version();"
[ "$status" -eq 0 ]
[[ "$output" =~ [0-9]+\.[0-9]+\.[0-9]+ ]] || false
}
# ---------------------------------------------------------------------------
# Branch enumeration
# ---------------------------------------------------------------------------
@test "compatibility: expected branches exist" {
run sql_csv -c "SELECT name FROM dolt_branches ORDER BY name;"
[ "$status" -eq 0 ]
[[ "$output" =~ "check_merge" ]] || false
[[ "$output" =~ "init" ]] || false
[[ "$output" =~ "main" ]] || false
[[ "$output" =~ "other" ]] || false
}
# ---------------------------------------------------------------------------
# Working set is clean on main
# ---------------------------------------------------------------------------
@test "compatibility: working set is clean on main" {
run sql -c "SELECT * FROM dolt_status;"
[ "$status" -eq 0 ]
[[ "$output" =~ "(0 rows)" ]] || false
}
# ---------------------------------------------------------------------------
# Schema on branch init (original schema before alterations)
# ---------------------------------------------------------------------------
@test "compatibility: schema on branch init has original abc columns" {
run sql <<SQL
SELECT dolt_checkout('init');
SELECT column_name FROM information_schema.columns
WHERE table_name = 'abc' ORDER BY ordinal_position;
SQL
[ "$status" -eq 0 ]
[[ "$output" =~ "pk" ]] || false
[[ "$output" =~ " a" ]] || false
[[ "$output" =~ " b" ]] || false
[[ "$output" =~ " w" ]] || false
[[ "$output" =~ " x" ]] || false
}
# ---------------------------------------------------------------------------
# Data on branch init
# ---------------------------------------------------------------------------
@test "compatibility: data on branch init matches initial insert" {
run sql_csv <<SQL
SELECT dolt_checkout('init');
SELECT pk, a, b, w, x FROM abc ORDER BY pk;
SQL
[ "$status" -eq 0 ]
[[ "$output" =~ "0,asdf,1.1,0,0" ]] || false
[[ "$output" =~ "1,asdf,1.1,0,0" ]] || false
[[ "$output" =~ "2,asdf,1.1,0,0" ]] || false
}
# ---------------------------------------------------------------------------
# Schema on main (after ALTER TABLE DROP/ADD)
# ---------------------------------------------------------------------------
@test "compatibility: schema on main has altered abc columns" {
run sql <<SQL
SELECT column_name FROM information_schema.columns
WHERE table_name = 'abc' ORDER BY ordinal_position;
SQL
[ "$status" -eq 0 ]
[[ "$output" =~ "pk" ]] || false
[[ "$output" =~ " a" ]] || false
[[ "$output" =~ " b" ]] || false
[[ "$output" =~ " x" ]] || false
[[ "$output" =~ " y" ]] || false
# 'w' was dropped on main
[[ ! "$output" =~ " w" ]] || false
}
# ---------------------------------------------------------------------------
# Data on main
# ---------------------------------------------------------------------------
@test "compatibility: data on main matches expected changes" {
run sql_csv -c "SELECT pk, a, x, y FROM abc ORDER BY pk;"
[ "$status" -eq 0 ]
[[ "$output" =~ "0,asdf,1,121" ]] || false
[[ "$output" =~ "2,asdf,0,121" ]] || false
[[ "$output" =~ "3,data,0,121" ]] || false
}
# ---------------------------------------------------------------------------
# Schema on branch other (different ALTER TABLE than main)
# ---------------------------------------------------------------------------
@test "compatibility: schema on branch other has w and z columns" {
run sql <<SQL
SELECT dolt_checkout('other');
SELECT column_name FROM information_schema.columns
WHERE table_name = 'abc' ORDER BY ordinal_position;
SQL
[ "$status" -eq 0 ]
[[ "$output" =~ "pk" ]] || false
[[ "$output" =~ " w" ]] || false
[[ "$output" =~ " z" ]] || false
# 'x' was dropped on other
[[ ! "$output" =~ " x" ]] || false
}
# ---------------------------------------------------------------------------
# Data on branch other
# ---------------------------------------------------------------------------
@test "compatibility: data on branch other matches expected changes" {
run sql_csv <<SQL
SELECT dolt_checkout('other');
SELECT pk, a, w, z FROM abc ORDER BY pk;
SQL
[ "$status" -eq 0 ]
[[ "$output" =~ "0,asdf,1,122" ]] || false
[[ "$output" =~ "1,asdf,0,122" ]] || false
[[ "$output" =~ "4,data,0,122" ]] || false
# pk=2 was deleted on other
[[ ! "$output" =~ ",2," ]] || false
}
# ---------------------------------------------------------------------------
# big table
# ---------------------------------------------------------------------------
@test "compatibility: big table has 1000 rows" {
run sql_csv -c "SELECT count(*) FROM big;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1000" ]] || false
}
@test "compatibility: big table supports delete and insert" {
sql -c "DELETE FROM big WHERE pk IN (71, 331, 881);"
run sql_csv -c "SELECT count(*) FROM big;"
[ "$status" -eq 0 ]
[[ "$output" =~ "997" ]] || false
sql -c "INSERT INTO big VALUES (1001, 'foo'), (1002, 'bar'), (1003, 'baz');"
run sql_csv -c "SELECT count(*) FROM big;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1000" ]] || false
sql -c "SELECT dolt_add('.'); SELECT dolt_commit('-m', 'modified big');"
}
# ---------------------------------------------------------------------------
# View
# ---------------------------------------------------------------------------
@test "compatibility: view1 is queryable" {
run sql -c "SELECT * FROM view1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "4" ]] || false
}
@test "compatibility: all_types_view has 3 rows" {
run sql_csv -c "SELECT count(*) FROM all_types_view;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3" ]] || false
}
# ---------------------------------------------------------------------------
# DML on existing tables (write test)
# ---------------------------------------------------------------------------
@test "compatibility: can insert and read back on main" {
sql -c "INSERT INTO abc (pk, a, b, x, y) VALUES (99, 'new', 9.9, 9, 99);"
run sql_csv -c "SELECT pk, a, x, y FROM abc WHERE pk=99;"
[ "$status" -eq 0 ]
[[ "$output" =~ "99,new,9,99" ]] || false
sql -c "DELETE FROM abc WHERE pk=99;"
}
@test "compatibility: can update a row on main" {
sql -c "UPDATE abc SET a='updated' WHERE pk=0;"
run sql_csv -c "SELECT pk, a FROM abc WHERE pk=0;"
[ "$status" -eq 0 ]
[[ "$output" =~ "0,updated" ]] || false
}
@test "compatibility: dml is committable" {
sql -c "INSERT INTO abc (pk, a, b, x, y) VALUES (200, 'commit-test', 1.0, 1, 1);"
sql -c "SELECT dolt_add('.');"
run sql -c "SELECT length(dolt_commit('-m', 'compat dml commit')::text);"
[ "$status" -eq 0 ]
[[ "$output" =~ "34" ]] || false
}
# ---------------------------------------------------------------------------
# Check constraint (def table)
# ---------------------------------------------------------------------------
@test "compatibility: check constraint is enforced" {
run sql -c "INSERT INTO def VALUES (-1);"
[ "$status" -ne 0 ]
}
@test "compatibility: valid insert into def succeeds" {
run sql -c "INSERT INTO def VALUES (100);"
[ "$status" -eq 0 ]
}
# ---------------------------------------------------------------------------
# Merge: check_merge into main (should succeed cleanly)
# ---------------------------------------------------------------------------
@test "compatibility: clean merge of check_merge into main succeeds" {
run sql <<SQL
SELECT dolt_merge('check_merge');
SQL
[ "$status" -eq 0 ]
}
# ---------------------------------------------------------------------------
# Merge: other into main (should produce a conflict on abc)
# ---------------------------------------------------------------------------
@test "compatibility: conflicting merge of other into main is detected" {
run sql -c "SELECT dolt_merge('other');"
# The merge should either return a non-zero status or report conflicts
# in dolt_conflicts — either outcome confirms conflict detection works.
if [ "$status" -eq 0 ]; then
run sql_csv -c "SELECT count(*) FROM dolt_conflicts;"
[ "$status" -eq 0 ]
[[ "$output" =~ [1-9] ]] || false
fi
}
@@ -0,0 +1,114 @@
# Common helpers for doltgresql compatibility BATS tests.
# Loaded by all test files in this directory tree via:
# load $BATS_TEST_DIRNAME/../helper/common.bash (from subdirs)
# or
# load $BATS_TEST_DIRNAME/helper/common.bash (from top level)
# ---------------------------------------------------------------------------
# Server lifecycle
# ---------------------------------------------------------------------------
COMPAT_SERVER_PID=""
COMPAT_SERVER_PORT=""
# pick_port — find an unused TCP port in the range 20486144.
pick_port() {
for i in {0..99}; do
port=$((RANDOM % 4096 + 2048))
if ! nc -z localhost "$port" 2>/dev/null; then
echo "$port"
return 0
fi
done
echo "ERROR: could not find a free port" >&2
return 1
}
# write_config <dir> <port> — write a minimal doltgres config.yaml into <dir>.
write_config() {
local dir="$1"
local port="$2"
cat > "$dir/compat-config.yaml" <<EOF
log_level: warning
behavior:
read_only: false
disable_client_multi_statements: false
listener:
host: localhost
port: $port
EOF
}
# start_doltgres <binary> <datadir> [logfile]
# Starts a doltgres server and waits for it to accept connections.
# Sets COMPAT_SERVER_PID and COMPAT_SERVER_PORT.
start_doltgres() {
local binary="${1:?start_doltgres: binary required}"
local datadir="${2:?start_doltgres: datadir required}"
local logfile="${3:-/dev/null}"
COMPAT_SERVER_PORT=$(pick_port)
write_config "$datadir" "$COMPAT_SERVER_PORT"
PGPASSWORD=password "$binary" -data-dir="$datadir" --config="$datadir/compat-config.yaml" \
> "$logfile" 2>&1 &
COMPAT_SERVER_PID=$!
# Wait up to 15 s
local end=$((SECONDS + 15))
while [ $SECONDS -lt $end ]; do
if PGPASSWORD=password psql -U postgres -h localhost -p "$COMPAT_SERVER_PORT" \
-c "SELECT 1;" postgres >/dev/null 2>&1; then
return 0
fi
sleep 0.5
done
echo "ERROR: doltgres server failed to start on port $COMPAT_SERVER_PORT" >&2
if [ -f "$logfile" ]; then cat "$logfile" >&2; fi
kill "$COMPAT_SERVER_PID" 2>/dev/null
COMPAT_SERVER_PID=""
return 1
}
# stop_doltgres — kill the server started by start_doltgres.
stop_doltgres() {
if [ -n "$COMPAT_SERVER_PID" ]; then
kill "$COMPAT_SERVER_PID" 2>/dev/null || true
wait "$COMPAT_SERVER_PID" 2>/dev/null || true
COMPAT_SERVER_PID=""
COMPAT_SERVER_PORT=""
fi
}
# ---------------------------------------------------------------------------
# Query helpers
# ---------------------------------------------------------------------------
# sql [psql-args...] — run psql against the current server's postgres database.
# Use with heredoc for multi-statement sessions:
# sql <<SQL
# SELECT dolt_checkout('other');
# SELECT * FROM abc;
# SQL
# Or with -c for a single statement:
# run sql -c "SELECT count(*) FROM abc;"
sql() {
PGPASSWORD=password psql -U postgres -h localhost -p "$COMPAT_SERVER_PORT" \
-v ON_ERROR_STOP=1 "$@" postgres
}
# sql_csv [psql-args...] — same as sql but outputs CSV (--csv flag).
sql_csv() {
PGPASSWORD=password psql --csv -U postgres -h localhost -p "$COMPAT_SERVER_PORT" \
-v ON_ERROR_STOP=1 "$@" postgres
}
# ---------------------------------------------------------------------------
# Repo isolation
# ---------------------------------------------------------------------------
# copy_repo <src> <dst> — copy a data directory for test isolation.
copy_repo() {
cp -Rpf "$1" "$2"
}
@@ -0,0 +1,367 @@
#!/usr/bin/env bats
# Types compatibility tests for doltgresql.
#
# Verifies that the current doltgres build can correctly read, write, and alter
# all_types rows that were originally written by an older doltgres version (or
# by the current version in a forward-compatibility run).
#
# Depends on the all_types table created by setup_repo.sh.
load $BATS_TEST_DIRNAME/helper/common.bash
BATS_REPO=""
setup() {
BATS_REPO="$BATS_TMPDIR/types-compat-repo-$$-$RANDOM"
copy_repo "$REPO_DIR" "$BATS_REPO"
start_doltgres "${DOLTGRES_TEST_BIN:-doltgres}" "$BATS_REPO" "$BATS_REPO/server.log"
}
teardown() {
stop_doltgres
rm -rf "$BATS_REPO"
}
# ---------------------------------------------------------------------------
# Row counts
# ---------------------------------------------------------------------------
@test "types_compat: all_types has 3 rows" {
run sql_csv -c "SELECT count(*) FROM all_types;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3" ]] || false
}
# ---------------------------------------------------------------------------
# Numeric types
# ---------------------------------------------------------------------------
@test "types_compat: smallint column readable" {
run sql_csv -c "SELECT pk, c_smallint FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,100" ]] || false
}
@test "types_compat: negative smallint readable" {
run sql_csv -c "SELECT pk, c_smallint FROM all_types WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2,-100" ]] || false
}
@test "types_compat: int and bigint columns readable" {
run sql_csv -c "SELECT pk, c_int, c_bigint FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,2000000,9223372036854775807" ]] || false
}
@test "types_compat: negative int and bigint readable" {
run sql_csv -c "SELECT pk, c_int, c_bigint FROM all_types WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2,-2000000,-9223372036854775807" ]] || false
}
@test "types_compat: real and double precision readable" {
run sql_csv -c "SELECT pk, c_real, c_double FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,1.5,2.5" ]] || false
}
@test "types_compat: numeric column readable" {
run sql_csv -c "SELECT pk, c_numeric FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,12345.67" ]] || false
}
@test "types_compat: negative numeric readable" {
run sql_csv -c "SELECT pk, c_numeric FROM all_types WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2,-12345.67" ]] || false
}
# ---------------------------------------------------------------------------
# Text types
# ---------------------------------------------------------------------------
@test "types_compat: char column readable" {
run sql_csv -c "SELECT pk, trim(c_char) FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "hello" ]] || false
}
@test "types_compat: varchar column readable" {
run sql_csv -c "SELECT pk, c_varchar FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,hello world" ]] || false
}
@test "types_compat: text column readable" {
run sql_csv -c "SELECT pk, c_text FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,text val" ]] || false
}
@test "types_compat: large text value (500 chars) readable" {
run sql_csv -c "SELECT pk, length(c_text) FROM all_types WHERE pk=3;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3,500" ]] || false
}
# ---------------------------------------------------------------------------
# Temporal types
# ---------------------------------------------------------------------------
@test "types_compat: date column readable" {
run sql_csv -c "SELECT pk, c_date FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,2024-01-15" ]] || false
}
@test "types_compat: time column readable" {
skip "broken functionality"
run sql_csv -c "SELECT pk, c_time FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "13:30:45" ]] || false
}
@test "types_compat: timestamp column readable" {
run sql_csv -c "SELECT pk, c_timestamp FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2024-01-15" ]] || false
[[ "$output" =~ "13:30:45" ]] || false
}
@test "types_compat: timestamptz not null for pk=1" {
run sql_csv -c "SELECT pk, (c_timestamptz IS NOT NULL) FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "t" ]] || false
}
@test "types_compat: timestamptz is null for pk=2" {
run sql_csv -c "SELECT pk, (c_timestamptz IS NULL) FROM all_types WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "t" ]] || false
}
# ---------------------------------------------------------------------------
# Boolean type
# ---------------------------------------------------------------------------
@test "types_compat: boolean column readable" {
run sql_csv -c "SELECT pk, c_boolean FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,t" ]] || false
}
@test "types_compat: false boolean readable" {
run sql_csv -c "SELECT pk, c_boolean FROM all_types WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2,f" ]] || false
}
# ---------------------------------------------------------------------------
# JSONB type
# ---------------------------------------------------------------------------
@test "types_compat: jsonb column readable" {
run sql_csv -c "SELECT pk, c_jsonb->'k' FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "v" ]] || false
}
@test "types_compat: jsonb array readable" {
skip "jsonb_array_length not implemented"
run sql_csv -c "SELECT pk, jsonb_array_length(c_jsonb) FROM all_types WHERE pk=2;"
[ "$status" -eq 0 ]
[[ "$output" =~ "3" ]] || false
}
# ---------------------------------------------------------------------------
# DML: insert and read back all column types
# ---------------------------------------------------------------------------
@test "types_compat: full-column-set insert round-trips correctly" {
skip "spurious error message for numeric type"
sql <<SQL
INSERT INTO all_types (pk, c_smallint, c_int, c_bigint,
c_real, c_double, c_numeric,
c_char, c_varchar, c_text, c_bytea,
c_date, c_time, c_timestamp, c_timestamptz,
c_boolean, c_jsonb)
VALUES (50, 55, 555555, 5555555555,
0.5, 0.25, 55.55,
'round', 'round trip', 'rt text', '\x526F756E64',
'2025-03-18', '09:00:00', '2025-03-18 09:00:00', '2025-03-18 09:00:00+00',
true, '{"round":true}');
SQL
run sql_csv -c "SELECT pk, c_smallint, c_int FROM all_types WHERE pk=50;"
[ "$status" -eq 0 ]
[[ "$output" =~ "50,55,555555" ]] || false
run sql_csv -c "SELECT pk, c_varchar, c_text FROM all_types WHERE pk=50;"
[ "$status" -eq 0 ]
[[ "$output" =~ "50,round trip,rt text" ]] || false
run sql_csv -c "SELECT pk, c_boolean FROM all_types WHERE pk=50;"
[ "$status" -eq 0 ]
[[ "$output" =~ "50,t" ]] || false
}
@test "types_compat: update all_types row written by older version" {
sql -c "UPDATE all_types SET c_varchar='updated', c_text='updated text' WHERE pk=1;"
run sql_csv -c "SELECT pk, c_varchar, c_text FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,updated,updated text" ]] || false
}
@test "types_compat: update text to large value" {
sql -c "UPDATE all_types SET c_text=repeat('u', 2000) WHERE pk=1;"
run sql_csv -c "SELECT pk, length(c_text) FROM all_types WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,2000" ]] || false
}
@test "types_compat: delete a row from old table" {
sql -c "DELETE FROM all_types WHERE pk=2;"
run sql_csv -c "SELECT count(*) FROM all_types;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2" ]] || false
}
# ---------------------------------------------------------------------------
# ALTER TABLE: add columns of various types to a table written by older version
# ---------------------------------------------------------------------------
@test "types_compat: add text column to abc and use dml" {
sql -c "ALTER TABLE abc ADD COLUMN new_text TEXT;"
sql -c "UPDATE abc SET new_text='text for row';"
sql -c "INSERT INTO abc (pk, a, b, x, y, new_text) VALUES (99, 'test', 1.0, 0, 0, 'inserted');"
run sql_csv -c "SELECT pk, new_text FROM abc WHERE pk=99;"
[ "$status" -eq 0 ]
[[ "$output" =~ "99,inserted" ]] || false
sql -c "DELETE FROM abc WHERE pk=99;"
}
@test "types_compat: add integer columns to abc and use dml" {
sql -c "ALTER TABLE abc ADD COLUMN new_smallint SMALLINT, ADD COLUMN new_bigint BIGINT;"
sql -c "UPDATE abc SET new_smallint=42, new_bigint=9999999999;"
sql -c "INSERT INTO abc (pk, a, b, x, y, new_smallint, new_bigint) VALUES (99, 'test', 1.0, 0, 0, -1, -9999999999);"
run sql_csv -c "SELECT pk, new_smallint, new_bigint FROM abc WHERE pk=99;"
[ "$status" -eq 0 ]
[[ "$output" =~ "99,-1,-9999999999" ]] || false
sql -c "DELETE FROM abc WHERE pk=99;"
}
@test "types_compat: add numeric column to abc and use dml" {
sql -c "ALTER TABLE abc ADD COLUMN new_numeric NUMERIC(12,4);"
sql -c "UPDATE abc SET new_numeric=9876.5432;"
sql -c "INSERT INTO abc (pk, a, b, x, y, new_numeric) VALUES (99, 'test', 1.0, 0, 0, -9876.5432);"
run sql_csv -c "SELECT pk, new_numeric FROM abc WHERE pk=99;"
[ "$status" -eq 0 ]
[[ "$output" =~ "99,-9876.5432" ]] || false
sql -c "DELETE FROM abc WHERE pk=99;"
}
@test "types_compat: add date/timestamp columns to abc and use dml" {
sql -c "ALTER TABLE abc ADD COLUMN new_date DATE, ADD COLUMN new_ts TIMESTAMP;"
sql -c "UPDATE abc SET new_date='2025-03-18', new_ts='2025-03-18 10:00:00';"
sql -c "INSERT INTO abc (pk, a, b, x, y, new_date, new_ts) VALUES (99, 'test', 1.0, 0, 0, '2025-06-01', '2025-06-01 12:00:00');"
run sql_csv -c "SELECT pk, new_date FROM abc WHERE pk=99;"
[ "$status" -eq 0 ]
[[ "$output" =~ "99,2025-06-01" ]] || false
sql -c "DELETE FROM abc WHERE pk=99;"
}
@test "types_compat: add boolean column to abc and use dml" {
sql -c "ALTER TABLE abc ADD COLUMN new_bool BOOLEAN;"
sql -c "UPDATE abc SET new_bool=true;"
sql -c "INSERT INTO abc (pk, a, b, x, y, new_bool) VALUES (99, 'test', 1.0, 0, 0, false);"
run sql_csv -c "SELECT pk, new_bool FROM abc WHERE pk=99;"
[ "$status" -eq 0 ]
[[ "$output" =~ "99,f" ]] || false
sql -c "DELETE FROM abc WHERE pk=99;"
}
@test "types_compat: add jsonb column to abc and use dml" {
sql -c "ALTER TABLE abc ADD COLUMN new_jsonb JSONB;"
sql -c "UPDATE abc SET new_jsonb='{\"updated\":true}';"
sql -c "INSERT INTO abc (pk, a, b, x, y, new_jsonb) VALUES (99, 'test', 1.0, 0, 0, '{\"inserted\":1}');"
run sql_csv -c "SELECT pk, new_jsonb->>'inserted' FROM abc WHERE pk=99;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1" ]] || false
sql -c "DELETE FROM abc WHERE pk=99;"
}
@test "types_compat: add bytea column to abc and use dml" {
skip "encode not implemented"
sql -c "ALTER TABLE abc ADD COLUMN new_bytea BYTEA;"
sql -c "UPDATE abc SET new_bytea='\xDEAD';"
sql -c "INSERT INTO abc (pk, a, b, x, y, new_bytea) VALUES (99, 'test', 1.0, 0, 0, '\xBEEF');"
run sql_csv -c "SELECT pk, encode(new_bytea, 'hex') FROM abc WHERE pk=99;"
[ "$status" -eq 0 ]
[[ "$output" =~ "beef" ]] || false
sql -c "DELETE FROM abc WHERE pk=99;"
}
# ---------------------------------------------------------------------------
# Schema inspection
# ---------------------------------------------------------------------------
@test "types_compat: all_types column types visible in information_schema" {
run sql -c "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='all_types' ORDER BY ordinal_position;"
[ "$status" -eq 0 ]
[[ "$output" =~ "smallint" ]] || false
[[ "$output" =~ "integer" ]] || false
[[ "$output" =~ "bigint" ]] || false
[[ "$output" =~ "real" ]] || false
[[ "$output" =~ "double precision" ]] || false
[[ "$output" =~ "numeric" ]] || false
[[ "$output" =~ "character" ]] || false
[[ "$output" =~ "text" ]] || false
[[ "$output" =~ "bytea" ]] || false
[[ "$output" =~ "date" ]] || false
[[ "$output" =~ "timestamp" ]] || false
[[ "$output" =~ "boolean" ]] || false
[[ "$output" =~ "jsonb" ]] || false
}
# ---------------------------------------------------------------------------
# Commit works after DML on old table
# ---------------------------------------------------------------------------
@test "types_compat: commit works after dml on all_types" {
sql -c "INSERT INTO all_types (pk, c_text) VALUES (98, 'commit test');"
sql -c "SELECT dolt_add('.');"
run sql -c "SELECT length(dolt_commit('-m', 'types compat commit')::text);"
[ "$status" -eq 0 ]
[[ "$output" =~ "34" ]] || false
}
# ---------------------------------------------------------------------------
# View over all_types
# ---------------------------------------------------------------------------
@test "types_compat: all_types_view returns same rows as underlying table" {
run sql_csv -c "SELECT pk, c_smallint, c_varchar FROM all_types_view WHERE pk=1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "1,100,hello world" ]] || false
}
@test "types_compat: all_types_view supports filtering" {
run sql_csv -c "SELECT count(*) FROM all_types_view WHERE pk < 3;"
[ "$status" -eq 0 ]
[[ "$output" =~ "2" ]] || false
}
@@ -0,0 +1,6 @@
# Doltgresql versions to test forward compatibility against.
# These older versions must be able to read repos created by the current HEAD.
# Typically a smaller set than backward_compatible_versions because forward
# compatibility windows are narrower — old clients may lack support for newer
# storage features written by HEAD.
v0.56.5
+247
View File
@@ -0,0 +1,247 @@
#!/bin/bash
# Creates a doltgresql repository populated with test data for compatibility testing.
# Usage: setup_repo.sh <datadir> [<doltgres-binary>]
#
# Starts a temporary doltgres server, creates tables / views / branches expected
# by compatibility.bats and types_compatibility.bats, then stops the server.
# The resulting data directory is the "repo" passed to BATS tests via REPO_DIR.
set -eo pipefail
DATADIR="$1"
DOLTGRES_BIN="${2:-doltgres}"
if [ -z "$DATADIR" ]; then
echo "Usage: setup_repo.sh <datadir> [<doltgres-binary>]" >&2
exit 1
fi
mkdir -p "$DATADIR"
# ---------------------------------------------------------------------------
# Server helpers
# ---------------------------------------------------------------------------
pick_port() {
for i in {0..99}; do
local port=$((RANDOM % 4096 + 2048))
if ! nc -z localhost "$port" 2>/dev/null; then
echo "$port"
return 0
fi
done
echo "ERROR: could not find a free port" >&2
return 1
}
PORT=$(pick_port)
CONFIGFILE="$DATADIR/setup-config.yaml"
cat > "$CONFIGFILE" <<EOF
log_level: warning
behavior:
read_only: false
disable_client_multi_statements: false
listener:
host: localhost
port: $PORT
EOF
"$DOLTGRES_BIN" -data-dir="$DATADIR" --config="$CONFIGFILE" \
> "$DATADIR/setup.log" 2>&1 &
SERVER_PID=$!
trap 'kill "$SERVER_PID" 2>/dev/null; wait "$SERVER_PID" 2>/dev/null; exit' EXIT INT TERM
echo "Waiting for doltgres server on port $PORT ..."
end=$((SECONDS + 20))
while [ $SECONDS -lt $end ]; do
if PGPASSWORD=password psql -U postgres -h localhost -p "$PORT" \
-c "SELECT 1;" postgres >/dev/null 2>&1; then
echo "Server ready."
break
fi
sleep 0.5
done
if ! PGPASSWORD=password psql -U postgres -h localhost -p "$PORT" \
-c "SELECT 1;" postgres >/dev/null 2>&1; then
echo "ERROR: server failed to start. Log:" >&2
cat "$DATADIR/setup.log" >&2
exit 1
fi
# Q — run SQL against the server (heredoc-friendly, single session).
Q() {
PGPASSWORD=password psql -U postgres -h localhost -p "$PORT" \
-v ON_ERROR_STOP=1 "$@" postgres
}
# ---------------------------------------------------------------------------
# Step 1: Create initial schema and data on main, then branch from there.
#
# Branch layout:
# init — snapshot of initial data (abc w/o alterations)
# other — diverges from init; drops x, adds z to abc
# check_merge — diverges from main after its alterations; adds rows to def
# main — drops w, adds y to abc; clean-merges check_merge
# ---------------------------------------------------------------------------
echo "Step 1: creating initial schema on main ..."
Q <<'SQL'
CREATE TABLE abc (
pk BIGINT NOT NULL,
a TEXT,
b DOUBLE PRECISION,
w BIGINT,
x BIGINT,
PRIMARY KEY (pk)
);
INSERT INTO abc VALUES
(0, 'asdf', 1.1, 0, 0),
(1, 'asdf', 1.1, 0, 0),
(2, 'asdf', 1.1, 0, 0);
CREATE VIEW view1 AS SELECT 2+2;
CREATE TABLE def (
i INT CHECK (i > 0)
);
INSERT INTO def VALUES (1), (2), (3);
CREATE TABLE all_types (
pk INT NOT NULL PRIMARY KEY,
c_smallint SMALLINT,
c_int INT,
c_bigint BIGINT,
c_real REAL,
c_double DOUBLE PRECISION,
c_numeric NUMERIC(10,2),
c_char CHAR(10),
c_varchar VARCHAR(255),
c_text TEXT,
c_bytea BYTEA,
c_date DATE,
c_time TIME,
c_timestamp TIMESTAMP,
c_timestamptz TIMESTAMPTZ,
c_boolean BOOLEAN,
c_jsonb JSONB
);
INSERT INTO all_types VALUES (
1,
100, 2000000, 9223372036854775807,
1.5, 2.5, 12345.67,
'hello', 'hello world', 'text val', E'\\xDEADBEEF',
'2024-01-15', '13:30:45', '2024-01-15 13:30:45', '2024-01-15 13:30:45+00',
true, '{"k":"v"}'
);
INSERT INTO all_types VALUES (
2,
-100, -2000000, -9223372036854775807,
-1.5, -2.5, -12345.67,
'hi', 'hi there', 'text val2', E'\\xC0FFEE',
'2023-12-31', '23:59:59', '2023-12-31 23:59:59', NULL,
false, '[1,2,3]'
);
INSERT INTO all_types (pk, c_text) VALUES (3, repeat('t', 500));
CREATE VIEW all_types_view AS SELECT * FROM all_types;
SQL
Q -c "SELECT dolt_add('.');"
Q -c "SELECT dolt_commit('-m', 'initialized data');"
Q <<'EOF'
CREATE TABLE big (
pk INT PRIMARY KEY,
str TEXT
);
EOF
for i in $(seq 0 9); do
start=$((i * 100 + 1))
end=$((start + 99))
stmt="INSERT INTO big VALUES "
for j in $(seq $start $end); do
stmt+=$(printf "(%d, 'row %d')" $j $j)
[ $j -lt $end ] && stmt+=", "
done
stmt+=";"
Q -c "$stmt"
done
# ---------------------------------------------------------------------------
# Step 2: Branch 'init' and 'other' from this initial-data commit.
# ---------------------------------------------------------------------------
echo "Step 2: branching init and other from initial data commit ..."
Q -c "SELECT dolt_branch('init');"
Q -c "SELECT dolt_branch('other');"
# ---------------------------------------------------------------------------
# Step 3: Advance main — alter abc (drop w, add y).
# ---------------------------------------------------------------------------
echo "Step 3: advancing main with abc alterations ..."
Q <<'SQL'
DELETE FROM abc WHERE pk=1;
UPDATE abc SET x = 1 WHERE pk = 0;
INSERT INTO abc VALUES (3, 'data', 1.1, 0, 0);
ALTER TABLE abc DROP COLUMN w;
ALTER TABLE abc ADD COLUMN y BIGINT;
UPDATE abc SET y = 121;
SQL
Q -c "SELECT dolt_add('.');"
Q -c "SELECT dolt_commit('-m', 'made changes to main');"
# Branch 'check_merge' from main's current commit (has abc alterations).
Q -c "SELECT dolt_branch('check_merge');"
# ---------------------------------------------------------------------------
# Step 4: Populate 'other' — different alterations to abc.
# ---------------------------------------------------------------------------
echo "Step 4: populating other branch ..."
Q <<'SQL'
SELECT dolt_checkout('other');
DELETE FROM abc WHERE pk=2;
UPDATE abc SET w = 1 WHERE pk = 0;
INSERT INTO abc VALUES (4, 'data', 1.1, 0, 0);
ALTER TABLE abc DROP COLUMN x;
ALTER TABLE abc ADD COLUMN z BIGINT;
UPDATE abc SET z = 122;
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'made changes to other');
SQL
# ---------------------------------------------------------------------------
# Step 5: Populate 'check_merge' — add rows to def only.
# ---------------------------------------------------------------------------
echo "Step 5: populating check_merge branch ..."
Q <<'SQL'
SELECT dolt_checkout('check_merge');
INSERT INTO def VALUES (5), (6), (7);
SELECT dolt_add('.');
SELECT dolt_commit('-m', 'made changes to check_merge');
SQL
# ---------------------------------------------------------------------------
# Done
# ---------------------------------------------------------------------------
echo ""
echo "Repository setup complete."
echo " Data dir : $DATADIR"
echo " Branches : main, init, other, check_merge"
kill "$SERVER_PID"
wait "$SERVER_PID" 2>/dev/null || true
trap - EXIT INT TERM