Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MemoryDB: UPDATE succeeds but is not reflected in subsequent SELECT #2415

Open
mlucic opened this issue Mar 27, 2024 · 0 comments
Open

MemoryDB: UPDATE succeeds but is not reflected in subsequent SELECT #2415

mlucic opened this issue Mar 27, 2024 · 0 comments

Comments

@mlucic
Copy link

mlucic commented Mar 27, 2024

Have 5 tables with the following relationships:

  graph TD;
      table1-->table2;
      table1-->table3;
      table3-->table2;
      table4-->table3;
      table5-->table3;
      table5-->table4;

Updating a row in table3 will show as succeeded with rowsAffected == 1, however a subsequent select will not reflect the change in the value.

Removing the table2 constraint from table1 will restore the expected behavior. Removing the table4 constraint from table5 also restores the expected behavior.

Something about the combination of these relationships results in updates not being reflected.

Here is a reproduction in go:

package blue_test

import (
	"context"
	"database/sql"
	"fmt"
	"net"
	"os"
	"testing"
	"time"

	sqle "github.com/dolthub/go-mysql-server"
	gmsSql "github.com/dolthub/go-mysql-server/sql"
	vsql "github.com/dolthub/vitess/go/mysql"
	"google.golang.org/grpc/test/bufconn"
	"gotest.tools/assert"

	"github.com/dolthub/go-mysql-server/memory"
	"github.com/dolthub/go-mysql-server/server"

	"github.com/go-sql-driver/mysql"
	_ "github.com/go-sql-driver/mysql"
)

func newMysql(t *testing.T) *sql.DB {
	dbName := "blue"
	listener := bufconn.Listen(1024)
	memdb := memory.NewDatabase(dbName)
	memdb.EnablePrimaryKeyIndexes()
	pro := memory.NewDBProvider(memdb)
	engine := sqle.NewDefault(pro)
	cfg := server.Config{Listener: listener}
	sessionBuilder := func(ctx context.Context, conn *vsql.Conn, addr string) (gmsSql.Session, error) {
		host := ""
		user := ""
		mysqlConnectionUser, ok := conn.UserData.(gmsSql.MysqlConnectionUser)
		if ok {
			host = mysqlConnectionUser.Host
			user = mysqlConnectionUser.User
		}

		client := gmsSql.Client{Address: host, User: user, Capabilities: conn.Capabilities}
		baseSession := gmsSql.NewBaseSessionWithClientServer(addr, client, conn.ConnectionID)
		return memory.NewSession(baseSession, pro), nil
	}
	s, err := server.NewServer(cfg, engine, sessionBuilder, nil)
	assert.NilError(t, err)

	networkName := "testNetwork"
	mysql.RegisterDialContext(networkName, func(ctx context.Context, addr string) (net.Conn, error) {
		return listener.DialContext(ctx)
	})
	driver, err := mysql.NewConnector(&mysql.Config{
		DBName:               dbName,
		Addr:                 "bufconn",
		Net:                  networkName,
		Passwd:               "",
		User:                 "root",
		AllowNativePasswords: true,
		MultiStatements:      true,
		ParseTime:            true,
		Loc:                  time.UTC,
	})
	assert.NilError(t, err)
	go func() {
		err := s.Start()
		if err != nil {
			panic(err)
		}
	}()
	db := sql.OpenDB(driver)
	var pingErr error
	for i := 0; i < 3; i++ {
		if pingErr = db.Ping(); pingErr == nil {
			break
		}
		time.Sleep(time.Millisecond * 5)
	}
	assert.NilError(t, pingErr)
	t.Cleanup(func() {
		s.Close()
		db.Close()
		engine.Close()
	})
	return db
}

