Exemple #1
0
func TestTabletErrorRetriableErrorTypeOverwrite(t *testing.T) {
	sqlErr := sqldb.NewSQLError(mysql.ErrOptionPreventsStatement, "HY000", "read-only")
	tabletErr := NewTabletErrorSQL(vtrpcpb.ErrorCode_INTERNAL_ERROR, sqlErr)
	if tabletErr.ErrorCode != vtrpcpb.ErrorCode_QUERY_NOT_SERVED {
		t.Fatalf("got: %v wanted: QUERY_NOT_SERVED", tabletErr.ErrorCode)
	}

	sqlErr = sqldb.NewSQLError(mysql.ErrDupEntry, "23000", "error")
	tabletErr = NewTabletErrorSQL(vtrpcpb.ErrorCode_INTERNAL_ERROR, sqlErr)
	if tabletErr.ErrorCode != vtrpcpb.ErrorCode_INTEGRITY_ERROR {
		t.Fatalf("got: %v wanted: INTEGRITY_ERROR", tabletErr.ErrorCode)
	}

	sqlErr = sqldb.NewSQLError(mysql.ErrDataTooLong, "22001", "error")
	tabletErr = NewTabletErrorSQL(vtrpcpb.ErrorCode_INTERNAL_ERROR, sqlErr)
	if tabletErr.ErrorCode != vtrpcpb.ErrorCode_BAD_INPUT {
		t.Fatalf("got: %v wanted: BAD_INPUT", tabletErr.ErrorCode)
	}

	sqlErr = sqldb.NewSQLError(mysql.ErrDataOutOfRange, "22003", "error")
	tabletErr = NewTabletErrorSQL(vtrpcpb.ErrorCode_INTERNAL_ERROR, sqlErr)
	if tabletErr.ErrorCode != vtrpcpb.ErrorCode_BAD_INPUT {
		t.Fatalf("got: %v wanted: BAD_INPUT", tabletErr.ErrorCode)
	}
}
Exemple #2
0
func TestQueryExecutorPlanUpsertPk(t *testing.T) {
	db := setUpQueryExecutorTest()
	db.AddQuery("insert into test_table values (1) /* _stream test_table (pk ) (1 ); */", &mproto.QueryResult{})
	want := &mproto.QueryResult{
		Rows: make([][]sqltypes.Value, 0),
	}
	query := "insert into test_table values(1) on duplicate key update val=1"
	ctx := context.Background()
	tsv := newTestTabletServer(ctx, enableRowCache|enableStrict, db)
	qre := newTestQueryExecutor(ctx, tsv, query, 0)
	defer tsv.StopService()
	checkPlanID(t, planbuilder.PLAN_UPSERT_PK, qre.plan.PlanId)
	got, err := qre.Execute()
	if err != nil {
		t.Fatalf("qre.Execute() = %v, want nil", err)
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("got: %v, want: %v", got, want)
	}

	db.AddRejectedQuery("insert into test_table values (1) /* _stream test_table (pk ) (1 ); */", errRejected)
	_, err = qre.Execute()
	wantErr := "error: rejected"
	if err == nil || err.Error() != wantErr {
		t.Fatalf("qre.Execute() = %v, want %v", err, wantErr)
	}

	db.AddRejectedQuery(
		"insert into test_table values (1) /* _stream test_table (pk ) (1 ); */",
		sqldb.NewSQLError(mysql.ErrDupEntry, "err"),
	)
	db.AddQuery("update test_table set val = 1 where pk in (1) /* _stream test_table (pk ) (1 ); */", &mproto.QueryResult{})
	_, err = qre.Execute()
	wantErr = "error: err (errno 1062)"
	if err == nil || err.Error() != wantErr {
		t.Fatalf("qre.Execute() = %v, want %v", err, wantErr)
	}

	db.AddRejectedQuery(
		"insert into test_table values (1) /* _stream test_table (pk ) (1 ); */",
		sqldb.NewSQLError(mysql.ErrDupEntry, "ERROR 1062 (23000): Duplicate entry '2' for key 'PRIMARY'"),
	)
	db.AddQuery(
		"update test_table set val = 1 where pk in (1) /* _stream test_table (pk ) (1 ); */",
		&mproto.QueryResult{RowsAffected: 1},
	)
	got, err = qre.Execute()
	if err != nil {
		t.Fatalf("qre.Execute() = %v, want nil", err)
	}
	want = &mproto.QueryResult{
		RowsAffected: 2,
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("got: %v, want: %v", got, want)
	}
}
Exemple #3
0
// ExecuteFetch executes the query on the connection
func (conn *Connection) ExecuteFetch(query string, maxrows int, wantfields bool) (qr *sqltypes.Result, err error) {
	if conn.IsClosed() {
		return nil, sqldb.NewSQLError(2006, "Connection is closed")
	}

	if C.vt_execute(&conn.c, (*C.char)(hack.StringPointer(query)), C.ulong(len(query)), 0) != 0 {
		return nil, conn.lastError(query)
	}
	defer conn.CloseResult()

	qr = &sqltypes.Result{}
	qr.RowsAffected = uint64(conn.c.affected_rows)
	qr.InsertID = uint64(conn.c.insert_id)
	if conn.c.num_fields == 0 {
		return qr, nil
	}

	if qr.RowsAffected > uint64(maxrows) {
		return nil, &sqldb.SQLError{
			Num:     0,
			Message: fmt.Sprintf("Row count exceeded %d", maxrows),
			Query:   string(query),
		}
	}
	if wantfields {
		qr.Fields = conn.Fields()
	}
	qr.Rows, err = conn.fetchAll()
	return qr, err
}
Exemple #4
0
func TestTabletErrorRetriableErrorTypeOverwrite(t *testing.T) {
	sqlErr := sqldb.NewSQLError(mysql.ErrOptionPreventsStatement, "read-only")
	tabletErr := NewTabletErrorSQL(ErrFatal, vtrpc.ErrorCode_INTERNAL_ERROR, sqlErr)
	if tabletErr.ErrorType != ErrRetry || tabletErr.ErrorCode != vtrpc.ErrorCode_QUERY_NOT_SERVED {
		t.Fatalf("tablet error should have error type ErrRetry and error code %v", vtrpc.ErrorCode_QUERY_NOT_SERVED)
	}
}
Exemple #5
0
func TestTabletErrorRetriableErrorTypeOverwrite(t *testing.T) {
	sqlErr := sqldb.NewSQLError(mysql.ErrOptionPreventsStatement, "HY000", "read-only")
	tabletErr := NewTabletErrorSQL(vtrpcpb.ErrorCode_INTERNAL_ERROR, sqlErr)
	if tabletErr.ErrorCode != vtrpcpb.ErrorCode_QUERY_NOT_SERVED {
		t.Fatalf("got: %v wanted: QUERY_NOT_SERVED", tabletErr.ErrorCode)
	}
}
Exemple #6
0
func TestTabletErrorPrefix(t *testing.T) {
	tabletErr := NewTabletErrorSQL(ErrRetry, vtrpc.ErrorCode_UNKNOWN_ERROR, sqldb.NewSQLError(2000, "test"))
	if tabletErr.Prefix() != "retry: " {
		t.Fatalf("tablet error with error type: ErrRetry should has prefix: 'retry: '")
	}
	tabletErr = NewTabletErrorSQL(ErrFatal, vtrpc.ErrorCode_UNKNOWN_ERROR, sqldb.NewSQLError(2000, "test"))
	if tabletErr.Prefix() != "fatal: " {
		t.Fatalf("tablet error with error type: ErrFatal should has prefix: 'fatal: '")
	}
	tabletErr = NewTabletErrorSQL(ErrTxPoolFull, vtrpc.ErrorCode_UNKNOWN_ERROR, sqldb.NewSQLError(2000, "test"))
	if tabletErr.Prefix() != "tx_pool_full: " {
		t.Fatalf("tablet error with error type: ErrTxPoolFull should has prefix: 'tx_pool_full: '")
	}
	tabletErr = NewTabletErrorSQL(ErrNotInTx, vtrpc.ErrorCode_UNKNOWN_ERROR, sqldb.NewSQLError(2000, "test"))
	if tabletErr.Prefix() != "not_in_tx: " {
		t.Fatalf("tablet error with error type: ErrNotInTx should has prefix: 'not_in_tx: '")
	}
}
Exemple #7
0
func TestTabletErrorPrefix(t *testing.T) {
	tabletErr := NewTabletErrorSQL(vtrpcpb.ErrorCode_QUERY_NOT_SERVED, sqldb.NewSQLError(2000, "HY000", "test"))
	if tabletErr.Prefix() != "retry: " {
		t.Fatalf("tablet error with error code: QUERY_NOT_SERVED should has prefix: 'retry: '")
	}
	tabletErr = NewTabletErrorSQL(vtrpcpb.ErrorCode_INTERNAL_ERROR, sqldb.NewSQLError(2000, "HY000", "test"))
	if tabletErr.Prefix() != "fatal: " {
		t.Fatalf("tablet error with error code: INTERNAL_ERROR should has prefix: 'fatal: '")
	}
	tabletErr = NewTabletErrorSQL(vtrpcpb.ErrorCode_RESOURCE_EXHAUSTED, sqldb.NewSQLError(2000, "HY000", "test"))
	if tabletErr.Prefix() != "tx_pool_full: " {
		t.Fatalf("tablet error with error code: RESOURCE_EXHAUSTED should has prefix: 'tx_pool_full: '")
	}
	tabletErr = NewTabletErrorSQL(vtrpcpb.ErrorCode_NOT_IN_TX, sqldb.NewSQLError(2000, "HY000", "test"))
	if tabletErr.Prefix() != "not_in_tx: " {
		t.Fatalf("tablet error with error code: NOT_IN_TX should has prefix: 'not_in_tx: '")
	}
}
Exemple #8
0
// ExecuteStreamFetch starts a streaming query to mysql. Use FetchNext
// on the Connection until it returns nil or error
func (conn *Connection) ExecuteStreamFetch(query string) (err error) {
	if conn.IsClosed() {
		return sqldb.NewSQLError(2006, "Connection is closed")
	}
	if C.vt_execute(&conn.c, (*C.char)(hack.StringPointer(query)), C.ulong(len(query)), 1) != 0 {
		return conn.lastError(query)
	}
	return nil
}
Exemple #9
0
func TestTabletErrorPrefix(t *testing.T) {
	tabletErr := NewTabletErrorSQL(ErrRetry, vtrpc.ErrorCode_QUERY_NOT_SERVED, sqldb.NewSQLError(2000, "test"))
	if tabletErr.Prefix() != "retry: " {
		t.Fatalf("tablet error with error type: ErrRetry should has prefix: 'retry: '")
	}
	tabletErr = NewTabletErrorSQL(ErrFatal, vtrpc.ErrorCode_INTERNAL_ERROR, sqldb.NewSQLError(2000, "test"))
	if tabletErr.Prefix() != "fatal: " {
		t.Fatalf("tablet error with error type: ErrFatal should has prefix: 'fatal: '")
	}
	tabletErr = NewTabletErrorSQL(ErrTxPoolFull, vtrpc.ErrorCode_RESOURCE_EXHAUSTED, sqldb.NewSQLError(2000, "test"))
	if tabletErr.Prefix() != "tx_pool_full: " {
		t.Fatalf("tablet error with error type: ErrTxPoolFull should has prefix: 'tx_pool_full: '")
	}
	tabletErr = NewTabletErrorSQL(ErrNotInTx, vtrpc.ErrorCode_NOT_IN_TX, sqldb.NewSQLError(2000, "test"))
	if tabletErr.Prefix() != "not_in_tx: " {
		t.Fatalf("tablet error with error type: ErrNotInTx should has prefix: 'not_in_tx: '")
	}
}
Exemple #10
0
func TestTabletErrorTxPoolFull(t *testing.T) {
	tabletErr := NewTabletErrorSQL(ErrTxPoolFull, vtrpc.ErrorCode_RESOURCE_EXHAUSTED, sqldb.NewSQLError(1000, "test"))
	queryServiceStats := NewQueryServiceStats("", false)
	defer func() {
		err := recover()
		if err != nil {
			t.Fatalf("error should have been handled already")
		}
	}()
	defer logError(queryServiceStats)
	panic(tabletErr)
}
Exemple #11
0
func TestTabletErrorFatal(t *testing.T) {
	tabletErr := NewTabletErrorSQL(ErrFatal, vtrpc.ErrorCode_INTERNAL_ERROR, sqldb.NewSQLError(1000, "test"))
	queryServiceStats := NewQueryServiceStats("", false)
	defer func() {
		err := recover()
		if err != nil {
			t.Fatalf("error should have been handled already")
		}
	}()
	defer logError(queryServiceStats)
	panic(tabletErr)
}
Exemple #12
0
func TestTabletErrorHandleRetryError(t *testing.T) {
	var err error
	tabletErr := NewTabletErrorSQL(ErrRetry, vtrpc.ErrorCode_UNKNOWN_ERROR, sqldb.NewSQLError(1000, "test"))
	logStats := newSqlQueryStats("TestTabletErrorHandleError", context.Background())
	queryServiceStats := NewQueryServiceStats("", false)
	defer func() {
		_, ok := err.(*TabletError)
		if !ok {
			t.Fatalf("error should be a TabletError, but got error: %v", err)
		}
	}()
	defer handleError(&err, logStats, queryServiceStats)
	panic(tabletErr)
}
Exemple #13
0
func TestTabletErrorHandleTxPoolFullError(t *testing.T) {
	var err error
	tabletErr := NewTabletErrorSQL(ErrTxPoolFull, vtrpc.ErrorCode_RESOURCE_EXHAUSTED, sqldb.NewSQLError(1000, "test"))
	logStats := newLogStats("TestTabletErrorHandleError", context.Background())
	queryServiceStats := NewQueryServiceStats("", false)
	defer func() {
		_, ok := err.(*TabletError)
		if !ok {
			t.Fatalf("error should be a TabletError, but got error: %v", err)
		}
	}()
	defer handleError(&err, logStats, queryServiceStats)
	panic(tabletErr)
}
Exemple #14
0
func TestTabletErrorMsgTooLong(t *testing.T) {
	buf := make([]byte, 2*maxErrLen)
	for i := 0; i < 2*maxErrLen; i++ {
		buf[i] = 'a'
	}
	msg := string(buf)
	sqlErr := sqldb.NewSQLError(mysql.ErrDupEntry, "23000", msg)
	tabletErr := NewTabletErrorSQL(vtrpcpb.ErrorCode_INTERNAL_ERROR, sqlErr)
	if tabletErr.ErrorCode != vtrpcpb.ErrorCode_INTEGRITY_ERROR {
		t.Fatalf("got %v wanted INTEGRITY_ERROR", tabletErr.ErrorCode)
	}
	if tabletErr.Message != string(buf[:maxErrLen]) {
		t.Fatalf("message should be capped, only %d character will be shown", maxErrLen)
	}
}
Exemple #15
0
func TestTabletErrorConnError(t *testing.T) {
	tabletErr := NewTabletErrorSQL(ErrFatal, vtrpc.ErrorCode_INTERNAL_ERROR, sqldb.NewSQLError(1999, "test"))
	if IsConnErr(tabletErr) {
		t.Fatalf("table error: %v is not a connection error", tabletErr)
	}
	tabletErr = NewTabletErrorSQL(ErrFatal, vtrpc.ErrorCode_INTERNAL_ERROR, sqldb.NewSQLError(2000, "test"))
	if !IsConnErr(tabletErr) {
		t.Fatalf("table error: %v is a connection error", tabletErr)
	}
	tabletErr = NewTabletErrorSQL(ErrFatal, vtrpc.ErrorCode_INTERNAL_ERROR, sqldb.NewSQLError(mysql.ErrServerLost, "test"))
	if IsConnErr(tabletErr) {
		t.Fatalf("table error: %v is not a connection error", tabletErr)
	}
	want := "fatal: the query was killed either because it timed out or was canceled: test (errno 2013)"
	if tabletErr.Error() != want {
		t.Fatalf("tablet error: %v, want %s", tabletErr, want)
	}
	sqlErr := sqldb.NewSQLError(1998, "test")
	if IsConnErr(sqlErr) {
		t.Fatalf("sql error: %v is not a connection error", sqlErr)
	}
	sqlErr = sqldb.NewSQLError(2001, "test")
	if !IsConnErr(sqlErr) {
		t.Fatalf("sql error: %v is a connection error", sqlErr)
	}

	err := fmt.Errorf("(errno 2005)")
	if !IsConnErr(err) {
		t.Fatalf("error: %v is a connection error", err)
	}

	err = fmt.Errorf("(errno 123456789012345678901234567890)")
	if IsConnErr(err) {
		t.Fatalf("error: %v is not a connection error", err)
	}
}
Exemple #16
0
func TestTabletErrorMsgTooLong(t *testing.T) {
	buf := make([]byte, 2*maxErrLen)
	for i := 0; i < 2*maxErrLen; i++ {
		buf[i] = 'a'
	}
	msg := string(buf)
	sqlErr := sqldb.NewSQLError(mysql.ErrDupEntry, msg)
	tabletErr := NewTabletErrorSQL(ErrFatal, vtrpc.ErrorCode_INTERNAL_ERROR, sqlErr)
	if tabletErr.ErrorType != ErrFatal || tabletErr.ErrorCode != vtrpc.ErrorCode_INTEGRITY_ERROR {
		t.Fatalf("tablet error should have error type ErrFatal and error code %v", vtrpc.ErrorCode_INTEGRITY_ERROR)
	}
	if tabletErr.Message != string(buf[:maxErrLen]) {
		t.Fatalf("message should be capped, only %s character will be shown", maxErrLen)
	}
}
Exemple #17
0
func TestTabletErrorRecordStats(t *testing.T) {
	tabletErr := NewTabletErrorSQL(ErrRetry, vtrpc.ErrorCode_QUERY_NOT_SERVED, sqldb.NewSQLError(2000, "test"))
	queryServiceStats := NewQueryServiceStats("", false)
	retryCounterBefore := queryServiceStats.InfoErrors.Counts()["Retry"]
	tabletErr.RecordStats(queryServiceStats)
	retryCounterAfter := queryServiceStats.InfoErrors.Counts()["Retry"]
	if retryCounterAfter-retryCounterBefore != 1 {
		t.Fatalf("tablet error with error type ErrRetry should increase Retry error count by 1")
	}

	tabletErr = NewTabletErrorSQL(ErrFatal, vtrpc.ErrorCode_INTERNAL_ERROR, sqldb.NewSQLError(2000, "test"))
	fatalCounterBefore := queryServiceStats.InfoErrors.Counts()["Fatal"]
	tabletErr.RecordStats(queryServiceStats)
	fatalCounterAfter := queryServiceStats.InfoErrors.Counts()["Fatal"]
	if fatalCounterAfter-fatalCounterBefore != 1 {
		t.Fatalf("tablet error with error type ErrFatal should increase Fatal error count by 1")
	}

	tabletErr = NewTabletErrorSQL(ErrTxPoolFull, vtrpc.ErrorCode_RESOURCE_EXHAUSTED, sqldb.NewSQLError(2000, "test"))
	txPoolFullCounterBefore := queryServiceStats.ErrorStats.Counts()["TxPoolFull"]
	tabletErr.RecordStats(queryServiceStats)
	txPoolFullCounterAfter := queryServiceStats.ErrorStats.Counts()["TxPoolFull"]
	if txPoolFullCounterAfter-txPoolFullCounterBefore != 1 {
		t.Fatalf("tablet error with error type ErrTxPoolFull should increase TxPoolFull error count by 1")
	}

	tabletErr = NewTabletErrorSQL(ErrNotInTx, vtrpc.ErrorCode_NOT_IN_TX, sqldb.NewSQLError(2000, "test"))
	notInTxCounterBefore := queryServiceStats.ErrorStats.Counts()["NotInTx"]
	tabletErr.RecordStats(queryServiceStats)
	notInTxCounterAfter := queryServiceStats.ErrorStats.Counts()["NotInTx"]
	if notInTxCounterAfter-notInTxCounterBefore != 1 {
		t.Fatalf("tablet error with error type ErrNotInTx should increase NotInTx error count by 1")
	}

	tabletErr = NewTabletErrorSQL(ErrFail, vtrpc.ErrorCode_UNKNOWN_ERROR, sqldb.NewSQLError(mysql.ErrDupEntry, "test"))
	dupKeyCounterBefore := queryServiceStats.InfoErrors.Counts()["DupKey"]
	tabletErr.RecordStats(queryServiceStats)
	dupKeyCounterAfter := queryServiceStats.InfoErrors.Counts()["DupKey"]
	if dupKeyCounterAfter-dupKeyCounterBefore != 1 {
		t.Fatalf("sql error with error type mysql.ErrDupEntry should increase DupKey error count by 1")
	}

	tabletErr = NewTabletErrorSQL(ErrFail, vtrpc.ErrorCode_UNKNOWN_ERROR, sqldb.NewSQLError(mysql.ErrLockWaitTimeout, "test"))
	lockWaitTimeoutCounterBefore := queryServiceStats.ErrorStats.Counts()["Deadlock"]
	tabletErr.RecordStats(queryServiceStats)
	lockWaitTimeoutCounterAfter := queryServiceStats.ErrorStats.Counts()["Deadlock"]
	if lockWaitTimeoutCounterAfter-lockWaitTimeoutCounterBefore != 1 {
		t.Fatalf("sql error with error type mysql.ErrLockWaitTimeout should increase Deadlock error count by 1")
	}

	tabletErr = NewTabletErrorSQL(ErrFail, vtrpc.ErrorCode_UNKNOWN_ERROR, sqldb.NewSQLError(mysql.ErrLockDeadlock, "test"))
	deadlockCounterBefore := queryServiceStats.ErrorStats.Counts()["Deadlock"]
	tabletErr.RecordStats(queryServiceStats)
	deadlockCounterAfter := queryServiceStats.ErrorStats.Counts()["Deadlock"]
	if deadlockCounterAfter-deadlockCounterBefore != 1 {
		t.Fatalf("sql error with error type mysql.ErrLockDeadlock should increase Deadlock error count by 1")
	}

	tabletErr = NewTabletErrorSQL(ErrFail, vtrpc.ErrorCode_UNKNOWN_ERROR, sqldb.NewSQLError(mysql.ErrOptionPreventsStatement, "test"))
	failCounterBefore := queryServiceStats.ErrorStats.Counts()["Fail"]
	tabletErr.RecordStats(queryServiceStats)
	failCounterAfter := queryServiceStats.ErrorStats.Counts()["Fail"]
	if failCounterAfter-failCounterBefore != 1 {
		t.Fatalf("sql error with error type mysql.ErrOptionPreventsStatement should increase Fail error count by 1")
	}
}
func TestQueryExecutorPlanUpsertPk(t *testing.T) {
	db := setUpQueryExecutorTest()
	db.AddQuery("insert into test_table values (1) /* _stream test_table (pk ) (1 ); */", &sqltypes.Result{})
	want := &sqltypes.Result{
		Rows: make([][]sqltypes.Value, 0),
	}
	query := "insert into test_table values(1) on duplicate key update val=1"
	ctx := context.Background()
	tsv := newTestTabletServer(ctx, enableStrict, db)
	txid := newTransaction(tsv)
	qre := newTestQueryExecutor(ctx, tsv, query, txid)
	defer tsv.StopService()
	checkPlanID(t, planbuilder.PlanUpsertPK, qre.plan.PlanID)
	got, err := qre.Execute()
	if err != nil {
		t.Fatalf("qre.Execute() = %v, want nil", err)
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("got: %v, want: %v", got, want)
	}
	wantqueries := []string{"insert into test_table values (1) /* _stream test_table (pk ) (1 ); */"}
	gotqueries := fetchRecordedQueries(qre)
	if !reflect.DeepEqual(gotqueries, wantqueries) {
		t.Errorf("queries: %v, want %v", gotqueries, wantqueries)
	}
	testCommitHelper(t, tsv, qre)

	db.AddRejectedQuery("insert into test_table values (1) /* _stream test_table (pk ) (1 ); */", errRejected)
	txid = newTransaction(tsv)
	qre = newTestQueryExecutor(ctx, tsv, query, txid)
	_, err = qre.Execute()
	wantErr := "error: rejected"
	if err == nil || err.Error() != wantErr {
		t.Errorf("qre.Execute() = %v, want %v", err, wantErr)
	}
	wantqueries = []string{}
	gotqueries = fetchRecordedQueries(qre)
	if !reflect.DeepEqual(gotqueries, wantqueries) {
		t.Errorf("queries: %v, want %v", gotqueries, wantqueries)
	}
	testCommitHelper(t, tsv, qre)

	db.AddRejectedQuery(
		"insert into test_table values (1) /* _stream test_table (pk ) (1 ); */",
		sqldb.NewSQLError(mysql.ErrDupEntry, "23000", "err"),
	)
	db.AddQuery("update test_table set val = 1 where pk in (1) /* _stream test_table (pk ) (1 ); */", &sqltypes.Result{})
	txid = newTransaction(tsv)
	qre = newTestQueryExecutor(ctx, tsv, query, txid)
	_, err = qre.Execute()
	wantErr = "error: err (errno 1062) (sqlstate 23000)"
	if err == nil || err.Error() != wantErr {
		t.Errorf("qre.Execute() = %v, want %v", err, wantErr)
	}
	wantqueries = []string{}
	gotqueries = fetchRecordedQueries(qre)
	if !reflect.DeepEqual(gotqueries, wantqueries) {
		t.Errorf("queries: %v, want %v", gotqueries, wantqueries)
	}
	testCommitHelper(t, tsv, qre)

	db.AddRejectedQuery(
		"insert into test_table values (1) /* _stream test_table (pk ) (1 ); */",
		sqldb.NewSQLError(mysql.ErrDupEntry, "23000", "ERROR 1062 (23000): Duplicate entry '2' for key 'PRIMARY'"),
	)
	db.AddQuery(
		"update test_table set val = 1 where pk in (1) /* _stream test_table (pk ) (1 ); */",
		&sqltypes.Result{RowsAffected: 1},
	)
	txid = newTransaction(tsv)
	qre = newTestQueryExecutor(ctx, tsv, query, txid)
	got, err = qre.Execute()
	if err != nil {
		t.Fatalf("qre.Execute() = %v, want nil", err)
	}
	want = &sqltypes.Result{
		RowsAffected: 2,
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got: %v, want: %v", got, want)
	}
	wantqueries = []string{"update test_table set val = 1 where pk in (1) /* _stream test_table (pk ) (1 ); */"}
	gotqueries = fetchRecordedQueries(qre)
	if !reflect.DeepEqual(gotqueries, wantqueries) {
		t.Errorf("queries: %v, want %v", gotqueries, wantqueries)
	}
	testCommitHelper(t, tsv, qre)
}