diff --git a/contrib/drivers/mariadb/mariadb_z_unit_feature_duplicate_test.go b/contrib/drivers/mariadb/mariadb_z_unit_feature_duplicate_test.go new file mode 100644 index 000000000..79b444d5b --- /dev/null +++ b/contrib/drivers/mariadb/mariadb_z_unit_feature_duplicate_test.go @@ -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) + }) +} diff --git a/contrib/drivers/mariadb/mariadb_z_unit_feature_json_test.go b/contrib/drivers/mariadb/mariadb_z_unit_feature_json_test.go new file mode 100644 index 000000000..e39ce260b --- /dev/null +++ b/contrib/drivers/mariadb/mariadb_z_unit_feature_json_test.go @@ -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) + }) +} diff --git a/contrib/drivers/mariadb/mariadb_z_unit_feature_lock_test.go b/contrib/drivers/mariadb/mariadb_z_unit_feature_lock_test.go new file mode 100644 index 000000000..f9109bcc1 --- /dev/null +++ b/contrib/drivers/mariadb/mariadb_z_unit_feature_lock_test.go @@ -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?", 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) + }) +} diff --git a/contrib/drivers/mariadb/mariadb_z_unit_feature_master_slave_test.go b/contrib/drivers/mariadb/mariadb_z_unit_feature_master_slave_test.go new file mode 100644 index 000000000..08f41559c --- /dev/null +++ b/contrib/drivers/mariadb/mariadb_z_unit_feature_master_slave_test.go @@ -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) + }) +} diff --git a/contrib/drivers/mariadb/mariadb_z_unit_feature_metadata_test.go b/contrib/drivers/mariadb/mariadb_z_unit_feature_metadata_test.go new file mode 100644 index 000000000..2d6a020cb --- /dev/null +++ b/contrib/drivers/mariadb/mariadb_z_unit_feature_metadata_test.go @@ -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`") + }) +} diff --git a/contrib/drivers/mariadb/mariadb_z_unit_feature_partition_test.go b/contrib/drivers/mariadb/mariadb_z_unit_feature_partition_test.go new file mode 100644 index 000000000..488491af1 --- /dev/null +++ b/contrib/drivers/mariadb/mariadb_z_unit_feature_partition_test.go @@ -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 + }) +} diff --git a/contrib/drivers/mariadb/mariadb_z_unit_issue_test.go b/contrib/drivers/mariadb/mariadb_z_unit_issue_test.go new file mode 100644 index 000000000..2f8134aed --- /dev/null +++ b/contrib/drivers/mariadb/mariadb_z_unit_issue_test.go @@ -0,0 +1,2075 @@ +// 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" + "encoding/json" + "fmt" + "sync" + "testing" + "time" + + "github.com/gogf/gf/v2/container/gvar" + "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/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gmeta" + "github.com/gogf/gf/v2/util/guid" +) + +// https://github.com/gogf/gf/issues/1380 +func Test_Issue1380(t *testing.T) { + type GiftImage struct { + Uid string `json:"uid"` + Url string `json:"url"` + Status string `json:"status"` + Name string `json:"name"` + } + + type GiftComment struct { + Name string `json:"name"` + Field string `json:"field"` + Required bool `json:"required"` + } + + type Prop struct { + Name string `json:"name"` + Values []string `json:"values"` + } + + type Sku struct { + GiftId int64 `json:"gift_id"` + Name string `json:"name"` + ScorePrice int `json:"score_price"` + MarketPrice int `json:"market_price"` + CostPrice int `json:"cost_price"` + Stock int `json:"stock"` + } + + type Covers struct { + List []GiftImage `json:"list"` + } + + type GiftEntity struct { + Id int64 `json:"id"` + StoreId int64 `json:"store_id"` + GiftType int `json:"gift_type"` + GiftName string `json:"gift_name"` + Description string `json:"description"` + Covers Covers `json:"covers"` + Cover string `json:"cover"` + GiftCategoryId []int64 `json:"gift_category_id"` + HasProps bool `json:"has_props"` + OutSn string `json:"out_sn"` + IsLimitSell bool `json:"is_limit_sell"` + LimitSellType int `json:"limit_sell_type"` + LimitSellCycle string `json:"limit_sell_cycle"` + LimitSellCycleCount int `json:"limit_sell_cycle_count"` + LimitSellCustom bool `json:"limit_sell_custom"` // 只允许特定会员兑换 + LimitCustomerTags []int64 `json:"limit_customer_tags"` // 允许兑换的成员 + ScorePrice int `json:"score_price"` + MarketPrice float64 `json:"market_price"` + CostPrice int `json:"cost_price"` + Stock int `json:"stock"` + Props []Prop `json:"props"` + Skus []Sku `json:"skus"` + ExpressType []string `json:"express_type"` + Comments []GiftComment `json:"comments"` + Content string `json:"content"` + AtLeastRechargeCount int `json:"at_least_recharge_count"` + Status int `json:"status"` + } + + type User struct { + Id int + Passport string + } + + table := "jfy_gift" + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `1380.sql`), ";") + for _, v := range array { + if _, err := db.Exec(ctx, v); err != nil { + gtest.Error(err) + } + } + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + var ( + entity = new(GiftEntity) + err = db.Model(table).Where("id", 17).Scan(entity) + ) + t.AssertNil(err) + t.Assert(len(entity.Skus), 2) + + t.Assert(entity.Skus[0].Name, "red") + t.Assert(entity.Skus[0].Stock, 10) + t.Assert(entity.Skus[0].GiftId, 1) + t.Assert(entity.Skus[0].CostPrice, 80) + t.Assert(entity.Skus[0].ScorePrice, 188) + t.Assert(entity.Skus[0].MarketPrice, 388) + + t.Assert(entity.Skus[1].Name, "blue") + t.Assert(entity.Skus[1].Stock, 100) + t.Assert(entity.Skus[1].GiftId, 2) + t.Assert(entity.Skus[1].CostPrice, 81) + t.Assert(entity.Skus[1].ScorePrice, 200) + t.Assert(entity.Skus[1].MarketPrice, 288) + + t.Assert(entity.Id, 17) + t.Assert(entity.StoreId, 100004) + t.Assert(entity.GiftType, 1) + t.Assert(entity.GiftName, "GIFT") + t.Assert(entity.Description, "支持个性定制的父亲节老师长辈的专属礼物") + t.Assert(len(entity.Covers.List), 3) + t.Assert(entity.OutSn, "259402") + t.Assert(entity.LimitCustomerTags, "[]") + t.Assert(entity.ScorePrice, 10) + t.Assert(len(entity.Props), 1) + t.Assert(len(entity.Comments), 2) + t.Assert(entity.Status, 99) + t.Assert(entity.Content, `

