From 1878202625ff3a237d05652c3df29e8da61714f2 Mon Sep 17 00:00:00 2001 From: Jack Ling <34231795+lingcoder@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:38:43 +0800 Subject: [PATCH] test(contrib/drivers/mariadb): add layer 3 features and issue regression tests (#4724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 --- .../mariadb_z_unit_feature_duplicate_test.go | 321 +++ .../mariadb_z_unit_feature_json_test.go | 394 ++++ .../mariadb_z_unit_feature_lock_test.go | 228 ++ ...ariadb_z_unit_feature_master_slave_test.go | 324 +++ .../mariadb_z_unit_feature_metadata_test.go | 115 + .../mariadb_z_unit_feature_partition_test.go | 364 +++ .../mariadb/mariadb_z_unit_issue_test.go | 2075 +++++++++++++++++ .../drivers/mariadb/testdata/issues/1380.sql | 35 + .../drivers/mariadb/testdata/issues/1401.sql | 32 + .../drivers/mariadb/testdata/issues/1412.sql | 30 + .../drivers/mariadb/testdata/issues/2105.sql | 9 + .../drivers/mariadb/testdata/issues/2119.sql | 47 + .../drivers/mariadb/testdata/issues/2439.sql | 19 + .../drivers/mariadb/testdata/issues/2643.sql | 7 + .../drivers/mariadb/testdata/issues/3086.sql | 10 + .../drivers/mariadb/testdata/issues/3218.sql | 14 + .../drivers/mariadb/testdata/issues/3626.sql | 5 + .../drivers/mariadb/testdata/issues/3754.sql | 8 + .../drivers/mariadb/testdata/issues/3915.sql | 9 + .../drivers/mariadb/testdata/issues/4034.sql | 8 + .../drivers/mariadb/testdata/issues/4086.sql | 10 + 21 files changed, 4064 insertions(+) create mode 100644 contrib/drivers/mariadb/mariadb_z_unit_feature_duplicate_test.go create mode 100644 contrib/drivers/mariadb/mariadb_z_unit_feature_json_test.go create mode 100644 contrib/drivers/mariadb/mariadb_z_unit_feature_lock_test.go create mode 100644 contrib/drivers/mariadb/mariadb_z_unit_feature_master_slave_test.go create mode 100644 contrib/drivers/mariadb/mariadb_z_unit_feature_metadata_test.go create mode 100644 contrib/drivers/mariadb/mariadb_z_unit_feature_partition_test.go create mode 100644 contrib/drivers/mariadb/mariadb_z_unit_issue_test.go create mode 100644 contrib/drivers/mariadb/testdata/issues/1380.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/1401.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/1412.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/2105.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/2119.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/2439.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/2643.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/3086.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/3218.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/3626.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/3754.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/3915.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/4034.sql create mode 100644 contrib/drivers/mariadb/testdata/issues/4086.sql 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