enginemongo

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package enginemongo is the MongoDB engine for r3: a full r3.CRUD backed by the official Go driver v2, using reflection-based struct metadata and BSON query building. The drivers/mongo package wraps it as a thin constructor.

Key types:

  • BaseCRUD: full r3.CRUD backed by *mongo.Collection
  • BaseRaw: escape hatch for arbitrary MongoDB operations
  • StructMeta: reflection-based struct metadata (fields, PK, relations, soft-delete)
  • PreparedListQuery: pre-computed BSON filter/sort/pagination from an r3.Query

Field mapping reads `bson` or `r3` struct tags; the primary key defaults to `_id`.

Index

Constants

View Source
const (
	// RelHasMany represents a one-to-many relationship.
	RelHasMany = r3tag.RelHasMany
	// RelBelongsTo represents a many-to-one relationship.
	RelBelongsTo = r3tag.RelBelongsTo
	// RelManyToMany represents a many-to-many relationship via a join collection.
	RelManyToMany = r3tag.RelManyToMany
)

Variables

This section is empty.

Functions

func BSONFilter

func BSONFilter(kv ...any) bson.D

BSONFilter creates a simple bson.D filter from key-value pairs. Usage: BSONFilter("status", "active", "age", bson.D{{"$gt", 18}}).

func NewBaseQuerier

func NewBaseQuerier[T any, ID comparable](coll *mongo.Collection, opts ...r3.Option) r3.Querier[T, ID]

NewBaseQuerier returns a BaseCRUD as a read-only r3.Querier, so callers get a compile-time guarantee they cannot write.

func NewBaseQuerierFromDB

func NewBaseQuerierFromDB[T any, ID comparable](db *mongo.Database, opts ...r3.Option) r3.Querier[T, ID]

NewBaseQuerierFromDB is NewBaseQuerier, deriving the collection name from T.

func RunPreloads

func RunPreloads(
	ctx context.Context,
	db *mongo.Database,
	meta *StructMeta,
	entitiesPtr any,
	preloads r3.Preloads,
) error

RunPreloads loads the requested relations for entities (a pointer to a slice, e.g. *[]City) via key-set $in queries, one per relation:

  • has-many: query children where fk $in (parent IDs), group by FK, assign to the parent's slice field.
  • belongs-to: query targets where _id $in (parent FKs), assign to the parent's pointer field.

Types

type BaseCRUD

type BaseCRUD[T any, ID comparable] struct {
	r3.DefaultsManager

	Collection *mongo.Collection

	Meta   StructMeta
	Config r3.Config
	Raw    *BaseRaw[T, ID]

	// CodecSchema is the minimal schema (codec'd fields, keyed by bson name) that
	// drives value-codec encoding of filter and cursor arguments. Zero when the
	// type declares no codecs.
	CodecSchema r3.Schema
}

BaseCRUD is a full r3.CRUD[T, ID] backed by a *mongo.Collection. ID must match the _id field type (e.g. primitive.ObjectID or string).

func NewBaseCRUD

func NewBaseCRUD[T any, ID comparable](coll *mongo.Collection, opts ...r3.Option) *BaseCRUD[T, ID]

NewBaseCRUD builds a BaseCRUD over the given collection. Accepts optional r3.Option values.

func NewBaseCRUDFromDB

func NewBaseCRUDFromDB[T any, ID comparable](db *mongo.Database, opts ...r3.Option) *BaseCRUD[T, ID]

NewBaseCRUDFromDB builds a BaseCRUD, deriving the collection name from T. Accepts optional r3.Option values.

func (*BaseCRUD[T, ID]) Aggregate added in v0.0.3

func (r *BaseCRUD[T, ID]) Aggregate(ctx context.Context, qarg ...r3.Query) ([]r3.AggregateRow, error)

Aggregate computes grouped aggregates via the pipeline: $match (filters + soft-delete) → $group → $project (flatten group keys) → $match (having) → $sort → $skip/$limit. See r3.Aggregator for the query semantics.

Degradations vs SQL: dotted (nested-path) group fields are unsupported, and COUNT(DISTINCT field) uses $addToSet, materializing the value set per group - fine for the moderate cardinalities aggregates target.

func (*BaseCRUD[T, ID]) AggregateThroughRelation added in v0.0.9

func (r *BaseCRUD[T, ID]) AggregateThroughRelation(
	ctx context.Context, relation string, qarg ...r3.Query,
) ([]r3.AggregateRow, error)

AggregateThroughRelation aggregates the entity's related rows through a declared relation, grouped and folded over the related base collection (the child collection for has-many, the join collection for many-to-many). It mirrors drivers/gorm/relaggregate.go; see r3.RelationAggregator for the query semantics.

