Hone logo
Hone
Problems

Go Query Builder

This challenge asks you to implement a basic SQL query builder in Go. A query builder is a useful tool for programmatically constructing SQL queries, which can help prevent SQL injection vulnerabilities and make query construction more readable and maintainable, especially for complex queries.

Problem Description

You need to design and implement a QueryBuilder struct in Go that allows users to construct a SQL SELECT statement. The builder should support adding SELECT fields, specifying a FROM table, and adding WHERE clauses. The goal is to produce a well-formed SQL string from the builder's configuration.

Key Requirements:

  1. NewQueryBuilder(): A constructor function that returns an initialized QueryBuilder.
  2. Select(fields ...string): A method to specify the columns to select. It should accept a variable number of string arguments representing column names. If Select is called multiple times, it should append to the existing selection. If no fields are provided, it should default to selecting all columns (*).
  3. From(table string): A method to specify the table to query from. This method should be called only once.
  4. Where(field string, operator string, value interface{}): A method to add a WHERE clause.
    • field: The column name.
    • operator: The comparison operator (e.g., =, >, <, >=, <=, !=, LIKE).
    • value: The value to compare against. This can be a string, integer, float, boolean, or nil.
    • The builder should handle parameterization for WHERE clause values to prevent SQL injection. You can represent parameterized values using placeholders like ?.
    • If Where is called multiple times, subsequent clauses should be joined by AND.
  5. Build(): A method that returns the generated SQL query string and a slice of the corresponding values for the parameterized WHERE clauses. If the builder is invalid (e.g., From not called), it should return an error.

Expected Behavior:

  • The generated SQL should be correctly formatted and syntactically valid.
  • WHERE clause values should be properly escaped or parameterized.
  • The order of operations in building the query should be logical (SELECT then FROM then WHERE).

Edge Cases to Consider:

  • Calling Build() before From().
  • Calling Select() with no arguments.
  • Adding WHERE clauses with nil values (should translate to IS NULL).
  • Handling different data types for WHERE clause values.

Examples

Example 1: Basic Select with Where Clause

Input:
builder := NewQueryBuilder()
builder.Select("name", "email")
builder.From("users")
builder.Where("id", "=", 123)
query, values := builder.Build()

Output:
Query: "SELECT name, email FROM users WHERE id = ?"
Values: [123]

Explanation:
The query selects "name" and "email" from the "users" table. A WHERE clause filters by "id" equal to 123, represented by a parameterized placeholder.

Example 2: Multiple Where Clauses and Different Operators

Input:
builder := NewQueryBuilder()
builder.Select("product_name", "price")
builder.From("products")
builder.Where("category", "=", "electronics")
builder.Where("price", ">", 500.00)
builder.Where("in_stock", "=", true)
query, values := builder.Build()

Output:
Query: "SELECT product_name, price FROM products WHERE category = ? AND price > ? AND in_stock = ?"
Values: ["electronics", 500.00, true]

Explanation:
This query selects product details from "products" and applies three WHERE conditions joined by "AND". All values are parameterized.

Example 3: Default Select All and NULL Value

Input:
builder := NewQueryBuilder()
builder.From("orders")
builder.Where("shipped_date", "=", nil)
query, values := builder.Build()

Output:
Query: "SELECT * FROM orders WHERE shipped_date IS NULL"
Values: []

Explanation:
When no fields are specified in `Select`, it defaults to `*`. A `nil` value in the WHERE clause correctly translates to `IS NULL`.

Example 4: Multiple Select Calls

Input:
builder := NewQueryBuilder()
builder.Select("first_name")
builder.Select("last_name")
builder.From("customers")
query, values := builder.Build()

Output:
Query: "SELECT first_name, last_name FROM customers"
Values: []

Explanation:
Calling `Select` multiple times appends to the list of selected fields.

Constraints

  • The QueryBuilder should support standard SQL data types for WHERE clause values: string, int, float64, bool, and nil.
  • The builder should handle up to 100 WHERE clauses efficiently.
  • The generated SQL string should not exceed 1024 characters.
  • The Build() method must return an error if From() has not been called.

Notes

  • Consider how to store the selected fields, table name, and WHERE clauses within the QueryBuilder struct.
  • You will need to manage the generation of placeholders for the WHERE clause values.
  • Think about how to handle different types of values when constructing the SQL string and the values slice. For example, how would you represent a boolean true in SQL?
  • The interface{} type in Go is useful for accepting a wide range of value types in the Where method.
  • For WHERE clauses, ensure that string values are enclosed in single quotes in the final SQL, unless they are part of a placeholder. The builder should abstract this away.
Loading editor...
go