Here is an implementation of the interfaces tailored for PostgreSQL or MySQL:
package sql
import (
"context"
"database/sql"
)
// Client represents a connection to a SQL database
type Client interface {
Connect(ctx context.Context) error
Disconnect(ctx context.Context) error
DB(name string) Database
}
// Database represents a specific database within the client
type Database interface {
Table(name string) Table
Client() Client
}
// Table represents a table in the database and provides CRUD operations
type Table interface {
InsertOne(ctx context.Context, data interface{}) (interface{}, error)
InsertMany(ctx context.Context, data []interface{}) ([]interface{}, error)
Update(ctx context.Context, where string, data interface{}, args ...interface{}) (int64, error)
Delete(ctx context.Context, where string, args ...interface{}) (int64, error)
SelectOne(ctx context.Context, query string, dest interface{}, args ...interface{}) error
Select(ctx context.Context, query string, dest interface{}, args ...interface{}) ([]interface{}, error)
}
// sqlClient implements the Client interface for SQL databases
type sqlClient struct {
db *sql.DB
}
// sqlDatabase implements the Database interface
type sqlDatabase struct {
db *sql.DB
name string
}
// sqlTable implements the Table interface
type sqlTable struct {
db *sql.DB
name string
}
func NewClient(dsn string) (Client, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
return &sqlClient{db: db}, nil
}
// Connect establishes a connection to the database
func (c *sqlClient) Connect(ctx context.Context) error {
return c.db.PingContext(ctx)
}
// Disconnect closes the database connection
func (c *sqlClient) Disconnect(ctx context.Context) error {
return c.db.Close()
}
// DB returns a Database interface for the specified database name
func (c *sqlClient) DB(name string) Database {
return &sqlDatabase{db: c.db, name: name}
}
// Table returns a Table interface for the specified table name
func (d *sqlDatabase) Table(name string) Table {
return &sqlTable{db: d.db, name: name}
}
// InsertOne inserts a single record into the table and returns the generated ID
func (t *sqlTable) InsertOne(ctx context.Context, data interface{}) (interface{}, error) {
// Implementation would depend on the specific ORM or query builder used
// This is just a placeholder implementation
return nil, nil
}
// InsertMany inserts multiple records into the table and returns the generated IDs
func (t *sqlTable) InsertMany(ctx context.Context, data []interface{}) ([]interface{}, error) {
// Implementation would depend on the specific ORM or query builder used
// This is just a placeholder implementation
return nil, nil
}
// Update updates records in the table based on the where clause and returns the number of affected rows
func (t *sqlTable) Update(ctx context.Context, where string, data interface{}, args ...interface{}) (int64, error) {
// Implementation would depend on the specific ORM or query builder used
// This is just a placeholder implementation
return 0, nil
}
// Delete deletes records from the table based on the where clause and returns the number of affected rows
func (t *sqlTable) Delete(ctx context.Context, where string, args ...interface{}) (int64, error) {
// Implementation would depend on the specific ORM or query builder used
// This is just a placeholder implementation
return 0, nil
}
// SelectOne retrieves a single record from the table based on the query and populates the destination struct
func (t *sqlTable) SelectOne(ctx context.Context, query string, dest interface{}, args ...interface{}) error {
// Implementation would depend on the specific ORM or query builder used
// This is just a placeholder implementation
return nil
}
// Select retrieves multiple records from the table based on the query and populates the destination slice
func (t *sqlTable) Select(ctx context.Context, query string, dest interface{}, args ...interface{}) ([]interface{}, error) {
// Implementation would depend on the specific ORM or query builder used
// This is just a placeholder implementation
return nil, nil
}
Here is an implementation of the interfaces tailored for Redis:
package redis
import (
"context"
"errors"
"github.com/gomodule/redigo/redis"
)
// Client represents a connection to a Redis database
type Client interface {
Connect(ctx context.Context) error
Disconnect(ctx context.Context) error
DB(number int) Database
}
// Database represents a specific database within the client
type Database interface {
Get(key string) (string, error)
Set(key string, value string, expiration time.Duration) error
Del(keys ...string) (int, error)
HGet(hashKey, field string) (string, error)
HSet(hashKey, field string, value string) bool
LPush(listKey string, values ...interface{}) (int, error)
RPop(listKey string) (string, error)
}
// redisClient implements the Client interface for Redis
type redisClient struct {
conn *redis.Pool
}
// redisDatabase implements the Database interface
type redisDatabase struct {
pool *redis.Pool
db int
}
func NewClient(address string, password string, dbNumber int) (Client, error) {
pool := &redis.Pool{
MaxIdle: 3,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
return redis.Dial("tcp", address, redis.Password(password))
},
}
return &redisClient{conn: pool}, nil
}
// Connect establishes a connection to the Redis server
func (c *redisClient) Connect(ctx context.Context) error {
conn := c.conn.Get()
defer conn.Close()
return nil // Redis connections are established on Dial, which is handled by the Pool
}
// Disconnect closes all connections in the pool
func (c *redisClient) Disconnect(ctx context.Context) error {
c.conn.Close()
return nil
}
// DB returns a Database interface for the specified database number
func (c *redisClient) DB(number int) Database {
return &redisDatabase{pool: c.conn, db: number}
}
// Get retrieves the value of a key
func (d *redisDatabase) Get(key string) (string, error) {
conn := d.pool.Get()
defer conn.Close()
result, err := redis.String(conn.Do("GET", key))
if err != nil {
return "", err
}
return result, nil
}
// Set sets the value of a key with an optional expiration time
func (d *redisDatabase) Set(key string, value string, expiration time.Duration) error {
conn := d.pool.Get()
defer conn.Close()
args := []interface{}{key, value}
if expiration != 0 {
args = append(args, "EX", int(expiration.Seconds()))
}
_, err := redis.String(conn.Do("SET", args...))
return err
}
// Del deletes one or more keys
func (d *redisDatabase) Del(keys ...string) (int, error) {
conn := d.pool.Get()
defer conn.Close()
result, err := redis.Int(conn.Do("DEL", keys...))
if err != nil {
return 0, err
}
return result, nil
}
// HGet retrieves the value of a field in a hash
func (d *redisDatabase) HGet(hashKey, field string) (string, error) {
conn := d.pool.Get()
defer conn.Close()
result, err := redis.String(conn.Do("HGET", hashKey, field))
if err != nil {
return "", err
}
return result, nil
}
// HSet sets the value of a field in a hash
func (d *redisDatabase) HSet(hashKey, field, value string) bool {
conn := d.pool.Get()
defer conn.Close()
result, err := redis.Bool(conn.Do("HSET", hashKey, field, value))
if err != nil {
return false
}
return result
}
// LPush adds one or more values to the head of a list
func (d *redisDatabase) LPush(listKey string, values ...interface{}) (int, error) {
conn := d.pool.Get()
defer conn.Close()
result, err := redis.Int(conn.Do("LPUSH", listKey, values...))
if err != nil {
return 0, err
}
return result, nil
}
// RPop removes and returns the last element of a list
func (d *redisDatabase) RPop(listKey string) (string, error) {
conn := d.pool.Get()
defer conn.Close()
result, err := redis.String(conn.Do("RPOP", listKey))
if err != nil {
return "", err
}
return result, nil
}
-
Relational Database Implementation:
- The
Client
interface now represents a connection to a SQL database (PostgreSQL/MySQL). - The
Database
interface provides access to specific databases within the client. - The
Collection
interface has been adapted to represent tables in a relational database, with methods for CRUD operations and queries. - Methods like
FindOne
,InsertOne
,DeleteOne
, etc., have been modified to fit SQL operations.
- The
-
Redis Implementation:
- The
Client
interface now represents a connection to a Redis server. - The
Database
interface provides access to specific Redis databases (by number). - Methods like
Get
,Set
,Del
,HGet
, etc., have been implemented to fit Redis operations.
- The
-
Common Features:
- Both implementations maintain the context-aware design for asynchronous operations.
- They include error handling and proper resource management (e.g., closing connections).
- The interfaces are designed to be flexible and extensible, allowing for additional methods as needed.
These implementations provide a clean and consistent way to interact with different types of databases while maintaining the architectural principles seen in the MongoDB example.