AI Agent Integration Guide ========================== This guide is designed for AI coding assistants and developers. It outlines the directory patterns, model definition conventions, code generation workflows, and query APIs of the Enterprise ORM. Directory Pattern ***************** Projects utilizing the Enterprise ORM must adhere to the following standard layout: .. code-block:: text your-project/ ├── db_models/ # 1. Human-defined database table definitions (schema definitions) │ ├── account.go │ ├── group.go │ └── constants.go # Helper constants for table & column names ├── generate/ # 2. Code-generation entrypoint │ └── generate.go # Program containing main() that executes model generation ├── models/ # 3. Auto-generated ORM packages (DO NOT edit manually) │ ├── client.go # Database initialization, transactions, and SQL loggers │ ├── account.go # Model struct, getters, setters, hooks, and list/bulk operations │ └── account_predicates.go # Code-generated WHERE filters for the model ├── migrate/ # 4. Schema migrations runner │ └── migrate.go # Program to compute diffs and run migrations └── main.go # Application entrypoint Defining Tables (Schemas) ************************* Table schemas are defined as Go functions under the ``db_models`` package. Each function returns a ``*models.Table``. Schema Rules & Guidelines ------------------------- 1. **Quoting & Keyword Safety**: Enterprise automatically double-quotes all table and column names in SQL. Always specify identifiers exactly as they should appear in DB. 2. **Primary Keys**: Every table must have a primary key defined via ``SetIDField()``. 3. **Builder Chaining**: Field builders return instances supporting method chaining (``SetNillable()``, ``AddSerial()``, ``Default()``, ``DefaultFunc()``). Supported Fields ---------------- - **Boolean**: ``models.BoolField(name)`` - **Byte**: ``models.ByteField(name)`` - **UUID**: ``models.UUIDField(name)`` - **Integers**: ``models.IntField(name)``, ``models.SmallIntField(name)``, ``models.BigIntField(name)`` - **Unsigned Integer**: ``models.UintField(name)`` - **Floats**: ``models.Float32Field(name)``, ``models.Float64Field(name)`` - **String**: ``models.StringField(name)`` - **Decimal**: ``models.DecimalField(name, precision, scale)`` - **String Array**: ``models.StringArrayField(name)`` - **Enum**: ``models.EnumField(name, stringSlice)`` - **Time**: ``models.TimeField(name)`` - **JSON**: ``models.JSONField(name)`` - **Custom Type**: ``models.CustomField(name, postgresType, valueScannerInstance)`` (requires implementing ``sql.Scanner`` and ``driver.Valuer``) Declaring Relationships ----------------------- - **Many-to-One**: ``models.ManyToOne(targetTable, targetPK, sourceFK)`` - **One-to-Many**: ``models.OneToMany(targetTable, sourcePK, targetFK)`` - **Many-to-Many**: ``models.ManyToMany(targetTable, sourceFK, targetFK, targetPK, joinTableName)`` Example Schema: ``db_models/account.go`` ---------------------------------------- .. code-block:: go package db_models import ( "github.com/MrSametBurgazoglu/enterprise/models" "github.com/google/uuid" ) func Account() *models.Table { idField := models.UUIDField("ID").DefaultFunc(uuid.New) tb := &models.Table{ Fields: []models.FieldI{ idField, models.StringField("Name"), models.StringField("Surname"), models.UUIDField("DenemeID").SetNillable(), models.UintField("Serial").AddSerial(), }, Relations: []*models.Relation{ models.ManyToOne("Deneme", "id", "deneme_id"), models.ManyToMany("Group", "account_id", "group_id", "id", "account_group"), }, } tb.SetTableName("account") tb.SetIDField(idField) tb.AddIndex("account_name_surname_idx", "Name", "Surname") return tb } Code Generation Workflow ************************ The generator script executes model generation. Create ``generate/generate.go``: .. code-block:: go package main import ( "github.com/MrSametBurgazoglu/enterprise/generate" "your-project/db_models" ) func main() { generate.Models( db_models.Account(), db_models.Group(), ) } Run the script from your terminal: .. code-block:: bash go run generate/generate.go This automatically compiles the templates and writes out the complete ``models/`` Go files. Database Setup & Migrations *************************** Initialize the database using ``models.Options`` and run auto-applied or file-based migrations. .. code-block:: go package main import ( "context" "log" "github.com/MrSametBurgazoglu/enterprise/migrate" "your-project/db_models" "your-project/models" ) func main() { ctx := context.Background() postgresURL := "postgresql://user:pass@localhost:5432/db?sslmode=disable" tables := []*models.Table{ db_models.Account(), db_models.Group(), } migrate.AutoApplyMigration(ctx, postgresURL, "init_schema", tables...) opts := &models.Options{ Url: postgresURL, Debug: true, } db, err := models.NewDB(opts) if err != nil { log.Fatal(err) } defer db.Exit() } Eager Relation Loading & Eager Filtering **************************************** To load child relations and filter them dynamically in a **single query** (preventing N+1 query patterns), use the ``With[RelationName]`` methods. .. code-block:: go account := models.NewAccount(ctx, db) account.Where(account.IsIDEqual(accountID)) // Join and filter relations inline account.WithDeneme(func(d *models.Deneme) { d.Where(d.IsActiveEqual(true)) }) account.WithGroupList(func(gl *models.GroupList) { gl.Where(gl.IsNameEqual("Admin Group")) }) err = account.Get() if err == nil { if account.Deneme != nil { log.Println("Active Deneme:", account.Deneme.GetID()) } } Query Predicates & Logical Composition ************************************** Where clauses accept a list of ``client.PredicateI`` interfaces. - **Logical Composition**: Nest queries using ``models.And(...)`` and ``models.Or(...)``. - **Dynamic Filters**: Use ``WhereIf(cond, predicate)`` and ``WhereIn(cond, predicate)`` to append predicates only if ``cond == true``. - **Zero Predicates**: Calling ``Where()`` with no arguments behaves as a no-op (no SQL WHERE clause). - **Wildcards**: Use case-sensitive ``IsXLike`` and case-insensitive ``IsXILike`` pattern matching. .. code-block:: go list := models.NewAccountList(ctx, db) list.Where( models.And( models.Or( list.IsNameEqual("Samet"), list.IsNameILike("admin%"), ), list.IsSerialGreater(10), ), ) list.WhereIf(filterByStatus, list.IsStatusEqual("active")) err = list.List() Bulk and In-Database Operations ******************************* Avoid loading entities into memory for bulk inserts, updates, or deletes. .. code-block:: go list := models.NewAccountList(ctx, db) // Bulk Create acc1 := models.NewAccount(ctx, db) acc1.SetName("Alice") acc2 := models.NewAccount(ctx, db) acc2.SetName("Bob") err := list.Create(acc1, acc2) // In-Database Update (UpdateWhere) list.Where(list.IsStatusEqual("pending")) rowsUpdated, err := list.UpdateWhere(map[string]any{ "status": "active", }) // In-Database Delete (DeleteWhere) list.Where(list.IsStatusEqual("banned")) rowsDeleted, err := list.DeleteWhere() Aggregates and Iterators (Go 1.23) ********************************** Enterprise supports basic aggregates (``Min``, ``Max``, ``Sum``, ``Avg``) on model fields as well as standard library iterators. Model Convenience Methods ------------------------- .. code-block:: go denemeList := models.NewDenemeList(ctx, db) maxVal, err := denemeList.MaxCount() Group By and Go 1.23 iter.Seq2 Iterators ---------------------------------------- .. code-block:: go groupList := models.NewGroupList(ctx, db) var ( groupName string groupSize int ) iterator := groupList.AggregateRowsSeq(func(a *client.Aggregate) { a.Field("name", &groupName) a.Count("id", &groupSize) a.GroupBy("name") }) for index, err := range iterator { if err != nil { log.Fatal(err) } fmt.Printf("Group %d: Name: %s, Size: %d\n", index, groupName, groupSize) }