Files
2026-07-13 12:32:25 +08:00

912 lines
33 KiB
Go

// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/dolthub/go-mysql-server/sql"
"github.com/stretchr/testify/require"
)
// localRemoteUrl returns a file:// URL for a fresh temp directory suitable for use as a Dolt
// remote. Each call produces a unique directory, so two calls with the same |name| never collide.
// We avoid using [testing.T.TempDir] here, because on Windows, NBS chunk files can still be
// memory-mapped and locked for a brief window after a server's controller reports itself stopped,
// which interfers with the cleanup.
func localRemoteUrl(t *testing.T, name string) string {
t.Helper()
safeName := strings.ReplaceAll(t.Name(), "/", "_")
dir, err := os.MkdirTemp(os.TempDir(), safeName+"_"+name)
require.NoError(t, err)
return "file://" + filepath.ToSlash(dir)
}
// runRemoteTest runs |script| inside a dedicated, freshly-started on-disk Doltgres server and
// tears the server down when the subtest completes.
//
// A new server is started for every call so that each push/pull/clone test begins with a clean
// database state. The server runs against a temporary on-disk directory (via
// CreateServerLocalWithPort) because pushing to, pulling from, or cloning a file:// remote
// requires a real NBS chunk store -- in-memory storage cannot be synced to a file:// URL.
//
// dolt_push/dolt_pull/dolt_fetch/dolt_clone/dolt_remote are all invoked with SELECT rather than
// CALL: Doltgres only allows built-in Dolt procedures to be invoked as functions (see
// ErrDoltProcedureSelectOnly in server/functions/dolt_procedures.go).
func runRemoteTest(t *testing.T, script ScriptTest) {
t.Helper()
t.Run(script.Name, func(t *testing.T) {
t.Helper()
port, err := sql.GetEmptyPort()
require.NoError(t, err)
ctx, conn, controller := CreateServerLocalWithPort(t, "postgres", port)
defer func() {
conn.Close(ctx)
controller.Stop()
require.NoError(t, controller.WaitForStop())
}()
runScript(t, ctx, script, conn, true)
})
}
// TestDoltRemote exercises the file-system remote push/pull/fetch/clone workflow end to end.
func TestDoltRemote(t *testing.T) {
remoteUrl := localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "push and clone preserve table data and commit history",
SetUpScript: []string{
"create table items (id int primary key, label text not null);",
"insert into items values (1, 'apple'), (2, 'banana');",
"select dolt_commit('-Am', 'first commit: add apple and banana');",
"insert into items values (3, 'cherry');",
"select dolt_commit('-Am', 'second commit: add cherry');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_items');", remoteUrl),
"USE cloned_items",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, label from items order by id;",
Expected: []sql.Row{{1, "apple"}, {2, "banana"}, {3, "cherry"}},
},
{
// Initialize + CREATE DATABASE + 2 user commits.
Query: "select count(*) from dolt.commits;",
Expected: []sql.Row{{int64(4)}},
},
{
Query: "select message from dolt.commits order by date;",
Expected: []sql.Row{{"Initialize data repository"}, {"CREATE DATABASE"}, {"first commit: add apple and banana"}, {"second commit: add cherry"}},
},
{
// dolt_clone automatically configures "origin" to point back at the source URL.
Query: "select name from dolt.remotes;",
Expected: []sql.Row{{"origin"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "standalone sequence state is preserved across push and clone",
SetUpScript: []string{
"create sequence counter start 1 increment 5;",
"select nextval('counter');", // advances to 1
"select nextval('counter');", // advances to 6
"select nextval('counter');", // advances to 11
"select dolt_commit('-Am', 'seed counter');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_seq');", remoteUrl),
"drop sequence counter", // delete the sequence in the first database
"USE cloned_seq",
},
Assertions: []ScriptTestAssertion{
{
Query: "select sequence_name, sequence_catalog from information_schema.sequences where sequence_schema = 'public';",
Expected: []sql.Row{{"counter", "cloned_seq"}},
},
{
// Must continue from where it left off (after 11, next is 16), not reset to 1.
Query: "select nextval('counter');",
Expected: []sql.Row{{int64(16)}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "serial column's owned sequence state is preserved across push and clone",
SetUpScript: []string{
"create table widgets (id serial primary key, name text);",
"insert into widgets (name) values ('a'), ('b'), ('c');",
"select dolt_commit('-Am', 'seed widgets');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_widgets');", remoteUrl),
"drop table widgets",
"USE cloned_widgets",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, name from widgets order by id;",
Expected: []sql.Row{{1, "a"}, {2, "b"}, {3, "c"}},
},
{
// Auto-generated id must continue from 4, proving the owned sequence's state was pushed.
Query: "insert into widgets (name) values ('d') returning id;",
Expected: []sql.Row{{4}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "identity column's owned sequence state is preserved across push and clone",
SetUpScript: []string{
"create table gadgets (id bigint generated by default as identity primary key, name text);",
"insert into gadgets (name) values ('a'), ('b'), ('c');",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed gadgets');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_gadgets');", remoteUrl),
"drop table gadgets",
"USE cloned_gadgets",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, name from gadgets order by id;",
Expected: []sql.Row{{1, "a"}, {2, "b"}, {3, "c"}},
},
{
Query: "insert into gadgets (name) values ('d') returning id;",
Expected: []sql.Row{{4}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "sequence in a non-public schema is preserved across push and clone",
SetUpScript: []string{
"create schema myschema;",
"create sequence myschema.seq2 start 100 increment 10;",
"select nextval('myschema.seq2');", // advances to 100
"select dolt_add('.');",
"select dolt_commit('-m', 'seed myschema.seq2');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_schema');", remoteUrl),
"USE cloned_schema",
},
Assertions: []ScriptTestAssertion{
{
Query: "select sequence_schema, sequence_name from information_schema.sequences where sequence_name = 'seq2';",
Expected: []sql.Row{{"myschema", "seq2"}},
},
{
// Must continue from 100 (after one call), not reset to the start value.
Query: "select nextval('myschema.seq2');",
Expected: []sql.Row{{int64(110)}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "incremental push and pull keep sequence and table state in sync",
SetUpScript: []string{
"create sequence counter start 1 increment 5;",
"create table orders (id int primary key default nextval('counter'), item text);",
"select dolt_commit('-Am', 'initial schema');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'replica');", remoteUrl),
// Advance the sequence and add data on the source side only.
"USE postgres",
"insert into orders (item) values ('widget');", // consumes nextval -> 1
"select dolt_commit('-Am', 'first order');",
"select dolt_push('origin', 'main');",
// Pull the change into the replica.
"USE replica",
"select dolt_pull('origin');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, item from orders order by id;",
Expected: []sql.Row{{1, "widget"}},
},
{
Query: "select nextval('counter');",
Expected: []sql.Row{{int64(6)}},
},
},
})
runRemoteTest(t, ScriptTest{
Name: "dolt_remotes reflects add and remove",
SetUpScript: []string{
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", localRemoteUrl(t, "remote_a")),
fmt.Sprintf("select dolt_remote('add', 'other', '%s');", localRemoteUrl(t, "remote_b")),
},
Assertions: []ScriptTestAssertion{
{
Query: "select name from dolt_remotes order by name;",
Expected: []sql.Row{{"origin"}, {"other"}},
},
{
Query: "select dolt_remote('remove', 'other');",
Expected: []sql.Row{{"{0}"}},
},
{
Query: "select name from dolt_remotes;",
Expected: []sql.Row{{"origin"}},
},
{
Query: "select dolt_remote('remove', 'other');",
ExpectedErr: "unknown remote",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "fetch updates remote-tracking branches without touching the working branch; pull merges",
SetUpScript: []string{
"create table events (id int primary key, name text);",
"insert into events values (1, 'first');",
"select dolt_commit('-Am', 'first event');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'subscriber');", remoteUrl),
"USE postgres",
"insert into events values (2, 'second');",
"select dolt_commit('-Am', 'second event');",
"select dolt_push('origin', 'main');",
"USE subscriber",
"select dolt_fetch('origin', 'main');",
},
Assertions: []ScriptTestAssertion{
{
// fetch alone must not update the working branch.
Query: "select id, name from events order by id;",
Expected: []sql.Row{{1, "first"}},
},
{
Query: "select name from dolt_remote_branches;",
Expected: []sql.Row{{"remotes/origin/main"}},
},
{
Query: "select dolt_pull('origin');",
Expected: []sql.Row{{`{1,0,"merge successful"}`}},
},
{
// pull must fast-forward the working branch to match the remote.
Query: "select id, name from events order by id;",
Expected: []sql.Row{{1, "first"}, {2, "second"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pull fails when the working set has uncommitted changes",
SetUpScript: []string{
"create table logs (id int primary key, msg text);",
"insert into logs values (1, 'a');",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'dirty_clone');", remoteUrl),
"USE postgres",
"insert into logs values (2, 'b');",
"select dolt_commit('-Am', 'second');",
"select dolt_push('origin', 'main');",
"USE dirty_clone",
// Leave an uncommitted change on the clone before pulling.
"insert into logs values (3, 'uncommitted');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_pull('origin');",
ExpectedErr: "cannot merge with uncommitted changes",
},
},
})
runRemoteTest(t, ScriptTest{
Name: "push without a configured remote returns an error",
SetUpScript: []string{
"create table t (id int primary key);",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_push('origin', 'main');",
ExpectedErr: "remote 'origin' not found",
},
},
})
runRemoteTest(t, ScriptTest{
Name: "pull without a configured remote returns an error",
SetUpScript: []string{
"create table t (id int primary key);",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_pull('origin');",
ExpectedErr: "remote 'origin' not found",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "custom enum type is preserved across push and clone",
SetUpScript: []string{
"create type mood as enum ('sad', 'ok', 'happy');",
"create table moods (id int primary key, m mood);",
"insert into moods values (1, 'happy'), (2, 'sad');",
"select dolt_commit('-Am', 'seed moods');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_moods');", remoteUrl),
"drop table moods;",
"drop type mood;",
"USE cloned_moods",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, m::text from moods order by id;",
Expected: []sql.Row{{1, "happy"}, {2, "sad"}},
},
{
// The enum type definition itself (not just data using it) must have transferred.
Query: "insert into moods values (3, 'ok') returning m::text;",
Expected: []sql.Row{{"ok"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "user-defined function is preserved across push and clone",
SetUpScript: []string{
"create function double_it(x int) returns int as $$ begin return x * 2; end; $$ language plpgsql;",
"select dolt_commit('-Am', 'seed function');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_func');", remoteUrl),
"drop function double_it();",
"USE cloned_func",
},
Assertions: []ScriptTestAssertion{
{
Query: "select double_it(21);",
Expected: []sql.Row{{42}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pushing and cloning a non-default branch carries its own sequence and table state",
SetUpScript: []string{
"create sequence counter start 1 increment 1;",
"create table gadgets (id int primary key default nextval('counter'), name text);",
"insert into gadgets (name) values ('a'), ('b'), ('c');", // consumes 1, 2, 3
"select dolt_commit('-Am', 'seed on main');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
"select dolt_checkout('-b', 'feature');",
"insert into gadgets (name) values ('feature-only');", // consumes 4
"select dolt_commit('-Am', 'feature work');",
"select dolt_push('origin', 'feature');",
fmt.Sprintf("select dolt_clone('--branch', 'feature', '%s', 'cloned_feature');", remoteUrl),
"drop table gadgets;",
"drop sequence counter;",
"USE cloned_feature",
},
Assertions: []ScriptTestAssertion{
{
Query: "select name from gadgets order by id;",
Expected: []sql.Row{{"a"}, {"b"}, {"c"}, {"feature-only"}},
},
{
// The owned sequence must reflect the feature branch's state (after 4), not main's.
Query: "insert into gadgets (name) values ('next') returning id;",
Expected: []sql.Row{{5}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "non-fast-forward push is rejected, then succeeds with --force",
SetUpScript: []string{
"create table t (id int primary key, note text);",
"insert into t values (1, 'source');",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'clone1');", remoteUrl),
// Diverge history on both sides so neither is a fast-forward of the other.
"insert into t values (2, 'source-diverged');",
"select dolt_commit('-Am', 'source diverges');",
"select dolt_push('origin', 'main');",
"USE clone1",
"insert into t values (3, 'clone-diverged');",
"select dolt_commit('-Am', 'clone diverges');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_push('origin', 'main');",
ExpectedErr: "failed to push some refs",
},
{
Query: "select dolt_push('-f', 'origin', 'main');",
SkipResultsCheck: true,
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pushing a branch delete removes it from the remote",
SetUpScript: []string{
"create table t (id int primary key);",
"insert into t values (1);",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
"select dolt_checkout('-b', 'doomed');",
"insert into t values (2);",
"select dolt_commit('-Am', 'doomed work');",
"select dolt_push('origin', 'doomed');",
fmt.Sprintf("select dolt_clone('%s', 'observer');", remoteUrl),
"USE observer",
"select dolt_fetch('origin', 'doomed');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select name from dolt_remote_branches order by name;",
Expected: []sql.Row{{"remotes/origin/doomed"}, {"remotes/origin/main"}},
},
{
Query: "USE postgres",
SkipResultsCheck: true,
},
{
// A ":branch" refspec deletes the branch on the remote, just like the CLI's `dolt push origin :doomed`.
Query: "select dolt_push('origin', ':doomed');",
SkipResultsCheck: true,
},
{
Query: "USE observer",
SkipResultsCheck: true,
},
{
Query: "select dolt_fetch('--prune', 'origin');",
SkipResultsCheck: true,
},
{
Query: "select name from dolt_remote_branches order by name;",
Expected: []sql.Row{{"remotes/origin/main"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "tags are pushed and fetched along with commits",
SetUpScript: []string{
"create table t (id int primary key);",
"insert into t values (1);",
"select dolt_commit('-Am', 'seed');",
"select dolt_tag('v1.0', '-m', 'first release');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
"select dolt_push('origin', 'v1.0');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_tags');", remoteUrl),
"USE cloned_tags",
},
Assertions: []ScriptTestAssertion{
{
Query: "select tag_name from dolt_tags;",
Expected: []sql.Row{{"v1.0"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "fetch supports an explicit refspec into a custom tracking ref",
SetUpScript: []string{
"create table t (id int primary key);",
"insert into t values (1);",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'observer');", remoteUrl),
"USE observer",
"select dolt_fetch('origin', 'refs/heads/main:refs/remotes/custom/main');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select name from dolt_remote_branches where name = 'remotes/custom/main';",
Expected: []sql.Row{{"remotes/custom/main"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "push --all pushes every local branch",
SetUpScript: []string{
"create table t (id int primary key);",
"insert into t values (1);",
"select dolt_commit('-Am', 'seed');",
"select dolt_checkout('-b', 'branch_a');",
"select dolt_checkout('main');",
"select dolt_checkout('-b', 'branch_b');",
"select dolt_checkout('main');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('--all', 'origin');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_all');", remoteUrl),
"USE cloned_all",
},
Assertions: []ScriptTestAssertion{
{
// Clone only checks out a local branch for the default branch; the others land as
// remote-tracking refs, proving --all pushed them all to the remote.
Query: "select name from dolt_remote_branches order by name;",
Expected: []sql.Row{{"remotes/origin/branch_a"}, {"remotes/origin/branch_b"}, {"remotes/origin/main"}},
},
},
})
runRemoteTest(t, ScriptTest{
Name: "adding a remote with a name that already exists returns an error",
SetUpScript: []string{
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", localRemoteUrl(t, "remote_a")),
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", localRemoteUrl(t, "remote_b")),
ExpectedErr: "remote already exists",
},
},
})
runRemoteTest(t, ScriptTest{
Name: "adding a remote with an invalid name returns an error",
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("select dolt_remote('add', 'bad name', '%s');", localRemoteUrl(t, "remote")),
ExpectedErr: "remote name invalid",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "fetching an invalid ref spec returns an error",
SetUpScript: []string{
"create table t (id int primary key);",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_fetch('origin', 'garbage');",
ExpectedErr: "invalid ref spec",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pull auto-merges non-conflicting divergent history",
SetUpScript: []string{
"create table t (id int primary key, v text);",
"insert into t values (1, 'a'), (2, 'b');",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'clone1');", remoteUrl),
"USE postgres",
"insert into t values (3, 'source-row');",
"select dolt_commit('-Am', 'source adds row 3');",
"select dolt_push('origin', 'main');",
"USE clone1",
"insert into t values (4, 'clone-row');",
"select dolt_commit('-Am', 'clone adds row 4');",
},
Assertions: []ScriptTestAssertion{
{
// Divergent but non-conflicting: no fast-forward, no conflicts, and it merges automatically.
Query: "select dolt_pull('origin');",
Expected: []sql.Row{{`{0,0,"merge successful"}`}},
},
{
Query: "select id, v from t order by id;",
Expected: []sql.Row{{1, "a"}, {2, "b"}, {3, "source-row"}, {4, "clone-row"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pull surfaces real merge conflicts, resolvable via dolt_conflicts_resolve",
SetUpScript: []string{
"create table t (id int primary key, v text);",
"insert into t values (1, 'a'), (2, 'b');",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'clone1');", remoteUrl),
"USE postgres",
"update t set v = 'source-edit' where id = 1;",
"select dolt_commit('-Am', 'source edits row 1');",
"select dolt_push('origin', 'main');",
"USE clone1",
"update t set v = 'clone-edit' where id = 1;",
"select dolt_commit('-Am', 'clone edits row 1');",
// Conflicts abort the implicit transaction under autocommit unless explicitly allowed.
"set dolt_allow_commit_conflicts to 1;",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_pull('origin');",
Expected: []sql.Row{{`{0,1,"merge has unresolved conflicts or constraint violations"}`}},
},
{
Query: "select base_v, our_v, their_v from dolt_conflicts_t;",
Expected: []sql.Row{{"a", "clone-edit", "source-edit"}},
},
{
Query: "select dolt_conflicts_resolve('--ours', 't');",
SkipResultsCheck: true,
},
{
Query: "select count(*) from dolt_conflicts;",
Expected: []sql.Row{{int64(0)}},
},
{
// --ours keeps the clone's own edit.
Query: "select id, v from t order by id;",
Expected: []sql.Row{{1, "clone-edit"}, {2, "b"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "domain type and its check constraint are preserved across push and clone",
SetUpScript: []string{
"create domain pos_int as integer check (value > 0);",
"create table measurements (id int primary key, val pos_int);",
"insert into measurements values (1, 42);",
"select dolt_commit('-Am', 'seed measurements');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_domain');", remoteUrl),
"drop table measurements;",
"drop domain pos_int;",
"USE cloned_domain",
},
Assertions: []ScriptTestAssertion{
{
// typtype = 'd' for domain types.
Query: "select typname, typtype from pg_type where typname = 'pos_int';",
Expected: []sql.Row{{"pos_int", "d"}},
},
{
Query: "select val from measurements where id = 1;",
Expected: []sql.Row{{42}},
},
{
// The domain's check constraint must still be enforced after cloning.
Query: "insert into measurements values (2, -1);",
ExpectedErr: "pos_int_check",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "user-defined composite type is preserved across push and clone",
SetUpScript: []string{
`create type point_t as (x float8, y float8);`,
`create function distance(p point_t) returns float8 as $$ begin return sqrt((p).x * (p).x + (p).y * (p).y); end; $$ language plpgsql;`,
"select dolt_commit('-Am', 'seed composite type');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_composite');", remoteUrl),
"drop function distance(point_t);",
"drop type point_t;",
"USE cloned_composite",
},
Assertions: []ScriptTestAssertion{
{
// typtype = 'c' for composite types.
Query: "select typname, typtype from pg_type where typname = 'point_t';",
Expected: []sql.Row{{"point_t", "c"}},
},
{
Query: "select distance(row(3.0, 4.0)::point_t);",
Expected: []sql.Row{{float64(5.0)}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "stored procedure is preserved across push and clone",
SetUpScript: []string{
"create table job_log (id int primary key, status text);",
"insert into job_log values (1, 'pending');",
`create procedure mark_done(job_id int) as $$ begin update job_log set status = 'done' where id = job_id; end; $$ language plpgsql;`,
"select dolt_commit('-Am', 'seed procedure');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_proc');", remoteUrl),
"drop procedure mark_done(int);",
"drop table job_log;",
"USE cloned_proc",
},
Assertions: []ScriptTestAssertion{
{
Query: "call mark_done(1);",
SkipResultsCheck: true,
},
{
Query: "select status from job_log where id = 1;",
Expected: []sql.Row{{"done"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "trigger is preserved and fires after clone",
SetUpScript: []string{
"create table readings (id int primary key, val int);",
`create function clamp_val() returns trigger as $$ begin if NEW.val > 100 then NEW.val := 100; end if; return NEW; end; $$ language plpgsql;`,
"create trigger clamp_trigger before insert on readings for each row execute function clamp_val();",
"select dolt_commit('-Am', 'seed trigger');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_trigger');", remoteUrl),
"drop trigger clamp_trigger on readings;",
"drop function clamp_val();",
"drop table readings;",
"USE cloned_trigger",
},
Assertions: []ScriptTestAssertion{
{
// Inserting val=200 must be clamped to 100 by the trigger, proving it still fires post-clone.
Query: "insert into readings values (1, 200);",
SkipResultsCheck: true,
},
{
Query: "select val from readings where id = 1;",
Expected: []sql.Row{{100}},
},
},
})
// TODO: Extension loading in Windows CI environments don't work currently
if !(runtime.GOOS == "windows" && os.Getenv("CI") != "") {
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "extension is preserved across push and clone",
SetUpScript: []string{
`create extension "uuid-ossp";`,
"select dolt_commit('-Am', 'seed extension');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_ext');", remoteUrl),
"USE cloned_ext",
},
Assertions: []ScriptTestAssertion{
{
// pg_extension is an unimplemented stub; calling the extension function is the best available proof.
// uuid_generate_v4() returns a 36-character UUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
Query: "select length(uuid_generate_v4()::text) = 36;",
Expected: []sql.Row{{"t"}},
},
},
})
}
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "named schema contents are preserved across push and clone",
SetUpScript: []string{
"create schema inventory;",
"create table inventory.products (id int primary key, name text);",
"insert into inventory.products values (1, 'widget'), (2, 'gadget');",
"select dolt_commit('-Am', 'add inventory schema');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_schema_contents');", remoteUrl),
"drop table inventory.products;",
"drop schema inventory;",
"USE cloned_schema_contents",
},
Assertions: []ScriptTestAssertion{
{
Query: "select schema_name from information_schema.schemata where schema_name = 'inventory';",
Expected: []sql.Row{{"inventory"}},
},
{
Query: "select id, name from inventory.products order by id;",
Expected: []sql.Row{{1, "widget"}, {2, "gadget"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "custom cast is preserved across push and clone",
SetUpScript: []string{
// CREATE TABLE implicitly creates a composite row type of the same name.
`CREATE TABLE cast_src (v text);`,
`CREATE TABLE cast_dst (v text, tag text);`,
`CREATE FUNCTION cast_src_to_dst(src cast_src) RETURNS cast_dst AS $$ SELECT ROW((src).v, 'casted')::cast_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_verify(dst cast_dst) RETURNS text AS $$ SELECT (dst).v || ':' || (dst).tag $$ LANGUAGE SQL;`,
`CREATE CAST (cast_src AS cast_dst) WITH FUNCTION cast_src_to_dst(cast_src);`,
"select dolt_commit('-Am', 'seed cast');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_cast');", remoteUrl),
"drop cast (cast_src as cast_dst);",
"drop function cast_src_to_dst(cast_src);",
"drop function cast_verify(cast_dst);",
"drop table cast_src;",
"drop table cast_dst;",
"USE cloned_cast",
},
Assertions: []ScriptTestAssertion{
{
// context 'e' = explicit, method 'f' = function.
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_src' AND dst.typname = 'cast_dst';`,
Expected: []sql.Row{{"e", "f"}},
},
{
Query: `SELECT cast_verify((ROW('hello')::cast_src)::cast_dst);`,
Expected: []sql.Row{{"hello:casted"}},
},
},
})
}