diff --git a/memcache/README.md b/memcache/README.md index 21d39dee1..6d2daeec2 100644 --- a/memcache/README.md +++ b/memcache/README.md @@ -73,6 +73,11 @@ type Config struct { // // Optional. Default is false Reset bool + + // DisableStartupCheck skips the initial connection validation during New. + // + // Optional. Default is false + DisableStartupCheck bool } ``` @@ -80,5 +85,6 @@ type Config struct { ```go var ConfigDefault = Config{ Servers: "127.0.0.1:11211", + DisableStartupCheck: false, } ``` diff --git a/memcache/config.go b/memcache/config.go index 7e34e06c6..cb96483b1 100644 --- a/memcache/config.go +++ b/memcache/config.go @@ -15,6 +15,11 @@ type Config struct { // Optional. Default is false Reset bool + // DisableStartupCheck skips the initial connection validation during New. + // + // Optional. Default is false + DisableStartupCheck bool + // The socket read/write timeout. // // Optional. Default is 100 * time.Millisecond @@ -31,9 +36,10 @@ type Config struct { // ConfigDefault is the default config var ConfigDefault = Config{ - Servers: "127.0.0.1:11211", - timeout: 100 * time.Millisecond, - maxIdleConns: 2, + Servers: "127.0.0.1:11211", + timeout: 100 * time.Millisecond, + maxIdleConns: 2, + DisableStartupCheck: false, } // Helper function to set default values diff --git a/memcache/memcache.go b/memcache/memcache.go index 139da935a..5547c7348 100644 --- a/memcache/memcache.go +++ b/memcache/memcache.go @@ -30,15 +30,17 @@ func New(config ...Config) *Storage { db.Timeout = cfg.timeout db.MaxIdleConns = cfg.maxIdleConns - // Ping database to ensure a connection has been made - if err := db.Ping(); err != nil { - panic(err) - } - - if cfg.Reset { - if err := db.DeleteAll(); err != nil { + if !cfg.DisableStartupCheck { + // Ping database to ensure a connection has been made + if err := db.Ping(); err != nil { panic(err) } + + if cfg.Reset { + if err := db.DeleteAll(); err != nil { + panic(err) + } + } } // Create storage diff --git a/memcache/memcache_test.go b/memcache/memcache_test.go index a6449f28a..4af3cf5cd 100644 --- a/memcache/memcache_test.go +++ b/memcache/memcache_test.go @@ -178,6 +178,16 @@ func Test_Memcache_Reset(t *testing.T) { require.Zero(t, len(result)) } +func Test_Memcache_New_DisableStartupCheck(t *testing.T) { + require.NotPanics(t, func() { + store := New(Config{ + Servers: "127.0.0.1:11210", + DisableStartupCheck: true, + }) + require.NotNil(t, store) + }) +} + func Test_Memcache_Close(t *testing.T) { testStore := newTestStore(t) require.Nil(t, testStore.Close()) diff --git a/postgres/README.md b/postgres/README.md index a49479f2d..068a5d3bc 100644 --- a/postgres/README.md +++ b/postgres/README.md @@ -56,10 +56,10 @@ store := postgres.New() // Initialize custom config store := postgres.New(postgres.Config{ - DB: dbPool, - Table: "fiber_storage", - Reset: false, - GCInterval: 10 * time.Second, + DB: dbPool, + Table: "fiber_storage", + Reset: false, + GCInterval: 10 * time.Second, }) ``` @@ -67,60 +67,65 @@ store := postgres.New(postgres.Config{ ```go // Config defines the config for storage. type Config struct { - // DB pgxpool.Pool object will override connection uri and other connection fields - // - // Optional. Default is nil - DB *pgxpool.Pool - - // Connection string to use for DB. Will override all other authentication values if used - // - // Optional. Default is "" - ConnectionURI string - - // Host name where the DB is hosted - // - // Optional. Default is "127.0.0.1" - Host string - - // Port where the DB is listening on - // - // Optional. Default is 5432 - Port int - - // Server username - // - // Optional. Default is "" - Username string - - // Server password - // - // Optional. Default is "" - Password string - - // Database name - // - // Optional. Default is "fiber" - Database string - - // Table name - // - // Optional. Default is "fiber_storage" - Table string - - // The SSL mode for the connection - // - // Optional. Default is "disable" - SSLMode string - - // Reset clears any existing keys in existing Table - // - // Optional. Default is false - Reset bool - - // Time before deleting expired keys - // - // Optional. Default is 10 * time.Second - GCInterval time.Duration + // DB pgxpool.Pool object will override connection uri and other connection fields + // + // Optional. Default is nil + DB *pgxpool.Pool + + // Connection string to use for DB. Will override all other authentication values if used + // + // Optional. Default is "" + ConnectionURI string + + // Host name where the DB is hosted + // + // Optional. Default is "127.0.0.1" + Host string + + // Port where the DB is listening on + // + // Optional. Default is 5432 + Port int + + // Server username + // + // Optional. Default is "" + Username string + + // Server password + // + // Optional. Default is "" + Password string + + // Database name + // + // Optional. Default is "fiber" + Database string + + // Table name + // + // Optional. Default is "fiber_storage" + Table string + + // The SSL mode for the connection + // + // Optional. Default is "disable" + SSLMode string + + // Reset clears any existing keys in existing Table + // + // Optional. Default is false + Reset bool + + // DisableStartupCheck skips the initial connection validation during New. + // + // Optional. Default is false + DisableStartupCheck bool + + // Time before deleting expired keys + // + // Optional. Default is 10 * time.Second + GCInterval time.Duration } ``` @@ -128,13 +133,14 @@ type Config struct { ```go // ConfigDefault is the default config var ConfigDefault = Config{ - ConnectionURI: "", - Host: "127.0.0.1", - Port: 5432, - Database: "fiber", - Table: "fiber_storage", - SSLMode: "disable", - Reset: false, - GCInterval: 10 * time.Second, + ConnectionURI: "", + Host: "127.0.0.1", + Port: 5432, + Database: "fiber", + Table: "fiber_storage", + SSLMode: "disable", + Reset: false, + DisableStartupCheck: false, + GCInterval: 10 * time.Second, } ``` diff --git a/postgres/config.go b/postgres/config.go index aecbfe973..d817b57e1 100644 --- a/postgres/config.go +++ b/postgres/config.go @@ -61,6 +61,11 @@ type Config struct { // Optional. Default is false Reset bool + // DisableStartupCheck skips the initial connection validation during New. + // + // Optional. Default is false + DisableStartupCheck bool + // Time before deleting expired keys // // Optional. Default is 10 * time.Second @@ -69,14 +74,15 @@ type Config struct { // ConfigDefault is the default config var ConfigDefault = Config{ - ConnectionURI: "", - Host: "127.0.0.1", - Port: 5432, - Database: "fiber", - Table: "fiber_storage", - SSLMode: "disable", - Reset: false, - GCInterval: 10 * time.Second, + ConnectionURI: "", + Host: "127.0.0.1", + Port: 5432, + Database: "fiber", + Table: "fiber_storage", + SSLMode: "disable", + Reset: false, + DisableStartupCheck: false, + GCInterval: 10 * time.Second, } func (c *Config) getDSN() string { diff --git a/postgres/postgres.go b/postgres/postgres.go index 3175906d3..0585b39d6 100644 --- a/postgres/postgres.go +++ b/postgres/postgres.go @@ -68,45 +68,47 @@ func New(config ...Config) *Storage { } } - // Ping database - if err := db.Ping(context.Background()); err != nil { - panic(err) - } - - // Parse out schema in config, if provided - schema := "public" - tableName := cfg.Table - if strings.Contains(cfg.Table, ".") { - schema = strings.Split(cfg.Table, ".")[0] - tableName = strings.Split(cfg.Table, ".")[1] - } - - // Drop table if set to true - if cfg.Reset { - if _, err := db.Exec(context.Background(), fmt.Sprintf(dropQuery, cfg.Table)); err != nil { - db.Close() + if !cfg.DisableStartupCheck { + // Ping database + if err := db.Ping(context.Background()); err != nil { panic(err) } - } - // Determine if table exists - tableExists := false - row := db.QueryRow(context.Background(), fmt.Sprintf(checkTableExistsQuery, schema, tableName)) - var count int - if err := row.Scan(&count); err != nil { - db.Close() - panic(err) - } - tableExists = count > 0 + // Parse out schema in config, if provided + schema := "public" + tableName := cfg.Table + if strings.Contains(cfg.Table, ".") { + schema = strings.Split(cfg.Table, ".")[0] + tableName = strings.Split(cfg.Table, ".")[1] + } - // Init database queries - if !tableExists { - for _, query := range initQuery { - if _, err := db.Exec(context.Background(), fmt.Sprintf(query, cfg.Table)); err != nil { + // Drop table if set to true + if cfg.Reset { + if _, err := db.Exec(context.Background(), fmt.Sprintf(dropQuery, cfg.Table)); err != nil { db.Close() panic(err) } } + + // Determine if table exists + tableExists := false + row := db.QueryRow(context.Background(), fmt.Sprintf(checkTableExistsQuery, schema, tableName)) + var count int + if err := row.Scan(&count); err != nil { + db.Close() + panic(err) + } + tableExists = count > 0 + + // Init database queries + if !tableExists { + for _, query := range initQuery { + if _, err := db.Exec(context.Background(), fmt.Sprintf(query, cfg.Table)); err != nil { + db.Close() + panic(err) + } + } + } } // Create storage @@ -121,7 +123,9 @@ func New(config ...Config) *Storage { sqlGC: fmt.Sprintf("DELETE FROM %s WHERE e <= $1 AND e != 0", cfg.Table), } - store.checkSchema(cfg.Table) + if !cfg.DisableStartupCheck { + store.checkSchema(cfg.Table) + } // Start garbage collector go store.gcTicker() diff --git a/postgres/postgres_test.go b/postgres/postgres_test.go index f20f8f1fd..4a711d0e1 100644 --- a/postgres/postgres_test.go +++ b/postgres/postgres_test.go @@ -202,6 +202,18 @@ func TestNoCreateUser(t *testing.T) { }) } + +func Test_Postgres_New_DisableStartupCheck(t *testing.T) { + require.NotPanics(t, func() { + store := New(Config{ + Host: "127.0.0.1", + Port: 65432, + DisableStartupCheck: true, + }) + require.NotNil(t, store) + defer store.Close() + }) +} func Test_Should_Panic_On_Wrong_Schema(t *testing.T) { testStore := newTestStore(t) defer testStore.Close() diff --git a/redis/README.md b/redis/README.md index 308262bef..974e8f4d7 100644 --- a/redis/README.md +++ b/redis/README.md @@ -180,6 +180,11 @@ type Config struct { // Optional. Default is false Reset bool + // DisableStartupCheck skips the initial connection validation during New. + // + // Optional. Default is false + DisableStartupCheck bool + // TLS Config to use. When set TLS will be negotiated. // // Optional. Default is nil @@ -208,6 +213,7 @@ var ConfigDefault = Config{ URL: "", Database: 0, Reset: false, + DisableStartupCheck: false, TLSConfig: nil, PoolSize: 10 * runtime.GOMAXPROCS(0), Addrs: []string{}, diff --git a/redis/config.go b/redis/config.go index 2f9f549e0..a7711b22f 100644 --- a/redis/config.go +++ b/redis/config.go @@ -68,6 +68,11 @@ type Config struct { // Optional. Default is false Reset bool + // DisableStartupCheck skips the initial connection validation during New. + // + // Optional. Default is false + DisableStartupCheck bool + // TLS Config to use. When set TLS will be negotiated. // // Optional. Default is nil @@ -87,21 +92,22 @@ type Config struct { // ConfigDefault is the default config var ConfigDefault = Config{ - Host: "127.0.0.1", - Port: 6379, - Username: "", - Password: "", - URL: "", - Database: 0, - Reset: false, - TLSConfig: nil, - PoolSize: 10 * runtime.GOMAXPROCS(0), - Addrs: []string{}, - MasterName: "", - ClientName: "", - SentinelUsername: "", - SentinelPassword: "", - IsClusterMode: false, + Host: "127.0.0.1", + Port: 6379, + Username: "", + Password: "", + URL: "", + Database: 0, + Reset: false, + DisableStartupCheck: false, + TLSConfig: nil, + PoolSize: 10 * runtime.GOMAXPROCS(0), + Addrs: []string{}, + MasterName: "", + ClientName: "", + SentinelUsername: "", + SentinelPassword: "", + IsClusterMode: false, } // Helper function to set default values diff --git a/redis/redis.go b/redis/redis.go index e2fc4b0fa..65f6d7c3d 100644 --- a/redis/redis.go +++ b/redis/redis.go @@ -65,16 +65,18 @@ func New(config ...Config) *Storage { IsClusterMode: cfg.IsClusterMode, }) - // Test connection - if err := db.Ping(context.Background()).Err(); err != nil { - panic(err) - } - - // Empty collection if Clear is true - if cfg.Reset { - if err := db.FlushDB(context.Background()).Err(); err != nil { + if !cfg.DisableStartupCheck { + // Test connection + if err := db.Ping(context.Background()).Err(); err != nil { panic(err) } + + // Empty collection if Clear is true + if cfg.Reset { + if err := db.FlushDB(context.Background()).Err(); err != nil { + panic(err) + } + } } // Create new store diff --git a/redis/redis_test.go b/redis/redis_test.go index d97126a7e..7f295aea2 100644 --- a/redis/redis_test.go +++ b/redis/redis_test.go @@ -198,6 +198,19 @@ func Test_Redis_Delete(t *testing.T) { require.Nil(t, keys) } +func Test_Redis_New_DisableStartupCheck(t *testing.T) { + require.NotPanics(t, func() { + store := New(Config{ + Host: "127.0.0.1", + Port: 6390, + Addrs: []string{"127.0.0.1:6390"}, + DisableStartupCheck: true, + }) + require.NotNil(t, store) + _ = store.Close() + }) +} + func Test_Redis_DeleteWithContext(t *testing.T) { var ( key = "john" diff --git a/rueidis/README.md b/rueidis/README.md index 32aa5890a..d0d68ebca 100644 --- a/rueidis/README.md +++ b/rueidis/README.md @@ -184,6 +184,13 @@ type Config struct { // Optional. Default is false Reset bool + // DisableStartupCheck skips the initial connection validation during New. + // When true and client creation fails, New returns a Storage whose + // operations surface the initialization error instead of panicking. + // + // Optional. Default is false + DisableStartupCheck bool + // CacheTTL TTL // // Optional. Default is time.Minute @@ -210,6 +217,7 @@ var ConfigDefault = Config{ DisableCache: false, AlwaysPipelining: true, Reset: false, + DisableStartupCheck: false, CacheTTL: time.Minute, } ``` diff --git a/rueidis/config.go b/rueidis/config.go index 17f83610a..c71937a9d 100644 --- a/rueidis/config.go +++ b/rueidis/config.go @@ -95,6 +95,13 @@ type Config struct { // Optional. Default is false Reset bool + // DisableStartupCheck skips the initial connection validation during New. + // When true and the client cannot be created, New returns a Storage whose + // operations will report the initialization error instead of panicking. + // + // Optional. Default is false + DisableStartupCheck bool + // CacheTTL TTL // // Optional. Default is time.Minute @@ -120,6 +127,7 @@ var ConfigDefault = Config{ DisableCache: false, AlwaysPipelining: true, Reset: false, + DisableStartupCheck: false, CacheTTL: time.Minute, } @@ -194,5 +202,9 @@ func configDefault(config ...Config) Config { cfg.Reset = true } + if userConfig.DisableStartupCheck { + cfg.DisableStartupCheck = true + } + return cfg } diff --git a/rueidis/rueidis.go b/rueidis/rueidis.go index f6c997932..3a26665cb 100644 --- a/rueidis/rueidis.go +++ b/rueidis/rueidis.go @@ -11,7 +11,8 @@ var cacheTTL = time.Second // Storage interface that is implemented by storage providers type Storage struct { - db rueidis.Client + db rueidis.Client + initErr error } // New creates a new rueidis storage @@ -45,7 +46,6 @@ func New(config ...Config) *Storage { } } - // Update config values accordingly and start new Client db, err := rueidis.NewClient(rueidis.ClientOption{ Username: cfg.Username, Password: cfg.Password, @@ -64,19 +64,25 @@ func New(config ...Config) *Storage { AlwaysPipelining: cfg.AlwaysPipelining, }) if err != nil { - panic(err) - } + if cfg.DisableStartupCheck { + return &Storage{initErr: err} + } - // Test connection - if err := db.Do(context.Background(), db.B().Ping().Build()).Error(); err != nil { panic(err) } - // Empty collection if Clear is true - if cfg.Reset { - if err := db.Do(context.Background(), db.B().Flushdb().Build()).Error(); err != nil { + if !cfg.DisableStartupCheck { + // Test connection + if err := db.Do(context.Background(), db.B().Ping().Build()).Error(); err != nil { panic(err) } + + // Empty collection if Clear is true + if cfg.Reset { + if err := db.Do(context.Background(), db.B().Flushdb().Build()).Error(); err != nil { + panic(err) + } + } } // Create new store @@ -87,6 +93,9 @@ func New(config ...Config) *Storage { // GetWithContext gets value by key with context func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) { + if s.db == nil { + return nil, s.initErr + } if len(key) <= 0 { return nil, nil } @@ -104,6 +113,9 @@ func (s *Storage) Get(key string) ([]byte, error) { // SetWithContext sets key with value with context func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error { + if s.db == nil { + return s.initErr + } if len(key) <= 0 || len(val) <= 0 { return nil } @@ -121,6 +133,9 @@ func (s *Storage) Set(key string, val []byte, exp time.Duration) error { // DeleteWithContext deletes key by key with context func (s *Storage) DeleteWithContext(ctx context.Context, key string) error { + if s.db == nil { + return s.initErr + } if len(key) <= 0 { return nil } @@ -134,6 +149,9 @@ func (s *Storage) Delete(key string) error { // ResetWithContext resets all keys with context func (s *Storage) ResetWithContext(ctx context.Context) error { + if s.db == nil { + return s.initErr + } return s.db.Do(ctx, s.db.B().Flushdb().Build()).Error() } @@ -144,6 +162,10 @@ func (s *Storage) Reset() error { // Close the database func (s *Storage) Close() error { + if s.db == nil { + return nil + } + s.db.Close() return nil } diff --git a/rueidis/rueidis_test.go b/rueidis/rueidis_test.go index 24b59ef61..2da9417a0 100644 --- a/rueidis/rueidis_test.go +++ b/rueidis/rueidis_test.go @@ -49,6 +49,17 @@ func newTestStore(t testing.TB, opts ...testredis.Option) *Storage { return New(newConfigFromContainer(t, opts...)) } +func Test_Rueidis_New_DisableStartupCheck(t *testing.T) { + require.NotPanics(t, func() { + store := New(Config{ + InitAddress: []string{"127.0.0.1:6390"}, + DisableStartupCheck: true, + }) + require.NotNil(t, store) + require.NoError(t, store.Close()) + }) +} + func Test_Rueidis_Set(t *testing.T) { var ( key = "john"