first commit
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Migrate creates the tables this application needs, if they don't already
|
||||
// exist. This is intentionally the simplest possible approach
|
||||
// (CREATE TABLE IF NOT EXISTS run on every startup) and is fine for a
|
||||
// learning project with a single table.
|
||||
//
|
||||
// For a real production project you'd want a proper migration tool (e.g.
|
||||
// golang-migrate/migrate) that tracks a version number, supports
|
||||
// incremental "up"/"down" migrations, and can safely evolve a schema that
|
||||
// already has production data in it. This function is a deliberate
|
||||
// shortcut around that complexity for now.
|
||||
func Migrate(ctx context.Context, db *sql.DB) error {
|
||||
_, err := db.ExecContext(ctx, `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL DEFAULT '',
|
||||
google_id VARCHAR(255) NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL
|
||||
)`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("migrate users table: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Package database owns everything related to talking to MySQL: opening
|
||||
// the connection pool and running startup migrations. Nothing outside this
|
||||
// package (and the repositories in internal/models) should import the
|
||||
// MySQL driver directly - that keeps the database technology an
|
||||
// implementation detail hidden behind a plain *sql.DB.
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
// Blank import: we never call anything from this package by name.
|
||||
// Importing it purely for its side effect - its init() function
|
||||
// registers "mysql" as a driver name with database/sql, which is what
|
||||
// lets sql.Open("mysql", ...) below work at all.
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"git.hamidsoltani.com/hamid/go-simple-api/internal/config"
|
||||
)
|
||||
|
||||
// NewMySQL opens a connection pool to MySQL, configures reasonable pool
|
||||
// limits, and verifies connectivity with a real ping before returning -
|
||||
// so a bad connection string / unreachable database fails loudly at
|
||||
// startup instead of silently on the first real query.
|
||||
func NewMySQL(ctx context.Context, cfg config.Config) (*sql.DB, error) {
|
||||
// DSN = Data Source Name. Format: user:password@tcp(host:port)/dbname?options
|
||||
// parseTime=true tells the driver to convert MySQL DATETIME/TIMESTAMP
|
||||
// columns into Go time.Time values automatically.
|
||||
dsn := fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%s)/%s?parseTime=true",
|
||||
cfg.DBUser, cfg.DBPassword, cfg.DBHost, cfg.DBPort, cfg.DBName,
|
||||
)
|
||||
|
||||
// sql.Open does NOT connect yet - it just validates the DSN and
|
||||
// returns a *sql.DB, which represents a pool of connections managed
|
||||
// for you, not a single live connection.
|
||||
db, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open mysql: %w", err)
|
||||
}
|
||||
|
||||
// Pool tuning:
|
||||
// MaxOpenConns - hard ceiling on simultaneous connections, protects
|
||||
// the database from being overwhelmed by this app.
|
||||
// MaxIdleConns - how many connections to keep warm/ready even when
|
||||
// idle, so we don't pay reconnect cost constantly.
|
||||
// ConnMaxLifetime - force connections to be recycled periodically,
|
||||
// useful behind load balancers or if MySQL itself
|
||||
// closes long-lived idle connections.
|
||||
db.SetMaxOpenConns(10)
|
||||
db.SetMaxIdleConns(5)
|
||||
db.SetConnMaxLifetime(5 * time.Minute)
|
||||
|
||||
// Give the ping a hard deadline instead of letting it hang forever if
|
||||
// the database host is unreachable.
|
||||
pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := db.PingContext(pingCtx); err != nil {
|
||||
return nil, fmt.Errorf("ping mysql: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
Reference in New Issue
Block a user