func TestBlue(t *testing.T) {
	db := newMysql(t)
	dat, err := os.ReadFile("schema.sql")
	assert.NilError(t, err)
	_, err = db.Exec(string(dat))
	assert.NilError(t, err)

	table2Insert, err := db.Exec("INSERT INTO table2 (name) VALUES (\"abcd\")")
	assert.NilError(t, err)
	table2Id, err := table2Insert.LastInsertId()
	assert.NilError(t, err)
	table3Insert, err := db.Exec(fmt.Sprintf("INSERT INTO table3 (t2Id) VALUES (%d)", table2Id))
	assert.NilError(t, err)
	table3Id, err := table3Insert.LastInsertId()
	assert.NilError(t, err)
	table3Update, err := db.Exec(fmt.Sprintf("UPDATE table3 SET flag=1 WHERE id = %d", table3Id))
	assert.NilError(t, err)
	rowsAffected, err := table3Update.RowsAffected()
	assert.NilError(t, err)
	assert.Equal(t, int(rowsAffected), 1)
	var t2Id int64
	var flag int
	row := db.QueryRow(fmt.Sprintf("SELECT t2Id, flag FROM table3 WHERE id = %d", table3Id))
	err = row.Scan(&t2Id, &flag)
	assert.NilError(t, err)
	assert.Equal(t, t2Id, table2Id)
	assert.Equal(t, flag, 1)
	_, err = db.Exec(fmt.Sprintf("UPDATE table3 SET flag=0 WHERE id = %d", table3Id))
	assert.NilError(t, err)
	var t2Id2 int64
	var flag2 int
	row = db.QueryRow(fmt.Sprintf("SELECT t2Id, flag FROM table3 WHERE id = %d", table3Id))
	err = row.Scan(&t2Id2, &flag2)
	assert.NilError(t, err)
	assert.Equal(t, t2Id2, table2Id)
	assert.Equal(t, flag2, 0)
}

schema.sql

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `table1`
--

DROP TABLE IF EXISTS `table1`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `table1` (
  `id` int NOT NULL AUTO_INCREMENT,
  `t3Id` int NOT NULL,
  `t2Id` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `t3Id` (`t3Id`),
  KEY `t2Id` (`t2Id`),
  CONSTRAINT `table1_ibfk_1` FOREIGN KEY (`t3Id`) REFERENCES `table3` (`id`),
  CONSTRAINT `table1_ibfk_2` FOREIGN KEY (`t2Id`) REFERENCES `table2` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `table1`
--

LOCK TABLES `table1` WRITE;
/*!40000 ALTER TABLE `table1` DISABLE KEYS */;
/*!40000 ALTER TABLE `table1` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `table2`
--

DROP TABLE IF EXISTS `table2`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `table2` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `table2`
--

LOCK TABLES `table2` WRITE;
/*!40000 ALTER TABLE `table2` DISABLE KEYS */;
/*!40000 ALTER TABLE `table2` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `table3`
--

DROP TABLE IF EXISTS `table3`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `table3` (
  `id` int NOT NULL AUTO_INCREMENT,
  `t2Id` int NOT NULL,
  `flag` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `t2Id` (`t2Id`),
  CONSTRAINT `table3_ibfk_1` FOREIGN KEY (`t2Id`) REFERENCES `table2` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `table3`
--

LOCK TABLES `table3` WRITE;
/*!40000 ALTER TABLE `table3` DISABLE KEYS */;
/*!40000 ALTER TABLE `table3` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `table4`
--

DROP TABLE IF EXISTS `table4`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `table4` (
  `id` int NOT NULL AUTO_INCREMENT,
  `t3Id` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `table4_t3Id_foreign_idx` (`t3Id`),
  CONSTRAINT `table4_t3Id_foreign_idx` FOREIGN KEY (`t3Id`) REFERENCES `table3` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `table4`
--

LOCK TABLES `table4` WRITE;
/*!40000 ALTER TABLE `table4` DISABLE KEYS */;
/*!40000 ALTER TABLE `table4` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `table5`
--

DROP TABLE IF EXISTS `table5`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `table5` (
  `id` int NOT NULL AUTO_INCREMENT,
  `t4Id` int DEFAULT NULL,
  `t3Id` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `table5_t3Id_foreign_idx` (`t3Id`),
  KEY `table5_t4Id_foreign_idx` (`t4Id`),
  CONSTRAINT `table5_t4Id_foreign_idx` FOREIGN KEY (`t4Id`) REFERENCES `table4` (`id`),
  CONSTRAINT `table5_t3Id_foreign_idx` FOREIGN KEY (`t3Id`) REFERENCES `table3` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `table5`
--

LOCK TABLES `table5` WRITE;
/*!40000 ALTER TABLE `table5` DISABLE KEYS */;
/*!40000 ALTER TABLE `table5` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants