[{"data":1,"prerenderedAt":7704},["ShallowReactive",2],{"search-sections-edamame":3,"nav-edamame":888,"content-tree-edamame":931,"footer-resources":950,"content-/v1.0.3/guides/testing":3703,"surround-/v1.0.3/guides/testing":7701},[4,10,14,20,25,30,35,41,46,51,56,61,64,69,74,79,84,89,94,99,104,108,113,118,123,127,132,137,142,147,152,157,162,167,172,177,182,187,192,197,202,207,212,217,221,225,230,234,239,244,249,254,259,264,269,273,277,281,286,291,296,301,306,311,316,321,326,331,336,341,346,351,356,361,366,371,376,381,386,391,396,401,406,411,416,421,426,431,436,441,444,449,454,459,463,467,472,477,482,487,492,497,502,507,512,517,522,526,531,535,540,545,550,555,560,565,570,575,580,585,590,594,598,603,607,612,617,622,627,632,636,641,646,651,656,661,665,669,675,680,685,690,695,700,705,709,714,719,724,729,734,739,744,748,753,758,763,768,773,778,783,787,792,796,800,805,809,813,817,821,825,829,834,839,844,849,854,859,864,869,874,879,884],{"id":5,"title":6,"titles":7,"content":8,"level":9},"/v1.0.3/overview","Overview",[],"Statement-driven query exec for Go applications",1,{"id":11,"title":6,"titles":12,"content":13,"level":9},"/v1.0.3/overview#overview",[],"Database operations in Go often mean choosing between raw SQL and heavy ORMs. Edamame offers a third path: a statement-driven query exec that stays out of your way while providing type-safe, declarative query definitions. // Define your model\ntype User struct {\n    ID    int    `db:\"id\" type:\"integer\" constraints:\"primarykey\"`\n    Email string `db:\"email\" type:\"text\" constraints:\"notnull,unique\"`\n    Name  string `db:\"name\" type:\"text\"`\n    Age   *int   `db:\"age\" type:\"integer\"`\n}\n\n// Define statements as package-level variables\nvar (\n    QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all users\", edamame.QuerySpec{})\n\n    SelectByID = edamame.NewSelectStatement(\"select-by-id\", \"Select user by ID\", edamame.SelectSpec{\n        Where: []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    })\n\n    DeleteByID = edamame.NewDeleteStatement(\"delete-by-id\", \"Delete user by ID\", edamame.DeleteSpec{\n        Where: []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    })\n\n    Adults = edamame.NewQueryStatement(\"adults\", \"Find users over a minimum age\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"age\", Operator: \">=\", Param: \"min_age\"},\n        },\n        OrderBy: []edamame.OrderBySpec{\n            {Field: \"name\", Direction: \"asc\"},\n        },\n    })\n)\n\n// Create a exec and execute\nexec, err := edamame.New[User](db, \"users\", renderer)\n\nusers, err := exec.ExecQuery(ctx, QueryAll, nil)\nuser, err := exec.ExecSelect(ctx, SelectByID, map[string]any{\"id\": 123})\ninserted, err := exec.ExecInsert(ctx, &user)\ndeleted, err := exec.ExecDelete(ctx, DeleteByID, map[string]any{\"id\": 123})\nadults, err := exec.ExecQuery(ctx, Adults, map[string]any{\"min_age\": 18}) Type-safe, injection-protected, declarative.",{"id":15,"title":16,"titles":17,"content":18,"level":19},"/v1.0.3/overview#architecture","Architecture",[6],"┌─────────────────────────────────────────────────────────────┐\n│                         Edamame                             │\n│                                                             │\n│  ┌─────────────────────────────────────────────────────┐    │\n│  │                   Executor[T]                        │    │\n│  │                                                     │    │\n│  │  ┌───────────────────────────────────────────────┐  │    │\n│  │  │              Statement Types                   │  │    │\n│  │  │                                                │  │    │\n│  │  │  QueryStatement  SelectStatement               │  │    │\n│  │  │  UpdateStatement DeleteStatement               │  │    │\n│  │  │  AggregateStatement                            │  │    │\n│  │  └────────────────────┬───────────────────────────┘  │    │\n│  │                       │                              │    │\n│  │                 ┌─────▼─────┐                        │    │\n│  │                 │    Soy    │                        │    │\n│  │                 │ (Builder) │                        │    │\n│  │                 └─────┬─────┘                        │    │\n│  │                       │                              │    │\n│  └───────────────────────┼──────────────────────────────┘    │\n│                          │                                   │\n│                    ┌─────▼─────┐                             │\n│                    │   sqlx    │                             │\n│                    │    DB     │                             │\n│                    └───────────┘                             │\n└─────────────────────────────────────────────────────────────┘ Edamame provides typed statements over soy's query builder. Each statement type encapsulates a declarative spec that maps to a parameterized SQL builder.",2,{"id":21,"title":22,"titles":23,"content":24,"level":19},"/v1.0.3/overview#philosophy","Philosophy",[6],"Edamame bridges two worlds: the declarative simplicity of specs and the type safety of Go generics. Define what you want, get SQL that's validated at build time and parameterized at runtime. // Define statements as package-level variables\nvar ActiveByRole = edamame.NewQueryStatement(\"active-by-role\", \"Find active users by role\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"active\", Operator: \"=\", Param: \"active\"},\n        {Field: \"role\", Operator: \"=\", Param: \"role\"},\n    },\n})\n\n// In your API handler\nusers, err := exec.ExecQuery(ctx, ActiveByRole, map[string]any{\n    \"active\": true,\n    \"role\":   \"admin\",\n}) Typed statements, compile-time safety, no magic strings.",{"id":26,"title":27,"titles":28,"content":29,"level":19},"/v1.0.3/overview#statement-types","Statement Types",[6],"Edamame provides five statement types for different operations: Statement TypeDescriptionQueryStatementMulti-record retrieval with filteringSelectStatementSingle-record retrievalUpdateStatementTargeted updates with SET/WHEREDeleteStatementConditional deletion with WHEREAggregateStatementCOUNT, SUM, AVG, MIN, MAX operations Each statement type accepts a spec that defines the query behavior: QuerySpec - Filtering, sorting, pagination, grouping, locking SelectSpec - Single-record filtering with optional locking UpdateSpec - SET clauses and WHERE conditions DeleteSpec - WHERE conditions for targeted deletion AggregateSpec - Field to aggregate and optional filtering",{"id":31,"title":32,"titles":33,"content":34,"level":19},"/v1.0.3/overview#priorities","Priorities",[6],"",{"id":36,"title":37,"titles":38,"content":39,"level":40},"/v1.0.3/overview#type-safety","Type Safety",[6,32],"Fields, operators, and params are validated at query build time. No runtime SQL injection, no magic strings. // Spec-based: field names validated against model metadata\nvar Adults = edamame.NewQueryStatement(\"adults\", \"Find adults\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"age\", Operator: \">=\", Param: \"min_age\"},\n    },\n})\n\n// Execution: params bound safely via sqlx\nusers, err := exec.ExecQuery(ctx, Adults, map[string]any{\n    \"min_age\": 18,  // Parameterized, never interpolated\n})",3,{"id":42,"title":43,"titles":44,"content":45,"level":40},"/v1.0.3/overview#compile-time-guarantees","Compile-Time Guarantees",[6,32],"Statements are typed. Pass a QueryStatement to ExecQuery, a SelectStatement to ExecSelect. The compiler catches mismatches. // Compiler ensures correct statement types\nusers, err := exec.ExecQuery(ctx, QueryAll, nil)       // QueryStatement\nuser, err := exec.ExecSelect(ctx, SelectByID, params)  // SelectStatement\ncount, err := exec.ExecAggregate(ctx, CountAll, nil)   // AggregateStatement",{"id":47,"title":48,"titles":49,"content":50,"level":40},"/v1.0.3/overview#security","Security",[6,32],"All SQL generation flows through soy's validated builder: Field names validated against model metadataOperators validated against allowlistAll values bound as parameters, never interpolatedNo raw SQL construction from user input",{"id":52,"title":53,"titles":54,"content":55,"level":40},"/v1.0.3/overview#performance","Performance",[6,32],"Lazy initialization - Builders created on demandMinimal allocations - Spec-to-builder conversion is lightweightBatch operations - Insert, update, delete batches in single transactions html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":57,"title":58,"titles":59,"content":60,"level":9},"/v1.0.3/learn/quickstart","Quickstart",[],"Get started with edamame in minutes",{"id":62,"title":58,"titles":63,"content":34,"level":9},"/v1.0.3/learn/quickstart#quickstart",[],{"id":65,"title":66,"titles":67,"content":68,"level":19},"/v1.0.3/learn/quickstart#requirements","Requirements",[58],"Go 1.24 or later.",{"id":70,"title":71,"titles":72,"content":73,"level":19},"/v1.0.3/learn/quickstart#installation","Installation",[58],"go get github.com/zoobz-io/edamame",{"id":75,"title":76,"titles":77,"content":78,"level":19},"/v1.0.3/learn/quickstart#basic-usage","Basic Usage",[58],"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n\n    \"github.com/jmoiron/sqlx\"\n    _ \"github.com/lib/pq\" // or mariadb, sqlite3, mssql driver\n    \"github.com/zoobz-io/astql/pkg/postgres\" // or mariadb, sqlite, mssql\n    \"github.com/zoobz-io/edamame\"\n)\n\n// Define your model with struct tags\ntype User struct {\n    ID    int    `db:\"id\" type:\"integer\" constraints:\"primarykey\"`\n    Email string `db:\"email\" type:\"text\" constraints:\"notnull,unique\"`\n    Name  string `db:\"name\" type:\"text\"`\n    Age   *int   `db:\"age\" type:\"integer\"`\n}\n\n// Define statements as package-level variables\nvar (\n    QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all users\", edamame.QuerySpec{})\n\n    SelectByID = edamame.NewSelectStatement(\"select-by-id\", \"Select user by ID\", edamame.SelectSpec{\n        Where: []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    })\n\n    CountAll = edamame.NewAggregateStatement(\"count-all\", \"Count all users\", edamame.AggCount, edamame.AggregateSpec{})\n)\n\nfunc main() {\n    // Connect to database (PostgreSQL shown; MariaDB, SQLite, SQL Server also supported)\n    db, err := sqlx.Connect(\"postgres\", \"postgres://user:pass@localhost/mydb?sslmode=disable\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // Create exec\n    exec, err := edamame.New[User](db, \"users\", postgres.New())\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    ctx := context.Background()\n\n    // Query all users\n    users, err := exec.ExecQuery(ctx, QueryAll, nil)\n    if err != nil {\n        log.Fatal(err)\n    }\n    fmt.Printf(\"Found %d users\\n\", len(users))\n\n    // Select user by ID\n    user, err := exec.ExecSelect(ctx, SelectByID, map[string]any{\"id\": 1})\n    if err != nil {\n        log.Fatal(err)\n    }\n    fmt.Printf(\"User: %s\\n\", user.Name)\n\n    // Insert a new user\n    age := 25\n    newUser := &User{\n        Email: \"alice@example.com\",\n        Name:  \"Alice\",\n        Age:   &age,\n    }\n    inserted, err := exec.ExecInsert(ctx, newUser)\n    if err != nil {\n        log.Fatal(err)\n    }\n    fmt.Printf(\"Inserted user ID: %d\\n\", inserted.ID)\n\n    // Count users\n    count, err := exec.ExecAggregate(ctx, CountAll, nil)\n    if err != nil {\n        log.Fatal(err)\n    }\n    fmt.Printf(\"Total users: %.0f\\n\", count)\n}",{"id":80,"title":81,"titles":82,"content":83,"level":19},"/v1.0.3/learn/quickstart#whats-happening","What's Happening",[58],"New[User](db, \"users\", postgres.New()) creates a exec for the User type bound to the \"users\" table with the PostgreSQL rendererStatements are defined as package-level variables with unique names and descriptionsExecQuery returns all records matching the statement's specExecSelect returns a single record (or error if not found)ExecInsert inserts a record and returns it with generated fields (like ID)ExecAggregate runs an aggregate function and returns the result",{"id":85,"title":86,"titles":87,"content":88,"level":19},"/v1.0.3/learn/quickstart#defining-custom-statements","Defining Custom Statements",[58],"// Define statements for your domain\nvar (\n    // Query for active adult users\n    ActiveAdults = edamame.NewQueryStatement(\"active-adults\", \"Find active users above minimum age\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"active\", Operator: \"=\", Param: \"active\"},\n            {Field: \"age\", Operator: \">=\", Param: \"min_age\"},\n        },\n        OrderBy: []edamame.OrderBySpec{\n            {Field: \"name\", Direction: \"asc\"},\n        },\n    })\n\n    // Update user name\n    UpdateName = edamame.NewUpdateStatement(\"update-name\", \"Update user name by ID\", edamame.UpdateSpec{\n        Set:   map[string]string{\"name\": \"new_name\"},\n        Where: []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    })\n\n    // Delete inactive users\n    DeleteInactive = edamame.NewDeleteStatement(\"delete-inactive\", \"Delete inactive users\", edamame.DeleteSpec{\n        Where: []edamame.ConditionSpec{{Field: \"active\", Operator: \"=\", Param: \"active\"}},\n    })\n\n    // Sum of ages\n    SumAges = edamame.NewAggregateStatement(\"sum-ages\", \"Sum all user ages\", edamame.AggSum, edamame.AggregateSpec{\n        Field: \"age\",\n    })\n)\n\n// Use the statements\nusers, err := exec.ExecQuery(ctx, ActiveAdults, map[string]any{\n    \"active\":  true,\n    \"min_age\": 18,\n})\n\naffected, err := exec.ExecUpdate(ctx, UpdateName, map[string]any{\n    \"id\":       123,\n    \"new_name\": \"New Name\",\n})\n\ndeleted, err := exec.ExecDelete(ctx, DeleteInactive, map[string]any{\n    \"active\": false,\n})\n\ntotal, err := exec.ExecAggregate(ctx, SumAges, nil)",{"id":90,"title":91,"titles":92,"content":93,"level":19},"/v1.0.3/learn/quickstart#statement-parameters","Statement Parameters",[58],"Statements automatically derive their parameters from the spec. You can inspect them: // Check statement parameters\nfor _, param := range ActiveAdults.Params() {\n    fmt.Printf(\"Param: %s (type: %s, required: %v)\\n\", param.Name, param.Type, param.Required)\n}\n// Output:\n// Param: active (type: any, required: true)\n// Param: min_age (type: any, required: true)",{"id":95,"title":96,"titles":97,"content":98,"level":19},"/v1.0.3/learn/quickstart#next-steps","Next Steps",[58],"Core Concepts - Understand factories, statements, and specsStatement Guide - Define custom queries, updates, and moreTesting - Test your database operations html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":100,"title":101,"titles":102,"content":103,"level":9},"/v1.0.3/learn/concepts","Core Concepts",[],"Executors, statements, and specs - the building blocks of edamame",{"id":105,"title":101,"titles":106,"content":107,"level":9},"/v1.0.3/learn/concepts#core-concepts",[],"Edamame has three primitives: executors, statements, and specs. Understanding these unlocks the full API.",{"id":109,"title":110,"titles":111,"content":112,"level":19},"/v1.0.3/learn/concepts#executor","Executor",[101],"An executor is the execution context for a single model type. It wraps soy and provides methods to execute typed statements. exec, err := edamame.New[User](db, \"users\", renderer) The executor: Wraps a soy instance for SQL buildingProvides execution methods that accept typed statementsSupports transactions via *Tx method variants",{"id":114,"title":115,"titles":116,"content":117,"level":40},"/v1.0.3/learn/concepts#struct-tags","Struct Tags",[101,110],"Edamame uses struct tags to understand your model: type User struct {\n    ID    int    `db:\"id\" type:\"integer\" constraints:\"primarykey\"`\n    Email string `db:\"email\" type:\"text\" constraints:\"notnull,unique\"`\n    Name  string `db:\"name\" type:\"text\"`\n    Age   *int   `db:\"age\" type:\"integer\"`\n} TagPurposeExampledbColumn namedb:\"user_id\"typeSQL typetype:\"text\", type:\"integer\"constraintsColumn constraintsconstraints:\"primarykey,notnull\"",{"id":119,"title":120,"titles":121,"content":122,"level":19},"/v1.0.3/learn/concepts#statements","Statements",[101],"A statement is a typed, named database operation. Define statements as package-level variables: var (\n    QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all users\", edamame.QuerySpec{})\n\n    SelectByID = edamame.NewSelectStatement(\"select-by-id\", \"Select user by ID\", edamame.SelectSpec{\n        Where: []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    })\n) Each statement has: Name - Human-readable identifierDescription - What the statement doesID - Auto-generated UUID for uniquenessSpec - Declarative definition of the operationParams - Required parameters (auto-derived from spec)Tags - Optional metadata for categorization",{"id":124,"title":27,"titles":125,"content":126,"level":40},"/v1.0.3/learn/concepts#statement-types",[101,120],"TypeConstructorReturnsUse CaseQueryStatementNewQueryStatement[]*TMulti-record retrievalSelectStatementNewSelectStatement*TSingle-record retrievalUpdateStatementNewUpdateStatement*TModify and return recordDeleteStatementNewDeleteStatementint64Remove records, return countAggregateStatementNewAggregateStatementfloat64COUNT, SUM, AVG, MIN, MAX",{"id":128,"title":129,"titles":130,"content":131,"level":40},"/v1.0.3/learn/concepts#defining-statements","Defining Statements",[101,120],"// Query statement\nvar ByStatus = edamame.NewQueryStatement(\"by-status\", \"Find users by status\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"status\", Operator: \"=\", Param: \"status\"},\n    },\n}, \"user\", \"filter\") // Optional tags\n\n// Select statement\nvar ByEmail = edamame.NewSelectStatement(\"by-email\", \"Find user by email\", edamame.SelectSpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"email\", Operator: \"=\", Param: \"email\"},\n    },\n})\n\n// Update statement\nvar Activate = edamame.NewUpdateStatement(\"activate\", \"Activate a user by ID\", edamame.UpdateSpec{\n    Set: map[string]string{\"active\": \"active\"},\n    Where: []edamame.ConditionSpec{\n        {Field: \"id\", Operator: \"=\", Param: \"id\"},\n    },\n})\n\n// Delete statement\nvar DeleteByID = edamame.NewDeleteStatement(\"delete-by-id\", \"Delete user by ID\", edamame.DeleteSpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"id\", Operator: \"=\", Param: \"id\"},\n    },\n})\n\n// Aggregate statement\nvar AvgAge = edamame.NewAggregateStatement(\"avg-age\", \"Average age of all users\", edamame.AggAvg, edamame.AggregateSpec{\n    Field: \"age\",\n})",{"id":133,"title":134,"titles":135,"content":136,"level":40},"/v1.0.3/learn/concepts#statement-metadata","Statement Metadata",[101,120],"Inspect statement properties: fmt.Println(ByStatus.Name())        // \"by-status\"\nfmt.Println(ByStatus.Description()) // \"Find users by status\"\nfmt.Println(ByStatus.ID())          // UUID\nfmt.Println(ByStatus.Tags())        // [\"user\", \"filter\"]\n\nfor _, p := range ByStatus.Params() {\n    fmt.Printf(\"Param: %s (type: %s, required: %v)\\n\", p.Name, p.Type, p.Required)\n}",{"id":138,"title":139,"titles":140,"content":141,"level":19},"/v1.0.3/learn/concepts#specs","Specs",[101],"A spec is a declarative definition of a database operation. Specs are pure data—no SQL strings, no builder calls.",{"id":143,"title":144,"titles":145,"content":146,"level":40},"/v1.0.3/learn/concepts#queryspec","QuerySpec",[101,139],"For multi-record retrieval: spec := edamame.QuerySpec{\n    Fields:     []string{\"id\", \"name\", \"email\"},  // SELECT columns (empty = all)\n    Where:      []edamame.ConditionSpec{...},     // WHERE clauses\n    OrderBy:    []edamame.OrderBySpec{...},       // ORDER BY clauses\n    GroupBy:    []string{\"status\"},               // GROUP BY columns\n    Having:     []edamame.ConditionSpec{...},     // HAVING clauses\n    Limit:      &limit,                           // LIMIT\n    Offset:     &offset,                          // OFFSET\n    Distinct:   true,                             // SELECT DISTINCT\n    ForLocking: \"update\",                         // FOR UPDATE/SHARE\n}",{"id":148,"title":149,"titles":150,"content":151,"level":40},"/v1.0.3/learn/concepts#selectspec","SelectSpec",[101,139],"For single-record retrieval (same structure as QuerySpec): spec := edamame.SelectSpec{\n    Fields:     []string{\"id\", \"name\"},\n    Where:      []edamame.ConditionSpec{...},\n    ForLocking: \"share\",\n}",{"id":153,"title":154,"titles":155,"content":156,"level":40},"/v1.0.3/learn/concepts#updatespec","UpdateSpec",[101,139],"For modifications: spec := edamame.UpdateSpec{\n    Set: map[string]string{\n        \"name\":   \"new_name\",    // field -> param mapping\n        \"status\": \"new_status\",\n    },\n    Where: []edamame.ConditionSpec{...},\n}",{"id":158,"title":159,"titles":160,"content":161,"level":40},"/v1.0.3/learn/concepts#deletespec","DeleteSpec",[101,139],"For deletions: spec := edamame.DeleteSpec{\n    Where: []edamame.ConditionSpec{...},\n}",{"id":163,"title":164,"titles":165,"content":166,"level":40},"/v1.0.3/learn/concepts#aggregatespec","AggregateSpec",[101,139],"For aggregate functions: spec := edamame.AggregateSpec{\n    Field: \"age\",                           // Field to aggregate\n    Where: []edamame.ConditionSpec{...},    // Optional filter\n} Use with AggCount, AggSum, AggAvg, AggMin, or AggMax.",{"id":168,"title":169,"titles":170,"content":171,"level":19},"/v1.0.3/learn/concepts#conditions","Conditions",[101],"Conditions define WHERE clauses. They can be simple or grouped.",{"id":173,"title":174,"titles":175,"content":176,"level":40},"/v1.0.3/learn/concepts#simple-conditions","Simple Conditions",[101,169],"cond := edamame.ConditionSpec{\n    Field:    \"age\",\n    Operator: \">=\",\n    Param:    \"min_age\",\n}",{"id":178,"title":179,"titles":180,"content":181,"level":40},"/v1.0.3/learn/concepts#null-conditions","NULL Conditions",[101,169],"// IS NULL\ncond := edamame.ConditionSpec{\n    Field:    \"deleted_at\",\n    IsNull:   true,\n    Operator: \"IS NULL\",\n}\n\n// IS NOT NULL\ncond := edamame.ConditionSpec{\n    Field:    \"email\",\n    IsNull:   true,\n    Operator: \"IS NOT NULL\",\n}",{"id":183,"title":184,"titles":185,"content":186,"level":40},"/v1.0.3/learn/concepts#grouped-conditions-or","Grouped Conditions (OR)",[101,169],"cond := edamame.ConditionSpec{\n    Logic: \"OR\",\n    Group: []edamame.ConditionSpec{\n        {Field: \"status\", Operator: \"=\", Param: \"status1\"},\n        {Field: \"status\", Operator: \"=\", Param: \"status2\"},\n    },\n}",{"id":188,"title":189,"titles":190,"content":191,"level":40},"/v1.0.3/learn/concepts#supported-operators","Supported Operators",[101,169],"OperatorDescription=Equal!=, \u003C>Not equal\u003C, \u003C=Less than>, >=Greater thanLIKE, ILIKEPattern matchingINValue in listIS NULLNULL checkIS NOT NULLNOT NULL check",{"id":193,"title":194,"titles":195,"content":196,"level":19},"/v1.0.3/learn/concepts#ordering","Ordering",[101],"order := edamame.OrderBySpec{\n    Field:     \"created_at\",\n    Direction: \"desc\",      // \"asc\" or \"desc\"\n    Nulls:     \"last\",      // \"first\" or \"last\" (optional)\n}",{"id":198,"title":199,"titles":200,"content":201,"level":40},"/v1.0.3/learn/concepts#expression-based-ordering","Expression-Based Ordering",[101,194],"For vector similarity or computed distances: order := edamame.OrderBySpec{\n    Field:     \"embedding\",\n    Operator:  \"\u003C->\",           // pgvector distance operator\n    Param:     \"query_vector\",\n    Direction: \"asc\",\n}",{"id":203,"title":204,"titles":205,"content":206,"level":19},"/v1.0.3/learn/concepts#execution","Execution",[101],"Execute statements with params: // Query (multiple records)\nusers, err := exec.ExecQuery(ctx, ByStatus, map[string]any{\n    \"status\": \"active\",\n})\n\n// Select (single record)\nuser, err := exec.ExecSelect(ctx, ByEmail, map[string]any{\n    \"email\": \"alice@example.com\",\n})\n\n// Update\nupdated, err := exec.ExecUpdate(ctx, Activate, map[string]any{\n    \"id\":     123,\n    \"active\": true,\n})\n\n// Delete\ncount, err := exec.ExecDelete(ctx, DeleteByID, map[string]any{\n    \"id\": 123,\n})\n\n// Aggregate\navg, err := exec.ExecAggregate(ctx, AvgAge, nil)\n\n// Insert (no statement needed)\ninserted, err := exec.ExecInsert(ctx, &user)",{"id":208,"title":209,"titles":210,"content":211,"level":40},"/v1.0.3/learn/concepts#transaction-support","Transaction Support",[101,204],"All execution methods have *Tx variants: tx, err := db.BeginTxx(ctx, nil)\n\nuser, err := exec.ExecSelectTx(ctx, tx, SelectByID, params)\n_, err = exec.ExecUpdateTx(ctx, tx, Activate, params)\n\ntx.Commit()",{"id":213,"title":214,"titles":215,"content":216,"level":40},"/v1.0.3/learn/concepts#batch-operations","Batch Operations",[101,204],"// Batch insert\ncount, err := exec.ExecInsertBatch(ctx, users)\n\n// Batch update\ncount, err := exec.ExecUpdateBatch(ctx, Activate, []map[string]any{\n    {\"id\": 1, \"active\": true},\n    {\"id\": 2, \"active\": true},\n}) html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":218,"title":16,"titles":219,"content":220,"level":9},"/v1.0.3/learn/architecture",[],"How edamame works with soy for SQL generation",{"id":222,"title":16,"titles":223,"content":224,"level":9},"/v1.0.3/learn/architecture#architecture",[],"Edamame is a semantic layer over soy. Understanding this relationship helps you use both effectively.",{"id":226,"title":227,"titles":228,"content":229,"level":19},"/v1.0.3/learn/architecture#layer-diagram","Layer Diagram",[16],"┌─────────────────────────────────────────────────────────────┐\n│                      Your Application                       │\n│                                                             │\n│   exec.ExecQuery(ctx, ByStatus, params)                     │\n└─────────────────────────┬───────────────────────────────────┘\n                          │\n┌─────────────────────────▼───────────────────────────────────┐\n│                         Edamame                             │\n│                                                             │\n│  ┌──────────────┐  Typed statements                         │\n│  │ Executor[T]  │  Spec → Builder conversion                │\n│  │              │  Event emission (capitan)                 │\n│  └──────┬───────┘                                           │\n│         │                                                   │\n│  ┌──────▼───────────────────────────────────────────────┐   │\n│  │                  Statement Types                      │   │\n│  │  QueryStatement  SelectStatement  UpdateStatement     │   │\n│  │  DeleteStatement  AggregateStatement                  │   │\n│  └──────────────────────────────────────────────────────┘   │\n└─────────┬───────────────────────────────────────────────────┘\n          │\n┌─────────▼───────────────────────────────────────────────────┐\n│                          Soy                                │\n│                                                             │\n│  Query builder API                                          │\n│  Field validation (via ASTQL)                               │\n│  Parameterized SQL generation                               │\n│  Render() → {SQL, Params}                                   │\n└─────────┬───────────────────────────────────────────────────┘\n          │\n┌─────────▼───────────────────────────────────────────────────┐\n│                          sqlx                               │\n│                                                             │\n│  Connection pooling                                         │\n│  Named parameter binding                                    │\n│  Struct scanning                                            │\n└─────────────────────────────────────────────────────────────┘",{"id":231,"title":232,"titles":233,"content":34,"level":19},"/v1.0.3/learn/architecture#responsibilities","Responsibilities",[16],{"id":235,"title":236,"titles":237,"content":238,"level":40},"/v1.0.3/learn/architecture#edamame","Edamame",[16,232],"Typed statements - Compile-time safe query definitionsSpec-to-builder conversion - Transform declarative specs into soy buildersExecution wrappers - Convenient Exec* methods with paramsEvents - Emit executor lifecycle events via capitan",{"id":240,"title":241,"titles":242,"content":243,"level":40},"/v1.0.3/learn/architecture#soy","Soy",[16,232],"Query building - Fluent API for constructing SQLField validation - Validate field names against model metadataOperator validation - Ensure operators are safe and validSQL generation - Render builders to parameterized SQLExecution - Execute queries via sqlx",{"id":245,"title":246,"titles":247,"content":248,"level":40},"/v1.0.3/learn/architecture#when-to-use-each","When to Use Each",[16,232],"TaskUseDefine named operationsStatement typesExecute named operationsexec.Exec* methodsAd-hoc queriesexec.Soy().Query()...Custom SQL constructionSoy builder API",{"id":250,"title":251,"titles":252,"content":253,"level":19},"/v1.0.3/learn/architecture#spec-to-builder-flow","Spec-to-Builder Flow",[16],"When you call exec.ExecQuery(ctx, ByStatus, params): Extract spec - Statement contains the spec internallyConvert - Spec is converted to a soy builder:\n// QuerySpec → soy.Query[T]\nbuilder := exec.queryFromSpec(stmt.spec)\nRender - Builder generates SQL:\nresult, err := builder.Render()\n// result.SQL = \"SELECT ... FROM users WHERE status = $1\"\n// result.Params = []any{\"active\"}\nExecute - sqlx runs the parameterized query:\nrows, err := db.QueryxContext(ctx, result.SQL, result.Params...)\nScan - Results are scanned into structs",{"id":255,"title":256,"titles":257,"content":258,"level":19},"/v1.0.3/learn/architecture#security-model","Security Model",[16],"SQL injection protection flows through the entire stack: Edamame - Specs are data, not SQL stringsSoy - Field names validated against model metadataSoy - Operators validated against allowlistSoy - All values become bound parameterssqlx - Parameters passed separately from SQL // User input\nparams := map[string]any{\"status\": userInput}\n\n// Never interpolated into SQL\nexec.ExecQuery(ctx, ByStatus, params)\n\n// Becomes:\n// SQL: \"SELECT ... WHERE status = $1\"\n// Args: [userInput]  // Bound separately",{"id":260,"title":261,"titles":262,"content":263,"level":19},"/v1.0.3/learn/architecture#event-integration","Event Integration",[16],"Edamame emits events via capitan for observability: SignalWhenFieldsExecutorCreatedExecutor initializedtable Hook for monitoring: capitan.Hook(edamame.ExecutorCreated, func(ctx context.Context, e *capitan.Event) {\n    table, _ := edamame.KeyTable.From(e)\n    log.Printf(\"Executor created for table: %s\", table)\n})",{"id":265,"title":266,"titles":267,"content":268,"level":19},"/v1.0.3/learn/architecture#direct-soy-access","Direct Soy Access",[16],"For operations not covered by statements, access soy directly: // Get the underlying soy instance\ns := exec.Soy()\n\n// Use soy's fluent API\nusers, err := s.Query().\n    Where(\"age\", \">=\", \"min_age\").\n    Where(\"status\", \"=\", \"status\").\n    OrderBy(\"name\", \"asc\").\n    Limit(10).\n    Exec(ctx, params) This bypasses statement types but still benefits from: Field validationParameterized queriesType-safe results",{"id":270,"title":37,"titles":271,"content":272,"level":19},"/v1.0.3/learn/architecture#type-safety",[16],"Statements provide compile-time guarantees: // Compiler enforces correct statement types\nusers, err := exec.ExecQuery(ctx, QueryAll, nil)       // Must be QueryStatement\nuser, err := exec.ExecSelect(ctx, SelectByID, params)  // Must be SelectStatement\ncount, err := exec.ExecAggregate(ctx, CountAll, nil)   // Must be AggregateStatement\n\n// This won't compile:\n// exec.ExecQuery(ctx, SelectByID, nil)  // SelectStatement not allowed No magic strings, no runtime lookup failures. html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":274,"title":120,"titles":275,"content":276,"level":9},"/v1.0.3/guides/statements",[],"Defining and using typed database statements",{"id":278,"title":120,"titles":279,"content":280,"level":9},"/v1.0.3/guides/statements#statements",[],"This guide covers defining typed statements for your database operations.",{"id":282,"title":283,"titles":284,"content":285,"level":19},"/v1.0.3/guides/statements#queries","Queries",[120],"Queries return multiple records. Use for lists, search results, and filtered collections.",{"id":287,"title":288,"titles":289,"content":290,"level":40},"/v1.0.3/guides/statements#basic-query","Basic Query",[120,283],"var ActiveUsers = edamame.NewQueryStatement(\"active-users\", \"Find all active users\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"active\", Operator: \"=\", Param: \"active\"},\n    },\n})\n\n// Usage\nusers, err := exec.ExecQuery(ctx, ActiveUsers, map[string]any{\n    \"active\": true,\n})",{"id":292,"title":293,"titles":294,"content":295,"level":40},"/v1.0.3/guides/statements#with-ordering-and-pagination","With Ordering and Pagination",[120,283],"limit := 20\noffset := 0\n\nvar RecentUsers = edamame.NewQueryStatement(\"recent-users\", \"Get users ordered by creation date\", edamame.QuerySpec{\n    OrderBy: []edamame.OrderBySpec{\n        {Field: \"created_at\", Direction: \"desc\"},\n    },\n    Limit:  &limit,\n    Offset: &offset,\n})",{"id":297,"title":298,"titles":299,"content":300,"level":40},"/v1.0.3/guides/statements#with-field-selection","With Field Selection",[120,283],"var UserNames = edamame.NewQueryStatement(\"user-names\", \"Get user names only\", edamame.QuerySpec{\n    Fields: []string{\"id\", \"name\"},  // Only select these columns\n})",{"id":302,"title":303,"titles":304,"content":305,"level":40},"/v1.0.3/guides/statements#with-grouping","With Grouping",[120,283],"var UsersByRole = edamame.NewQueryStatement(\"users-by-role\", \"Group users by role\", edamame.QuerySpec{\n    Fields:  []string{\"role\", \"COUNT(*) as count\"},\n    GroupBy: []string{\"role\"},\n})",{"id":307,"title":308,"titles":309,"content":310,"level":40},"/v1.0.3/guides/statements#having-with-aggregates","HAVING with Aggregates",[120,283],"Use HavingAgg for aggregate conditions in HAVING clauses: var PopularRoles = edamame.NewQueryStatement(\"popular-roles\", \"Roles with minimum user count\", edamame.QuerySpec{\n    Fields:  []string{\"role\"},\n    GroupBy: []string{\"role\"},\n    HavingAgg: []edamame.HavingAggSpec{\n        {Func: \"count\", Field: \"*\", Operator: \">=\", Param: \"min_count\"},\n    },\n})\n\n// Generates: SELECT role FROM users GROUP BY role HAVING COUNT(*) >= $1 Multiple aggregate conditions: var HighValueRoles = edamame.NewQueryStatement(\"high-value-roles\", \"Roles with high balance and count\", edamame.QuerySpec{\n    Fields:  []string{\"role\"},\n    GroupBy: []string{\"role\"},\n    HavingAgg: []edamame.HavingAggSpec{\n        {Func: \"count\", Field: \"*\", Operator: \">=\", Param: \"min_count\"},\n        {Func: \"sum\", Field: \"balance\", Operator: \">=\", Param: \"min_total\"},\n    },\n})",{"id":312,"title":313,"titles":314,"content":315,"level":40},"/v1.0.3/guides/statements#distinct-on-postgresql","DISTINCT ON (PostgreSQL)",[120,283],"Use DistinctOn for PostgreSQL's DISTINCT ON clause: var LatestPerUser = edamame.NewQueryStatement(\"latest-per-user\", \"Latest record per user\", edamame.QuerySpec{\n    DistinctOn: []string{\"user_id\"},\n    OrderBy: []edamame.OrderBySpec{\n        {Field: \"user_id\", Direction: \"asc\"},\n        {Field: \"created_at\", Direction: \"desc\"},\n    },\n})\n\n// Generates: SELECT DISTINCT ON (user_id) * FROM ... ORDER BY user_id ASC, created_at DESC",{"id":317,"title":318,"titles":319,"content":320,"level":40},"/v1.0.3/guides/statements#complex-conditions","Complex Conditions",[120,283],"var FilteredUsers = edamame.NewQueryStatement(\"filtered-users\", \"Filter users by age and role\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"age\", Operator: \">=\", Param: \"min_age\"},\n        {Field: \"age\", Operator: \"\u003C=\", Param: \"max_age\"},\n        {\n            Logic: \"OR\",\n            Group: []edamame.ConditionSpec{\n                {Field: \"role\", Operator: \"=\", Param: \"role1\"},\n                {Field: \"role\", Operator: \"=\", Param: \"role2\"},\n            },\n        },\n    },\n})\n\n// Generates: WHERE age >= $1 AND age \u003C= $2 AND (role = $3 OR role = $4)",{"id":322,"title":323,"titles":324,"content":325,"level":40},"/v1.0.3/guides/statements#between-conditions","BETWEEN Conditions",[120,283],"var UsersInAgeRange = edamame.NewQueryStatement(\"users-in-age-range\", \"Users in age range\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"age\", Between: true, LowParam: \"min_age\", HighParam: \"max_age\"},\n    },\n})\n\n// Generates: WHERE age BETWEEN $1 AND $2\n\n// NOT BETWEEN\nvar UsersOutsideRange = edamame.NewQueryStatement(\"users-outside-range\", \"Users outside age range\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"age\", NotBetween: true, LowParam: \"min_age\", HighParam: \"max_age\"},\n    },\n})\n\n// Generates: WHERE age NOT BETWEEN $1 AND $2",{"id":327,"title":328,"titles":329,"content":330,"level":40},"/v1.0.3/guides/statements#field-to-field-comparisons","Field-to-Field Comparisons",[120,283],"Compare two columns directly without parameters: var ModifiedAfterCreated = edamame.NewQueryStatement(\"modified-after-created\", \"Records updated after creation\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"updated_at\", Operator: \">\", RightField: \"created_at\"},\n    },\n})\n\n// Generates: WHERE updated_at > created_at",{"id":332,"title":333,"titles":334,"content":335,"level":40},"/v1.0.3/guides/statements#parameterized-pagination","Parameterized Pagination",[120,283],"Use parameter-driven limits and offsets for flexible pagination: var PaginatedUsers = edamame.NewQueryStatement(\"paginated-users\", \"Paginated user list\", edamame.QuerySpec{\n    LimitParam:  \"page_size\",\n    OffsetParam: \"offset\",\n    OrderBy: []edamame.OrderBySpec{\n        {Field: \"created_at\", Direction: \"desc\"},\n    },\n})\n\n// Usage\nusers, err := exec.ExecQuery(ctx, PaginatedUsers, map[string]any{\n    \"page_size\": 20,\n    \"offset\":    40,  // Page 3\n})",{"id":337,"title":338,"titles":339,"content":340,"level":40},"/v1.0.3/guides/statements#select-expressions","Select Expressions",[120,283],"Add computed columns using SQL functions: var UsersWithComputed = edamame.NewQueryStatement(\"users-with-computed\", \"Users with computed columns\", edamame.QuerySpec{\n    Fields: []string{\"id\", \"name\"},\n    SelectExprs: []edamame.SelectExprSpec{\n        {Func: \"upper\", Field: \"name\", Alias: \"upper_name\"},\n        {Func: \"length\", Field: \"email\", Alias: \"email_length\"},\n        {Func: \"count_star\", Alias: \"total\"},\n        {Func: \"now\", Alias: \"query_time\"},\n    },\n    GroupBy: []string{\"id\", \"name\"},\n}) Available functions include: upper, lower, length, trim, concat, abs, ceil, floor, round, now, current_date, cast, count, sum, avg, min, max, coalesce, nullif.",{"id":342,"title":343,"titles":344,"content":345,"level":40},"/v1.0.3/guides/statements#with-row-locking","With Row Locking",[120,283],"var ForUpdate = edamame.NewQueryStatement(\"for-update\", \"Query with row lock\", edamame.QuerySpec{\n    Where:      []edamame.ConditionSpec{{Field: \"status\", Operator: \"=\", Param: \"status\"}},\n    ForLocking: \"update\",  // FOR UPDATE\n}) Locking options: \"update\", \"no_key_update\", \"share\", \"key_share\"",{"id":347,"title":348,"titles":349,"content":350,"level":19},"/v1.0.3/guides/statements#selects","Selects",[120],"Selects return a single record. Use for lookups by unique identifier.",{"id":352,"title":353,"titles":354,"content":355,"level":40},"/v1.0.3/guides/statements#by-unique-field","By Unique Field",[120,348],"var ByEmail = edamame.NewSelectStatement(\"by-email\", \"Find user by email address\", edamame.SelectSpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"email\", Operator: \"=\", Param: \"email\"},\n    },\n})\n\n// Usage - returns error if not found\nuser, err := exec.ExecSelect(ctx, ByEmail, map[string]any{\n    \"email\": \"alice@example.com\",\n})",{"id":357,"title":358,"titles":359,"content":360,"level":40},"/v1.0.3/guides/statements#with-locking","With Locking",[120,348],"var ForShare = edamame.NewSelectStatement(\"for-share\", \"Select with shared lock\", edamame.SelectSpec{\n    Where:      []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    ForLocking: \"share\",  // FOR SHARE\n})",{"id":362,"title":363,"titles":364,"content":365,"level":19},"/v1.0.3/guides/statements#updates","Updates",[120],"Updates modify records and return the updated row.",{"id":367,"title":368,"titles":369,"content":370,"level":40},"/v1.0.3/guides/statements#single-field-update","Single Field Update",[120,363],"var Activate = edamame.NewUpdateStatement(\"activate\", \"Activate a user by ID\", edamame.UpdateSpec{\n    Set: map[string]string{\n        \"active\": \"active\",  // field -> param\n    },\n    Where: []edamame.ConditionSpec{\n        {Field: \"id\", Operator: \"=\", Param: \"id\"},\n    },\n})\n\n// Usage\nupdated, err := exec.ExecUpdate(ctx, Activate, map[string]any{\n    \"id\":     123,\n    \"active\": true,\n})",{"id":372,"title":373,"titles":374,"content":375,"level":40},"/v1.0.3/guides/statements#multi-field-update","Multi-Field Update",[120,363],"var UpdateProfile = edamame.NewUpdateStatement(\"update-profile\", \"Update user profile\", edamame.UpdateSpec{\n    Set: map[string]string{\n        \"name\":  \"new_name\",\n        \"email\": \"new_email\",\n        \"bio\":   \"new_bio\",\n    },\n    Where: []edamame.ConditionSpec{\n        {Field: \"id\", Operator: \"=\", Param: \"id\"},\n    },\n})",{"id":377,"title":378,"titles":379,"content":380,"level":40},"/v1.0.3/guides/statements#batch-updates","Batch Updates",[120,363],"// Execute same update with different params\ncount, err := exec.ExecUpdateBatch(ctx, Activate, []map[string]any{\n    {\"id\": 1, \"active\": true},\n    {\"id\": 2, \"active\": true},\n    {\"id\": 3, \"active\": false},\n})",{"id":382,"title":383,"titles":384,"content":385,"level":19},"/v1.0.3/guides/statements#deletes","Deletes",[120],"Deletes remove records and return the count of deleted rows.",{"id":387,"title":388,"titles":389,"content":390,"level":40},"/v1.0.3/guides/statements#by-single-field","By Single Field",[120,383],"var ByStatus = edamame.NewDeleteStatement(\"by-status\", \"Delete all users with given status\", edamame.DeleteSpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"status\", Operator: \"=\", Param: \"status\"},\n    },\n})\n\n// Usage\ncount, err := exec.ExecDelete(ctx, ByStatus, map[string]any{\n    \"status\": \"inactive\",\n})",{"id":392,"title":393,"titles":394,"content":395,"level":40},"/v1.0.3/guides/statements#with-multiple-conditions","With Multiple Conditions",[120,383],"var ExpiredSessions = edamame.NewDeleteStatement(\"expired-sessions\", \"Delete expired sessions\", edamame.DeleteSpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"expires_at\", Operator: \"\u003C\", Param: \"now\"},\n        {Field: \"active\", Operator: \"=\", Param: \"active\"},\n    },\n})",{"id":397,"title":398,"titles":399,"content":400,"level":19},"/v1.0.3/guides/statements#aggregates","Aggregates",[120],"Aggregates compute values across records.",{"id":402,"title":403,"titles":404,"content":405,"level":40},"/v1.0.3/guides/statements#count","Count",[120,398],"var CountActive = edamame.NewAggregateStatement(\"count-active\", \"Count active users\", edamame.AggCount, edamame.AggregateSpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"active\", Operator: \"=\", Param: \"active\"},\n    },\n})\n\ncount, err := exec.ExecAggregate(ctx, CountActive, map[string]any{\n    \"active\": true,\n})",{"id":407,"title":408,"titles":409,"content":410,"level":40},"/v1.0.3/guides/statements#sum-avg-min-max","Sum, Avg, Min, Max",[120,398],"// Sum\nvar TotalBalance = edamame.NewAggregateStatement(\"total-balance\", \"Total balance\", edamame.AggSum, edamame.AggregateSpec{\n    Field: \"balance\",\n})\n\n// Average\nvar AvgAge = edamame.NewAggregateStatement(\"avg-age\", \"Average age\", edamame.AggAvg, edamame.AggregateSpec{\n    Field: \"age\",\n})\n\n// Min/Max\nvar Youngest = edamame.NewAggregateStatement(\"youngest\", \"Youngest user\", edamame.AggMin, edamame.AggregateSpec{\n    Field: \"age\",\n})",{"id":412,"title":413,"titles":414,"content":415,"level":19},"/v1.0.3/guides/statements#inserts","Inserts",[120],"Inserts don't use statements - they're driven by struct fields: // Single insert\ninserted, err := exec.ExecInsert(ctx, &user)\n\n// Batch insert\ncount, err := exec.ExecInsertBatch(ctx, users)",{"id":417,"title":418,"titles":419,"content":420,"level":40},"/v1.0.3/guides/statements#with-conflict-handling","With Conflict Handling",[120,413],"For upsert patterns, use the underlying soy API: s := exec.Soy()\n\n// ON CONFLICT DO NOTHING\nresult, err := s.Insert().\n    OnConflictDoNothing(\"email\").\n    Exec(ctx, &user)\n\n// ON CONFLICT DO UPDATE\nresult, err := s.Insert().\n    OnConflict(\"email\").\n    DoUpdate(map[string]string{\"name\": \"name\"}).\n    Exec(ctx, &user)",{"id":422,"title":423,"titles":424,"content":425,"level":19},"/v1.0.3/guides/statements#compound-queries","Compound Queries",[120],"Compound queries combine multiple SELECT statements using set operations.",{"id":427,"title":428,"titles":429,"content":430,"level":40},"/v1.0.3/guides/statements#union","UNION",[120,423],"spec := edamame.CompoundQuerySpec{\n    Base: edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"role\", Operator: \"=\", Param: \"role1\"},\n        },\n    },\n    Operands: []edamame.CompoundOperand{\n        {\n            Operation: \"union\",\n            Query: edamame.QuerySpec{\n                Where: []edamame.ConditionSpec{\n                    {Field: \"role\", Operator: \"=\", Param: \"role2\"},\n                },\n            },\n        },\n    },\n    OrderBy: []edamame.OrderBySpec{\n        {Field: \"name\", Direction: \"asc\"},\n    },\n}\n\nusers, err := exec.ExecCompound(ctx, spec, map[string]any{\n    \"role1\": \"admin\",\n    \"role2\": \"moderator\",\n})",{"id":432,"title":433,"titles":434,"content":435,"level":40},"/v1.0.3/guides/statements#available-operations","Available Operations",[120,423],"OperationDescriptionunionCombine results, remove duplicatesunion_allCombine results, keep duplicatesintersectOnly rows in both queriesintersect_allIntersection with duplicatesexceptRows in first but not secondexcept_allExcept with duplicates",{"id":437,"title":438,"titles":439,"content":440,"level":40},"/v1.0.3/guides/statements#rendering-for-inspection","Rendering for Inspection",[120,423],"sql, err := exec.RenderCompound(spec)\n// SELECT ... WHERE role = $1 UNION SELECT ... WHERE role = $2 ORDER BY name ASC",{"id":442,"title":134,"titles":443,"content":136,"level":19},"/v1.0.3/guides/statements#statement-metadata",[120],{"id":445,"title":446,"titles":447,"content":448,"level":19},"/v1.0.3/guides/statements#tags","Tags",[120],"Statements support optional tags for categorization: var ByRole = edamame.NewQueryStatement(\"by-role\", \"Find users by role\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"role\", Operator: \"=\", Param: \"role\"},\n    },\n}, \"user\", \"filter\", \"security\")  // Tags as variadic args",{"id":450,"title":451,"titles":452,"content":453,"level":19},"/v1.0.3/guides/statements#parameter-derivation","Parameter Derivation",[120],"Params are automatically derived from specs: var Example = edamame.NewQueryStatement(\"example\", \"Example query\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{\n        {Field: \"age\", Operator: \">=\", Param: \"min_age\"},\n        {Field: \"status\", Operator: \"=\", Param: \"status\"},\n    },\n})\n\n// Params auto-derived:\n// - min_age (required)\n// - status (required)\n\nfor _, p := range Example.Params() {\n    fmt.Printf(\"Param: %s (required: %v)\\n\", p.Name, p.Required)\n} html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}",{"id":455,"title":456,"titles":457,"content":458,"level":9},"/v1.0.3/guides/testing","Testing",[],"Testing strategies for edamame-based applications",{"id":460,"title":456,"titles":461,"content":462,"level":9},"/v1.0.3/guides/testing#testing",[],"Edamame provides testing utilities in the github.com/zoobz-io/edamame/testing package.",{"id":464,"title":465,"titles":466,"content":34,"level":19},"/v1.0.3/guides/testing#test-helpers","Test Helpers",[456],{"id":468,"title":469,"titles":470,"content":471,"level":40},"/v1.0.3/guides/testing#querycapture","QueryCapture",[456,465],"Capture rendered SQL for verification: import (\n    edamametesting \"github.com/zoobz-io/edamame/testing\"\n    \"github.com/zoobz-io/astql/pkg/postgres\"\n)\n\nfunc TestQueryRendering(t *testing.T) {\n    capture := edamametesting.NewQueryCapture()\n\n    exec, _ := edamame.New[User](nil, \"users\", postgres.New())\n\n    // Define a statement\n    var ByStatus = edamame.NewQueryStatement(\"by-status\", \"Find by status\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{{Field: \"status\", Operator: \"=\", Param: \"status\"}},\n    })\n\n    // Get the builder and render\n    q, _ := exec.Query(ByStatus)\n    result, _ := q.Render()\n\n    capture.CaptureQuery(\"by-status\", \"query\", result.SQL, nil)\n\n    // Verify\n    if capture.Count() != 1 {\n        t.Errorf(\"expected 1 query, got %d\", capture.Count())\n    }\n\n    last := capture.Last()\n    if last.Type != \"query\" {\n        t.Errorf(\"expected type 'query', got %q\", last.Type)\n    }\n}",{"id":473,"title":474,"titles":475,"content":476,"level":40},"/v1.0.3/guides/testing#executoreventcapture","ExecutorEventCapture",[456,465],"Capture executor creation events via capitan: func TestExecutorEvents(t *testing.T) {\n    c := capitan.New(capitan.WithSyncMode())\n    defer c.Shutdown()\n\n    capture := edamametesting.NewExecutorEventCapture()\n    c.Hook(edamame.ExecutorCreated, capture.Handler())\n\n    exec, _ := edamame.New[User](nil, \"users\", postgres.New())\n\n    // Verify event captured\n    if capture.Count() != 1 {\n        t.Error(\"expected executor created event\")\n    }\n\n    tables := capture.Tables()\n    if tables[0].Table != \"users\" {\n        t.Errorf(\"expected table 'users', got %q\", tables[0].Table)\n    }\n}",{"id":478,"title":479,"titles":480,"content":481,"level":40},"/v1.0.3/guides/testing#parambuilder","ParamBuilder",[456,465],"Build test parameters fluently: func TestWithParams(t *testing.T) {\n    params := edamametesting.NewParamBuilder().\n        Set(\"id\", 123).\n        Set(\"status\", \"active\").\n        Set(\"limit\", 10).\n        Build()\n\n    var Filtered = edamame.NewQueryStatement(\"filtered\", \"Filtered query\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{{Field: \"status\", Operator: \"=\", Param: \"status\"}},\n    })\n\n    // Use in tests\n    users, err := exec.ExecQuery(ctx, Filtered, params)\n}",{"id":483,"title":484,"titles":485,"content":486,"level":19},"/v1.0.3/guides/testing#unit-testing-without-database","Unit Testing Without Database",[456],"Test statement creation and specs without a database: func TestStatements(t *testing.T) {\n    // nil db is valid for testing statements\n    exec, err := edamame.New[User](nil, \"users\", postgres.New())\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // Define statements\n    var QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all\", edamame.QuerySpec{})\n\n    var ByStatus = edamame.NewQueryStatement(\"by-status\", \"Find by status\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"status\", Operator: \"=\", Param: \"status\"},\n        },\n    })\n\n    // Test statement metadata\n    if ByStatus.Name() != \"by-status\" {\n        t.Errorf(\"expected name 'by-status', got %q\", ByStatus.Name())\n    }\n\n    // Test params derived correctly\n    params := ByStatus.Params()\n    if len(params) != 1 || params[0].Name != \"status\" {\n        t.Error(\"expected 1 param named 'status'\")\n    }\n\n    // Test builder creation\n    q, err := exec.Query(ByStatus)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // Test rendering\n    result, err := q.Render()\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    if result.SQL == \"\" {\n        t.Error(\"empty SQL rendered\")\n    }\n}",{"id":488,"title":489,"titles":490,"content":491,"level":19},"/v1.0.3/guides/testing#testing-sql-rendering","Testing SQL Rendering",[456],"Verify generated SQL without executing: func TestSQLRendering(t *testing.T) {\n    exec, _ := edamame.New[User](nil, \"users\", postgres.New())\n\n    var Adults = edamame.NewQueryStatement(\"adults\", \"Find adults\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"age\", Operator: \">=\", Param: \"min_age\"},\n        },\n        OrderBy: []edamame.OrderBySpec{\n            {Field: \"name\", Direction: \"asc\"},\n        },\n    })\n\n    q, _ := exec.Query(Adults)\n    result, err := q.Render()\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // Verify SQL structure\n    if !strings.Contains(result.SQL, \"WHERE\") {\n        t.Error(\"SQL missing WHERE clause\")\n    }\n    if !strings.Contains(result.SQL, \"ORDER BY\") {\n        t.Error(\"SQL missing ORDER BY clause\")\n    }\n}",{"id":493,"title":494,"titles":495,"content":496,"level":19},"/v1.0.3/guides/testing#integration-testing-with-testcontainers","Integration Testing with Testcontainers",[456],"For database integration tests, use testcontainers: //go:build integration\n\npackage integration\n\nimport (\n    \"context\"\n    \"testing\"\n\n    \"github.com/testcontainers/testcontainers-go\"\n    \"github.com/testcontainers/testcontainers-go/wait\"\n)\n\nfunc TestWithPostgres(t *testing.T) {\n    ctx := context.Background()\n\n    // Start PostgreSQL container\n    req := testcontainers.ContainerRequest{\n        Image:        \"postgres:16-alpine\",\n        ExposedPorts: []string{\"5432/tcp\"},\n        WaitingFor:   wait.ForLog(\"database system is ready to accept connections\"),\n        Env: map[string]string{\n            \"POSTGRES_USER\":     \"test\",\n            \"POSTGRES_PASSWORD\": \"test\",\n            \"POSTGRES_DB\":       \"testdb\",\n        },\n    }\n\n    container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{\n        ContainerRequest: req,\n        Started:          true,\n    })\n    if err != nil {\n        t.Fatal(err)\n    }\n    defer container.Terminate(ctx)\n\n    // Get connection details\n    host, _ := container.Host(ctx)\n    port, _ := container.MappedPort(ctx, \"5432\")\n\n    // Connect and test\n    dsn := fmt.Sprintf(\"host=%s port=%s user=test password=test dbname=testdb sslmode=disable\", host, port.Port())\n    db, err := sqlx.Connect(\"postgres\", dsn)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // Create table\n    db.ExecContext(ctx, `CREATE TABLE users (\n        id SERIAL PRIMARY KEY,\n        email TEXT NOT NULL UNIQUE,\n        name TEXT,\n        age INTEGER\n    )`)\n\n    // Test executor\n    exec, _ := edamame.New[User](db, \"users\", postgres.New())\n\n    age := 25\n    user := &User{Email: \"test@example.com\", Name: \"Test\", Age: &age}\n    inserted, err := exec.ExecInsert(ctx, user)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    if inserted.ID == 0 {\n        t.Error(\"expected non-zero ID\")\n    }\n} Run integration tests: go test -tags=integration ./testing/integration/...",{"id":498,"title":499,"titles":500,"content":501,"level":19},"/v1.0.3/guides/testing#benchmarking","Benchmarking",[456],"The testing/benchmarks package provides performance benchmarks: func BenchmarkQueryBuilding(b *testing.B) {\n    exec, _ := edamame.New[User](nil, \"users\", postgres.New())\n\n    var QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all\", edamame.QuerySpec{})\n\n    b.ResetTimer()\n    b.ReportAllocs()\n\n    for i := 0; i \u003C b.N; i++ {\n        _, _ = exec.Query(QueryAll)\n    }\n} Run benchmarks: go test ./testing/benchmarks/... -bench=. -benchmem",{"id":503,"title":504,"titles":505,"content":506,"level":19},"/v1.0.3/guides/testing#testing-events","Testing Events",[456],"Verify capitan events are emitted correctly: func TestExecutorEmitsCreatedEvent(t *testing.T) {\n    c := capitan.New(capitan.WithSyncMode())\n    defer c.Shutdown()\n\n    capture := edamametesting.NewExecutorEventCapture()\n    c.Hook(edamame.ExecutorCreated, capture.Handler())\n\n    _, _ = edamame.New[User](nil, \"users\", postgres.New())\n\n    if capture.Count() != 1 {\n        t.Errorf(\"expected 1 executor created event, got %d\", capture.Count())\n    }\n\n    tables := capture.Tables()\n    if tables[0].Table != \"users\" {\n        t.Errorf(\"expected table 'users', got %q\", tables[0].Table)\n    }\n}",{"id":508,"title":509,"titles":510,"content":511,"level":19},"/v1.0.3/guides/testing#testing-transaction-behavior","Testing Transaction Behavior",[456],"func TestTransaction(t *testing.T) {\n    // ... setup db and exec ...\n\n    var QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all\", edamame.QuerySpec{})\n    var SelectByID = edamame.NewSelectStatement(\"select-by-id\", \"Select by ID\", edamame.SelectSpec{\n        Where: []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    })\n\n    tx, _ := db.BeginTxx(ctx, nil)\n\n    // Insert in transaction\n    user := &User{Email: \"tx@test.com\", Name: \"TxTest\"}\n    inserted, err := exec.ExecInsertTx(ctx, tx, user)\n    if err != nil {\n        tx.Rollback()\n        t.Fatal(err)\n    }\n\n    // Verify visible in transaction\n    users, _ := exec.ExecQueryTx(ctx, tx, QueryAll, nil)\n    if len(users) != 1 {\n        t.Error(\"expected 1 user in transaction\")\n    }\n\n    // Rollback\n    tx.Rollback()\n\n    // Verify not visible after rollback\n    users, _ = exec.ExecQuery(ctx, QueryAll, nil)\n    if len(users) != 0 {\n        t.Error(\"expected 0 users after rollback\")\n    }\n}",{"id":513,"title":514,"titles":515,"content":516,"level":19},"/v1.0.3/guides/testing#async-event-testing","Async Event Testing",[456],"Use WaitForCount for async event verification: func TestAsyncEvents(t *testing.T) {\n    c := capitan.New()  // async mode\n    defer c.Shutdown()\n\n    capture := edamametesting.NewExecutorEventCapture()\n    c.Hook(edamame.ExecutorCreated, capture.Handler())\n\n    _, _ = edamame.New[User](nil, \"users\", postgres.New())\n\n    // Wait for async event processing\n    if !capture.WaitForCount(1, 500*time.Millisecond) {\n        t.Error(\"timed out waiting for event\")\n    }\n} html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}",{"id":518,"title":519,"titles":520,"content":521,"level":9},"/v1.0.3/cookbook/llm-integration","LLM Integration",[],"Using edamame statements with AI assistants",{"id":523,"title":519,"titles":524,"content":525,"level":9},"/v1.0.3/cookbook/llm-integration#llm-integration",[],"Edamame's typed statements with self-describing metadata make it ideal for AI-assisted database operations.",{"id":527,"title":528,"titles":529,"content":530,"level":19},"/v1.0.3/cookbook/llm-integration#the-pattern","The Pattern",[519],"Define statements with descriptive names and descriptionsCollect statement metadata into a registrySerialize registry as JSON for LLM contextLLM selects statement by name and provides paramsExecute statement with LLM-provided inputs User Query → LLM (with statement metadata) → Statement Name + Params → Edamame → Results",{"id":532,"title":129,"titles":533,"content":534,"level":19},"/v1.0.3/cookbook/llm-integration#defining-statements",[519],"Statements are self-describing with name, description, params, and tags: var (\n    QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all users\", edamame.QuerySpec{})\n\n    ByRole = edamame.NewQueryStatement(\"by-role\", \"Find users by role\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"role\", Operator: \"=\", Param: \"role\"},\n        },\n    }, \"filter\", \"security\")\n\n    ActiveAdults = edamame.NewQueryStatement(\"active-adults\", \"Find active users over 18\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"active\", Operator: \"=\", Param: \"active\"},\n            {Field: \"age\", Operator: \">=\", Param: \"min_age\"},\n        },\n    }, \"filter\")\n\n    SelectByID = edamame.NewSelectStatement(\"select-by-id\", \"Select a single user by ID\", edamame.SelectSpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"id\", Operator: \"=\", Param: \"id\"},\n        },\n    })\n\n    CountAll = edamame.NewAggregateStatement(\"count-all\", \"Count all users\", edamame.AggCount, edamame.AggregateSpec{})\n)",{"id":536,"title":537,"titles":538,"content":539,"level":19},"/v1.0.3/cookbook/llm-integration#building-a-statement-registry","Building a Statement Registry",[519],"Create a registry to collect statements for LLM consumption: type StatementInfo struct {\n    Name        string      `json:\"name\"`\n    Description string      `json:\"description\"`\n    Type        string      `json:\"type\"`\n    Params      []ParamInfo `json:\"params\"`\n    Tags        []string    `json:\"tags,omitempty\"`\n}\n\ntype ParamInfo struct {\n    Name     string `json:\"name\"`\n    Type     string `json:\"type\"`\n    Required bool   `json:\"required\"`\n}\n\ntype StatementRegistry struct {\n    Table      string          `json:\"table\"`\n    Queries    []StatementInfo `json:\"queries,omitempty\"`\n    Selects    []StatementInfo `json:\"selects,omitempty\"`\n    Updates    []StatementInfo `json:\"updates,omitempty\"`\n    Deletes    []StatementInfo `json:\"deletes,omitempty\"`\n    Aggregates []StatementInfo `json:\"aggregates,omitempty\"`\n}\n\nfunc NewRegistry(table string) *StatementRegistry {\n    return &StatementRegistry{Table: table}\n}\n\nfunc (r *StatementRegistry) AddQuery(stmt edamame.QueryStatement) {\n    r.Queries = append(r.Queries, toStatementInfo(stmt.Name(), stmt.Description(), \"query\", stmt.Params(), stmt.Tags()))\n}\n\nfunc (r *StatementRegistry) AddSelect(stmt edamame.SelectStatement) {\n    r.Selects = append(r.Selects, toStatementInfo(stmt.Name(), stmt.Description(), \"select\", stmt.Params(), stmt.Tags()))\n}\n\nfunc (r *StatementRegistry) AddAggregate(stmt edamame.AggregateStatement) {\n    r.Aggregates = append(r.Aggregates, toStatementInfo(stmt.Name(), stmt.Description(), \"aggregate\", stmt.Params(), stmt.Tags()))\n}\n\nfunc toStatementInfo(name, desc, typ string, params []edamame.ParamSpec, tags []string) StatementInfo {\n    info := StatementInfo{\n        Name:        name,\n        Description: desc,\n        Type:        typ,\n        Tags:        tags,\n    }\n    for _, p := range params {\n        info.Params = append(info.Params, ParamInfo{\n            Name:     p.Name,\n            Type:     p.Type,\n            Required: p.Required,\n        })\n    }\n    return info\n}\n\nfunc (r *StatementRegistry) JSON() (string, error) {\n    data, err := json.MarshalIndent(r, \"\", \"  \")\n    return string(data), err\n}",{"id":541,"title":542,"titles":543,"content":544,"level":19},"/v1.0.3/cookbook/llm-integration#exporting-for-llm-context","Exporting for LLM Context",[519],"// Build registry\nregistry := NewRegistry(\"users\")\nregistry.AddQuery(QueryAll)\nregistry.AddQuery(ByRole)\nregistry.AddQuery(ActiveAdults)\nregistry.AddSelect(SelectByID)\nregistry.AddAggregate(CountAll)\n\n// Export as JSON\njson, _ := registry.JSON() Example output: {\n  \"table\": \"users\",\n  \"queries\": [\n    {\n      \"name\": \"query-all\",\n      \"description\": \"Query all users\",\n      \"type\": \"query\",\n      \"params\": []\n    },\n    {\n      \"name\": \"by-role\",\n      \"description\": \"Find users by role\",\n      \"type\": \"query\",\n      \"params\": [\n        {\"name\": \"role\", \"type\": \"any\", \"required\": true}\n      ],\n      \"tags\": [\"filter\", \"security\"]\n    },\n    {\n      \"name\": \"active-adults\",\n      \"description\": \"Find active users over 18\",\n      \"type\": \"query\",\n      \"params\": [\n        {\"name\": \"active\", \"type\": \"any\", \"required\": true},\n        {\"name\": \"min_age\", \"type\": \"any\", \"required\": true}\n      ],\n      \"tags\": [\"filter\"]\n    }\n  ],\n  \"selects\": [\n    {\n      \"name\": \"select-by-id\",\n      \"description\": \"Select a single user by ID\",\n      \"type\": \"select\",\n      \"params\": [\n        {\"name\": \"id\", \"type\": \"any\", \"required\": true}\n      ]\n    }\n  ],\n  \"aggregates\": [\n    {\n      \"name\": \"count-all\",\n      \"description\": \"Count all users\",\n      \"type\": \"aggregate\",\n      \"params\": []\n    }\n  ]\n}",{"id":546,"title":547,"titles":548,"content":549,"level":19},"/v1.0.3/cookbook/llm-integration#system-prompt-design","System Prompt Design",[519],"Provide statement metadata in your LLM system prompt: You are a database assistant. You have access to the following operations:\n\n{registry_json}\n\nWhen the user asks a question about data:\n1. Identify the appropriate operation\n2. Extract required parameters from the user's request\n3. Respond with JSON: {\"statement\": \"name\", \"type\": \"query|select|...\", \"params\": {...}}\n\nExamples:\n- \"How many users are there?\" → {\"statement\": \"count-all\", \"type\": \"aggregate\", \"params\": {}}\n- \"Find admins\" → {\"statement\": \"by-role\", \"type\": \"query\", \"params\": {\"role\": \"admin\"}}\n- \"Get user 123\" → {\"statement\": \"select-by-id\", \"type\": \"select\", \"params\": {\"id\": 123}}",{"id":551,"title":552,"titles":553,"content":554,"level":19},"/v1.0.3/cookbook/llm-integration#executing-llm-responses","Executing LLM Responses",[519],"Create a dispatcher that maps statement names to actual statements: type LLMResponse struct {\n    Statement string         `json:\"statement\"`\n    Type      string         `json:\"type\"`\n    Params    map[string]any `json:\"params\"`\n}\n\ntype StatementDispatcher struct {\n    exec       *edamame.Executor[User]\n    queries    map[string]edamame.QueryStatement\n    selects    map[string]edamame.SelectStatement\n    aggregates map[string]edamame.AggregateStatement\n}\n\nfunc NewDispatcher(exec *edamame.Executor[User]) *StatementDispatcher {\n    return &StatementDispatcher{\n        exec:       exec,\n        queries:    make(map[string]edamame.QueryStatement),\n        selects:    make(map[string]edamame.SelectStatement),\n        aggregates: make(map[string]edamame.AggregateStatement),\n    }\n}\n\nfunc (d *StatementDispatcher) RegisterQuery(stmt edamame.QueryStatement) {\n    d.queries[stmt.Name()] = stmt\n}\n\nfunc (d *StatementDispatcher) RegisterSelect(stmt edamame.SelectStatement) {\n    d.selects[stmt.Name()] = stmt\n}\n\nfunc (d *StatementDispatcher) RegisterAggregate(stmt edamame.AggregateStatement) {\n    d.aggregates[stmt.Name()] = stmt\n}\n\nfunc (d *StatementDispatcher) Execute(ctx context.Context, resp LLMResponse) (any, error) {\n    switch resp.Type {\n    case \"query\":\n        stmt, ok := d.queries[resp.Statement]\n        if !ok {\n            return nil, fmt.Errorf(\"unknown query: %s\", resp.Statement)\n        }\n        return d.exec.ExecQuery(ctx, stmt, resp.Params)\n    case \"select\":\n        stmt, ok := d.selects[resp.Statement]\n        if !ok {\n            return nil, fmt.Errorf(\"unknown select: %s\", resp.Statement)\n        }\n        return d.exec.ExecSelect(ctx, stmt, resp.Params)\n    case \"aggregate\":\n        stmt, ok := d.aggregates[resp.Statement]\n        if !ok {\n            return nil, fmt.Errorf(\"unknown aggregate: %s\", resp.Statement)\n        }\n        return d.exec.ExecAggregate(ctx, stmt, resp.Params)\n    default:\n        return nil, fmt.Errorf(\"unknown type: %s\", resp.Type)\n    }\n}",{"id":556,"title":557,"titles":558,"content":559,"level":19},"/v1.0.3/cookbook/llm-integration#complete-setup","Complete Setup",[519],"// Create executor\nexec, _ := edamame.New[User](db, \"users\", postgres.New())\n\n// Create dispatcher and register statements\ndispatcher := NewDispatcher(exec)\ndispatcher.RegisterQuery(QueryAll)\ndispatcher.RegisterQuery(ByRole)\ndispatcher.RegisterQuery(ActiveAdults)\ndispatcher.RegisterSelect(SelectByID)\ndispatcher.RegisterAggregate(CountAll)\n\n// Build registry for LLM context\nregistry := NewRegistry(\"users\")\nregistry.AddQuery(QueryAll)\nregistry.AddQuery(ByRole)\nregistry.AddQuery(ActiveAdults)\nregistry.AddSelect(SelectByID)\nregistry.AddAggregate(CountAll)\n\nllmContext, _ := registry.JSON()",{"id":561,"title":562,"titles":563,"content":564,"level":19},"/v1.0.3/cookbook/llm-integration#validation","Validation",[519],"Validate LLM responses before execution: func (d *StatementDispatcher) Validate(resp LLMResponse) error {\n    switch resp.Type {\n    case \"query\":\n        stmt, ok := d.queries[resp.Statement]\n        if !ok {\n            return fmt.Errorf(\"unknown query: %s\", resp.Statement)\n        }\n        return validateParams(stmt.Params(), resp.Params)\n    case \"select\":\n        stmt, ok := d.selects[resp.Statement]\n        if !ok {\n            return fmt.Errorf(\"unknown select: %s\", resp.Statement)\n        }\n        return validateParams(stmt.Params(), resp.Params)\n    case \"aggregate\":\n        stmt, ok := d.aggregates[resp.Statement]\n        if !ok {\n            return fmt.Errorf(\"unknown aggregate: %s\", resp.Statement)\n        }\n        return validateParams(stmt.Params(), resp.Params)\n    default:\n        return fmt.Errorf(\"unknown type: %s\", resp.Type)\n    }\n}\n\nfunc validateParams(specs []edamame.ParamSpec, provided map[string]any) error {\n    for _, spec := range specs {\n        if spec.Required {\n            if _, ok := provided[spec.Name]; !ok {\n                return fmt.Errorf(\"missing required param: %s\", spec.Name)\n            }\n        }\n    }\n    return nil\n}",{"id":566,"title":567,"titles":568,"content":569,"level":19},"/v1.0.3/cookbook/llm-integration#rate-limiting","Rate Limiting",[519],"Protect against LLM-driven query floods: type RateLimitedDispatcher struct {\n    dispatcher *StatementDispatcher\n    limiter    *rate.Limiter\n}\n\nfunc NewRateLimitedDispatcher(dispatcher *StatementDispatcher, rps float64) *RateLimitedDispatcher {\n    return &RateLimitedDispatcher{\n        dispatcher: dispatcher,\n        limiter:    rate.NewLimiter(rate.Limit(rps), 10),\n    }\n}\n\nfunc (d *RateLimitedDispatcher) Execute(ctx context.Context, resp LLMResponse) (any, error) {\n    if err := d.limiter.Wait(ctx); err != nil {\n        return nil, err\n    }\n    return d.dispatcher.Execute(ctx, resp)\n}",{"id":571,"title":572,"titles":573,"content":574,"level":19},"/v1.0.3/cookbook/llm-integration#audit-logging","Audit Logging",[519],"Log LLM-driven operations: func (d *StatementDispatcher) ExecuteWithAudit(ctx context.Context, resp LLMResponse, userID string) (any, error) {\n    start := time.Now()\n\n    result, err := d.Execute(ctx, resp)\n\n    log.Printf(\"LLM execution: user=%s statement=%s type=%s params=%v duration=%v error=%v\",\n        userID,\n        resp.Statement,\n        resp.Type,\n        resp.Params,\n        time.Since(start),\n        err,\n    )\n\n    return result, err\n}",{"id":576,"title":577,"titles":578,"content":579,"level":19},"/v1.0.3/cookbook/llm-integration#example-chat-interface","Example: Chat Interface",[519],"Complete example with a simple chat interface: func HandleChat(ctx context.Context, dispatcher *StatementDispatcher, registry *StatementRegistry, llm LLMClient, userMessage string) string {\n    // 1. Get statement metadata\n    specs, _ := registry.JSON()\n\n    // 2. Build prompt\n    prompt := fmt.Sprintf(`You are a database assistant. Available operations:\n%s\n\nUser: %s\n\nRespond with JSON: {\"statement\": \"...\", \"type\": \"...\", \"params\": {...}}\nOr respond with {\"error\": \"...\"} if the request cannot be fulfilled.`, specs, userMessage)\n\n    // 3. Get LLM response\n    llmResp, err := llm.Complete(ctx, prompt)\n    if err != nil {\n        return \"I couldn't process that request.\"\n    }\n\n    // 4. Parse response\n    var resp LLMResponse\n    if err := json.Unmarshal([]byte(llmResp), &resp); err != nil {\n        return \"I couldn't understand how to query the database.\"\n    }\n\n    // 5. Validate\n    if err := dispatcher.Validate(resp); err != nil {\n        return fmt.Sprintf(\"Invalid request: %v\", err)\n    }\n\n    // 6. Execute\n    result, err := dispatcher.Execute(ctx, resp)\n    if err != nil {\n        return fmt.Sprintf(\"Query failed: %v\", err)\n    }\n\n    // 7. Format response\n    return formatResult(result)\n}",{"id":581,"title":582,"titles":583,"content":584,"level":19},"/v1.0.3/cookbook/llm-integration#security-considerations","Security Considerations",[519],"Validate all LLM outputs before executionUse parameterized queries (edamame handles this)Limit exposed statements to what's safeRate limit LLM-driven operationsAudit log all executionsNever expose raw SQL generation to LLMs html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":586,"title":587,"titles":588,"content":589,"level":9},"/v1.0.3/reference/api","API Reference",[],"Complete API reference for the edamame package",{"id":591,"title":587,"titles":592,"content":593,"level":9},"/v1.0.3/reference/api#api-reference",[],"Complete API reference for the github.com/zoobz-io/edamame package.",{"id":595,"title":110,"titles":596,"content":597,"level":19},"/v1.0.3/reference/api#executor",[587],"The main execution context for a model type.",{"id":599,"title":600,"titles":601,"content":602,"level":40},"/v1.0.3/reference/api#new","New",[587,110],"func New[T any](db *sqlx.DB, tableName string, renderer astql.Renderer) (*Executor[T], error) Creates a new executor for type T bound to the given table with the specified SQL renderer. The db parameter can be nil for testing statement rendering without database access. Supported renderers via astql: github.com/zoobz-io/astql/pkg/postgres - PostgreSQLgithub.com/zoobz-io/astql/pkg/mariadb - MariaDBgithub.com/zoobz-io/astql/pkg/sqlite - SQLitegithub.com/zoobz-io/astql/pkg/mssql - SQL Server import \"github.com/zoobz-io/astql/pkg/postgres\" // or mariadb, sqlite, mssql\n\nexec, err := edamame.New[User](db, \"users\", postgres.New())",{"id":604,"title":605,"titles":606,"content":34,"level":19},"/v1.0.3/reference/api#statement-constructors","Statement Constructors",[587],{"id":608,"title":609,"titles":610,"content":611,"level":40},"/v1.0.3/reference/api#newquerystatement","NewQueryStatement",[587,605],"func NewQueryStatement(name, description string, spec QuerySpec, tags ...string) QueryStatement Creates a typed query statement for multi-record retrieval. Parameters are automatically derived from the spec.",{"id":613,"title":614,"titles":615,"content":616,"level":40},"/v1.0.3/reference/api#newselectstatement","NewSelectStatement",[587,605],"func NewSelectStatement(name, description string, spec SelectSpec, tags ...string) SelectStatement Creates a typed select statement for single-record retrieval.",{"id":618,"title":619,"titles":620,"content":621,"level":40},"/v1.0.3/reference/api#newupdatestatement","NewUpdateStatement",[587,605],"func NewUpdateStatement(name, description string, spec UpdateSpec, tags ...string) UpdateStatement Creates a typed update statement for modifications.",{"id":623,"title":624,"titles":625,"content":626,"level":40},"/v1.0.3/reference/api#newdeletestatement","NewDeleteStatement",[587,605],"func NewDeleteStatement(name, description string, spec DeleteSpec, tags ...string) DeleteStatement Creates a typed delete statement for deletions.",{"id":628,"title":629,"titles":630,"content":631,"level":40},"/v1.0.3/reference/api#newaggregatestatement","NewAggregateStatement",[587,605],"func NewAggregateStatement(name, description string, fn AggregateFunc, spec AggregateSpec, tags ...string) AggregateStatement Creates a typed aggregate statement for COUNT, SUM, AVG, MIN, MAX operations.",{"id":633,"title":27,"titles":634,"content":635,"level":19},"/v1.0.3/reference/api#statement-types",[587],"All statement types share common methods: func (s Statement) ID() uuid.UUID      // Unique identifier\nfunc (s Statement) Name() string       // Human-readable name\nfunc (s Statement) Description() string // What the statement does\nfunc (s Statement) Params() []ParamSpec // Required parameters\nfunc (s Statement) Tags() []string     // Optional categorization tags",{"id":637,"title":638,"titles":639,"content":640,"level":40},"/v1.0.3/reference/api#querystatement","QueryStatement",[587,27],"For multi-record retrieval operations.",{"id":642,"title":643,"titles":644,"content":645,"level":40},"/v1.0.3/reference/api#selectstatement","SelectStatement",[587,27],"For single-record retrieval operations.",{"id":647,"title":648,"titles":649,"content":650,"level":40},"/v1.0.3/reference/api#updatestatement","UpdateStatement",[587,27],"For modification operations.",{"id":652,"title":653,"titles":654,"content":655,"level":40},"/v1.0.3/reference/api#deletestatement","DeleteStatement",[587,27],"For deletion operations.",{"id":657,"title":658,"titles":659,"content":660,"level":40},"/v1.0.3/reference/api#aggregatestatement","AggregateStatement",[587,27],"For aggregate operations (COUNT, SUM, AVG, MIN, MAX).",{"id":662,"title":663,"titles":664,"content":34,"level":19},"/v1.0.3/reference/api#executor-methods","Executor Methods",[587],{"id":666,"title":667,"titles":668,"content":34,"level":40},"/v1.0.3/reference/api#builder-access","Builder Access",[587,663],{"id":670,"title":671,"titles":672,"content":673,"level":674},"/v1.0.3/reference/api#query","Query",[587,663,667],"func (e *Executor[T]) Query(stmt QueryStatement) (*soy.Query[T], error) Returns a soy Query builder for the statement.",4,{"id":676,"title":677,"titles":678,"content":679,"level":674},"/v1.0.3/reference/api#select","Select",[587,663,667],"func (e *Executor[T]) Select(stmt SelectStatement) (*soy.Select[T], error) Returns a soy Select builder for the statement.",{"id":681,"title":682,"titles":683,"content":684,"level":674},"/v1.0.3/reference/api#update","Update",[587,663,667],"func (e *Executor[T]) Update(stmt UpdateStatement) (*soy.Update[T], error) Returns a soy Update builder for the statement.",{"id":686,"title":687,"titles":688,"content":689,"level":674},"/v1.0.3/reference/api#delete","Delete",[587,663,667],"func (e *Executor[T]) Delete(stmt DeleteStatement) (*soy.Delete[T], error) Returns a soy Delete builder for the statement.",{"id":691,"title":692,"titles":693,"content":694,"level":674},"/v1.0.3/reference/api#aggregate","Aggregate",[587,663,667],"func (e *Executor[T]) Aggregate(stmt AggregateStatement) *soy.Aggregate[T] Returns a soy Aggregate builder for the statement.",{"id":696,"title":697,"titles":698,"content":699,"level":674},"/v1.0.3/reference/api#insert","Insert",[587,663,667],"func (e *Executor[T]) Insert() *soy.Create[T] Returns a soy Create builder for inserts.",{"id":701,"title":702,"titles":703,"content":704,"level":674},"/v1.0.3/reference/api#compound","Compound",[587,663,667],"func (e *Executor[T]) Compound(spec CompoundQuerySpec) (*soy.Compound[T], error) Returns a soy Compound builder for set operations (UNION, INTERSECT, EXCEPT).",{"id":706,"title":707,"titles":708,"content":34,"level":40},"/v1.0.3/reference/api#execution-methods","Execution Methods",[587,663],{"id":710,"title":711,"titles":712,"content":713,"level":674},"/v1.0.3/reference/api#execquery-execquerytx","ExecQuery / ExecQueryTx",[587,663,707],"func (e *Executor[T]) ExecQuery(ctx context.Context, stmt QueryStatement, params map[string]any) ([]*T, error)\nfunc (e *Executor[T]) ExecQueryTx(ctx context.Context, tx *sqlx.Tx, stmt QueryStatement, params map[string]any) ([]*T, error) Executes a query statement, returning multiple records.",{"id":715,"title":716,"titles":717,"content":718,"level":674},"/v1.0.3/reference/api#execselect-execselecttx","ExecSelect / ExecSelectTx",[587,663,707],"func (e *Executor[T]) ExecSelect(ctx context.Context, stmt SelectStatement, params map[string]any) (*T, error)\nfunc (e *Executor[T]) ExecSelectTx(ctx context.Context, tx *sqlx.Tx, stmt SelectStatement, params map[string]any) (*T, error) Executes a select statement, returning a single record.",{"id":720,"title":721,"titles":722,"content":723,"level":674},"/v1.0.3/reference/api#execupdate-execupdatetx","ExecUpdate / ExecUpdateTx",[587,663,707],"func (e *Executor[T]) ExecUpdate(ctx context.Context, stmt UpdateStatement, params map[string]any) (*T, error)\nfunc (e *Executor[T]) ExecUpdateTx(ctx context.Context, tx *sqlx.Tx, stmt UpdateStatement, params map[string]any) (*T, error) Executes an update statement, returning the updated record.",{"id":725,"title":726,"titles":727,"content":728,"level":674},"/v1.0.3/reference/api#execdelete-execdeletetx","ExecDelete / ExecDeleteTx",[587,663,707],"func (e *Executor[T]) ExecDelete(ctx context.Context, stmt DeleteStatement, params map[string]any) (int64, error)\nfunc (e *Executor[T]) ExecDeleteTx(ctx context.Context, tx *sqlx.Tx, stmt DeleteStatement, params map[string]any) (int64, error) Executes a delete statement, returning the count of deleted rows.",{"id":730,"title":731,"titles":732,"content":733,"level":674},"/v1.0.3/reference/api#execaggregate-execaggregatetx","ExecAggregate / ExecAggregateTx",[587,663,707],"func (e *Executor[T]) ExecAggregate(ctx context.Context, stmt AggregateStatement, params map[string]any) (float64, error)\nfunc (e *Executor[T]) ExecAggregateTx(ctx context.Context, tx *sqlx.Tx, stmt AggregateStatement, params map[string]any) (float64, error) Executes an aggregate statement, returning the result.",{"id":735,"title":736,"titles":737,"content":738,"level":674},"/v1.0.3/reference/api#execinsert-execinserttx","ExecInsert / ExecInsertTx",[587,663,707],"func (e *Executor[T]) ExecInsert(ctx context.Context, record *T) (*T, error)\nfunc (e *Executor[T]) ExecInsertTx(ctx context.Context, tx *sqlx.Tx, record *T) (*T, error) Inserts a record, returning it with generated fields populated.",{"id":740,"title":741,"titles":742,"content":743,"level":674},"/v1.0.3/reference/api#execcompound-execcompoundtx","ExecCompound / ExecCompoundTx",[587,663,707],"func (e *Executor[T]) ExecCompound(ctx context.Context, spec CompoundQuerySpec, params map[string]any) ([]*T, error)\nfunc (e *Executor[T]) ExecCompoundTx(ctx context.Context, tx *sqlx.Tx, spec CompoundQuerySpec, params map[string]any) ([]*T, error) Executes a compound query (UNION, INTERSECT, EXCEPT), returning multiple records.",{"id":745,"title":746,"titles":747,"content":34,"level":40},"/v1.0.3/reference/api#batch-execution","Batch Execution",[587,663],{"id":749,"title":750,"titles":751,"content":752,"level":674},"/v1.0.3/reference/api#execinsertbatch-execinsertbatchtx","ExecInsertBatch / ExecInsertBatchTx",[587,663,746],"func (e *Executor[T]) ExecInsertBatch(ctx context.Context, records []*T) (int64, error)\nfunc (e *Executor[T]) ExecInsertBatchTx(ctx context.Context, tx *sqlx.Tx, records []*T) (int64, error) Inserts multiple records, returning the count.",{"id":754,"title":755,"titles":756,"content":757,"level":674},"/v1.0.3/reference/api#execupdatebatch-execupdatebatchtx","ExecUpdateBatch / ExecUpdateBatchTx",[587,663,746],"func (e *Executor[T]) ExecUpdateBatch(ctx context.Context, stmt UpdateStatement, batchParams []map[string]any) (int64, error)\nfunc (e *Executor[T]) ExecUpdateBatchTx(ctx context.Context, tx *sqlx.Tx, stmt UpdateStatement, batchParams []map[string]any) (int64, error) Executes an update statement with multiple parameter sets.",{"id":759,"title":760,"titles":761,"content":762,"level":674},"/v1.0.3/reference/api#execdeletebatch-execdeletebatchtx","ExecDeleteBatch / ExecDeleteBatchTx",[587,663,746],"func (e *Executor[T]) ExecDeleteBatch(ctx context.Context, stmt DeleteStatement, batchParams []map[string]any) (int64, error)\nfunc (e *Executor[T]) ExecDeleteBatchTx(ctx context.Context, tx *sqlx.Tx, stmt DeleteStatement, batchParams []map[string]any) (int64, error) Executes a delete statement with multiple parameter sets.",{"id":764,"title":765,"titles":766,"content":767,"level":40},"/v1.0.3/reference/api#type-erased-execution-atom","Type-Erased Execution (Atom)",[587,663],"These methods return results as atom.Atom types, enabling type-erased execution where the concrete type T is not known at consumption time. Useful for dynamic query handling and LLM-driven database operations.",{"id":769,"title":770,"titles":771,"content":772,"level":674},"/v1.0.3/reference/api#execqueryatom","ExecQueryAtom",[587,663,765],"func (e *Executor[T]) ExecQueryAtom(ctx context.Context, stmt QueryStatement, params map[string]any) ([]*atom.Atom, error) Executes a query statement and returns results as Atoms.",{"id":774,"title":775,"titles":776,"content":777,"level":674},"/v1.0.3/reference/api#execselectatom","ExecSelectAtom",[587,663,765],"func (e *Executor[T]) ExecSelectAtom(ctx context.Context, stmt SelectStatement, params map[string]any) (*atom.Atom, error) Executes a select statement and returns the result as an Atom.",{"id":779,"title":780,"titles":781,"content":782,"level":674},"/v1.0.3/reference/api#execinsertatom","ExecInsertAtom",[587,663,765],"func (e *Executor[T]) ExecInsertAtom(ctx context.Context, params map[string]any) (*atom.Atom, error) Executes an insert using parameter map and returns the result as an Atom.",{"id":784,"title":785,"titles":786,"content":34,"level":40},"/v1.0.3/reference/api#rendering","Rendering",[587,663],{"id":788,"title":789,"titles":790,"content":791,"level":674},"/v1.0.3/reference/api#rendercompound","RenderCompound",[587,663,785],"func (e *Executor[T]) RenderCompound(spec CompoundQuerySpec) (string, error) Renders a compound query to SQL for inspection or debugging.",{"id":793,"title":794,"titles":795,"content":34,"level":40},"/v1.0.3/reference/api#other","Other",[587,663],{"id":797,"title":241,"titles":798,"content":799,"level":674},"/v1.0.3/reference/api#soy",[587,663,794],"func (e *Executor[T]) Soy() *soy.Soy[T] Returns the underlying soy instance for direct builder access.",{"id":801,"title":802,"titles":803,"content":804,"level":674},"/v1.0.3/reference/api#tablename","TableName",[587,663,794],"func (e *Executor[T]) TableName() string Returns the table name.",{"id":806,"title":807,"titles":808,"content":34,"level":19},"/v1.0.3/reference/api#spec-types","Spec Types",[587],{"id":810,"title":144,"titles":811,"content":812,"level":40},"/v1.0.3/reference/api#queryspec",[587,807],"type QuerySpec struct {\n    Fields      []string\n    SelectExprs []SelectExprSpec  // Expression-based SELECT (functions, aggregates)\n    Where       []ConditionSpec\n    OrderBy     []OrderBySpec\n    GroupBy     []string\n    Having      []ConditionSpec\n    HavingAgg   []HavingAggSpec\n    Limit       *int\n    LimitParam  string            // Parameterized LIMIT\n    Offset      *int\n    OffsetParam string            // Parameterized OFFSET\n    Distinct    bool\n    DistinctOn  []string\n    ForLocking  string\n}",{"id":814,"title":149,"titles":815,"content":816,"level":40},"/v1.0.3/reference/api#selectspec",[587,807],"type SelectSpec struct {\n    Fields      []string\n    SelectExprs []SelectExprSpec  // Expression-based SELECT (functions, aggregates)\n    Where       []ConditionSpec\n    OrderBy     []OrderBySpec\n    GroupBy     []string\n    Having      []ConditionSpec\n    HavingAgg   []HavingAggSpec\n    Limit       *int\n    LimitParam  string            // Parameterized LIMIT\n    Offset      *int\n    OffsetParam string            // Parameterized OFFSET\n    Distinct    bool\n    DistinctOn  []string\n    ForLocking  string\n}",{"id":818,"title":154,"titles":819,"content":820,"level":40},"/v1.0.3/reference/api#updatespec",[587,807],"type UpdateSpec struct {\n    Set   map[string]string  // field -> param\n    Where []ConditionSpec\n}",{"id":822,"title":159,"titles":823,"content":824,"level":40},"/v1.0.3/reference/api#deletespec",[587,807],"type DeleteSpec struct {\n    Where []ConditionSpec\n}",{"id":826,"title":164,"titles":827,"content":828,"level":40},"/v1.0.3/reference/api#aggregatespec",[587,807],"type AggregateSpec struct {\n    Field string\n    Where []ConditionSpec\n}",{"id":830,"title":831,"titles":832,"content":833,"level":40},"/v1.0.3/reference/api#conditionspec","ConditionSpec",[587,807],"type ConditionSpec struct {\n    Field      string\n    Operator   string\n    Param      string\n    IsNull     bool\n    Logic      string           // \"AND\" or \"OR\" for groups\n    Group      []ConditionSpec  // Nested conditions\n    Between    bool             // Use BETWEEN with LowParam/HighParam\n    NotBetween bool             // Use NOT BETWEEN with LowParam/HighParam\n    LowParam   string           // Lower bound param for BETWEEN\n    HighParam  string           // Upper bound param for BETWEEN\n    RightField string           // For field-to-field comparisons (WHERE a.field = b.field)\n}",{"id":835,"title":836,"titles":837,"content":838,"level":674},"/v1.0.3/reference/api#helper-methods","Helper Methods",[587,807,831],"func (c ConditionSpec) IsGroup() bool           // Returns true if this is a grouped condition\nfunc (c ConditionSpec) IsBetween() bool         // Returns true if Between is set\nfunc (c ConditionSpec) IsNotBetween() bool      // Returns true if NotBetween is set\nfunc (c ConditionSpec) IsFieldComparison() bool // Returns true if RightField is set",{"id":840,"title":841,"titles":842,"content":843,"level":40},"/v1.0.3/reference/api#orderbyspec","OrderBySpec",[587,807],"type OrderBySpec struct {\n    Field     string\n    Direction string  // \"asc\" or \"desc\"\n    Nulls     string  // \"first\" or \"last\"\n    Operator  string  // For expressions (e.g., \"\u003C->\")\n    Param     string  // For expression parameters\n}",{"id":845,"title":846,"titles":847,"content":848,"level":40},"/v1.0.3/reference/api#paramspec","ParamSpec",[587,807],"type ParamSpec struct {\n    Name        string\n    Type        string\n    Required    bool\n    Default     any\n    Description string\n}",{"id":850,"title":851,"titles":852,"content":853,"level":40},"/v1.0.3/reference/api#selectexprspec","SelectExprSpec",[587,807],"Defines expression-based SELECT columns (functions, aggregates, casts). type SelectExprSpec struct {\n    Func     string          // Function name (see supported functions below)\n    Field    string          // Primary field for single-field functions\n    Fields   []string        // Multiple fields for multi-field functions (concat)\n    Params   []string        // Additional parameters (substring positions, power exponent)\n    CastType string          // Target type for cast operations\n    Filter   *ConditionSpec  // Filter clause for aggregate functions\n    Alias    string          // Required: column alias in result\n} Supported Functions: CategoryFunctionsStringupper, lower, length, trim, ltrim, rtrim, substring, replace, concatMathabs, ceil, floor, round, sqrt, powerDate/Timenow, current_date, current_time, current_timestampTypecastAggregatecount, count_star, count_distinct, sum, avg, min, maxConditionalcoalesce, nullif",{"id":855,"title":856,"titles":857,"content":858,"level":40},"/v1.0.3/reference/api#havingaggspec","HavingAggSpec",[587,807],"Defines aggregate conditions for HAVING clauses. type HavingAggSpec struct {\n    Func     string  // Aggregate function: \"count\", \"sum\", \"avg\", \"min\", \"max\"\n    Field    string  // Field to aggregate\n    Operator string  // Comparison operator\n    Param    string  // Parameter name for comparison value\n}",{"id":860,"title":861,"titles":862,"content":863,"level":40},"/v1.0.3/reference/api#compoundqueryspec","CompoundQuerySpec",[587,807],"Defines compound queries using set operations (UNION, INTERSECT, EXCEPT). type CompoundQuerySpec struct {\n    Base     QuerySpec         // The base query\n    Operands []CompoundOperand // Set operations with additional queries\n    OrderBy  []OrderBySpec     // Final ORDER BY (applies to combined result)\n    Limit    *int              // Final LIMIT\n    Offset   *int              // Final OFFSET\n}",{"id":865,"title":866,"titles":867,"content":868,"level":40},"/v1.0.3/reference/api#compoundoperand","CompoundOperand",[587,807],"Defines a set operation in a compound query. type CompoundOperand struct {\n    Operation string    // \"union\", \"union_all\", \"intersect\", \"intersect_all\", \"except\", \"except_all\"\n    Query     QuerySpec // The query to combine\n}",{"id":870,"title":871,"titles":872,"content":873,"level":19},"/v1.0.3/reference/api#aggregatefunc","AggregateFunc",[587],"type AggregateFunc string\n\nconst (\n    AggCount AggregateFunc = \"COUNT\"\n    AggSum   AggregateFunc = \"SUM\"\n    AggAvg   AggregateFunc = \"AVG\"\n    AggMin   AggregateFunc = \"MIN\"\n    AggMax   AggregateFunc = \"MAX\"\n)",{"id":875,"title":876,"titles":877,"content":878,"level":19},"/v1.0.3/reference/api#event-keys","Event Keys",[587],"var (\n    KeyTable    = capitan.NewStringKey(\"table\")\n    KeyError    = capitan.NewStringKey(\"error\")\n    KeyDuration = capitan.NewDurationKey(\"duration\")\n)",{"id":880,"title":881,"titles":882,"content":883,"level":19},"/v1.0.3/reference/api#signals","Signals",[587],"var (\n    ExecutorCreated = capitan.NewSignal(\"edamame.executor.created\", \"Executor instance created\")\n) Hook for monitoring: capitan.Hook(edamame.ExecutorCreated, func(ctx context.Context, e *capitan.Event) {\n    table, _ := edamame.KeyTable.From(e)\n    log.Printf(\"Executor created for table: %s\", table)\n})",{"id":885,"title":115,"titles":886,"content":887,"level":19},"/v1.0.3/reference/api#struct-tags",[587],"Edamame uses struct tags to understand your model: type User struct {\n    ID    int    `db:\"id\" type:\"integer\" constraints:\"primarykey\"`\n    Email string `db:\"email\" type:\"text\" constraints:\"notnull,unique\"`\n    Name  string `db:\"name\" type:\"text\"`\n    Age   *int   `db:\"age\" type:\"integer\"`\n} TagPurposeExampledbColumn namedb:\"user_id\"typeSQL typetype:\"text\", type:\"integer\"constraintsColumn constraintsconstraints:\"primarykey,notnull\" html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",[889],{"title":890,"path":891,"stem":892,"children":893,"page":907},"V103","/v1.0.3","v1.0.3",[894,896,908,917,924],{"title":6,"path":5,"stem":895,"description":8},"v1.0.3/1.overview",{"title":897,"path":898,"stem":899,"children":900,"page":907},"Learn","/v1.0.3/learn","v1.0.3/2.learn",[901,903,905],{"title":58,"path":57,"stem":902,"description":60},"v1.0.3/2.learn/1.quickstart",{"title":101,"path":100,"stem":904,"description":103},"v1.0.3/2.learn/2.concepts",{"title":16,"path":218,"stem":906,"description":220},"v1.0.3/2.learn/3.architecture",false,{"title":909,"path":910,"stem":911,"children":912,"page":907},"Guides","/v1.0.3/guides","v1.0.3/3.guides",[913,915],{"title":120,"path":274,"stem":914,"description":276},"v1.0.3/3.guides/1.statements",{"title":456,"path":455,"stem":916,"description":458},"v1.0.3/3.guides/2.testing",{"title":918,"path":919,"stem":920,"children":921,"page":907},"Cookbook","/v1.0.3/cookbook","v1.0.3/4.cookbook",[922],{"title":519,"path":518,"stem":923,"description":521},"v1.0.3/4.cookbook/1.llm-integration",{"title":925,"path":926,"stem":927,"children":928,"page":907},"Reference","/v1.0.3/reference","v1.0.3/5.reference",[929],{"title":587,"path":586,"stem":930,"description":589},"v1.0.3/5.reference/1.api",[932],{"title":890,"path":891,"stem":892,"children":933,"page":907},[934,935,940,944,947],{"title":6,"path":5,"stem":895},{"title":897,"path":898,"stem":899,"children":936,"page":907},[937,938,939],{"title":58,"path":57,"stem":902},{"title":101,"path":100,"stem":904},{"title":16,"path":218,"stem":906},{"title":909,"path":910,"stem":911,"children":941,"page":907},[942,943],{"title":120,"path":274,"stem":914},{"title":456,"path":455,"stem":916},{"title":918,"path":919,"stem":920,"children":945,"page":907},[946],{"title":519,"path":518,"stem":923},{"title":925,"path":926,"stem":927,"children":948,"page":907},[949],{"title":587,"path":586,"stem":930},[951,2753,3194],{"id":952,"title":953,"body":954,"description":34,"extension":2746,"icon":2747,"meta":2748,"navigation":1117,"path":2749,"seo":2750,"stem":2751,"__hash__":2752},"resources/readme.md","README",{"type":955,"value":956,"toc":2731},"minimark",[957,961,1029,1032,1035,1040,1043,1488,1491,1495,1512,1520,1524,2344,2348,2444,2448,2491,2495,2501,2504,2650,2653,2657,2665,2669,2683,2686,2697,2700,2706,2710,2718,2721,2727],[958,959,960],"h1",{"id":960},"edamame",[962,963,964,975,983,991,999,1007,1014,1021],"p",{},[965,966,970],"a",{"href":967,"rel":968},"https://github.com/zoobz-io/edamame/actions/workflows/ci.yml",[969],"nofollow",[971,972],"img",{"alt":973,"src":974},"CI Status","https://github.com/zoobz-io/edamame/workflows/CI/badge.svg",[965,976,979],{"href":977,"rel":978},"https://codecov.io/gh/zoobz-io/edamame",[969],[971,980],{"alt":981,"src":982},"codecov","https://codecov.io/gh/zoobz-io/edamame/graph/badge.svg?branch=main",[965,984,987],{"href":985,"rel":986},"https://goreportcard.com/report/github.com/zoobz-io/edamame",[969],[971,988],{"alt":989,"src":990},"Go Report Card","https://goreportcard.com/badge/github.com/zoobz-io/edamame",[965,992,995],{"href":993,"rel":994},"https://github.com/zoobz-io/edamame/security/code-scanning",[969],[971,996],{"alt":997,"src":998},"CodeQL","https://github.com/zoobz-io/edamame/workflows/CodeQL/badge.svg",[965,1000,1003],{"href":1001,"rel":1002},"https://pkg.go.dev/github.com/zoobz-io/edamame",[969],[971,1004],{"alt":1005,"src":1006},"Go Reference","https://pkg.go.dev/badge/github.com/zoobz-io/edamame.svg",[965,1008,1010],{"href":1009},"LICENSE",[971,1011],{"alt":1012,"src":1013},"License","https://img.shields.io/github/license/zoobz-io/edamame",[965,1015,1017],{"href":1016},"go.mod",[971,1018],{"alt":1019,"src":1020},"Go Version","https://img.shields.io/github/go-mod/go-version/zoobz-io/edamame",[965,1022,1025],{"href":1023,"rel":1024},"https://github.com/zoobz-io/edamame/releases",[969],[971,1026],{"alt":1027,"src":1028},"Release","https://img.shields.io/github/v/release/zoobz-io/edamame",[962,1030,1031],{},"Statement-driven query exec for Go.",[962,1033,1034],{},"Define database queries as typed statements, execute them without magic strings.",[1036,1037,1039],"h2",{"id":1038},"queries-as-data","Queries as Data",[962,1041,1042],{},"Edamame treats queries as specs—pure data structures wrapped in typed statements.",[1044,1045,1049],"pre",{"className":1046,"code":1047,"language":1048,"meta":34,"style":34},"language-go shiki shiki-themes","// Define statements as package-level variables\nvar (\n    QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all users\", edamame.QuerySpec{})\n\n    ByStatus = edamame.NewQueryStatement(\"by-status\", \"Query users by status\", edamame.QuerySpec{\n        Where:   []edamame.ConditionSpec{{Field: \"status\", Operator: \"=\", Param: \"status\"}},\n        OrderBy: []edamame.OrderBySpec{{Field: \"created_at\", Direction: \"desc\"}},\n        Limit:   ptr(50),\n    })\n\n    SelectByID = edamame.NewSelectStatement(\"select-by-id\", \"Select user by ID\", edamame.SelectSpec{\n        Where: []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    })\n)\n\n// Execute with type safety\nusers, _ := exec.ExecQuery(ctx, ByStatus, map[string]any{\"status\": \"active\"})\nuser, _ := exec.ExecSelect(ctx, SelectByID, map[string]any{\"id\": 123})\n","go",[1050,1051,1052,1060,1070,1113,1119,1154,1206,1244,1264,1270,1275,1309,1351,1356,1362,1367,1373,1436],"code",{"__ignoreMap":34},[1053,1054,1056],"span",{"class":1055,"line":9},"line",[1053,1057,1059],{"class":1058},"sLkEo","// Define statements as package-level variables\n",[1053,1061,1062,1066],{"class":1055,"line":19},[1053,1063,1065],{"class":1064},"sUt3r","var",[1053,1067,1069],{"class":1068},"sq5bi"," (\n",[1053,1071,1072,1076,1079,1082,1085,1088,1091,1095,1098,1101,1103,1106,1108,1110],{"class":1055,"line":40},[1053,1073,1075],{"class":1074},"sh8_p","    QueryAll",[1053,1077,1078],{"class":1074}," =",[1053,1080,1081],{"class":1074}," edamame",[1053,1083,1084],{"class":1068},".",[1053,1086,609],{"class":1087},"s5klm",[1053,1089,1090],{"class":1068},"(",[1053,1092,1094],{"class":1093},"sxAnc","\"query-all\"",[1053,1096,1097],{"class":1068},",",[1053,1099,1100],{"class":1093}," \"Query all users\"",[1053,1102,1097],{"class":1068},[1053,1104,1081],{"class":1105},"sYBwO",[1053,1107,1084],{"class":1068},[1053,1109,144],{"class":1105},[1053,1111,1112],{"class":1068},"{})\n",[1053,1114,1115],{"class":1055,"line":674},[1053,1116,1118],{"emptyLinePlaceholder":1117},true,"\n",[1053,1120,1122,1125,1127,1129,1131,1133,1135,1138,1140,1143,1145,1147,1149,1151],{"class":1055,"line":1121},5,[1053,1123,1124],{"class":1074},"    ByStatus",[1053,1126,1078],{"class":1074},[1053,1128,1081],{"class":1074},[1053,1130,1084],{"class":1068},[1053,1132,609],{"class":1087},[1053,1134,1090],{"class":1068},[1053,1136,1137],{"class":1093},"\"by-status\"",[1053,1139,1097],{"class":1068},[1053,1141,1142],{"class":1093}," \"Query users by status\"",[1053,1144,1097],{"class":1068},[1053,1146,1081],{"class":1105},[1053,1148,1084],{"class":1068},[1053,1150,144],{"class":1105},[1053,1152,1153],{"class":1068},"{\n",[1053,1155,1157,1161,1164,1167,1169,1171,1173,1176,1179,1181,1184,1186,1189,1191,1194,1196,1199,1201,1203],{"class":1055,"line":1156},6,[1053,1158,1160],{"class":1159},"sBGCq","        Where",[1053,1162,1163],{"class":1068},":",[1053,1165,1166],{"class":1068},"   []",[1053,1168,960],{"class":1105},[1053,1170,1084],{"class":1068},[1053,1172,831],{"class":1105},[1053,1174,1175],{"class":1068},"{{",[1053,1177,1178],{"class":1159},"Field",[1053,1180,1163],{"class":1068},[1053,1182,1183],{"class":1093}," \"status\"",[1053,1185,1097],{"class":1068},[1053,1187,1188],{"class":1159}," Operator",[1053,1190,1163],{"class":1068},[1053,1192,1193],{"class":1093}," \"=\"",[1053,1195,1097],{"class":1068},[1053,1197,1198],{"class":1159}," Param",[1053,1200,1163],{"class":1068},[1053,1202,1183],{"class":1093},[1053,1204,1205],{"class":1068},"}},\n",[1053,1207,1209,1212,1214,1217,1219,1221,1223,1225,1227,1229,1232,1234,1237,1239,1242],{"class":1055,"line":1208},7,[1053,1210,1211],{"class":1159},"        OrderBy",[1053,1213,1163],{"class":1068},[1053,1215,1216],{"class":1068}," []",[1053,1218,960],{"class":1105},[1053,1220,1084],{"class":1068},[1053,1222,841],{"class":1105},[1053,1224,1175],{"class":1068},[1053,1226,1178],{"class":1159},[1053,1228,1163],{"class":1068},[1053,1230,1231],{"class":1093}," \"created_at\"",[1053,1233,1097],{"class":1068},[1053,1235,1236],{"class":1159}," Direction",[1053,1238,1163],{"class":1068},[1053,1240,1241],{"class":1093}," \"desc\"",[1053,1243,1205],{"class":1068},[1053,1245,1247,1250,1252,1255,1257,1261],{"class":1055,"line":1246},8,[1053,1248,1249],{"class":1159},"        Limit",[1053,1251,1163],{"class":1068},[1053,1253,1254],{"class":1087},"   ptr",[1053,1256,1090],{"class":1068},[1053,1258,1260],{"class":1259},"sMAmT","50",[1053,1262,1263],{"class":1068},"),\n",[1053,1265,1267],{"class":1055,"line":1266},9,[1053,1268,1269],{"class":1068},"    })\n",[1053,1271,1273],{"class":1055,"line":1272},10,[1053,1274,1118],{"emptyLinePlaceholder":1117},[1053,1276,1278,1281,1283,1285,1287,1289,1291,1294,1296,1299,1301,1303,1305,1307],{"class":1055,"line":1277},11,[1053,1279,1280],{"class":1074},"    SelectByID",[1053,1282,1078],{"class":1074},[1053,1284,1081],{"class":1074},[1053,1286,1084],{"class":1068},[1053,1288,614],{"class":1087},[1053,1290,1090],{"class":1068},[1053,1292,1293],{"class":1093},"\"select-by-id\"",[1053,1295,1097],{"class":1068},[1053,1297,1298],{"class":1093}," \"Select user by ID\"",[1053,1300,1097],{"class":1068},[1053,1302,1081],{"class":1105},[1053,1304,1084],{"class":1068},[1053,1306,149],{"class":1105},[1053,1308,1153],{"class":1068},[1053,1310,1312,1314,1316,1318,1320,1322,1324,1326,1328,1330,1333,1335,1337,1339,1341,1343,1345,1347,1349],{"class":1055,"line":1311},12,[1053,1313,1160],{"class":1159},[1053,1315,1163],{"class":1068},[1053,1317,1216],{"class":1068},[1053,1319,960],{"class":1105},[1053,1321,1084],{"class":1068},[1053,1323,831],{"class":1105},[1053,1325,1175],{"class":1068},[1053,1327,1178],{"class":1159},[1053,1329,1163],{"class":1068},[1053,1331,1332],{"class":1093}," \"id\"",[1053,1334,1097],{"class":1068},[1053,1336,1188],{"class":1159},[1053,1338,1163],{"class":1068},[1053,1340,1193],{"class":1093},[1053,1342,1097],{"class":1068},[1053,1344,1198],{"class":1159},[1053,1346,1163],{"class":1068},[1053,1348,1332],{"class":1093},[1053,1350,1205],{"class":1068},[1053,1352,1354],{"class":1055,"line":1353},13,[1053,1355,1269],{"class":1068},[1053,1357,1359],{"class":1055,"line":1358},14,[1053,1360,1361],{"class":1068},")\n",[1053,1363,1365],{"class":1055,"line":1364},15,[1053,1366,1118],{"emptyLinePlaceholder":1117},[1053,1368,1370],{"class":1055,"line":1369},16,[1053,1371,1372],{"class":1058},"// Execute with type safety\n",[1053,1374,1376,1379,1381,1384,1387,1390,1392,1395,1397,1400,1402,1405,1407,1410,1413,1416,1419,1422,1425,1428,1430,1433],{"class":1055,"line":1375},17,[1053,1377,1378],{"class":1074},"users",[1053,1380,1097],{"class":1068},[1053,1382,1383],{"class":1074}," _",[1053,1385,1386],{"class":1074}," :=",[1053,1388,1389],{"class":1074}," exec",[1053,1391,1084],{"class":1068},[1053,1393,1394],{"class":1087},"ExecQuery",[1053,1396,1090],{"class":1068},[1053,1398,1399],{"class":1074},"ctx",[1053,1401,1097],{"class":1068},[1053,1403,1404],{"class":1074}," ByStatus",[1053,1406,1097],{"class":1068},[1053,1408,1409],{"class":1064}," map",[1053,1411,1412],{"class":1068},"[",[1053,1414,1415],{"class":1105},"string",[1053,1417,1418],{"class":1068},"]",[1053,1420,1421],{"class":1105},"any",[1053,1423,1424],{"class":1068},"{",[1053,1426,1427],{"class":1093},"\"status\"",[1053,1429,1163],{"class":1068},[1053,1431,1432],{"class":1093}," \"active\"",[1053,1434,1435],{"class":1068},"})\n",[1053,1437,1439,1442,1444,1446,1448,1450,1452,1455,1457,1459,1461,1464,1466,1468,1470,1472,1474,1476,1478,1481,1483,1486],{"class":1055,"line":1438},18,[1053,1440,1441],{"class":1074},"user",[1053,1443,1097],{"class":1068},[1053,1445,1383],{"class":1074},[1053,1447,1386],{"class":1074},[1053,1449,1389],{"class":1074},[1053,1451,1084],{"class":1068},[1053,1453,1454],{"class":1087},"ExecSelect",[1053,1456,1090],{"class":1068},[1053,1458,1399],{"class":1074},[1053,1460,1097],{"class":1068},[1053,1462,1463],{"class":1074}," SelectByID",[1053,1465,1097],{"class":1068},[1053,1467,1409],{"class":1064},[1053,1469,1412],{"class":1068},[1053,1471,1415],{"class":1105},[1053,1473,1418],{"class":1068},[1053,1475,1421],{"class":1105},[1053,1477,1424],{"class":1068},[1053,1479,1480],{"class":1093},"\"id\"",[1053,1482,1163],{"class":1068},[1053,1484,1485],{"class":1259}," 123",[1053,1487,1435],{"class":1068},[962,1489,1490],{},"Type-safe. No magic strings. Compile-time guarantees.",[1036,1492,1494],{"id":1493},"install","Install",[1044,1496,1500],{"className":1497,"code":1498,"language":1499,"meta":34,"style":34},"language-bash shiki shiki-themes","go get github.com/zoobz-io/edamame\n","bash",[1050,1501,1502],{"__ignoreMap":34},[1053,1503,1504,1506,1509],{"class":1055,"line":9},[1053,1505,1048],{"class":1087},[1053,1507,1508],{"class":1093}," get",[1053,1510,1511],{"class":1093}," github.com/zoobz-io/edamame\n",[962,1513,1514,1515,1084],{},"Requires Go 1.24+. Supports PostgreSQL, MariaDB, SQLite, and SQL Server via ",[965,1516,1519],{"href":1517,"rel":1518},"https://github.com/zoobz-io/astql",[969],"astql",[1036,1521,1523],{"id":1522},"quick-start","Quick Start",[1044,1525,1527],{"className":1046,"code":1526,"language":1048,"meta":34,"style":34},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n\n    \"github.com/jmoiron/sqlx\"\n    _ \"github.com/lib/pq\" // or mariadb, sqlite3, mssql driver\n    \"github.com/zoobz-io/astql/pkg/postgres\" // or mariadb, sqlite, mssql\n    \"github.com/zoobz-io/edamame\"\n)\n\ntype User struct {\n    ID     int    `db:\"id\" type:\"integer\" constraints:\"primarykey\"`\n    Email  string `db:\"email\" type:\"text\" constraints:\"notnull,unique\"`\n    Name   string `db:\"name\" type:\"text\"`\n    Status string `db:\"status\" type:\"text\"`\n}\n\n// Define statements\nvar (\n    QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all users\", edamame.QuerySpec{})\n\n    SelectByID = edamame.NewSelectStatement(\"select-by-id\", \"Select user by ID\", edamame.SelectSpec{\n        Where: []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    })\n\n    CountAll = edamame.NewAggregateStatement(\"count-all\", \"Count all users\", edamame.AggCount, edamame.AggregateSpec{})\n\n    ActiveUsers = edamame.NewQueryStatement(\"active\", \"Query active users\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"status\", Operator: \"=\", Param: \"status\"},\n        },\n    })\n)\n\nfunc main() {\n    db, _ := sqlx.Connect(\"postgres\", \"postgres://localhost/mydb?sslmode=disable\")\n    ctx := context.Background()\n\n    // Create exec\n    exec, _ := edamame.New[User](db, \"users\", postgres.New())\n\n    // Execute statements\n    users, _ := exec.ExecQuery(ctx, QueryAll, nil)\n    user, _ := exec.ExecSelect(ctx, SelectByID, map[string]any{\"id\": 1})\n    count, _ := exec.ExecAggregate(ctx, CountAll, nil)\n    active, _ := exec.ExecQuery(ctx, ActiveUsers, map[string]any{\"status\": \"active\"})\n\n    fmt.Printf(\"%d users, user #1: %s, %.0f total, %d active\\n\", len(users), user.Name, count, len(active))\n}\n",[1050,1528,1529,1537,1541,1549,1554,1559,1563,1568,1580,1588,1593,1597,1601,1615,1626,1637,1648,1659,1664,1669,1675,1682,1713,1718,1749,1790,1795,1800,1843,1848,1882,1899,1930,1936,1941,1946,1951,1965,1997,2016,2021,2027,2073,2078,2084,2118,2167,2201,2250,2255,2339],{"__ignoreMap":34},[1053,1530,1531,1534],{"class":1055,"line":9},[1053,1532,1533],{"class":1064},"package",[1053,1535,1536],{"class":1105}," main\n",[1053,1538,1539],{"class":1055,"line":19},[1053,1540,1118],{"emptyLinePlaceholder":1117},[1053,1542,1543,1546],{"class":1055,"line":40},[1053,1544,1545],{"class":1064},"import",[1053,1547,1069],{"class":1548},"soy-K",[1053,1550,1551],{"class":1055,"line":674},[1053,1552,1553],{"class":1093},"    \"context\"\n",[1053,1555,1556],{"class":1055,"line":1121},[1053,1557,1558],{"class":1093},"    \"fmt\"\n",[1053,1560,1561],{"class":1055,"line":1156},[1053,1562,1118],{"emptyLinePlaceholder":1117},[1053,1564,1565],{"class":1055,"line":1208},[1053,1566,1567],{"class":1093},"    \"github.com/jmoiron/sqlx\"\n",[1053,1569,1570,1574,1577],{"class":1055,"line":1246},[1053,1571,1573],{"class":1572},"sSYET","    _",[1053,1575,1576],{"class":1093}," \"github.com/lib/pq\"",[1053,1578,1579],{"class":1058}," // or mariadb, sqlite3, mssql driver\n",[1053,1581,1582,1585],{"class":1055,"line":1266},[1053,1583,1584],{"class":1093},"    \"github.com/zoobz-io/astql/pkg/postgres\"",[1053,1586,1587],{"class":1058}," // or mariadb, sqlite, mssql\n",[1053,1589,1590],{"class":1055,"line":1272},[1053,1591,1592],{"class":1093},"    \"github.com/zoobz-io/edamame\"\n",[1053,1594,1595],{"class":1055,"line":1277},[1053,1596,1361],{"class":1548},[1053,1598,1599],{"class":1055,"line":1311},[1053,1600,1118],{"emptyLinePlaceholder":1117},[1053,1602,1603,1606,1609,1612],{"class":1055,"line":1353},[1053,1604,1605],{"class":1064},"type",[1053,1607,1608],{"class":1105}," User",[1053,1610,1611],{"class":1064}," struct",[1053,1613,1614],{"class":1068}," {\n",[1053,1616,1617,1620,1623],{"class":1055,"line":1358},[1053,1618,1619],{"class":1159},"    ID",[1053,1621,1622],{"class":1105},"     int",[1053,1624,1625],{"class":1093},"    `db:\"id\" type:\"integer\" constraints:\"primarykey\"`\n",[1053,1627,1628,1631,1634],{"class":1055,"line":1364},[1053,1629,1630],{"class":1159},"    Email",[1053,1632,1633],{"class":1105},"  string",[1053,1635,1636],{"class":1093}," `db:\"email\" type:\"text\" constraints:\"notnull,unique\"`\n",[1053,1638,1639,1642,1645],{"class":1055,"line":1369},[1053,1640,1641],{"class":1159},"    Name",[1053,1643,1644],{"class":1105},"   string",[1053,1646,1647],{"class":1093}," `db:\"name\" type:\"text\"`\n",[1053,1649,1650,1653,1656],{"class":1055,"line":1375},[1053,1651,1652],{"class":1159},"    Status",[1053,1654,1655],{"class":1105}," string",[1053,1657,1658],{"class":1093}," `db:\"status\" type:\"text\"`\n",[1053,1660,1661],{"class":1055,"line":1438},[1053,1662,1663],{"class":1068},"}\n",[1053,1665,1667],{"class":1055,"line":1666},19,[1053,1668,1118],{"emptyLinePlaceholder":1117},[1053,1670,1672],{"class":1055,"line":1671},20,[1053,1673,1674],{"class":1058},"// Define statements\n",[1053,1676,1678,1680],{"class":1055,"line":1677},21,[1053,1679,1065],{"class":1064},[1053,1681,1069],{"class":1068},[1053,1683,1685,1687,1689,1691,1693,1695,1697,1699,1701,1703,1705,1707,1709,1711],{"class":1055,"line":1684},22,[1053,1686,1075],{"class":1074},[1053,1688,1078],{"class":1074},[1053,1690,1081],{"class":1074},[1053,1692,1084],{"class":1068},[1053,1694,609],{"class":1087},[1053,1696,1090],{"class":1068},[1053,1698,1094],{"class":1093},[1053,1700,1097],{"class":1068},[1053,1702,1100],{"class":1093},[1053,1704,1097],{"class":1068},[1053,1706,1081],{"class":1105},[1053,1708,1084],{"class":1068},[1053,1710,144],{"class":1105},[1053,1712,1112],{"class":1068},[1053,1714,1716],{"class":1055,"line":1715},23,[1053,1717,1118],{"emptyLinePlaceholder":1117},[1053,1719,1721,1723,1725,1727,1729,1731,1733,1735,1737,1739,1741,1743,1745,1747],{"class":1055,"line":1720},24,[1053,1722,1280],{"class":1074},[1053,1724,1078],{"class":1074},[1053,1726,1081],{"class":1074},[1053,1728,1084],{"class":1068},[1053,1730,614],{"class":1087},[1053,1732,1090],{"class":1068},[1053,1734,1293],{"class":1093},[1053,1736,1097],{"class":1068},[1053,1738,1298],{"class":1093},[1053,1740,1097],{"class":1068},[1053,1742,1081],{"class":1105},[1053,1744,1084],{"class":1068},[1053,1746,149],{"class":1105},[1053,1748,1153],{"class":1068},[1053,1750,1752,1754,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788],{"class":1055,"line":1751},25,[1053,1753,1160],{"class":1159},[1053,1755,1163],{"class":1068},[1053,1757,1216],{"class":1068},[1053,1759,960],{"class":1105},[1053,1761,1084],{"class":1068},[1053,1763,831],{"class":1105},[1053,1765,1175],{"class":1068},[1053,1767,1178],{"class":1159},[1053,1769,1163],{"class":1068},[1053,1771,1332],{"class":1093},[1053,1773,1097],{"class":1068},[1053,1775,1188],{"class":1159},[1053,1777,1163],{"class":1068},[1053,1779,1193],{"class":1093},[1053,1781,1097],{"class":1068},[1053,1783,1198],{"class":1159},[1053,1785,1163],{"class":1068},[1053,1787,1332],{"class":1093},[1053,1789,1205],{"class":1068},[1053,1791,1793],{"class":1055,"line":1792},26,[1053,1794,1269],{"class":1068},[1053,1796,1798],{"class":1055,"line":1797},27,[1053,1799,1118],{"emptyLinePlaceholder":1117},[1053,1801,1803,1806,1808,1810,1812,1814,1816,1819,1821,1824,1826,1828,1830,1833,1835,1837,1839,1841],{"class":1055,"line":1802},28,[1053,1804,1805],{"class":1074},"    CountAll",[1053,1807,1078],{"class":1074},[1053,1809,1081],{"class":1074},[1053,1811,1084],{"class":1068},[1053,1813,629],{"class":1087},[1053,1815,1090],{"class":1068},[1053,1817,1818],{"class":1093},"\"count-all\"",[1053,1820,1097],{"class":1068},[1053,1822,1823],{"class":1093}," \"Count all users\"",[1053,1825,1097],{"class":1068},[1053,1827,1081],{"class":1074},[1053,1829,1084],{"class":1068},[1053,1831,1832],{"class":1074},"AggCount",[1053,1834,1097],{"class":1068},[1053,1836,1081],{"class":1105},[1053,1838,1084],{"class":1068},[1053,1840,164],{"class":1105},[1053,1842,1112],{"class":1068},[1053,1844,1846],{"class":1055,"line":1845},29,[1053,1847,1118],{"emptyLinePlaceholder":1117},[1053,1849,1851,1854,1856,1858,1860,1862,1864,1867,1869,1872,1874,1876,1878,1880],{"class":1055,"line":1850},30,[1053,1852,1853],{"class":1074},"    ActiveUsers",[1053,1855,1078],{"class":1074},[1053,1857,1081],{"class":1074},[1053,1859,1084],{"class":1068},[1053,1861,609],{"class":1087},[1053,1863,1090],{"class":1068},[1053,1865,1866],{"class":1093},"\"active\"",[1053,1868,1097],{"class":1068},[1053,1870,1871],{"class":1093}," \"Query active users\"",[1053,1873,1097],{"class":1068},[1053,1875,1081],{"class":1105},[1053,1877,1084],{"class":1068},[1053,1879,144],{"class":1105},[1053,1881,1153],{"class":1068},[1053,1883,1885,1887,1889,1891,1893,1895,1897],{"class":1055,"line":1884},31,[1053,1886,1160],{"class":1159},[1053,1888,1163],{"class":1068},[1053,1890,1216],{"class":1068},[1053,1892,960],{"class":1105},[1053,1894,1084],{"class":1068},[1053,1896,831],{"class":1105},[1053,1898,1153],{"class":1068},[1053,1900,1902,1905,1907,1909,1911,1913,1915,1917,1919,1921,1923,1925,1927],{"class":1055,"line":1901},32,[1053,1903,1904],{"class":1068},"            {",[1053,1906,1178],{"class":1159},[1053,1908,1163],{"class":1068},[1053,1910,1183],{"class":1093},[1053,1912,1097],{"class":1068},[1053,1914,1188],{"class":1159},[1053,1916,1163],{"class":1068},[1053,1918,1193],{"class":1093},[1053,1920,1097],{"class":1068},[1053,1922,1198],{"class":1159},[1053,1924,1163],{"class":1068},[1053,1926,1183],{"class":1093},[1053,1928,1929],{"class":1068},"},\n",[1053,1931,1933],{"class":1055,"line":1932},33,[1053,1934,1935],{"class":1068},"        },\n",[1053,1937,1939],{"class":1055,"line":1938},34,[1053,1940,1269],{"class":1068},[1053,1942,1944],{"class":1055,"line":1943},35,[1053,1945,1361],{"class":1068},[1053,1947,1949],{"class":1055,"line":1948},36,[1053,1950,1118],{"emptyLinePlaceholder":1117},[1053,1952,1954,1957,1960,1963],{"class":1055,"line":1953},37,[1053,1955,1956],{"class":1064},"func",[1053,1958,1959],{"class":1087}," main",[1053,1961,1962],{"class":1068},"()",[1053,1964,1614],{"class":1068},[1053,1966,1968,1971,1973,1975,1977,1980,1982,1985,1987,1990,1992,1995],{"class":1055,"line":1967},38,[1053,1969,1970],{"class":1074},"    db",[1053,1972,1097],{"class":1068},[1053,1974,1383],{"class":1074},[1053,1976,1386],{"class":1074},[1053,1978,1979],{"class":1074}," sqlx",[1053,1981,1084],{"class":1068},[1053,1983,1984],{"class":1087},"Connect",[1053,1986,1090],{"class":1068},[1053,1988,1989],{"class":1093},"\"postgres\"",[1053,1991,1097],{"class":1068},[1053,1993,1994],{"class":1093}," \"postgres://localhost/mydb?sslmode=disable\"",[1053,1996,1361],{"class":1068},[1053,1998,2000,2003,2005,2008,2010,2013],{"class":1055,"line":1999},39,[1053,2001,2002],{"class":1074},"    ctx",[1053,2004,1386],{"class":1074},[1053,2006,2007],{"class":1074}," context",[1053,2009,1084],{"class":1068},[1053,2011,2012],{"class":1087},"Background",[1053,2014,2015],{"class":1068},"()\n",[1053,2017,2019],{"class":1055,"line":2018},40,[1053,2020,1118],{"emptyLinePlaceholder":1117},[1053,2022,2024],{"class":1055,"line":2023},41,[1053,2025,2026],{"class":1058},"    // Create exec\n",[1053,2028,2030,2033,2035,2037,2039,2041,2043,2045,2047,2050,2053,2056,2058,2061,2063,2066,2068,2070],{"class":1055,"line":2029},42,[1053,2031,2032],{"class":1074},"    exec",[1053,2034,1097],{"class":1068},[1053,2036,1383],{"class":1074},[1053,2038,1386],{"class":1074},[1053,2040,1081],{"class":1074},[1053,2042,1084],{"class":1068},[1053,2044,600],{"class":1087},[1053,2046,1412],{"class":1068},[1053,2048,2049],{"class":1105},"User",[1053,2051,2052],{"class":1068},"](",[1053,2054,2055],{"class":1074},"db",[1053,2057,1097],{"class":1068},[1053,2059,2060],{"class":1093}," \"users\"",[1053,2062,1097],{"class":1068},[1053,2064,2065],{"class":1074}," postgres",[1053,2067,1084],{"class":1068},[1053,2069,600],{"class":1087},[1053,2071,2072],{"class":1068},"())\n",[1053,2074,2076],{"class":1055,"line":2075},43,[1053,2077,1118],{"emptyLinePlaceholder":1117},[1053,2079,2081],{"class":1055,"line":2080},44,[1053,2082,2083],{"class":1058},"    // Execute statements\n",[1053,2085,2087,2090,2092,2094,2096,2098,2100,2102,2104,2106,2108,2111,2113,2116],{"class":1055,"line":2086},45,[1053,2088,2089],{"class":1074},"    users",[1053,2091,1097],{"class":1068},[1053,2093,1383],{"class":1074},[1053,2095,1386],{"class":1074},[1053,2097,1389],{"class":1074},[1053,2099,1084],{"class":1068},[1053,2101,1394],{"class":1087},[1053,2103,1090],{"class":1068},[1053,2105,1399],{"class":1074},[1053,2107,1097],{"class":1068},[1053,2109,2110],{"class":1074}," QueryAll",[1053,2112,1097],{"class":1068},[1053,2114,2115],{"class":1064}," nil",[1053,2117,1361],{"class":1068},[1053,2119,2121,2124,2126,2128,2130,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150,2152,2154,2156,2158,2160,2162,2165],{"class":1055,"line":2120},46,[1053,2122,2123],{"class":1074},"    user",[1053,2125,1097],{"class":1068},[1053,2127,1383],{"class":1074},[1053,2129,1386],{"class":1074},[1053,2131,1389],{"class":1074},[1053,2133,1084],{"class":1068},[1053,2135,1454],{"class":1087},[1053,2137,1090],{"class":1068},[1053,2139,1399],{"class":1074},[1053,2141,1097],{"class":1068},[1053,2143,1463],{"class":1074},[1053,2145,1097],{"class":1068},[1053,2147,1409],{"class":1064},[1053,2149,1412],{"class":1068},[1053,2151,1415],{"class":1105},[1053,2153,1418],{"class":1068},[1053,2155,1421],{"class":1105},[1053,2157,1424],{"class":1068},[1053,2159,1480],{"class":1093},[1053,2161,1163],{"class":1068},[1053,2163,2164],{"class":1259}," 1",[1053,2166,1435],{"class":1068},[1053,2168,2170,2173,2175,2177,2179,2181,2183,2186,2188,2190,2192,2195,2197,2199],{"class":1055,"line":2169},47,[1053,2171,2172],{"class":1074},"    count",[1053,2174,1097],{"class":1068},[1053,2176,1383],{"class":1074},[1053,2178,1386],{"class":1074},[1053,2180,1389],{"class":1074},[1053,2182,1084],{"class":1068},[1053,2184,2185],{"class":1087},"ExecAggregate",[1053,2187,1090],{"class":1068},[1053,2189,1399],{"class":1074},[1053,2191,1097],{"class":1068},[1053,2193,2194],{"class":1074}," CountAll",[1053,2196,1097],{"class":1068},[1053,2198,2115],{"class":1064},[1053,2200,1361],{"class":1068},[1053,2202,2204,2207,2209,2211,2213,2215,2217,2219,2221,2223,2225,2228,2230,2232,2234,2236,2238,2240,2242,2244,2246,2248],{"class":1055,"line":2203},48,[1053,2205,2206],{"class":1074},"    active",[1053,2208,1097],{"class":1068},[1053,2210,1383],{"class":1074},[1053,2212,1386],{"class":1074},[1053,2214,1389],{"class":1074},[1053,2216,1084],{"class":1068},[1053,2218,1394],{"class":1087},[1053,2220,1090],{"class":1068},[1053,2222,1399],{"class":1074},[1053,2224,1097],{"class":1068},[1053,2226,2227],{"class":1074}," ActiveUsers",[1053,2229,1097],{"class":1068},[1053,2231,1409],{"class":1064},[1053,2233,1412],{"class":1068},[1053,2235,1415],{"class":1105},[1053,2237,1418],{"class":1068},[1053,2239,1421],{"class":1105},[1053,2241,1424],{"class":1068},[1053,2243,1427],{"class":1093},[1053,2245,1163],{"class":1068},[1053,2247,1432],{"class":1093},[1053,2249,1435],{"class":1068},[1053,2251,2253],{"class":1055,"line":2252},49,[1053,2254,1118],{"emptyLinePlaceholder":1117},[1053,2256,2258,2261,2263,2266,2268,2271,2275,2278,2281,2284,2287,2290,2292,2295,2299,2301,2303,2307,2309,2311,2314,2317,2319,2322,2324,2327,2329,2331,2333,2336],{"class":1055,"line":2257},50,[1053,2259,2260],{"class":1074},"    fmt",[1053,2262,1084],{"class":1068},[1053,2264,2265],{"class":1087},"Printf",[1053,2267,1090],{"class":1068},[1053,2269,2270],{"class":1093},"\"",[1053,2272,2274],{"class":2273},"scyPU","%d",[1053,2276,2277],{"class":1093}," users, user #1: ",[1053,2279,2280],{"class":2273},"%s",[1053,2282,2283],{"class":1093},", ",[1053,2285,2286],{"class":2273},"%.0f",[1053,2288,2289],{"class":1093}," total, ",[1053,2291,2274],{"class":2273},[1053,2293,2294],{"class":1093}," active",[1053,2296,2298],{"class":2297},"suWN2","\\n",[1053,2300,2270],{"class":1093},[1053,2302,1097],{"class":1068},[1053,2304,2306],{"class":2305},"skxcq"," len",[1053,2308,1090],{"class":1068},[1053,2310,1378],{"class":1074},[1053,2312,2313],{"class":1068},"),",[1053,2315,2316],{"class":1074}," user",[1053,2318,1084],{"class":1068},[1053,2320,2321],{"class":1074},"Name",[1053,2323,1097],{"class":1068},[1053,2325,2326],{"class":1074}," count",[1053,2328,1097],{"class":1068},[1053,2330,2306],{"class":2305},[1053,2332,1090],{"class":1068},[1053,2334,2335],{"class":1074},"active",[1053,2337,2338],{"class":1068},"))\n",[1053,2340,2342],{"class":1055,"line":2341},51,[1053,2343,1663],{"class":1068},[1036,2345,2347],{"id":2346},"capabilities","Capabilities",[2349,2350,2351,2367],"table",{},[2352,2353,2354],"thead",{},[2355,2356,2357,2361,2364],"tr",{},[2358,2359,2360],"th",{},"Feature",[2358,2362,2363],{},"Description",[2358,2365,2366],{},"Docs",[2368,2369,2370,2383,2401,2417,2430],"tbody",{},[2355,2371,2372,2375,2378],{},[2373,2374,27],"td",{},[2373,2376,2377],{},"Query, Select, Aggregate, Insert, Update, Delete",[2373,2379,2380],{},[965,2381,120],{"href":2382},"docs/guides/statements",[2355,2384,2385,2388,2395],{},[2373,2386,2387],{},"Generic Executor",[2373,2389,2390,2391,2394],{},"Type-safe ",[1050,2392,2393],{},"Executor[T]"," with compile-time checking",[2373,2396,2397],{},[965,2398,2400],{"href":2399},"docs/learn/concepts","Concepts",[2355,2402,2403,2406,2412],{},[2373,2404,2405],{},"Multi-Dialect Support",[2373,2407,2408,2409],{},"PostgreSQL, MariaDB, SQLite, SQL Server via ",[965,2410,1519],{"href":1517,"rel":2411},[969],[2373,2413,2414],{},[965,2415,58],{"href":2416},"docs/learn/quickstart",[2355,2418,2419,2422,2425],{},[2373,2420,2421],{},"Declarative Specs",[2373,2423,2424],{},"Queries as pure data structures",[2373,2426,2427],{},[965,2428,16],{"href":2429},"docs/learn/architecture",[2355,2431,2432,2435,2438],{},[2373,2433,2434],{},"Thread-Safe Execution",[2373,2436,2437],{},"Concurrent access, no shared mutable state",[2373,2439,2440],{},[965,2441,2443],{"href":2442},"docs/reference/api","API",[1036,2445,2447],{"id":2446},"why-edamame","Why edamame?",[2449,2450,2451,2467,2473,2479,2485],"ul",{},[2452,2453,2454,2458,2459,2461,2462],"li",{},[2455,2456,2457],"strong",{},"Type-safe"," — Generic ",[1050,2460,2393],{}," with compile-time safety via ",[965,2463,2466],{"href":2464,"rel":2465},"https://github.com/zoobz-io/soy",[969],"soy",[2452,2468,2469,2472],{},[2455,2470,2471],{},"No magic strings"," — Typed statements, not string keys",[2452,2474,2475,2478],{},[2455,2476,2477],{},"Declarative"," — Specs are data, statements wrap them with identity",[2452,2480,2481,2484],{},[2455,2482,2483],{},"Compile-time guarantees"," — Pass wrong statement type? Compiler catches it",[2452,2486,2487,2490],{},[2455,2488,2489],{},"Thread-safe"," — Concurrent execution, no shared mutable state",[1036,2492,2494],{"id":2493},"structural-query-safety","Structural Query Safety",[962,2496,2497,2498,1084],{},"Edamame enables a pattern: ",[2455,2499,2500],{},"define statements once, execute them anywhere",[962,2502,2503],{},"Your query logic lives in typed statements as package-level variables. The executor consumes them with full type safety. No string interpolation, no runtime query building, no SQL injection vectors.",[1044,2505,2507],{"className":1046,"code":2506,"language":1048,"meta":34,"style":34},"// In your repository package\nvar FindActive = edamame.NewQueryStatement(\"find-active\", \"Find active users\", edamame.QuerySpec{\n    Where: []edamame.ConditionSpec{{Field: \"status\", Operator: \"=\", Param: \"status\"}},\n})\n\n// In your service layer\nusers, err := exec.ExecQuery(ctx, FindActive, map[string]any{\"status\": \"active\"})\n",[1050,2508,2509,2514,2549,2590,2594,2598,2603],{"__ignoreMap":34},[1053,2510,2511],{"class":1055,"line":9},[1053,2512,2513],{"class":1058},"// In your repository package\n",[1053,2515,2516,2518,2521,2523,2525,2527,2529,2531,2534,2536,2539,2541,2543,2545,2547],{"class":1055,"line":19},[1053,2517,1065],{"class":1064},[1053,2519,2520],{"class":1074}," FindActive",[1053,2522,1078],{"class":1074},[1053,2524,1081],{"class":1074},[1053,2526,1084],{"class":1068},[1053,2528,609],{"class":1087},[1053,2530,1090],{"class":1068},[1053,2532,2533],{"class":1093},"\"find-active\"",[1053,2535,1097],{"class":1068},[1053,2537,2538],{"class":1093}," \"Find active users\"",[1053,2540,1097],{"class":1068},[1053,2542,1081],{"class":1105},[1053,2544,1084],{"class":1068},[1053,2546,144],{"class":1105},[1053,2548,1153],{"class":1068},[1053,2550,2551,2554,2556,2558,2560,2562,2564,2566,2568,2570,2572,2574,2576,2578,2580,2582,2584,2586,2588],{"class":1055,"line":40},[1053,2552,2553],{"class":1159},"    Where",[1053,2555,1163],{"class":1068},[1053,2557,1216],{"class":1068},[1053,2559,960],{"class":1105},[1053,2561,1084],{"class":1068},[1053,2563,831],{"class":1105},[1053,2565,1175],{"class":1068},[1053,2567,1178],{"class":1159},[1053,2569,1163],{"class":1068},[1053,2571,1183],{"class":1093},[1053,2573,1097],{"class":1068},[1053,2575,1188],{"class":1159},[1053,2577,1163],{"class":1068},[1053,2579,1193],{"class":1093},[1053,2581,1097],{"class":1068},[1053,2583,1198],{"class":1159},[1053,2585,1163],{"class":1068},[1053,2587,1183],{"class":1093},[1053,2589,1205],{"class":1068},[1053,2591,2592],{"class":1055,"line":674},[1053,2593,1435],{"class":1068},[1053,2595,2596],{"class":1055,"line":1121},[1053,2597,1118],{"emptyLinePlaceholder":1117},[1053,2599,2600],{"class":1055,"line":1156},[1053,2601,2602],{"class":1058},"// In your service layer\n",[1053,2604,2605,2607,2609,2612,2614,2616,2618,2620,2622,2624,2626,2628,2630,2632,2634,2636,2638,2640,2642,2644,2646,2648],{"class":1055,"line":1208},[1053,2606,1378],{"class":1074},[1053,2608,1097],{"class":1068},[1053,2610,2611],{"class":1074}," err",[1053,2613,1386],{"class":1074},[1053,2615,1389],{"class":1074},[1053,2617,1084],{"class":1068},[1053,2619,1394],{"class":1087},[1053,2621,1090],{"class":1068},[1053,2623,1399],{"class":1074},[1053,2625,1097],{"class":1068},[1053,2627,2520],{"class":1074},[1053,2629,1097],{"class":1068},[1053,2631,1409],{"class":1064},[1053,2633,1412],{"class":1068},[1053,2635,1415],{"class":1105},[1053,2637,1418],{"class":1068},[1053,2639,1421],{"class":1105},[1053,2641,1424],{"class":1068},[1053,2643,1427],{"class":1093},[1053,2645,1163],{"class":1068},[1053,2647,1432],{"class":1093},[1053,2649,1435],{"class":1068},[962,2651,2652],{},"Statements are the single source of truth. The database dialect handles rendering. Your code stays clean.",[1036,2654,2656],{"id":2655},"documentation","Documentation",[2449,2658,2659],{},[2452,2660,2661,2664],{},[965,2662,6],{"href":2663},"docs/overview"," — Design philosophy and architecture",[2666,2667,897],"h3",{"id":2668},"learn",[2449,2670,2671,2675,2679],{},[2452,2672,2673],{},[965,2674,58],{"href":2416},[2452,2676,2677],{},[965,2678,101],{"href":2399},[2452,2680,2681],{},[965,2682,16],{"href":2429},[2666,2684,909],{"id":2685},"guides",[2449,2687,2688,2692],{},[2452,2689,2690],{},[965,2691,120],{"href":2382},[2452,2693,2694],{},[965,2695,456],{"href":2696},"docs/guides/testing",[2666,2698,925],{"id":2699},"reference",[2449,2701,2702],{},[2452,2703,2704],{},[965,2705,587],{"href":2442},[1036,2707,2709],{"id":2708},"contributing","Contributing",[962,2711,2712,2713,2717],{},"See ",[965,2714,2716],{"href":2715},"CONTRIBUTING","CONTRIBUTING.md"," for guidelines.",[1036,2719,1012],{"id":2720},"license",[962,2722,2723,2724,2726],{},"MIT License — see ",[965,2725,1009],{"href":1009}," for details.",[2728,2729,2730],"style",{},"html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}",{"title":34,"searchDepth":19,"depth":19,"links":2732},[2733,2734,2735,2736,2737,2738,2739,2744,2745],{"id":1038,"depth":19,"text":1039},{"id":1493,"depth":19,"text":1494},{"id":1522,"depth":19,"text":1523},{"id":2346,"depth":19,"text":2347},{"id":2446,"depth":19,"text":2447},{"id":2493,"depth":19,"text":2494},{"id":2655,"depth":19,"text":2656,"children":2740},[2741,2742,2743],{"id":2668,"depth":40,"text":897},{"id":2685,"depth":40,"text":909},{"id":2699,"depth":40,"text":925},{"id":2708,"depth":19,"text":2709},{"id":2720,"depth":19,"text":1012},"md","book-open",{},"/readme",{"title":953,"description":34},"readme","BscYQNVE6yaJgKSiVGtWPnquY2UTEYnFwgQPUaLtbJU",{"id":2754,"title":48,"body":2755,"description":34,"extension":2746,"icon":3188,"meta":3189,"navigation":1117,"path":3190,"seo":3191,"stem":3192,"__hash__":3193},"resources/security.md",{"type":955,"value":2756,"toc":3174},[2757,2761,2765,2768,2807,2811,2814,2818,2823,2826,2865,2869,2872,2921,2925,2951,2955,2958,2962,2965,3061,3065,3068,3099,3103,3106,3131,3135,3149,3153,3156,3162,3165,3171],[958,2758,2760],{"id":2759},"security-policy","Security Policy",[1036,2762,2764],{"id":2763},"supported-versions","Supported Versions",[962,2766,2767],{},"We release patches for security vulnerabilities. Which versions are eligible for receiving such patches depends on the CVSS v3.0 Rating:",[2349,2769,2770,2783],{},[2352,2771,2772],{},[2355,2773,2774,2777,2780],{},[2358,2775,2776],{},"Version",[2358,2778,2779],{},"Supported",[2358,2781,2782],{},"Status",[2368,2784,2785,2796],{},[2355,2786,2787,2790,2793],{},[2373,2788,2789],{},"latest",[2373,2791,2792],{},"✅",[2373,2794,2795],{},"Active development",[2355,2797,2798,2801,2804],{},[2373,2799,2800],{},"\u003C latest",[2373,2802,2803],{},"❌",[2373,2805,2806],{},"Security fixes only for critical issues",[1036,2808,2810],{"id":2809},"reporting-a-vulnerability","Reporting a Vulnerability",[962,2812,2813],{},"We take the security of edamame seriously. If you have discovered a security vulnerability in this project, please report it responsibly.",[2666,2815,2817],{"id":2816},"how-to-report","How to Report",[962,2819,2820],{},[2455,2821,2822],{},"Please DO NOT report security vulnerabilities through public GitHub issues.",[962,2824,2825],{},"Instead, please report them via one of the following methods:",[2827,2828,2829,2852],"ol",{},[2452,2830,2831,2834,2835],{},[2455,2832,2833],{},"GitHub Security Advisories"," (Preferred)",[2449,2836,2837,2846,2849],{},[2452,2838,2839,2840,2845],{},"Go to the ",[965,2841,2844],{"href":2842,"rel":2843},"https://github.com/zoobz-io/edamame/security",[969],"Security tab"," of this repository",[2452,2847,2848],{},"Click \"Report a vulnerability\"",[2452,2850,2851],{},"Fill out the form with details about the vulnerability",[2452,2853,2854,2857],{},[2455,2855,2856],{},"Email",[2449,2858,2859,2862],{},[2452,2860,2861],{},"Send details to the repository maintainer through GitHub profile contact information",[2452,2863,2864],{},"Use PGP encryption if possible for sensitive details",[2666,2866,2868],{"id":2867},"what-to-include","What to Include",[962,2870,2871],{},"Please include the following information (as much as you can provide) to help us better understand the nature and scope of the possible issue:",[2449,2873,2874,2880,2886,2892,2898,2903,2909,2915],{},[2452,2875,2876,2879],{},[2455,2877,2878],{},"Type of issue"," (e.g., SQL injection, race condition, information disclosure, etc.)",[2452,2881,2882,2885],{},[2455,2883,2884],{},"Full paths of source file(s)"," related to the manifestation of the issue",[2452,2887,2888,2891],{},[2455,2889,2890],{},"The location of the affected source code"," (tag/branch/commit or direct URL)",[2452,2893,2894,2897],{},[2455,2895,2896],{},"Any special configuration required"," to reproduce the issue",[2452,2899,2900,2897],{},[2455,2901,2902],{},"Step-by-step instructions",[2452,2904,2905,2908],{},[2455,2906,2907],{},"Proof-of-concept or exploit code"," (if possible)",[2452,2910,2911,2914],{},[2455,2912,2913],{},"Impact of the issue",", including how an attacker might exploit the issue",[2452,2916,2917,2920],{},[2455,2918,2919],{},"Your name and affiliation"," (optional)",[2666,2922,2924],{"id":2923},"what-to-expect","What to Expect",[2449,2926,2927,2933,2939,2945],{},[2452,2928,2929,2932],{},[2455,2930,2931],{},"Acknowledgment",": We will acknowledge receipt of your vulnerability report within 48 hours",[2452,2934,2935,2938],{},[2455,2936,2937],{},"Initial Assessment",": Within 7 days, we will provide an initial assessment of the report",[2452,2940,2941,2944],{},[2455,2942,2943],{},"Resolution Timeline",": We aim to resolve critical issues within 30 days",[2452,2946,2947,2950],{},[2455,2948,2949],{},"Disclosure",": We will coordinate with you on the disclosure timeline",[2666,2952,2954],{"id":2953},"preferred-languages","Preferred Languages",[962,2956,2957],{},"We prefer all communications to be in English.",[1036,2959,2961],{"id":2960},"security-best-practices","Security Best Practices",[962,2963,2964],{},"When using edamame in your applications, we recommend:",[2827,2966,2967,2988,3007,3026,3045],{},[2452,2968,2969,2972],{},[2455,2970,2971],{},"Keep Dependencies Updated",[1044,2973,2975],{"className":1497,"code":2974,"language":1499,"meta":34,"style":34},"go get -u github.com/zoobz-io/edamame\n",[1050,2976,2977],{"__ignoreMap":34},[1053,2978,2979,2981,2983,2986],{"class":1055,"line":9},[1053,2980,1048],{"class":1087},[1053,2982,1508],{"class":1093},[1053,2984,2985],{"class":1064}," -u",[1053,2987,1511],{"class":1093},[2452,2989,2990,2993],{},[2455,2991,2992],{},"Database Security",[2449,2994,2995,2998,3001,3004],{},[2452,2996,2997],{},"Use least-privilege database accounts",[2452,2999,3000],{},"Never expose factory specs publicly without authentication",[2452,3002,3003],{},"Validate all user input before passing to capabilities",[2452,3005,3006],{},"Use transactions for multi-step operations",[2452,3008,3009,3012],{},[2455,3010,3011],{},"LLM Integration Security",[2449,3013,3014,3017,3020,3023],{},[2452,3015,3016],{},"Always validate LLM-generated capability names and params",[2452,3018,3019],{},"Rate limit LLM-driven database operations",[2452,3021,3022],{},"Audit log all LLM-executed queries",[2452,3024,3025],{},"Never expose raw SQL generation to external systems",[2452,3027,3028,3031],{},[2455,3029,3030],{},"Capability Management",[2449,3032,3033,3036,3039,3042],{},[2452,3034,3035],{},"Only expose capabilities that are safe for your use case",[2452,3037,3038],{},"Use explicit params with validation",[2452,3040,3041],{},"Avoid overly permissive WHERE clauses",[2452,3043,3044],{},"Review auto-derived params before deployment",[2452,3046,3047,3050],{},[2455,3048,3049],{},"Error Handling",[2449,3051,3052,3055,3058],{},[2452,3053,3054],{},"Implement proper error handling",[2452,3056,3057],{},"Don't expose internal errors to users",[2452,3059,3060],{},"Log errors appropriately",[1036,3062,3064],{"id":3063},"security-features","Security Features",[962,3066,3067],{},"edamame includes several built-in security features:",[2449,3069,3070,3076,3081,3087,3093],{},[2452,3071,3072,3075],{},[2455,3073,3074],{},"Parameterized Queries",": All queries use parameterized SQL via sqlx, preventing SQL injection",[2452,3077,3078,3080],{},[2455,3079,37],{},": Generic types prevent type confusion",[2452,3082,3083,3086],{},[2455,3084,3085],{},"Spec Validation",": Capability specs are validated before execution",[2452,3088,3089,3092],{},[2455,3090,3091],{},"No Raw SQL",": Users define specs, not raw SQL strings",[2452,3094,3095,3098],{},[2455,3096,3097],{},"Introspection Only",": Spec export is read-only, doesn't expose credentials",[1036,3100,3102],{"id":3101},"automated-security-scanning","Automated Security Scanning",[962,3104,3105],{},"This project uses:",[2449,3107,3108,3113,3119,3125],{},[2452,3109,3110,3112],{},[2455,3111,997],{},": GitHub's semantic code analysis for security vulnerabilities",[2452,3114,3115,3118],{},[2455,3116,3117],{},"gosec",": Go security checker for common vulnerabilities",[2452,3120,3121,3124],{},[2455,3122,3123],{},"golangci-lint",": Static analysis including security linters (sqlclosecheck, noctx, bodyclose)",[2452,3126,3127,3130],{},[2455,3128,3129],{},"Codecov",": Coverage tracking to ensure security-critical code is tested",[1036,3132,3134],{"id":3133},"vulnerability-disclosure-policy","Vulnerability Disclosure Policy",[2449,3136,3137,3140,3143,3146],{},[2452,3138,3139],{},"Security vulnerabilities will be disclosed via GitHub Security Advisories",[2452,3141,3142],{},"We follow a 90-day disclosure timeline for non-critical issues",[2452,3144,3145],{},"Critical vulnerabilities may be disclosed sooner after patches are available",[2452,3147,3148],{},"We will credit reporters who follow responsible disclosure practices",[1036,3150,3152],{"id":3151},"credits","Credits",[962,3154,3155],{},"We thank the following individuals for responsibly disclosing security issues:",[962,3157,3158],{},[3159,3160,3161],"em",{},"This list is currently empty. Be the first to help improve our security!",[3163,3164],"hr",{},[962,3166,3167,3170],{},[2455,3168,3169],{},"Last Updated",": 2025-12-17",[2728,3172,3173],{},"html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":34,"searchDepth":19,"depth":19,"links":3175},[3176,3177,3183,3184,3185,3186,3187],{"id":2763,"depth":19,"text":2764},{"id":2809,"depth":19,"text":2810,"children":3178},[3179,3180,3181,3182],{"id":2816,"depth":40,"text":2817},{"id":2867,"depth":40,"text":2868},{"id":2923,"depth":40,"text":2924},{"id":2953,"depth":40,"text":2954},{"id":2960,"depth":19,"text":2961},{"id":3063,"depth":19,"text":3064},{"id":3101,"depth":19,"text":3102},{"id":3133,"depth":19,"text":3134},{"id":3151,"depth":19,"text":3152},"shield",{},"/security",{"title":48,"description":34},"security","Sx8ErY-iTzxHLX7iGbih6ydeebS0veDo30qsuQcZLEI",{"id":3195,"title":2709,"body":3196,"description":3204,"extension":2746,"icon":1050,"meta":3699,"navigation":1117,"path":3700,"seo":3701,"stem":2708,"__hash__":3702},"resources/contributing.md",{"type":955,"value":3197,"toc":3675},[3198,3202,3205,3209,3212,3216,3260,3264,3268,3286,3289,3312,3314,3328,3332,3336,3350,3354,3365,3369,3372,3386,3390,3418,3421,3424,3437,3440,3452,3455,3467,3470,3482,3485,3497,3501,3509,3513,3516,3560,3564,3568,3571,3582,3585,3599,3603,3630,3636,3640,3643,3654,3658,3669,3672],[958,3199,3201],{"id":3200},"contributing-to-edamame","Contributing to edamame",[962,3203,3204],{},"Thank you for your interest in contributing to edamame! This guide will help you get started.",[1036,3206,3208],{"id":3207},"code-of-conduct","Code of Conduct",[962,3210,3211],{},"By participating in this project, you agree to maintain a respectful and inclusive environment for all contributors.",[1036,3213,3215],{"id":3214},"getting-started","Getting Started",[2827,3217,3218,3221,3227,3233,3236,3242,3248,3251,3257],{},[2452,3219,3220],{},"Fork the repository",[2452,3222,3223,3224],{},"Clone your fork: ",[1050,3225,3226],{},"git clone https://github.com/yourusername/edamame.git",[2452,3228,3229,3230],{},"Create a feature branch: ",[1050,3231,3232],{},"git checkout -b feature/your-feature-name",[2452,3234,3235],{},"Make your changes",[2452,3237,3238,3239],{},"Run tests: ",[1050,3240,3241],{},"make test",[2452,3243,3244,3245],{},"Run linters: ",[1050,3246,3247],{},"make lint",[2452,3249,3250],{},"Commit your changes with a descriptive message",[2452,3252,3253,3254],{},"Push to your fork: ",[1050,3255,3256],{},"git push origin feature/your-feature-name",[2452,3258,3259],{},"Create a Pull Request",[1036,3261,3263],{"id":3262},"development-guidelines","Development Guidelines",[2666,3265,3267],{"id":3266},"code-style","Code Style",[2449,3269,3270,3273,3280,3283],{},[2452,3271,3272],{},"Follow standard Go conventions",[2452,3274,3275,3276,3279],{},"Run ",[1050,3277,3278],{},"go fmt"," before committing",[2452,3281,3282],{},"Add comments for exported functions and types",[2452,3284,3285],{},"Keep functions small and focused",[2666,3287,456],{"id":3288},"testing",[2449,3290,3291,3294,3299,3302,3305],{},[2452,3292,3293],{},"Write tests for new functionality",[2452,3295,3296,3297],{},"Ensure all tests pass: ",[1050,3298,3241],{},[2452,3300,3301],{},"Maintain 1:1 file-to-test ratio",[2452,3303,3304],{},"Aim for 80%+ test coverage",[2452,3306,3307,3308,3311],{},"Use the ",[1050,3309,3310],{},"testing/"," package helpers for capability testing",[2666,3313,2656],{"id":2655},[2449,3315,3316,3319,3322,3325],{},[2452,3317,3318],{},"Update README.md for API changes",[2452,3320,3321],{},"Add comments to all exported types",[2452,3323,3324],{},"Keep doc comments clear and concise",[2452,3326,3327],{},"Update docs/ for significant changes",[1036,3329,3331],{"id":3330},"types-of-contributions","Types of Contributions",[2666,3333,3335],{"id":3334},"bug-reports","Bug Reports",[2449,3337,3338,3341,3344,3347],{},[2452,3339,3340],{},"Use GitHub Issues",[2452,3342,3343],{},"Include minimal reproduction code",[2452,3345,3346],{},"Describe expected vs actual behavior",[2452,3348,3349],{},"Include Go version and OS",[2666,3351,3353],{"id":3352},"feature-requests","Feature Requests",[2449,3355,3356,3359,3362],{},[2452,3357,3358],{},"Open an issue for discussion first",[2452,3360,3361],{},"Explain the use case",[2452,3363,3364],{},"Consider backwards compatibility",[2666,3366,3368],{"id":3367},"code-contributions","Code Contributions",[962,3370,3371],{},"All contributions should:",[2449,3373,3374,3377,3380,3383],{},[2452,3375,3376],{},"Include comprehensive tests",[2452,3378,3379],{},"Pass linter checks",[2452,3381,3382],{},"Maintain existing code style",[2452,3384,3385],{},"Update documentation as needed",[1036,3387,3389],{"id":3388},"pull-request-process","Pull Request Process",[2827,3391,3392,3398,3403,3408,3413],{},[2452,3393,3394,3397],{},[2455,3395,3396],{},"Keep PRs focused"," - One feature/fix per PR",[2452,3399,3400],{},[2455,3401,3402],{},"Write descriptive commit messages",[2452,3404,3405],{},[2455,3406,3407],{},"Update tests and documentation",[2452,3409,3410],{},[2455,3411,3412],{},"Ensure CI passes",[2452,3414,3415],{},[2455,3416,3417],{},"Respond to review feedback",[1036,3419,456],{"id":3420},"testing-1",[962,3422,3423],{},"Run the full test suite:",[1044,3425,3427],{"className":1497,"code":3426,"language":1499,"meta":34,"style":34},"make test\n",[1050,3428,3429],{"__ignoreMap":34},[1053,3430,3431,3434],{"class":1055,"line":9},[1053,3432,3433],{"class":1087},"make",[1053,3435,3436],{"class":1093}," test\n",[962,3438,3439],{},"Run with coverage:",[1044,3441,3443],{"className":1497,"code":3442,"language":1499,"meta":34,"style":34},"make coverage\n",[1050,3444,3445],{"__ignoreMap":34},[1053,3446,3447,3449],{"class":1055,"line":9},[1053,3448,3433],{"class":1087},[1053,3450,3451],{"class":1093}," coverage\n",[962,3453,3454],{},"Run linters:",[1044,3456,3458],{"className":1497,"code":3457,"language":1499,"meta":34,"style":34},"make lint\n",[1050,3459,3460],{"__ignoreMap":34},[1053,3461,3462,3464],{"class":1055,"line":9},[1053,3463,3433],{"class":1087},[1053,3465,3466],{"class":1093}," lint\n",[962,3468,3469],{},"Run integration tests (requires Docker):",[1044,3471,3473],{"className":1497,"code":3472,"language":1499,"meta":34,"style":34},"make test-integration\n",[1050,3474,3475],{"__ignoreMap":34},[1053,3476,3477,3479],{"class":1055,"line":9},[1053,3478,3433],{"class":1087},[1053,3480,3481],{"class":1093}," test-integration\n",[962,3483,3484],{},"Run full CI simulation:",[1044,3486,3488],{"className":1497,"code":3487,"language":1499,"meta":34,"style":34},"make ci\n",[1050,3489,3490],{"__ignoreMap":34},[1053,3491,3492,3494],{"class":1055,"line":9},[1053,3493,3433],{"class":1087},[1053,3495,3496],{"class":1093}," ci\n",[1036,3498,3500],{"id":3499},"project-structure","Project Structure",[1044,3502,3507],{"className":3503,"code":3505,"language":3506},[3504],"language-text","edamame/\n├── *.go              # Core library files\n├── *_test.go         # Tests (1:1 with source files)\n├── testing/          # Test helpers and benchmarks\n│   ├── helpers.go    # Test utilities\n│   ├── benchmarks/   # Performance benchmarks\n│   └── integration/  # Database integration tests\n├── docs/             # Documentation\n├── .github/          # GitHub workflows and templates\n├── README.md         # Project documentation\n└── Makefile          # Build and test commands\n","text",[1050,3508,3505],{"__ignoreMap":34},[1036,3510,3512],{"id":3511},"commit-messages","Commit Messages",[962,3514,3515],{},"Follow conventional commits:",[2449,3517,3518,3524,3530,3536,3542,3548,3554],{},[2452,3519,3520,3523],{},[1050,3521,3522],{},"feat:"," New feature",[2452,3525,3526,3529],{},[1050,3527,3528],{},"fix:"," Bug fix",[2452,3531,3532,3535],{},[1050,3533,3534],{},"docs:"," Documentation changes",[2452,3537,3538,3541],{},[1050,3539,3540],{},"test:"," Test additions/changes",[2452,3543,3544,3547],{},[1050,3545,3546],{},"refactor:"," Code refactoring",[2452,3549,3550,3553],{},[1050,3551,3552],{},"perf:"," Performance improvements",[2452,3555,3556,3559],{},[1050,3557,3558],{},"chore:"," Maintenance tasks",[1036,3561,3563],{"id":3562},"release-process","Release Process",[2666,3565,3567],{"id":3566},"automated-releases","Automated Releases",[962,3569,3570],{},"This project uses automated release versioning. To create a release:",[2827,3572,3573,3576,3579],{},[2452,3574,3575],{},"Go to Actions → Release → Run workflow",[2452,3577,3578],{},"Leave \"Version override\" empty for automatic version inference",[2452,3580,3581],{},"Click \"Run workflow\"",[962,3583,3584],{},"The system will:",[2449,3586,3587,3590,3593,3596],{},[2452,3588,3589],{},"Automatically determine the next version from conventional commits",[2452,3591,3592],{},"Create a git tag",[2452,3594,3595],{},"Generate release notes via GoReleaser",[2452,3597,3598],{},"Publish the release to GitHub",[2666,3600,3602],{"id":3601},"commit-conventions-for-versioning","Commit Conventions for Versioning",[2449,3604,3605,3610,3615,3621],{},[2452,3606,3607,3609],{},[1050,3608,3522],{}," new features (minor version: 1.2.0 → 1.3.0)",[2452,3611,3612,3614],{},[1050,3613,3528],{}," bug fixes (patch version: 1.2.0 → 1.2.1)",[2452,3616,3617,3620],{},[1050,3618,3619],{},"feat!:"," breaking changes (major version: 1.2.0 → 2.0.0)",[2452,3622,3623,2283,3625,2283,3627,3629],{},[1050,3624,3534],{},[1050,3626,3540],{},[1050,3628,3558],{}," no version change",[962,3631,3632,3633],{},"Example: ",[1050,3634,3635],{},"feat(factory): add batch delete support",[2666,3637,3639],{"id":3638},"version-preview-on-pull-requests","Version Preview on Pull Requests",[962,3641,3642],{},"Every PR automatically shows the next version that will be created:",[2449,3644,3645,3648,3651],{},[2452,3646,3647],{},"Check PR comments for \"Version Preview\"",[2452,3649,3650],{},"Updates automatically as you add commits",[2452,3652,3653],{},"Helps verify your commits have the intended effect",[1036,3655,3657],{"id":3656},"questions","Questions?",[2449,3659,3660,3663,3666],{},[2452,3661,3662],{},"Open an issue for questions",[2452,3664,3665],{},"Check existing issues first",[2452,3667,3668],{},"Be patient and respectful",[962,3670,3671],{},"Thank you for contributing to edamame!",[2728,3673,3674],{},"html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":34,"searchDepth":19,"depth":19,"links":3676},[3677,3678,3679,3684,3689,3690,3691,3692,3693,3698],{"id":3207,"depth":19,"text":3208},{"id":3214,"depth":19,"text":3215},{"id":3262,"depth":19,"text":3263,"children":3680},[3681,3682,3683],{"id":3266,"depth":40,"text":3267},{"id":3288,"depth":40,"text":456},{"id":2655,"depth":40,"text":2656},{"id":3330,"depth":19,"text":3331,"children":3685},[3686,3687,3688],{"id":3334,"depth":40,"text":3335},{"id":3352,"depth":40,"text":3353},{"id":3367,"depth":40,"text":3368},{"id":3388,"depth":19,"text":3389},{"id":3420,"depth":19,"text":456},{"id":3499,"depth":19,"text":3500},{"id":3511,"depth":19,"text":3512},{"id":3562,"depth":19,"text":3563,"children":3694},[3695,3696,3697],{"id":3566,"depth":40,"text":3567},{"id":3601,"depth":40,"text":3602},{"id":3638,"depth":40,"text":3639},{"id":3656,"depth":19,"text":3657},{},"/contributing",{"title":2709,"description":3204},"QcMmj_qs3DqAuZIWSIkA2CW6kAKXQPr0ftDXK0qwA9k",{"id":3704,"title":456,"author":3705,"body":3706,"description":458,"extension":2746,"meta":7692,"navigation":1117,"path":455,"published":7693,"readtime":7694,"seo":7695,"stem":916,"tags":7696,"updated":7699,"__hash__":7700},"edamame/v1.0.3/3.guides/2.testing.md","zoobzio",{"type":955,"value":3707,"toc":7678},[3708,3710,3717,3720,3723,3726,4163,4166,4169,4458,4461,4464,4689,4692,4695,5229,5232,5235,5625,5628,5631,6441,6444,6462,6465,6472,6674,6677,6697,6700,6703,6980,6983,7455,7458,7465,7675],[958,3709,456],{"id":3288},[962,3711,3712,3713,3716],{},"Edamame provides testing utilities in the ",[1050,3714,3715],{},"github.com/zoobz-io/edamame/testing"," package.",[1036,3718,465],{"id":3719},"test-helpers",[2666,3721,469],{"id":3722},"querycapture",[962,3724,3725],{},"Capture rendered SQL for verification:",[1044,3727,3729],{"className":1046,"code":3728,"language":1048,"meta":34,"style":34},"import (\n    edamametesting \"github.com/zoobz-io/edamame/testing\"\n    \"github.com/zoobz-io/astql/pkg/postgres\"\n)\n\nfunc TestQueryRendering(t *testing.T) {\n    capture := edamametesting.NewQueryCapture()\n\n    exec, _ := edamame.New[User](nil, \"users\", postgres.New())\n\n    // Define a statement\n    var ByStatus = edamame.NewQueryStatement(\"by-status\", \"Find by status\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{{Field: \"status\", Operator: \"=\", Param: \"status\"}},\n    })\n\n    // Get the builder and render\n    q, _ := exec.Query(ByStatus)\n    result, _ := q.Render()\n\n    capture.CaptureQuery(\"by-status\", \"query\", result.SQL, nil)\n\n    // Verify\n    if capture.Count() != 1 {\n        t.Errorf(\"expected 1 query, got %d\", capture.Count())\n    }\n\n    last := capture.Last()\n    if last.Type != \"query\" {\n        t.Errorf(\"expected type 'query', got %q\", last.Type)\n    }\n}\n",[1050,3730,3731,3737,3745,3750,3754,3758,3786,3803,3807,3846,3850,3855,3889,3929,3933,3937,3942,3966,3987,3991,4025,4029,4034,4055,4084,4089,4093,4109,4127,4155,4159],{"__ignoreMap":34},[1053,3732,3733,3735],{"class":1055,"line":9},[1053,3734,1545],{"class":1064},[1053,3736,1069],{"class":1548},[1053,3738,3739,3742],{"class":1055,"line":19},[1053,3740,3741],{"class":1572},"    edamametesting",[1053,3743,3744],{"class":1093}," \"github.com/zoobz-io/edamame/testing\"\n",[1053,3746,3747],{"class":1055,"line":40},[1053,3748,3749],{"class":1093},"    \"github.com/zoobz-io/astql/pkg/postgres\"\n",[1053,3751,3752],{"class":1055,"line":674},[1053,3753,1361],{"class":1548},[1053,3755,3756],{"class":1055,"line":1121},[1053,3757,1118],{"emptyLinePlaceholder":1117},[1053,3759,3760,3762,3765,3767,3770,3774,3776,3778,3781,3784],{"class":1055,"line":1156},[1053,3761,1956],{"class":1064},[1053,3763,3764],{"class":1087}," TestQueryRendering",[1053,3766,1090],{"class":1068},[1053,3768,3769],{"class":1572},"t",[1053,3771,3773],{"class":3772},"sW3Qg"," *",[1053,3775,3288],{"class":1105},[1053,3777,1084],{"class":1068},[1053,3779,3780],{"class":1105},"T",[1053,3782,3783],{"class":1068},")",[1053,3785,1614],{"class":1068},[1053,3787,3788,3791,3793,3796,3798,3801],{"class":1055,"line":1208},[1053,3789,3790],{"class":1074},"    capture",[1053,3792,1386],{"class":1074},[1053,3794,3795],{"class":1074}," edamametesting",[1053,3797,1084],{"class":1068},[1053,3799,3800],{"class":1087},"NewQueryCapture",[1053,3802,2015],{"class":1068},[1053,3804,3805],{"class":1055,"line":1246},[1053,3806,1118],{"emptyLinePlaceholder":1117},[1053,3808,3809,3811,3813,3815,3817,3819,3821,3823,3825,3827,3829,3832,3834,3836,3838,3840,3842,3844],{"class":1055,"line":1266},[1053,3810,2032],{"class":1074},[1053,3812,1097],{"class":1068},[1053,3814,1383],{"class":1074},[1053,3816,1386],{"class":1074},[1053,3818,1081],{"class":1074},[1053,3820,1084],{"class":1068},[1053,3822,600],{"class":1087},[1053,3824,1412],{"class":1068},[1053,3826,2049],{"class":1105},[1053,3828,2052],{"class":1068},[1053,3830,3831],{"class":1064},"nil",[1053,3833,1097],{"class":1068},[1053,3835,2060],{"class":1093},[1053,3837,1097],{"class":1068},[1053,3839,2065],{"class":1074},[1053,3841,1084],{"class":1068},[1053,3843,600],{"class":1087},[1053,3845,2072],{"class":1068},[1053,3847,3848],{"class":1055,"line":1272},[1053,3849,1118],{"emptyLinePlaceholder":1117},[1053,3851,3852],{"class":1055,"line":1277},[1053,3853,3854],{"class":1058},"    // Define a statement\n",[1053,3856,3857,3860,3862,3864,3866,3868,3870,3872,3874,3876,3879,3881,3883,3885,3887],{"class":1055,"line":1311},[1053,3858,3859],{"class":1064},"    var",[1053,3861,1404],{"class":1074},[1053,3863,1078],{"class":1074},[1053,3865,1081],{"class":1074},[1053,3867,1084],{"class":1068},[1053,3869,609],{"class":1087},[1053,3871,1090],{"class":1068},[1053,3873,1137],{"class":1093},[1053,3875,1097],{"class":1068},[1053,3877,3878],{"class":1093}," \"Find by status\"",[1053,3880,1097],{"class":1068},[1053,3882,1081],{"class":1105},[1053,3884,1084],{"class":1068},[1053,3886,144],{"class":1105},[1053,3888,1153],{"class":1068},[1053,3890,3891,3893,3895,3897,3899,3901,3903,3905,3907,3909,3911,3913,3915,3917,3919,3921,3923,3925,3927],{"class":1055,"line":1353},[1053,3892,1160],{"class":1159},[1053,3894,1163],{"class":1068},[1053,3896,1216],{"class":1068},[1053,3898,960],{"class":1105},[1053,3900,1084],{"class":1068},[1053,3902,831],{"class":1105},[1053,3904,1175],{"class":1068},[1053,3906,1178],{"class":1159},[1053,3908,1163],{"class":1068},[1053,3910,1183],{"class":1093},[1053,3912,1097],{"class":1068},[1053,3914,1188],{"class":1159},[1053,3916,1163],{"class":1068},[1053,3918,1193],{"class":1093},[1053,3920,1097],{"class":1068},[1053,3922,1198],{"class":1159},[1053,3924,1163],{"class":1068},[1053,3926,1183],{"class":1093},[1053,3928,1205],{"class":1068},[1053,3930,3931],{"class":1055,"line":1358},[1053,3932,1269],{"class":1068},[1053,3934,3935],{"class":1055,"line":1364},[1053,3936,1118],{"emptyLinePlaceholder":1117},[1053,3938,3939],{"class":1055,"line":1369},[1053,3940,3941],{"class":1058},"    // Get the builder and render\n",[1053,3943,3944,3947,3949,3951,3953,3955,3957,3959,3961,3964],{"class":1055,"line":1375},[1053,3945,3946],{"class":1074},"    q",[1053,3948,1097],{"class":1068},[1053,3950,1383],{"class":1074},[1053,3952,1386],{"class":1074},[1053,3954,1389],{"class":1074},[1053,3956,1084],{"class":1068},[1053,3958,671],{"class":1087},[1053,3960,1090],{"class":1068},[1053,3962,3963],{"class":1074},"ByStatus",[1053,3965,1361],{"class":1068},[1053,3967,3968,3971,3973,3975,3977,3980,3982,3985],{"class":1055,"line":1438},[1053,3969,3970],{"class":1074},"    result",[1053,3972,1097],{"class":1068},[1053,3974,1383],{"class":1074},[1053,3976,1386],{"class":1074},[1053,3978,3979],{"class":1074}," q",[1053,3981,1084],{"class":1068},[1053,3983,3984],{"class":1087},"Render",[1053,3986,2015],{"class":1068},[1053,3988,3989],{"class":1055,"line":1666},[1053,3990,1118],{"emptyLinePlaceholder":1117},[1053,3992,3993,3995,3997,4000,4002,4004,4006,4009,4011,4014,4016,4019,4021,4023],{"class":1055,"line":1671},[1053,3994,3790],{"class":1074},[1053,3996,1084],{"class":1068},[1053,3998,3999],{"class":1087},"CaptureQuery",[1053,4001,1090],{"class":1068},[1053,4003,1137],{"class":1093},[1053,4005,1097],{"class":1068},[1053,4007,4008],{"class":1093}," \"query\"",[1053,4010,1097],{"class":1068},[1053,4012,4013],{"class":1074}," result",[1053,4015,1084],{"class":1068},[1053,4017,4018],{"class":1074},"SQL",[1053,4020,1097],{"class":1068},[1053,4022,2115],{"class":1064},[1053,4024,1361],{"class":1068},[1053,4026,4027],{"class":1055,"line":1677},[1053,4028,1118],{"emptyLinePlaceholder":1117},[1053,4030,4031],{"class":1055,"line":1684},[1053,4032,4033],{"class":1058},"    // Verify\n",[1053,4035,4036,4039,4042,4044,4046,4048,4051,4053],{"class":1055,"line":1715},[1053,4037,4038],{"class":3772},"    if",[1053,4040,4041],{"class":1074}," capture",[1053,4043,1084],{"class":1068},[1053,4045,403],{"class":1087},[1053,4047,1962],{"class":1068},[1053,4049,4050],{"class":3772}," !=",[1053,4052,2164],{"class":1259},[1053,4054,1614],{"class":1068},[1053,4056,4057,4060,4062,4065,4067,4070,4072,4074,4076,4078,4080,4082],{"class":1055,"line":1720},[1053,4058,4059],{"class":1074},"        t",[1053,4061,1084],{"class":1068},[1053,4063,4064],{"class":1087},"Errorf",[1053,4066,1090],{"class":1068},[1053,4068,4069],{"class":1093},"\"expected 1 query, got ",[1053,4071,2274],{"class":2273},[1053,4073,2270],{"class":1093},[1053,4075,1097],{"class":1068},[1053,4077,4041],{"class":1074},[1053,4079,1084],{"class":1068},[1053,4081,403],{"class":1087},[1053,4083,2072],{"class":1068},[1053,4085,4086],{"class":1055,"line":1751},[1053,4087,4088],{"class":1068},"    }\n",[1053,4090,4091],{"class":1055,"line":1792},[1053,4092,1118],{"emptyLinePlaceholder":1117},[1053,4094,4095,4098,4100,4102,4104,4107],{"class":1055,"line":1797},[1053,4096,4097],{"class":1074},"    last",[1053,4099,1386],{"class":1074},[1053,4101,4041],{"class":1074},[1053,4103,1084],{"class":1068},[1053,4105,4106],{"class":1087},"Last",[1053,4108,2015],{"class":1068},[1053,4110,4111,4113,4116,4118,4121,4123,4125],{"class":1055,"line":1802},[1053,4112,4038],{"class":3772},[1053,4114,4115],{"class":1074}," last",[1053,4117,1084],{"class":1068},[1053,4119,4120],{"class":1074},"Type",[1053,4122,4050],{"class":3772},[1053,4124,4008],{"class":1093},[1053,4126,1614],{"class":1068},[1053,4128,4129,4131,4133,4135,4137,4140,4143,4145,4147,4149,4151,4153],{"class":1055,"line":1845},[1053,4130,4059],{"class":1074},[1053,4132,1084],{"class":1068},[1053,4134,4064],{"class":1087},[1053,4136,1090],{"class":1068},[1053,4138,4139],{"class":1093},"\"expected type 'query', got ",[1053,4141,4142],{"class":2273},"%q",[1053,4144,2270],{"class":1093},[1053,4146,1097],{"class":1068},[1053,4148,4115],{"class":1074},[1053,4150,1084],{"class":1068},[1053,4152,4120],{"class":1074},[1053,4154,1361],{"class":1068},[1053,4156,4157],{"class":1055,"line":1850},[1053,4158,4088],{"class":1068},[1053,4160,4161],{"class":1055,"line":1884},[1053,4162,1663],{"class":1068},[2666,4164,474],{"id":4165},"executoreventcapture",[962,4167,4168],{},"Capture executor creation events via capitan:",[1044,4170,4172],{"className":1046,"code":4171,"language":1048,"meta":34,"style":34},"func TestExecutorEvents(t *testing.T) {\n    c := capitan.New(capitan.WithSyncMode())\n    defer c.Shutdown()\n\n    capture := edamametesting.NewExecutorEventCapture()\n    c.Hook(edamame.ExecutorCreated, capture.Handler())\n\n    exec, _ := edamame.New[User](nil, \"users\", postgres.New())\n\n    // Verify event captured\n    if capture.Count() != 1 {\n        t.Error(\"expected executor created event\")\n    }\n\n    tables := capture.Tables()\n    if tables[0].Table != \"users\" {\n        t.Errorf(\"expected table 'users', got %q\", tables[0].Table)\n    }\n}\n",[1050,4173,4174,4197,4223,4238,4242,4257,4286,4290,4328,4332,4337,4355,4371,4375,4379,4395,4419,4450,4454],{"__ignoreMap":34},[1053,4175,4176,4178,4181,4183,4185,4187,4189,4191,4193,4195],{"class":1055,"line":9},[1053,4177,1956],{"class":1064},[1053,4179,4180],{"class":1087}," TestExecutorEvents",[1053,4182,1090],{"class":1068},[1053,4184,3769],{"class":1572},[1053,4186,3773],{"class":3772},[1053,4188,3288],{"class":1105},[1053,4190,1084],{"class":1068},[1053,4192,3780],{"class":1105},[1053,4194,3783],{"class":1068},[1053,4196,1614],{"class":1068},[1053,4198,4199,4202,4204,4207,4209,4211,4213,4216,4218,4221],{"class":1055,"line":19},[1053,4200,4201],{"class":1074},"    c",[1053,4203,1386],{"class":1074},[1053,4205,4206],{"class":1074}," capitan",[1053,4208,1084],{"class":1068},[1053,4210,600],{"class":1087},[1053,4212,1090],{"class":1068},[1053,4214,4215],{"class":1074},"capitan",[1053,4217,1084],{"class":1068},[1053,4219,4220],{"class":1087},"WithSyncMode",[1053,4222,2072],{"class":1068},[1053,4224,4225,4228,4231,4233,4236],{"class":1055,"line":40},[1053,4226,4227],{"class":3772},"    defer",[1053,4229,4230],{"class":1074}," c",[1053,4232,1084],{"class":1068},[1053,4234,4235],{"class":1087},"Shutdown",[1053,4237,2015],{"class":1068},[1053,4239,4240],{"class":1055,"line":674},[1053,4241,1118],{"emptyLinePlaceholder":1117},[1053,4243,4244,4246,4248,4250,4252,4255],{"class":1055,"line":1121},[1053,4245,3790],{"class":1074},[1053,4247,1386],{"class":1074},[1053,4249,3795],{"class":1074},[1053,4251,1084],{"class":1068},[1053,4253,4254],{"class":1087},"NewExecutorEventCapture",[1053,4256,2015],{"class":1068},[1053,4258,4259,4261,4263,4266,4268,4270,4272,4275,4277,4279,4281,4284],{"class":1055,"line":1156},[1053,4260,4201],{"class":1074},[1053,4262,1084],{"class":1068},[1053,4264,4265],{"class":1087},"Hook",[1053,4267,1090],{"class":1068},[1053,4269,960],{"class":1074},[1053,4271,1084],{"class":1068},[1053,4273,4274],{"class":1074},"ExecutorCreated",[1053,4276,1097],{"class":1068},[1053,4278,4041],{"class":1074},[1053,4280,1084],{"class":1068},[1053,4282,4283],{"class":1087},"Handler",[1053,4285,2072],{"class":1068},[1053,4287,4288],{"class":1055,"line":1208},[1053,4289,1118],{"emptyLinePlaceholder":1117},[1053,4291,4292,4294,4296,4298,4300,4302,4304,4306,4308,4310,4312,4314,4316,4318,4320,4322,4324,4326],{"class":1055,"line":1246},[1053,4293,2032],{"class":1074},[1053,4295,1097],{"class":1068},[1053,4297,1383],{"class":1074},[1053,4299,1386],{"class":1074},[1053,4301,1081],{"class":1074},[1053,4303,1084],{"class":1068},[1053,4305,600],{"class":1087},[1053,4307,1412],{"class":1068},[1053,4309,2049],{"class":1105},[1053,4311,2052],{"class":1068},[1053,4313,3831],{"class":1064},[1053,4315,1097],{"class":1068},[1053,4317,2060],{"class":1093},[1053,4319,1097],{"class":1068},[1053,4321,2065],{"class":1074},[1053,4323,1084],{"class":1068},[1053,4325,600],{"class":1087},[1053,4327,2072],{"class":1068},[1053,4329,4330],{"class":1055,"line":1266},[1053,4331,1118],{"emptyLinePlaceholder":1117},[1053,4333,4334],{"class":1055,"line":1272},[1053,4335,4336],{"class":1058},"    // Verify event captured\n",[1053,4338,4339,4341,4343,4345,4347,4349,4351,4353],{"class":1055,"line":1277},[1053,4340,4038],{"class":3772},[1053,4342,4041],{"class":1074},[1053,4344,1084],{"class":1068},[1053,4346,403],{"class":1087},[1053,4348,1962],{"class":1068},[1053,4350,4050],{"class":3772},[1053,4352,2164],{"class":1259},[1053,4354,1614],{"class":1068},[1053,4356,4357,4359,4361,4364,4366,4369],{"class":1055,"line":1311},[1053,4358,4059],{"class":1074},[1053,4360,1084],{"class":1068},[1053,4362,4363],{"class":1087},"Error",[1053,4365,1090],{"class":1068},[1053,4367,4368],{"class":1093},"\"expected executor created event\"",[1053,4370,1361],{"class":1068},[1053,4372,4373],{"class":1055,"line":1353},[1053,4374,4088],{"class":1068},[1053,4376,4377],{"class":1055,"line":1358},[1053,4378,1118],{"emptyLinePlaceholder":1117},[1053,4380,4381,4384,4386,4388,4390,4393],{"class":1055,"line":1364},[1053,4382,4383],{"class":1074},"    tables",[1053,4385,1386],{"class":1074},[1053,4387,4041],{"class":1074},[1053,4389,1084],{"class":1068},[1053,4391,4392],{"class":1087},"Tables",[1053,4394,2015],{"class":1068},[1053,4396,4397,4399,4402,4404,4407,4410,4413,4415,4417],{"class":1055,"line":1369},[1053,4398,4038],{"class":3772},[1053,4400,4401],{"class":1074}," tables",[1053,4403,1412],{"class":1068},[1053,4405,4406],{"class":1259},"0",[1053,4408,4409],{"class":1068},"].",[1053,4411,4412],{"class":1074},"Table",[1053,4414,4050],{"class":3772},[1053,4416,2060],{"class":1093},[1053,4418,1614],{"class":1068},[1053,4420,4421,4423,4425,4427,4429,4432,4434,4436,4438,4440,4442,4444,4446,4448],{"class":1055,"line":1375},[1053,4422,4059],{"class":1074},[1053,4424,1084],{"class":1068},[1053,4426,4064],{"class":1087},[1053,4428,1090],{"class":1068},[1053,4430,4431],{"class":1093},"\"expected table 'users', got ",[1053,4433,4142],{"class":2273},[1053,4435,2270],{"class":1093},[1053,4437,1097],{"class":1068},[1053,4439,4401],{"class":1074},[1053,4441,1412],{"class":1068},[1053,4443,4406],{"class":1259},[1053,4445,4409],{"class":1068},[1053,4447,4412],{"class":1074},[1053,4449,1361],{"class":1068},[1053,4451,4452],{"class":1055,"line":1438},[1053,4453,4088],{"class":1068},[1053,4455,4456],{"class":1055,"line":1666},[1053,4457,1663],{"class":1068},[2666,4459,479],{"id":4460},"parambuilder",[962,4462,4463],{},"Build test parameters fluently:",[1044,4465,4467],{"className":1046,"code":4466,"language":1048,"meta":34,"style":34},"func TestWithParams(t *testing.T) {\n    params := edamametesting.NewParamBuilder().\n        Set(\"id\", 123).\n        Set(\"status\", \"active\").\n        Set(\"limit\", 10).\n        Build()\n\n    var Filtered = edamame.NewQueryStatement(\"filtered\", \"Filtered query\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{{Field: \"status\", Operator: \"=\", Param: \"status\"}},\n    })\n\n    // Use in tests\n    users, err := exec.ExecQuery(ctx, Filtered, params)\n}\n",[1050,4468,4469,4492,4509,4525,4539,4555,4562,4566,4601,4641,4645,4649,4654,4685],{"__ignoreMap":34},[1053,4470,4471,4473,4476,4478,4480,4482,4484,4486,4488,4490],{"class":1055,"line":9},[1053,4472,1956],{"class":1064},[1053,4474,4475],{"class":1087}," TestWithParams",[1053,4477,1090],{"class":1068},[1053,4479,3769],{"class":1572},[1053,4481,3773],{"class":3772},[1053,4483,3288],{"class":1105},[1053,4485,1084],{"class":1068},[1053,4487,3780],{"class":1105},[1053,4489,3783],{"class":1068},[1053,4491,1614],{"class":1068},[1053,4493,4494,4497,4499,4501,4503,4506],{"class":1055,"line":19},[1053,4495,4496],{"class":1074},"    params",[1053,4498,1386],{"class":1074},[1053,4500,3795],{"class":1074},[1053,4502,1084],{"class":1068},[1053,4504,4505],{"class":1087},"NewParamBuilder",[1053,4507,4508],{"class":1068},"().\n",[1053,4510,4511,4514,4516,4518,4520,4522],{"class":1055,"line":40},[1053,4512,4513],{"class":1087},"        Set",[1053,4515,1090],{"class":1068},[1053,4517,1480],{"class":1093},[1053,4519,1097],{"class":1068},[1053,4521,1485],{"class":1259},[1053,4523,4524],{"class":1068},").\n",[1053,4526,4527,4529,4531,4533,4535,4537],{"class":1055,"line":674},[1053,4528,4513],{"class":1087},[1053,4530,1090],{"class":1068},[1053,4532,1427],{"class":1093},[1053,4534,1097],{"class":1068},[1053,4536,1432],{"class":1093},[1053,4538,4524],{"class":1068},[1053,4540,4541,4543,4545,4548,4550,4553],{"class":1055,"line":1121},[1053,4542,4513],{"class":1087},[1053,4544,1090],{"class":1068},[1053,4546,4547],{"class":1093},"\"limit\"",[1053,4549,1097],{"class":1068},[1053,4551,4552],{"class":1259}," 10",[1053,4554,4524],{"class":1068},[1053,4556,4557,4560],{"class":1055,"line":1156},[1053,4558,4559],{"class":1087},"        Build",[1053,4561,2015],{"class":1068},[1053,4563,4564],{"class":1055,"line":1208},[1053,4565,1118],{"emptyLinePlaceholder":1117},[1053,4567,4568,4570,4573,4575,4577,4579,4581,4583,4586,4588,4591,4593,4595,4597,4599],{"class":1055,"line":1246},[1053,4569,3859],{"class":1064},[1053,4571,4572],{"class":1074}," Filtered",[1053,4574,1078],{"class":1074},[1053,4576,1081],{"class":1074},[1053,4578,1084],{"class":1068},[1053,4580,609],{"class":1087},[1053,4582,1090],{"class":1068},[1053,4584,4585],{"class":1093},"\"filtered\"",[1053,4587,1097],{"class":1068},[1053,4589,4590],{"class":1093}," \"Filtered query\"",[1053,4592,1097],{"class":1068},[1053,4594,1081],{"class":1105},[1053,4596,1084],{"class":1068},[1053,4598,144],{"class":1105},[1053,4600,1153],{"class":1068},[1053,4602,4603,4605,4607,4609,4611,4613,4615,4617,4619,4621,4623,4625,4627,4629,4631,4633,4635,4637,4639],{"class":1055,"line":1266},[1053,4604,1160],{"class":1159},[1053,4606,1163],{"class":1068},[1053,4608,1216],{"class":1068},[1053,4610,960],{"class":1105},[1053,4612,1084],{"class":1068},[1053,4614,831],{"class":1105},[1053,4616,1175],{"class":1068},[1053,4618,1178],{"class":1159},[1053,4620,1163],{"class":1068},[1053,4622,1183],{"class":1093},[1053,4624,1097],{"class":1068},[1053,4626,1188],{"class":1159},[1053,4628,1163],{"class":1068},[1053,4630,1193],{"class":1093},[1053,4632,1097],{"class":1068},[1053,4634,1198],{"class":1159},[1053,4636,1163],{"class":1068},[1053,4638,1183],{"class":1093},[1053,4640,1205],{"class":1068},[1053,4642,4643],{"class":1055,"line":1272},[1053,4644,1269],{"class":1068},[1053,4646,4647],{"class":1055,"line":1277},[1053,4648,1118],{"emptyLinePlaceholder":1117},[1053,4650,4651],{"class":1055,"line":1311},[1053,4652,4653],{"class":1058},"    // Use in tests\n",[1053,4655,4656,4658,4660,4662,4664,4666,4668,4670,4672,4674,4676,4678,4680,4683],{"class":1055,"line":1353},[1053,4657,2089],{"class":1074},[1053,4659,1097],{"class":1068},[1053,4661,2611],{"class":1074},[1053,4663,1386],{"class":1074},[1053,4665,1389],{"class":1074},[1053,4667,1084],{"class":1068},[1053,4669,1394],{"class":1087},[1053,4671,1090],{"class":1068},[1053,4673,1399],{"class":1074},[1053,4675,1097],{"class":1068},[1053,4677,4572],{"class":1074},[1053,4679,1097],{"class":1068},[1053,4681,4682],{"class":1074}," params",[1053,4684,1361],{"class":1068},[1053,4686,4687],{"class":1055,"line":1358},[1053,4688,1663],{"class":1068},[1036,4690,484],{"id":4691},"unit-testing-without-database",[962,4693,4694],{},"Test statement creation and specs without a database:",[1044,4696,4698],{"className":1046,"code":4697,"language":1048,"meta":34,"style":34},"func TestStatements(t *testing.T) {\n    // nil db is valid for testing statements\n    exec, err := edamame.New[User](nil, \"users\", postgres.New())\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // Define statements\n    var QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all\", edamame.QuerySpec{})\n\n    var ByStatus = edamame.NewQueryStatement(\"by-status\", \"Find by status\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"status\", Operator: \"=\", Param: \"status\"},\n        },\n    })\n\n    // Test statement metadata\n    if ByStatus.Name() != \"by-status\" {\n        t.Errorf(\"expected name 'by-status', got %q\", ByStatus.Name())\n    }\n\n    // Test params derived correctly\n    params := ByStatus.Params()\n    if len(params) != 1 || params[0].Name != \"status\" {\n        t.Error(\"expected 1 param named 'status'\")\n    }\n\n    // Test builder creation\n    q, err := exec.Query(ByStatus)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // Test rendering\n    result, err := q.Render()\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    if result.SQL == \"\" {\n        t.Error(\"empty SQL rendered\")\n    }\n}\n",[1050,4699,4700,4723,4728,4766,4778,4794,4798,4802,4807,4840,4844,4876,4892,4920,4924,4928,4932,4937,4956,4983,4987,4991,4996,5011,5047,5062,5066,5070,5075,5097,5109,5123,5127,5131,5136,5154,5166,5180,5184,5188,5206,5221,5225],{"__ignoreMap":34},[1053,4701,4702,4704,4707,4709,4711,4713,4715,4717,4719,4721],{"class":1055,"line":9},[1053,4703,1956],{"class":1064},[1053,4705,4706],{"class":1087}," TestStatements",[1053,4708,1090],{"class":1068},[1053,4710,3769],{"class":1572},[1053,4712,3773],{"class":3772},[1053,4714,3288],{"class":1105},[1053,4716,1084],{"class":1068},[1053,4718,3780],{"class":1105},[1053,4720,3783],{"class":1068},[1053,4722,1614],{"class":1068},[1053,4724,4725],{"class":1055,"line":19},[1053,4726,4727],{"class":1058},"    // nil db is valid for testing statements\n",[1053,4729,4730,4732,4734,4736,4738,4740,4742,4744,4746,4748,4750,4752,4754,4756,4758,4760,4762,4764],{"class":1055,"line":40},[1053,4731,2032],{"class":1074},[1053,4733,1097],{"class":1068},[1053,4735,2611],{"class":1074},[1053,4737,1386],{"class":1074},[1053,4739,1081],{"class":1074},[1053,4741,1084],{"class":1068},[1053,4743,600],{"class":1087},[1053,4745,1412],{"class":1068},[1053,4747,2049],{"class":1105},[1053,4749,2052],{"class":1068},[1053,4751,3831],{"class":1064},[1053,4753,1097],{"class":1068},[1053,4755,2060],{"class":1093},[1053,4757,1097],{"class":1068},[1053,4759,2065],{"class":1074},[1053,4761,1084],{"class":1068},[1053,4763,600],{"class":1087},[1053,4765,2072],{"class":1068},[1053,4767,4768,4770,4772,4774,4776],{"class":1055,"line":674},[1053,4769,4038],{"class":3772},[1053,4771,2611],{"class":1074},[1053,4773,4050],{"class":3772},[1053,4775,2115],{"class":1064},[1053,4777,1614],{"class":1068},[1053,4779,4780,4782,4784,4787,4789,4792],{"class":1055,"line":1121},[1053,4781,4059],{"class":1074},[1053,4783,1084],{"class":1068},[1053,4785,4786],{"class":1087},"Fatal",[1053,4788,1090],{"class":1068},[1053,4790,4791],{"class":1074},"err",[1053,4793,1361],{"class":1068},[1053,4795,4796],{"class":1055,"line":1156},[1053,4797,4088],{"class":1068},[1053,4799,4800],{"class":1055,"line":1208},[1053,4801,1118],{"emptyLinePlaceholder":1117},[1053,4803,4804],{"class":1055,"line":1246},[1053,4805,4806],{"class":1058},"    // Define statements\n",[1053,4808,4809,4811,4813,4815,4817,4819,4821,4823,4825,4827,4830,4832,4834,4836,4838],{"class":1055,"line":1266},[1053,4810,3859],{"class":1064},[1053,4812,2110],{"class":1074},[1053,4814,1078],{"class":1074},[1053,4816,1081],{"class":1074},[1053,4818,1084],{"class":1068},[1053,4820,609],{"class":1087},[1053,4822,1090],{"class":1068},[1053,4824,1094],{"class":1093},[1053,4826,1097],{"class":1068},[1053,4828,4829],{"class":1093}," \"Query all\"",[1053,4831,1097],{"class":1068},[1053,4833,1081],{"class":1105},[1053,4835,1084],{"class":1068},[1053,4837,144],{"class":1105},[1053,4839,1112],{"class":1068},[1053,4841,4842],{"class":1055,"line":1272},[1053,4843,1118],{"emptyLinePlaceholder":1117},[1053,4845,4846,4848,4850,4852,4854,4856,4858,4860,4862,4864,4866,4868,4870,4872,4874],{"class":1055,"line":1277},[1053,4847,3859],{"class":1064},[1053,4849,1404],{"class":1074},[1053,4851,1078],{"class":1074},[1053,4853,1081],{"class":1074},[1053,4855,1084],{"class":1068},[1053,4857,609],{"class":1087},[1053,4859,1090],{"class":1068},[1053,4861,1137],{"class":1093},[1053,4863,1097],{"class":1068},[1053,4865,3878],{"class":1093},[1053,4867,1097],{"class":1068},[1053,4869,1081],{"class":1105},[1053,4871,1084],{"class":1068},[1053,4873,144],{"class":1105},[1053,4875,1153],{"class":1068},[1053,4877,4878,4880,4882,4884,4886,4888,4890],{"class":1055,"line":1311},[1053,4879,1160],{"class":1159},[1053,4881,1163],{"class":1068},[1053,4883,1216],{"class":1068},[1053,4885,960],{"class":1105},[1053,4887,1084],{"class":1068},[1053,4889,831],{"class":1105},[1053,4891,1153],{"class":1068},[1053,4893,4894,4896,4898,4900,4902,4904,4906,4908,4910,4912,4914,4916,4918],{"class":1055,"line":1353},[1053,4895,1904],{"class":1068},[1053,4897,1178],{"class":1159},[1053,4899,1163],{"class":1068},[1053,4901,1183],{"class":1093},[1053,4903,1097],{"class":1068},[1053,4905,1188],{"class":1159},[1053,4907,1163],{"class":1068},[1053,4909,1193],{"class":1093},[1053,4911,1097],{"class":1068},[1053,4913,1198],{"class":1159},[1053,4915,1163],{"class":1068},[1053,4917,1183],{"class":1093},[1053,4919,1929],{"class":1068},[1053,4921,4922],{"class":1055,"line":1358},[1053,4923,1935],{"class":1068},[1053,4925,4926],{"class":1055,"line":1364},[1053,4927,1269],{"class":1068},[1053,4929,4930],{"class":1055,"line":1369},[1053,4931,1118],{"emptyLinePlaceholder":1117},[1053,4933,4934],{"class":1055,"line":1375},[1053,4935,4936],{"class":1058},"    // Test statement metadata\n",[1053,4938,4939,4941,4943,4945,4947,4949,4951,4954],{"class":1055,"line":1438},[1053,4940,4038],{"class":3772},[1053,4942,1404],{"class":1074},[1053,4944,1084],{"class":1068},[1053,4946,2321],{"class":1087},[1053,4948,1962],{"class":1068},[1053,4950,4050],{"class":3772},[1053,4952,4953],{"class":1093}," \"by-status\"",[1053,4955,1614],{"class":1068},[1053,4957,4958,4960,4962,4964,4966,4969,4971,4973,4975,4977,4979,4981],{"class":1055,"line":1666},[1053,4959,4059],{"class":1074},[1053,4961,1084],{"class":1068},[1053,4963,4064],{"class":1087},[1053,4965,1090],{"class":1068},[1053,4967,4968],{"class":1093},"\"expected name 'by-status', got ",[1053,4970,4142],{"class":2273},[1053,4972,2270],{"class":1093},[1053,4974,1097],{"class":1068},[1053,4976,1404],{"class":1074},[1053,4978,1084],{"class":1068},[1053,4980,2321],{"class":1087},[1053,4982,2072],{"class":1068},[1053,4984,4985],{"class":1055,"line":1671},[1053,4986,4088],{"class":1068},[1053,4988,4989],{"class":1055,"line":1677},[1053,4990,1118],{"emptyLinePlaceholder":1117},[1053,4992,4993],{"class":1055,"line":1684},[1053,4994,4995],{"class":1058},"    // Test params derived correctly\n",[1053,4997,4998,5000,5002,5004,5006,5009],{"class":1055,"line":1715},[1053,4999,4496],{"class":1074},[1053,5001,1386],{"class":1074},[1053,5003,1404],{"class":1074},[1053,5005,1084],{"class":1068},[1053,5007,5008],{"class":1087},"Params",[1053,5010,2015],{"class":1068},[1053,5012,5013,5015,5017,5019,5022,5024,5026,5028,5031,5033,5035,5037,5039,5041,5043,5045],{"class":1055,"line":1720},[1053,5014,4038],{"class":3772},[1053,5016,2306],{"class":2305},[1053,5018,1090],{"class":1068},[1053,5020,5021],{"class":1074},"params",[1053,5023,3783],{"class":1068},[1053,5025,4050],{"class":3772},[1053,5027,2164],{"class":1259},[1053,5029,5030],{"class":3772}," ||",[1053,5032,4682],{"class":1074},[1053,5034,1412],{"class":1068},[1053,5036,4406],{"class":1259},[1053,5038,4409],{"class":1068},[1053,5040,2321],{"class":1074},[1053,5042,4050],{"class":3772},[1053,5044,1183],{"class":1093},[1053,5046,1614],{"class":1068},[1053,5048,5049,5051,5053,5055,5057,5060],{"class":1055,"line":1751},[1053,5050,4059],{"class":1074},[1053,5052,1084],{"class":1068},[1053,5054,4363],{"class":1087},[1053,5056,1090],{"class":1068},[1053,5058,5059],{"class":1093},"\"expected 1 param named 'status'\"",[1053,5061,1361],{"class":1068},[1053,5063,5064],{"class":1055,"line":1792},[1053,5065,4088],{"class":1068},[1053,5067,5068],{"class":1055,"line":1797},[1053,5069,1118],{"emptyLinePlaceholder":1117},[1053,5071,5072],{"class":1055,"line":1802},[1053,5073,5074],{"class":1058},"    // Test builder creation\n",[1053,5076,5077,5079,5081,5083,5085,5087,5089,5091,5093,5095],{"class":1055,"line":1845},[1053,5078,3946],{"class":1074},[1053,5080,1097],{"class":1068},[1053,5082,2611],{"class":1074},[1053,5084,1386],{"class":1074},[1053,5086,1389],{"class":1074},[1053,5088,1084],{"class":1068},[1053,5090,671],{"class":1087},[1053,5092,1090],{"class":1068},[1053,5094,3963],{"class":1074},[1053,5096,1361],{"class":1068},[1053,5098,5099,5101,5103,5105,5107],{"class":1055,"line":1850},[1053,5100,4038],{"class":3772},[1053,5102,2611],{"class":1074},[1053,5104,4050],{"class":3772},[1053,5106,2115],{"class":1064},[1053,5108,1614],{"class":1068},[1053,5110,5111,5113,5115,5117,5119,5121],{"class":1055,"line":1884},[1053,5112,4059],{"class":1074},[1053,5114,1084],{"class":1068},[1053,5116,4786],{"class":1087},[1053,5118,1090],{"class":1068},[1053,5120,4791],{"class":1074},[1053,5122,1361],{"class":1068},[1053,5124,5125],{"class":1055,"line":1901},[1053,5126,4088],{"class":1068},[1053,5128,5129],{"class":1055,"line":1932},[1053,5130,1118],{"emptyLinePlaceholder":1117},[1053,5132,5133],{"class":1055,"line":1938},[1053,5134,5135],{"class":1058},"    // Test rendering\n",[1053,5137,5138,5140,5142,5144,5146,5148,5150,5152],{"class":1055,"line":1943},[1053,5139,3970],{"class":1074},[1053,5141,1097],{"class":1068},[1053,5143,2611],{"class":1074},[1053,5145,1386],{"class":1074},[1053,5147,3979],{"class":1074},[1053,5149,1084],{"class":1068},[1053,5151,3984],{"class":1087},[1053,5153,2015],{"class":1068},[1053,5155,5156,5158,5160,5162,5164],{"class":1055,"line":1948},[1053,5157,4038],{"class":3772},[1053,5159,2611],{"class":1074},[1053,5161,4050],{"class":3772},[1053,5163,2115],{"class":1064},[1053,5165,1614],{"class":1068},[1053,5167,5168,5170,5172,5174,5176,5178],{"class":1055,"line":1953},[1053,5169,4059],{"class":1074},[1053,5171,1084],{"class":1068},[1053,5173,4786],{"class":1087},[1053,5175,1090],{"class":1068},[1053,5177,4791],{"class":1074},[1053,5179,1361],{"class":1068},[1053,5181,5182],{"class":1055,"line":1967},[1053,5183,4088],{"class":1068},[1053,5185,5186],{"class":1055,"line":1999},[1053,5187,1118],{"emptyLinePlaceholder":1117},[1053,5189,5190,5192,5194,5196,5198,5201,5204],{"class":1055,"line":2018},[1053,5191,4038],{"class":3772},[1053,5193,4013],{"class":1074},[1053,5195,1084],{"class":1068},[1053,5197,4018],{"class":1074},[1053,5199,5200],{"class":3772}," ==",[1053,5202,5203],{"class":1093}," \"\"",[1053,5205,1614],{"class":1068},[1053,5207,5208,5210,5212,5214,5216,5219],{"class":1055,"line":2023},[1053,5209,4059],{"class":1074},[1053,5211,1084],{"class":1068},[1053,5213,4363],{"class":1087},[1053,5215,1090],{"class":1068},[1053,5217,5218],{"class":1093},"\"empty SQL rendered\"",[1053,5220,1361],{"class":1068},[1053,5222,5223],{"class":1055,"line":2029},[1053,5224,4088],{"class":1068},[1053,5226,5227],{"class":1055,"line":2075},[1053,5228,1663],{"class":1068},[1036,5230,489],{"id":5231},"testing-sql-rendering",[962,5233,5234],{},"Verify generated SQL without executing:",[1044,5236,5238],{"className":1046,"code":5237,"language":1048,"meta":34,"style":34},"func TestSQLRendering(t *testing.T) {\n    exec, _ := edamame.New[User](nil, \"users\", postgres.New())\n\n    var Adults = edamame.NewQueryStatement(\"adults\", \"Find adults\", edamame.QuerySpec{\n        Where: []edamame.ConditionSpec{\n            {Field: \"age\", Operator: \">=\", Param: \"min_age\"},\n        },\n        OrderBy: []edamame.OrderBySpec{\n            {Field: \"name\", Direction: \"asc\"},\n        },\n    })\n\n    q, _ := exec.Query(Adults)\n    result, err := q.Render()\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // Verify SQL structure\n    if !strings.Contains(result.SQL, \"WHERE\") {\n        t.Error(\"SQL missing WHERE clause\")\n    }\n    if !strings.Contains(result.SQL, \"ORDER BY\") {\n        t.Error(\"SQL missing ORDER BY clause\")\n    }\n}\n",[1050,5239,5240,5263,5301,5305,5340,5356,5387,5391,5407,5429,5433,5437,5441,5464,5482,5494,5508,5512,5516,5521,5554,5569,5573,5602,5617,5621],{"__ignoreMap":34},[1053,5241,5242,5244,5247,5249,5251,5253,5255,5257,5259,5261],{"class":1055,"line":9},[1053,5243,1956],{"class":1064},[1053,5245,5246],{"class":1087}," TestSQLRendering",[1053,5248,1090],{"class":1068},[1053,5250,3769],{"class":1572},[1053,5252,3773],{"class":3772},[1053,5254,3288],{"class":1105},[1053,5256,1084],{"class":1068},[1053,5258,3780],{"class":1105},[1053,5260,3783],{"class":1068},[1053,5262,1614],{"class":1068},[1053,5264,5265,5267,5269,5271,5273,5275,5277,5279,5281,5283,5285,5287,5289,5291,5293,5295,5297,5299],{"class":1055,"line":19},[1053,5266,2032],{"class":1074},[1053,5268,1097],{"class":1068},[1053,5270,1383],{"class":1074},[1053,5272,1386],{"class":1074},[1053,5274,1081],{"class":1074},[1053,5276,1084],{"class":1068},[1053,5278,600],{"class":1087},[1053,5280,1412],{"class":1068},[1053,5282,2049],{"class":1105},[1053,5284,2052],{"class":1068},[1053,5286,3831],{"class":1064},[1053,5288,1097],{"class":1068},[1053,5290,2060],{"class":1093},[1053,5292,1097],{"class":1068},[1053,5294,2065],{"class":1074},[1053,5296,1084],{"class":1068},[1053,5298,600],{"class":1087},[1053,5300,2072],{"class":1068},[1053,5302,5303],{"class":1055,"line":40},[1053,5304,1118],{"emptyLinePlaceholder":1117},[1053,5306,5307,5309,5312,5314,5316,5318,5320,5322,5325,5327,5330,5332,5334,5336,5338],{"class":1055,"line":674},[1053,5308,3859],{"class":1064},[1053,5310,5311],{"class":1074}," Adults",[1053,5313,1078],{"class":1074},[1053,5315,1081],{"class":1074},[1053,5317,1084],{"class":1068},[1053,5319,609],{"class":1087},[1053,5321,1090],{"class":1068},[1053,5323,5324],{"class":1093},"\"adults\"",[1053,5326,1097],{"class":1068},[1053,5328,5329],{"class":1093}," \"Find adults\"",[1053,5331,1097],{"class":1068},[1053,5333,1081],{"class":1105},[1053,5335,1084],{"class":1068},[1053,5337,144],{"class":1105},[1053,5339,1153],{"class":1068},[1053,5341,5342,5344,5346,5348,5350,5352,5354],{"class":1055,"line":1121},[1053,5343,1160],{"class":1159},[1053,5345,1163],{"class":1068},[1053,5347,1216],{"class":1068},[1053,5349,960],{"class":1105},[1053,5351,1084],{"class":1068},[1053,5353,831],{"class":1105},[1053,5355,1153],{"class":1068},[1053,5357,5358,5360,5362,5364,5367,5369,5371,5373,5376,5378,5380,5382,5385],{"class":1055,"line":1156},[1053,5359,1904],{"class":1068},[1053,5361,1178],{"class":1159},[1053,5363,1163],{"class":1068},[1053,5365,5366],{"class":1093}," \"age\"",[1053,5368,1097],{"class":1068},[1053,5370,1188],{"class":1159},[1053,5372,1163],{"class":1068},[1053,5374,5375],{"class":1093}," \">=\"",[1053,5377,1097],{"class":1068},[1053,5379,1198],{"class":1159},[1053,5381,1163],{"class":1068},[1053,5383,5384],{"class":1093}," \"min_age\"",[1053,5386,1929],{"class":1068},[1053,5388,5389],{"class":1055,"line":1208},[1053,5390,1935],{"class":1068},[1053,5392,5393,5395,5397,5399,5401,5403,5405],{"class":1055,"line":1246},[1053,5394,1211],{"class":1159},[1053,5396,1163],{"class":1068},[1053,5398,1216],{"class":1068},[1053,5400,960],{"class":1105},[1053,5402,1084],{"class":1068},[1053,5404,841],{"class":1105},[1053,5406,1153],{"class":1068},[1053,5408,5409,5411,5413,5415,5418,5420,5422,5424,5427],{"class":1055,"line":1266},[1053,5410,1904],{"class":1068},[1053,5412,1178],{"class":1159},[1053,5414,1163],{"class":1068},[1053,5416,5417],{"class":1093}," \"name\"",[1053,5419,1097],{"class":1068},[1053,5421,1236],{"class":1159},[1053,5423,1163],{"class":1068},[1053,5425,5426],{"class":1093}," \"asc\"",[1053,5428,1929],{"class":1068},[1053,5430,5431],{"class":1055,"line":1272},[1053,5432,1935],{"class":1068},[1053,5434,5435],{"class":1055,"line":1277},[1053,5436,1269],{"class":1068},[1053,5438,5439],{"class":1055,"line":1311},[1053,5440,1118],{"emptyLinePlaceholder":1117},[1053,5442,5443,5445,5447,5449,5451,5453,5455,5457,5459,5462],{"class":1055,"line":1353},[1053,5444,3946],{"class":1074},[1053,5446,1097],{"class":1068},[1053,5448,1383],{"class":1074},[1053,5450,1386],{"class":1074},[1053,5452,1389],{"class":1074},[1053,5454,1084],{"class":1068},[1053,5456,671],{"class":1087},[1053,5458,1090],{"class":1068},[1053,5460,5461],{"class":1074},"Adults",[1053,5463,1361],{"class":1068},[1053,5465,5466,5468,5470,5472,5474,5476,5478,5480],{"class":1055,"line":1358},[1053,5467,3970],{"class":1074},[1053,5469,1097],{"class":1068},[1053,5471,2611],{"class":1074},[1053,5473,1386],{"class":1074},[1053,5475,3979],{"class":1074},[1053,5477,1084],{"class":1068},[1053,5479,3984],{"class":1087},[1053,5481,2015],{"class":1068},[1053,5483,5484,5486,5488,5490,5492],{"class":1055,"line":1364},[1053,5485,4038],{"class":3772},[1053,5487,2611],{"class":1074},[1053,5489,4050],{"class":3772},[1053,5491,2115],{"class":1064},[1053,5493,1614],{"class":1068},[1053,5495,5496,5498,5500,5502,5504,5506],{"class":1055,"line":1369},[1053,5497,4059],{"class":1074},[1053,5499,1084],{"class":1068},[1053,5501,4786],{"class":1087},[1053,5503,1090],{"class":1068},[1053,5505,4791],{"class":1074},[1053,5507,1361],{"class":1068},[1053,5509,5510],{"class":1055,"line":1375},[1053,5511,4088],{"class":1068},[1053,5513,5514],{"class":1055,"line":1438},[1053,5515,1118],{"emptyLinePlaceholder":1117},[1053,5517,5518],{"class":1055,"line":1666},[1053,5519,5520],{"class":1058},"    // Verify SQL structure\n",[1053,5522,5523,5525,5528,5531,5533,5536,5538,5541,5543,5545,5547,5550,5552],{"class":1055,"line":1671},[1053,5524,4038],{"class":3772},[1053,5526,5527],{"class":3772}," !",[1053,5529,5530],{"class":1074},"strings",[1053,5532,1084],{"class":1068},[1053,5534,5535],{"class":1087},"Contains",[1053,5537,1090],{"class":1068},[1053,5539,5540],{"class":1074},"result",[1053,5542,1084],{"class":1068},[1053,5544,4018],{"class":1074},[1053,5546,1097],{"class":1068},[1053,5548,5549],{"class":1093}," \"WHERE\"",[1053,5551,3783],{"class":1068},[1053,5553,1614],{"class":1068},[1053,5555,5556,5558,5560,5562,5564,5567],{"class":1055,"line":1677},[1053,5557,4059],{"class":1074},[1053,5559,1084],{"class":1068},[1053,5561,4363],{"class":1087},[1053,5563,1090],{"class":1068},[1053,5565,5566],{"class":1093},"\"SQL missing WHERE clause\"",[1053,5568,1361],{"class":1068},[1053,5570,5571],{"class":1055,"line":1684},[1053,5572,4088],{"class":1068},[1053,5574,5575,5577,5579,5581,5583,5585,5587,5589,5591,5593,5595,5598,5600],{"class":1055,"line":1715},[1053,5576,4038],{"class":3772},[1053,5578,5527],{"class":3772},[1053,5580,5530],{"class":1074},[1053,5582,1084],{"class":1068},[1053,5584,5535],{"class":1087},[1053,5586,1090],{"class":1068},[1053,5588,5540],{"class":1074},[1053,5590,1084],{"class":1068},[1053,5592,4018],{"class":1074},[1053,5594,1097],{"class":1068},[1053,5596,5597],{"class":1093}," \"ORDER BY\"",[1053,5599,3783],{"class":1068},[1053,5601,1614],{"class":1068},[1053,5603,5604,5606,5608,5610,5612,5615],{"class":1055,"line":1720},[1053,5605,4059],{"class":1074},[1053,5607,1084],{"class":1068},[1053,5609,4363],{"class":1087},[1053,5611,1090],{"class":1068},[1053,5613,5614],{"class":1093},"\"SQL missing ORDER BY clause\"",[1053,5616,1361],{"class":1068},[1053,5618,5619],{"class":1055,"line":1751},[1053,5620,4088],{"class":1068},[1053,5622,5623],{"class":1055,"line":1792},[1053,5624,1663],{"class":1068},[1036,5626,494],{"id":5627},"integration-testing-with-testcontainers",[962,5629,5630],{},"For database integration tests, use testcontainers:",[1044,5632,5634],{"className":1046,"code":5633,"language":1048,"meta":34,"style":34},"//go:build integration\n\npackage integration\n\nimport (\n    \"context\"\n    \"testing\"\n\n    \"github.com/testcontainers/testcontainers-go\"\n    \"github.com/testcontainers/testcontainers-go/wait\"\n)\n\nfunc TestWithPostgres(t *testing.T) {\n    ctx := context.Background()\n\n    // Start PostgreSQL container\n    req := testcontainers.ContainerRequest{\n        Image:        \"postgres:16-alpine\",\n        ExposedPorts: []string{\"5432/tcp\"},\n        WaitingFor:   wait.ForLog(\"database system is ready to accept connections\"),\n        Env: map[string]string{\n            \"POSTGRES_USER\":     \"test\",\n            \"POSTGRES_PASSWORD\": \"test\",\n            \"POSTGRES_DB\":       \"testdb\",\n        },\n    }\n\n    container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{\n        ContainerRequest: req,\n        Started:          true,\n    })\n    if err != nil {\n        t.Fatal(err)\n    }\n    defer container.Terminate(ctx)\n\n    // Get connection details\n    host, _ := container.Host(ctx)\n    port, _ := container.MappedPort(ctx, \"5432\")\n\n    // Connect and test\n    dsn := fmt.Sprintf(\"host=%s port=%s user=test password=test dbname=testdb sslmode=disable\", host, port.Port())\n    db, err := sqlx.Connect(\"postgres\", dsn)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    // Create table\n    db.ExecContext(ctx, `CREATE TABLE users (\n        id SERIAL PRIMARY KEY,\n        email TEXT NOT NULL UNIQUE,\n        name TEXT,\n        age INTEGER\n    )`)\n\n    // Test executor\n    exec, _ := edamame.New[User](db, \"users\", postgres.New())\n\n    age := 25\n    user := &User{Email: \"test@example.com\", Name: \"Test\", Age: &age}\n    inserted, err := exec.ExecInsert(ctx, user)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    if inserted.ID == 0 {\n        t.Error(\"expected non-zero ID\")\n    }\n}\n",[1050,5635,5636,5641,5645,5652,5656,5662,5666,5671,5675,5680,5685,5689,5693,5716,5730,5734,5739,5756,5769,5787,5809,5828,5840,5852,5864,5868,5872,5876,5909,5921,5933,5937,5949,5963,5967,5985,5989,5994,6018,6047,6051,6056,6103,6130,6142,6156,6160,6164,6169,6187,6192,6197,6203,6209,6217,6222,6228,6267,6272,6283,6328,6357,6370,6385,6390,6395,6415,6431,6436],{"__ignoreMap":34},[1053,5637,5638],{"class":1055,"line":9},[1053,5639,5640],{"class":1058},"//go:build integration\n",[1053,5642,5643],{"class":1055,"line":19},[1053,5644,1118],{"emptyLinePlaceholder":1117},[1053,5646,5647,5649],{"class":1055,"line":40},[1053,5648,1533],{"class":1064},[1053,5650,5651],{"class":1105}," integration\n",[1053,5653,5654],{"class":1055,"line":674},[1053,5655,1118],{"emptyLinePlaceholder":1117},[1053,5657,5658,5660],{"class":1055,"line":1121},[1053,5659,1545],{"class":1064},[1053,5661,1069],{"class":1548},[1053,5663,5664],{"class":1055,"line":1156},[1053,5665,1553],{"class":1093},[1053,5667,5668],{"class":1055,"line":1208},[1053,5669,5670],{"class":1093},"    \"testing\"\n",[1053,5672,5673],{"class":1055,"line":1246},[1053,5674,1118],{"emptyLinePlaceholder":1117},[1053,5676,5677],{"class":1055,"line":1266},[1053,5678,5679],{"class":1093},"    \"github.com/testcontainers/testcontainers-go\"\n",[1053,5681,5682],{"class":1055,"line":1272},[1053,5683,5684],{"class":1093},"    \"github.com/testcontainers/testcontainers-go/wait\"\n",[1053,5686,5687],{"class":1055,"line":1277},[1053,5688,1361],{"class":1548},[1053,5690,5691],{"class":1055,"line":1311},[1053,5692,1118],{"emptyLinePlaceholder":1117},[1053,5694,5695,5697,5700,5702,5704,5706,5708,5710,5712,5714],{"class":1055,"line":1353},[1053,5696,1956],{"class":1064},[1053,5698,5699],{"class":1087}," TestWithPostgres",[1053,5701,1090],{"class":1068},[1053,5703,3769],{"class":1572},[1053,5705,3773],{"class":3772},[1053,5707,3288],{"class":1105},[1053,5709,1084],{"class":1068},[1053,5711,3780],{"class":1105},[1053,5713,3783],{"class":1068},[1053,5715,1614],{"class":1068},[1053,5717,5718,5720,5722,5724,5726,5728],{"class":1055,"line":1358},[1053,5719,2002],{"class":1074},[1053,5721,1386],{"class":1074},[1053,5723,2007],{"class":1074},[1053,5725,1084],{"class":1068},[1053,5727,2012],{"class":1087},[1053,5729,2015],{"class":1068},[1053,5731,5732],{"class":1055,"line":1364},[1053,5733,1118],{"emptyLinePlaceholder":1117},[1053,5735,5736],{"class":1055,"line":1369},[1053,5737,5738],{"class":1058},"    // Start PostgreSQL container\n",[1053,5740,5741,5744,5746,5749,5751,5754],{"class":1055,"line":1375},[1053,5742,5743],{"class":1074},"    req",[1053,5745,1386],{"class":1074},[1053,5747,5748],{"class":1105}," testcontainers",[1053,5750,1084],{"class":1068},[1053,5752,5753],{"class":1105},"ContainerRequest",[1053,5755,1153],{"class":1068},[1053,5757,5758,5761,5763,5766],{"class":1055,"line":1438},[1053,5759,5760],{"class":1159},"        Image",[1053,5762,1163],{"class":1068},[1053,5764,5765],{"class":1093},"        \"postgres:16-alpine\"",[1053,5767,5768],{"class":1068},",\n",[1053,5770,5771,5774,5776,5778,5780,5782,5785],{"class":1055,"line":1666},[1053,5772,5773],{"class":1159},"        ExposedPorts",[1053,5775,1163],{"class":1068},[1053,5777,1216],{"class":1068},[1053,5779,1415],{"class":1105},[1053,5781,1424],{"class":1068},[1053,5783,5784],{"class":1093},"\"5432/tcp\"",[1053,5786,1929],{"class":1068},[1053,5788,5789,5792,5794,5797,5799,5802,5804,5807],{"class":1055,"line":1671},[1053,5790,5791],{"class":1159},"        WaitingFor",[1053,5793,1163],{"class":1068},[1053,5795,5796],{"class":1074},"   wait",[1053,5798,1084],{"class":1068},[1053,5800,5801],{"class":1087},"ForLog",[1053,5803,1090],{"class":1068},[1053,5805,5806],{"class":1093},"\"database system is ready to accept connections\"",[1053,5808,1263],{"class":1068},[1053,5810,5811,5814,5816,5818,5820,5822,5824,5826],{"class":1055,"line":1677},[1053,5812,5813],{"class":1159},"        Env",[1053,5815,1163],{"class":1068},[1053,5817,1409],{"class":1064},[1053,5819,1412],{"class":1068},[1053,5821,1415],{"class":1105},[1053,5823,1418],{"class":1068},[1053,5825,1415],{"class":1105},[1053,5827,1153],{"class":1068},[1053,5829,5830,5833,5835,5838],{"class":1055,"line":1684},[1053,5831,5832],{"class":1093},"            \"POSTGRES_USER\"",[1053,5834,1163],{"class":1068},[1053,5836,5837],{"class":1093},"     \"test\"",[1053,5839,5768],{"class":1068},[1053,5841,5842,5845,5847,5850],{"class":1055,"line":1715},[1053,5843,5844],{"class":1093},"            \"POSTGRES_PASSWORD\"",[1053,5846,1163],{"class":1068},[1053,5848,5849],{"class":1093}," \"test\"",[1053,5851,5768],{"class":1068},[1053,5853,5854,5857,5859,5862],{"class":1055,"line":1720},[1053,5855,5856],{"class":1093},"            \"POSTGRES_DB\"",[1053,5858,1163],{"class":1068},[1053,5860,5861],{"class":1093},"       \"testdb\"",[1053,5863,5768],{"class":1068},[1053,5865,5866],{"class":1055,"line":1751},[1053,5867,1935],{"class":1068},[1053,5869,5870],{"class":1055,"line":1792},[1053,5871,4088],{"class":1068},[1053,5873,5874],{"class":1055,"line":1797},[1053,5875,1118],{"emptyLinePlaceholder":1117},[1053,5877,5878,5881,5883,5885,5887,5889,5891,5894,5896,5898,5900,5902,5904,5907],{"class":1055,"line":1802},[1053,5879,5880],{"class":1074},"    container",[1053,5882,1097],{"class":1068},[1053,5884,2611],{"class":1074},[1053,5886,1386],{"class":1074},[1053,5888,5748],{"class":1074},[1053,5890,1084],{"class":1068},[1053,5892,5893],{"class":1087},"GenericContainer",[1053,5895,1090],{"class":1068},[1053,5897,1399],{"class":1074},[1053,5899,1097],{"class":1068},[1053,5901,5748],{"class":1105},[1053,5903,1084],{"class":1068},[1053,5905,5906],{"class":1105},"GenericContainerRequest",[1053,5908,1153],{"class":1068},[1053,5910,5911,5914,5916,5919],{"class":1055,"line":1845},[1053,5912,5913],{"class":1159},"        ContainerRequest",[1053,5915,1163],{"class":1068},[1053,5917,5918],{"class":1074}," req",[1053,5920,5768],{"class":1068},[1053,5922,5923,5926,5928,5931],{"class":1055,"line":1850},[1053,5924,5925],{"class":1159},"        Started",[1053,5927,1163],{"class":1068},[1053,5929,5930],{"class":1064},"          true",[1053,5932,5768],{"class":1068},[1053,5934,5935],{"class":1055,"line":1884},[1053,5936,1269],{"class":1068},[1053,5938,5939,5941,5943,5945,5947],{"class":1055,"line":1901},[1053,5940,4038],{"class":3772},[1053,5942,2611],{"class":1074},[1053,5944,4050],{"class":3772},[1053,5946,2115],{"class":1064},[1053,5948,1614],{"class":1068},[1053,5950,5951,5953,5955,5957,5959,5961],{"class":1055,"line":1932},[1053,5952,4059],{"class":1074},[1053,5954,1084],{"class":1068},[1053,5956,4786],{"class":1087},[1053,5958,1090],{"class":1068},[1053,5960,4791],{"class":1074},[1053,5962,1361],{"class":1068},[1053,5964,5965],{"class":1055,"line":1938},[1053,5966,4088],{"class":1068},[1053,5968,5969,5971,5974,5976,5979,5981,5983],{"class":1055,"line":1943},[1053,5970,4227],{"class":3772},[1053,5972,5973],{"class":1074}," container",[1053,5975,1084],{"class":1068},[1053,5977,5978],{"class":1087},"Terminate",[1053,5980,1090],{"class":1068},[1053,5982,1399],{"class":1074},[1053,5984,1361],{"class":1068},[1053,5986,5987],{"class":1055,"line":1948},[1053,5988,1118],{"emptyLinePlaceholder":1117},[1053,5990,5991],{"class":1055,"line":1953},[1053,5992,5993],{"class":1058},"    // Get connection details\n",[1053,5995,5996,5999,6001,6003,6005,6007,6009,6012,6014,6016],{"class":1055,"line":1967},[1053,5997,5998],{"class":1074},"    host",[1053,6000,1097],{"class":1068},[1053,6002,1383],{"class":1074},[1053,6004,1386],{"class":1074},[1053,6006,5973],{"class":1074},[1053,6008,1084],{"class":1068},[1053,6010,6011],{"class":1087},"Host",[1053,6013,1090],{"class":1068},[1053,6015,1399],{"class":1074},[1053,6017,1361],{"class":1068},[1053,6019,6020,6023,6025,6027,6029,6031,6033,6036,6038,6040,6042,6045],{"class":1055,"line":1999},[1053,6021,6022],{"class":1074},"    port",[1053,6024,1097],{"class":1068},[1053,6026,1383],{"class":1074},[1053,6028,1386],{"class":1074},[1053,6030,5973],{"class":1074},[1053,6032,1084],{"class":1068},[1053,6034,6035],{"class":1087},"MappedPort",[1053,6037,1090],{"class":1068},[1053,6039,1399],{"class":1074},[1053,6041,1097],{"class":1068},[1053,6043,6044],{"class":1093}," \"5432\"",[1053,6046,1361],{"class":1068},[1053,6048,6049],{"class":1055,"line":2018},[1053,6050,1118],{"emptyLinePlaceholder":1117},[1053,6052,6053],{"class":1055,"line":2023},[1053,6054,6055],{"class":1058},"    // Connect and test\n",[1053,6057,6058,6061,6063,6066,6068,6071,6073,6076,6078,6081,6083,6086,6088,6091,6093,6096,6098,6101],{"class":1055,"line":2029},[1053,6059,6060],{"class":1074},"    dsn",[1053,6062,1386],{"class":1074},[1053,6064,6065],{"class":1074}," fmt",[1053,6067,1084],{"class":1068},[1053,6069,6070],{"class":1087},"Sprintf",[1053,6072,1090],{"class":1068},[1053,6074,6075],{"class":1093},"\"host=",[1053,6077,2280],{"class":2273},[1053,6079,6080],{"class":1093}," port=",[1053,6082,2280],{"class":2273},[1053,6084,6085],{"class":1093}," user=test password=test dbname=testdb sslmode=disable\"",[1053,6087,1097],{"class":1068},[1053,6089,6090],{"class":1074}," host",[1053,6092,1097],{"class":1068},[1053,6094,6095],{"class":1074}," port",[1053,6097,1084],{"class":1068},[1053,6099,6100],{"class":1087},"Port",[1053,6102,2072],{"class":1068},[1053,6104,6105,6107,6109,6111,6113,6115,6117,6119,6121,6123,6125,6128],{"class":1055,"line":2075},[1053,6106,1970],{"class":1074},[1053,6108,1097],{"class":1068},[1053,6110,2611],{"class":1074},[1053,6112,1386],{"class":1074},[1053,6114,1979],{"class":1074},[1053,6116,1084],{"class":1068},[1053,6118,1984],{"class":1087},[1053,6120,1090],{"class":1068},[1053,6122,1989],{"class":1093},[1053,6124,1097],{"class":1068},[1053,6126,6127],{"class":1074}," dsn",[1053,6129,1361],{"class":1068},[1053,6131,6132,6134,6136,6138,6140],{"class":1055,"line":2080},[1053,6133,4038],{"class":3772},[1053,6135,2611],{"class":1074},[1053,6137,4050],{"class":3772},[1053,6139,2115],{"class":1064},[1053,6141,1614],{"class":1068},[1053,6143,6144,6146,6148,6150,6152,6154],{"class":1055,"line":2086},[1053,6145,4059],{"class":1074},[1053,6147,1084],{"class":1068},[1053,6149,4786],{"class":1087},[1053,6151,1090],{"class":1068},[1053,6153,4791],{"class":1074},[1053,6155,1361],{"class":1068},[1053,6157,6158],{"class":1055,"line":2120},[1053,6159,4088],{"class":1068},[1053,6161,6162],{"class":1055,"line":2169},[1053,6163,1118],{"emptyLinePlaceholder":1117},[1053,6165,6166],{"class":1055,"line":2203},[1053,6167,6168],{"class":1058},"    // Create table\n",[1053,6170,6171,6173,6175,6178,6180,6182,6184],{"class":1055,"line":2252},[1053,6172,1970],{"class":1074},[1053,6174,1084],{"class":1068},[1053,6176,6177],{"class":1087},"ExecContext",[1053,6179,1090],{"class":1068},[1053,6181,1399],{"class":1074},[1053,6183,1097],{"class":1068},[1053,6185,6186],{"class":1093}," `CREATE TABLE users (\n",[1053,6188,6189],{"class":1055,"line":2257},[1053,6190,6191],{"class":1093},"        id SERIAL PRIMARY KEY,\n",[1053,6193,6194],{"class":1055,"line":2341},[1053,6195,6196],{"class":1093},"        email TEXT NOT NULL UNIQUE,\n",[1053,6198,6200],{"class":1055,"line":6199},52,[1053,6201,6202],{"class":1093},"        name TEXT,\n",[1053,6204,6206],{"class":1055,"line":6205},53,[1053,6207,6208],{"class":1093},"        age INTEGER\n",[1053,6210,6212,6215],{"class":1055,"line":6211},54,[1053,6213,6214],{"class":1093},"    )`",[1053,6216,1361],{"class":1068},[1053,6218,6220],{"class":1055,"line":6219},55,[1053,6221,1118],{"emptyLinePlaceholder":1117},[1053,6223,6225],{"class":1055,"line":6224},56,[1053,6226,6227],{"class":1058},"    // Test executor\n",[1053,6229,6231,6233,6235,6237,6239,6241,6243,6245,6247,6249,6251,6253,6255,6257,6259,6261,6263,6265],{"class":1055,"line":6230},57,[1053,6232,2032],{"class":1074},[1053,6234,1097],{"class":1068},[1053,6236,1383],{"class":1074},[1053,6238,1386],{"class":1074},[1053,6240,1081],{"class":1074},[1053,6242,1084],{"class":1068},[1053,6244,600],{"class":1087},[1053,6246,1412],{"class":1068},[1053,6248,2049],{"class":1105},[1053,6250,2052],{"class":1068},[1053,6252,2055],{"class":1074},[1053,6254,1097],{"class":1068},[1053,6256,2060],{"class":1093},[1053,6258,1097],{"class":1068},[1053,6260,2065],{"class":1074},[1053,6262,1084],{"class":1068},[1053,6264,600],{"class":1087},[1053,6266,2072],{"class":1068},[1053,6268,6270],{"class":1055,"line":6269},58,[1053,6271,1118],{"emptyLinePlaceholder":1117},[1053,6273,6275,6278,6280],{"class":1055,"line":6274},59,[1053,6276,6277],{"class":1074},"    age",[1053,6279,1386],{"class":1074},[1053,6281,6282],{"class":1259}," 25\n",[1053,6284,6286,6288,6290,6293,6295,6297,6299,6301,6304,6306,6309,6311,6314,6316,6319,6321,6323,6326],{"class":1055,"line":6285},60,[1053,6287,2123],{"class":1074},[1053,6289,1386],{"class":1074},[1053,6291,6292],{"class":3772}," &",[1053,6294,2049],{"class":1105},[1053,6296,1424],{"class":1068},[1053,6298,2856],{"class":1159},[1053,6300,1163],{"class":1068},[1053,6302,6303],{"class":1093}," \"test@example.com\"",[1053,6305,1097],{"class":1068},[1053,6307,6308],{"class":1159}," Name",[1053,6310,1163],{"class":1068},[1053,6312,6313],{"class":1093}," \"Test\"",[1053,6315,1097],{"class":1068},[1053,6317,6318],{"class":1159}," Age",[1053,6320,1163],{"class":1068},[1053,6322,6292],{"class":3772},[1053,6324,6325],{"class":1074},"age",[1053,6327,1663],{"class":1068},[1053,6329,6331,6334,6336,6338,6340,6342,6344,6347,6349,6351,6353,6355],{"class":1055,"line":6330},61,[1053,6332,6333],{"class":1074},"    inserted",[1053,6335,1097],{"class":1068},[1053,6337,2611],{"class":1074},[1053,6339,1386],{"class":1074},[1053,6341,1389],{"class":1074},[1053,6343,1084],{"class":1068},[1053,6345,6346],{"class":1087},"ExecInsert",[1053,6348,1090],{"class":1068},[1053,6350,1399],{"class":1074},[1053,6352,1097],{"class":1068},[1053,6354,2316],{"class":1074},[1053,6356,1361],{"class":1068},[1053,6358,6360,6362,6364,6366,6368],{"class":1055,"line":6359},62,[1053,6361,4038],{"class":3772},[1053,6363,2611],{"class":1074},[1053,6365,4050],{"class":3772},[1053,6367,2115],{"class":1064},[1053,6369,1614],{"class":1068},[1053,6371,6373,6375,6377,6379,6381,6383],{"class":1055,"line":6372},63,[1053,6374,4059],{"class":1074},[1053,6376,1084],{"class":1068},[1053,6378,4786],{"class":1087},[1053,6380,1090],{"class":1068},[1053,6382,4791],{"class":1074},[1053,6384,1361],{"class":1068},[1053,6386,6388],{"class":1055,"line":6387},64,[1053,6389,4088],{"class":1068},[1053,6391,6393],{"class":1055,"line":6392},65,[1053,6394,1118],{"emptyLinePlaceholder":1117},[1053,6396,6398,6400,6403,6405,6408,6410,6413],{"class":1055,"line":6397},66,[1053,6399,4038],{"class":3772},[1053,6401,6402],{"class":1074}," inserted",[1053,6404,1084],{"class":1068},[1053,6406,6407],{"class":1074},"ID",[1053,6409,5200],{"class":3772},[1053,6411,6412],{"class":1259}," 0",[1053,6414,1614],{"class":1068},[1053,6416,6418,6420,6422,6424,6426,6429],{"class":1055,"line":6417},67,[1053,6419,4059],{"class":1074},[1053,6421,1084],{"class":1068},[1053,6423,4363],{"class":1087},[1053,6425,1090],{"class":1068},[1053,6427,6428],{"class":1093},"\"expected non-zero ID\"",[1053,6430,1361],{"class":1068},[1053,6432,6434],{"class":1055,"line":6433},68,[1053,6435,4088],{"class":1068},[1053,6437,6439],{"class":1055,"line":6438},69,[1053,6440,1663],{"class":1068},[962,6442,6443],{},"Run integration tests:",[1044,6445,6447],{"className":1497,"code":6446,"language":1499,"meta":34,"style":34},"go test -tags=integration ./testing/integration/...\n",[1050,6448,6449],{"__ignoreMap":34},[1053,6450,6451,6453,6456,6459],{"class":1055,"line":9},[1053,6452,1048],{"class":1087},[1053,6454,6455],{"class":1093}," test",[1053,6457,6458],{"class":1064}," -tags=integration",[1053,6460,6461],{"class":1093}," ./testing/integration/...\n",[1036,6463,499],{"id":6464},"benchmarking",[962,6466,6467,6468,6471],{},"The ",[1050,6469,6470],{},"testing/benchmarks"," package provides performance benchmarks:",[1044,6473,6475],{"className":1046,"code":6474,"language":1048,"meta":34,"style":34},"func BenchmarkQueryBuilding(b *testing.B) {\n    exec, _ := edamame.New[User](nil, \"users\", postgres.New())\n\n    var QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all\", edamame.QuerySpec{})\n\n    b.ResetTimer()\n    b.ReportAllocs()\n\n    for i := 0; i \u003C b.N; i++ {\n        _, _ = exec.Query(QueryAll)\n    }\n}\n",[1050,6476,6477,6502,6540,6544,6576,6580,6592,6603,6607,6642,6666,6670],{"__ignoreMap":34},[1053,6478,6479,6481,6484,6486,6489,6491,6493,6495,6498,6500],{"class":1055,"line":9},[1053,6480,1956],{"class":1064},[1053,6482,6483],{"class":1087}," BenchmarkQueryBuilding",[1053,6485,1090],{"class":1068},[1053,6487,6488],{"class":1572},"b",[1053,6490,3773],{"class":3772},[1053,6492,3288],{"class":1105},[1053,6494,1084],{"class":1068},[1053,6496,6497],{"class":1105},"B",[1053,6499,3783],{"class":1068},[1053,6501,1614],{"class":1068},[1053,6503,6504,6506,6508,6510,6512,6514,6516,6518,6520,6522,6524,6526,6528,6530,6532,6534,6536,6538],{"class":1055,"line":19},[1053,6505,2032],{"class":1074},[1053,6507,1097],{"class":1068},[1053,6509,1383],{"class":1074},[1053,6511,1386],{"class":1074},[1053,6513,1081],{"class":1074},[1053,6515,1084],{"class":1068},[1053,6517,600],{"class":1087},[1053,6519,1412],{"class":1068},[1053,6521,2049],{"class":1105},[1053,6523,2052],{"class":1068},[1053,6525,3831],{"class":1064},[1053,6527,1097],{"class":1068},[1053,6529,2060],{"class":1093},[1053,6531,1097],{"class":1068},[1053,6533,2065],{"class":1074},[1053,6535,1084],{"class":1068},[1053,6537,600],{"class":1087},[1053,6539,2072],{"class":1068},[1053,6541,6542],{"class":1055,"line":40},[1053,6543,1118],{"emptyLinePlaceholder":1117},[1053,6545,6546,6548,6550,6552,6554,6556,6558,6560,6562,6564,6566,6568,6570,6572,6574],{"class":1055,"line":674},[1053,6547,3859],{"class":1064},[1053,6549,2110],{"class":1074},[1053,6551,1078],{"class":1074},[1053,6553,1081],{"class":1074},[1053,6555,1084],{"class":1068},[1053,6557,609],{"class":1087},[1053,6559,1090],{"class":1068},[1053,6561,1094],{"class":1093},[1053,6563,1097],{"class":1068},[1053,6565,4829],{"class":1093},[1053,6567,1097],{"class":1068},[1053,6569,1081],{"class":1105},[1053,6571,1084],{"class":1068},[1053,6573,144],{"class":1105},[1053,6575,1112],{"class":1068},[1053,6577,6578],{"class":1055,"line":1121},[1053,6579,1118],{"emptyLinePlaceholder":1117},[1053,6581,6582,6585,6587,6590],{"class":1055,"line":1156},[1053,6583,6584],{"class":1074},"    b",[1053,6586,1084],{"class":1068},[1053,6588,6589],{"class":1087},"ResetTimer",[1053,6591,2015],{"class":1068},[1053,6593,6594,6596,6598,6601],{"class":1055,"line":1208},[1053,6595,6584],{"class":1074},[1053,6597,1084],{"class":1068},[1053,6599,6600],{"class":1087},"ReportAllocs",[1053,6602,2015],{"class":1068},[1053,6604,6605],{"class":1055,"line":1246},[1053,6606,1118],{"emptyLinePlaceholder":1117},[1053,6608,6609,6612,6615,6617,6619,6622,6624,6627,6630,6632,6635,6637,6640],{"class":1055,"line":1266},[1053,6610,6611],{"class":3772},"    for",[1053,6613,6614],{"class":1074}," i",[1053,6616,1386],{"class":1074},[1053,6618,6412],{"class":1259},[1053,6620,6621],{"class":1068},";",[1053,6623,6614],{"class":1074},[1053,6625,6626],{"class":3772}," \u003C",[1053,6628,6629],{"class":1074}," b",[1053,6631,1084],{"class":1068},[1053,6633,6634],{"class":1074},"N",[1053,6636,6621],{"class":1068},[1053,6638,6639],{"class":1074}," i++",[1053,6641,1614],{"class":1068},[1053,6643,6644,6647,6649,6651,6653,6655,6657,6659,6661,6664],{"class":1055,"line":1272},[1053,6645,6646],{"class":1074},"        _",[1053,6648,1097],{"class":1068},[1053,6650,1383],{"class":1074},[1053,6652,1078],{"class":1074},[1053,6654,1389],{"class":1074},[1053,6656,1084],{"class":1068},[1053,6658,671],{"class":1087},[1053,6660,1090],{"class":1068},[1053,6662,6663],{"class":1074},"QueryAll",[1053,6665,1361],{"class":1068},[1053,6667,6668],{"class":1055,"line":1277},[1053,6669,4088],{"class":1068},[1053,6671,6672],{"class":1055,"line":1311},[1053,6673,1663],{"class":1068},[962,6675,6676],{},"Run benchmarks:",[1044,6678,6680],{"className":1497,"code":6679,"language":1499,"meta":34,"style":34},"go test ./testing/benchmarks/... -bench=. -benchmem\n",[1050,6681,6682],{"__ignoreMap":34},[1053,6683,6684,6686,6688,6691,6694],{"class":1055,"line":9},[1053,6685,1048],{"class":1087},[1053,6687,6455],{"class":1093},[1053,6689,6690],{"class":1093}," ./testing/benchmarks/...",[1053,6692,6693],{"class":1064}," -bench=.",[1053,6695,6696],{"class":1064}," -benchmem\n",[1036,6698,504],{"id":6699},"testing-events",[962,6701,6702],{},"Verify capitan events are emitted correctly:",[1044,6704,6706],{"className":1046,"code":6705,"language":1048,"meta":34,"style":34},"func TestExecutorEmitsCreatedEvent(t *testing.T) {\n    c := capitan.New(capitan.WithSyncMode())\n    defer c.Shutdown()\n\n    capture := edamametesting.NewExecutorEventCapture()\n    c.Hook(edamame.ExecutorCreated, capture.Handler())\n\n    _, _ = edamame.New[User](nil, \"users\", postgres.New())\n\n    if capture.Count() != 1 {\n        t.Errorf(\"expected 1 executor created event, got %d\", capture.Count())\n    }\n\n    tables := capture.Tables()\n    if tables[0].Table != \"users\" {\n        t.Errorf(\"expected table 'users', got %q\", tables[0].Table)\n    }\n}\n",[1050,6707,6708,6731,6753,6765,6769,6783,6809,6813,6851,6855,6873,6900,6904,6908,6922,6942,6972,6976],{"__ignoreMap":34},[1053,6709,6710,6712,6715,6717,6719,6721,6723,6725,6727,6729],{"class":1055,"line":9},[1053,6711,1956],{"class":1064},[1053,6713,6714],{"class":1087}," TestExecutorEmitsCreatedEvent",[1053,6716,1090],{"class":1068},[1053,6718,3769],{"class":1572},[1053,6720,3773],{"class":3772},[1053,6722,3288],{"class":1105},[1053,6724,1084],{"class":1068},[1053,6726,3780],{"class":1105},[1053,6728,3783],{"class":1068},[1053,6730,1614],{"class":1068},[1053,6732,6733,6735,6737,6739,6741,6743,6745,6747,6749,6751],{"class":1055,"line":19},[1053,6734,4201],{"class":1074},[1053,6736,1386],{"class":1074},[1053,6738,4206],{"class":1074},[1053,6740,1084],{"class":1068},[1053,6742,600],{"class":1087},[1053,6744,1090],{"class":1068},[1053,6746,4215],{"class":1074},[1053,6748,1084],{"class":1068},[1053,6750,4220],{"class":1087},[1053,6752,2072],{"class":1068},[1053,6754,6755,6757,6759,6761,6763],{"class":1055,"line":40},[1053,6756,4227],{"class":3772},[1053,6758,4230],{"class":1074},[1053,6760,1084],{"class":1068},[1053,6762,4235],{"class":1087},[1053,6764,2015],{"class":1068},[1053,6766,6767],{"class":1055,"line":674},[1053,6768,1118],{"emptyLinePlaceholder":1117},[1053,6770,6771,6773,6775,6777,6779,6781],{"class":1055,"line":1121},[1053,6772,3790],{"class":1074},[1053,6774,1386],{"class":1074},[1053,6776,3795],{"class":1074},[1053,6778,1084],{"class":1068},[1053,6780,4254],{"class":1087},[1053,6782,2015],{"class":1068},[1053,6784,6785,6787,6789,6791,6793,6795,6797,6799,6801,6803,6805,6807],{"class":1055,"line":1156},[1053,6786,4201],{"class":1074},[1053,6788,1084],{"class":1068},[1053,6790,4265],{"class":1087},[1053,6792,1090],{"class":1068},[1053,6794,960],{"class":1074},[1053,6796,1084],{"class":1068},[1053,6798,4274],{"class":1074},[1053,6800,1097],{"class":1068},[1053,6802,4041],{"class":1074},[1053,6804,1084],{"class":1068},[1053,6806,4283],{"class":1087},[1053,6808,2072],{"class":1068},[1053,6810,6811],{"class":1055,"line":1208},[1053,6812,1118],{"emptyLinePlaceholder":1117},[1053,6814,6815,6817,6819,6821,6823,6825,6827,6829,6831,6833,6835,6837,6839,6841,6843,6845,6847,6849],{"class":1055,"line":1246},[1053,6816,1573],{"class":1074},[1053,6818,1097],{"class":1068},[1053,6820,1383],{"class":1074},[1053,6822,1078],{"class":1074},[1053,6824,1081],{"class":1074},[1053,6826,1084],{"class":1068},[1053,6828,600],{"class":1087},[1053,6830,1412],{"class":1068},[1053,6832,2049],{"class":1105},[1053,6834,2052],{"class":1068},[1053,6836,3831],{"class":1064},[1053,6838,1097],{"class":1068},[1053,6840,2060],{"class":1093},[1053,6842,1097],{"class":1068},[1053,6844,2065],{"class":1074},[1053,6846,1084],{"class":1068},[1053,6848,600],{"class":1087},[1053,6850,2072],{"class":1068},[1053,6852,6853],{"class":1055,"line":1266},[1053,6854,1118],{"emptyLinePlaceholder":1117},[1053,6856,6857,6859,6861,6863,6865,6867,6869,6871],{"class":1055,"line":1272},[1053,6858,4038],{"class":3772},[1053,6860,4041],{"class":1074},[1053,6862,1084],{"class":1068},[1053,6864,403],{"class":1087},[1053,6866,1962],{"class":1068},[1053,6868,4050],{"class":3772},[1053,6870,2164],{"class":1259},[1053,6872,1614],{"class":1068},[1053,6874,6875,6877,6879,6881,6883,6886,6888,6890,6892,6894,6896,6898],{"class":1055,"line":1277},[1053,6876,4059],{"class":1074},[1053,6878,1084],{"class":1068},[1053,6880,4064],{"class":1087},[1053,6882,1090],{"class":1068},[1053,6884,6885],{"class":1093},"\"expected 1 executor created event, got ",[1053,6887,2274],{"class":2273},[1053,6889,2270],{"class":1093},[1053,6891,1097],{"class":1068},[1053,6893,4041],{"class":1074},[1053,6895,1084],{"class":1068},[1053,6897,403],{"class":1087},[1053,6899,2072],{"class":1068},[1053,6901,6902],{"class":1055,"line":1311},[1053,6903,4088],{"class":1068},[1053,6905,6906],{"class":1055,"line":1353},[1053,6907,1118],{"emptyLinePlaceholder":1117},[1053,6909,6910,6912,6914,6916,6918,6920],{"class":1055,"line":1358},[1053,6911,4383],{"class":1074},[1053,6913,1386],{"class":1074},[1053,6915,4041],{"class":1074},[1053,6917,1084],{"class":1068},[1053,6919,4392],{"class":1087},[1053,6921,2015],{"class":1068},[1053,6923,6924,6926,6928,6930,6932,6934,6936,6938,6940],{"class":1055,"line":1364},[1053,6925,4038],{"class":3772},[1053,6927,4401],{"class":1074},[1053,6929,1412],{"class":1068},[1053,6931,4406],{"class":1259},[1053,6933,4409],{"class":1068},[1053,6935,4412],{"class":1074},[1053,6937,4050],{"class":3772},[1053,6939,2060],{"class":1093},[1053,6941,1614],{"class":1068},[1053,6943,6944,6946,6948,6950,6952,6954,6956,6958,6960,6962,6964,6966,6968,6970],{"class":1055,"line":1369},[1053,6945,4059],{"class":1074},[1053,6947,1084],{"class":1068},[1053,6949,4064],{"class":1087},[1053,6951,1090],{"class":1068},[1053,6953,4431],{"class":1093},[1053,6955,4142],{"class":2273},[1053,6957,2270],{"class":1093},[1053,6959,1097],{"class":1068},[1053,6961,4401],{"class":1074},[1053,6963,1412],{"class":1068},[1053,6965,4406],{"class":1259},[1053,6967,4409],{"class":1068},[1053,6969,4412],{"class":1074},[1053,6971,1361],{"class":1068},[1053,6973,6974],{"class":1055,"line":1375},[1053,6975,4088],{"class":1068},[1053,6977,6978],{"class":1055,"line":1438},[1053,6979,1663],{"class":1068},[1036,6981,509],{"id":6982},"testing-transaction-behavior",[1044,6984,6986],{"className":1046,"code":6985,"language":1048,"meta":34,"style":34},"func TestTransaction(t *testing.T) {\n    // ... setup db and exec ...\n\n    var QueryAll = edamame.NewQueryStatement(\"query-all\", \"Query all\", edamame.QuerySpec{})\n    var SelectByID = edamame.NewSelectStatement(\"select-by-id\", \"Select by ID\", edamame.SelectSpec{\n        Where: []edamame.ConditionSpec{{Field: \"id\", Operator: \"=\", Param: \"id\"}},\n    })\n\n    tx, _ := db.BeginTxx(ctx, nil)\n\n    // Insert in transaction\n    user := &User{Email: \"tx@test.com\", Name: \"TxTest\"}\n    inserted, err := exec.ExecInsertTx(ctx, tx, user)\n    if err != nil {\n        tx.Rollback()\n        t.Fatal(err)\n    }\n\n    // Verify visible in transaction\n    users, _ := exec.ExecQueryTx(ctx, tx, QueryAll, nil)\n    if len(users) != 1 {\n        t.Error(\"expected 1 user in transaction\")\n    }\n\n    // Rollback\n    tx.Rollback()\n\n    // Verify not visible after rollback\n    users, _ = exec.ExecQuery(ctx, QueryAll, nil)\n    if len(users) != 0 {\n        t.Error(\"expected 0 users after rollback\")\n    }\n}\n",[1050,6987,6988,7011,7016,7020,7052,7085,7125,7129,7133,7162,7166,7171,7201,7233,7245,7257,7271,7275,7279,7284,7319,7337,7352,7356,7360,7365,7375,7379,7384,7414,7432,7447,7451],{"__ignoreMap":34},[1053,6989,6990,6992,6995,6997,6999,7001,7003,7005,7007,7009],{"class":1055,"line":9},[1053,6991,1956],{"class":1064},[1053,6993,6994],{"class":1087}," TestTransaction",[1053,6996,1090],{"class":1068},[1053,6998,3769],{"class":1572},[1053,7000,3773],{"class":3772},[1053,7002,3288],{"class":1105},[1053,7004,1084],{"class":1068},[1053,7006,3780],{"class":1105},[1053,7008,3783],{"class":1068},[1053,7010,1614],{"class":1068},[1053,7012,7013],{"class":1055,"line":19},[1053,7014,7015],{"class":1058},"    // ... setup db and exec ...\n",[1053,7017,7018],{"class":1055,"line":40},[1053,7019,1118],{"emptyLinePlaceholder":1117},[1053,7021,7022,7024,7026,7028,7030,7032,7034,7036,7038,7040,7042,7044,7046,7048,7050],{"class":1055,"line":674},[1053,7023,3859],{"class":1064},[1053,7025,2110],{"class":1074},[1053,7027,1078],{"class":1074},[1053,7029,1081],{"class":1074},[1053,7031,1084],{"class":1068},[1053,7033,609],{"class":1087},[1053,7035,1090],{"class":1068},[1053,7037,1094],{"class":1093},[1053,7039,1097],{"class":1068},[1053,7041,4829],{"class":1093},[1053,7043,1097],{"class":1068},[1053,7045,1081],{"class":1105},[1053,7047,1084],{"class":1068},[1053,7049,144],{"class":1105},[1053,7051,1112],{"class":1068},[1053,7053,7054,7056,7058,7060,7062,7064,7066,7068,7070,7072,7075,7077,7079,7081,7083],{"class":1055,"line":1121},[1053,7055,3859],{"class":1064},[1053,7057,1463],{"class":1074},[1053,7059,1078],{"class":1074},[1053,7061,1081],{"class":1074},[1053,7063,1084],{"class":1068},[1053,7065,614],{"class":1087},[1053,7067,1090],{"class":1068},[1053,7069,1293],{"class":1093},[1053,7071,1097],{"class":1068},[1053,7073,7074],{"class":1093}," \"Select by ID\"",[1053,7076,1097],{"class":1068},[1053,7078,1081],{"class":1105},[1053,7080,1084],{"class":1068},[1053,7082,149],{"class":1105},[1053,7084,1153],{"class":1068},[1053,7086,7087,7089,7091,7093,7095,7097,7099,7101,7103,7105,7107,7109,7111,7113,7115,7117,7119,7121,7123],{"class":1055,"line":1156},[1053,7088,1160],{"class":1159},[1053,7090,1163],{"class":1068},[1053,7092,1216],{"class":1068},[1053,7094,960],{"class":1105},[1053,7096,1084],{"class":1068},[1053,7098,831],{"class":1105},[1053,7100,1175],{"class":1068},[1053,7102,1178],{"class":1159},[1053,7104,1163],{"class":1068},[1053,7106,1332],{"class":1093},[1053,7108,1097],{"class":1068},[1053,7110,1188],{"class":1159},[1053,7112,1163],{"class":1068},[1053,7114,1193],{"class":1093},[1053,7116,1097],{"class":1068},[1053,7118,1198],{"class":1159},[1053,7120,1163],{"class":1068},[1053,7122,1332],{"class":1093},[1053,7124,1205],{"class":1068},[1053,7126,7127],{"class":1055,"line":1208},[1053,7128,1269],{"class":1068},[1053,7130,7131],{"class":1055,"line":1246},[1053,7132,1118],{"emptyLinePlaceholder":1117},[1053,7134,7135,7138,7140,7142,7144,7147,7149,7152,7154,7156,7158,7160],{"class":1055,"line":1266},[1053,7136,7137],{"class":1074},"    tx",[1053,7139,1097],{"class":1068},[1053,7141,1383],{"class":1074},[1053,7143,1386],{"class":1074},[1053,7145,7146],{"class":1074}," db",[1053,7148,1084],{"class":1068},[1053,7150,7151],{"class":1087},"BeginTxx",[1053,7153,1090],{"class":1068},[1053,7155,1399],{"class":1074},[1053,7157,1097],{"class":1068},[1053,7159,2115],{"class":1064},[1053,7161,1361],{"class":1068},[1053,7163,7164],{"class":1055,"line":1272},[1053,7165,1118],{"emptyLinePlaceholder":1117},[1053,7167,7168],{"class":1055,"line":1277},[1053,7169,7170],{"class":1058},"    // Insert in transaction\n",[1053,7172,7173,7175,7177,7179,7181,7183,7185,7187,7190,7192,7194,7196,7199],{"class":1055,"line":1311},[1053,7174,2123],{"class":1074},[1053,7176,1386],{"class":1074},[1053,7178,6292],{"class":3772},[1053,7180,2049],{"class":1105},[1053,7182,1424],{"class":1068},[1053,7184,2856],{"class":1159},[1053,7186,1163],{"class":1068},[1053,7188,7189],{"class":1093}," \"tx@test.com\"",[1053,7191,1097],{"class":1068},[1053,7193,6308],{"class":1159},[1053,7195,1163],{"class":1068},[1053,7197,7198],{"class":1093}," \"TxTest\"",[1053,7200,1663],{"class":1068},[1053,7202,7203,7205,7207,7209,7211,7213,7215,7218,7220,7222,7224,7227,7229,7231],{"class":1055,"line":1353},[1053,7204,6333],{"class":1074},[1053,7206,1097],{"class":1068},[1053,7208,2611],{"class":1074},[1053,7210,1386],{"class":1074},[1053,7212,1389],{"class":1074},[1053,7214,1084],{"class":1068},[1053,7216,7217],{"class":1087},"ExecInsertTx",[1053,7219,1090],{"class":1068},[1053,7221,1399],{"class":1074},[1053,7223,1097],{"class":1068},[1053,7225,7226],{"class":1074}," tx",[1053,7228,1097],{"class":1068},[1053,7230,2316],{"class":1074},[1053,7232,1361],{"class":1068},[1053,7234,7235,7237,7239,7241,7243],{"class":1055,"line":1358},[1053,7236,4038],{"class":3772},[1053,7238,2611],{"class":1074},[1053,7240,4050],{"class":3772},[1053,7242,2115],{"class":1064},[1053,7244,1614],{"class":1068},[1053,7246,7247,7250,7252,7255],{"class":1055,"line":1364},[1053,7248,7249],{"class":1074},"        tx",[1053,7251,1084],{"class":1068},[1053,7253,7254],{"class":1087},"Rollback",[1053,7256,2015],{"class":1068},[1053,7258,7259,7261,7263,7265,7267,7269],{"class":1055,"line":1369},[1053,7260,4059],{"class":1074},[1053,7262,1084],{"class":1068},[1053,7264,4786],{"class":1087},[1053,7266,1090],{"class":1068},[1053,7268,4791],{"class":1074},[1053,7270,1361],{"class":1068},[1053,7272,7273],{"class":1055,"line":1375},[1053,7274,4088],{"class":1068},[1053,7276,7277],{"class":1055,"line":1438},[1053,7278,1118],{"emptyLinePlaceholder":1117},[1053,7280,7281],{"class":1055,"line":1666},[1053,7282,7283],{"class":1058},"    // Verify visible in transaction\n",[1053,7285,7286,7288,7290,7292,7294,7296,7298,7301,7303,7305,7307,7309,7311,7313,7315,7317],{"class":1055,"line":1671},[1053,7287,2089],{"class":1074},[1053,7289,1097],{"class":1068},[1053,7291,1383],{"class":1074},[1053,7293,1386],{"class":1074},[1053,7295,1389],{"class":1074},[1053,7297,1084],{"class":1068},[1053,7299,7300],{"class":1087},"ExecQueryTx",[1053,7302,1090],{"class":1068},[1053,7304,1399],{"class":1074},[1053,7306,1097],{"class":1068},[1053,7308,7226],{"class":1074},[1053,7310,1097],{"class":1068},[1053,7312,2110],{"class":1074},[1053,7314,1097],{"class":1068},[1053,7316,2115],{"class":1064},[1053,7318,1361],{"class":1068},[1053,7320,7321,7323,7325,7327,7329,7331,7333,7335],{"class":1055,"line":1677},[1053,7322,4038],{"class":3772},[1053,7324,2306],{"class":2305},[1053,7326,1090],{"class":1068},[1053,7328,1378],{"class":1074},[1053,7330,3783],{"class":1068},[1053,7332,4050],{"class":3772},[1053,7334,2164],{"class":1259},[1053,7336,1614],{"class":1068},[1053,7338,7339,7341,7343,7345,7347,7350],{"class":1055,"line":1684},[1053,7340,4059],{"class":1074},[1053,7342,1084],{"class":1068},[1053,7344,4363],{"class":1087},[1053,7346,1090],{"class":1068},[1053,7348,7349],{"class":1093},"\"expected 1 user in transaction\"",[1053,7351,1361],{"class":1068},[1053,7353,7354],{"class":1055,"line":1715},[1053,7355,4088],{"class":1068},[1053,7357,7358],{"class":1055,"line":1720},[1053,7359,1118],{"emptyLinePlaceholder":1117},[1053,7361,7362],{"class":1055,"line":1751},[1053,7363,7364],{"class":1058},"    // Rollback\n",[1053,7366,7367,7369,7371,7373],{"class":1055,"line":1792},[1053,7368,7137],{"class":1074},[1053,7370,1084],{"class":1068},[1053,7372,7254],{"class":1087},[1053,7374,2015],{"class":1068},[1053,7376,7377],{"class":1055,"line":1797},[1053,7378,1118],{"emptyLinePlaceholder":1117},[1053,7380,7381],{"class":1055,"line":1802},[1053,7382,7383],{"class":1058},"    // Verify not visible after rollback\n",[1053,7385,7386,7388,7390,7392,7394,7396,7398,7400,7402,7404,7406,7408,7410,7412],{"class":1055,"line":1845},[1053,7387,2089],{"class":1074},[1053,7389,1097],{"class":1068},[1053,7391,1383],{"class":1074},[1053,7393,1078],{"class":1074},[1053,7395,1389],{"class":1074},[1053,7397,1084],{"class":1068},[1053,7399,1394],{"class":1087},[1053,7401,1090],{"class":1068},[1053,7403,1399],{"class":1074},[1053,7405,1097],{"class":1068},[1053,7407,2110],{"class":1074},[1053,7409,1097],{"class":1068},[1053,7411,2115],{"class":1064},[1053,7413,1361],{"class":1068},[1053,7415,7416,7418,7420,7422,7424,7426,7428,7430],{"class":1055,"line":1850},[1053,7417,4038],{"class":3772},[1053,7419,2306],{"class":2305},[1053,7421,1090],{"class":1068},[1053,7423,1378],{"class":1074},[1053,7425,3783],{"class":1068},[1053,7427,4050],{"class":3772},[1053,7429,6412],{"class":1259},[1053,7431,1614],{"class":1068},[1053,7433,7434,7436,7438,7440,7442,7445],{"class":1055,"line":1884},[1053,7435,4059],{"class":1074},[1053,7437,1084],{"class":1068},[1053,7439,4363],{"class":1087},[1053,7441,1090],{"class":1068},[1053,7443,7444],{"class":1093},"\"expected 0 users after rollback\"",[1053,7446,1361],{"class":1068},[1053,7448,7449],{"class":1055,"line":1901},[1053,7450,4088],{"class":1068},[1053,7452,7453],{"class":1055,"line":1932},[1053,7454,1663],{"class":1068},[1036,7456,514],{"id":7457},"async-event-testing",[962,7459,7460,7461,7464],{},"Use ",[1050,7462,7463],{},"WaitForCount"," for async event verification:",[1044,7466,7468],{"className":1046,"code":7467,"language":1048,"meta":34,"style":34},"func TestAsyncEvents(t *testing.T) {\n    c := capitan.New()  // async mode\n    defer c.Shutdown()\n\n    capture := edamametesting.NewExecutorEventCapture()\n    c.Hook(edamame.ExecutorCreated, capture.Handler())\n\n    _, _ = edamame.New[User](nil, \"users\", postgres.New())\n\n    // Wait for async event processing\n    if !capture.WaitForCount(1, 500*time.Millisecond) {\n        t.Error(\"timed out waiting for event\")\n    }\n}\n",[1050,7469,7470,7493,7510,7522,7526,7540,7566,7570,7608,7612,7617,7652,7667,7671],{"__ignoreMap":34},[1053,7471,7472,7474,7477,7479,7481,7483,7485,7487,7489,7491],{"class":1055,"line":9},[1053,7473,1956],{"class":1064},[1053,7475,7476],{"class":1087}," TestAsyncEvents",[1053,7478,1090],{"class":1068},[1053,7480,3769],{"class":1572},[1053,7482,3773],{"class":3772},[1053,7484,3288],{"class":1105},[1053,7486,1084],{"class":1068},[1053,7488,3780],{"class":1105},[1053,7490,3783],{"class":1068},[1053,7492,1614],{"class":1068},[1053,7494,7495,7497,7499,7501,7503,7505,7507],{"class":1055,"line":19},[1053,7496,4201],{"class":1074},[1053,7498,1386],{"class":1074},[1053,7500,4206],{"class":1074},[1053,7502,1084],{"class":1068},[1053,7504,600],{"class":1087},[1053,7506,1962],{"class":1068},[1053,7508,7509],{"class":1058},"  // async mode\n",[1053,7511,7512,7514,7516,7518,7520],{"class":1055,"line":40},[1053,7513,4227],{"class":3772},[1053,7515,4230],{"class":1074},[1053,7517,1084],{"class":1068},[1053,7519,4235],{"class":1087},[1053,7521,2015],{"class":1068},[1053,7523,7524],{"class":1055,"line":674},[1053,7525,1118],{"emptyLinePlaceholder":1117},[1053,7527,7528,7530,7532,7534,7536,7538],{"class":1055,"line":1121},[1053,7529,3790],{"class":1074},[1053,7531,1386],{"class":1074},[1053,7533,3795],{"class":1074},[1053,7535,1084],{"class":1068},[1053,7537,4254],{"class":1087},[1053,7539,2015],{"class":1068},[1053,7541,7542,7544,7546,7548,7550,7552,7554,7556,7558,7560,7562,7564],{"class":1055,"line":1156},[1053,7543,4201],{"class":1074},[1053,7545,1084],{"class":1068},[1053,7547,4265],{"class":1087},[1053,7549,1090],{"class":1068},[1053,7551,960],{"class":1074},[1053,7553,1084],{"class":1068},[1053,7555,4274],{"class":1074},[1053,7557,1097],{"class":1068},[1053,7559,4041],{"class":1074},[1053,7561,1084],{"class":1068},[1053,7563,4283],{"class":1087},[1053,7565,2072],{"class":1068},[1053,7567,7568],{"class":1055,"line":1208},[1053,7569,1118],{"emptyLinePlaceholder":1117},[1053,7571,7572,7574,7576,7578,7580,7582,7584,7586,7588,7590,7592,7594,7596,7598,7600,7602,7604,7606],{"class":1055,"line":1246},[1053,7573,1573],{"class":1074},[1053,7575,1097],{"class":1068},[1053,7577,1383],{"class":1074},[1053,7579,1078],{"class":1074},[1053,7581,1081],{"class":1074},[1053,7583,1084],{"class":1068},[1053,7585,600],{"class":1087},[1053,7587,1412],{"class":1068},[1053,7589,2049],{"class":1105},[1053,7591,2052],{"class":1068},[1053,7593,3831],{"class":1064},[1053,7595,1097],{"class":1068},[1053,7597,2060],{"class":1093},[1053,7599,1097],{"class":1068},[1053,7601,2065],{"class":1074},[1053,7603,1084],{"class":1068},[1053,7605,600],{"class":1087},[1053,7607,2072],{"class":1068},[1053,7609,7610],{"class":1055,"line":1266},[1053,7611,1118],{"emptyLinePlaceholder":1117},[1053,7613,7614],{"class":1055,"line":1272},[1053,7615,7616],{"class":1058},"    // Wait for async event processing\n",[1053,7618,7619,7621,7623,7626,7628,7630,7632,7635,7637,7640,7643,7645,7648,7650],{"class":1055,"line":1277},[1053,7620,4038],{"class":3772},[1053,7622,5527],{"class":3772},[1053,7624,7625],{"class":1074},"capture",[1053,7627,1084],{"class":1068},[1053,7629,7463],{"class":1087},[1053,7631,1090],{"class":1068},[1053,7633,7634],{"class":1259},"1",[1053,7636,1097],{"class":1068},[1053,7638,7639],{"class":1259}," 500",[1053,7641,7642],{"class":1074},"*time",[1053,7644,1084],{"class":1068},[1053,7646,7647],{"class":1074},"Millisecond",[1053,7649,3783],{"class":1068},[1053,7651,1614],{"class":1068},[1053,7653,7654,7656,7658,7660,7662,7665],{"class":1055,"line":1311},[1053,7655,4059],{"class":1074},[1053,7657,1084],{"class":1068},[1053,7659,4363],{"class":1087},[1053,7661,1090],{"class":1068},[1053,7663,7664],{"class":1093},"\"timed out waiting for event\"",[1053,7666,1361],{"class":1068},[1053,7668,7669],{"class":1055,"line":1353},[1053,7670,4088],{"class":1068},[1053,7672,7673],{"class":1055,"line":1358},[1053,7674,1663],{"class":1068},[2728,7676,7677],{},"html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}",{"title":34,"searchDepth":19,"depth":19,"links":7679},[7680,7685,7686,7687,7688,7689,7690,7691],{"id":3719,"depth":19,"text":465,"children":7681},[7682,7683,7684],{"id":3722,"depth":40,"text":469},{"id":4165,"depth":40,"text":474},{"id":4460,"depth":40,"text":479},{"id":4691,"depth":19,"text":484},{"id":5231,"depth":19,"text":489},{"id":5627,"depth":19,"text":494},{"id":6464,"depth":19,"text":499},{"id":6699,"depth":19,"text":504},{"id":6982,"depth":19,"text":509},{"id":7457,"depth":19,"text":514},{},"2025-12-17T00:00:00.000Z",null,{"title":456,"description":458},[456,7697,7698],"Helpers","Integration","2026-01-04T00:00:00.000Z","yerknnfp-QTE87jbSf06rC0Q9-LJaH98q4gSJipJJ5w",[7702,7703],{"title":120,"path":274,"stem":914,"description":276,"children":-1},{"title":519,"path":518,"stem":923,"description":521,"children":-1},1776270501873]