mirror of
https://gitee.com/johng/gf
synced 2026-07-04 13:02:36 +08:00
test(contrib/drivers/mariadb): add layer 3 features and issue regression tests (#4724)
## Summary - Port 5 feature tests: duplicate-key handling (OnDuplicate/OnDuplicateEx/Save), JSON field operations, row-level locking (Lock/LockUpdate/LockShared with transactions), master-slave configuration, table metadata inspection - Port 1 partition test: RANGE partitioning with Partition() clause (adapted from MySQL baseline) - Port 30 issue regression tests from MySQL baseline - Includes 14 testdata SQL files for issue-specific table schemas Layer 3 tests cover MariaDB-specific adaptations where needed (e.g., SKIP LOCKED requires MariaDB 10.6+ — commented out for compatibility, LOCK IN SHARE MODE instead of FOR SHARE for older versions). All tests are structurally aligned with the MySQL driver baseline. Package and import references are adapted for MariaDB. ref #4689 Co-authored-by: John Guo <claymore1986@gmail.com>
This commit is contained in:
321
contrib/drivers/mariadb/mariadb_z_unit_feature_duplicate_test.go
Normal file
321
contrib/drivers/mariadb/mariadb_z_unit_feature_duplicate_test.go
Normal file
@ -0,0 +1,321 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mariadb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func createDuplicateTable(table ...string) string {
|
||||
var name string
|
||||
if len(table) > 0 {
|
||||
name = table[0]
|
||||
} else {
|
||||
name = fmt.Sprintf(`duplicate_table_%d`, gtime.TimestampNano())
|
||||
}
|
||||
dropTable(name)
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
email varchar(100) NOT NULL,
|
||||
username varchar(45) NULL,
|
||||
score int(10) unsigned DEFAULT 0,
|
||||
login_count int(10) unsigned DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_email (email)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`, name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func Test_OnDuplicateKeyUpdate_Basic(t *testing.T) {
|
||||
table := createDuplicateTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// First insert
|
||||
_, err := db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score)",
|
||||
table,
|
||||
), "user1@example.com", "user1", 100)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err := db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user1")
|
||||
t.Assert(one["score"], 100)
|
||||
|
||||
// Duplicate insert - should update
|
||||
_, err = db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score)",
|
||||
table,
|
||||
), "user1@example.com", "user1_updated", 200)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err = db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user1_updated")
|
||||
t.Assert(one["score"], 200)
|
||||
|
||||
// Verify only one record exists
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OnDuplicateKeyUpdate_Increment(t *testing.T) {
|
||||
table := createDuplicateTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// First insert
|
||||
_, err := db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, login_count) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE login_count = login_count + 1",
|
||||
table,
|
||||
), "user1@example.com", "user1", 1)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err := db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["login_count"], 1)
|
||||
|
||||
// Duplicate - increment login_count
|
||||
_, err = db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, login_count) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE login_count = login_count + 1",
|
||||
table,
|
||||
), "user1@example.com", "user1", 1)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err = db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["login_count"], 2)
|
||||
|
||||
// Third time
|
||||
_, err = db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, login_count) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE login_count = login_count + 1",
|
||||
table,
|
||||
), "user1@example.com", "user1", 1)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err = db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["login_count"], 3)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OnDuplicateKeyUpdate_MultipleColumns(t *testing.T) {
|
||||
table := createDuplicateTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// First insert
|
||||
_, err := db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score, login_count) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score), login_count = login_count + 1",
|
||||
table,
|
||||
), "user1@example.com", "user1", 100, 1)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err := db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user1")
|
||||
t.Assert(one["score"], 100)
|
||||
t.Assert(one["login_count"], 1)
|
||||
|
||||
// Duplicate - update multiple columns
|
||||
_, err = db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score, login_count) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score), login_count = login_count + 1",
|
||||
table,
|
||||
), "user1@example.com", "user1_v2", 200, 1)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err = db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user1_v2")
|
||||
t.Assert(one["score"], 200)
|
||||
t.Assert(one["login_count"], 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OnDuplicateKeyUpdate_Batch(t *testing.T) {
|
||||
table := createDuplicateTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert multiple records
|
||||
_, err := db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score)",
|
||||
table,
|
||||
), "user1@example.com", "user1", 100,
|
||||
"user2@example.com", "user2", 200,
|
||||
"user3@example.com", "user3", 300)
|
||||
t.AssertNil(err)
|
||||
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 3)
|
||||
|
||||
// Update with duplicate - should update specific records
|
||||
_, err = db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?), (?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score)",
|
||||
table,
|
||||
), "user1@example.com", "user1_updated", 150,
|
||||
"user2@example.com", "user2_updated", 250)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Still 3 records
|
||||
count, err = db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 3)
|
||||
|
||||
// Verify updates
|
||||
one, err := db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user1_updated")
|
||||
t.Assert(one["score"], 150)
|
||||
|
||||
one, err = db.Model(table).Where("email", "user2@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user2_updated")
|
||||
t.Assert(one["score"], 250)
|
||||
|
||||
// user3 unchanged
|
||||
one, err = db.Model(table).Where("email", "user3@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user3")
|
||||
t.Assert(one["score"], 300)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OnDuplicateKeyUpdate_ConditionalUpdate(t *testing.T) {
|
||||
table := createDuplicateTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// First insert
|
||||
_, err := db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE score = IF(VALUES(score) > score, VALUES(score), score)",
|
||||
table,
|
||||
), "user1@example.com", "user1", 100)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err := db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["score"], 100)
|
||||
|
||||
// Try to update with lower score - should not update
|
||||
_, err = db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE score = IF(VALUES(score) > score, VALUES(score), score)",
|
||||
table,
|
||||
), "user1@example.com", "user1", 50)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err = db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["score"], 100) // Still 100
|
||||
|
||||
// Update with higher score - should update
|
||||
_, err = db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE score = IF(VALUES(score) > score, VALUES(score), score)",
|
||||
table,
|
||||
), "user1@example.com", "user1", 150)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err = db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["score"], 150) // Updated to 150
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OnDuplicateKeyUpdate_WithTransaction(t *testing.T) {
|
||||
table := createDuplicateTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Transaction with ON DUPLICATE KEY UPDATE
|
||||
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// First insert
|
||||
_, err := tx.Exec(fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score)",
|
||||
table,
|
||||
), "user1@example.com", "user1", 100)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplicate in same transaction
|
||||
_, err = tx.Exec(fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score)",
|
||||
table,
|
||||
), "user1@example.com", "user1_updated", 200)
|
||||
return err
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify final state
|
||||
one, err := db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user1_updated")
|
||||
t.Assert(one["score"], 200)
|
||||
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OnDuplicateKeyUpdate_MixedInsertUpdate(t *testing.T) {
|
||||
table := createDuplicateTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// First batch insert
|
||||
_, err := db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?), (?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score)",
|
||||
table,
|
||||
), "user1@example.com", "user1", 100,
|
||||
"user2@example.com", "user2", 200)
|
||||
t.AssertNil(err)
|
||||
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 2)
|
||||
|
||||
// Mixed batch: one duplicate, one new
|
||||
_, err = db.Exec(ctx, fmt.Sprintf(
|
||||
"INSERT INTO %s (email, username, score) VALUES (?, ?, ?), (?, ?, ?) ON DUPLICATE KEY UPDATE username = VALUES(username), score = VALUES(score)",
|
||||
table,
|
||||
), "user1@example.com", "user1_updated", 150,
|
||||
"user3@example.com", "user3", 300)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Should have 3 records now
|
||||
count, err = db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 3)
|
||||
|
||||
// Verify user1 was updated
|
||||
one, err := db.Model(table).Where("email", "user1@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user1_updated")
|
||||
t.Assert(one["score"], 150)
|
||||
|
||||
// Verify user3 was inserted
|
||||
one, err = db.Model(table).Where("email", "user3@example.com").One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["username"], "user3")
|
||||
t.Assert(one["score"], 300)
|
||||
})
|
||||
}
|
||||
394
contrib/drivers/mariadb/mariadb_z_unit_feature_json_test.go
Normal file
394
contrib/drivers/mariadb/mariadb_z_unit_feature_json_test.go
Normal file
@ -0,0 +1,394 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mariadb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func createJSONTable(table ...string) string {
|
||||
var name string
|
||||
if len(table) > 0 {
|
||||
name = table[0]
|
||||
} else {
|
||||
name = fmt.Sprintf(`json_table_%d`, gtime.TimestampNano())
|
||||
}
|
||||
dropTable(name)
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NULL,
|
||||
config json NULL,
|
||||
metadata json NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`, name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func Test_JSON_Insert_Map(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"name": "user1",
|
||||
"config": g.Map{
|
||||
"theme": "dark",
|
||||
"lang": "zh-CN",
|
||||
},
|
||||
"metadata": g.Map{
|
||||
"tags": g.Slice{"admin", "developer"},
|
||||
"level": 5,
|
||||
},
|
||||
}
|
||||
result, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.LastInsertId()
|
||||
t.Assert(n, 1)
|
||||
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["name"], "user1")
|
||||
t.AssertNE(one["config"], nil)
|
||||
t.AssertNE(one["metadata"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Insert_String(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"name": "user2",
|
||||
"config": `{"theme":"light","lang":"en-US"}`,
|
||||
"metadata": `{"tags":["user"],"level":1}`,
|
||||
}
|
||||
result, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.LastInsertId()
|
||||
t.Assert(n, 1)
|
||||
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["name"], "user2")
|
||||
t.AssertNE(one["config"], nil)
|
||||
t.AssertNE(one["metadata"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Insert_Null(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"name": "user3",
|
||||
"config": nil,
|
||||
"metadata": nil,
|
||||
}
|
||||
result, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.LastInsertId()
|
||||
t.Assert(n, 1)
|
||||
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["name"], "user3")
|
||||
t.Assert(one["config"], nil)
|
||||
t.Assert(one["metadata"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Update(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert initial data
|
||||
_, err := db.Model(table).Data(g.Map{
|
||||
"name": "user1",
|
||||
"config": g.Map{
|
||||
"theme": "dark",
|
||||
},
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Update JSON column
|
||||
result, err := db.Model(table).Data(g.Map{
|
||||
"config": g.Map{
|
||||
"theme": "light",
|
||||
"lang": "en-US",
|
||||
},
|
||||
}).WherePri(1).Update()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(one["config"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Extract_Where(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert test data
|
||||
data := g.Slice{
|
||||
g.Map{
|
||||
"name": "user1",
|
||||
"config": g.Map{
|
||||
"theme": "dark",
|
||||
"lang": "zh-CN",
|
||||
},
|
||||
},
|
||||
g.Map{
|
||||
"name": "user2",
|
||||
"config": g.Map{
|
||||
"theme": "light",
|
||||
"lang": "en-US",
|
||||
},
|
||||
},
|
||||
g.Map{
|
||||
"name": "user3",
|
||||
"config": g.Map{
|
||||
"theme": "dark",
|
||||
"lang": "en-US",
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Query by JSON field using JSON_EXTRACT
|
||||
all, err := db.Model(table).Where("JSON_EXTRACT(config, '$.theme') = ?", "dark").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 2)
|
||||
|
||||
all, err = db.Model(table).Where("JSON_EXTRACT(config, '$.lang') = ?", "en-US").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Extract_Select(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert test data
|
||||
_, err := db.Model(table).Data(g.Map{
|
||||
"name": "user1",
|
||||
"config": g.Map{
|
||||
"theme": "dark",
|
||||
"lang": "zh-CN",
|
||||
},
|
||||
"metadata": g.Map{
|
||||
"level": 5,
|
||||
},
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Select with JSON_EXTRACT
|
||||
one, err := db.Model(table).Fields("name, JSON_EXTRACT(config, '$.theme') as theme, JSON_EXTRACT(metadata, '$.level') as level").WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["name"], "user1")
|
||||
t.AssertNE(one["theme"], nil)
|
||||
t.AssertNE(one["level"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Array_Query(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert data with JSON array
|
||||
data := g.Slice{
|
||||
g.Map{
|
||||
"name": "user1",
|
||||
"metadata": g.Map{
|
||||
"tags": g.Slice{"admin", "developer"},
|
||||
},
|
||||
},
|
||||
g.Map{
|
||||
"name": "user2",
|
||||
"metadata": g.Map{
|
||||
"tags": g.Slice{"user"},
|
||||
},
|
||||
},
|
||||
g.Map{
|
||||
"name": "user3",
|
||||
"metadata": g.Map{
|
||||
"tags": g.Slice{"admin", "user"},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Query by JSON array contains
|
||||
all, err := db.Model(table).Where("JSON_CONTAINS(metadata, ?, '$.tags')", `"admin"`).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Batch_Insert(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Slice{
|
||||
g.Map{
|
||||
"name": "user1",
|
||||
"config": g.Map{
|
||||
"theme": "dark",
|
||||
},
|
||||
},
|
||||
g.Map{
|
||||
"name": "user2",
|
||||
"config": g.Map{
|
||||
"theme": "light",
|
||||
},
|
||||
},
|
||||
g.Map{
|
||||
"name": "user3",
|
||||
"config": nil,
|
||||
},
|
||||
}
|
||||
result, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 3)
|
||||
|
||||
all, err := db.Model(table).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Scan_To_Struct(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type Config struct {
|
||||
Theme string `json:"theme"`
|
||||
Lang string `json:"lang"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
Config *Config
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert data
|
||||
_, err := db.Model(table).Data(g.Map{
|
||||
"name": "user1",
|
||||
"config": g.Map{
|
||||
"theme": "dark",
|
||||
"lang": "zh-CN",
|
||||
},
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Scan to struct
|
||||
var user User
|
||||
err = db.Model(table).WherePri(1).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Name, "user1")
|
||||
t.AssertNE(user.Config, nil)
|
||||
if user.Config != nil {
|
||||
t.Assert(user.Config.Theme, "dark")
|
||||
t.Assert(user.Config.Lang, "zh-CN")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Complex_Structure(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert complex nested JSON
|
||||
data := g.Map{
|
||||
"name": "user1",
|
||||
"config": g.Map{
|
||||
"ui": g.Map{
|
||||
"theme": "dark",
|
||||
"fontSize": g.Map{
|
||||
"base": 14,
|
||||
"code": 12,
|
||||
},
|
||||
},
|
||||
"editor": g.Map{
|
||||
"tabSize": 4,
|
||||
"wordWrap": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.LastInsertId()
|
||||
t.Assert(n, 1)
|
||||
|
||||
// Query nested JSON path
|
||||
one, err := db.Model(table).Fields("JSON_EXTRACT(config, '$.ui.theme') as theme, JSON_EXTRACT(config, '$.ui.fontSize.base') as base_font").WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(one["theme"], nil)
|
||||
t.AssertNE(one["base_font"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_JSON_Transaction(t *testing.T) {
|
||||
table := createJSONTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// Insert in transaction
|
||||
_, err := tx.Model(table).Ctx(ctx).Data(g.Map{
|
||||
"name": "user1",
|
||||
"config": g.Map{
|
||||
"theme": "dark",
|
||||
},
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update in transaction
|
||||
_, err = tx.Model(table).Ctx(ctx).Data(g.Map{
|
||||
"config": g.Map{
|
||||
"theme": "light",
|
||||
},
|
||||
}).WherePri(1).Update()
|
||||
return err
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify data
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["name"], "user1")
|
||||
t.AssertNE(one["config"], nil)
|
||||
})
|
||||
}
|
||||
228
contrib/drivers/mariadb/mariadb_z_unit_feature_lock_test.go
Normal file
228
contrib/drivers/mariadb/mariadb_z_unit_feature_lock_test.go
Normal file
@ -0,0 +1,228 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mariadb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
// Test_Model_Lock tests the Lock method with custom lock clause
|
||||
func Test_Model_Lock(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test basic Lock with FOR UPDATE
|
||||
one, err := db.Model(table).Lock("FOR UPDATE").Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["id"], 1)
|
||||
|
||||
// Test Lock with legacy LOCK IN SHARE MODE (MariaDB/MySQL compatible)
|
||||
one, err = db.Model(table).Lock("LOCK IN SHARE MODE").Where("id", 3).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["id"], 3)
|
||||
|
||||
// Test Lock with predefined constants
|
||||
one, err = db.Model(table).Lock(gdb.LockForUpdate).Where("id", 4).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["id"], 4)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_LockUpdate tests the LockUpdate convenience method
|
||||
func Test_Model_LockUpdate(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test LockUpdate is equivalent to Lock("FOR UPDATE")
|
||||
one, err := db.Model(table).LockUpdate().Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["id"], 1)
|
||||
t.Assert(one["passport"], "user_1")
|
||||
|
||||
// Test LockUpdate with All()
|
||||
all, err := db.Model(table).LockUpdate().Where("id<?", 4).Order("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 3)
|
||||
t.Assert(all[0]["id"], 1)
|
||||
t.Assert(all[2]["id"], 3)
|
||||
|
||||
// Test LockUpdate with Count()
|
||||
count, err := db.Model(table).LockUpdate().Where("id>?", 5).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 5)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_LockUpdateSkipLocked tests the LockUpdateSkipLocked convenience method
|
||||
// Note: SKIP LOCKED requires MariaDB 10.6+, skipped for compatibility
|
||||
// func Test_Model_LockUpdateSkipLocked(t *testing.T) {
|
||||
// table := createInitTable()
|
||||
// defer dropTable(table)
|
||||
//
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// // Test LockUpdateSkipLocked basic usage
|
||||
// one, err := db.Model(table).LockUpdateSkipLocked().Where("id", 1).One()
|
||||
// t.AssertNil(err)
|
||||
// t.Assert(one["id"], 1)
|
||||
//
|
||||
// // Test LockUpdateSkipLocked with All()
|
||||
// all, err := db.Model(table).LockUpdateSkipLocked().Where("id>?", 7).Order("id").All()
|
||||
// t.AssertNil(err)
|
||||
// t.Assert(len(all), 3)
|
||||
// })
|
||||
// }
|
||||
|
||||
// Test_Model_LockShared tests the LockShared convenience method
|
||||
func Test_Model_LockShared(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test LockShared is equivalent to Lock("LOCK IN SHARE MODE")
|
||||
one, err := db.Model(table).LockShared().Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["id"], 1)
|
||||
|
||||
// Test LockShared with All()
|
||||
all, err := db.Model(table).LockShared().Where("id<=?", 5).Order("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 5)
|
||||
t.Assert(all[0]["id"], 1)
|
||||
t.Assert(all[4]["id"], 5)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Lock_WithTransaction tests Lock methods within transaction
|
||||
func Test_Model_Lock_WithTransaction(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// Lock row for update in transaction
|
||||
one, err := tx.Model(table).LockUpdate().Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["id"], 1)
|
||||
|
||||
// Update the locked row
|
||||
_, err = tx.Model(table).Data(g.Map{"nickname": "updated_name"}).Where("id", 1).Update()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify update
|
||||
updated, err := tx.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(updated["nickname"], "updated_name")
|
||||
|
||||
return nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify transaction committed successfully
|
||||
one, err := db.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["nickname"], "updated_name")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Lock_ReleaseAfterCommit tests lock is released after transaction commit
|
||||
func Test_Model_Lock_ReleaseAfterCommit(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Start transaction and lock a row
|
||||
tx, err := db.Begin(ctx)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err := tx.Model(table).LockUpdate().Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["id"], 1)
|
||||
|
||||
// Update within transaction
|
||||
_, err = tx.Model(table).Data(g.Map{"nickname": "tx_update"}).Where("id", 1).Update()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Commit transaction - this should release the lock
|
||||
err = tx.Commit()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Another query should succeed without blocking
|
||||
one, err = db.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["nickname"], "tx_update")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Lock_ReleaseAfterRollback tests lock is released after transaction rollback
|
||||
func Test_Model_Lock_ReleaseAfterRollback(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Start transaction and lock a row
|
||||
tx, err := db.Begin(ctx)
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err := tx.Model(table).LockUpdate().Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["id"], 1)
|
||||
|
||||
// Update within transaction
|
||||
_, err = tx.Model(table).Data(g.Map{"nickname": "rollback_update"}).Where("id", 1).Update()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Rollback transaction - this should release the lock and discard changes
|
||||
err = tx.Rollback()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify original value is preserved
|
||||
one, err = db.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Lock_ChainedMethods tests Lock with other chained methods
|
||||
func Test_Model_Lock_ChainedMethods(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Lock with Fields
|
||||
one, err := db.Model(table).Fields("id,passport").LockUpdate().Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["id"], 1)
|
||||
t.Assert(one["passport"], "user_1")
|
||||
|
||||
// Lock with Order and Limit
|
||||
all, err := db.Model(table).LockShared().Where("id>?", 5).Order("id desc").Limit(3).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 3)
|
||||
t.Assert(all[0]["id"], 10)
|
||||
t.Assert(all[2]["id"], 8)
|
||||
|
||||
// Lock with Group and Having
|
||||
all, err = db.Model(table).Fields("LEFT(passport,4) as prefix, COUNT(*) as cnt").
|
||||
LockUpdate().
|
||||
Group("prefix").
|
||||
Having("cnt>?", 0).
|
||||
Order("prefix").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 1)
|
||||
t.Assert(all[0]["prefix"], "user")
|
||||
t.Assert(all[0]["cnt"], 10)
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,324 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mariadb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
func Test_Master_Slave(t *testing.T) {
|
||||
var err error
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err = db.Exec(ctx, "CREATE DATABASE IF NOT EXISTS `master` CHARACTER SET UTF8")
|
||||
t.AssertNil(err)
|
||||
_, err = db.Exec(ctx, "CREATE DATABASE IF NOT EXISTS `slave` CHARACTER SET UTF8")
|
||||
t.AssertNil(err)
|
||||
})
|
||||
defer func() {
|
||||
_, _ = db.Exec(ctx, "DROP DATABASE `master`")
|
||||
_, _ = db.Exec(ctx, "DROP DATABASE `slave`")
|
||||
}()
|
||||
var (
|
||||
configKey = guid.S()
|
||||
configGroup = gdb.ConfigGroup{
|
||||
gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3307",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
Name: "master",
|
||||
Type: "mariadb",
|
||||
Role: "master",
|
||||
Debug: true,
|
||||
Weight: 100,
|
||||
},
|
||||
gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3307",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
Name: "slave",
|
||||
Type: "mariadb",
|
||||
Role: "slave",
|
||||
Debug: true,
|
||||
Weight: 100,
|
||||
},
|
||||
}
|
||||
)
|
||||
gdb.SetConfigGroup(configKey, configGroup)
|
||||
masterSlaveDB := g.DB(configKey)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := "table_" + guid.S()
|
||||
createTableWithDb(masterSlaveDB.Schema("master"), table)
|
||||
createTableWithDb(masterSlaveDB.Schema("slave"), table)
|
||||
defer dropTableWithDb(masterSlaveDB.Schema("master"), table)
|
||||
defer dropTableWithDb(masterSlaveDB.Schema("slave"), table)
|
||||
|
||||
// Data insert to master.
|
||||
array := garray.New(true)
|
||||
for i := 1; i <= TableSize; i++ {
|
||||
array.Append(g.Map{
|
||||
"id": i,
|
||||
"passport": fmt.Sprintf(`user_%d`, i),
|
||||
"password": fmt.Sprintf(`pass_%d`, i),
|
||||
"nickname": fmt.Sprintf(`name_%d`, i),
|
||||
"create_time": gtime.NewFromStr(CreateTime).String(),
|
||||
})
|
||||
}
|
||||
_, err = masterSlaveDB.Model(table).Data(array).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
var count int
|
||||
// Auto slave.
|
||||
count, err = masterSlaveDB.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, int64(0))
|
||||
|
||||
// slave.
|
||||
count, err = masterSlaveDB.Model(table).Slave().Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, int64(0))
|
||||
|
||||
// master.
|
||||
count, err = masterSlaveDB.Model(table).Master().Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, int64(TableSize))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Master_Slave_Concurrent_ReadWrite tests concurrent read/write routing
|
||||
func Test_Master_Slave_Concurrent_ReadWrite(t *testing.T) {
|
||||
var err error
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err = db.Exec(ctx, "CREATE DATABASE IF NOT EXISTS `master` CHARACTER SET UTF8")
|
||||
t.AssertNil(err)
|
||||
_, err = db.Exec(ctx, "CREATE DATABASE IF NOT EXISTS `slave` CHARACTER SET UTF8")
|
||||
t.AssertNil(err)
|
||||
})
|
||||
defer func() {
|
||||
_, _ = db.Exec(ctx, "DROP DATABASE `master`")
|
||||
_, _ = db.Exec(ctx, "DROP DATABASE `slave`")
|
||||
}()
|
||||
|
||||
var (
|
||||
configKey = guid.S()
|
||||
configGroup = gdb.ConfigGroup{
|
||||
gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3307",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
Name: "master",
|
||||
Type: "mariadb",
|
||||
Role: "master",
|
||||
Weight: 100,
|
||||
},
|
||||
gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3307",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
Name: "slave",
|
||||
Type: "mariadb",
|
||||
Role: "slave",
|
||||
Weight: 100,
|
||||
},
|
||||
}
|
||||
)
|
||||
gdb.SetConfigGroup(configKey, configGroup)
|
||||
masterSlaveDB := g.DB(configKey)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := "table_" + guid.S()
|
||||
createTableWithDb(masterSlaveDB.Schema("master"), table)
|
||||
createTableWithDb(masterSlaveDB.Schema("slave"), table)
|
||||
defer dropTableWithDb(masterSlaveDB.Schema("master"), table)
|
||||
defer dropTableWithDb(masterSlaveDB.Schema("slave"), table)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
concurrency := 10
|
||||
|
||||
// Concurrent writes to master
|
||||
wg.Add(concurrency)
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
_, err := masterSlaveDB.Model(table).Insert(g.Map{
|
||||
"passport": fmt.Sprintf("concurrent_%d", id),
|
||||
"password": fmt.Sprintf("pass_%d", id),
|
||||
"nickname": fmt.Sprintf("name_%d", id),
|
||||
})
|
||||
t.AssertNil(err)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Verify writes went to master
|
||||
count, err := masterSlaveDB.Model(table).Master().Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, concurrency)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Master_Slave_Transaction_Routing tests transaction routing to master
|
||||
func Test_Master_Slave_Transaction_Routing(t *testing.T) {
|
||||
var err error
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err = db.Exec(ctx, "CREATE DATABASE IF NOT EXISTS `master` CHARACTER SET UTF8")
|
||||
t.AssertNil(err)
|
||||
_, err = db.Exec(ctx, "CREATE DATABASE IF NOT EXISTS `slave` CHARACTER SET UTF8")
|
||||
t.AssertNil(err)
|
||||
})
|
||||
defer func() {
|
||||
_, _ = db.Exec(ctx, "DROP DATABASE `master`")
|
||||
_, _ = db.Exec(ctx, "DROP DATABASE `slave`")
|
||||
}()
|
||||
|
||||
var (
|
||||
configKey = guid.S()
|
||||
configGroup = gdb.ConfigGroup{
|
||||
gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3307",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
Name: "master",
|
||||
Type: "mariadb",
|
||||
Role: "master",
|
||||
Weight: 100,
|
||||
},
|
||||
gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3307",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
Name: "slave",
|
||||
Type: "mariadb",
|
||||
Role: "slave",
|
||||
Weight: 100,
|
||||
},
|
||||
}
|
||||
)
|
||||
gdb.SetConfigGroup(configKey, configGroup)
|
||||
masterSlaveDB := g.DB(configKey)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := "table_" + guid.S()
|
||||
createTableWithDb(masterSlaveDB.Schema("master"), table)
|
||||
createTableWithDb(masterSlaveDB.Schema("slave"), table)
|
||||
defer dropTableWithDb(masterSlaveDB.Schema("master"), table)
|
||||
defer dropTableWithDb(masterSlaveDB.Schema("slave"), table)
|
||||
|
||||
// Transaction should route to master
|
||||
err := masterSlaveDB.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
_, err := tx.Model(table).Insert(g.Map{
|
||||
"passport": "tx_user",
|
||||
"password": "tx_pass",
|
||||
"nickname": "tx_name",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read within transaction should also use master
|
||||
count, err := tx.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
|
||||
return nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify data is in master
|
||||
count, err := masterSlaveDB.Model(table).Master().Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Master_Slave_Explicit_Selection tests explicit master/slave selection
|
||||
func Test_Master_Slave_Explicit_Selection(t *testing.T) {
|
||||
var err error
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err = db.Exec(ctx, "CREATE DATABASE IF NOT EXISTS `master` CHARACTER SET UTF8")
|
||||
t.AssertNil(err)
|
||||
_, err = db.Exec(ctx, "CREATE DATABASE IF NOT EXISTS `slave` CHARACTER SET UTF8")
|
||||
t.AssertNil(err)
|
||||
})
|
||||
defer func() {
|
||||
_, _ = db.Exec(ctx, "DROP DATABASE `master`")
|
||||
_, _ = db.Exec(ctx, "DROP DATABASE `slave`")
|
||||
}()
|
||||
|
||||
var (
|
||||
configKey = guid.S()
|
||||
configGroup = gdb.ConfigGroup{
|
||||
gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3307",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
Name: "master",
|
||||
Type: "mariadb",
|
||||
Role: "master",
|
||||
Weight: 100,
|
||||
},
|
||||
gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3307",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
Name: "slave",
|
||||
Type: "mariadb",
|
||||
Role: "slave",
|
||||
Weight: 100,
|
||||
},
|
||||
}
|
||||
)
|
||||
gdb.SetConfigGroup(configKey, configGroup)
|
||||
masterSlaveDB := g.DB(configKey)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := "table_" + guid.S()
|
||||
createTableWithDb(masterSlaveDB.Schema("master"), table)
|
||||
createTableWithDb(masterSlaveDB.Schema("slave"), table)
|
||||
defer dropTableWithDb(masterSlaveDB.Schema("master"), table)
|
||||
defer dropTableWithDb(masterSlaveDB.Schema("slave"), table)
|
||||
|
||||
// Insert to master
|
||||
_, err := masterSlaveDB.Model(table).Master().Insert(g.Map{
|
||||
"passport": "explicit_test",
|
||||
"password": "pass",
|
||||
"nickname": "name",
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
// Explicitly read from slave (should be empty)
|
||||
count, err := masterSlaveDB.Model(table).Slave().Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 0)
|
||||
|
||||
// Explicitly read from master (should have data)
|
||||
count, err = masterSlaveDB.Model(table).Master().Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
115
contrib/drivers/mariadb/mariadb_z_unit_feature_metadata_test.go
Normal file
115
contrib/drivers/mariadb/mariadb_z_unit_feature_metadata_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mariadb_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
// Test_TableFields_Basic tests basic TableFields functionality
|
||||
func Test_TableFields_Basic(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
fields, err := db.TableFields(ctx, table)
|
||||
t.AssertNil(err)
|
||||
t.AssertGT(len(fields), 0)
|
||||
|
||||
// Verify common fields exist
|
||||
_, ok := fields["id"]
|
||||
t.Assert(ok, true)
|
||||
_, ok = fields["passport"]
|
||||
t.Assert(ok, true)
|
||||
_, ok = fields["password"]
|
||||
t.Assert(ok, true)
|
||||
_, ok = fields["nickname"]
|
||||
t.Assert(ok, true)
|
||||
_, ok = fields["create_time"]
|
||||
t.Assert(ok, true)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_TableFields_Schema tests TableFields with explicit schema
|
||||
func Test_TableFields_Schema(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
fields, err := db.TableFields(ctx, table, TestSchema1)
|
||||
t.AssertNil(err)
|
||||
t.AssertGT(len(fields), 0)
|
||||
|
||||
// Verify field properties
|
||||
idField, ok := fields["id"]
|
||||
t.Assert(ok, true)
|
||||
t.Assert(idField.Name, "id")
|
||||
t.AssertGT(idField.Index, -1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_HasField_Positive tests HasField for existing field
|
||||
func Test_HasField_Positive(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
has, err := db.GetCore().HasField(ctx, table, "id")
|
||||
t.AssertNil(err)
|
||||
t.Assert(has, true)
|
||||
|
||||
has, err = db.GetCore().HasField(ctx, table, "passport")
|
||||
t.AssertNil(err)
|
||||
t.Assert(has, true)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_HasField_Negative tests HasField for non-existent field
|
||||
func Test_HasField_Negative(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
has, err := db.GetCore().HasField(ctx, table, "non_exist_field")
|
||||
t.AssertNil(err)
|
||||
t.Assert(has, false)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_HasField_Schema tests HasField with explicit schema
|
||||
func Test_HasField_Schema(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
has, err := db.GetCore().HasField(ctx, table, "id", TestSchema1)
|
||||
t.AssertNil(err)
|
||||
t.Assert(has, true)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_QuoteWord_Basic tests basic QuoteWord functionality
|
||||
func Test_QuoteWord_Basic(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
quoted := db.GetCore().QuoteWord("user")
|
||||
t.Assert(quoted, "`user`")
|
||||
|
||||
quoted = db.GetCore().QuoteWord("user_table")
|
||||
t.Assert(quoted, "`user_table`")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_QuoteWord_AlreadyQuoted tests QuoteWord with already quoted words
|
||||
func Test_QuoteWord_AlreadyQuoted(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// If already quoted, should not double quote
|
||||
quoted := db.GetCore().QuoteWord("`user`")
|
||||
t.Assert(quoted, "`user`")
|
||||
})
|
||||
}
|
||||
364
contrib/drivers/mariadb/mariadb_z_unit_feature_partition_test.go
Normal file
364
contrib/drivers/mariadb/mariadb_z_unit_feature_partition_test.go
Normal file
@ -0,0 +1,364 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mariadb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func createRangePartitionTable(table ...string) string {
|
||||
var name string
|
||||
if len(table) > 0 {
|
||||
name = table[0]
|
||||
} else {
|
||||
name = fmt.Sprintf(`partition_range_%d`, gtime.TimestampNano())
|
||||
}
|
||||
if _, err := db3.Exec(ctx, fmt.Sprintf("DROP TABLE IF EXISTS `%s`", name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := db3.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
sales_date date DEFAULT NULL,
|
||||
amount decimal(10,2) DEFAULT NULL,
|
||||
region varchar(50) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
PARTITION BY RANGE (YEAR(sales_date))
|
||||
(PARTITION p2020 VALUES LESS THAN (2021) ENGINE = InnoDB,
|
||||
PARTITION p2021 VALUES LESS THAN (2022) ENGINE = InnoDB,
|
||||
PARTITION p2022 VALUES LESS THAN (2023) ENGINE = InnoDB,
|
||||
PARTITION p2023 VALUES LESS THAN (2024) ENGINE = InnoDB,
|
||||
PARTITION p_future VALUES LESS THAN MAXVALUE ENGINE = InnoDB);
|
||||
`, name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func createHashPartitionTable(table ...string) string {
|
||||
var name string
|
||||
if len(table) > 0 {
|
||||
name = table[0]
|
||||
} else {
|
||||
name = fmt.Sprintf(`partition_hash_%d`, gtime.TimestampNano())
|
||||
}
|
||||
if _, err := db3.Exec(ctx, fmt.Sprintf("DROP TABLE IF EXISTS `%s`", name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := db3.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
user_id int(11) NOT NULL,
|
||||
username varchar(50) DEFAULT NULL,
|
||||
email varchar(100) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
PARTITION BY HASH (user_id)
|
||||
PARTITIONS 4;
|
||||
`, name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func createListPartitionTable(table ...string) string {
|
||||
var name string
|
||||
if len(table) > 0 {
|
||||
name = table[0]
|
||||
} else {
|
||||
name = fmt.Sprintf(`partition_list_%d`, gtime.TimestampNano())
|
||||
}
|
||||
if _, err := db3.Exec(ctx, fmt.Sprintf("DROP TABLE IF EXISTS `%s`", name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := db3.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
region_code int(11) NOT NULL,
|
||||
city varchar(50) DEFAULT NULL,
|
||||
population int(11) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
PARTITION BY LIST (region_code)
|
||||
(PARTITION p_north VALUES IN (1,2,3) ENGINE = InnoDB,
|
||||
PARTITION p_south VALUES IN (4,5,6) ENGINE = InnoDB,
|
||||
PARTITION p_east VALUES IN (7,8,9) ENGINE = InnoDB,
|
||||
PARTITION p_west VALUES IN (10,11,12) ENGINE = InnoDB);
|
||||
`, name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func dropPartitionTable(table string) {
|
||||
if _, err := db3.Exec(ctx, fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Partition_Range_Insert_And_Query(t *testing.T) {
|
||||
table := createRangePartitionTable()
|
||||
defer dropPartitionTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert data across different partitions
|
||||
data := g.Slice{
|
||||
g.Map{"id": 1, "sales_date": "2020-06-15", "amount": 1000.50, "region": "North"},
|
||||
g.Map{"id": 2, "sales_date": "2021-03-20", "amount": 2000.75, "region": "South"},
|
||||
g.Map{"id": 3, "sales_date": "2022-09-10", "amount": 3000.00, "region": "East"},
|
||||
g.Map{"id": 4, "sales_date": "2023-12-01", "amount": 4000.25, "region": "West"},
|
||||
g.Map{"id": 5, "sales_date": "2024-01-15", "amount": 5000.00, "region": "North"},
|
||||
}
|
||||
_, err := db3.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Query all data
|
||||
all, err := db3.Model(table).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 5)
|
||||
|
||||
// Query specific year (should hit specific partition)
|
||||
result, err := db3.Model(table).Where("YEAR(sales_date) = ?", 2022).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 1)
|
||||
t.Assert(result[0]["id"], 3)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Partition_Range_PartitionQuery(t *testing.T) {
|
||||
// Known limitation: Model.Partition() sets m.partition field but it's not used in SQL generation
|
||||
// See: database/gdb/gdb_model_select.go lines 735,755 - m.tables is used without PARTITION clause
|
||||
// TODO: Add PARTITION clause support to GoFrame query builder
|
||||
t.Skip("Partition clause in SELECT queries not yet supported in GoFrame query builder")
|
||||
|
||||
table := createRangePartitionTable()
|
||||
defer dropPartitionTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert data
|
||||
data := g.Slice{
|
||||
g.Map{"id": 1, "sales_date": "2020-06-15", "amount": 1000.50},
|
||||
g.Map{"id": 2, "sales_date": "2021-03-20", "amount": 2000.75},
|
||||
g.Map{"id": 3, "sales_date": "2022-09-10", "amount": 3000.00},
|
||||
g.Map{"id": 4, "sales_date": "2023-12-01", "amount": 4000.25},
|
||||
}
|
||||
_, err := db3.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Query specific partition
|
||||
result, err := db3.Model(table).Partition("p2022").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 1)
|
||||
t.Assert(result[0]["id"], 3)
|
||||
|
||||
// Query multiple partitions
|
||||
result, err = db3.Model(table).Partition("p2021", "p2022").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Partition_Hash_Insert_And_Distribution(t *testing.T) {
|
||||
table := createHashPartitionTable()
|
||||
defer dropPartitionTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert data that will be distributed across hash partitions
|
||||
data := g.Slice{}
|
||||
for i := 1; i <= 20; i++ {
|
||||
data = append(data, g.Map{
|
||||
"id": i,
|
||||
"user_id": i * 10,
|
||||
"username": fmt.Sprintf("user_%d", i),
|
||||
"email": fmt.Sprintf("user%d@example.com", i),
|
||||
})
|
||||
}
|
||||
_, err := db3.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Query all data
|
||||
all, err := db3.Model(table).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 20)
|
||||
|
||||
// Query specific user_id (will hit specific partition based on hash)
|
||||
result, err := db3.Model(table).Where("user_id", 100).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(result["username"], "user_10")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Partition_List_Insert_And_Query(t *testing.T) {
|
||||
// Known limitation: Model.Partition() sets m.partition field but it's not used in SQL generation
|
||||
// See: database/gdb/gdb_model_select.go lines 735,755 - m.tables is used without PARTITION clause
|
||||
// TODO: Add PARTITION clause support to GoFrame query builder
|
||||
t.Skip("Partition clause in SELECT queries not yet supported in GoFrame query builder")
|
||||
|
||||
table := createListPartitionTable()
|
||||
defer dropPartitionTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert data for different regions
|
||||
data := g.Slice{
|
||||
g.Map{"id": 1, "region_code": 1, "city": "Beijing", "population": 2154},
|
||||
g.Map{"id": 2, "region_code": 2, "city": "Harbin", "population": 1063},
|
||||
g.Map{"id": 3, "region_code": 5, "city": "Guangzhou", "population": 1868},
|
||||
g.Map{"id": 4, "region_code": 7, "city": "Shanghai", "population": 2428},
|
||||
g.Map{"id": 5, "region_code": 10, "city": "Chengdu", "population": 2093},
|
||||
}
|
||||
_, err := db3.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Query all
|
||||
all, err := db3.Model(table).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 5)
|
||||
|
||||
// Query specific partition (north region)
|
||||
result, err := db3.Model(table).Partition("p_north").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 2)
|
||||
|
||||
// Query specific partition (south region)
|
||||
result, err = db3.Model(table).Partition("p_south").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 1)
|
||||
t.Assert(result[0]["city"], "Guangzhou")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Partition_Range_Update(t *testing.T) {
|
||||
table := createRangePartitionTable()
|
||||
defer dropPartitionTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert data
|
||||
_, err := db3.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"sales_date": "2022-06-15",
|
||||
"amount": 1000.00,
|
||||
"region": "North",
|
||||
}).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Update data within same partition
|
||||
result, err := db3.Model(table).Data(g.Map{
|
||||
"amount": 1500.00,
|
||||
"region": "South",
|
||||
}).Where("id", 1).Update()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
// Verify update
|
||||
one, err := db3.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["amount"], "1500.00")
|
||||
t.Assert(one["region"], "South")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Partition_Range_Delete(t *testing.T) {
|
||||
table := createRangePartitionTable()
|
||||
defer dropPartitionTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert data
|
||||
data := g.Slice{
|
||||
g.Map{"id": 1, "sales_date": "2020-06-15", "amount": 1000.50},
|
||||
g.Map{"id": 2, "sales_date": "2021-03-20", "amount": 2000.75},
|
||||
g.Map{"id": 3, "sales_date": "2022-09-10", "amount": 3000.00},
|
||||
}
|
||||
_, err := db3.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Delete from specific partition
|
||||
result, err := db3.Model(table).Where("YEAR(sales_date) = ?", 2021).Delete()
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
// Verify deletion
|
||||
all, err := db3.Model(table).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 2)
|
||||
|
||||
// Verify remaining data
|
||||
result2, err := db3.Model(table).Where("YEAR(sales_date) = ?", 2021).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result2), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Partition_Transaction(t *testing.T) {
|
||||
table := createRangePartitionTable()
|
||||
defer dropPartitionTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Transaction with partitioned table
|
||||
err := db3.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// Insert across multiple partitions
|
||||
data := g.Slice{
|
||||
g.Map{"id": 1, "sales_date": "2020-06-15", "amount": 1000.50},
|
||||
g.Map{"id": 2, "sales_date": "2021-03-20", "amount": 2000.75},
|
||||
g.Map{"id": 3, "sales_date": "2022-09-10", "amount": 3000.00},
|
||||
}
|
||||
_, err := tx.Model(table).Ctx(ctx).Data(data).Insert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update in transaction
|
||||
_, err = tx.Model(table).Ctx(ctx).Data(g.Map{
|
||||
"amount": 1500.00,
|
||||
}).Where("id", 1).Update()
|
||||
return err
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify transaction committed
|
||||
all, err := db3.Model(table).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 3)
|
||||
|
||||
one, err := db3.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["amount"], "1500.00")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Partition_Range_Count_And_Sum(t *testing.T) {
|
||||
table := createRangePartitionTable()
|
||||
defer dropPartitionTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert data
|
||||
data := g.Slice{
|
||||
g.Map{"id": 1, "sales_date": "2020-06-15", "amount": 1000.00},
|
||||
g.Map{"id": 2, "sales_date": "2020-09-20", "amount": 1500.00},
|
||||
g.Map{"id": 3, "sales_date": "2021-03-20", "amount": 2000.00},
|
||||
g.Map{"id": 4, "sales_date": "2022-09-10", "amount": 3000.00},
|
||||
}
|
||||
_, err := db3.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Count by year (specific partition)
|
||||
count, err := db3.Model(table).Where("YEAR(sales_date) = ?", 2020).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 2)
|
||||
|
||||
// Sum across partitions
|
||||
value, err := db3.Model(table).Fields("SUM(amount) as total").Value()
|
||||
t.AssertNil(err)
|
||||
t.AssertGT(value.Float64(), 7000.0) // 1000+1500+2000+3000 = 7500
|
||||
})
|
||||
}
|
||||
2075
contrib/drivers/mariadb/mariadb_z_unit_issue_test.go
Normal file
2075
contrib/drivers/mariadb/mariadb_z_unit_issue_test.go
Normal file
File diff suppressed because it is too large
Load Diff
35
contrib/drivers/mariadb/testdata/issues/1380.sql
vendored
Normal file
35
contrib/drivers/mariadb/testdata/issues/1380.sql
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
CREATE TABLE `jfy_gift` (
|
||||
`id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`gift_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '礼品名称',
|
||||
`at_least_recharge_count` int(0) UNSIGNED NOT NULL DEFAULT 1 COMMENT '最少兑换数量',
|
||||
`comments` json NOT NULL COMMENT '礼品留言',
|
||||
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '礼品详情',
|
||||
`cost_price` decimal(10, 2) NULL DEFAULT NULL COMMENT '成本价',
|
||||
`cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '封面',
|
||||
`covers` json NOT NULL COMMENT '礼品图片库',
|
||||
`description` varchar(62) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '礼品备注',
|
||||
`express_type` json NOT NULL COMMENT '配送方式',
|
||||
`gift_type` int(0) NOT NULL COMMENT '礼品类型:1:实物;2:虚拟;3:优惠券;4:积分券',
|
||||
`has_props` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否有多个属性',
|
||||
`is_limit_sell` tinyint(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否限购',
|
||||
`limit_customer_tags` json NOT NULL COMMENT '语序购买的会员标签',
|
||||
`limit_sell_custom` tinyint(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启允许购买的会员标签',
|
||||
`limit_sell_cycle` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '限购周期',
|
||||
`limit_sell_cycle_count` int(0) NOT NULL COMMENT '限购期内允许购买的数量',
|
||||
`limit_sell_type` tinyint(0) NOT NULL COMMENT '限购类型',
|
||||
`market_price` decimal(10, 2) NOT NULL COMMENT '市场价',
|
||||
`out_sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '内部编码',
|
||||
`props` json NOT NULL COMMENT '规格',
|
||||
`skus` json NOT NULL COMMENT 'SKU',
|
||||
`score_price` decimal(10, 2) NOT NULL COMMENT '兑换所需积分',
|
||||
`stock` int(0) NOT NULL COMMENT '库存',
|
||||
`create_at` datetime(0) NOT NULL COMMENT '创建日期',
|
||||
`store_id` int(0) NOT NULL COMMENT '所属商城',
|
||||
`status` int(0) UNSIGNED NULL DEFAULT 1 COMMENT '1:下架;20:审核中;30:复审中;99:上架',
|
||||
`view_count` int(0) NOT NULL DEFAULT 0 COMMENT '访问量',
|
||||
`sell_count` int(0) NULL DEFAULT 0 COMMENT '销量',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
|
||||
INSERT INTO `jfy_gift` VALUES (17, 'GIFT', 1, '[{\"name\": \"身份证\", \"field\": \"idcard\", \"required\": false}, {\"name\": \"留言2\", \"field\": \"text\", \"required\": false}]', '<p>礼品详情</p>', 0.00, '', '{\"list\": [{\"uid\": \"vc-upload-1629292486099-3\", \"url\": \"https://cdn.taobao.com/sULsYiwaOPjsKGoBXwKtuewPzACpBDfQ.jpg\", \"name\": \"O1CN01OH6PIP1Oc5ot06U17_!!922361725.jpg\", \"status\": \"done\"}, {\"uid\": \"vc-upload-1629292486099-4\", \"url\": \"https://cdn.taobao.com/lqLHDcrFTgNvlWyXfLYZwmsrODzIBtFH.jpg\", \"name\": \"O1CN018hBckI1Oc5ouc8ppl_!!922361725.jpg\", \"status\": \"done\"}, {\"uid\": \"vc-upload-1629292486099-5\", \"url\": \"https://cdn.taobao.com/pvqyutXckICmHhbPBQtrVLHuMlXuGxUg.jpg\", \"name\": \"O1CN0185Ubp91Oc5osQTTcc_!!922361725.jpg\", \"status\": \"done\"}]}', '支持个性定制的父亲节老师长辈的专属礼物', '[\"快递包邮\", \"同城配送\"]', 1, 0, 0, '[]', 0, 'day', 0, 1, 0.00, '259402', '[{\"name\": \"颜色\", \"values\": [\"红色\", \"蓝色\"]}]', '[{\"name\": \"red\", \"stock\": 10, \"gift_id\": 1, \"cost_price\": 80, \"score_price\": 188, \"market_price\": 388}, {\"name\": \"blue\", \"stock\": 100, \"gift_id\": 2, \"cost_price\": 81, \"score_price\": 200, \"market_price\": 288}]', 10.00, 0, '2021-08-18 21:26:13', 100004, 99, 0, 0);
|
||||
32
contrib/drivers/mariadb/testdata/issues/1401.sql
vendored
Normal file
32
contrib/drivers/mariadb/testdata/issues/1401.sql
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
-- ----------------------------
|
||||
-- Table structure for parcel_items
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `parcel_items`;
|
||||
CREATE TABLE `parcel_items` (
|
||||
`id` int(11) NOT NULL,
|
||||
`parcel_id` int(11) NULL DEFAULT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of parcel_items
|
||||
-- ----------------------------
|
||||
INSERT INTO `parcel_items` VALUES (1, 1, '新品');
|
||||
INSERT INTO `parcel_items` VALUES (2, 3, '新品2');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for parcels
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `parcels`;
|
||||
CREATE TABLE `parcels` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of parcels
|
||||
-- ----------------------------
|
||||
INSERT INTO `parcels` VALUES (1);
|
||||
INSERT INTO `parcels` VALUES (2);
|
||||
INSERT INTO `parcels` VALUES (3);
|
||||
30
contrib/drivers/mariadb/testdata/issues/1412.sql
vendored
Normal file
30
contrib/drivers/mariadb/testdata/issues/1412.sql
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
-- ----------------------------
|
||||
-- Table structure for items
|
||||
-- ----------------------------
|
||||
CREATE TABLE `items` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of items
|
||||
-- ----------------------------
|
||||
INSERT INTO `items` VALUES (1, '金秋产品1');
|
||||
INSERT INTO `items` VALUES (2, '金秋产品2');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for parcels
|
||||
-- ----------------------------
|
||||
CREATE TABLE `parcels` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`item_id` int(11) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of parcels
|
||||
-- ----------------------------
|
||||
INSERT INTO `parcels` VALUES (1, 1);
|
||||
INSERT INTO `parcels` VALUES (2, 2);
|
||||
INSERT INTO `parcels` VALUES (3, 0);
|
||||
9
contrib/drivers/mariadb/testdata/issues/2105.sql
vendored
Normal file
9
contrib/drivers/mariadb/testdata/issues/2105.sql
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
CREATE TABLE `issue2105` (
|
||||
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
|
||||
INSERT INTO `issue2105` VALUES ('1', NULL);
|
||||
INSERT INTO `issue2105` VALUES ('2', '[{\"Name\": \"任务类型\", \"Value\": \"高价值\"}, {\"Name\": \"优先级\", \"Value\": \"高\"}, {\"Name\": \"是否亮点功能\", \"Value\": \"是\"}]');
|
||||
47
contrib/drivers/mariadb/testdata/issues/2119.sql
vendored
Normal file
47
contrib/drivers/mariadb/testdata/issues/2119.sql
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_role
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_role`;
|
||||
CREATE TABLE `sys_role` (
|
||||
`id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '||s',
|
||||
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色名称||s,r',
|
||||
`code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色 code||s,r',
|
||||
`description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '描述信息|text',
|
||||
`weight` int(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序||r|min:0#发布状态不能小于 0',
|
||||
`status_id` int(0) UNSIGNED NOT NULL DEFAULT 1 COMMENT '发布状态|hasOne|f:status,fk:id',
|
||||
`created_at` datetime(0) NULL DEFAULT NULL,
|
||||
`updated_at` datetime(0) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `code`(`code`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1091 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色表' ROW_FORMAT = Compact;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sys_role
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_role` VALUES (1, '开发人员', 'developer', '123123', 900, 2, '2022-09-03 21:25:03', '2022-09-09 23:35:23');
|
||||
INSERT INTO `sys_role` VALUES (2, '管理员', 'admin', '', 800, 1, '2022-09-03 21:25:03', '2022-09-09 23:00:17');
|
||||
INSERT INTO `sys_role` VALUES (3, '运营', 'operator', '', 700, 1, '2022-09-03 21:25:03', '2022-09-03 21:25:03');
|
||||
INSERT INTO `sys_role` VALUES (4, '客服', 'service', '', 600, 1, '2022-09-03 21:25:03', '2022-09-03 21:25:03');
|
||||
INSERT INTO `sys_role` VALUES (5, '收银', 'account', '', 500, 1, '2022-09-03 21:25:03', '2022-09-03 21:25:03');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_status
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_status`;
|
||||
CREATE TABLE `sys_status` (
|
||||
`id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`en` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '英文名称',
|
||||
`cn` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '中文名称',
|
||||
`weight` int(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序权重',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '发布状态' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sys_status
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_status` VALUES (1, 'on line', '上线', 900);
|
||||
INSERT INTO `sys_status` VALUES (2, 'undecided', '未决定', 800);
|
||||
INSERT INTO `sys_status` VALUES (3, 'off line', '下线', 700);
|
||||
19
contrib/drivers/mariadb/testdata/issues/2439.sql
vendored
Normal file
19
contrib/drivers/mariadb/testdata/issues/2439.sql
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
CREATE TABLE `a` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (id) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
INSERT INTO `a` (`id`) VALUES ('2');
|
||||
|
||||
CREATE TABLE `b` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL ,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
INSERT INTO `b` (`id`, `name`) VALUES ('2', 'a');
|
||||
INSERT INTO `b` (`id`, `name`) VALUES ('3', 'b');
|
||||
|
||||
CREATE TABLE `c` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB;
|
||||
INSERT INTO `c` (`id`) VALUES ('2');
|
||||
7
contrib/drivers/mariadb/testdata/issues/2643.sql
vendored
Normal file
7
contrib/drivers/mariadb/testdata/issues/2643.sql
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE TABLE `issue2643` (
|
||||
`id` INT(10) NULL DEFAULT NULL,
|
||||
`name` VARCHAR(50) NULL DEFAULT NULL,
|
||||
`value` INT(10) NULL DEFAULT NULL,
|
||||
`dept` VARCHAR(50) NULL DEFAULT NULL
|
||||
)
|
||||
ENGINE=InnoDB
|
||||
10
contrib/drivers/mariadb/testdata/issues/3086.sql
vendored
Normal file
10
contrib/drivers/mariadb/testdata/issues/3086.sql
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TABLE `issue3086_user`
|
||||
(
|
||||
`id` int(10) unsigned NOT NULL COMMENT 'User ID',
|
||||
`passport` varchar(45) NOT NULL COMMENT 'User Passport',
|
||||
`password` varchar(45) DEFAULT NULL COMMENT 'User Password',
|
||||
`nickname` varchar(45) DEFAULT NULL COMMENT 'User Nickname',
|
||||
`create_at` datetime DEFAULT NULL COMMENT 'Created Time',
|
||||
`update_at` datetime DEFAULT NULL COMMENT 'Updated Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
14
contrib/drivers/mariadb/testdata/issues/3218.sql
vendored
Normal file
14
contrib/drivers/mariadb/testdata/issues/3218.sql
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
CREATE TABLE `issue3218_sys_config` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '配置名称',
|
||||
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '配置值',
|
||||
`created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `name`(`name`(191)) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sys_config
|
||||
-- ----------------------------
|
||||
INSERT INTO `issue3218_sys_config` VALUES (49, 'site', '{\"banned_ip\":\"22\",\"filings\":\"2222\",\"fixed_page\":\"\",\"site_name\":\"22\",\"version\":\"22\"}', '2023-12-19 14:08:25', '2023-12-19 14:08:25');
|
||||
5
contrib/drivers/mariadb/testdata/issues/3626.sql
vendored
Normal file
5
contrib/drivers/mariadb/testdata/issues/3626.sql
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE TABLE `issue3626` (
|
||||
id int(11) NOT NULL,
|
||||
name varchar(45) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
8
contrib/drivers/mariadb/testdata/issues/3754.sql
vendored
Normal file
8
contrib/drivers/mariadb/testdata/issues/3754.sql
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE `issue3754` (
|
||||
id int(11) NOT NULL,
|
||||
name varchar(45) DEFAULT NULL,
|
||||
create_at datetime(0) DEFAULT NULL,
|
||||
update_at datetime(0) DEFAULT NULL,
|
||||
delete_at datetime(0) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
9
contrib/drivers/mariadb/testdata/issues/3915.sql
vendored
Normal file
9
contrib/drivers/mariadb/testdata/issues/3915.sql
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
CREATE TABLE `issue3915` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'user id',
|
||||
`a` float DEFAULT NULL COMMENT 'user name',
|
||||
`b` float DEFAULT NULL COMMENT 'user status',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
INSERT INTO `issue3915` (`id`,`a`,`b`) VALUES (1,1,2);
|
||||
INSERT INTO `issue3915` (`id`,`a`,`b`) VALUES (2,5,4);
|
||||
8
contrib/drivers/mariadb/testdata/issues/4034.sql
vendored
Normal file
8
contrib/drivers/mariadb/testdata/issues/4034.sql
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE issue4034 (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
passport VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
nickname VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
10
contrib/drivers/mariadb/testdata/issues/4086.sql
vendored
Normal file
10
contrib/drivers/mariadb/testdata/issues/4086.sql
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
DROP TABLE IF EXISTS `issue4086`;
|
||||
CREATE TABLE `issue4086` (
|
||||
`proxy_id` bigint NOT NULL,
|
||||
`recommend_ids` json DEFAULT NULL,
|
||||
`photos` json DEFAULT NULL,
|
||||
PRIMARY KEY (`proxy_id`)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
INSERT INTO `issue4086` (`proxy_id`, `recommend_ids`, `photos`) VALUES (1, '[584, 585]', 'null');
|
||||
INSERT INTO `issue4086` (`proxy_id`, `recommend_ids`, `photos`) VALUES (2, '[]', NULL);
|
||||
Reference in New Issue
Block a user