diff --git a/contrib/drivers/pgsql/pgsql.go b/contrib/drivers/pgsql/pgsql.go index 9654caab3..255d35955 100644 --- a/contrib/drivers/pgsql/pgsql.go +++ b/contrib/drivers/pgsql/pgsql.go @@ -35,6 +35,7 @@ type Driver struct { const ( internalPrimaryKeyInCtx gctx.StrKey = "primary_key" + defaultSchema = "public" ) func init() { @@ -222,9 +223,18 @@ func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args [ // It's mainly used in cli tool chain for automatically generating the models. func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var ( - result gdb.Result - querySchema = gutil.GetOrDefaultStr("public", schema...) - query = fmt.Sprintf(` + result gdb.Result + usedSchema = gutil.GetOrDefaultStr(d.GetConfig().Namespace, schema...) + ) + if usedSchema == "" { + usedSchema = defaultSchema + } + // DO NOT use `usedSchema` as parameter for function `SlaveLink`. + link, err := d.SlaveLink(schema...) + if err != nil { + return nil, err + } + var query = fmt.Sprintf(` SELECT c.relname FROM @@ -235,16 +245,11 @@ WHERE n.nspname = '%s' AND c.relkind IN ('r', 'p') AND c.relpartbound IS NULL - AND PG_TABLE_IS_VISIBLE(c.oid) ORDER BY c.relname`, - querySchema, - ) + usedSchema, ) - link, err := d.SlaveLink(schema...) - if err != nil { - return nil, err - } + query, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(query)) result, err = d.DoSelect(ctx, link, query) if err != nil { diff --git a/contrib/drivers/pgsql/pgsql_z_db_test.go b/contrib/drivers/pgsql/pgsql_z_db_test.go index 6572f282d..1943dc7bf 100644 --- a/contrib/drivers/pgsql/pgsql_z_db_test.go +++ b/contrib/drivers/pgsql/pgsql_z_db_test.go @@ -277,14 +277,11 @@ func Test_DB_Delete(t *testing.T) { func Test_DB_Tables(t *testing.T) { gtest.C(t, func(t *gtest.T) { tables := []string{"t_user1", "pop", "haha"} - for _, v := range tables { createTable(v) } - result, err := db.Tables(ctx) gtest.Assert(err, nil) - for i := 0; i < len(tables); i++ { find := false for j := 0; j < len(result); j++ { diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index cad3428a0..5e18cf220 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -179,15 +179,22 @@ type DB interface { // Core is the base struct for database management. type Core struct { - db DB // DB interface object. - ctx context.Context // Context for chaining operation only. Do not set a default value in Core initialization. - group string // Configuration group name. - schema string // Custom schema for this object. - debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime. - cache *gcache.Cache // Cache manager, SQL result cache only. - links *gmap.StrAnyMap // links caches all created links by node. - logger glog.ILogger // Logger for logging functionality. - config *ConfigNode // Current config node. + db DB // DB interface object. + ctx context.Context // Context for chaining operation only. Do not set a default value in Core initialization. + group string // Configuration group name. + schema string // Custom schema for this object. + debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime. + cache *gcache.Cache // Cache manager, SQL result cache only. + links *gmap.StrAnyMap // links caches all created links by node. + logger glog.ILogger // Logger for logging functionality. + config *ConfigNode // Current config node. + dynamicConfig dynamicConfig // Dynamic configurations, which can be changed in runtime. +} + +type dynamicConfig struct { + MaxIdleConnCount int + MaxOpenConnCount int + MaxConnLifeTime time.Duration } // DoCommitInput is the input parameters for function DoCommit. @@ -237,6 +244,7 @@ type Sql struct { Start int64 // Start execution timestamp in milliseconds. End int64 // End execution timestamp in milliseconds. Group string // Group is the group name of the configuration that the sql is executed from. + Schema string // Schema is the schema name of the configuration that the sql is executed from. IsTransaction bool // IsTransaction marks whether this sql is executed in transaction. RowsAffected int64 // RowsAffected marks retrieved or affected number with current sql statement. } @@ -424,6 +432,10 @@ func NewByGroup(group ...string) (db DB, err error) { } // newDBByConfigNode creates and returns an ORM object with given configuration node and group name. +// +// Very Note: +// The parameter `node` is used for DB creation, not for underlying connection creation. +// So all db type configurations in the same group should be the same. func newDBByConfigNode(node *ConfigNode, group string) (db DB, err error) { if node.Link != "" { node = parseConfigNodeLink(node) @@ -435,6 +447,11 @@ func newDBByConfigNode(node *ConfigNode, group string) (db DB, err error) { links: gmap.NewStrAnyMap(true), logger: glog.New(), config: node, + dynamicConfig: dynamicConfig{ + MaxIdleConnCount: node.MaxIdleConnCount, + MaxOpenConnCount: node.MaxOpenConnCount, + MaxConnLifeTime: node.MaxConnLifeTime, + }, } if v, ok := driverMap[node.Type]; ok { if c.db, err = v.New(c, node); err != nil { @@ -538,7 +555,9 @@ func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode { for i := 0; i < len(cg); i++ { max = min + cg[i].Weight*100 if random >= min && random < max { - // Return a copy of the ConfigNode. + // ==================================================== + // Return a COPY of the ConfigNode. + // ==================================================== node := ConfigNode{} node = cg[i] return &node @@ -552,17 +571,23 @@ func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode { // The parameter `master` specifies whether retrieves master node connection if // master-slave nodes are configured. func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error) { - var node *ConfigNode - // Load balance. + var ( + node *ConfigNode + ctx = c.db.GetCtx() + ) if c.group != "" { + // Load balance. configs.RLock() defer configs.RUnlock() + // Value COPY for node. node, err = getConfigNodeByGroup(c.group, master) if err != nil { return nil, err } } else { - node = c.config + // Value COPY for node. + n := *c.db.GetConfig() + node = &n } if node.Charset == "" { node.Charset = defaultCharset @@ -570,42 +595,42 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error // Changes the schema. nodeSchema := gutil.GetOrDefaultStr(c.schema, schema...) if nodeSchema != "" { - // Value copy. - n := *node - n.Name = nodeSchema - node = &n + node.Name = nodeSchema + } + // Update the configuration object in internal data. + internalData := c.GetInternalCtxDataFromCtx(ctx) + if internalData != nil { + internalData.ConfigNode = node } // Cache the underlying connection pool object by node. - instanceNameByNode := fmt.Sprintf( - `%s@%s(%s:%s)/%s`, - node.User, node.Protocol, node.Host, node.Port, node.Name, - ) - v := c.links.GetOrSetFuncLock(instanceNameByNode, func() interface{} { + instanceNameByNode := fmt.Sprintf(`%+v`, node) + instanceValue := c.links.GetOrSetFuncLock(instanceNameByNode, func() interface{} { if sqlDb, err = c.db.Open(node); err != nil { return nil } if sqlDb == nil { return nil } - if c.config.MaxIdleConnCount > 0 { - sqlDb.SetMaxIdleConns(c.config.MaxIdleConnCount) + if c.dynamicConfig.MaxIdleConnCount > 0 { + sqlDb.SetMaxIdleConns(c.dynamicConfig.MaxIdleConnCount) } else { sqlDb.SetMaxIdleConns(defaultMaxIdleConnCount) } - if c.config.MaxOpenConnCount > 0 { - sqlDb.SetMaxOpenConns(c.config.MaxOpenConnCount) + if c.dynamicConfig.MaxOpenConnCount > 0 { + sqlDb.SetMaxOpenConns(c.dynamicConfig.MaxOpenConnCount) } else { sqlDb.SetMaxOpenConns(defaultMaxOpenConnCount) } - if c.config.MaxConnLifeTime > 0 { - sqlDb.SetConnMaxLifetime(c.config.MaxConnLifeTime) + if c.dynamicConfig.MaxConnLifeTime > 0 { + sqlDb.SetConnMaxLifetime(c.dynamicConfig.MaxConnLifeTime) } else { sqlDb.SetConnMaxLifetime(defaultMaxConnLifeTime) } return sqlDb }) - if v != nil && sqlDb == nil { - sqlDb = v.(*sql.DB) + if instanceValue != nil && sqlDb == nil { + // It reads from instance map. + sqlDb = instanceValue.(*sql.DB) } if node.Debug { c.db.SetDebug(node.Debug) diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 2b6f7d4a6..481da701e 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -665,15 +665,9 @@ func (c *Core) writeSqlToLogger(ctx context.Context, sql *Sql) { transactionIdStr = fmt.Sprintf(`[txid:%d] `, v.(uint64)) } } - // Runtime schema. - schemaName := c.GetSchema() - if schemaName == "" { - // The original schema in configuration. - schemaName = c.config.Name - } s := fmt.Sprintf( "[%3d ms] [%s] [%s] [rows:%-3d] %s%s", - sql.End-sql.Start, sql.Group, schemaName, sql.RowsAffected, transactionIdStr, sql.Format, + sql.End-sql.Start, sql.Group, sql.Schema, sql.RowsAffected, transactionIdStr, sql.Format, ) if sql.Error != nil { s += "\nError: " + sql.Error.Error() diff --git a/database/gdb/gdb_core_config.go b/database/gdb/gdb_core_config.go index 3ee78853f..2e6c6a7b5 100644 --- a/database/gdb/gdb_core_config.go +++ b/database/gdb/gdb_core_config.go @@ -41,13 +41,14 @@ type ConfigNode struct { Charset string `json:"charset"` // (Optional, "utf8mb4" in default) Custom charset when operating on database. Protocol string `json:"protocol"` // (Optional, "tcp" in default) See net.Dial for more information which networks are available. Timezone string `json:"timezone"` // (Optional) Sets the time zone for displaying and interpreting time stamps. + Namespace string `json:"namespace"` // Namespace for some databases. Eg, in pgsql, the `Name` acts as the `catalog`, the `NameSpace` acts as the `schema`. MaxIdleConnCount int `json:"maxIdle"` // (Optional) Max idle connection configuration for underlying connection pool. MaxOpenConnCount int `json:"maxOpen"` // (Optional) Max open connection configuration for underlying connection pool. MaxConnLifeTime time.Duration `json:"maxLifeTime"` // (Optional) Max amount of time a connection may be idle before being closed. QueryTimeout time.Duration `json:"queryTimeout"` // (Optional) Max query time for per dql. ExecTimeout time.Duration `json:"execTimeout"` // (Optional) Max exec time for dml. - TranTimeout time.Duration `json:"tranTimeout"` // (Optional) Max exec time time for a transaction. - PrepareTimeout time.Duration `json:"prepareTimeout"` // (Optional) Max exec time time for prepare operation. + TranTimeout time.Duration `json:"tranTimeout"` // (Optional) Max exec time for a transaction. + PrepareTimeout time.Duration `json:"prepareTimeout"` // (Optional) Max exec time for prepare operation. CreatedAt string `json:"createdAt"` // (Optional) The filed name of table for automatic-filled created datetime. UpdatedAt string `json:"updatedAt"` // (Optional) The filed name of table for automatic-filled updated datetime. DeletedAt string `json:"deletedAt"` // (Optional) The filed name of table for automatic-filled updated datetime. @@ -181,7 +182,7 @@ func (c *Core) GetLogger() glog.ILogger { // The default max idle connections is currently 2. This may change in // a future release. func (c *Core) SetMaxIdleConnCount(n int) { - c.config.MaxIdleConnCount = n + c.dynamicConfig.MaxIdleConnCount = n } // SetMaxOpenConnCount sets the maximum number of open connections to the database. @@ -193,7 +194,7 @@ func (c *Core) SetMaxIdleConnCount(n int) { // If n <= 0, then there is no limit on the number of open connections. // The default is 0 (unlimited). func (c *Core) SetMaxOpenConnCount(n int) { - c.config.MaxOpenConnCount = n + c.dynamicConfig.MaxOpenConnCount = n } // SetMaxConnLifeTime sets the maximum amount of time a connection may be reused. @@ -202,11 +203,15 @@ func (c *Core) SetMaxOpenConnCount(n int) { // // If d <= 0, connections are not closed due to a connection's age. func (c *Core) SetMaxConnLifeTime(d time.Duration) { - c.config.MaxConnLifeTime = d + c.dynamicConfig.MaxConnLifeTime = d } // GetConfig returns the current used node configuration. func (c *Core) GetConfig() *ConfigNode { + internalData := c.GetInternalCtxDataFromCtx(c.db.GetCtx()) + if internalData != nil && internalData.ConfigNode != nil { + return internalData.ConfigNode + } return c.config } @@ -247,7 +252,11 @@ func (c *Core) GetPrefix() string { // GetSchema returns the schema configured. func (c *Core) GetSchema() string { - return c.schema + schema := c.schema + if schema == "" { + schema = c.db.GetConfig().Name + } + return schema } func parseConfigNodeLink(node *ConfigNode) *ConfigNode { diff --git a/database/gdb/gdb_core_ctx.go b/database/gdb/gdb_core_ctx.go index 9d138f01d..2f32bf1e3 100644 --- a/database/gdb/gdb_core_ctx.go +++ b/database/gdb/gdb_core_ctx.go @@ -17,6 +17,9 @@ type internalCtxData struct { // Operation DB. DB DB + // Used configuration node in current operation. + ConfigNode *ConfigNode + // The first column in result response from database server. // This attribute is used for Value/Count selection statement purpose, // which is to avoid HOOK handler that might modify the result columns diff --git a/database/gdb/gdb_core_underlying.go b/database/gdb/gdb_core_underlying.go index 493fb6602..fb7136acb 100644 --- a/database/gdb/gdb_core_underlying.go +++ b/database/gdb/gdb_core_underlying.go @@ -48,8 +48,8 @@ func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...inter } } - if c.GetConfig().QueryTimeout > 0 { - ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout) + if c.db.GetConfig().QueryTimeout > 0 { + ctx, _ = context.WithTimeout(ctx, c.db.GetConfig().QueryTimeout) } // Sql filtering. @@ -107,9 +107,9 @@ func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interf } } - if c.GetConfig().ExecTimeout > 0 { + if c.db.GetConfig().ExecTimeout > 0 { var cancelFunc context.CancelFunc - ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout) + ctx, cancelFunc = context.WithTimeout(ctx, c.db.GetConfig().ExecTimeout) defer cancelFunc() } @@ -264,6 +264,7 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp Start: timestampMilli1, End: timestampMilli2, Group: c.db.GetGroup(), + Schema: c.db.GetSchema(), RowsAffected: rowsAffected, IsTransaction: in.IsTransaction, } @@ -333,9 +334,9 @@ func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (stmt *Stmt } } - if c.GetConfig().PrepareTimeout > 0 { + if c.db.GetConfig().PrepareTimeout > 0 { // DO NOT USE cancel function in prepare statement. - ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout) + ctx, _ = context.WithTimeout(ctx, c.db.GetConfig().PrepareTimeout) } // Link execution. diff --git a/database/gdb/gdb_model_cache.go b/database/gdb/gdb_model_cache.go index e4195da2e..7c3635227 100644 --- a/database/gdb/gdb_model_cache.go +++ b/database/gdb/gdb_model_cache.go @@ -14,6 +14,7 @@ import ( "github.com/gogf/gf/v2/internal/json" ) +// CacheOption is options for model cache control in query. type CacheOption struct { // Duration is the TTL for the cache. // If the parameter `Duration` < 0, which means it clear the cache with given `Name`.