Skip to content

Support INSERT ... DEFAULT VALUES for all-defaulted records #51

@klaidliadon

Description

@klaidliadon

Problem

StatementBuilder.InsertRecord and InsertRecords reject records that produce zero columns from Map (e.g. a struct where every field is tagged ,omitempty or ,omitzero and every value is the type's zero). The rejection is a build-time error suggesting sq.Expr("INSERT INTO ... DEFAULT VALUES") as the workaround, but that bypasses the type-safe builder.

The "every column wants its DB default" use case is legitimate (think a row identified only by an INSERT ... RETURNING id flow with all-defaulted columns). The right fix is to emit INSERT INTO <table> DEFAULT VALUES natively.

Why not in #50

The omitzero PR rejects empty-column records with a clear error pointing at sq.Expr as the escape hatch. Supporting DEFAULT VALUES natively requires more invasive work:

  • Squirrel's sq.InsertBuilder doesn't support DEFAULT VALUES natively; calling .ToSql() on an Insert without Columns/Values errors with "insert statements must have at least one set of values or select clause".
  • pgkit's InsertBuilder embeds sq.InsertBuilder, so the chain methods (.Suffix(...), .ToSql(), etc.) inherit from squirrel.
  • A proper fix needs either:
    1. An override path on pgkit's InsertBuilder.ToSql that swaps to a raw sq.Expr(...) when cols is empty, plus matching override for .Suffix(...) so RETURNING clauses still compose, or
    2. A separate constructor (InsertRecordDefaults?) that returns a different shape and skips the squirrel embed entirely.

Either path expands pgkit's API surface beyond what the omitzero PR justified.

Proposed work

  1. Decide between the override path (option 1 above) or a separate constructor (option 2). Lean override — keeps the InsertBuilder return type stable, callers don't have to branch.
  2. Implement, with .Suffix(...) composition working for RETURNING.
  3. Drop the len(cols) == 0 rejection in builder.go and replace with the native path.
  4. Add round-trip tests against PostgreSQL exercising an all-default INSERT + RETURNING flow.

Repro

type AllDefaults struct {
    ID   int64     `db:"id,omitempty"`
    Tags []string  `db:"tags,omitzero"`
}
sb.InsertRecord(&AllDefaults{}, "items") // errors: "Map returned no columns..."

Tests catching the current behavior: TestInsertRecord_EmptyColumnsRejected, TestInsertRecords_EmptyColumnsRejected (added in #50).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions