diff --git a/cmd/gf/go.sum b/cmd/gf/go.sum index bb5f8a4a868..533674fa5aa 100644 --- a/cmd/gf/go.sum +++ b/cmd/gf/go.sum @@ -38,6 +38,20 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.5.6 h1:yziPSf9AycEWphv9WiNjcRAVPOJtUauMMvP6pHQB4jY= +github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.5.6/go.mod h1:yOlpwhFXgW+P2sf4goA20PUtxdVLliBx4dJRyJeOtto= +github.com/gogf/gf/contrib/drivers/mssql/v2 v2.5.6 h1:LGQIe5IvYVr4hZ/vUAFiqWssxE7QeILyVPJ9swo1Cmk= +github.com/gogf/gf/contrib/drivers/mssql/v2 v2.5.6/go.mod h1:EcF8v8jqCV61/YqN6DXxdo3kh8waGmEj6WpFqbLkkrM= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.6 h1:oR9F4LVoKa/fjf/o6Y/CQRNiYy35Bszo07WwvMWYMxo= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.6/go.mod h1:gvHSRqCpv2c+N0gDHsEldHgU/yM9tcCBdIEKZ32/TaE= +github.com/gogf/gf/contrib/drivers/oracle/v2 v2.5.6 h1:3Y3lEoO9SoG1AmfaKjgTsDt93+T2q/qTMog8wBvIIGM= +github.com/gogf/gf/contrib/drivers/oracle/v2 v2.5.6/go.mod h1:cR3lFoU6ZtSaMQ3DpCJwWnYW6EvHPYGGeqv/kzgH4gw= +github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.5.6 h1:0WHVzqITqIBu/NNPXt3tN2eiWAGiNjs9sg6wh+WbUvY= +github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.5.6/go.mod h1:qZCTNQ0n2gHcuBwM9wUl3pelync3xK0gTnChJZD6f0I= +github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.5.6 h1:6clfLvFoHXHdw+skmXg4yxw+cLwgAG8gRiS/6f9Y9Xc= +github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.5.6/go.mod h1:QV6Rrj+4G4OaJVkP9XXRZ1LWL+ls6qH7ebeMcxsulqA= +github.com/gogf/gf/v2 v2.5.6 h1:a1UK1yUP3s+l+vPxmV91+8gTarAP9b1IEOw0W7LNl6E= +github.com/gogf/gf/v2 v2.5.6/go.mod h1:17K/gBYrp0bHGC3XYC7bSPoywmZ6MrZHrZakTfh4eIQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= diff --git a/contrib/drivers/mssql/mssql_z_model_test.go b/contrib/drivers/mssql/mssql_z_model_test.go index 2310493a320..7a809039fc7 100644 --- a/contrib/drivers/mssql/mssql_z_model_test.go +++ b/contrib/drivers/mssql/mssql_z_model_test.go @@ -9,10 +9,11 @@ package mssql_test import ( "database/sql" "fmt" - "github.com/gogf/gf/v2/util/gconv" "testing" "time" + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/container/garray" "github.com/gogf/gf/v2/container/gmap" "github.com/gogf/gf/v2/database/gdb" @@ -1906,12 +1907,14 @@ func Test_Model_HasTable(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable(table) t.Assert(result, true) t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable("table12321") t.Assert(result, false) t.AssertNil(err) diff --git a/contrib/drivers/mysql/mysql_model_test.go b/contrib/drivers/mysql/mysql_model_test.go index 230100f760b..1a2c7e1f152 100644 --- a/contrib/drivers/mysql/mysql_model_test.go +++ b/contrib/drivers/mysql/mysql_model_test.go @@ -3097,12 +3097,14 @@ func Test_Model_HasTable(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable(table) t.Assert(result, true) t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable("table12321") t.Assert(result, false) t.AssertNil(err) @@ -4706,3 +4708,44 @@ func Test_Scan_Nil_Result_Error(t *testing.T) { t.Assert(err, sql.ErrNoRows) }) } + +func Test_Model_FixGdbJoin(t *testing.T) { + array := gstr.SplitAndTrim(gtest.DataContent(`fix_gdb_join.sql`), ";") + for _, v := range array { + if _, err := db.Exec(ctx, v); err != nil { + gtest.Error(err) + } + } + defer dropTable(`common_resource`) + defer dropTable(`managed_resource`) + defer dropTable(`rules_template`) + defer dropTable(`resource_mark`) + gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) + sqlSlice, err := gdb.CatchSQL(ctx, func(ctx context.Context) error { + orm := db.Model(`managed_resource`).Ctx(ctx). + LeftJoinOnField(`common_resource`, `resource_id`). + LeftJoinOnFields(`resource_mark`, `resource_mark_id`, `=`, `id`). + LeftJoinOnFields(`rules_template`, `rule_template_id`, `=`, `template_id`). + FieldsPrefix( + `managed_resource`, + "resource_id", "user", "status", "status_message", "safe_publication", "rule_template_id", + "created_at", "comments", "expired_at", "resource_mark_id", "instance_id", "resource_name", + "pay_mode"). + FieldsPrefix(`resource_mark`, "mark_name", "color"). + FieldsPrefix(`rules_template`, "name"). + FieldsPrefix(`common_resource`, `src_instance_id`, "database_kind", "source_type", "ip", "port") + all, err := orm.OrderAsc("src_instance_id").All() + t.Assert(len(all), 4) + t.Assert(all[0]["pay_mode"], 1) + t.Assert(all[0]["src_instance_id"], 2) + t.Assert(all[3]["instance_id"], "dmcins-jxy0x75m") + t.Assert(all[3]["src_instance_id"], "vdb-6b6m3u1u") + t.Assert(all[3]["resource_mark_id"], "11") + return err + }) + t.AssertNil(err) + + t.Assert(gtest.DataContent(`fix_gdb_join_expect.sql`), sqlSlice[len(sqlSlice)-1]) + }) +} diff --git a/contrib/drivers/mysql/testdata/fix_gdb_join.sql b/contrib/drivers/mysql/testdata/fix_gdb_join.sql new file mode 100644 index 00000000000..48cf1efb315 --- /dev/null +++ b/contrib/drivers/mysql/testdata/fix_gdb_join.sql @@ -0,0 +1,151 @@ + + +DROP TABLE IF EXISTS `common_resource`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `common_resource` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `app_id` bigint(20) NOT NULL, + `resource_id` varchar(64) NOT NULL, + `src_instance_id` varchar(64) DEFAULT NULL, + `region` varchar(36) DEFAULT NULL, + `zone` varchar(36) DEFAULT NULL, + `database_kind` varchar(20) NOT NULL, + `source_type` varchar(64) NOT NULL, + `ip` varchar(64) DEFAULT NULL, + `port` int(10) DEFAULT NULL, + `vpc_id` varchar(20) DEFAULT NULL, + `subnet_id` varchar(20) DEFAULT NULL, + `proxy_ip` varchar(64) DEFAULT NULL, + `proxy_port` int(10) DEFAULT NULL, + `proxy_id` bigint(20) DEFAULT NULL, + `proxy_snat_ip` varchar(64) DEFAULT NULL, + `lease_at` timestamp NULL DEFAULT NULL, + `uin` varchar(32) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `unique_resource` (`app_id`,`src_instance_id`,`vpc_id`,`subnet_id`,`ip`,`port`), + KEY `resource_id` (`resource_id`) +) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COMMENT='资源公共信息表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `common_resource` +-- + +LOCK TABLES `common_resource` WRITE; +/*!40000 ALTER TABLE `common_resource` DISABLE KEYS */; +INSERT INTO `common_resource` VALUES (1,1,'2','2','2','3','1','1','1',1,'1','1','1',1,1,'1',NULL,''),(3,2,'3','3','3','3','3','3','3',3,'3','3','3',3,3,'3',NULL,''),(18,1303697168,'dmc-rgnh9qre','vdb-6b6m3u1u','ap-guangzhou','','vdb','cloud','10.0.1.16',80,'vpc-m3dchft7','subnet-9as3a3z2','9.27.72.189',11131,228476,'169.254.128.5, ','2023-11-08 08:13:04',''),(20,1303697168,'dmc-4grzi4jg','tdsqlshard-313spncx','ap-guangzhou','','tdsql','cloud','10.255.0.27',3306,'vpc-407k0e8x','subnet-qhkkk3bo','30.86.239.200',24087,0,'',NULL,''); +/*!40000 ALTER TABLE `common_resource` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `managed_resource` +-- + +DROP TABLE IF EXISTS `managed_resource`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `managed_resource` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `instance_id` varchar(64) NOT NULL, + `resource_id` varchar(64) NOT NULL, + `resource_name` varchar(64) DEFAULT NULL, + `status` varchar(36) NOT NULL DEFAULT 'valid', + `status_message` varchar(64) DEFAULT NULL, + `user` varchar(64) NOT NULL, + `password` varchar(1024) NOT NULL, + `pay_mode` tinyint(1) DEFAULT '0', + `safe_publication` bit(1) DEFAULT b'0', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `expired_at` timestamp NULL DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `resource_mark_id` int(11) DEFAULT NULL, + `comments` varchar(64) DEFAULT NULL, + `rule_template_id` varchar(64) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `resource_id` (`resource_id`), + KEY `instance_id` (`instance_id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COMMENT='管控实例表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `managed_resource` +-- + +LOCK TABLES `managed_resource` WRITE; +/*!40000 ALTER TABLE `managed_resource` DISABLE KEYS */; +INSERT INTO `managed_resource` VALUES (1,'2','3','1','1','1','1','1',1,_binary '','2023-11-06 12:14:21','2023-11-06 12:14:21',NULL,1,1,'1',''),(2,'3','2','1','1','1','1','1',1,_binary '\0','2023-11-06 12:15:07','2023-11-06 12:15:07',NULL,1,2,'1',''),(5,'dmcins-jxy0x75m','dmc-rgnh9qre','erichmao-vdb-test','invalid','The Ip field is required','root','2e39af3dd1d447e2b1437b40c62c35995fa22b370c7455ff7815dace3a6e8891ccadcfc893fe1342a4102d742bd7a3e603cd0ac1fcdc072d7c0b5be5836ec87306981b629f9b59aedf0316e9504ab172fa1c95756d5b260114e4feaa0b19223fb61cb268cc4818307ed193dbab830cf556b91cde182686eb70f70ea77f69eff66230dec2ce92bd3352cad31abf47597a5cc6a0d638381dc3bae7aa1b142730790a6d4cefdef1bd460061c966ad5008c2b5fc971b7f4d7dddffa5b1456c45e2917763dd8fffb1fa7fc4783feca95dafc9a9f4edf21b0579f76b0a3154f087e3b9a7fc49af8ff92b12e7b03caa865e72e777dd9d35a11910df0d55ead90e47d5f8',1,_binary '','2023-11-08 08:13:20','2023-11-09 05:31:07',NULL,0,11,NULL,'12345'),(6,'dmcins-erxms6ya','dmc-4grzi4jg','erichmao-vdb-test','invalid','The Ip field is required','leotaowang','641d846cf75bc7944202251d97dca8335f7f149dd4fd911ca5b87c71ef1dc5d0a66c4e5021ef7ad53136cda2fb2567d34e3dd1a7666e3f64ebf532eb2a55d84952aac86b4211f563f7b9da7dd0f88ec288d6680d3513cea0c1b7ad7babb474717f77ebbc9d63bb458adaf982887da9e63df957ffda572c1c3ed187471b99fdc640b45fed76a6d50dc1090eee79b4d94d056c4d43416133481f55bd040759398680104a84d801e6475dcfe919a00859908296747430b728a00c8d54256ae220235a138e0bbf08fe8b6fc8589971436b55bff966154721a91adbdc9c2b6f50ef5849ed77e5b028116abac51584b8d401cd3a88d18df127006358ed33fc3fa6f480',1,_binary '','2023-11-08 22:15:17','2023-11-09 05:31:07',NULL,0,11,NULL,'12345'); +/*!40000 ALTER TABLE `managed_resource` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `rules_template` +-- + +DROP TABLE IF EXISTS `rules_template`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `rules_template` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `app_id` bigint(20) DEFAULT NULL, + `name` varchar(255) NOT NULL, + `database_kind` varchar(64) DEFAULT NULL, + `is_default` tinyint(1) NOT NULL DEFAULT '0', + `win_rules` varchar(2048) DEFAULT NULL, + `inception_rules` varchar(2048) DEFAULT NULL, + `auto_exec_rules` varchar(2048) DEFAULT NULL, + `order_check_step` varchar(2048) DEFAULT NULL, + `template_id` varchar(64) NOT NULL DEFAULT '', + `version` int(11) NOT NULL DEFAULT '1', + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `is_system` tinyint(1) NOT NULL DEFAULT '0', + `uin` varchar(64) DEFAULT NULL, + `subAccountUin` varchar(64) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_template_id` (`template_id`), + UNIQUE KEY `uniq_name` (`name`,`app_id`,`deleted`,`uin`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `rules_template` +-- + +LOCK TABLES `rules_template` WRITE; +/*!40000 ALTER TABLE `rules_template` DISABLE KEYS */; +/*!40000 ALTER TABLE `rules_template` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `resource_mark` +-- + +DROP TABLE IF EXISTS `resource_mark`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `resource_mark` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `app_id` bigint(20) NOT NULL, + `mark_name` varchar(64) NOT NULL, + `color` varchar(11) NOT NULL, + `creator` varchar(32) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `app_id_name` (`app_id`,`mark_name`) +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='标签信息表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `resource_mark` +-- + +LOCK TABLES `resource_mark` WRITE; +/*!40000 ALTER TABLE `resource_mark` DISABLE KEYS */; +INSERT INTO `resource_mark` VALUES (10,1,'test','red','1','2023-11-06 02:45:46','2023-11-06 02:45:46'); +/*!40000 ALTER TABLE `resource_mark` ENABLE KEYS */; +UNLOCK TABLES; + diff --git a/contrib/drivers/mysql/testdata/fix_gdb_join_expect.sql b/contrib/drivers/mysql/testdata/fix_gdb_join_expect.sql new file mode 100644 index 00000000000..c391675cbb6 --- /dev/null +++ b/contrib/drivers/mysql/testdata/fix_gdb_join_expect.sql @@ -0,0 +1 @@ +SELECT managed_resource.resource_id,managed_resource.user,managed_resource.status,managed_resource.status_message,managed_resource.safe_publication,managed_resource.rule_template_id,managed_resource.created_at,managed_resource.comments,managed_resource.expired_at,managed_resource.resource_mark_id,managed_resource.instance_id,managed_resource.resource_name,managed_resource.pay_mode,resource_mark.mark_name,resource_mark.color,rules_template.name,common_resource.src_instance_id,common_resource.database_kind,common_resource.source_type,common_resource.ip,common_resource.port FROM `managed_resource` LEFT JOIN `common_resource` ON (`managed_resource`.`resource_id`=`common_resource`.`resource_id`) LEFT JOIN `resource_mark` ON (`managed_resource`.`resource_mark_id` = `resource_mark`.`id`) LEFT JOIN `rules_template` ON (`managed_resource`.`rule_template_id` = `rules_template`.`template_id`) ORDER BY `src_instance_id` ASC \ No newline at end of file diff --git a/contrib/drivers/oracle/oracle_z_model_test.go b/contrib/drivers/oracle/oracle_z_model_test.go index 1ab58ddd3a1..d50d46cfee5 100644 --- a/contrib/drivers/oracle/oracle_z_model_test.go +++ b/contrib/drivers/oracle/oracle_z_model_test.go @@ -950,12 +950,14 @@ func Test_Model_HasTable(t *testing.T) { defer dropTable(table) // db.SetDebug(true) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable(strings.ToUpper(table)) t.Assert(result, true) t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable("table12321") t.Assert(result, false) t.AssertNil(err) diff --git a/contrib/drivers/sqlite/sqlite_model_test.go b/contrib/drivers/sqlite/sqlite_model_test.go index f39804f1ef6..a6d6f61a57e 100644 --- a/contrib/drivers/sqlite/sqlite_model_test.go +++ b/contrib/drivers/sqlite/sqlite_model_test.go @@ -2807,12 +2807,14 @@ func Test_Model_HasTable(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable(table) t.Assert(result, true) t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable("table12321") t.Assert(result, false) t.AssertNil(err) diff --git a/contrib/drivers/sqlitecgo/sqlite_model_test.go b/contrib/drivers/sqlitecgo/sqlite_model_test.go index 8aa297e7a2b..b9f1760d184 100644 --- a/contrib/drivers/sqlitecgo/sqlite_model_test.go +++ b/contrib/drivers/sqlitecgo/sqlite_model_test.go @@ -2835,12 +2835,14 @@ func Test_Model_HasTable(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable(table) t.Assert(result, true) t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { + t.AssertNil(db.GetCore().ClearCacheAll(ctx)) result, err := db.GetCore().HasTable("table12321") t.Assert(result, false) t.AssertNil(err) diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 1c3344149cb..e0ddc0329bc 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -353,7 +353,7 @@ type ( type CatchSQLManager struct { SQLArray *garray.StrArray - DoCommit bool + DoCommit bool // DoCommit marks it will be committed to underlying driver or not. } const ( diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index f4ac5f7817f..482caf80b47 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -762,27 +762,37 @@ func (c *Core) writeSqlToLogger(ctx context.Context, sql *Sql) { // HasTable determine whether the table name exists in the database. func (c *Core) HasTable(name string) (bool, error) { + tables, err := c.GetTablesWithCache() + if err != nil { + return false, err + } + for _, table := range tables { + if table == name { + return true, nil + } + } + return false, nil +} + +// GetTablesWithCache retrieves and returns the table names of current database with cache. +func (c *Core) GetTablesWithCache() ([]string, error) { var ( ctx = c.db.GetCtx() - cacheKey = fmt.Sprintf(`HasTable: %s`, name) + cacheKey = fmt.Sprintf(`Tables: %s`, c.db.GetGroup()) ) - result, err := c.GetCache().GetOrSetFuncLock(ctx, cacheKey, func(ctx context.Context) (interface{}, error) { - tableList, err := c.db.Tables(ctx) - if err != nil { - return false, err - } - for _, table := range tableList { - if table == name { - return true, nil + result, err := c.GetCache().GetOrSetFuncLock( + ctx, cacheKey, func(ctx context.Context) (interface{}, error) { + tableList, err := c.db.Tables(ctx) + if err != nil { + return false, err } - } - return false, nil - }, 0, + return tableList, nil + }, 0, ) if err != nil { - return false, err + return nil, err } - return result.Bool(), nil + return result.Strings(), nil } // isSoftCreatedFieldName checks and returns whether given field name is an automatic-filled created time. diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 7df37ce3a6c..28710a203c3 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -96,7 +96,7 @@ func DBFromCtx(ctx context.Context) DB { return nil } -// ToSQL formats and returns the last one of sql statements in given closure function. +// ToSQL formats and returns the last one of sql statements in given closure function without truly executing it. func ToSQL(ctx context.Context, f func(ctx context.Context) error) (sql string, err error) { var manager = &CatchSQLManager{ SQLArray: garray.NewStrArray(), diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 5e70154b989..dc8f596bac7 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -17,39 +17,40 @@ import ( // Model is core struct implementing the DAO for ORM. type Model struct { - db DB // Underlying DB interface. - tx TX // Underlying TX interface. - rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model. - schema string // Custom database schema. - linkType int // Mark for operation on master or slave. - tablesInit string // Table names when model initialization. - tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud". - fields string // Operation fields, multiple fields joined using char ','. - fieldsEx string // Excluded operation fields, multiple fields joined using char ','. - withArray []interface{} // Arguments for With feature. - withAll bool // Enable model association operations on all objects that have "with" tag in the struct. - extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver. - whereBuilder *WhereBuilder // Condition builder for where operation. - groupBy string // Used for "group by" statement. - orderBy string // Used for "order by" statement. - having []interface{} // Used for "having..." statement. - start int // Used for "select ... start, limit ..." statement. - limit int // Used for "select ... start, limit ..." statement. - option int // Option for extra operation features. - offset int // Offset statement for some databases grammar. - partition string // Partition table partition name. - data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. - batch int // Batch number for batch Insert/Replace/Save operations. - filter bool // Filter data and where key-value pairs according to the fields of the table. - distinct string // Force the query to only return distinct results. - lockInfo string // Lock for update or in shared lock. - cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage. - cacheOption CacheOption // Cache option for query statement. - hookHandler HookHandler // Hook functions for model hook feature. - unscoped bool // Disables soft deleting features when select/delete operations. - safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. - onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement. - onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement. + db DB // Underlying DB interface. + tx TX // Underlying TX interface. + rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model. + schema string // Custom database schema. + linkType int // Mark for operation on master or slave. + tablesInit string // Table names when model initialization. + tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud". + fields string // Operation fields, multiple fields joined using char ','. + fieldsEx string // Excluded operation fields, multiple fields joined using char ','. + withArray []interface{} // Arguments for With feature. + withAll bool // Enable model association operations on all objects that have "with" tag in the struct. + extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver. + whereBuilder *WhereBuilder // Condition builder for where operation. + groupBy string // Used for "group by" statement. + orderBy string // Used for "order by" statement. + having []interface{} // Used for "having..." statement. + start int // Used for "select ... start, limit ..." statement. + limit int // Used for "select ... start, limit ..." statement. + option int // Option for extra operation features. + offset int // Offset statement for some databases grammar. + partition string // Partition table partition name. + data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. + batch int // Batch number for batch Insert/Replace/Save operations. + filter bool // Filter data and where key-value pairs according to the fields of the table. + distinct string // Force the query to only return distinct results. + lockInfo string // Lock for update or in shared lock. + cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage. + cacheOption CacheOption // Cache option for query statement. + hookHandler HookHandler // Hook functions for model hook feature. + unscoped bool // Disables soft deleting features when select/delete operations. + safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. + onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement. + onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement. + tableAliasMap map[string]string // Table alias to true table name, usually used in join statements. } // ModelHandler is a function that handles given Model and returns a new Model that is custom modified. @@ -125,15 +126,16 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model { } } m := &Model{ - db: c.db, - schema: c.schema, - tablesInit: tableStr, - tables: tableStr, - fields: defaultFields, - start: -1, - offset: -1, - filter: true, - extraArgs: extraArgs, + db: c.db, + schema: c.schema, + tablesInit: tableStr, + tables: tableStr, + fields: defaultFields, + start: -1, + offset: -1, + filter: true, + extraArgs: extraArgs, + tableAliasMap: make(map[string]string), } m.whereBuilder = m.Builder() if defaultModelSafe { diff --git a/database/gdb/gdb_model_fields.go b/database/gdb/gdb_model_fields.go index a9a54711c67..c9e29a3f89d 100644 --- a/database/gdb/gdb_model_fields.go +++ b/database/gdb/gdb_model_fields.go @@ -27,7 +27,7 @@ func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model { if length == 0 { return m } - fields := m.getFieldsFrom(fieldNamesOrMapStruct...) + fields := m.getFieldsFrom(m.tablesInit, fieldNamesOrMapStruct...) if len(fields) == 0 { return m } @@ -35,12 +35,12 @@ func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model { } // FieldsPrefix performs as function Fields but add extra prefix for each field. -func (m *Model) FieldsPrefix(prefix string, fieldNamesOrMapStruct ...interface{}) *Model { - fields := m.getFieldsFrom(fieldNamesOrMapStruct...) +func (m *Model) FieldsPrefix(prefixOrAlias string, fieldNamesOrMapStruct ...interface{}) *Model { + fields := m.getFieldsFrom(m.getTableNameByPrefixOrAlias(prefixOrAlias), fieldNamesOrMapStruct...) if len(fields) == 0 { return m } - gstr.PrefixArray(fields, prefix+".") + gstr.PrefixArray(fields, prefixOrAlias+".") return m.appendFieldsByStr(gstr.Join(fields, ",")) } @@ -51,11 +51,14 @@ func (m *Model) FieldsPrefix(prefix string, fieldNamesOrMapStruct ...interface{} // // Also see Fields. func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model { + return m.doFieldsEx(m.tablesInit, fieldNamesOrMapStruct...) +} +func (m *Model) doFieldsEx(table string, fieldNamesOrMapStruct ...interface{}) *Model { length := len(fieldNamesOrMapStruct) if length == 0 { return m } - fields := m.getFieldsFrom(fieldNamesOrMapStruct...) + fields := m.getFieldsFrom(table, fieldNamesOrMapStruct...) if len(fields) == 0 { return m } @@ -63,10 +66,10 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model { } // FieldsExPrefix performs as function FieldsEx but add extra prefix for each field. -func (m *Model) FieldsExPrefix(prefix string, fieldNamesOrMapStruct ...interface{}) *Model { - model := m.FieldsEx(fieldNamesOrMapStruct...) +func (m *Model) FieldsExPrefix(prefixOrAlias string, fieldNamesOrMapStruct ...interface{}) *Model { + model := m.doFieldsEx(m.getTableNameByPrefixOrAlias(prefixOrAlias), fieldNamesOrMapStruct...) array := gstr.SplitAndTrim(model.fieldsEx, ",") - gstr.PrefixArray(array, prefix+".") + gstr.PrefixArray(array, prefixOrAlias+".") model.fieldsEx = gstr.Join(array, ",") return model } @@ -185,7 +188,8 @@ func (m *Model) HasField(field string) (bool, error) { return m.db.GetCore().HasField(m.GetCtx(), m.tablesInit, field) } -func (m *Model) getFieldsFrom(fieldNamesOrMapStruct ...interface{}) []string { +// getFieldsFrom retrieves, filters and returns fields name from table `table`. +func (m *Model) getFieldsFrom(table string, fieldNamesOrMapStruct ...interface{}) []string { length := len(fieldNamesOrMapStruct) if length == 0 { return nil @@ -193,23 +197,25 @@ func (m *Model) getFieldsFrom(fieldNamesOrMapStruct ...interface{}) []string { switch { // String slice. case length >= 2: - return m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct), true) + return m.mappingAndFilterToTableFields( + table, gconv.Strings(fieldNamesOrMapStruct), true, + ) // It needs type asserting. case length == 1: structOrMap := fieldNamesOrMapStruct[0] switch r := structOrMap.(type) { case string: - return m.mappingAndFilterToTableFields([]string{r}, false) + return m.mappingAndFilterToTableFields(table, []string{r}, false) case []string: - return m.mappingAndFilterToTableFields(r, true) + return m.mappingAndFilterToTableFields(table, r, true) case Raw, *Raw: return []string{gconv.String(structOrMap)} default: - return m.mappingAndFilterToTableFields(getFieldsFromStructOrMap(structOrMap), true) + return m.mappingAndFilterToTableFields(table, getFieldsFromStructOrMap(structOrMap), true) } default: diff --git a/database/gdb/gdb_model_join.go b/database/gdb/gdb_model_join.go index eacb0d5a8b2..2ebc2436ddd 100644 --- a/database/gdb/gdb_model_join.go +++ b/database/gdb/gdb_model_join.go @@ -159,6 +159,8 @@ func (m *Model) doJoin(operator joinOperator, tableOrSubQueryAndJoinConditions . var ( model = m.getModel() joinStr = "" + table string + alias string ) // Check the first parameter table or sub-query. if len(tableOrSubQueryAndJoinConditions) > 0 { @@ -168,24 +170,29 @@ func (m *Model) doJoin(operator joinOperator, tableOrSubQueryAndJoinConditions . joinStr = "(" + joinStr + ")" } } else { - joinStr = m.db.GetCore().QuotePrefixTableName(tableOrSubQueryAndJoinConditions[0]) + table = tableOrSubQueryAndJoinConditions[0] + joinStr = m.db.GetCore().QuotePrefixTableName(table) } } // Generate join condition statement string. conditionLength := len(tableOrSubQueryAndJoinConditions) switch { case conditionLength > 2: + alias = tableOrSubQueryAndJoinConditions[1] model.tables += fmt.Sprintf( " %s JOIN %s AS %s ON (%s)", operator, joinStr, - m.db.GetCore().QuoteWord(tableOrSubQueryAndJoinConditions[1]), + m.db.GetCore().QuoteWord(alias), tableOrSubQueryAndJoinConditions[2], ) + m.tableAliasMap[alias] = table + case conditionLength == 2: model.tables += fmt.Sprintf( " %s JOIN %s ON (%s)", operator, joinStr, tableOrSubQueryAndJoinConditions[1], ) + case conditionLength == 1: model.tables += fmt.Sprintf( " %s JOIN %s", operator, joinStr, @@ -194,6 +201,16 @@ func (m *Model) doJoin(operator joinOperator, tableOrSubQueryAndJoinConditions . return model } +// getTableNameByPrefixOrAlias checks and returns the table name if `prefixOrAlias` is an alias of a table, +// it or else returns the `prefixOrAlias` directly. +func (m *Model) getTableNameByPrefixOrAlias(prefixOrAlias string) string { + value, ok := m.tableAliasMap[prefixOrAlias] + if ok { + return value + } + return prefixOrAlias +} + // isSubQuery checks and returns whether given string a sub-query sql string. func isSubQuery(s string) bool { s = gstr.TrimLeft(s, "()") diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index fbf51f7c982..0873af25f2c 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -52,8 +52,19 @@ func (m *Model) getModel() *Model { // Eg: // ID -> id // NICK_Name -> nickname. -func (m *Model) mappingAndFilterToTableFields(fields []string, filter bool) []string { - fieldsMap, _ := m.TableFields(m.tablesInit) +func (m *Model) mappingAndFilterToTableFields(table string, fields []string, filter bool) []string { + var fieldsTable = table + if fieldsTable != "" { + hasTable, _ := m.db.GetCore().HasTable(fieldsTable) + if !hasTable { + fieldsTable = m.tablesInit + } + } + if fieldsTable == "" { + fieldsTable = m.tablesInit + } + + fieldsMap, _ := m.TableFields(fieldsTable) if len(fieldsMap) == 0 { return fields }