Hone logo
Hone
Problems

Go Database Seeding with a Flexible Configuration

Database seeding is a crucial process for initializing a database with sample or default data. This challenge requires you to build a flexible database seeding utility in Go that can handle various data types and configurations, allowing developers to easily populate their databases for testing, development, or initial deployment.

Problem Description

Your task is to create a Go package that provides a generic mechanism for seeding a database. The seeder should be able to insert data into different tables based on a provided configuration. This configuration should allow specifying the table name, the fields to be populated, and the data for those fields. The seeder should be robust enough to handle different data types and potential errors during insertion.

Key Requirements:

  1. Generic Seeder Function: Create a function (e.g., Seed) that accepts a database connection and a configuration structure.
  2. Configuration Structure: Define a Go struct to represent the seeding configuration. This struct should include:
    • Table Name (string)
    • Fields and their corresponding data (e.g., a map or a slice of structs).
  3. Data Handling: Support seeding with various Go data types (e.g., string, int, bool, time.Time). The seeder should correctly map these to appropriate SQL data types.
  4. Error Handling: Implement proper error handling for database operations, such as connection errors or insertion failures. The Seed function should return an error if any seeding operation fails.
  5. Flexibility: The configuration should allow for multiple records to be inserted into a single table in one Seed call.
  6. Database Agnosticism (Conceptual): While the implementation will likely use a specific database driver (e.g., database/sql with PostgreSQL, MySQL, or SQLite), the core seeding logic should be as abstract as possible to minimize driver-specific code. For this challenge, assume you are working with a standard *sql.DB connection.

Expected Behavior:

When Seed is called with a valid database connection and configuration, the specified data should be inserted into the corresponding tables in the database. If an error occurs during any insertion, the function should stop processing and return the error.

Edge Cases to Consider:

  • Empty seeding configurations.
  • Tables with no columns specified in the configuration.
  • Data types that do not directly map to SQL types (e.g., custom structs without explicit conversion).
  • Database connection already closed or invalid.

Examples

Example 1: Seeding a users table

// Assume db is a valid *sql.DB connection
// Assume a 'users' table exists with columns: id (serial), username (varchar), email (varchar), active (boolean)

config := SeederConfig{
    TableName: "users",
    Records: []map[string]interface{}{
        {"username": "alice", "email": "alice@example.com", "active": true},
        {"username": "bob", "email": "bob@example.com", "active": false},
    },
}

err := Seed(db, config)
// If successful, the 'users' table will contain two records.
// If an error occurs (e.g., duplicate username if username is unique), err will be non-nil.

Example 2: Seeding a products table with different data types

// Assume db is a valid *sql.DB connection
// Assume a 'products' table exists with columns: id (serial), name (varchar), price (decimal), in_stock (boolean), created_at (timestamp)

config := SeederConfig{
    TableName: "products",
    Records: []map[string]interface{}{
        {"name": "Laptop", "price": 1200.50, "in_stock": true, "created_at": time.Now()},
        {"name": "Keyboard", "price": 75.00, "in_stock": false, "created_at": time.Now().Add(-24 * time.Hour)},
    },
}

err := Seed(db, config)
// If successful, the 'products' table will contain two records.

Example 3: Handling an empty configuration

// Assume db is a valid *sql.DB connection

config := SeederConfig{
    TableName: "categories",
    Records:   []map[string]interface{}{}, // Empty records
}

err := Seed(db, config)
// err should be nil. No operations will be performed on the database.

Constraints

  • The Seed function must accept a *sql.DB as its database connection argument.
  • The configuration Records field should be a slice of maps, where keys are column names (strings) and values are the data for that column (interface{}).
  • The seeder should be able to handle standard SQL data types that can be represented by Go's basic types and time.Time.
  • Performance is important, but the primary focus is on correctness and flexibility. Avoid overly complex or inefficient query generation.

Notes

  • You will need to construct SQL INSERT statements dynamically based on the provided configuration.
  • Consider how to handle cases where a record in the configuration might be missing a column that exists in the database table. For simplicity in this challenge, assume that all keys in a record map correspond to columns in the target table.
  • The challenge does not require you to handle auto-generated primary keys; assume they are handled by the database itself.
  • For constructing the INSERT statements, you'll need to generate placeholders for values and then pass the actual values to the database driver's Exec method.
  • A helper function to build the INSERT statement string and its arguments would be beneficial.
Loading editor...
go