diff --git a/config_server.go b/config_server.go index a09c96f..ada2261 100644 --- a/config_server.go +++ b/config_server.go @@ -3,6 +3,7 @@ package env import ( "encoding/json" "errors" + "github.com/alfarih31/nb-go-env/internal" "github.com/joho/godotenv" "log" "os" @@ -34,11 +35,11 @@ func (dc defaultConfigServer) Get(k string) (string, bool) { if dc.useDotEnv { cfg, exist := dc.envs[k] - return cfg, !hasZeroValue(cfg) && exist + return cfg, !internal.HasZeroValue(cfg) && exist } cfg := os.Getenv(k) - return cfg, !hasZeroValue(cfg) + return cfg, !internal.HasZeroValue(cfg) } func NewDefaultConfigServer(envPath string, fallbackToWide ...bool) (ConfigServer, error) { diff --git a/env.go b/env.go index db2ffa8..c062866 100644 --- a/env.go +++ b/env.go @@ -4,32 +4,12 @@ import ( "errors" "fmt" "github.com/alfarih31/nb-go-parser" - "reflect" ) type Err struct { e error } -// HasZeroValue Check a variable has Zero Value -func hasZeroValue(v interface{}) bool { - if v == nil { - return true - } - - t := reflect.TypeOf(v) - if t == nil { - return true - } - - switch t.Kind() { - case reflect.Map, reflect.Slice, reflect.Array: - return false - } - - return v == reflect.Zero(t).Interface() -} - func (e *Err) Errorf(f string, s ...interface{}) *Err { return &Err{ e: errors.New(fmt.Sprintf(fmt.Sprintf("this %s: %s", e.e.Error(), f), s...)), diff --git a/examples/using-sql-config-server/go.mod b/examples/using-sql-config-server/go.mod new file mode 100644 index 0000000..ef173ad --- /dev/null +++ b/examples/using-sql-config-server/go.mod @@ -0,0 +1,10 @@ +module example + +go 1.19 + +require ( + github.com/alfarih31/nb-go-env v1.0.4 // indirect + github.com/alfarih31/nb-go-parser v1.0.11 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/lib/pq v1.10.9 // indirect +) diff --git a/examples/using-sql-config-server/go.sum b/examples/using-sql-config-server/go.sum new file mode 100644 index 0000000..cf32046 --- /dev/null +++ b/examples/using-sql-config-server/go.sum @@ -0,0 +1,8 @@ +github.com/alfarih31/nb-go-env v1.0.4 h1:jIrQUsunn9uUMecqHQaK1lMJ3XgEqa420HQSJkUkTlg= +github.com/alfarih31/nb-go-env v1.0.4/go.mod h1:oPDLWPYpw2X/NVLoJfhr5KzZ3Wy+UPxKktOBIk7TL5s= +github.com/alfarih31/nb-go-parser v1.0.11 h1:jW+pU/UH1pBwP/HSavsGhsVmIzEDS6cvmLiAAheKaao= +github.com/alfarih31/nb-go-parser v1.0.11/go.mod h1:0+2qf5oT5sEy4qyNxY/ewsREpHtUYfis3/bERolDFGI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/examples/using-sql-config-server/main.go b/examples/using-sql-config-server/main.go new file mode 100644 index 0000000..2341065 --- /dev/null +++ b/examples/using-sql-config-server/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "database/sql" + "fmt" + env2 "github.com/alfarih31/nb-go-env" + sql_config_server "github.com/alfarih31/nb-go-env/sql-config-server" + _ "github.com/lib/pq" +) + +const ( + host = "localhost" + port = 5432 + user = "postgres" + password = "postgres" + dbname = "postgres" + namespace = "example" +) + +func CheckError(err error) { + if err != nil { + panic(err) + } +} + +func main() { + psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) + + // open database + db, err := sql.Open("postgres", psqlconn) + CheckError(err) + + // create config server + configService, err := sql_config_server.NewSqlConfigServer(db, "configs", + sql_config_server.WithNamespace(namespace), + sql_config_server.WithTableCreation(sql_config_server.DialectPostgres)) + + CheckError(err) + + // seed the database + insertStmt := fmt.Sprintf(`insert into "configs"("key", "value", "namespace") values('foo', 'ba2', '%s')`, namespace) + _, e := db.Exec(insertStmt) + CheckError(e) + + env, err := env2.LoadWithConfigServer(configService) + CheckError(err) + + fmt.Printf("Config with key 'foo' -> '%s'\n", env.MustGetString("foo")) + + db.Close() +} diff --git a/go.mod b/go.mod index 096a58e..f4daf15 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.0 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index ab081cc..1909cb6 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/go.work b/go.work new file mode 100644 index 0000000..db893f5 --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.19 + +use ( + examples/using-sql-config-server + . +) \ No newline at end of file diff --git a/internal/helper.go b/internal/helper.go new file mode 100644 index 0000000..04dbf3e --- /dev/null +++ b/internal/helper.go @@ -0,0 +1,22 @@ +package internal + +import "reflect" + +// HasZeroValue Check a variable has Zero Value +func HasZeroValue(v interface{}) bool { + if v == nil { + return true + } + + t := reflect.TypeOf(v) + if t == nil { + return true + } + + switch t.Kind() { + case reflect.Map, reflect.Slice, reflect.Array: + return false + } + + return v == reflect.Zero(t).Interface() +} diff --git a/sql-config-server/constants.go b/sql-config-server/constants.go new file mode 100644 index 0000000..8a621e6 --- /dev/null +++ b/sql-config-server/constants.go @@ -0,0 +1,9 @@ +package sql_config_server + +type Dialect uint + +const ( + DialectPostgres Dialect = iota + DialectMysql + DialectSqlite +) diff --git a/sql-config-server/options.go b/sql-config-server/options.go new file mode 100644 index 0000000..ecb1565 --- /dev/null +++ b/sql-config-server/options.go @@ -0,0 +1,15 @@ +package sql_config_server + +type Option func(*sqlConfigServer) + +func WithTableCreation(dialect Dialect) Option { + return func(server *sqlConfigServer) { + server.dialect = &dialect + } +} + +func WithNamespace(ns string) Option { + return func(server *sqlConfigServer) { + server.namespace = &ns + } +} diff --git a/sql-config-server/sql_config_server.go b/sql-config-server/sql_config_server.go new file mode 100644 index 0000000..de3efe3 --- /dev/null +++ b/sql-config-server/sql_config_server.go @@ -0,0 +1,140 @@ +package sql_config_server + +import ( + "database/sql" + "encoding/json" + "errors" + "fmt" + env "github.com/alfarih31/nb-go-env" + "github.com/alfarih31/nb-go-env/internal" + "log" +) + +type sqlConfigServer struct { + tableName string + namespace *string + db *sql.DB + dialect *Dialect +} + +type SqlConfigServer interface { + env.ConfigServer +} + +func (s *sqlConfigServer) Get(key string) (string, bool) { + var r *sql.Row + if s.namespace != nil { + r = s.db.QueryRow(fmt.Sprintf(`SELECT value FROM "%s" WHERE key = '%s' AND namespace = '%s' ORDER BY id DESC LIMIT 1`, s.tableName, key, *s.namespace)) + } else { + r = s.db.QueryRow(fmt.Sprintf(`SELECT value FROM "%s" WHERE key = '%s' ORDER BY id DESC LIMIT 1`, s.tableName, key)) + } + + var v string + if err := r.Scan(&v); err != nil { + log.Println(err) + return "", false + } + + return v, !internal.HasZeroValue(v) +} + +func (s *sqlConfigServer) Dump() (string, error) { + var ( + rs *sql.Rows + err error + ) + if s.namespace != nil { + rs, err = s.db.Query(fmt.Sprintf(`SELECT key, value FROM "%s" WHERE namespace = '%s' ORDER BY id DESC`, s.tableName, *s.namespace)) + } else { + rs, err = s.db.Query(fmt.Sprintf(`SELECT key, value FROM "%s" ORDER BY id DESC`, s.tableName)) + } + + if err != nil { + return "", err + } + + configs := map[string]string{} + for rs.Next() { + var k, v string + if err = rs.Scan(&k, &v); err != nil { + return "", err + } + configs[k] = v + } + + if err = rs.Close(); err != nil { + return "", err + } + + j, e := json.Marshal(configs) + + return string(j), e +} + +func (s *sqlConfigServer) executeTableCreation() error { + if s.dialect != nil { + var createStmt string + switch *s.dialect { + case DialectPostgres: + createStmt = fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS "%s" ( + id SERIAL PRIMARY KEY, + namespace CHARACTER VARYING(511), + key VARCHAR(511) NOT NULL, + value TEXT NOT NULL + );`, s.tableName) + case DialectMysql: + createStmt = fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + namespace VARCHAR(511), + key VARCHAR(511) NOT NULL, + value TEXT NOT NULL + );`, s.tableName) + case DialectSqlite: + createStmt = fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + namespace VARCHAR(511), + key VARCHAR(511) NOT NULL, + value TEXT NOT NULL + );`, s.tableName) + default: + return errors.New("unknown dialect") + } + + _, err := s.db.Exec(createStmt) + if err != nil { + return err + } + + return nil + } + + return nil +} + +func (s *sqlConfigServer) init() error { + if err := s.executeTableCreation(); err != nil { + return err + } + + return nil +} + +func NewSqlConfigServer(db *sql.DB, tableName string, opts ...Option) (SqlConfigServer, error) { + cs := &sqlConfigServer{ + db: db, + tableName: tableName, + } + + for _, opt := range opts { + opt(cs) + } + + if err := cs.init(); err != nil { + return nil, err + } + + return cs, nil +}