Mongo has no server-side join, so owner restriction and target soft-delete exclusion are done as pre-query key sets (like the relationship filters), then the shared $group pipeline runs over the base collection.

func (*BaseCRUD[T, ID]) Count

func (r *BaseCRUD[T, ID]) Count(ctx context.Context, qarg ...r3.Query) (int64, error)

Count returns the number of documents matching the query's filters.

func (*BaseCRUD[T, ID]) Create

func (r *BaseCRUD[T, ID]) Create(ctx context.Context, entity T) (T, error)

Create inserts a document and returns it with its _id. A pre-assigned non-zero _id (e.g. an app-generated UUID string PK, as used by features/history) is stored as-is; a zero _id is excluded from the insert so MongoDB generates one, which is then written back onto the entity.

func (*BaseCRUD[T, ID]) Delete

func (r *BaseCRUD[T, ID]) Delete(ctx context.Context, id ID) error

Delete removes a document by its _id, or soft-deletes it (setting the soft-delete field to now) when the model has one.

func (*BaseCRUD[T, ID]) Get

func (r *BaseCRUD[T, ID]) Get(ctx context.Context, id ID, qarg ...r3.Query) (T, error)

Get retrieves a document by its _id, normalizing not-found to r3.ErrNotFound.

func (*BaseCRUD[T, ID]) HardDelete

func (r *BaseCRUD[T, ID]) HardDelete(ctx context.Context, id ID) error

HardDelete permanently removes a document, bypassing soft-delete.

func (*BaseCRUD[T, ID]) List

func (r *BaseCRUD[T, ID]) List(ctx context.Context, qarg ...r3.Query) ([]T, int64, error)

List retrieves documents matching the query.

func (*BaseCRUD[T, ID]) Patch

func (r *BaseCRUD[T, ID]) Patch(ctx context.Context, entity T, fields r3.Fields) (T, error)

Patch updates only the given fields on the document identified by entity's _id, then re-fetches and returns it.

func (*BaseCRUD[T, ID]) PatchWhere added in v0.0.9

func (r *BaseCRUD[T, ID]) PatchWhere(
	ctx context.Context, filters r3.Filters, entity T, fields r3.Fields,
) (int64, error)

