Skip to content

Commit 416f314

Browse files
authored
fix(contrib/drivers/pgsql): Merge duplicated fields, especially for key constraints. (gogf#4465)
pgsql 执行TableFields 或者字段信息时需要合并key信息
1 parent 98f0c36 commit 416f314

3 files changed

Lines changed: 101 additions & 9 deletions

File tree

contrib/drivers/pgsql/pgsql_table_fields.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,21 @@ func (d *Driver) TableFields(ctx context.Context, table string, schema ...string
5757
}
5858
fields = make(map[string]*gdb.TableField)
5959
var (
60-
index = 0
61-
name string
62-
ok bool
60+
index = 0
61+
name string
62+
ok bool
63+
existingField *gdb.TableField
6364
)
6465
for _, m := range result {
6566
name = m["field"].String()
66-
// Filter duplicated fields.
67-
if _, ok = fields[name]; ok {
67+
// Merge duplicated fields, especially for key constraints.
68+
// Priority: pri > uni > others
69+
if existingField, ok = fields[name]; ok {
70+
currentKey := m["key"].String()
71+
// Merge key information with priority: pri > uni
72+
if currentKey == "pri" || (currentKey == "uni" && existingField.Key != "pri") {
73+
existingField.Key = currentKey
74+
}
6875
continue
6976
}
7077
fields[name] = &gdb.TableField{

contrib/drivers/pgsql/pgsql_z_unit_db_test.go

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ func Test_DB_TableFields(t *testing.T) {
302302
defer dropTable(table)
303303

304304
var expect = map[string][]any{
305-
//[]string: Index Type Null Key Default Comment
306-
//id is bigserial so the default is a pgsql function
305+
// []string: Index Type Null Key Default Comment
306+
// id is bigserial so the default is a pgsql function
307307
"id": {0, "int8", false, "pri", fmt.Sprintf("nextval('%s_id_seq'::regclass)", table), ""},
308308
"passport": {1, "varchar", false, "", nil, ""},
309309
"password": {2, "varchar", false, "", nil, ""},
@@ -384,6 +384,91 @@ int_col INT);`
384384

385385
}
386386

387+
func Test_DB_TableFields_DuplicateConstraints(t *testing.T) {
388+
// Test for the fix of duplicate field results with multiple constraints
389+
// This test verifies that when a field has multiple constraints (e.g., both primary key and unique),
390+
// the TableFields method correctly merges the results with proper priority (pri > uni > others)
391+
gtest.C(t, func(t *gtest.T) {
392+
tableName := "test_multi_constraint"
393+
createSql := fmt.Sprintf(`
394+
CREATE TABLE %s (
395+
id bigserial NOT NULL PRIMARY KEY,
396+
email varchar(100) NOT NULL UNIQUE,
397+
username varchar(50) NOT NULL,
398+
status int NOT NULL DEFAULT 1
399+
)`, tableName)
400+
401+
_, err := db.Exec(ctx, createSql)
402+
t.AssertNil(err)
403+
defer dropTable(tableName)
404+
405+
// Get table fields
406+
fields, err := db.TableFields(ctx, tableName)
407+
t.AssertNil(err)
408+
409+
// Verify id field has primary key constraint
410+
t.AssertNE(fields["id"], nil)
411+
t.Assert(fields["id"].Key, "pri")
412+
t.Assert(fields["id"].Name, "id")
413+
t.Assert(fields["id"].Type, "int8")
414+
415+
// Verify email field has unique constraint
416+
t.AssertNE(fields["email"], nil)
417+
t.Assert(fields["email"].Key, "uni")
418+
t.Assert(fields["email"].Name, "email")
419+
t.Assert(fields["email"].Type, "varchar")
420+
421+
// Verify username field has no constraint
422+
t.AssertNE(fields["username"], nil)
423+
t.Assert(fields["username"].Key, "")
424+
t.Assert(fields["username"].Name, "username")
425+
426+
// Verify status field has no constraint and has default value
427+
t.AssertNE(fields["status"], nil)
428+
t.Assert(fields["status"].Key, "")
429+
t.Assert(fields["status"].Name, "status")
430+
t.Assert(fields["status"].Default, 1)
431+
432+
// Verify field count is correct (no duplicates)
433+
t.Assert(len(fields), 4)
434+
})
435+
436+
// Test table with composite constraints
437+
gtest.C(t, func(t *gtest.T) {
438+
tableName := "test_composite_constraint"
439+
createSql := fmt.Sprintf(`
440+
CREATE TABLE %s (
441+
user_id bigint NOT NULL,
442+
project_id bigint NOT NULL,
443+
role varchar(50) NOT NULL,
444+
PRIMARY KEY (user_id, project_id)
445+
)`, tableName)
446+
447+
_, err := db.Exec(ctx, createSql)
448+
t.AssertNil(err)
449+
defer dropTable(tableName)
450+
451+
// Get table fields
452+
fields, err := db.TableFields(ctx, tableName)
453+
t.AssertNil(err)
454+
455+
// In PostgreSQL, composite primary keys may appear in query results
456+
// The first field in the composite key should be marked as 'pri'
457+
t.AssertNE(fields["user_id"], nil)
458+
t.Assert(fields["user_id"].Name, "user_id")
459+
460+
t.AssertNE(fields["project_id"], nil)
461+
t.Assert(fields["project_id"].Name, "project_id")
462+
463+
t.AssertNE(fields["role"], nil)
464+
t.Assert(fields["role"].Name, "role")
465+
t.Assert(fields["role"].Key, "")
466+
467+
// Verify field count is correct (no duplicates)
468+
t.Assert(len(fields), 3)
469+
})
470+
}
471+
387472
func Test_DB_InsertIgnore(t *testing.T) {
388473
table := createTable()
389474
defer dropTable(table)

contrib/drivers/pgsql/pgsql_z_unit_init_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ func init() {
3737
Link: `pgsql:postgres:12345678@tcp(127.0.0.1:5432)`,
3838
}
3939

40-
//pgsql only permit to connect to the designation database.
41-
//so you need to create the pgsql database before you use orm
40+
// pgsql only permit to connect to the designation database.
41+
// so you need to create the pgsql database before you use orm
4242
gdb.AddConfigNode(gdb.DefaultGroupName, configNode)
4343
if r, err := gdb.New(configNode); err != nil {
4444
gtest.Fatal(err)

0 commit comments

Comments
 (0)