Skip to content

Commit 2ccc587

Browse files
committed
fix(database): add schema support for Tables() and TableFields() across multiple drivers
- pgsql/gaussdb: use search_path for multi-schema support, add table_schema filter - dm: fix Tables() to use USER_TABLES/ALL_TABLES, fix TableFields() usedSchema - clickhouse: add database filter to TableFields() - gdb: add schema to cache key for proper isolation closes gogf#4495
1 parent 609f44c commit 2ccc587

16 files changed

Lines changed: 1290 additions & 59 deletions

contrib/drivers/clickhouse/clickhouse_table_fields.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ func (d *Driver) TableFields(ctx context.Context, table string, schema ...string
3131
return nil, err
3232
}
3333
var (
34+
// Filter by database to ensure consistency with Tables() method.
35+
// Use d.GetConfig().Name as database name, same as Tables().
3436
getColumnsSql = fmt.Sprintf(
35-
"select %s from `system`.columns c where `table` = '%s'",
36-
tableFieldsColumns, table,
37+
"select %s from `system`.columns c where `database` = '%s' and `table` = '%s'",
38+
tableFieldsColumns, d.GetConfig().Name, table,
3739
)
3840
)
3941
result, err = d.DoSelect(ctx, link, getColumnsSql)

contrib/drivers/clickhouse/clickhouse_z_unit_db_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,87 @@ func Test_DB_TableFields(t *testing.T) {
332332
gtest.AssertNQ(field, nil)
333333
})
334334
}
335+
336+
// https://github.com/gogf/gf/issues/4495
337+
// Test TableFields() correctly filters by database name.
338+
func Test_Issue4495_TableFields_DatabaseFilter(t *testing.T) {
339+
table := createInitTable("issue4495_test")
340+
defer dropTable(table)
341+
342+
gtest.C(t, func(t *gtest.T) {
343+
// TableFields should return fields only for tables in the configured database
344+
fields, err := db.TableFields(ctx, "issue4495_test")
345+
gtest.AssertNil(err)
346+
gtest.AssertEQ(len(fields), 5)
347+
348+
// Verify field names
349+
_, hasId := fields["id"]
350+
_, hasPassport := fields["passport"]
351+
_, hasPassword := fields["password"]
352+
_, hasNickname := fields["nickname"]
353+
_, hasCreateTime := fields["create_time"]
354+
gtest.AssertEQ(hasId, true)
355+
gtest.AssertEQ(hasPassport, true)
356+
gtest.AssertEQ(hasPassword, true)
357+
gtest.AssertEQ(hasNickname, true)
358+
gtest.AssertEQ(hasCreateTime, true)
359+
})
360+
}
361+
362+
// https://github.com/gogf/gf/issues/4495
363+
// Test Tables() returns tables from the configured database.
364+
func Test_Issue4495_Tables_DatabaseFilter(t *testing.T) {
365+
table1 := createTable("issue4495_t1")
366+
table2 := createTable("issue4495_t2")
367+
defer dropTable(table1)
368+
defer dropTable(table2)
369+
370+
gtest.C(t, func(t *gtest.T) {
371+
tables, err := db.Tables(ctx)
372+
gtest.AssertNil(err)
373+
374+
// Should contain our created tables
375+
found1 := false
376+
found2 := false
377+
for _, tbl := range tables {
378+
if tbl == "issue4495_t1" {
379+
found1 = true
380+
}
381+
if tbl == "issue4495_t2" {
382+
found2 = true
383+
}
384+
}
385+
gtest.AssertEQ(found1, true)
386+
gtest.AssertEQ(found2, true)
387+
})
388+
}
389+
390+
// https://github.com/gogf/gf/issues/4495
391+
// Test cache isolation - ensure cache keys include database name.
392+
func Test_Issue4495_CacheIsolation(t *testing.T) {
393+
table := createInitTable("issue4495_cache")
394+
defer dropTable(table)
395+
396+
gtest.C(t, func(t *gtest.T) {
397+
// First call should cache the result
398+
tables1, err := db.Tables(ctx)
399+
gtest.AssertNil(err)
400+
401+
// Second call should use cache (same database)
402+
tables2, err := db.Tables(ctx)
403+
gtest.AssertNil(err)
404+
405+
// Results should be consistent
406+
gtest.AssertEQ(len(tables1), len(tables2))
407+
408+
// Both should contain our table
409+
found := false
410+
for _, tbl := range tables1 {
411+
if tbl == "issue4495_cache" {
412+
found = true
413+
break
414+
}
415+
}
416+
gtest.AssertEQ(found, true)
417+
})
418+
}

