Documentation
¶
Overview ¶
Package norm is a lightweight SQL query builder for Go structs.
It generates SQL fragments (field lists, bind parameters, WHERE conditions, full SELECT/INSERT/UPDATE/DELETE queries) from struct definitions using reflection. It is not an ORM — it does not execute queries or manage connections. You compose the generated SQL with any PostgreSQL driver (pgx, database/sql, etc.).
Quick start ¶
type User struct {
Id int `norm:"pk"`
Name string
Email string
}
orm := norm.NewNorm(nil)
orm.AddModel(&User{}, "users")
// Sync tables (create/add columns)
mig := migrate.New(db, orm)
mig.Sync(ctx)
// SELECT
user := User{}
m, _ := orm.M(&user)
sql, args, _ := m.Select(norm.Where("id = ?", 42))
_ = pool.QueryRow(ctx, sql, args...).Scan(m.Pointers()...)
// INSERT
sql, vals, _ := m.Insert(norm.Exclude("id"))
_, _ = pool.Exec(ctx, sql, vals...)
Field and table naming ¶
All names are automatically converted to snake_case:
UserProfile → user_profile (table) UserName → user_name (column) CreatedAt → created_at (column)
If your database column doesn't follow snake_case, override with dbName:
UserName string `norm:"dbName=username"` // → column "username"
Struct tags ¶
Fields are configured via the "norm" struct tag:
pk — mark as primary key notnull — NOT NULL constraint unique — UNIQUE constraint default=val — DEFAULT value dbName=name — override column name dbType=type — override PostgreSQL type fk=Model — foreign key (accepts CamelCase, camelCase, snake_case) - — skip field entirely
Configuration ¶
Config controls default PostgreSQL types and JSON codec:
orm := norm.NewNorm(&norm.Config{
DefaultString: "varchar", // default: "text"
DefaultTime: "timestamp", // default: "timestamptz"
DefaultJSON: "json", // default: "jsonb"
JSONMarshal: sonic.Marshal, // default: encoding/json
JSONUnmarshal: sonic.Unmarshal,
})
Thread safety ¶
Struct metadata is cached and shared safely across goroutines. Model is not safe for concurrent use — each goroutine should call Norm.M to get its own Model instance.
JSON fields ¶
Struct and *struct fields (except time.Time) are automatically marshaled to JSON when writing (Model.Values, Model.Insert, Model.Update) and unmarshaled when reading (Model.Pointers).
Index ¶
- func Binds(count int) string
- func BuildWhere(startBind int, where string, args ...any) (string, []any)
- func Prefix(prefix string) prefixOption
- type ComposedOptions
- type Cond
- func Eq(field string, value any) Cond
- func Gt(field string, value any) Cond
- func Gte(field string, value any) Cond
- func In(field string, values ...any) Cond
- func IsNull(field string, isNull bool) Cond
- func Like(field string, value any) Cond
- func Lt(field string, value any) Cond
- func Lte(field string, value any) Cond
- func Ne(field string, value any) Cond
- type Config
- type Field
- type Join
- func (j *Join) Auto(m *Model) *Join
- func (j *Join) AutoLeft(m *Model) *Join
- func (j *Join) Inner(m *Model, on string) *Join
- func (j *Join) Left(m *Model, on string) *Join
- func (j *Join) Limit(limit int) *Join
- func (j *Join) Offset(offset int) *Join
- func (j *Join) Order(orderBy string) *Join
- func (j *Join) Pointers() []any
- func (j *Join) Right(m *Model, on string) *Join
- func (j *Join) Select() (string, []any, error)
- func (j *Join) Where(where string, args ...any) *Join
- type Model
- func (m Model) Binds(opts ...Option) string
- func (m Model) BuildConditions(conds ...Cond) ([]string, []any)
- func (m *Model) Delete(opts ...Option) (string, []any, error)
- func (m Model) FieldByName(name string) (*Field, bool)
- func (m Model) FieldDescriptions() []*Field
- func (m Model) Fields(opts ...Option) string
- func (m *Model) Insert(opts ...Option) (string, []any, error)
- func (m Model) LimitOffset(limit, offset int) string
- func (m Model) NewInstance() any
- func (m Model) OrderBy(orderBy string) string
- func (m Model) Parse(obj any, table string) error
- func (m *Model) Pointer(name string) any
- func (m *Model) Pointers(opts ...Option) []any
- func (m Model) Returning(fields string) string
- func (m *Model) Select(opts ...Option) (string, []any, error)
- func (m Model) Table() string
- func (m *Model) Update(opts ...Option) (string, []any, error)
- func (m Model) UpdateFields(opts ...Option) (string, int)
- func (m *Model) Values(opts ...Option) []any
- type Norm
- type Option
- type OptionType
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Binds ¶
Binds generates a bind placeholder string in "$1, $2, ..." format for the given number of parameters.
norm.Binds(3) // "$1, $2, $3" norm.Binds(0) // ""
func BuildWhere ¶
BuildWhere renders a WHERE clause string, replacing "?" placeholders with "$N" starting from startBind. Returns the rendered string and the args slice unchanged. Useful for building UPDATE queries manually.
set, nextBind := m.UpdateFields(norm.Exclude("id"))
whereStr, whereArgs := norm.BuildWhere(nextBind, "id = ?", user.Id)
Types ¶
type ComposedOptions ¶
type ComposedOptions struct {
Exclude []string
Fields []string
Returning []string
Prefix string
Where *whereOption
AddTargets []any
Offset int
Limit int
OrderBy string
}
ComposedOptions holds the parsed result of all options passed to a method.
func ComposeOptions ¶
func ComposeOptions(opts ...Option) ComposedOptions
ComposeOptions parses a list of Option values into a single ComposedOptions.
type Cond ¶
type Cond interface {
// contains filtered or unexported methods
}
Cond represents a single WHERE condition for [BuildConditions]. Use the constructor functions (Eq, Gt, Like, In, IsNull, etc.) to create conditions.
func Gte ¶
Gte creates a greater-or-equal condition: field >= value.
norm.Gte("age", 18) // age >= $1
func In ¶
In creates an IN (...) condition.
norm.In("id", 1, 2, 3) // id IN ($1, $2, $3)
norm.In("name", "Alice", "Bob") // name IN ($1, $2)
func IsNull ¶
IsNull creates an IS NULL or IS NOT NULL condition.
norm.IsNull("email", true) // email IS NULL
norm.IsNull("email", false) // email IS NOT NULL
func Like ¶
Like creates a LIKE condition: field LIKE value.
norm.Like("name", "%john%") // name LIKE $1
type Config ¶
type Config struct {
// DefaultString sets the PostgreSQL type used for Go string fields
// when no dbType tag is specified. Defaults to "text".
DefaultString string
// DefaultTime sets the PostgreSQL type used for time.Time fields
// when no dbType tag is specified. Defaults to "timestamptz".
DefaultTime string
// DefaultJSON sets the PostgreSQL type used for struct fields
// serialized as JSON when no dbType tag is specified. Defaults to "jsonb".
DefaultJSON string
// JSONMarshal is the function used to marshal struct fields to JSON.
// Defaults to [encoding/json.Marshal]. Replace with a faster
// implementation (sonic, go-json, json-iterator) for better performance.
//
// orm := norm.NewNorm(&norm.Config{
// JSONMarshal: sonic.Marshal,
// })
JSONMarshal func(v any) ([]byte, error)
// JSONUnmarshal is the function used to unmarshal JSON into struct fields.
// Defaults to [encoding/json.Unmarshal].
JSONUnmarshal func(data []byte, v any) error
}
Config holds optional configuration for a Norm instance.
type Field ¶
type Field struct {
// contains filtered or unexported fields
}
Field holds metadata about a single struct field: its Go name, database column name, type, and parsed tag values.
func (*Field) IsJSON ¶
IsJSON reports whether the field should be marshaled/unmarshaled as JSON. A field is JSON if its underlying type is a struct (but not time.Time). Maps and slices are excluded — database drivers handle them natively.
type Join ¶
type Join struct {
// contains filtered or unexported fields
}
Join is a fluent query builder for SELECT queries with JOINs. Column names are automatically prefixed with table names to avoid ambiguity. Join.Pointers collects scan targets from all joined models.
j := norm.NewJoin(mUser).
Inner(mOrder, "orders.user_id = users.id").
Where("users.active = ?", true).
Limit(10)
sql, args, _ := j.Select()
err := row.Scan(j.Pointers()...)
func (*Join) Auto ¶
Auto adds an INNER JOIN with the ON clause resolved automatically from fk struct tags. The FK relationship is searched in both directions. Panics if no FK relationship is found or if the relationship is ambiguous (multiple FKs to the same table) — use Join.Inner in those cases.
// Given: Order has `norm:"fk=User"` on UserId field j.Auto(mOrder) // → INNER JOIN orders ON orders.user_id = users.id
func (*Join) AutoLeft ¶
AutoLeft adds a LEFT JOIN with the ON clause resolved automatically from fk struct tags. See Join.Auto for details on FK resolution.
func (*Join) Inner ¶
Inner adds an INNER JOIN with an explicit ON clause.
j.Inner(mOrder, "orders.user_id = users.id")
func (*Join) Left ¶
Left adds a LEFT JOIN with an explicit ON clause.
j.Left(mOrder, "orders.user_id = users.id")
func (*Join) Order ¶
Order sets the ORDER BY clause. Use raw SQL with table.column format.
j.Order("users.name DESC, orders.total ASC")
func (*Join) Pointers ¶
Pointers returns scan targets from all models in order (base first, then each joined model). Suitable for passing to rows.Scan().
err := row.Scan(j.Pointers()...)
func (*Join) Right ¶
Right adds a RIGHT JOIN with an explicit ON clause.
j.Right(mOrder, "orders.user_id = users.id")
type Model ¶
type Model struct {
// contains filtered or unexported fields
}
Model binds cached metadata to a specific struct instance. It provides methods for building SQL queries and extracting values/pointers from the bound struct.
Model is not safe for concurrent use. Each goroutine should obtain its own Model via Norm.M.
func (Model) Binds ¶
Binds returns a comma-separated list of bind placeholders ($1, $2, ...). Supports Exclude and Fields options.
m.Binds() // "$1, $2, $3"
m.Binds(norm.Exclude("id")) // "$1, $2"
func (Model) BuildConditions ¶
BuildConditions builds SQL WHERE conditions from typed Cond values. Returns a slice of condition strings and a slice of bind values.
Field names accept any format (struct name, camelCase, snake_case). Dot-prefixed names like "u.name" are supported — the prefix is preserved in the output SQL, and the field is looked up without it. Use Prefix to add the same prefix to all conditions at once. Use "field->>jsonKey" for JSON field access.
conds, vals := m.BuildConditions(
norm.Eq("u.name", "John"),
norm.Gte("u.age", 18),
norm.In("o.id", 1, 2, 3),
norm.IsNull("u.deleted_at", true),
)
func (*Model) Delete ¶
Delete builds a full DELETE query and returns the SQL string and WHERE args. Supports Where and Returning options.
sql, args, _ := m.Delete(norm.Where("id = ?", 42))
// "DELETE FROM users WHERE id=$1"
func (Model) FieldByName ¶
FieldByName looks up a field by any name format (struct name, camelCase, or snake_case db name). Returns the Field and true if found.
func (Model) FieldDescriptions ¶
func (m Model) FieldDescriptions() []*Field
FieldDescriptions returns the slice of all Field descriptors for this model.
func (Model) Fields ¶
Fields returns a comma-separated list of column names in snake_case. Supports Exclude, Fields, and Prefix options.
m.Fields() // "id, name, email"
m.Fields(norm.Exclude("id")) // "name, email"
m.Fields(norm.Prefix("u.")) // "u.id, u.name, u.email"
func (*Model) Insert ¶
Insert builds a full INSERT query and returns the SQL string and values from the bound struct. Struct fields are automatically JSON-marshaled. Supports Exclude, Fields, and Returning options.
sql, vals, _ := m.Insert(norm.Exclude("id"), norm.Returning("Id"))
// "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id"
func (Model) LimitOffset ¶
LimitOffset returns a LIMIT/OFFSET clause string. Pass 0 to omit either part.
m.LimitOffset(10, 0) // "LIMIT 10" m.LimitOffset(10, 20) // "LIMIT 10 OFFSET 20"
func (Model) NewInstance ¶
func (m Model) NewInstance() any
NewInstance creates and returns a new zero-value pointer to the struct type that this model represents.
inst := m.NewInstance() // returns *User (zero value)
func (Model) OrderBy ¶
OrderBy validates and renders an ORDER BY clause string. Field names are validated against the model and converted to database column names. Accepts any name format (struct name, camelCase, snake_case). Direction defaults to ASC if omitted. Panics if a field is not found or direction is invalid — these are programmer errors.
m.OrderBy("Name DESC") // "name DESC"
m.OrderBy("Name ASC, Email") // "name ASC, email ASC"
func (Model) Parse ¶
Parse extracts field metadata from obj and stores it in the modelMeta. obj must be a struct or pointer to struct. If table is empty, the table name is derived from the struct name in snake_case.
func (*Model) Pointer ¶
Pointer returns a pointer to a single named field of the bound struct. Panics if the field name is not found — this is a programmer error.
err := row.Scan(m.Pointer("Id"))
func (*Model) Pointers ¶
Pointers returns a slice of pointers to the bound struct's fields, suitable for passing to rows.Scan(). Struct fields (except time.Time) are wrapped in a JSON scanner automatically. Supports Exclude, Fields, and AddTargets options.
err := row.Scan(m.Pointers()...) err := row.Scan(m.Pointers(norm.AddTargets(&totalCount))...)
func (Model) Returning ¶
Returning validates field names and returns a RETURNING clause string (e.g. "RETURNING id, name"). Fields is a comma-separated list of field names in any format (struct name, camelCase, or db name). Returns empty string if fields is empty. Panics if a field is not found — this is a programmer error.
m.Returning("Id") // "RETURNING id"
m.Returning("Id, Email") // "RETURNING id, email"
func (*Model) Select ¶
Select builds a full SELECT query from the bound model. Returns the SQL string, positional arguments, and any error. Supports Exclude, Fields, Prefix, Where, Order, Limit, Offset options.
sql, args, _ := m.Select(
norm.Where("active = ?", true),
norm.Order("Name DESC"),
norm.Limit(10),
)
// "SELECT id, name, email FROM users WHERE active=$1 ORDER BY name DESC LIMIT 10"
func (Model) Table ¶
func (m Model) Table() string
Table returns the database table name for this model.
func (*Model) Update ¶
Update builds a full UPDATE query and returns the SQL string and combined args (SET values followed by WHERE args). Struct fields are automatically JSON-marshaled. Bind numbering is chained: SET uses $1..$N, WHERE continues from $N+1. Supports Exclude, Fields, Where, and Returning options.
sql, args, _ := m.Update(norm.Exclude("id"), norm.Where("id = ?", user.Id))
// "UPDATE users SET name=$1, email=$2 WHERE id=$3"
func (Model) UpdateFields ¶
UpdateFields returns a SET clause string ("name=$1, email=$2") and the next bind parameter number. Supports Exclude and Fields options.
set, nextBind := m.UpdateFields(norm.Exclude("id"))
// set = "name=$1, email=$2", nextBind = 3
type Norm ¶
type Norm struct {
// contains filtered or unexported fields
}
Norm is the entry point for the norm library. It caches struct metadata so that reflection only happens once per type. Create one instance per application and reuse it — it is safe for concurrent use.
func NewNorm ¶
NewNorm creates a new Norm instance. Pass nil for default configuration.
orm := norm.NewNorm(nil)
orm := norm.NewNorm(&norm.Config{DefaultString: "varchar"})
func (*Norm) AddModel ¶
AddModel registers a struct with an explicit table name and returns a Model bound to obj. Panics if obj is not a struct or pointer to struct.
orm.AddModel(&User{}, "app_users")
func (*Norm) FieldsByTable ¶
FieldsByTable returns field descriptors for a registered table. Returns nil if the table is not registered.
func (*Norm) M ¶
M returns a Model bound to obj. obj must be a pointer to a struct.
Struct metadata is cached by type — reflection happens only on the first call for each struct type. Each call returns a new Model bound to the given obj instance.
user := User{Id: 1, Name: "John"}
m, err := orm.M(&user)
type Option ¶
type Option interface {
Type() OptionType
Value() any
}
Option is a functional option for customizing query building methods. Use the constructor functions (Exclude, Fields, Where, etc.) to create options.
func AddTargets ¶
AddTargets creates an option that appends extra scan targets to the pointers returned by Model.Pointers. Useful for scanning computed columns not present in the struct.
var count int ptrs := m.Pointers(norm.AddTargets(&count))
func Exclude ¶
Exclude creates an option that excludes the named fields (comma-separated db column names) from the result.
m.Fields(norm.Exclude("id,password"))
func Fields ¶
Fields creates an option that includes only the named fields (comma-separated db column names).
m.Fields(norm.Fields("name,email"))
func Limit ¶
Limit creates an option that adds a LIMIT clause to a SELECT query.
m.Select(norm.Limit(10))
func Offset ¶
Offset creates an option that adds an OFFSET clause to a SELECT query.
m.Select(norm.Offset(20))
func Order ¶
Order creates an option that adds an ORDER BY clause to a SELECT query. Field names are validated against the model and converted to column names.
m.Select(norm.Order("Name DESC"))
type OptionType ¶
type OptionType int
OptionType identifies the kind of an Option.
const ( ExcludeOption OptionType = iota // Exclude fields by db name FieldsOption // Include only specified fields ReturningOption // RETURNING clause fields PrefixOption // Table alias prefix for field names WhereOption // WHERE clause with ? placeholders AddTargetsOption // Extra scan targets for Pointers OffsetOption // OFFSET value LimitOption // LIMIT value OrderByOption // ORDER BY clause )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package gen generates Go struct source code from PostgreSQL database schemas.
|
Package gen generates Go struct source code from PostgreSQL database schemas. |
|
Package migrate creates and alters PostgreSQL tables to match registered norm models.
|
Package migrate creates and alters PostgreSQL tables to match registered norm models. |