From 33c47b2aa89de5279a9d28ba23f0c72e5b3b14a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=98=99=E2=97=A6=20The=20Tablet=20=E2=9D=80=20GamerGirla?= =?UTF-8?q?ndCo=20=E2=97=A6=E2=9D=A7?= Date: Mon, 14 Jul 2025 17:24:30 -0400 Subject: [PATCH] add some documentation --- diamond.go | 17 ++++++++++++++--- document.go | 3 +++ errors.go | 2 +- field.go | 22 +++++++++++----------- model.go | 31 +++++++++---------------------- model_internals.go | 2 +- query.go | 39 +++++++++++++++++++++++++++++---------- query_populate.go | 3 +++ query_tail.go | 8 ++++++++ relationship.go | 33 +++++++++++++++++++++------------ 10 files changed, 100 insertions(+), 60 deletions(-) diff --git a/diamond.go b/diamond.go index ffb4b0a..8aaaeac 100644 --- a/diamond.go +++ b/diamond.go @@ -12,13 +12,14 @@ import ( "time" ) +// LevelQuery enables logging of SQL queries if passed to Config.LogLevel const LevelQuery = slog.Level(-6) const defaultKey = "default" type Config struct { - DryRun bool - LogLevel slog.Level - LogTo io.Writer + DryRun bool // when true, queries will not run on the underlying database + LogLevel slog.Level // controls the level of information logged; defaults to slog.LevelInfo if not set + LogTo io.Writer // where to write log output to; defaults to os.Stdout } type Engine struct { @@ -34,6 +35,7 @@ type Engine struct { connStr string } +// Models - parse and register one or more types as persistable models func (e *Engine) Models(v ...any) { emm := makeModelMap(v...) for k := range emm.Map { @@ -45,6 +47,8 @@ func (e *Engine) Models(v ...any) { } } +// Model - createes a Query and sets its model to +// the one corresponding to the type of `val` func (e *Engine) Model(val any) *Query { qq := &Query{ engine: e, @@ -57,10 +61,12 @@ func (e *Engine) Model(val any) *Query { return qq.setModel(val) } +// QueryRaw - wrapper for the Query method of pgxpool.Pool func (e *Engine) QueryRaw(sql string, args ...any) (pgx.Rows, error) { return e.conn.Query(e.ctx, sql, args...) } +// Migrate - non-destructive; run migrations to update the underlying schema, WITHOUT dropping tables beforehand func (e *Engine) Migrate() error { failedMigrations := make(map[string]*Model) var err error @@ -83,6 +89,8 @@ func (e *Engine) Migrate() error { return err } +// MigrateDropping - destructive migration; DROP the necessary tables if they exist, +// then recreate them to match your models' schema func (e *Engine) MigrateDropping() error { for _, m := range e.modelMap.Map { sql := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE;", m.TableName) @@ -109,10 +117,13 @@ func (e *Engine) logSql(msg, sql string) { e.logger.Log(e.ctx, LevelQuery, msg, "sql", sql) } +// Disconnect - closes and disposes of this Engine's connection pool. func (e *Engine) Disconnect() { e.conn.Close() } +// Open - creates a new connection according to `connString` +// and returns a brand new Engine to run FUCK operations on. func Open(connString string, cfg *Config) (*Engine, error) { if cfg == nil { cfg = &Config{ diff --git a/document.go b/document.go index a0dc344..efa5bcc 100644 --- a/document.go +++ b/document.go @@ -2,11 +2,14 @@ package orm import "time" +// Document - embed this into your structs anonymously to specify +// model parameters like table name via struct tags type Document struct { Created time.Time `json:"createdAt" tstype:"Date"` Modified time.Time `json:"modifiedAt" tstype:"Date"` } +// SaveOptions - unused (for now) type SaveOptions struct { SetTimestamps bool } diff --git a/errors.go b/errors.go index dbd5866..fa65837 100644 --- a/errors.go +++ b/errors.go @@ -3,5 +3,5 @@ package orm import "fmt" var ErrNoConditionOnDeleteOrUpdate = fmt.Errorf("refusing to delete/update with no conditions specified.\n"+ - " (hint: call `.Where(%s)` or `.Where(%s)` to do so anyways)", + " (hint: call `.WhereRaw(%s)` or `.WhereRaw(%s)` to do so anyways)", `"true"`, `"1 = 1"`) diff --git a/field.go b/field.go index 38e66d5..c363a51 100644 --- a/field.go +++ b/field.go @@ -8,17 +8,17 @@ import ( // Field - represents a field with a valid SQL type in a Model type Field struct { - Name string - ColumnName string - ColumnType string - Type reflect.Type - Original reflect.StructField - Model *Model - Index int - AutoIncrement bool - PrimaryKey bool - Nullable bool - embeddedFields map[string]*Field + Name string // the name of this field as it appears in its Model's type definition + ColumnName string // this field's snake_cased column name as it appears in database + ColumnType string // the SQL type of this field's column (bigint, bigserial, text, ...) + Type reflect.Type // the reflect.Type of the struct field this Field represents + Original reflect.StructField // the raw struct field, as obtained by using reflect.Type.Field or reflect.Type.FieldByName + Model *Model // the Model this field belongs to + Index int // the index at which Original appears in its struct + AutoIncrement bool // whether this field's column is an auto-incrementing column + PrimaryKey bool // true if this field's column is a primary key + Nullable bool // true if this field's column can be NULL + embeddedFields map[string]*Field // mapping of column names to Field pointers that correspond to the surrounding struct's fields } func (f *Field) isAnonymous() bool { diff --git a/model.go b/model.go index 1cdd989..0fb18a8 100644 --- a/model.go +++ b/model.go @@ -4,15 +4,16 @@ import ( "reflect" ) +// Model - an intermediate representation of a Go struct type Model struct { - Name string - Type reflect.Type - Relationships map[string]*Relationship - IDField string - Fields map[string]*Field - FieldsByColumnName map[string]*Field - TableName string - embeddedIsh bool + Name string // the name, almost always the name of the underlying Type + Type reflect.Type // the Go type this model represents + Relationships map[string]*Relationship // a mapping of struct field names to Relationship pointers + IDField string // the name of the field containing this model's ID + Fields map[string]*Field // mapping of struct field names to Field pointers + FieldsByColumnName map[string]*Field // mapping of database column names to Field pointers + TableName string // the name of the table where this model's data is stored. defaults to a snake_cased version of the type/struct name if not provided explicitly via tag + embeddedIsh bool // INTERNAL - whether this model is: 1) contained in another type and 2) wasn't explicitly passed to the `Models` method (i.e., it can't "exist" on its own) } func (m *Model) addField(field *Field) { @@ -63,17 +64,3 @@ func (m *Model) needsPrimaryKey(val reflect.Value) bool { _, pk := m.getPrimaryKey(val) return pk == nil || reflect.ValueOf(pk).IsZero() } - -func (m *Model) columnsWith(rel *Relationship) (cols []string, err error) { - for _, f := range m.Fields { - if f.ColumnType != "" { - cols = append(cols, f.ColumnName) - } - } - for _, r2 := range m.Relationships { - if r2.Type == ManyToOne { - cols = append(cols, pascalToSnakeCase(r2.joinField())) - } - } - return -} diff --git a/model_internals.go b/model_internals.go index 838a253..e6e577e 100644 --- a/model_internals.go +++ b/model_internals.go @@ -43,7 +43,7 @@ func makeModelMap(models ...any) *internalModelMap { modelMap := &internalModelMap{ Map: make(map[string]*Model), } - //modelMap := make(map[string]*Model) + //internalModelMap := make(map[string]*Model) for _, model := range models { minfo := parseModel(model) modelMap.Mux.Lock() diff --git a/query.go b/query.go index 31721b0..10e42e2 100644 --- a/query.go +++ b/query.go @@ -9,17 +9,19 @@ import ( "strings" ) +// Query - contains the state and other details +// pertaining to the current FUCK operation (Find, Update/Create, Kill [Delete]) type Query struct { - engine *Engine - model *Model - tx pgx.Tx - ctx context.Context - populationTree map[string]any - wheres map[string][]any - joins []string - orders []string - limit int - offset int + engine *Engine // the Engine instance that created this Query + model *Model // the primary Model this Query pertains to + tx pgx.Tx // the transaction for insert, update and delete operations + ctx context.Context // does nothing, but is needed by some pgx functions + populationTree map[string]any // a tree-like map representing the dot-separated paths of fields to populate + wheres map[string][]any // a mapping of where clauses to a list of their arguments + joins []string // slice of tables to join on before executing Find. useful for hwne you have Where clauses referencing fields/columns in other structs/tables + orders []string // slice of `ORDER BY` clauses + limit int // argument to a LIMIT clause, if non-zero + offset int // unused (for now) } func (q *Query) setModel(val any) *Query { @@ -36,11 +38,15 @@ func (q *Query) cleanupTx() { q.tx = nil } +// Order - add an `ORDER BY` clause to the current Query. +// Only applicable for Find queries func (q *Query) Order(order string) *Query { q.orders = append(q.orders, order) return q } +// Limit - limit resultset to at most `limit` results. +// does nothing if `limit` <= 0 or the final operation isn't Find func (q *Query) Limit(limit int) *Query { if limit > -1 { q.limit = limit @@ -48,6 +54,8 @@ func (q *Query) Limit(limit int) *Query { return q } +// Offset - skip to the nth result, where n = `offset`. +// does nothing if `offset` <= 0 or the final operation isn't Find func (q *Query) Offset(offset int) *Query { if offset > -1 { q.offset = offset @@ -55,21 +63,30 @@ func (q *Query) Offset(offset int) *Query { return q } +// Where - add a `WHERE` clause to this query. +// struct field names can be passed to this method, +// and they will be automatically converted func (q *Query) Where(cond string, args ...any) *Query { q.processWheres(cond, "eq", args...) return q } +// WhereRaw - add a `WHERE` clause to this query, except `cond` is passed as-is. func (q *Query) WhereRaw(cond string, args ...any) *Query { q.wheres[cond] = args return q } +// In - add a `WHERE ... IN(...)` clause to this query func (q *Query) In(cond string, args ...any) *Query { q.processWheres(cond, "in", args...) return q } +// Join - join the current model's table with the table +// representing the type of struct field named `field`. +// Must be called before Where if referencing other +// structs/types to avoid errors func (q *Query) Join(field string) *Query { var clauses []string parts := strings.Split(field, ".") @@ -208,6 +225,8 @@ func (q *Query) processWheres(cond string, exprKind string, args ...any) { q.wheres[tq] = args } +// buildSQL - aggregates the information in this Query into a pgq.SelectBuilder. +// it returns a slice of column names as well to avoid issues with scanning func (q *Query) buildSQL() (cols []string, anonymousCols map[string][]string, finalSb sb.SelectBuilder, err error) { var inParents []any anonymousCols = make(map[string][]string) diff --git a/query_populate.go b/query_populate.go index c7a8f67..1ee3738 100644 --- a/query_populate.go +++ b/query_populate.go @@ -10,6 +10,9 @@ import ( const PopulateAll = "~~~ALL~~~" +// Populate - allows you to pre-load embedded structs/slices within the current model. +// use dots between field names to specify nested paths. use the PopulateAll constant to populate all +// relationships non-recursively func (q *Query) Populate(fields ...string) *Query { if q.populationTree == nil { q.populationTree = make(map[string]any) diff --git a/query_tail.go b/query_tail.go index 4198200..8f68524 100644 --- a/query_tail.go +++ b/query_tail.go @@ -8,6 +8,7 @@ import ( "time" ) +// Find - transpiles this query into SQL and places the result in `dest` func (q *Query) Find(dest any) error { dstVal := reflect.ValueOf(dest) if dstVal.Kind() != reflect.Ptr { @@ -63,10 +64,13 @@ func (q *Query) Find(dest any) error { return nil } +// Save - create or update `val` in the database func (q *Query) Save(val any) error { return q.saveOrCreate(val, false) } +// Create - like Save, but hints to the query processor that you want to insert, not update. +// useful if you're importing data and want to keep the IDs intact. func (q *Query) Create(val any) error { return q.saveOrCreate(val, true) } @@ -116,6 +120,10 @@ func (q *Query) UpdateRaw(values map[string]any) (int64, error) { return ctag.RowsAffected(), q.tx.Commit(q.ctx) } +// Delete - delete one or more entities matching previous conditions specified +// by methods like Where, WhereRaw, or In. will refuse to execute if no +// conditions were specified for safety reasons. to override this, call +// WhereRaw("true") or WhereRaw("1 = 1") before this method. func (q *Query) Delete() (int64, error) { var err error var subQuery sb.SelectBuilder diff --git a/relationship.go b/relationship.go index 4b2b1df..17f6e36 100644 --- a/relationship.go +++ b/relationship.go @@ -7,30 +7,39 @@ import ( "strings" ) +// RelationshipType - used to distinguish a Relationship between various +// common entity relationship types type RelationshipType int const ( HasOne RelationshipType = iota HasMany BelongsTo - ManyToOne + ManyToOne // the other side of a HasMany relationship ManyToMany ) +// Relationship - intermediate representation of how two types +// relate to each other. i.e., if struct A embeds struct B, +// a Relationship will be created for those two while parsing the Model for A. type Relationship struct { - Type RelationshipType - JoinTable string - Model *Model - FieldName string - Idx int - RelatedType reflect.Type - RelatedModel *Model - Kind reflect.Kind // field kind (struct, slice, ...) - m2mInverse *Relationship - Nullable bool - OriginalField reflect.StructField + Type RelationshipType // the type of this relationship (see RelationshipType) + JoinTable string // the name of the join table, if specified explicitly via struct tag, otherwise blank + Model *Model // the primary Model which contains this relationship + FieldName string // the name of the struct field with this relationship + Idx int // the index of the struct field with this relationship + RelatedType reflect.Type // the reflect.Type for the struct field named by FieldName + RelatedModel *Model // the Model representing the type of the embedded slice/struct + Kind reflect.Kind // field kind (struct, slice, ...) + m2mInverse *Relationship // the "inverse" side of an explicit ManyToMany relationship + Nullable bool // whether the foreign key for this relationship can have a nullable column + OriginalField reflect.StructField // the original reflect.StructField object associated with this relationship } +// ComputeJoinTable - computes the name of the join table for ManyToMany relationships. +// will return a snake_cased autogenerated name for unidirectional ManyToMany . +// the "implicit" behavior is invoked upon one of the following conditions being met: +// - the primary Relationship.Model func (r *Relationship) ComputeJoinTable() string { if r.JoinTable != "" { return r.JoinTable