From 0c5e07e62e2306e37d3e58a6337275d3c1147c6e Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 17 Feb 2024 23:12:04 +0900 Subject: [PATCH 1/7] dsn: make Config.TimeTrancate private --- connection.go | 2 +- dsn.go | 17 ++++++++++++----- dsn_test.go | 2 +- packets.go | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/connection.go b/connection.go index 99eb8a808..c170114fe 100644 --- a/connection.go +++ b/connection.go @@ -251,7 +251,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin buf = append(buf, "'0000-00-00'"...) } else { buf = append(buf, '\'') - buf, err = appendDateTime(buf, v.In(mc.cfg.Loc), mc.cfg.TimeTruncate) + buf, err = appendDateTime(buf, v.In(mc.cfg.Loc), mc.cfg.timeTruncate) if err != nil { return "", err } diff --git a/dsn.go b/dsn.go index ce5d85ff0..caef09a27 100644 --- a/dsn.go +++ b/dsn.go @@ -34,6 +34,8 @@ var ( // If a new Config is created instead of being parsed from a DSN string, // the NewConfig function should be used, which sets default values. type Config struct { + // non boolean fields + User string // Username Passwd string // Password (requires User) Net string // Network (e.g. "tcp", "tcp6", "unix". default: "tcp") @@ -45,15 +47,15 @@ type Config struct { Loc *time.Location // Location for time.Time values MaxAllowedPacket int // Max packet size allowed ServerPubKey string // Server public key name - pubKey *rsa.PublicKey // Server public key TLSConfig string // TLS configuration name TLS *tls.Config // TLS configuration, its priority is higher than TLSConfig - TimeTruncate time.Duration // Truncate time.Time values to the specified duration Timeout time.Duration // Dial timeout ReadTimeout time.Duration // I/O read timeout WriteTimeout time.Duration // I/O write timeout Logger Logger // Logger + // boolean fields + AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE AllowCleartextPasswords bool // Allows the cleartext client side plugin AllowFallbackToPlaintext bool // Allows fallback to unencrypted connection if server does not support TLS @@ -66,6 +68,11 @@ type Config struct { MultiStatements bool // Allow multiple statements in one query ParseTime bool // Parse time values to time.Time RejectReadOnly bool // Reject read-only connections + + // private fields. new options should be come here + + pubKey *rsa.PublicKey // Server public key + timeTruncate time.Duration // Truncate time.Time values to the specified duration } // NewConfig creates a new Config and sets default values. @@ -263,8 +270,8 @@ func (cfg *Config) FormatDSN() string { writeDSNParam(&buf, &hasParam, "parseTime", "true") } - if cfg.TimeTruncate > 0 { - writeDSNParam(&buf, &hasParam, "timeTruncate", cfg.TimeTruncate.String()) + if cfg.timeTruncate > 0 { + writeDSNParam(&buf, &hasParam, "timeTruncate", cfg.timeTruncate.String()) } if cfg.ReadTimeout > 0 { @@ -509,7 +516,7 @@ func parseDSNParams(cfg *Config, params string) (err error) { // time.Time truncation case "timeTruncate": - cfg.TimeTruncate, err = time.ParseDuration(value) + cfg.timeTruncate, err = time.ParseDuration(value) if err != nil { return } diff --git a/dsn_test.go b/dsn_test.go index 75cbda700..dd8cd935c 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -76,7 +76,7 @@ var testDSNs = []struct { &Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true}, }, { "user:password@/dbname?loc=UTC&timeout=30s&parseTime=true&timeTruncate=1h", - &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, Timeout: 30 * time.Second, ParseTime: true, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TimeTruncate: time.Hour}, + &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, Timeout: 30 * time.Second, ParseTime: true, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, timeTruncate: time.Hour}, }, } diff --git a/packets.go b/packets.go index e5a6e4727..3d6e5308c 100644 --- a/packets.go +++ b/packets.go @@ -1172,7 +1172,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { if v.IsZero() { b = append(b, "0000-00-00"...) } else { - b, err = appendDateTime(b, v.In(mc.cfg.Loc), mc.cfg.TimeTruncate) + b, err = appendDateTime(b, v.In(mc.cfg.Loc), mc.cfg.timeTruncate) if err != nil { return err } From 5c9a97bda0a4308771a7083ac0c91c13feeddc54 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 17 Feb 2024 23:26:27 +0900 Subject: [PATCH 2/7] make Config.TimeTruncate a functional option --- dsn.go | 33 +++++++++++++++++++++++++++++---- result.go | 5 ++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/dsn.go b/dsn.go index caef09a27..83dc8d874 100644 --- a/dsn.go +++ b/dsn.go @@ -71,19 +71,44 @@ type Config struct { // private fields. new options should be come here - pubKey *rsa.PublicKey // Server public key - timeTruncate time.Duration // Truncate time.Time values to the specified duration + pubKey *rsa.PublicKey // Server public key + timeTruncate time.Duration // Truncate time.Time values to the specified duration } +// Functional Options Pattern +// https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis +type option func(*Config) error + // NewConfig creates a new Config and sets default values. -func NewConfig() *Config { - return &Config{ +func NewConfig(opts ...option) *Config { + cfg := &Config{ Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, } + + cfg.SetOptions(opts...) + return cfg +} + +func (c *Config) SetOptions(opts ...option) error { + for _, opt := range opts { + err := opt(c) + if err != nil { + return err + } + } +} + +// TimeTruncate sets the time duration to truncate time.Time values in +// query parameters. +func TimeTruncate(d time.Duration) option { + return func(cfg *Config) error { + cfg.timeTruncate = d + return nil + } } func (cfg *Config) Clone() *Config { diff --git a/result.go b/result.go index 36a432e81..d51631468 100644 --- a/result.go +++ b/result.go @@ -15,9 +15,8 @@ import "database/sql/driver" // This is accessible by executing statements using sql.Conn.Raw() and // downcasting the returned result: // -// res, err := rawConn.Exec(...) -// res.(mysql.Result).AllRowsAffected() -// +// res, err := rawConn.Exec(...) +// res.(mysql.Result).AllRowsAffected() type Result interface { driver.Result // AllRowsAffected returns a slice containing the affected rows for each From f4c212eee0317ce2e53ea7d97fe7900d5ff29f1c Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 17 Feb 2024 23:39:13 +0900 Subject: [PATCH 3/7] fixup --- dsn.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dsn.go b/dsn.go index 83dc8d874..e9ef038ee 100644 --- a/dsn.go +++ b/dsn.go @@ -100,6 +100,7 @@ func (c *Config) SetOptions(opts ...option) error { return err } } + return nil } // TimeTruncate sets the time duration to truncate time.Time values in @@ -543,7 +544,7 @@ func parseDSNParams(cfg *Config, params string) (err error) { case "timeTruncate": cfg.timeTruncate, err = time.ParseDuration(value) if err != nil { - return + return fmt.Errorf("invalid timeTruncate value: %v, error: %w", value, err) } // I/O read Timeout From 5f2654f07eabf76810f783dd93b3f601ba8edd83 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 26 Feb 2024 18:50:48 +0900 Subject: [PATCH 4/7] revert NewConfig incompatible change --- dsn.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dsn.go b/dsn.go index e9ef038ee..2d2b7736c 100644 --- a/dsn.go +++ b/dsn.go @@ -80,7 +80,7 @@ type Config struct { type option func(*Config) error // NewConfig creates a new Config and sets default values. -func NewConfig(opts ...option) *Config { +func NewConfig() *Config { cfg := &Config{ Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, @@ -89,7 +89,6 @@ func NewConfig(opts ...option) *Config { CheckConnLiveness: true, } - cfg.SetOptions(opts...) return cfg } From e42533ef361af8ca3dc963225a29007d26d649c4 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 26 Feb 2024 18:52:31 +0900 Subject: [PATCH 5/7] make Option public, and rename SetOptions to Apply --- dsn.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsn.go b/dsn.go index 2d2b7736c..a4b4dff22 100644 --- a/dsn.go +++ b/dsn.go @@ -77,7 +77,7 @@ type Config struct { // Functional Options Pattern // https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis -type option func(*Config) error +type Option func(*Config) error // NewConfig creates a new Config and sets default values. func NewConfig() *Config { @@ -92,7 +92,7 @@ func NewConfig() *Config { return cfg } -func (c *Config) SetOptions(opts ...option) error { +func (c *Config) Apply(opts ...Option) error { for _, opt := range opts { err := opt(c) if err != nil { @@ -104,7 +104,7 @@ func (c *Config) SetOptions(opts ...option) error { // TimeTruncate sets the time duration to truncate time.Time values in // query parameters. -func TimeTruncate(d time.Duration) option { +func TimeTruncate(d time.Duration) Option { return func(cfg *Config) error { cfg.timeTruncate = d return nil From 86075b6cee877cb7f35bbd3b7205b3d493e08e7b Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 26 Feb 2024 22:35:24 +0900 Subject: [PATCH 6/7] add doc comment --- dsn.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dsn.go b/dsn.go index a4b4dff22..82006e2d7 100644 --- a/dsn.go +++ b/dsn.go @@ -92,6 +92,7 @@ func NewConfig() *Config { return cfg } +// Apply applies the given options to the Config object. func (c *Config) Apply(opts ...Option) error { for _, opt := range opts { err := opt(c) From 5b603c9080a96c10ff699f1d0a89e77d6853f6b3 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 27 Feb 2024 10:18:46 +0900 Subject: [PATCH 7/7] private -> unexported --- dsn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsn.go b/dsn.go index 82006e2d7..d0fbf3bd9 100644 --- a/dsn.go +++ b/dsn.go @@ -69,7 +69,7 @@ type Config struct { ParseTime bool // Parse time values to time.Time RejectReadOnly bool // Reject read-only connections - // private fields. new options should be come here + // unexported fields. new options should be come here pubKey *rsa.PublicKey // Server public key timeTruncate time.Duration // Truncate time.Time values to the specified duration