Merge branch 'main' into jennifersp-9517b259

This commit is contained in:
jennifersp
2022-05-04 17:09:01 -07:00
committed by GitHub
16 changed files with 967 additions and 202 deletions

View File

@@ -15,6 +15,7 @@
package durable
import (
"bytes"
"context"
"errors"
"fmt"
@@ -96,6 +97,9 @@ type Table interface {
GetAutoIncrement(ctx context.Context) (uint64, error)
// SetAutoIncrement sets the AUTO_INCREMENT sequence value for this table.
SetAutoIncrement(ctx context.Context, val uint64) (Table, error)
// DebugString returns the table contents for debugging purposes
DebugString(ctx context.Context) string
}
type nomsTable struct {
@@ -103,6 +107,26 @@ type nomsTable struct {
tableStruct types.Struct
}
func (t nomsTable) DebugString(ctx context.Context) string {
var buf bytes.Buffer
buf.WriteString("\tStruct:\n")
types.WriteEncodedValue(ctx, &buf, t.tableStruct)
buf.WriteString("\n\tRows:\n")
data, err := t.GetTableRows(ctx)
if err != nil {
panic(err)
}
err = types.WriteEncodedValue(ctx, &buf, NomsMapFromIndex(data))
if err != nil {
panic(err)
}
return buf.String()
}
var _ Table = nomsTable{}
var sharePool = pool.NewBuffPool()
@@ -570,6 +594,10 @@ type doltDevTable struct {
msg *serial.Table
}
func (t doltDevTable) DebugString(ctx context.Context) string {
return "doltDevTable has no DebugString" // TODO: fill in
}
var _ Table = doltDevTable{}
type serialTableFields struct {

View File

@@ -1317,20 +1317,10 @@ func (root *RootValue) DebugString(ctx context.Context, transitive bool) string
if transitive {
buf.WriteString("\nTables:")
root.IterTables(ctx, func(name string, table *Table, sch schema.Schema) (stop bool, err error) {
buf.WriteString("\nName:")
buf.WriteString("\nTable ")
buf.WriteString(name)
buf.WriteString("\n")
buf.WriteString("Data:\n")
data, err := table.GetNomsRowData(ctx)
if err != nil {
panic(err)
}
err = types.WriteEncodedValue(ctx, &buf, data)
if err != nil {
panic(err)
}
buf.WriteString(": \n")
buf.WriteString(table.table.DebugString(ctx))
return false, nil
})
}

View File

@@ -287,6 +287,8 @@ func (db Database) GetTableInsensitiveWithRoot(ctx *sql.Context, root *doltdb.Ro
sess := dsess.DSessFromSess(ctx.Session)
// NOTE: system tables are not suitable for caching
// TODO: these tables that cache a root value at construction time should not, they need to get it from the session
// at runtime
switch {
case strings.HasPrefix(lwrName, doltdb.DoltDiffTablePrefix):
suffix := tblName[len(doltdb.DoltDiffTablePrefix):]
@@ -350,7 +352,7 @@ func (db Database) GetTableInsensitiveWithRoot(ctx *sql.Context, root *doltdb.Ro
}
dt, found = dtables.NewUnscopedDiffTable(ctx, db.ddb, head), true
case doltdb.TableOfTablesInConflictName:
dt, found = dtables.NewTableOfTablesInConflict(ctx, db.ddb, root), true
dt, found = dtables.NewTableOfTablesInConflict(ctx, db.name, db.ddb), true
case doltdb.TableOfTablesWithViolationsName:
dt, found = dtables.NewTableOfTablesConstraintViolations(ctx, root), true
case doltdb.BranchesTableName:

View File

@@ -422,7 +422,6 @@ func checkForUncommittedChanges(ctx *sql.Context, root *doltdb.RootValue, headRo
}
if rh != hrh {
fmt.Printf("root: %s\nheadRoot: %s\n", root.DebugString(ctx, true), headRoot.DebugString(ctx, true))
return ErrUncommittedChanges.New()
}
return nil

View File

@@ -211,7 +211,14 @@ func (sess *Session) StartTransaction(ctx *sql.Context, dbName string, tCharacte
// SetWorkingSet always sets the dirty bit, but by definition we are clean at transaction start
sessionState.dirty = false
return NewDoltTransaction(ws, wsRef, sessionState.dbData, sessionState.WriteSession.GetOptions(), tCharacteristic), nil
return NewDoltTransaction(
dbName,
ws,
wsRef,
sessionState.dbData,
sessionState.WriteSession.GetOptions(),
tCharacteristic,
), nil
}
func (sess *Session) newWorkingSetForHead(ctx *sql.Context, wsRef ref.WorkingSetRef, dbName string) (*doltdb.WorkingSet, error) {
@@ -321,10 +328,20 @@ func (sess *Session) DoltCommit(
commit *doltdb.PendingCommit,
) (*doltdb.Commit, error) {
commitFunc := func(ctx *sql.Context, dtx *DoltTransaction, workingSet *doltdb.WorkingSet) (*doltdb.WorkingSet, *doltdb.Commit, error) {
return dtx.DoltCommit(
ws, commit, err := dtx.DoltCommit(
ctx,
workingSet.WithWorkingRoot(commit.Roots.Working).WithStagedRoot(commit.Roots.Staged),
commit)
if err != nil {
return nil, nil, err
}
// Unlike normal COMMIT statements, CALL DOLT_COMMIT() doesn't get the current transaction cleared out by the query
// engine, so we do it here.
// TODO: the engine needs to manage this
ctx.SetTransaction(nil)
return ws, commit, err
}
return sess.doCommit(ctx, dbName, tx, commitFunc)
@@ -406,7 +423,7 @@ func (sess *Session) NewPendingCommit(ctx *sql.Context, dbName string, roots dol
// RollbackTransaction rolls the given transaction back
func (sess *Session) RollbackTransaction(ctx *sql.Context, dbName string, tx sql.Transaction) error {
if !TransactionsDisabled(ctx) || dbName == "" {
if TransactionsDisabled(ctx) || dbName == "" {
return nil
}
@@ -424,6 +441,9 @@ func (sess *Session) RollbackTransaction(ctx *sql.Context, dbName string, tx sql
return fmt.Errorf("expected a DoltTransaction")
}
// This operation usually doesn't matter, because the engine will process a `rollback` statement by first calling
// this logic, then discarding any current transaction. So the next statement will get a fresh transaction regardless,
// and this is throwaway work. It only matters if this method is used outside a standalone `rollback` statement.
err = sess.SetRoot(ctx, dbName, dtx.startState.WorkingRoot())
if err != nil {
return err
@@ -717,7 +737,14 @@ func (sess *Session) SwitchWorkingSet(
tCharacteristic = sql.ReadOnly
}
}
ctx.SetTransaction(NewDoltTransaction(ws, wsRef, sessionState.dbData, sessionState.WriteSession.GetOptions(), tCharacteristic))
ctx.SetTransaction(NewDoltTransaction(
dbName,
ws,
wsRef,
sessionState.dbData,
sessionState.WriteSession.GetOptions(),
tCharacteristic,
))
return nil
}

View File

@@ -15,6 +15,7 @@
package dsess
import (
"errors"
"fmt"
"strings"
"sync"
@@ -36,6 +37,8 @@ const (
maxTxCommitRetries = 5
)
var ErrRetryTransaction = errors.New("this transaction conflicts with a committed transaction from another client, please retry")
func TransactionsDisabled(ctx *sql.Context) bool {
enabled, err := ctx.GetSessionVariable(ctx, TransactionsDisabledSysVar)
if err != nil {
@@ -64,6 +67,7 @@ func (d DisabledTransaction) IsReadOnly() bool {
}
type DoltTransaction struct {
sourceDbName string
startState *doltdb.WorkingSet
workingSetRef ref.WorkingSetRef
dbData env.DbData
@@ -78,6 +82,7 @@ type savepoint struct {
}
func NewDoltTransaction(
dbName string,
startState *doltdb.WorkingSet,
workingSet ref.WorkingSetRef,
dbData env.DbData,
@@ -85,6 +90,7 @@ func NewDoltTransaction(
tCharacteristic sql.TransactionCharacteristic,
) *DoltTransaction {
return &DoltTransaction{
sourceDbName: dbName,
startState: startState,
workingSetRef: workingSet,
dbData: dbData,
@@ -164,10 +170,6 @@ func (tx *DoltTransaction) doCommit(
commit *doltdb.PendingCommit,
writeFn transactionWrite,
) (*doltdb.WorkingSet, *doltdb.Commit, error) {
err := checkForConflictsAndConstraintViolations(ctx, workingSet)
if err != nil {
return nil, nil, err
}
for i := 0; i < maxTxCommitRetries; i++ {
updatedWs, newCommit, err := func() (*doltdb.WorkingSet, *doltdb.Commit, error) {
@@ -177,24 +179,28 @@ func (tx *DoltTransaction) doCommit(
newWorkingSet := false
ws, err := tx.dbData.Ddb.ResolveWorkingSet(ctx, tx.workingSetRef)
existingWs, err := tx.dbData.Ddb.ResolveWorkingSet(ctx, tx.workingSetRef)
if err == doltdb.ErrWorkingSetNotFound {
// This is to handle the case where an existing DB pre working sets is committing to this HEAD for the
// first time. Can be removed and called an error post 1.0
ws = doltdb.EmptyWorkingSet(tx.workingSetRef)
existingWs = doltdb.EmptyWorkingSet(tx.workingSetRef)
newWorkingSet = true
} else if err != nil {
return nil, nil, err
}
wsHash, err := ws.HashOf()
wsHash, err := existingWs.HashOf()
if err != nil {
return nil, nil, err
}
existingWorkingRoot := ws.WorkingRoot()
if newWorkingSet || rootsEqual(existingWorkingRoot, tx.startState.WorkingRoot()) {
if newWorkingSet || rootsEqual(existingWs.WorkingRoot(), tx.startState.WorkingRoot()) {
// ff merge
err = tx.validateWorkingSetForCommit(ctx, workingSet, isFfMerge)
if err != nil {
return nil, nil, err
}
var newCommit *doltdb.Commit
workingSet, newCommit, err = writeFn(ctx, tx, commit, workingSet, wsHash)
if err == datas.ErrOptimisticLockFailed {
@@ -207,34 +213,24 @@ func (tx *DoltTransaction) doCommit(
return workingSet, newCommit, nil
}
// otherwise (not a ff), merge the working sets together
start := time.Now()
mergedRoot, stats, err := merge.MergeRoots(ctx, existingWorkingRoot, workingSet.WorkingRoot(), tx.startState.WorkingRoot(), tx.mergeEditOpts)
// TODO: this loses track of merge conflicts in the working set, clearing them out and replacing them with any
// new merge conflicts produced by this merge operation. We want to preserve merge conflicts in the working set
// given and permit them to be committed as long as a) no new ones are introduced, and b) any merge conflicts in
// the shared working set match the merge conflicts in this one. Longer term, we will implement transaction
// commit without a merge, making this point moot.
mergedWorkingSet, err := tx.mergeRoots(ctx, existingWs, workingSet)
if err != nil {
return nil, nil, err
}
logrus.Tracef("merge took %s", time.Since(start))
var tablesWithConflicts []string
for table, mergeStats := range stats {
if mergeStats.Conflicts > 0 {
if transactionMergeStomp {
tablesWithConflicts = append(tablesWithConflicts, table)
} else {
// TODO: surface duplicate key errors as appropriate
return nil, nil, fmt.Errorf("conflict in table %s", table)
}
}
err = tx.validateWorkingSetForCommit(ctx, mergedWorkingSet, notFfMerge)
if err != nil {
return nil, nil, err
}
// Only resolve conflicts automatically if the stomp environment key is set
if len(tablesWithConflicts) > 0 {
mergedRoot, err = tx.stompConflicts(ctx, mergedRoot, tablesWithConflicts)
if err != nil {
return nil, nil, err
}
}
mergedWorkingSet := workingSet.WithWorkingRoot(mergedRoot)
var newCommit *doltdb.Commit
mergedWorkingSet, newCommit, err = writeFn(ctx, tx, commit, mergedWorkingSet, wsHash)
if err == datas.ErrOptimisticLockFailed {
@@ -258,9 +254,73 @@ func (tx *DoltTransaction) doCommit(
return nil, nil, datas.ErrOptimisticLockFailed
}
// checkForConflictsAndConstraintViolations determines which conflicts and constraint violations are ok to commit
// given the state of certain system variables.
func checkForConflictsAndConstraintViolations(ctx *sql.Context, workingSet *doltdb.WorkingSet) error {
// mergeRoots merges the roots in the existing working set with the one being committed and returns the resulting
// working set. Conflicts are automatically resolved with "accept ours" if the session settings dictate it.
func (tx *DoltTransaction) mergeRoots(
ctx *sql.Context,
existingWorkingRoot *doltdb.WorkingSet,
workingSet *doltdb.WorkingSet,
) (*doltdb.WorkingSet, error) {
mergedRoot, mergeStats, err := merge.MergeRoots(
ctx,
existingWorkingRoot.WorkingRoot(),
workingSet.WorkingRoot(),
tx.startState.WorkingRoot(),
tx.mergeEditOpts,
)
if err != nil {
return nil, err
}
// If the conflict stomp env variable is set, resolve conflicts automatically (using the "accept ours" strategy)
if transactionMergeStomp {
var tablesWithConflicts []string
for table, stat := range mergeStats {
if stat.Conflicts > 0 {
tablesWithConflicts = append(tablesWithConflicts, table)
}
}
if len(tablesWithConflicts) > 0 {
mergedRoot, err = tx.stompConflicts(ctx, mergedRoot, tablesWithConflicts)
if err != nil {
return nil, err
}
}
}
return workingSet.WithWorkingRoot(mergedRoot), nil
}
// rollback attempts a transaction rollback
func (tx *DoltTransaction) rollback(ctx *sql.Context) error {
sess := DSessFromSess(ctx.Session)
rollbackErr := sess.RollbackTransaction(ctx, tx.sourceDbName, tx)
if rollbackErr != nil {
return rollbackErr
}
// We also need to cancel out the transaction here so that a new one will begin on the next statement
// TODO: it would be better for the engine to handle these details probably, this code is duplicated from the
// rollback statement implementation in the engine.
ctx.SetTransaction(nil)
ctx.SetIgnoreAutoCommit(false)
return nil
}
type ffMerge bool
const (
isFfMerge = ffMerge(true)
notFfMerge = ffMerge(false)
)
// validateWorkingSetForCommit validates that the working set given is legal to commit according to the session
// settings. Returns an error if the given working set has conflicts or constraint violations and the session settings
// do not allow them.
func (tx *DoltTransaction) validateWorkingSetForCommit(ctx *sql.Context, workingSet *doltdb.WorkingSet, isFf ffMerge) error {
forceTransactionCommit, err := ctx.GetSessionVariable(ctx, ForceTransactionCommit)
if err != nil {
return err
@@ -272,13 +332,31 @@ func checkForConflictsAndConstraintViolations(ctx *sql.Context, workingSet *dolt
}
workingRoot := workingSet.WorkingRoot()
hasConflicts, err := workingRoot.HasConflicts(ctx)
if err != nil {
return err
}
if !(allowCommitConflicts.(int8) == 1 || forceTransactionCommit.(int8) == 1) {
hasConflicts, err := workingRoot.HasConflicts(ctx)
if err != nil {
return err
if hasConflicts {
// Conflicts are never acceptable when they resulted from a merge with the existing working set -- it's equivalent
// to hitting a write lock (which we didn't take). Always roll back and return an error in this case.
if !isFf {
rollbackErr := tx.rollback(ctx)
if rollbackErr != nil {
return rollbackErr
}
return ErrRetryTransaction
}
if hasConflicts {
// If there were conflicts before merge with the persisted working set, whether we allow it to be committed is a
// session setting
if !(allowCommitConflicts.(int8) == 1 || forceTransactionCommit.(int8) == 1) {
rollbackErr := tx.rollback(ctx)
if rollbackErr != nil {
return rollbackErr
}
return doltdb.ErrUnresolvedConflicts
}
}

View File

@@ -75,7 +75,7 @@ func init() {
Dynamic: true,
SetVarHintApplies: false,
Type: sql.NewSystemBoolType(AllowCommitConflicts),
Default: int8(1),
Default: int8(0),
},
})
}

View File

@@ -21,19 +21,20 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/conflict"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
)
var _ sql.Table = (*TableOfTablesInConflict)(nil)
// TableOfTablesInConflict is a sql.Table implementation that implements a system table which shows the current conflicts
type TableOfTablesInConflict struct {
ddb *doltdb.DoltDB
root *doltdb.RootValue
dbName string
ddb *doltdb.DoltDB
}
// NewTableOfTablesInConflict creates a TableOfTablesInConflict
func NewTableOfTablesInConflict(_ *sql.Context, ddb *doltdb.DoltDB, root *doltdb.RootValue) sql.Table {
return &TableOfTablesInConflict{ddb: ddb, root: root}
func NewTableOfTablesInConflict(_ *sql.Context, dbName string, ddb *doltdb.DoltDB) sql.Table {
return &TableOfTablesInConflict{dbName: dbName, ddb: ddb}
}
// Name is a sql.Table interface function which returns the name of the table which is defined by the constant
@@ -61,7 +62,6 @@ type tableInConflict struct {
size uint64
done bool
schemas conflict.ConflictSchema
//cnfItr types.MapIterator
}
// Key returns a unique key for the partition
@@ -111,15 +111,21 @@ func (p *tablesInConflict) Close(*sql.Context) error {
// Partitions is a sql.Table interface function that returns a partition of the data. Conflict data is partitioned by table.
func (dt *TableOfTablesInConflict) Partitions(ctx *sql.Context) (sql.PartitionIter, error) {
tblNames, err := dt.root.TablesInConflict(ctx)
sess := dsess.DSessFromSess(ctx.Session)
ws, err := sess.WorkingSet(ctx, dt.dbName)
if err != nil {
return nil, err
}
root := ws.WorkingRoot()
tblNames, err := root.TablesInConflict(ctx)
if err != nil {
return nil, err
}
var partitions []*tableInConflict
for _, tblName := range tblNames {
tbl, ok, err := dt.root.GetTable(ctx, tblName)
tbl, ok, err := root.GetTable(ctx, tblName)
if err != nil {
return nil, err

View File

@@ -552,6 +552,7 @@ func TestStoredProcedures(t *testing.T) {
func TestTransactions(t *testing.T) {
skipNewFormat(t)
enginetest.TestTransactionScripts(t, newDoltHarness(t))
for _, script := range DoltTransactionTests {
enginetest.TestTransactionScript(t, newDoltHarness(t), script)
}
@@ -559,6 +560,10 @@ func TestTransactions(t *testing.T) {
for _, script := range DoltSqlFuncTransactionTests {
enginetest.TestTransactionScript(t, newDoltHarness(t), script)
}
for _, script := range DoltConflictHandlingTests {
enginetest.TestTransactionScript(t, newDoltHarness(t), script)
}
}
func TestConcurrentTransactions(t *testing.T) {
@@ -607,79 +612,90 @@ func TestSingleTransactionScript(t *testing.T) {
t.Skip()
script := enginetest.TransactionTest{
Name: "rollback",
Name: "allow commit conflicts on, conflict on dolt_merge",
SetUpScript: []string{
"create table t (x int primary key, y int)",
"insert into t values (1, 1)",
"CREATE TABLE test (pk int primary key, val int)",
"INSERT INTO test VALUES (0, 0)",
"SELECT DOLT_COMMIT('-a', '-m', 'initial table');",
},
Assertions: []enginetest.ScriptTestAssertion{
{
Query: "/* client a */ set autocommit = off",
Expected: []sql.Row{{}},
},
{
Query: "/* client b */ set autocommit = off",
Query: "/* client a */ set autocommit = off, dolt_allow_commit_conflicts = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client a */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client b */ set autocommit = off, dolt_allow_commit_conflicts = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client b */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client a */ insert into t values (2, 2)",
Query: "/* client a */ insert into test values (1, 1)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ insert into t values (3, 3)",
Query: "/* client b */ call dolt_checkout('-b', 'new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client a */ call dolt_commit('-am', 'commit on main')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ insert into test values (1, 2)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client a */ select * from t order by x",
Expected: []sql.Row{{1, 1}, {2, 2}},
Query: "/* client b */ call dolt_commit('-am', 'commit on new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ call dolt_merge('main')",
Expected: []sql.Row{{0}},
},
{
Query: "/* client b */ select count(*) from dolt_conflicts",
Expected: []sql.Row{{1}},
},
{
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 2}},
},
{ // no error because of our session settings
Query: "/* client b */ commit",
Expected: []sql.Row{},
},
{ // TODO: it should be possible to do this without specifying a literal in the subselect, but it's not working
Query: "/* client b */ update test t set val = (select their_val from dolt_conflicts_test where our_pk = 1) where pk = 1",
Expected: []sql.Row{{sql.OkResult{
RowsAffected: 1,
Info: plan.UpdateInfo{
Matched: 1,
Updated: 1,
},
}}},
},
{
Query: "/* client b */ delete from dolt_conflicts_test",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client a */ select * from t order by x",
Expected: []sql.Row{{1, 1}, {2, 2}},
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 1}},
},
{
Query: "/* client a */ rollback",
Expected: []sql.Row{},
},
{
Query: "/* client a */ select * from t order by x",
Expected: []sql.Row{{1, 1}, {3, 3}},
},
{
Query: "/* client a */ insert into t values (2, 2)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ select * from t order by x",
Expected: []sql.Row{{1, 1}, {3, 3}},
},
{
Query: "/* client a */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client b */ select * from t order by x",
Expected: []sql.Row{{1, 1}, {3, 3}},
},
{
Query: "/* client b */ rollback",
Expected: []sql.Row{},
},
{
Query: "/* client b */ select * from t order by x",
Expected: []sql.Row{{1, 1}, {2, 2}, {3, 3}},
Query: "/* client b */ select count(*) from dolt_conflicts",
Expected: []sql.Row{{0}},
},
},
}

View File

@@ -844,7 +844,7 @@ var DoltMerge = []enginetest.ScriptTest{
},
},
{
Name: "DOLT_MERGE with conflict is queryable and commitable until dolt_allow_commit_conflicts is turned off",
Name: "DOLT_MERGE with conflict is queryable and committable with dolt_allow_commit_conflicts on",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key, val int)",
"INSERT INTO test VALUES (0, 0)",
@@ -856,6 +856,7 @@ var DoltMerge = []enginetest.ScriptTest{
"SELECT DOLT_CHECKOUT('main');",
"UPDATE test SET val=1001 WHERE pk=0;",
"SELECT DOLT_COMMIT('-a', '-m', 'update a value');",
"set dolt_allow_commit_conflicts = on",
},
Assertions: []enginetest.ScriptTestAssertion{
{
@@ -891,8 +892,8 @@ var DoltMerge = []enginetest.ScriptTest{
ExpectedErrStr: doltdb.ErrUnresolvedConflicts.Error(),
},
{
Query: "SELECT count(*) from dolt_conflicts_test", // Commit allows queries when flags are set.
ExpectedErrStr: doltdb.ErrUnresolvedConflicts.Error(),
Query: "SELECT count(*) from dolt_conflicts_test", // transaction has been rolled back, 0 results
Expected: []sql.Row{{0}},
},
},
},

View File

@@ -19,6 +19,8 @@ import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
)
@@ -110,17 +112,15 @@ var DoltTransactionTests = []enginetest.TransactionTest{
},
{
Query: "/* client b */ commit",
ExpectedErrStr: "conflict in table t",
ExpectedErrStr: dsess.ErrRetryTransaction.Error(),
},
{
Query: "/* client a */ select * from t order by x",
Expected: []sql.Row{{1, 1}, {2, 2}},
},
// TODO: behavior right now is to leave the session state dirty on an unsuccessful commit, letting the
// client choose whether to rollback or not. Not clear if this is the right behavior for a failed commit.
{
{ // client b gets a rollback after failed commit, so gets a new tx
Query: "/* client b */ select * from t order by x",
Expected: []sql.Row{{1, 1}, {2, 3}},
Expected: []sql.Row{{1, 1}, {2, 2}},
},
{
Query: "/* client b */ rollback",
@@ -263,15 +263,15 @@ var DoltTransactionTests = []enginetest.TransactionTest{
},
{
Query: "/* client b */ commit",
ExpectedErrStr: "conflict in table t",
ExpectedErrStr: dsess.ErrRetryTransaction.Error(),
},
{
Query: "/* client a */ select * from t order by x",
Expected: []sql.Row{{1, 3}, {2, 3}},
},
{
{ // client b got rolled back when its commit failed, so it sees the same values as client a
Query: "/* client b */ select * from t order by x",
Expected: []sql.Row{{1, 4}, {2, 4}},
Expected: []sql.Row{{1, 3}, {2, 3}},
},
{
Query: "/* client b */ rollback",
@@ -283,73 +283,6 @@ var DoltTransactionTests = []enginetest.TransactionTest{
},
},
},
{
Name: "conflicting updates, resolved with more updates",
SetUpScript: []string{
"create table t (x int primary key, y int)",
"insert into t values (1, 1), (2, 2)",
},
Assertions: []enginetest.ScriptTestAssertion{
{
Query: "/* client a */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client b */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client a */ update t set y = 3",
Expected: []sql.Row{{sql.OkResult{
RowsAffected: uint64(2),
Info: plan.UpdateInfo{
Matched: 2,
Updated: 2,
},
}}},
},
{
Query: "/* client b */ update t set y = 4",
Expected: []sql.Row{{sql.OkResult{
RowsAffected: uint64(2),
Info: plan.UpdateInfo{
Matched: 2,
Updated: 2,
},
}}},
},
{
Query: "/* client a */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client b */ commit",
ExpectedErrStr: "conflict in table t",
},
{
Query: "/* client b */ update t set y = 3",
Expected: []sql.Row{{sql.OkResult{
RowsAffected: uint64(2),
Info: plan.UpdateInfo{
Matched: 2,
Updated: 2,
},
}}},
},
{
Query: "/* client b */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client a */ select * from t order by x",
Expected: []sql.Row{{1, 3}, {2, 3}},
},
{
Query: "/* client b */ select * from t order by x",
Expected: []sql.Row{{1, 3}, {2, 3}},
},
},
},
{
Name: "non overlapping updates (diff rows)",
SetUpScript: []string{
@@ -598,7 +531,7 @@ var DoltTransactionTests = []enginetest.TransactionTest{
},
{
Query: "/* client b */ commit",
ExpectedErrStr: "conflict in table t",
ExpectedErrStr: dsess.ErrRetryTransaction.Error(),
},
{
Query: "/* client b */ rollback",
@@ -741,7 +674,7 @@ var DoltTransactionTests = []enginetest.TransactionTest{
},
},
{
Name: "Edits from different clients to table with out of order primary key set",
Name: "edits from different clients to table with out of order primary key set",
SetUpScript: []string{
"create table test (x int, y int, z int, primary key(z, y))",
"insert into test values (1, 1, 1), (2, 2, 2)",
@@ -795,9 +728,402 @@ var DoltTransactionTests = []enginetest.TransactionTest{
},
}
var DoltConflictHandlingTests = []enginetest.TransactionTest{
{
Name: "default behavior (rollback on commit conflict)",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key, val int)",
"INSERT INTO test VALUES (0, 0)",
"SELECT DOLT_COMMIT('-a', '-m', 'initial table');",
},
Assertions: []enginetest.ScriptTestAssertion{
{
Query: "/* client a */ set autocommit = off",
Expected: []sql.Row{{}},
},
{
Query: "/* client a */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client b */ set autocommit = off",
Expected: []sql.Row{{}},
},
{
Query: "/* client b */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client a */ insert into test values (1, 1)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ insert into test values (1, 2)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client a */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client b */ commit",
ExpectedErrStr: dsess.ErrRetryTransaction.Error(),
},
{ // no conflicts, transaction got rolled back
Query: "/* client b */ select count(*) from dolt_conflicts",
Expected: []sql.Row{{0}},
},
{
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 1}},
},
},
},
{
Name: "allow commit conflicts on, conflict on transaction commit",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key, val int)",
"INSERT INTO test VALUES (0, 0)",
"SELECT DOLT_COMMIT('-a', '-m', 'initial table');",
},
Assertions: []enginetest.ScriptTestAssertion{
{
Query: "/* client a */ set autocommit = off, dolt_allow_commit_conflicts = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client a */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client b */ set autocommit = off, dolt_allow_commit_conflicts = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client b */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client a */ insert into test values (1, 1)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ insert into test values (1, 2)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client a */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client b */ commit",
ExpectedErrStr: dsess.ErrRetryTransaction.Error(),
},
{ // We see the merge value from a's commit here because we were rolled back and a new transaction begun
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 1}},
},
},
},
{
Name: "force commit on, conflict on transaction commit (same as dolt_allow_commit_conflicts)",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key, val int)",
"INSERT INTO test VALUES (0, 0)",
"SELECT DOLT_COMMIT('-a', '-m', 'initial table');",
},
Assertions: []enginetest.ScriptTestAssertion{
{
Query: "/* client a */ set autocommit = off, dolt_force_transaction_commit = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client a */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client b */ set autocommit = off, dolt_force_transaction_commit = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client b */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client a */ insert into test values (1, 1)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ insert into test values (1, 2)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client a */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client b */ commit",
ExpectedErrStr: dsess.ErrRetryTransaction.Error(),
},
{ // We see the merge value from a's commit here because we were rolled back and a new transaction begun
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 1}},
},
},
},
{
Name: "allow commit conflicts on, conflict on dolt_merge",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key, val int)",
"INSERT INTO test VALUES (0, 0)",
"SELECT DOLT_COMMIT('-a', '-m', 'initial table');",
},
Assertions: []enginetest.ScriptTestAssertion{
{
Query: "/* client a */ set autocommit = off, dolt_allow_commit_conflicts = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client a */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client b */ set autocommit = off, dolt_allow_commit_conflicts = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client b */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client a */ insert into test values (1, 1)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ call dolt_checkout('-b', 'new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client a */ call dolt_commit('-am', 'commit on main')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ insert into test values (1, 2)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ call dolt_commit('-am', 'commit on new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ call dolt_merge('main')",
Expected: []sql.Row{{0}},
},
{
Query: "/* client b */ select count(*) from dolt_conflicts",
Expected: []sql.Row{{1}},
},
{
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 2}},
},
{ // no error because of our session settings
// TODO: we should also be able to commit this if the other client made a compatible change
// (has the same merge conflicts we do), but that's an error right now
Query: "/* client b */ commit",
Expected: []sql.Row{},
},
{ // TODO: it should be possible to do this without specifying a literal in the subselect, but it's not working
Query: "/* client b */ update test t set val = (select their_val from dolt_conflicts_test where our_pk = 1) where pk = 1",
Expected: []sql.Row{{sql.OkResult{
RowsAffected: 1,
Info: plan.UpdateInfo{
Matched: 1,
Updated: 1,
},
}}},
},
{
Query: "/* client b */ delete from dolt_conflicts_test",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 1}},
},
{
Query: "/* client b */ select count(*) from dolt_conflicts",
Expected: []sql.Row{{0}},
},
},
},
{
Name: "force commit on, conflict on dolt_merge (same as dolt_allow_commit_conflicts)",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key, val int)",
"INSERT INTO test VALUES (0, 0)",
"SELECT DOLT_COMMIT('-a', '-m', 'initial table');",
},
Assertions: []enginetest.ScriptTestAssertion{
{
Query: "/* client a */ set autocommit = off, dolt_force_transaction_commit = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client a */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client b */ set autocommit = off, dolt_force_transaction_commit = on",
Expected: []sql.Row{{}},
},
{
Query: "/* client b */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client a */ insert into test values (1, 1)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ call dolt_checkout('-b', 'new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client a */ call dolt_commit('-am', 'commit on main')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ insert into test values (1, 2)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ call dolt_commit('-am', 'commit on new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ call dolt_merge('main')",
Expected: []sql.Row{{0}},
},
{
Query: "/* client b */ select count(*) from dolt_conflicts",
Expected: []sql.Row{{1}},
},
{
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 2}},
},
{ // no error because of our session settings
Query: "/* client b */ commit",
Expected: []sql.Row{},
},
{ // TODO: it should be possible to do this without specifying a literal in the subselect, but it's not working
Query: "/* client b */ update test t set val = (select their_val from dolt_conflicts_test where our_pk = 1) where pk = 1",
Expected: []sql.Row{{sql.OkResult{
RowsAffected: 1,
Info: plan.UpdateInfo{
Matched: 1,
Updated: 1,
},
}}},
},
{
Query: "/* client b */ delete from dolt_conflicts_test",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 1}},
},
{
Query: "/* client b */ select count(*) from dolt_conflicts",
Expected: []sql.Row{{0}},
},
},
},
{
Name: "allow commit conflicts off, conflict on dolt_merge",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key, val int)",
"INSERT INTO test VALUES (0, 0)",
"SELECT DOLT_COMMIT('-a', '-m', 'initial table');",
},
Assertions: []enginetest.ScriptTestAssertion{
{
Query: "/* client a */ set autocommit = off",
Expected: []sql.Row{{}},
},
{
Query: "/* client a */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client b */ set autocommit = off",
Expected: []sql.Row{{}},
},
{
Query: "/* client b */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client a */ insert into test values (1, 1)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ call dolt_checkout('-b', 'new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client a */ call dolt_commit('-am', 'commit on main')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ insert into test values (1, 2)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ call dolt_commit('-am', 'commit on new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ call dolt_merge('main')",
Expected: []sql.Row{{0}},
},
{
Query: "/* client b */ select count(*) from dolt_conflicts",
Expected: []sql.Row{{1}},
},
{
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 2}},
},
{
Query: "/* client b */ insert into test values (2, 2)",
Expected: []sql.Row{{sql.NewOkResult(1)}},
},
{
Query: "/* client b */ commit",
ExpectedErrStr: doltdb.ErrUnresolvedConflicts.Error(),
},
{ // our transaction got rolled back, so we lose the above insert
Query: "/* client b */ select * from test order by 1",
Expected: []sql.Row{{0, 0}, {1, 2}},
},
},
},
}
var DoltSqlFuncTransactionTests = []enginetest.TransactionTest{
{
Name: "Committed conflicts are seen by other sessions",
Name: "committed conflicts are seen by other sessions",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key, val int)",
"INSERT INTO test VALUES (0, 0)",
@@ -831,6 +1157,10 @@ var DoltSqlFuncTransactionTests = []enginetest.TransactionTest{
Query: "/* client b */ SELECT count(*) from dolt_conflicts_test",
Expected: []sql.Row{{0}},
},
{
Query: "/* client a */ set dolt_allow_commit_conflicts = 1",
Expected: []sql.Row{{}},
},
{
Query: "/* client a */ commit",
Expected: []sql.Row{},
@@ -867,13 +1197,13 @@ var DoltSqlFuncTransactionTests = []enginetest.TransactionTest{
Query: "/* client a */ SELECT DOLT_MERGE('feature-branch')",
ExpectedErrStr: doltdb.ErrUnresolvedConflicts.Error(),
},
{
Query: "/* client a */ SELECT count(*) from dolt_conflicts_test",
ExpectedErrStr: doltdb.ErrUnresolvedConflicts.Error(),
{ // client rolled back on merge with conflicts
Query: "/* client a */ SELECT count(*) from dolt_conflicts_test",
Expected: []sql.Row{{0}},
},
{
Query: "/* client a */ commit",
ExpectedErrStr: doltdb.ErrUnresolvedConflicts.Error(),
Query: "/* client a */ commit",
Expected: []sql.Row{},
},
{
Query: "/* client b */ SELECT count(*) from dolt_conflicts_test",

View File

@@ -63,8 +63,9 @@ func ProceduresTableSchema() schema.Schema {
return schema.MustSchemaFromCols(colColl)
}
// DoltProceduresGetTable returns the `dolt_procedures` table from the given db, creating it if it does not already exist.
func DoltProceduresGetTable(ctx *sql.Context, db Database) (*WritableDoltTable, error) {
// DoltProceduresGetOrCreateTable returns the `dolt_procedures` table from the given db, creating it in the db's
// current root if it doesn't exist
func DoltProceduresGetOrCreateTable(ctx *sql.Context, db Database) (*WritableDoltTable, error) {
root, err := db.GetRoot(ctx)
if err != nil {
return nil, err
@@ -96,10 +97,29 @@ func DoltProceduresGetTable(ctx *sql.Context, db Database) (*WritableDoltTable,
return tbl.(*WritableDoltTable), nil
}
// DoltProceduresGetTable returns the `dolt_procedures` table from the given db, or nil if the table doesn't exist
func DoltProceduresGetTable(ctx *sql.Context, db Database) (*WritableDoltTable, error) {
root, err := db.GetRoot(ctx)
if err != nil {
return nil, err
}
tbl, found, err := db.GetTableInsensitiveWithRoot(ctx, root, doltdb.ProceduresTableName)
if err != nil {
return nil, err
}
if found {
return tbl.(*WritableDoltTable), nil
} else {
return nil, nil
}
}
func DoltProceduresGetAll(ctx *sql.Context, db Database) ([]sql.StoredProcedureDetails, error) {
tbl, err := DoltProceduresGetTable(ctx, db)
if err != nil {
return nil, err
} else if tbl == nil {
return nil, nil
}
indexes, err := tbl.GetIndexes(ctx)
@@ -167,7 +187,7 @@ func DoltProceduresGetAll(ctx *sql.Context, db Database) ([]sql.StoredProcedureD
// DoltProceduresAddProcedure adds the stored procedure to the `dolt_procedures` table in the given db, creating it if
// it does not exist.
func DoltProceduresAddProcedure(ctx *sql.Context, db Database, spd sql.StoredProcedureDetails) (retErr error) {
tbl, err := DoltProceduresGetTable(ctx, db)
tbl, err := DoltProceduresGetOrCreateTable(ctx, db)
if err != nil {
return err
}
@@ -193,13 +213,17 @@ func DoltProceduresAddProcedure(ctx *sql.Context, db Database, spd sql.StoredPro
})
}
// DoltProceduresDropProcedure removes the stored procedure from the `dolt_procedures` table.
// DoltProceduresDropProcedure removes the stored procedure from the `dolt_procedures` table. The procedure named must
// exist.
func DoltProceduresDropProcedure(ctx *sql.Context, db Database, name string) (retErr error) {
strings.ToLower(name)
tbl, err := DoltProceduresGetTable(ctx, db)
if err != nil {
return err
} else if tbl == nil {
return sql.ErrStoredProcedureDoesNotExist.New(name)
}
_, ok, err := DoltProceduresGetDetails(ctx, tbl, name)
if err != nil {
return err

View File

@@ -487,12 +487,12 @@ SQL
dolt checkout main
run dolt sql <<"SQL"
SET dolt_allow_commit_conflicts = 0;
SELECT DOLT_MERGE('other');
SQL
[ "$status" -eq "1" ]
[[ "$output" =~ "conflicts" ]] || false
run dolt sql <<"SQL"
SET dolt_allow_commit_conflicts = 1;
SELECT DOLT_MERGE('other');
SQL
[ "$status" -eq "0" ]

View File

@@ -0,0 +1,255 @@
#!/usr/bin/env bats
setup() {
REPO_NAME="dolt_repo_$$"
mkdir $REPO_NAME
cd $REPO_NAME
dolt init
}
teardown() {
cd ..
rm -rf $REPO_NAME
}
@test "import-mysqldump: database with view" {
run dolt sql <<SQL
DROP TABLE IF EXISTS mytable;
CREATE TABLE mytable (
id bigint NOT NULL,
col2 bigint DEFAULT '999',
col3 datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
LOCK TABLES mytable WRITE;
/*!40000 ALTER TABLE mytable DISABLE KEYS */;
/*!40000 ALTER TABLE mytable ENABLE KEYS */;
UNLOCK TABLES;
--
-- Temporary view structure for view myview
--
DROP TABLE IF EXISTS myview;
/*!50001 DROP VIEW IF EXISTS myview*/;
/*!50001 CREATE VIEW myview AS SELECT
1 AS id,
1 AS col2,
1 AS col3*/;
--
-- Final view structure for view myview
--
/*!50001 DROP VIEW IF EXISTS myview*/;
/*!50001 CREATE ALGORITHM=UNDEFINED */
/*!50013 DEFINER=\`root\`@\`localhost\` SQL SECURITY DEFINER */
/*!50001 VIEW \`myview\` AS select \`mytable\`.\`id\` AS \`id\`,\`mytable\`.\`col2\` AS \`col2\`,\`mytable\`.\`col3\` AS \`col3\` from \`mytable\` */;
SQL
[ "$status" -eq 0 ]
dolt sql -q "INSERT INTO mytable (id, col3) VALUES (1, TIMESTAMP('2003-12-31'));"
run dolt sql -q "SELECT * FROM myview;" -r csv
[ "$status" -eq 0 ]
[[ "$output" =~ "1,999,2003-12-31 00:00:00 +0000 UTC" ]] || false
run dolt sql -q "SHOW CREATE VIEW myview;" -r csv
[ "$status" -eq 0 ]
[[ "$output" =~ "CREATE VIEW \`myview\` AS select \`mytable\`.\`id\` AS \`id\`,\`mytable\`.\`col2\` AS \`col2\`,\`mytable\`.\`col3\` AS \`col3\` from \`mytable\`" ]] || false
}
@test "import-mysqldump: database with trigger" {
run dolt sql <<SQL
DROP TABLE IF EXISTS mytable;
CREATE TABLE mytable (
pk bigint NOT NULL,
v1 bigint DEFAULT NULL,
PRIMARY KEY (pk)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
LOCK TABLES mytable WRITE;
/*!40000 ALTER TABLE mytable DISABLE KEYS */;
INSERT INTO mytable VALUES (0,2),(1,3),(2,44);
/*!40000 ALTER TABLE mytable ENABLE KEYS */;
UNLOCK TABLES;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=\`root\`@\`localhost\`*/ /*!50003 TRIGGER tt BEFORE INSERT ON mytable FOR EACH ROW SET NEW.v1 = NEW.v1 * 11 */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
SQL
[ "$status" -eq 0 ]
dolt sql -q "INSERT INTO mytable VALUES (6,8)"
run dolt sql -q "SELECT * FROM mytable" -r csv
[ "$status" -eq 0 ]
[[ "$output" =~ "6,88" ]] || false
run dolt sql -q "SELECT trigger_name, event_object_table, action_statement, definer FROM information_schema.triggers" -r csv
[ "$status" -eq 0 ]
[[ "$output" =~ "tt,mytable,SET NEW.v1 = NEW.v1 * 11,\`root\`@\`localhost\`" ]] || false
}
@test "import-mysqldump: database with procedure dumped with --routines flag" {
run dolt sql <<SQL
/*!50003 DROP PROCEDURE IF EXISTS new_proc */;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
CREATE DEFINER=\`root\`@\`localhost\` PROCEDURE new_proc(x DOUBLE, y DOUBLE)
SELECT x*y ;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
SQL
[ "$status" -eq 0 ]
run dolt sql -q "CALL new_proc(2, 3);" -r csv
[ "$status" -eq 0 ]
[[ "$output" =~ "6" ]] || false
run dolt sql -q "SHOW PROCEDURE STATUS" -r csv
[ "$status" -eq 0 ]
[[ "$output" =~ "new_proc,PROCEDURE,\`root\`@\`localhost\`" ]] || false
}
@test "import-mysqldump: a table with all types with DEFAULT NULL dump" {
run dolt sql <<SQL
CREATE TABLE all_types (
pk int NOT NULL,
v1 binary(1) DEFAULT NULL,
v2 bigint DEFAULT NULL,
v3 bit(1) DEFAULT NULL,
v4 blob,
v5 char(1) DEFAULT NULL,
v6 date DEFAULT NULL,
v7 datetime DEFAULT NULL,
v8 decimal(5,2) DEFAULT NULL,
v9 double DEFAULT NULL,
v10 enum('s','m','l') DEFAULT NULL,
v11 float DEFAULT NULL,
v12 geometry DEFAULT NULL,
v13 int DEFAULT NULL,
v14 json DEFAULT NULL,
v15 linestring DEFAULT NULL,
v16 longblob,
v17 longtext,
v18 mediumblob,
v19 mediumint DEFAULT NULL,
v20 mediumtext,
v21 point DEFAULT NULL,
v22 polygon DEFAULT NULL,
v23 set('one','two') DEFAULT NULL,
v24 smallint DEFAULT NULL,
v25 text,
v26 time(6) DEFAULT NULL,
v27 timestamp NULL DEFAULT NULL,
v28 tinyblob,
v29 tinyint DEFAULT NULL,
v30 tinytext,
v31 varchar(255) DEFAULT NULL,
v32 varbinary(255) DEFAULT NULL,
v33 year DEFAULT NULL,
PRIMARY KEY (pk)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO all_types VALUES (2,0x01,1,0x01,0x616263,'i','2022-02-22','2022-02-22 22:22:22',999.99,1.1,'s',1.1,0x000000000101000000000000000000F03F0000000000000040,1,'{\"a\": 1}',0x0000000001020000000200000000000000000000000000000000000000000000000000F03F0000000000000040,0x616263,'abc',0x616263,1,'abc',0x000000000101000000000000000000F03F0000000000000040,0x00000000010300000001000000050000000000000000000000000000000000000000000000000020400000000000000000000000000000284000000000000022400000000000000000000000000000224000000000000000000000000000000000,'one',1,'abc','11:59:59.000000','2021-01-19 11:14:07',0x616263,1,'abc','varchar value',0x3131313131,2018);
SQL
[ "$status" -eq 0 ]
dolt sql -q "INSERT INTO all_types (pk) VALUES (1);"
run dolt sql -q "SELECT st_aswkt(v15) from all_types;"
[ "$status" -eq 0 ]
[[ "$output" =~ "LINESTRING(0 0,1 2)" ]] || false
[[ "$output" =~ "NULL" ]] || false
}
@test "import-mysqldump: a table with all types with DEFAULT not-null VALUE dump" {
run dolt sql <<SQL
CREATE TABLE types_default (
pk int NOT NULL,
v1 binary(1) DEFAULT '1',
v2 bigint DEFAULT '1',
v3 bit(2) DEFAULT b'10',
v4 blob DEFAULT (_utf8mb4'abc'),
v5 char(1) DEFAULT 'i',
v6 date DEFAULT '2022-02-22',
v7 datetime DEFAULT '2022-02-22 22:22:22',
v8 decimal(5,2) DEFAULT '999.99',
v9 double DEFAULT '1.1',
v10 enum('s','m','l') DEFAULT 's',
v11 float DEFAULT '1.1',
v12 geometry DEFAULT (point(1.3,3)),
v13 int DEFAULT '1',
v14 json DEFAULT (json_object(_utf8mb4'a',1)),
v15 linestring DEFAULT (linestring(point(0,0),point(1,2))),
v16 longblob DEFAULT (_utf8mb4'abc'),
v17 longtext DEFAULT (_utf8mb4'abc'),
v18 mediumblob DEFAULT (_utf8mb4'abc'),
v19 mediumint DEFAULT '1',
v20 mediumtext DEFAULT (_utf8mb4'abc'),
v21 point DEFAULT (point(1,2)),
v22 polygon DEFAULT (polygon(linestring(point(0,0),point(8,0),point(12,9),point(0,9),point(0,0)))),
v23 set('one','two') DEFAULT 'one',
v24 smallint DEFAULT '1',
v25 text DEFAULT (_utf8mb4'abc'),
v26 time(6) DEFAULT '11:59:59.000000',
v27 timestamp NULL DEFAULT '2021-01-19 11:14:07',
v28 tinyblob DEFAULT (_utf8mb4'abc'),
v29 tinyint DEFAULT '1',
v30 tinytext DEFAULT (_utf8mb4'abc'),
v31 varchar(255) DEFAULT 'varchar value',
v32 varbinary(255) DEFAULT '11111',
v33 year DEFAULT '2018',
PRIMARY KEY (pk)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
SQL
[ "$status" -eq 0 ]
dolt sql -q "INSERT INTO types_default (pk) VALUES (1);"
run dolt sql -q "SELECT hex(st_aswkb(v12)) from types_default;"
[ "$status" -eq 0 ]
# should be "000000000101000000CDCCCCCCCCCCF43F0000000000000840"
[[ "$output" =~ "0101000000CDCCCCCCCCCCF43F0000000000000840" ]] || false
}
@test "import-mysqldump: a table with string literal representation in column definition" {
run dolt sql <<SQL
CREATE TABLE mytable (
pk int NOT NULL,
col2 int DEFAULT (date_format(now(),_utf8mb4'%Y')),
col3 varchar(20) NOT NULL DEFAULT 'sometext',
PRIMARY KEY (pk),
CONSTRAINT status CHECK ((col3 like _utf8mb4'%sometext%'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
SQL
[ "$status" -eq 0 ]
dolt sql -q "INSERT INTO mytable VALUES (1, 2003, 'first_sometext');"
run dolt sql -q "SELECT * FROM mytable;" -r csv
[ "$status" -eq 0 ]
[[ "$output" =~ "1,2003,first_sometext" ]] || false
}
@test "import-mysqldump: charset introducer in tables from mysql db" {
run dolt sql <<SQL
CREATE TABLE engine_cost (
engine_name varchar(64) NOT NULL,
device_type int NOT NULL,
cost_name varchar(64) NOT NULL,
cost_value float DEFAULT NULL,
last_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
comment varchar(1024) DEFAULT NULL,
default_value float GENERATED ALWAYS AS ((case cost_name when _utf8mb3'io_block_read_cost' then 1.0 when _utf8mb3'memory_block_read_cost' then 0.25 else NULL end)) VIRTUAL,
PRIMARY KEY (cost_name,engine_name,device_type)
) /*!50100 TABLESPACE mysql */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC;
SQL
[ "$status" -eq 0 ]
skip "generated always as functionality is not supported"
run dolt sql -q "SHOW CREATE TABLE engine_cost"
[ "$status" -eq 0 ]
[[ "$output" =~ "GENERATED ALWAYS AS" ]] || false
}

View File

@@ -42,7 +42,7 @@ teardown() {
}
@test "sql-checkout: CALL DOLT_CHECKOUT just works" {
run dolt sql -q "SELECT DOLT_CHECKOUT('-b', 'feature-branch')"
run dolt sql -q "CALL DOLT_CHECKOUT('-b', 'feature-branch')"
[ $status -eq 0 ]
# dolt sql -q "select dolt_checkout() should not change the branch
@@ -62,6 +62,13 @@ teardown() {
run dolt status
[ $status -eq 0 ]
[[ "$output" =~ "main" ]] || false
# Should also work in a transaction
dolt sql <<SQL
START TRANSACTION;
CALL DOLT_CHECKOUT('-b', 'new-branch');
SQL
}
@test "sql-checkout: DOLT_CHECKOUT -b throws error on branches that already exist" {

View File

@@ -887,8 +887,9 @@ SQL
[[ "${lines[1]}" =~ "nothing to commit, working tree clean" ]] || false
}
@test "sql-merge: DOLT_MERGE can correctly commit unresolved conflicts" {
@test "sql-merge: DOLT_MERGE can commit unresolved conflicts with dolt_allow_commit_conflicts set" {
dolt sql << SQL
set dolt_allow_commit_conflicts = on;
CREATE TABLE one_pk (
pk1 BIGINT NOT NULL,
c1 BIGINT,
@@ -915,8 +916,9 @@ SQL
[[ $output =~ "merge has unresolved conflicts" ]] || false
}
@test "sql-merge: CALL DOLT_MERGE can correctly commit unresolved conflicts" {
@test "sql-merge: CALL DOLT_MERGE can commit unresolved conflicts with dolt_allow_commit_conflicts on" {
dolt sql << SQL
set dolt_allow_commit_conflicts = on;
CREATE TABLE one_pk (
pk1 BIGINT NOT NULL,
c1 BIGINT,