PatchWhere sets fields (taken from entity's values) on every document matching filters and returns the modified-document count - the multi-document Patch. It shares Patch's field validation (ValidatePatchFields: no unknown, ID, or soft-delete field), maps to a single UpdateMany with a $set, and excludes soft-deleted documents by default. Relationship ("has") filters are not supported and return an error. Mirrors engine/sql/bulkpatch.go.

func (*BaseCRUD[T, ID]) Restore

func (r *BaseCRUD[T, ID]) Restore(ctx context.Context, id ID) error

Restore un-deletes a soft-deleted document by unsetting its soft-delete field; errors if the model has none.

func (*BaseCRUD[T, ID]) Update

func (r *BaseCRUD[T, ID]) Update(ctx context.Context, entity T) (T, error)

Update replaces an existing document identified by its _id.

func (*BaseCRUD[T, ID]) Upsert added in v0.0.9

func (r *BaseCRUD[T, ID]) Upsert(ctx context.Context, entity T, opts ...r3.UpsertOption) (T, error)

Upsert inserts entity, or updates the colliding document on a conflict against the conflict target (the _id primary key by default), via UpdateOne with upsert:true.

The conflict target is UpsertSpec.ConflictColumns (a unique-index field set), defaulting to the PK. UpsertSpec.UpdateFields, when set, restricts the fields overwritten on conflict (Patch-validated); empty means a full replace of every non-conflict field. Fields not written by the update branch are still applied on insert via $setOnInsert, so a new document is stored complete. The stored document is re-fetched and returned. Mirrors the SQL semantics in engine/sql/upsert.go.

type BaseRaw

type BaseRaw[T any, ID any] struct {
	Collection *mongo.Collection
	Meta       StructMeta
}

BaseRaw is the escape hatch for arbitrary operations against the underlying collection.

func NewBaseRaw

func NewBaseRaw[T any, ID comparable](coll *mongo.Collection, meta StructMeta) *BaseRaw[T, ID]

NewBaseRaw builds a BaseRaw over coll.

func (*BaseRaw[T, ID]) Aggregate

func (r *BaseRaw[T, ID]) Aggregate(ctx context.Context, pipeline any) ([]T, error)

Aggregate executes an aggregation pipeline and decodes results into T.

func (*BaseRaw[T, ID]) BulkWrite

func (r *BaseRaw[T, ID]) BulkWrite(ctx context.Context, models []mongo.WriteModel) (*mongo.BulkWriteResult, error)

BulkWrite executes a bulk write operation.

func (*BaseRaw[T, ID]) Col

func (r *BaseRaw[T, ID]) Col() *mongo.Collection

Col returns the underlying *mongo.Collection for fully custom usage.

func (*BaseRaw[T, ID]) CountDocuments

func (r *BaseRaw[T, ID]) CountDocuments(ctx context.Context, filter any) (int64, error)

CountDocuments returns the count of documents matching the filter.

func (*BaseRaw[T, ID]) DeleteMany

func (r *BaseRaw[T, ID]) DeleteMany(ctx context.Context, filter any) (*mongo.DeleteResult, error)

DeleteMany deletes all documents matching the filter.

func (*BaseRaw[T, ID]) DeleteOne

func (r *BaseRaw[T, ID]) DeleteOne(ctx context.Context, filter any) (*mongo.DeleteResult, error)

DeleteOne deletes a single document matching the filter.

func (*BaseRaw[T, ID]) Distinct

func (r *BaseRaw[T, ID]) Distinct(ctx context.Context, fieldName string, filter any) *mongo.DistinctResult

Distinct returns the distinct values for a given field.

func (*BaseRaw[T, ID]) Find

func (r *BaseRaw[T, ID]) Find(
	ctx context.Context,
	filter any,
	opts ...options.Lister[options.FindOptions],
) ([]T, error)

Find executes a find query with the given filter and returns matching documents.

func (*BaseRaw[T, ID]) FindOne

func (r *BaseRaw[T, ID]) FindOne(
	ctx context.Context,
	filter any,
	opts ...options.Lister[options.FindOneOptions],
) (T, error)

FindOne executes a find query expected to return a single document.

func (*BaseRaw[T, ID]) FindOneAndDelete

func (r *BaseRaw[T, ID]) FindOneAndDelete(
	ctx context.Context,
	filter any,
	opts ...options.Lister[options.FindOneAndDeleteOptions],
) (T, error)

FindOneAndDelete atomically finds a document and deletes it, returning the deleted document.

func (*BaseRaw[T, ID]) FindOneAndUpdate

func (r *BaseRaw[T, ID]) FindOneAndUpdate(
	ctx context.Context,
	filter any,
	update any,
	opts ...options.Lister[options.FindOneAndUpdateOptions],
) (T, error)

FindOneAndUpdate atomically finds a document and updates it, returning the result.

func (*BaseRaw[T, ID]) InsertOne

func (r *BaseRaw[T, ID]) InsertOne(ctx context.Context, doc any) (*mongo.InsertOneResult, error)

InsertOne inserts a single document.

func (*BaseRaw[T, ID]) UpdateMany

func (r *BaseRaw[T, ID]) UpdateMany(ctx context.Context, filter, update any) (*mongo.UpdateResult, error)

UpdateMany updates all documents matching the filter.

func (*BaseRaw[T, ID]) UpdateOne

func (r *BaseRaw[T, ID]) UpdateOne(ctx context.Context, filter, update any) (*mongo.UpdateResult, error)

UpdateOne updates a single document matching the filter.

type PreparedListQuery

type PreparedListQuery struct {
	Filter     bson.D // combined BSON filter
	Sort       bson.D
	Projection bson.D // field selection

	// IsPaginated marks offset pagination; Limit and Offset (skip) are then set.
	IsPaginated bool
	Limit       int64
	Offset      int64

	// IsCursorPaginated marks cursor/keyset pagination: use CursorFilter and
	// CursorLimit, with no skip and no count query.
	IsCursorPaginated bool
	CursorFilter      bson.D // keyset filter; empty for the first page
	CursorLimit       int64

	// Query is the merged r3.Query, for Preloads, IncludeTrashed, etc.
	Query r3.Query
}

PreparedListQuery holds the BSON-ready pieces (filter, sort, projection, pagination) derived from an r3.Query.

func PrepareListQuery

func PrepareListQuery(dm *r3.DefaultsManager, schema r3.Schema, qarg ...r3.Query) (PreparedListQuery, error)

PrepareListQuery merges defaults with user args, then converts filters, sorts, and pagination into BSON via PrepareMergedListQuery.

func PrepareMergedListQuery added in v0.0.9

func PrepareMergedListQuery(schema r3.Schema, q r3.Query) (PreparedListQuery, error)

PrepareMergedListQuery converts an already-merged query's filters, sorts, and pagination into BSON. Cursor pagination takes precedence over offset. It is the half of PrepareListQuery after the merge, exposed so a driver can transform the query first (e.g. lower relationship "has" filters into key-set In filters).

schema drives value-codec encoding of filter and cursor arguments to stored form (e.g. a time.Time bound against an int column); a zero schema (no codecs) leaves the arguments untouched.

type RelationKind

type RelationKind = r3tag.RelationKind

RelationKind describes the type of relationship between two entities.

type RelationMeta

type RelationMeta struct {
	FieldName  string       // relation name (Go field name, or spec name)
	FieldIndex int          // struct field index for preload assignment; -1 for a declared spec
	Kind       RelationKind // has-many, belongs-to, or many-to-many
	FKField    string       // foreign key field: child-side (has-many), owner-side (belongs-to), owner-side in join (m2m)
	RefField   string       // related-side FK field in the join collection (many-to-many only)
	JoinTable  string       // join collection name (many-to-many only)
	TargetMeta StructMeta   // metadata for the related entity (collection, IDField, SoftDeleteField)
	TargetType reflect.Type // reflect.Type of the target entity (element type); nil for a declared spec
}

RelationMeta holds metadata about a relation - either reflected from a struct field (preloadable) or declared explicitly via r3.WithRelations (FieldIndex -1, TargetType nil).

func RelationMetaFromSpec added in v0.0.9

func RelationMetaFromSpec(spec r3.RelationSpec) RelationMeta

RelationMetaFromSpec converts a core r3.RelationSpec (a relation declared by collection and field names) into a RelationMeta. The target Go type is unknown - the point of an explicit spec - so TargetType is nil and FieldIndex is -1: the relation is filterable and aggregatable but has no struct field to preload into.

type StructMeta

type StructMeta struct {
	CollectionName string   // e.g. "cities"
	Fields         []string // BSON field names in order, e.g. ["_id", "name", ...]
	FieldIndices   []int    // struct field index per BSON field
	IDField        string   // primary key BSON field name (defaults to "_id")
	IDFieldIdx     int      // index into Fields/FieldIndices for the ID entry

	// SoftDeleteField is the soft-delete BSON field name; empty disables soft-delete.
	SoftDeleteField string

	// SoftDeleteZero is the zero value of a non-pointer soft-delete field, which a
	// live record persists (a time.Time stores the zero BSON Date, not null), so
	// the "not deleted" filter must match it as well as null. Nil for a pointer
	// field (live records store null) or when soft-delete is disabled.
	SoftDeleteZero any

	// Codecs maps a BSON field name to the value codec declared on it
	// (r3:"...,codec:name"). Keyed by the physical bson name - not the schema
	// name - because reads/writes and filter args all reference the stored field.
	// Empty when the type declares no codecs.
	Codecs map[string]r3.Codec

	Relations []RelationMeta
}

StructMeta is reflection-based metadata about a struct type T, the MongoDB equivalent of enginesql.StructMeta.

func GetStructMeta

func GetStructMeta[T any]() StructMeta

GetStructMeta derives collection name and field info from T. Field-name priority: `r3` tag, then `bson`, then `db`, then snake_case; a "-" in any tag skips the field.

func WithDeclaredRelations added in v0.0.9

func WithDeclaredRelations(meta StructMeta, specs []r3.RelationSpec) StructMeta

WithDeclaredRelations returns a copy of meta with specs added to its Relations. A declared relation whose name matches a tag-reflected one replaces it (explicit spec wins). The input meta is never mutated (shared and cached); nil/empty specs return meta unchanged.

func (*StructMeta) FieldValues

func (m *StructMeta) FieldValues(entity any) []any

FieldValues extracts the BSON field values from a struct in the same order as Fields.

func (*StructMeta) FieldValuesForFields

func (m *StructMeta) FieldValuesForFields(entity any, fields []string) []any

FieldValuesForFields extracts field values from an entity for the given BSON field names.

func (*StructMeta) HasCodecs added in v0.0.9

func (m *StructMeta) HasCodecs() bool

HasCodecs reports whether the type declares any value codec.

func (*StructMeta) IDValue

func (m *StructMeta) IDValue(entity any) any

IDValue extracts the ID field value from an entity.

func (*StructMeta) NonIDFieldValues

func (m *StructMeta) NonIDFieldValues(entity any) []any

NonIDFieldValues extracts field values excluding the ID, for inserts.

func (*StructMeta) NonIDFields

func (m *StructMeta) NonIDFields() []string

NonIDFields returns all BSON field names except the ID field.

func (*StructMeta) SetFieldsFromMap

func (m *StructMeta) SetFieldsFromMap(entityPtr any, data map[string]any)

SetFieldsFromMap sets struct fields from a map of BSON field name -> value.

func (*StructMeta) SetIDValue

func (m *StructMeta) SetIDValue(entityPtr, val any)

SetIDValue sets the ID field value on an entity (via pointer).

func (*StructMeta) ToBSONDoc

func (m *StructMeta) ToBSONDoc(entity any, includeID bool) map[string]any

ToBSONDoc converts an entity to a BSON document for insert/update, optionally omitting the ID.

func (*StructMeta) ValidatePatchFields

func (m *StructMeta) ValidatePatchFields(fields []string) ([]string, error)

ValidatePatchFields checks that the given field names are valid for a Patch operation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL