diff --git a/fixtures/create-tasks.fixture.go b/fixtures/create-tasks.fixture.go deleted file mode 100644 index 25cfa1e..0000000 --- a/fixtures/create-tasks.fixture.go +++ /dev/null @@ -1,141 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "math/rand" - "os" - "strings" - "time" - - "git.ksdemosapps.com/kylesoda/pgx-learning/internal/models" - "github.com/jackc/pgx/v5/pgxpool" - "github.com/joho/godotenv" -) - -func createTasksTable(db *pgxpool.Pool, ctx context.Context) error { - createTableSQL := ` -CREATE TABLE IF NOT EXISTS tasks ( - id SERIAL PRIMARY KEY, - text TEXT NOT NULL, - completed BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() -);` - - _, err := db.Exec(ctx, createTableSQL) - if err != nil { - return fmt.Errorf("error creating tasks table: %w", err) - } - - return nil -} - -func truncateTasksTable(db *pgxpool.Pool, ctx context.Context) error { - truncateSQL := `TRUNCATE TABLE tasks RESTART IDENTITY CASCADE;` - _, err := db.Exec(ctx, truncateSQL) - if err != nil { - return fmt.Errorf("error truncating tasks table: %w", err) - } - return nil -} - -func generateTasks(count int) []models.Task { - tasks := make([]models.Task, count) - - for i := 1; i <= count; i++ { - tasks[i-1] = models.Task{ - Text: fmt.Sprintf("random task Nº %v", i), - Completed: rand.Float64() > 0.5, - } - } - - return tasks -} - -func bulkInsertTasks(db *pgxpool.Pool, ctx context.Context, tasks []models.Task) error { - if len(tasks) == 0 { - return nil - } - - var sb strings.Builder - sb.WriteString(`INSERT INTO tasks (text, completed) VALUES `) - - args := make([]any, 0, len(tasks)*2) - - for i, task := range tasks { - if i > 0 { - sb.WriteString(`, `) - } - - fmt.Fprintf(&sb, `($%d, $%d)`, i*2+1, i*2+2) - args = append(args, task.Text, task.Completed) - } - - sb.WriteString(`;`) - - _, err := db.Exec(ctx, sb.String(), args...) - - if err != nil { - return fmt.Errorf("error bulk inserting tasks: %w", err) - } - - return nil -} - -func main() { - err := godotenv.Load() - if err != nil { - log.Println("Warning: could not load .env file") - } - - dbURL := os.Getenv("PG_FROM_DB_URL") - if dbURL == "" { - log.Fatal("PG_FROM_DB_URL environment variable not set") - } - - ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) - defer cancel() - - db, err := pgxpool.New(ctx, dbURL) - if err != nil { - log.Fatalf("Unable to create connection pool: %v", err) - } - defer db.Close() - - fmt.Println("Creating tasks table...") - err = createTasksTable(db, ctx) - if err != nil { - log.Fatalf("Failed to create table: %v", err) - } - fmt.Println("✓ Tasks table created/already exists") - - fmt.Println("Clearing existing data...") - err = truncateTasksTable(db, ctx) - if err != nil { - log.Fatalf("Failed to truncate table: %v", err) - } - fmt.Println("✓ Table cleared") - - fmt.Println("Generating 1000 tasks records...") - tasks := generateTasks(1000) - fmt.Println("✓ Tasks generated") - - fmt.Println("Performing bulk insert...") - err = bulkInsertTasks(db, ctx, tasks) - if err != nil { - log.Fatalf("Failed to bulk insert tasks: %v", err) - } - fmt.Println("✓ All 1000 task records inserted successfully") - - var count int - err = db.QueryRow(ctx, "SELECT COUNT(*) FROM tasks;").Scan(&count) - if err != nil { - log.Printf("Warning: could not verify record count: %v", err) - } else { - fmt.Printf("✓ Final task count: %d\n", count) - } - - fmt.Println("\nFixture creation completed successfully!") -} diff --git a/internal/config/main.go b/internal/config/main.go new file mode 100644 index 0000000..6bd7141 --- /dev/null +++ b/internal/config/main.go @@ -0,0 +1,34 @@ +package config + +import ( + "log" + "os" + + "github.com/joho/godotenv" +) + +type dbConfig struct { + Url string +} + +func loadEnv() { + err := godotenv.Load() + if err != nil { + log.Println("Warning: could not load .env file") + } +} + +func getDbConfig() *dbConfig { + loadEnv() + + dbUrl := os.Getenv("PG_FROM_DB_URL") + if dbUrl == "" { + log.Fatal("PG_FROM_DB_URL environment variable not set") + } + + return &dbConfig{ + Url: dbUrl, + } +} + +var Db *dbConfig = getDbConfig() diff --git a/scripts/reset-db/main.go b/scripts/reset-db/main.go new file mode 100644 index 0000000..275a28f --- /dev/null +++ b/scripts/reset-db/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "git.ksdemosapps.com/kylesoda/pgx-learning/internal/config" + "github.com/jackc/pgx/v5/pgxpool" +) + +func dropTables(db *pgxpool.Pool, ctx context.Context) error { + dropTableSQL := `DROP TABLE IF EXISTS tasks` + + _, err := db.Exec(ctx, dropTableSQL) + if err != nil { + return fmt.Errorf("error droping tasks table: %w", err) + } + + return nil +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + + db, err := pgxpool.New(ctx, config.Db.Url) + if err != nil { + log.Fatalf("Unable to create connection pool: %v", err) + } + + if err := dropTables(db, ctx); err != nil { + log.Fatalf("Unexpected error: %v", err) + } else { + fmt.Println("Database reset completed succesfully") + } + + defer db.Close() +} diff --git a/scripts/seed-db/main.go b/scripts/seed-db/main.go new file mode 100644 index 0000000..3e29c74 --- /dev/null +++ b/scripts/seed-db/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "context" + "fmt" + "log" + "math/rand" + "time" + + "git.ksdemosapps.com/kylesoda/pgx-learning/internal/config" + "git.ksdemosapps.com/kylesoda/pgx-learning/internal/models" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" +) + +func generateTasks(count int) []models.Task { + tasks := make([]models.Task, count) + + for i := 1; i <= count; i++ { + tasks[i-1] = models.Task{ + Text: fmt.Sprintf("random task Nº %v", i), + Completed: rand.Float64() > 0.5, + } + } + + return tasks +} + +func saveTasks(db *pgxpool.Pool, ctx context.Context, tasks []models.Task) error { + start := time.Now() + + rowsCopied, err := db.CopyFrom( + ctx, + pgx.Identifier{"tasks"}, + []string{"text", "completed"}, + pgx.CopyFromSlice(len(tasks), func(i int) ([]any, error) { + return []any{ + (tasks)[i].Text, + (tasks)[i].Completed, + }, nil + }), + ) + + if err != nil { + return fmt.Errorf("Error al realizar el CopyFrom: %w", err) + } + + duration := time.Since(start) + fmt.Printf("Se insertaron exitosamente %d registros en %v\n", rowsCopied, duration) + + return nil +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + + db, err := pgxpool.New(ctx, config.Db.Url) + if err != nil { + log.Fatalf("Unable to create connection pool: %v", err) + } + defer db.Close() + + count := 100000 + fmt.Printf("Generando %d registros...\n", count) + tasks := generateTasks(count) + + if err := saveTasks(db, ctx, tasks); err != nil { + log.Fatalf("Unexpected error: %v", err) + } else { + fmt.Println("Database seed completed succesfully") + } +} diff --git a/scripts/setup-db/main.go b/scripts/setup-db/main.go new file mode 100644 index 0000000..170ca7d --- /dev/null +++ b/scripts/setup-db/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "git.ksdemosapps.com/kylesoda/pgx-learning/internal/config" + "github.com/jackc/pgx/v5/pgxpool" +) + +func createTasksTable(db *pgxpool.Pool, ctx context.Context) error { + createTableSQL := ` +CREATE TABLE IF NOT EXISTS tasks ( + id BIGSERIAL PRIMARY KEY, + text TEXT NOT NULL, + completed BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ +); +` + + _, err := db.Exec(ctx, createTableSQL) + if err != nil { + return fmt.Errorf("error creating tasks table: %w", err) + } + + return nil +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + + db, err := pgxpool.New(ctx, config.Db.Url) + if err != nil { + log.Fatalf("Unable to create connection pool: %v", err) + } + + if err := createTasksTable(db, ctx); err != nil { + log.Fatalf("Unexpected error: %v", err) + } else { + fmt.Println("Database setup completed succesfully") + } + + defer db.Close() +}