礼品详情

`) + }) +} + +// https://github.com/gogf/gf/issues/1934 +func Test_Issue1934(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + one, err := db.Model(table).Where(" id ", 1).One() + t.AssertNil(err) + t.Assert(one["id"], 1) + }) +} + +// https://github.com/gogf/gf/issues/1570 +func Test_Issue1570(t *testing.T) { + var ( + tableUser = "user_" + gtime.TimestampMicroStr() + tableUserDetail = "user_detail_" + gtime.TimestampMicroStr() + tableUserScores = "user_scores_" + gtime.TimestampMicroStr() + ) + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(ctx, fmt.Sprintf(` +CREATE TABLE %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User *EntityUser + UserDetail *EntityUserDetail + UserScores []*EntityUserScores + } + + // Initialize the data. + gtest.C(t, func(t *gtest.T) { + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(ctx, tableUser, g.Map{ + "uid": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + t.AssertNil(err) + // Detail. + _, err = db.Insert(ctx, tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + t.AssertNil(err) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(ctx, tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + t.AssertNil(err) + } + } + }) + + // Result ScanList with struct elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []Entity + // User + err := db.Model(tableUser). + Where("uid", g.Slice{3, 4}). + Fields("uid"). + Order("uid asc"). + ScanList(&users, "User") + t.AssertNil(err) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, ""}) + t.Assert(users[1].User, &EntityUser{4, ""}) + // Detail + err = db.Model(tableUserDetail). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("uid asc"). + ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + err = db.Model(tableUserScores). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("id asc"). + ScanList(&users, "UserScores", "User", "uid:Uid") + t.AssertNil(err) + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) +} + +// https://github.com/gogf/gf/issues/1401 +func Test_Issue1401(t *testing.T) { + var ( + table1 = "parcels" + table2 = "parcel_items" + ) + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `1401.sql`), ";") + for _, v := range array { + if _, err := db.Exec(ctx, v); err != nil { + gtest.Error(err) + } + } + defer dropTable(table1) + defer dropTable(table2) + + gtest.C(t, func(t *gtest.T) { + type NItem struct { + Id int `json:"id"` + ParcelId int `json:"parcel_id"` + } + + type ParcelItem struct { + gmeta.Meta `orm:"table:parcel_items"` + NItem + } + + type ParcelRsp struct { + gmeta.Meta `orm:"table:parcels"` + Id int `json:"id"` + Items []*ParcelItem `json:"items" orm:"with:parcel_id=Id"` + } + + parcelDetail := &ParcelRsp{} + err := db.Model(table1).With(parcelDetail.Items).Where("id", 3).Scan(&parcelDetail) + t.AssertNil(err) + t.Assert(parcelDetail.Id, 3) + t.Assert(len(parcelDetail.Items), 1) + t.Assert(parcelDetail.Items[0].Id, 2) + t.Assert(parcelDetail.Items[0].ParcelId, 3) + }) +} + +// https://github.com/gogf/gf/issues/1412 +func Test_Issue1412(t *testing.T) { + var ( + table1 = "parcels" + table2 = "items" + ) + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `1412.sql`), ";") + for _, v := range array { + if _, err := db.Exec(ctx, v); err != nil { + gtest.Error(err) + } + } + defer dropTable(table1) + defer dropTable(table2) + + gtest.C(t, func(t *gtest.T) { + type Items struct { + gmeta.Meta `orm:"table:items"` + Id int `json:"id"` + Name string `json:"name"` + } + + type ParcelRsp struct { + gmeta.Meta `orm:"table:parcels"` + Id int `json:"id"` + ItemId int `json:"item_id"` + Items Items `json:"items" orm:"with:Id=ItemId"` + } + + entity := &ParcelRsp{} + err := db.Model("parcels").With(Items{}).Where("id=3").Scan(&entity) + t.AssertNil(err) + t.Assert(entity.Id, 3) + t.Assert(entity.ItemId, 0) + t.Assert(entity.Items.Id, 0) + t.Assert(entity.Items.Name, "") + }) + + gtest.C(t, func(t *gtest.T) { + type Items struct { + gmeta.Meta `orm:"table:items"` + Id int `json:"id"` + Name string `json:"name"` + } + + type ParcelRsp struct { + gmeta.Meta `orm:"table:parcels"` + Id int `json:"id"` + ItemId int `json:"item_id"` + Items Items `json:"items" orm:"with:Id=ItemId"` + } + + entity := &ParcelRsp{} + err := db.Model("parcels").With(Items{}).Where("id=30000").Scan(&entity) + t.AssertNE(err, nil) + t.Assert(entity.Id, 0) + t.Assert(entity.ItemId, 0) + t.Assert(entity.Items.Id, 0) + t.Assert(entity.Items.Name, "") + }) +} + +// https://github.com/gogf/gf/issues/1002 +func Test_Issue1002(t *testing.T) { + table := createTable() + defer dropTable(table) + + result, err := db.Model(table).Data(g.Map{ + "id": 1, + "passport": "port_1", + "password": "pass_1", + "nickname": "name_2", + "create_time": "2020-10-27 19:03:33", + }).Insert() + gtest.AssertNil(err) + n, _ := result.RowsAffected() + gtest.Assert(n, 1) + + // where + string. + gtest.C(t, func(t *gtest.T) { + v, err := db.Model(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").Value() + t.AssertNil(err) + t.Assert(v.Int(), 1) + }) + gtest.C(t, func(t *gtest.T) { + v, err := db.Model(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").Value() + t.AssertNil(err) + t.Assert(v.Int(), 1) + }) + // where + string arguments. + gtest.C(t, func(t *gtest.T) { + v, err := db.Model(table).Fields("id").Where("create_time>? and create_time? and create_time? and create_time? and create_time? and create_time? and create_time b").All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 2) + + all, err = db.Model(table).Where(gdb.Raw("a > b")).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 2) + + all, err = db.Model(table).WhereGT("a", gdb.Raw("`b`")).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 2) + }) +} + +type RoleBase struct { + gmeta.Meta `orm:"table:sys_role"` + Name string `json:"name" description:"角色名称" ` + Code string `json:"code" description:"角色 code" ` + Description string `json:"description" description:"描述信息" ` + Weight int `json:"weight" description:"排序" ` + StatusId int `json:"statusId" description:"发布状态" ` + CreatedAt *gtime.Time `json:"createdAt" description:"" ` + UpdatedAt *gtime.Time `json:"updatedAt" description:"" ` +} + +type Role struct { + gmeta.Meta `orm:"table:sys_role"` + RoleBase + Id uint `json:"id" description:""` + Status *Status `json:"status" description:"发布状态" orm:"with:id=status_id" ` +} + +type StatusBase struct { + gmeta.Meta `orm:"table:sys_status"` + En string `json:"en" description:"英文名称" ` + Cn string `json:"cn" description:"中文名称" ` + Weight int `json:"weight" description:"排序权重" ` +} + +type Status struct { + gmeta.Meta `orm:"table:sys_status"` + StatusBase + Id uint `json:"id" description:""` +} + +// https://github.com/gogf/gf/issues/2119 +func Test_Issue2119(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + tables := []string{ + "sys_role", + "sys_status", + } + + defer dropTable(tables[0]) + defer dropTable(tables[1]) + _ = tables + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `2119.sql`), ";") + for _, v := range array { + _, err := db.Exec(ctx, v) + t.AssertNil(err) + } + roles := make([]*Role, 0) + err := db.Ctx(context.Background()).Model(&Role{}).WithAll().Scan(&roles) + t.AssertNil(err) + expectStatus := []*Status{ + { + StatusBase: StatusBase{ + En: "undecided", + Cn: "未决定", + Weight: 800, + }, + Id: 2, + }, + { + StatusBase: StatusBase{ + En: "on line", + Cn: "上线", + Weight: 900, + }, + Id: 1, + }, + { + StatusBase: StatusBase{ + En: "on line", + Cn: "上线", + Weight: 900, + }, + Id: 1, + }, + { + StatusBase: StatusBase{ + En: "on line", + Cn: "上线", + Weight: 900, + }, + Id: 1, + }, + { + StatusBase: StatusBase{ + En: "on line", + Cn: "上线", + Weight: 900, + }, + Id: 1, + }, + } + + for i := 0; i < len(roles); i++ { + t.Assert(roles[i].Status, expectStatus[i]) + } + }) +} + +// https://github.com/gogf/gf/issues/4034 +func Test_Issue4034(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := "issue4034" + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `4034.sql`), ";") + for _, v := range array { + _, err := db.Exec(ctx, v) + t.AssertNil(err) + } + defer dropTable(table) + + err := issue4034SaveDeviceAndToken(ctx, table) + t.AssertNil(err) + }) +} + +func issue4034SaveDeviceAndToken(ctx context.Context, table string) error { + return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + if err := issue4034SaveAppDevice(ctx, table, tx); err != nil { + return err + } + return nil + }) +} + +func issue4034SaveAppDevice(ctx context.Context, table string, tx gdb.TX) error { + _, err := db.Model(table).Safe().Ctx(ctx).TX(tx).Data(g.Map{ + "passport": "111", + "password": "222", + "nickname": "333", + }).Save() + return err +} + +// https://github.com/gogf/gf/issues/4086 +func Test_Issue4086(t *testing.T) { + table := "issue4086" + defer dropTable(table) + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `4086.sql`), ";") + for _, v := range array { + _, err := db.Exec(ctx, v) + gtest.AssertNil(err) + } + + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []int64 `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) + + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []float32 `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) + + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []string `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) + + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []any `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos string `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: "null", + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: "", + }, + }) + }) + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds string `json:"recommendIds" orm:"recommend_ids"` + Photos json.RawMessage `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: "[584, 585]", + Photos: json.RawMessage("null"), + }, + { + ProxyId: 2, + RecommendIds: "[]", + Photos: json.RawMessage("null"), + }, + }) + }) +} + +// https://github.com/gogf/gf/issues/4500 +// Raw() Count ignores Where condition +func Test_Issue4500(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // Test 1: Raw SQL with WHERE + external Where condition + Count + // This tests that formatCondition correctly uses AND when Raw SQL already has WHERE + gtest.C(t, func(t *gtest.T) { + count, err := db. + Raw(fmt.Sprintf("SELECT * FROM %s WHERE id IN (?)", table), g.Slice{1, 5, 7, 8, 9, 10}). + WhereLT("id", 8). + Count() + t.AssertNil(err) + // Raw SQL: id IN (1,5,7,8,9,10) = 6 records + // Where: id < 8 filters to {1,5,7} = 3 records + t.Assert(count, 3) + }) + + // Test 2: Raw SQL without WHERE + external Where condition + Count + // This tests that formatCondition correctly adds WHERE + gtest.C(t, func(t *gtest.T) { + count, err := db. + Raw(fmt.Sprintf("SELECT * FROM %s", table)). + WhereLT("id", 5). + Count() + t.AssertNil(err) + // Raw SQL: all 10 records + // Where: id < 5 = {1,2,3,4} = 4 records + t.Assert(count, 4) + }) + + // Test 3: Raw + Where + ScanAndCount + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + Passport string + } + var users []User + var total int + err := db. + Raw(fmt.Sprintf("SELECT * FROM %s WHERE id IN (?)", table), g.Slice{1, 5, 7, 8, 9, 10}). + WhereLT("id", 8). + ScanAndCount(&users, &total, false) + t.AssertNil(err) + // Both scan result and count should respect Where condition + t.Assert(len(users), 3) + t.Assert(total, 3) + }) + + // Test 4: Raw + multiple Where conditions + Count + gtest.C(t, func(t *gtest.T) { + count, err := db. + Raw(fmt.Sprintf("SELECT * FROM %s WHERE id > ?", table), 0). + WhereLT("id", 5). + WhereGTE("id", 2). + Count() + t.AssertNil(err) + // Raw: id > 0 (all 10 records) + // Where: id < 5 AND id >= 2 = {2, 3, 4} = 3 records + t.Assert(count, 3) + }) + + // Test 5: Raw SQL with no external Where + Count (baseline test) + gtest.C(t, func(t *gtest.T) { + count, err := db. + Raw(fmt.Sprintf("SELECT * FROM %s WHERE id IN (?)", table), g.Slice{1, 2, 3}). + Count() + t.AssertNil(err) + // Should count 3 records + t.Assert(count, 3) + }) + + // Test 6: Verify All() still works correctly with Raw + Where + gtest.C(t, func(t *gtest.T) { + all, err := db. + Raw(fmt.Sprintf("SELECT * FROM %s WHERE id IN (?)", table), g.Slice{1, 5, 7, 8, 9, 10}). + WhereLT("id", 8). + All() + t.AssertNil(err) + t.Assert(len(all), 3) + }) +} + +// https://github.com/gogf/gf/issues/4697 +func Test_Issue4697(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + // Fields("") should be treated as Fields() and select all fields + result, err := db.Model(table).Fields("").Limit(1).All() + t.AssertNil(err) + t.AssertGT(len(result), 0) + // Should have all fields (id, passport, password, nickname, create_time, create_date) + t.Assert(len(result[0]), 6) + }) + + gtest.C(t, func(t *gtest.T) { + // Fields("", "id") should ignore empty string and only select "id" + result, err := db.Model(table).Fields("", "id").Limit(1).All() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(len(result[0]), 1) + t.AssertNE(result[0]["id"], nil) + }) + + gtest.C(t, func(t *gtest.T) { + // Fields("id", "", "nickname") should ignore empty string + result, err := db.Model(table).Fields("id", "", "nickname").Limit(1).All() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(len(result[0]), 2) + t.AssertNE(result[0]["id"], nil) + t.AssertNE(result[0]["nickname"], nil) + }) +} + +// https://github.com/gogf/gf/issues/4698 +func Test_Issue4698(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // Test 1: AllAndCount with multiple fields should generate valid COUNT SQL + gtest.C(t, func(t *gtest.T) { + result, count, err := db.Model(table).Fields("id, nickname").AllAndCount(true) + t.AssertNil(err) + t.Assert(count, TableSize) + t.Assert(len(result), TableSize) + t.AssertNE(result[0]["id"], nil) + t.AssertNE(result[0]["nickname"], nil) + t.Assert(result[0]["passport"], nil) + }) + + // Test 2: AllAndCount(false) with multiple fields + gtest.C(t, func(t *gtest.T) { + result, count, err := db.Model(table).Fields("id, nickname").AllAndCount(false) + t.AssertNil(err) + t.Assert(count, TableSize) + t.Assert(len(result), TableSize) + }) + + // Test 3: ScanAndCount with multiple fields + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + Nickname string + } + var users []User + var total int + err := db.Model(table).Fields("id, nickname").ScanAndCount(&users, &total, true) + t.AssertNil(err) + t.Assert(total, TableSize) + t.Assert(len(users), TableSize) + t.AssertGT(users[0].Id, 0) + t.AssertNE(users[0].Nickname, "") + }) + + // Test 4: AllAndCount with single field and useFieldForCount=true + gtest.C(t, func(t *gtest.T) { + result, count, err := db.Model(table).Fields("id").AllAndCount(true) + t.AssertNil(err) + t.Assert(count, TableSize) + t.Assert(len(result), TableSize) + t.Assert(len(result[0]), 1) + }) + + // Test 5: AllAndCount with Where condition + gtest.C(t, func(t *gtest.T) { + result, count, err := db.Model(table).Fields("id, nickname").Where("id礼品详情