contrib/drivers/dm/dm_table_fields.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ func (d *Driver) TableFields(
4343
if link, err = d.SlaveLink(usedSchema); err != nil {
4444
return nil, err
4545
}
46-
// The link has been distinguished and no longer needs to judge the owner
46+
// Use usedSchema for OWNER filter to be consistent with Tables()
4747
result, err = d.DoSelect(
4848
ctx, link,
4949
fmt.Sprintf(
5050
tableFieldsSqlTmp,
5151
escapeSingleQuote(strings.ToUpper(table)),
52-
escapeSingleQuote(strings.ToUpper(d.GetSchema())),
52+
escapeSingleQuote(strings.ToUpper(usedSchema)),
5353
),
5454
)
5555
if err != nil {
@@ -66,7 +66,7 @@ func (d *Driver) TableFields(
6666
if pkResult.IsEmpty() {
6767
pkResult, err = d.DoSelect(
6868
ctx, link,
69-
fmt.Sprintf(tableFieldsPkSqlDBATmp, escapeSingleQuote(strings.ToUpper(table)), escapeSingleQuote(strings.ToUpper(d.GetSchema()))),
69+
fmt.Sprintf(tableFieldsPkSqlDBATmp, escapeSingleQuote(strings.ToUpper(table)), escapeSingleQuote(strings.ToUpper(usedSchema))),
7070
)
7171
if err != nil {
7272
return nil, err

contrib/drivers/dm/dm_tables.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,52 @@ package dm
88

99
import (
1010
"context"
11+
"fmt"
12+
"strings"
1113

1214
"github.com/gogf/gf/v2/database/gdb"
15+
"github.com/gogf/gf/v2/util/gutil"
1316
)
1417

1518
const (
16-
tablesSqlTmp = `SELECT * FROM ALL_TABLES`
19+
// tablesSqlByUser returns tables owned by current user, similar to Oracle's USER_TABLES.
20+
tablesSqlByUser = `SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME`
21+
22+
// tablesSqlBySchema returns tables of specified schema/owner.
23+
tablesSqlBySchema = `SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = '%s' ORDER BY TABLE_NAME`
1724
)
1825

1926
// Tables retrieves and returns the tables of current schema.
2027
// It's mainly used in cli tool chain for automatically generating the models.
28+
//
29+
// When schema is specified (via parameter or config), it queries tables from that schema.
30+
// When schema is not specified, it queries tables owned by current user (like Oracle's USER_TABLES).
2131
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
22-
var result gdb.Result
32+
var (
33+
result gdb.Result
34+
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
35+
)
2336
// When schema is empty, return the default link
2437
link, err := d.SlaveLink(schema...)
2538
if err != nil {
2639
return nil, err
2740
}
28-
// The link has been distinguished and no longer needs to judge the owner
29-
result, err = d.DoSelect(ctx, link, tablesSqlTmp)
41+
42+
var query string
43+
if usedSchema != "" {
44+
// Use specified schema
45+
query = fmt.Sprintf(tablesSqlBySchema, strings.ToUpper(usedSchema))
46+
} else {
47+
// Use current user's tables (like Oracle's USER_TABLES)
48+
query = tablesSqlByUser
49+
}
50+
51+
result, err = d.DoSelect(ctx, link, query)
3052
if err != nil {
3153
return
3254
}
3355
for _, m := range result {
34-
if v, ok := m["IOT_NAME"]; ok {
56+
if v, ok := m["TABLE_NAME"]; ok {
3557
tables = append(tables, v.String())
3658
}
3759
}

contrib/drivers/dm/dm_z_unit_issue_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,136 @@ func Test_Issue2594(t *testing.T) {
7272
})
7373
}
7474

75+
// https://github.com/gogf/gf/issues/4495
76+
// Test Tables() returns correct tables using USER_TABLES (for current user)
77+
// and ALL_TABLES (when schema is specified).
78+
func Test_Issue4495_Tables(t *testing.T) {
79+
table1 := createInitTable()
80+
table2 := createInitTable()
81+
defer dropTable(table1)
82+
defer dropTable(table2)
83+
84+
// Test 1: Tables() returns tables for current user (using USER_TABLES)
85+
gtest.C(t, func(t *gtest.T) {
86+
tables, err := db.Tables(ctx)
87+
t.AssertNil(err)
88+
89+
// Should contain our created tables (case-insensitive comparison for DM)
90+
found1 := false
91+
found2 := false
92+
for _, tbl := range tables {
93+
if gstr.Equal(tbl, table1) {
94+
found1 = true
95+
}
96+
if gstr.Equal(tbl, table2) {
97+
found2 = true
98+
}
99+
}
100+
t.Assert(found1, true)
101+
t.Assert(found2, true)
102+
})
103+
104+
// Test 2: Tables() with explicit schema parameter (using ALL_TABLES with OWNER filter)
105+
gtest.C(t, func(t *gtest.T) {
106+
tables, err := db.Tables(ctx, TestDBName)
107+
t.AssertNil(err)
108+
109+
// Should contain our created tables
110+
found1 := false
111+
found2 := false
112+
for _, tbl := range tables {
113+
if gstr.Equal(tbl, table1) {
114+
found1 = true
115+
}
116+
if gstr.Equal(tbl, table2) {
117+
found2 = true
118+
}
119+
}
120+
t.Assert(found1, true)
121+
t.Assert(found2, true)
122+
})
123+
}
124+
125+
// https://github.com/gogf/gf/issues/4495
126+
// Test TableFields() returns correct field info with proper schema handling.
127+
func Test_Issue4495_TableFields(t *testing.T) {
128+
table := createInitTable()
129+
defer dropTable(table)
130+
131+
// Test 1: TableFields() without schema uses current schema
132+
gtest.C(t, func(t *gtest.T) {
133+
fields, err := db.TableFields(ctx, table)
134+
t.AssertNil(err)
135+
t.Assert(len(fields) > 0, true)
136+
137+
// Verify key fields exist
138+
_, hasID := fields["ID"]
139+
_, hasAccountName := fields["ACCOUNT_NAME"]
140+
t.Assert(hasID, true)
141+
t.Assert(hasAccountName, true)
142+
})
143+
144+
// Test 2: TableFields() with explicit schema parameter
145+
gtest.C(t, func(t *gtest.T) {
146+
fields, err := db.TableFields(ctx, table, TestDBName)
147+
t.AssertNil(err)
148+
t.Assert(len(fields) > 0, true)
149+
150+
// Verify key fields exist
151+
_, hasID := fields["ID"]
152+
_, hasAccountName := fields["ACCOUNT_NAME"]
153+
t.Assert(hasID, true)
154+
t.Assert(hasAccountName, true)
155+
})
156+
157+
// Test 3: TableFields() with Schema() method
158+
gtest.C(t, func(t *gtest.T) {
159+
fields, err := db.Schema(TestDBName).TableFields(ctx, table)
160+
t.AssertNil(err)
161+
t.Assert(len(fields) > 0, true)
162+
163+
// Verify ID is primary key
164+
idField, hasID := fields["ID"]
165+
t.Assert(hasID, true)
166+
t.Assert(idField.Key, "PRI")
167+
})
168+
}
169+
170+
// https://github.com/gogf/gf/issues/4495
171+
// Test cache isolation for different schemas.
172+
func Test_Issue4495_CacheIsolation(t *testing.T) {
173+
table := createInitTable()
174+
defer dropTable(table)
175+
176+
gtest.C(t, func(t *gtest.T) {
177+
// Get tables using default schema
178+
tables1, err := db.Tables(ctx)
179+
t.AssertNil(err)
180+
181+
// Get tables using explicit schema
182+
tables2, err := db.Schema(TestDBName).Tables(ctx)
183+
t.AssertNil(err)
184+
185+
// Both should contain the created table
186+
found1 := false
187+
found2 := false
188+
for _, tbl := range tables1 {
189+
if gstr.Equal(tbl, table) {
190+
found1 = true
191+
break
192+
}
193+
}
194+
for _, tbl := range tables2 {
195+
if gstr.Equal(tbl, table) {
196+
found2 = true
197+
break
198+
}
199+
}
200+
t.Assert(found1, true)
201+
t.Assert(found2, true)
202+
})
203+
}
204+
75205
// Test_MultilineSQLStatement tests that multi-line SQL statements are properly supported.
76206
// This test verifies that newlines and tabs in SQL queries are preserved,
77207
// which is essential for readability and proper SQL statement handling.

contrib/drivers/gaussdb/gaussdb.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ type Driver struct {
2121

2222
const (
2323
internalPrimaryKeyInCtx gctx.StrKey = "primary_key"
24-
defaultSchema string = "public"
2524
quoteChar string = `"`
2625
)
2726

contrib/drivers/gaussdb/gaussdb_table_fields.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ SELECT
2626
numeric_scale AS scale
2727
FROM pg_attribute a
2828
LEFT JOIN pg_class c ON a.attrelid = c.oid
29+
LEFT JOIN pg_namespace n ON c.relnamespace = n.oid
2930
LEFT JOIN pg_constraint d ON d.conrelid = c.oid AND a.attnum = d.conkey[1]
3031
LEFT JOIN pg_description b ON a.attrelid = b.objoid AND a.attnum = b.objsubid
3132
LEFT JOIN pg_type t ON a.atttypid = t.oid
32-
LEFT JOIN information_schema.columns ic ON ic.column_name = a.attname AND ic.table_name = c.relname
33+
LEFT JOIN information_schema.columns ic ON ic.column_name = a.attname AND ic.table_name = c.relname AND ic.table_schema = n.nspname
3334
WHERE c.oid = '%s'::regclass
3435
AND a.attisdropped IS FALSE
3536
AND a.attnum > 0

0 commit comments

Comments
 (0)