', 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); diff --git a/contrib/drivers/mariadb/testdata/issues/1401.sql b/contrib/drivers/mariadb/testdata/issues/1401.sql new file mode 100644 index 000000000..b09087326 --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/1401.sql @@ -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); \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/1412.sql b/contrib/drivers/mariadb/testdata/issues/1412.sql new file mode 100644 index 000000000..453fca783 --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/1412.sql @@ -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); \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/2105.sql b/contrib/drivers/mariadb/testdata/issues/2105.sql new file mode 100644 index 000000000..55f09b8b0 --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/2105.sql @@ -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\": \"是\"}]'); diff --git a/contrib/drivers/mariadb/testdata/issues/2119.sql b/contrib/drivers/mariadb/testdata/issues/2119.sql new file mode 100644 index 000000000..89da7d17a --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/2119.sql @@ -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); \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/2439.sql b/contrib/drivers/mariadb/testdata/issues/2439.sql new file mode 100644 index 000000000..0949263cd --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/2439.sql @@ -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'); \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/2643.sql b/contrib/drivers/mariadb/testdata/issues/2643.sql new file mode 100644 index 000000000..e145460a1 --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/2643.sql @@ -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 \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/3086.sql b/contrib/drivers/mariadb/testdata/issues/3086.sql new file mode 100644 index 000000000..329ca847c --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/3086.sql @@ -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; diff --git a/contrib/drivers/mariadb/testdata/issues/3218.sql b/contrib/drivers/mariadb/testdata/issues/3218.sql new file mode 100644 index 000000000..93b5f9dac --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/3218.sql @@ -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'); \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/3626.sql b/contrib/drivers/mariadb/testdata/issues/3626.sql new file mode 100644 index 000000000..e4ee53023 --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/3626.sql @@ -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; \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/3754.sql b/contrib/drivers/mariadb/testdata/issues/3754.sql new file mode 100644 index 000000000..e6cdac030 --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/3754.sql @@ -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; \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/3915.sql b/contrib/drivers/mariadb/testdata/issues/3915.sql new file mode 100644 index 000000000..6fa6b86c8 --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/3915.sql @@ -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); \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/4034.sql b/contrib/drivers/mariadb/testdata/issues/4034.sql new file mode 100644 index 000000000..abb99cedc --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/4034.sql @@ -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 +); \ No newline at end of file diff --git a/contrib/drivers/mariadb/testdata/issues/4086.sql b/contrib/drivers/mariadb/testdata/issues/4086.sql new file mode 100644 index 000000000..5e7ba66e1 --- /dev/null +++ b/contrib/drivers/mariadb/testdata/issues/4086.sql @@ -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); \ No newline at end of file